kennel 1.131.0 → 1.132.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/filter.rb +17 -2
 - data/lib/kennel/models/record.rb +2 -1
 - data/lib/kennel/parts_serializer.rb +9 -6
 - data/lib/kennel/syncer/matched_expected.rb +44 -0
 - data/lib/kennel/syncer/plan_displayer.rb +34 -0
 - data/lib/kennel/syncer/resolver.rb +81 -0
 - data/lib/kennel/syncer/types.rb +73 -0
 - data/lib/kennel/syncer.rb +14 -232
 - data/lib/kennel/version.rb +1 -1
 - data/lib/kennel.rb +2 -3
 - metadata +6 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 3d0c423b6658afcd8aa09a7e80169f85312540c03aad0bc8c3543e76c5a9420b
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 71ab44e7dc1ed76114f6a3aadfd70d79d905d673d0090f85f96e40e6c8967620
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: a4b6217e61f1282dd003bd2feea590b92f7dd8ddd2c7e9efc693ee58d0735baad7ea69d260ad376e581a7ff4b6364c51ddd2d42defd978928326183e5c8c5de3
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: ed60a47513aa4db4e1a1082cee541dbf9a1b345a512a058810eb4ad8763cc0505d3158b91f5b9df97e33a788cde84511fa0ad8b4de19aa14ca907f6944de8a2b
         
     | 
    
        data/lib/kennel/filter.rb
    CHANGED
    
    | 
         @@ -2,8 +2,6 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module Kennel
         
     | 
| 
       4 
4 
     | 
    
         
             
              class Filter
         
     | 
| 
       5 
     | 
    
         
            -
                attr_reader :project_filter, :tracking_id_filter
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
5 
     | 
    
         
             
                def initialize
         
     | 
| 
       8 
6 
     | 
    
         
             
                  # build early so we fail fast on invalid user input
         
     | 
| 
       9 
7 
     | 
    
         
             
                  @tracking_id_filter = build_tracking_id_filter
         
     | 
| 
         @@ -18,8 +16,25 @@ module Kennel 
     | 
|
| 
       18 
16 
     | 
    
         
             
                  filter_resources(parts, :tracking_id, tracking_id_filter, "resources", "TRACKING_ID")
         
     | 
| 
       19 
17 
     | 
    
         
             
                end
         
     | 
| 
       20 
18 
     | 
    
         | 
| 
      
 19 
     | 
    
         
            +
                def filtering?
         
     | 
| 
      
 20 
     | 
    
         
            +
                  !project_filter.nil?
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def matches_project_id?(project_id)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  !filtering? || project_filter.include?(project_id)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def matches_tracking_id?(tracking_id)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  return true unless filtering?
         
     | 
| 
      
 29 
     | 
    
         
            +
                  return tracking_id_filter.include?(tracking_id) if tracking_id_filter
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  project_filter.include?(tracking_id.split(":").first)
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
       21 
34 
     | 
    
         
             
                private
         
     | 
| 
       22 
35 
     | 
    
         | 
| 
      
 36 
     | 
    
         
            +
                attr_reader :project_filter, :tracking_id_filter
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
       23 
38 
     | 
    
         
             
                def build_project_filter
         
     | 
| 
       24 
39 
     | 
    
         
             
                  project_names = ENV["PROJECT"]&.split(",")&.sort&.uniq
         
     | 
| 
       25 
40 
     | 
    
         
             
                  tracking_project_names = tracking_id_filter&.map { |id| id.split(":", 2).first }&.sort&.uniq
         
     | 
    
        data/lib/kennel/models/record.rb
    CHANGED
    
    | 
         @@ -35,7 +35,8 @@ module Kennel 
     | 
|
| 
       35 
35 
     | 
    
         
             
                    :klass, :tracking_id # added by syncer.rb
         
     | 
| 
       36 
36 
     | 
    
         
             
                  ].freeze
         
     | 
| 
       37 
37 
     | 
    
         
             
                  ALLOWED_KENNEL_ID_CHARS = "a-zA-Z_\\d.-"
         
     | 
| 
       38 
     | 
    
         
            -
                   
     | 
| 
      
 38 
     | 
    
         
            +
                  ALLOWED_KENNEL_ID_SEGMENT = /[#{ALLOWED_KENNEL_ID_CHARS}]+/
         
     | 
| 
      
 39 
     | 
    
         
            +
                  ALLOWED_KENNEL_ID_FULL = "#{ALLOWED_KENNEL_ID_SEGMENT}:#{ALLOWED_KENNEL_ID_SEGMENT}".freeze
         
     | 
| 
       39 
40 
     | 
    
         
             
                  ALLOWED_KENNEL_ID_REGEX = /\A#{ALLOWED_KENNEL_ID_FULL}\z/
         
     | 
| 
       40 
41 
     | 
    
         | 
| 
       41 
42 
     | 
    
         
             
                  settings :id, :kennel_id
         
     | 
| 
         @@ -35,13 +35,16 @@ module Kennel 
     | 
|
| 
       35 
35 
     | 
    
         
             
                end
         
     | 
| 
       36 
36 
     | 
    
         | 
| 
       37 
37 
     | 
    
         
             
                def existing_files_and_folders
         
     | 
| 
       38 
     | 
    
         
            -
                   
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                   
     | 
| 
       41 
     | 
    
         
            -
                     
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 38 
     | 
    
         
            +
                  paths = Dir["generated/**/*"]
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  if filter.filtering?
         
     | 
| 
      
 41 
     | 
    
         
            +
                    paths.select! do |path|
         
     | 
| 
      
 42 
     | 
    
         
            +
                      tracking_id = path.split("/")[1..2].to_a.join(":")
         
     | 
| 
      
 43 
     | 
    
         
            +
                      filter.matches_tracking_id?(tracking_id)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
       44 
45 
     | 
    
         
             
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  paths
         
     | 
| 
       45 
48 
     | 
    
         
             
                end
         
     | 
| 
       46 
49 
     | 
    
         | 
| 
       47 
50 
     | 
    
         
             
                def path_for_tracking_id(tracking_id)
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Kennel
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Syncer
         
     | 
| 
      
 5 
     | 
    
         
            +
                module MatchedExpected
         
     | 
| 
      
 6 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 7 
     | 
    
         
            +
                    def partition(expected, actual)
         
     | 
| 
      
 8 
     | 
    
         
            +
                      lookup_map = matching_expected_lookup_map(expected)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      unmatched_expected = Set.new(expected) # for efficient deletion
         
     | 
| 
      
 10 
     | 
    
         
            +
                      unmatched_actual = []
         
     | 
| 
      
 11 
     | 
    
         
            +
                      matched = []
         
     | 
| 
      
 12 
     | 
    
         
            +
                      actual.each do |a|
         
     | 
| 
      
 13 
     | 
    
         
            +
                        e = matching_expected(a, lookup_map)
         
     | 
| 
      
 14 
     | 
    
         
            +
                        if e && unmatched_expected.delete?(e)
         
     | 
| 
      
 15 
     | 
    
         
            +
                          matched << [e, a]
         
     | 
| 
      
 16 
     | 
    
         
            +
                        else
         
     | 
| 
      
 17 
     | 
    
         
            +
                          unmatched_actual << a
         
     | 
| 
      
 18 
     | 
    
         
            +
                        end
         
     | 
| 
      
 19 
     | 
    
         
            +
                      end.compact
         
     | 
| 
      
 20 
     | 
    
         
            +
                      [matched, unmatched_expected.to_a, unmatched_actual]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    private
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    # index list by all the thing we look up by: tracking id and actual id
         
     | 
| 
      
 26 
     | 
    
         
            +
                    def matching_expected_lookup_map(expected)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      expected.each_with_object({}) do |e, all|
         
     | 
| 
      
 28 
     | 
    
         
            +
                        keys = [e.tracking_id]
         
     | 
| 
      
 29 
     | 
    
         
            +
                        keys << "#{e.class.api_resource}:#{e.id}" if e.id
         
     | 
| 
      
 30 
     | 
    
         
            +
                        keys.compact.each do |key|
         
     | 
| 
      
 31 
     | 
    
         
            +
                          raise "Lookup #{key} is duplicated" if all[key]
         
     | 
| 
      
 32 
     | 
    
         
            +
                          all[key] = e
         
     | 
| 
      
 33 
     | 
    
         
            +
                        end
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    def matching_expected(a, map)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      klass = a.fetch(:klass)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      map["#{klass.api_resource}:#{a.fetch(:id)}"] || map[a.fetch(:tracking_id)]
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Kennel
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Syncer
         
     | 
| 
      
 5 
     | 
    
         
            +
                class PlanDisplayer
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @attribute_differ = AttributeDiffer.new
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def display(internal_plan)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    Kennel.out.puts "Plan:"
         
     | 
| 
      
 12 
     | 
    
         
            +
                    if internal_plan.empty?
         
     | 
| 
      
 13 
     | 
    
         
            +
                      Kennel.out.puts Console.color(:green, "Nothing to do")
         
     | 
| 
      
 14 
     | 
    
         
            +
                    else
         
     | 
| 
      
 15 
     | 
    
         
            +
                      print_changes "Create", internal_plan.creates, :green
         
     | 
| 
      
 16 
     | 
    
         
            +
                      print_changes "Update", internal_plan.updates, :yellow
         
     | 
| 
      
 17 
     | 
    
         
            +
                      print_changes "Delete", internal_plan.deletes, :red
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  private
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def print_changes(step, list, color)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    return if list.empty?
         
     | 
| 
      
 25 
     | 
    
         
            +
                    list.each do |item|
         
     | 
| 
      
 26 
     | 
    
         
            +
                      Kennel.out.puts Console.color(color, "#{step} #{item.api_resource} #{item.tracking_id}")
         
     | 
| 
      
 27 
     | 
    
         
            +
                      if item.class::TYPE == :update
         
     | 
| 
      
 28 
     | 
    
         
            +
                        item.diff.each { |args| Kennel.out.puts @attribute_differ.format(*args) } # only for update
         
     | 
| 
      
 29 
     | 
    
         
            +
                      end
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative "../id_map"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Kennel
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Syncer
         
     | 
| 
      
 7 
     | 
    
         
            +
                class Resolver
         
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(expected:, filter:)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @id_map = IdMap.new
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @filter = filter
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    # mark everything as new
         
     | 
| 
      
 13 
     | 
    
         
            +
                    expected.each do |e|
         
     | 
| 
      
 14 
     | 
    
         
            +
                      id_map.set(e.class.api_resource, e.tracking_id, IdMap::NEW)
         
     | 
| 
      
 15 
     | 
    
         
            +
                      if e.class.api_resource == "synthetics/tests"
         
     | 
| 
      
 16 
     | 
    
         
            +
                        id_map.set(Kennel::Models::Monitor.api_resource, e.tracking_id, IdMap::NEW)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def add_actual(actual)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    # override resources that exist with their id
         
     | 
| 
      
 23 
     | 
    
         
            +
                    actual.each do |a|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      # ignore when not managed by kennel
         
     | 
| 
      
 25 
     | 
    
         
            +
                      next unless tracking_id = a.fetch(:tracking_id)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      # ignore when deleted from the codebase
         
     | 
| 
      
 28 
     | 
    
         
            +
                      # (when running with filters we cannot see the other resources in the codebase)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      api_resource = a.fetch(:klass).api_resource
         
     | 
| 
      
 30 
     | 
    
         
            +
                      next if !id_map.get(api_resource, tracking_id) && filter.matches_tracking_id?(tracking_id)
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                      id_map.set(api_resource, tracking_id, a.fetch(:id))
         
     | 
| 
      
 33 
     | 
    
         
            +
                      if a.fetch(:klass).api_resource == "synthetics/tests"
         
     | 
| 
      
 34 
     | 
    
         
            +
                        id_map.set(Kennel::Models::Monitor.api_resource, tracking_id, a.fetch(:monitor_id))
         
     | 
| 
      
 35 
     | 
    
         
            +
                      end
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def resolve_as_much_as_possible(expected)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    expected.each do |e|
         
     | 
| 
      
 41 
     | 
    
         
            +
                      e.resolve_linked_tracking_ids!(id_map, force: false)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  # loop over items until everything is resolved or crash when we get stuck
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
         
     | 
| 
      
 47 
     | 
    
         
            +
                  def each_resolved(list)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    list = list.dup
         
     | 
| 
      
 49 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 50 
     | 
    
         
            +
                      return if list.empty?
         
     | 
| 
      
 51 
     | 
    
         
            +
                      list.reject! do |item|
         
     | 
| 
      
 52 
     | 
    
         
            +
                        if resolved?(item.expected)
         
     | 
| 
      
 53 
     | 
    
         
            +
                          yield item
         
     | 
| 
      
 54 
     | 
    
         
            +
                          true
         
     | 
| 
      
 55 
     | 
    
         
            +
                        else
         
     | 
| 
      
 56 
     | 
    
         
            +
                          false
         
     | 
| 
      
 57 
     | 
    
         
            +
                        end
         
     | 
| 
      
 58 
     | 
    
         
            +
                      end ||
         
     | 
| 
      
 59 
     | 
    
         
            +
                        assert_resolved(list[0].expected) # resolve something or show a circular dependency error
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  private
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  attr_reader :id_map, :filter
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  # TODO: optimize by storing an instance variable if already resolved
         
     | 
| 
      
 68 
     | 
    
         
            +
                  def resolved?(e)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    assert_resolved e
         
     | 
| 
      
 70 
     | 
    
         
            +
                    true
         
     | 
| 
      
 71 
     | 
    
         
            +
                  rescue UnresolvableIdError
         
     | 
| 
      
 72 
     | 
    
         
            +
                    false
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                  # raises UnresolvableIdError when not resolved
         
     | 
| 
      
 76 
     | 
    
         
            +
                  def assert_resolved(e)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    e.resolve_linked_tracking_ids!(id_map, force: true)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,73 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Kennel
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Syncer
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Types
         
     | 
| 
      
 6 
     | 
    
         
            +
                  class PlannedChange
         
     | 
| 
      
 7 
     | 
    
         
            +
                    def initialize(klass, tracking_id)
         
     | 
| 
      
 8 
     | 
    
         
            +
                      @klass = klass
         
     | 
| 
      
 9 
     | 
    
         
            +
                      @tracking_id = tracking_id
         
     | 
| 
      
 10 
     | 
    
         
            +
                    end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    def api_resource
         
     | 
| 
      
 13 
     | 
    
         
            +
                      klass.api_resource
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    def url(id = nil)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      klass.url(id || self.id)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    def change(id = nil)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      Change.new(self.class::TYPE, api_resource, tracking_id, id)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    attr_reader :klass, :tracking_id
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  class PlannedCreate < PlannedChange
         
     | 
| 
      
 28 
     | 
    
         
            +
                    TYPE = :create
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    def initialize(expected)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      super(expected.class, expected.tracking_id)
         
     | 
| 
      
 32 
     | 
    
         
            +
                      @expected = expected
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    attr_reader :expected
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  class PlannedUpdate < PlannedChange
         
     | 
| 
      
 39 
     | 
    
         
            +
                    TYPE = :update
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    def initialize(expected, actual, diff)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      super(expected.class, expected.tracking_id)
         
     | 
| 
      
 43 
     | 
    
         
            +
                      @expected = expected
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @actual = actual
         
     | 
| 
      
 45 
     | 
    
         
            +
                      @diff = diff
         
     | 
| 
      
 46 
     | 
    
         
            +
                      @id = actual.fetch(:id)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    def change
         
     | 
| 
      
 50 
     | 
    
         
            +
                      super(id)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    attr_reader :expected, :actual, :diff, :id
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  class PlannedDelete < PlannedChange
         
     | 
| 
      
 57 
     | 
    
         
            +
                    TYPE = :delete
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    def initialize(actual)
         
     | 
| 
      
 60 
     | 
    
         
            +
                      super(actual.fetch(:klass), actual.fetch(:tracking_id))
         
     | 
| 
      
 61 
     | 
    
         
            +
                      @actual = actual
         
     | 
| 
      
 62 
     | 
    
         
            +
                      @id = actual.fetch(:id)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    def change
         
     | 
| 
      
 66 
     | 
    
         
            +
                      super(id)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    attr_reader :actual, :id
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/kennel/syncer.rb
    CHANGED
    
    | 
         @@ -1,5 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require_relative "./syncer/matched_expected"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "./syncer/plan_displayer"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative "./syncer/resolver"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative "./syncer/types"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
       3 
8 
     | 
    
         
             
            module Kennel
         
     | 
| 
       4 
9 
     | 
    
         
             
              class Syncer
         
     | 
| 
       5 
10 
     | 
    
         
             
                DELETE_ORDER = ["dashboard", "slo", "monitor", "synthetics/tests"].freeze # dashboards references monitors + slos, slos reference monitors
         
     | 
| 
         @@ -15,13 +20,12 @@ module Kennel 
     | 
|
| 
       15 
20 
     | 
    
         | 
| 
       16 
21 
     | 
    
         
             
                Change = Struct.new(:type, :api_resource, :tracking_id, :id)
         
     | 
| 
       17 
22 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
                def initialize(api, expected, actual, strict_imports: true 
     | 
| 
      
 23 
     | 
    
         
            +
                def initialize(api, expected, actual, filter:, strict_imports: true)
         
     | 
| 
       19 
24 
     | 
    
         
             
                  @api = api
         
     | 
| 
       20 
25 
     | 
    
         
             
                  @strict_imports = strict_imports
         
     | 
| 
       21 
     | 
    
         
            -
                  @ 
     | 
| 
       22 
     | 
    
         
            -
                  @tracking_id_filter = tracking_id_filter
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @filter = filter
         
     | 
| 
       23 
27 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
                  @resolver = Resolver.new(expected: expected,  
     | 
| 
      
 28 
     | 
    
         
            +
                  @resolver = Resolver.new(expected: expected, filter: filter)
         
     | 
| 
       25 
29 
     | 
    
         | 
| 
       26 
30 
     | 
    
         
             
                  internal_plan = calculate_changes(expected: expected, actual: actual)
         
     | 
| 
       27 
31 
     | 
    
         
             
                  validate_changes(internal_plan)
         
     | 
| 
         @@ -81,7 +85,7 @@ module Kennel 
     | 
|
| 
       81 
85 
     | 
    
         | 
| 
       82 
86 
     | 
    
         
             
                private
         
     | 
| 
       83 
87 
     | 
    
         | 
| 
       84 
     | 
    
         
            -
                attr_reader :resolver, :internal_plan
         
     | 
| 
      
 88 
     | 
    
         
            +
                attr_reader :filter, :resolver, :internal_plan
         
     | 
| 
       85 
89 
     | 
    
         | 
| 
       86 
90 
     | 
    
         
             
                def calculate_changes(expected:, actual:)
         
     | 
| 
       87 
91 
     | 
    
         
             
                  @warnings = []
         
     | 
| 
         @@ -101,7 +105,7 @@ module Kennel 
     | 
|
| 
       101 
105 
     | 
    
         
             
                      # Refuse to "adopt" existing items into kennel while running with a filter (i.e. on a branch).
         
     | 
| 
       102 
106 
     | 
    
         
             
                      # Without this, we'd adopt an item, then the next CI run would delete it
         
     | 
| 
       103 
107 
     | 
    
         
             
                      # (instead of "unadopting" it).
         
     | 
| 
       104 
     | 
    
         
            -
                      e.add_tracking_id unless  
     | 
| 
      
 108 
     | 
    
         
            +
                      e.add_tracking_id unless filter.filtering? && a.fetch(:tracking_id).nil?
         
     | 
| 
       105 
109 
     | 
    
         
             
                      id = a.fetch(:id)
         
     | 
| 
       106 
110 
     | 
    
         
             
                      diff = e.diff(a)
         
     | 
| 
       107 
111 
     | 
    
         
             
                      a[:id] = id
         
     | 
| 
         @@ -150,233 +154,11 @@ module Kennel 
     | 
|
| 
       150 
154 
     | 
    
         
             
                end
         
     | 
| 
       151 
155 
     | 
    
         | 
| 
       152 
156 
     | 
    
         
             
                def filter_actual!(actual)
         
     | 
| 
       153 
     | 
    
         
            -
                   
     | 
| 
       154 
     | 
    
         
            -
                    actual.select! do |a|
         
     | 
| 
       155 
     | 
    
         
            -
                      tracking_id = a.fetch(:tracking_id)
         
     | 
| 
       156 
     | 
    
         
            -
                      !tracking_id || @tracking_id_filter.include?(tracking_id)
         
     | 
| 
       157 
     | 
    
         
            -
                    end
         
     | 
| 
       158 
     | 
    
         
            -
                  elsif @project_filter
         
     | 
| 
       159 
     | 
    
         
            -
                    project_prefixes = @project_filter.map { |p| "#{p}:" }
         
     | 
| 
       160 
     | 
    
         
            -
                    actual.select! do |a|
         
     | 
| 
       161 
     | 
    
         
            -
                      tracking_id = a.fetch(:tracking_id)
         
     | 
| 
       162 
     | 
    
         
            -
                      !tracking_id || tracking_id.start_with?(*project_prefixes)
         
     | 
| 
       163 
     | 
    
         
            -
                    end
         
     | 
| 
       164 
     | 
    
         
            -
                  end
         
     | 
| 
       165 
     | 
    
         
            -
                end
         
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
                class PlanDisplayer
         
     | 
| 
       168 
     | 
    
         
            -
                  def initialize
         
     | 
| 
       169 
     | 
    
         
            -
                    @attribute_differ = AttributeDiffer.new
         
     | 
| 
       170 
     | 
    
         
            -
                  end
         
     | 
| 
       171 
     | 
    
         
            -
             
     | 
| 
       172 
     | 
    
         
            -
                  def display(internal_plan)
         
     | 
| 
       173 
     | 
    
         
            -
                    Kennel.out.puts "Plan:"
         
     | 
| 
       174 
     | 
    
         
            -
                    if internal_plan.empty?
         
     | 
| 
       175 
     | 
    
         
            -
                      Kennel.out.puts Console.color(:green, "Nothing to do")
         
     | 
| 
       176 
     | 
    
         
            -
                    else
         
     | 
| 
       177 
     | 
    
         
            -
                      print_changes "Create", internal_plan.creates, :green
         
     | 
| 
       178 
     | 
    
         
            -
                      print_changes "Update", internal_plan.updates, :yellow
         
     | 
| 
       179 
     | 
    
         
            -
                      print_changes "Delete", internal_plan.deletes, :red
         
     | 
| 
       180 
     | 
    
         
            -
                    end
         
     | 
| 
       181 
     | 
    
         
            -
                  end
         
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
                  private
         
     | 
| 
       184 
     | 
    
         
            -
             
     | 
| 
       185 
     | 
    
         
            -
                  def print_changes(step, list, color)
         
     | 
| 
       186 
     | 
    
         
            -
                    return if list.empty?
         
     | 
| 
       187 
     | 
    
         
            -
                    list.each do |item|
         
     | 
| 
       188 
     | 
    
         
            -
                      Kennel.out.puts Console.color(color, "#{step} #{item.api_resource} #{item.tracking_id}")
         
     | 
| 
       189 
     | 
    
         
            -
                      if item.class::TYPE == :update
         
     | 
| 
       190 
     | 
    
         
            -
                        item.diff.each { |args| Kennel.out.puts @attribute_differ.format(*args) } # only for update
         
     | 
| 
       191 
     | 
    
         
            -
                      end
         
     | 
| 
       192 
     | 
    
         
            -
                    end
         
     | 
| 
       193 
     | 
    
         
            -
                  end
         
     | 
| 
       194 
     | 
    
         
            -
                end
         
     | 
| 
       195 
     | 
    
         
            -
             
     | 
| 
       196 
     | 
    
         
            -
                class Resolver
         
     | 
| 
       197 
     | 
    
         
            -
                  def initialize(expected:, project_filter:, tracking_id_filter:)
         
     | 
| 
       198 
     | 
    
         
            -
                    @id_map = IdMap.new
         
     | 
| 
       199 
     | 
    
         
            -
                    @project_filter = project_filter
         
     | 
| 
       200 
     | 
    
         
            -
                    @tracking_id_filter = tracking_id_filter
         
     | 
| 
       201 
     | 
    
         
            -
             
     | 
| 
       202 
     | 
    
         
            -
                    # mark everything as new
         
     | 
| 
       203 
     | 
    
         
            -
                    expected.each do |e|
         
     | 
| 
       204 
     | 
    
         
            -
                      id_map.set(e.class.api_resource, e.tracking_id, IdMap::NEW)
         
     | 
| 
       205 
     | 
    
         
            -
                      if e.class.api_resource == "synthetics/tests"
         
     | 
| 
       206 
     | 
    
         
            -
                        id_map.set(Kennel::Models::Monitor.api_resource, e.tracking_id, IdMap::NEW)
         
     | 
| 
       207 
     | 
    
         
            -
                      end
         
     | 
| 
       208 
     | 
    
         
            -
                    end
         
     | 
| 
       209 
     | 
    
         
            -
                  end
         
     | 
| 
       210 
     | 
    
         
            -
             
     | 
| 
       211 
     | 
    
         
            -
                  def add_actual(actual)
         
     | 
| 
       212 
     | 
    
         
            -
                    # override resources that exist with their id
         
     | 
| 
       213 
     | 
    
         
            -
                    project_prefixes = project_filter&.map { |p| "#{p}:" }
         
     | 
| 
       214 
     | 
    
         
            -
             
     | 
| 
       215 
     | 
    
         
            -
                    actual.each do |a|
         
     | 
| 
       216 
     | 
    
         
            -
                      # ignore when not managed by kennel
         
     | 
| 
       217 
     | 
    
         
            -
                      next unless tracking_id = a.fetch(:tracking_id)
         
     | 
| 
       218 
     | 
    
         
            -
             
     | 
| 
       219 
     | 
    
         
            -
                      # ignore when deleted from the codebase
         
     | 
| 
       220 
     | 
    
         
            -
                      # (when running with filters we cannot see the other resources in the codebase)
         
     | 
| 
       221 
     | 
    
         
            -
                      api_resource = a.fetch(:klass).api_resource
         
     | 
| 
       222 
     | 
    
         
            -
                      next if
         
     | 
| 
       223 
     | 
    
         
            -
                        !id_map.get(api_resource, tracking_id) &&
         
     | 
| 
       224 
     | 
    
         
            -
                        (!project_prefixes || tracking_id.start_with?(*project_prefixes)) &&
         
     | 
| 
       225 
     | 
    
         
            -
                        (!tracking_id_filter || tracking_id_filter.include?(tracking_id))
         
     | 
| 
       226 
     | 
    
         
            -
             
     | 
| 
       227 
     | 
    
         
            -
                      id_map.set(api_resource, tracking_id, a.fetch(:id))
         
     | 
| 
       228 
     | 
    
         
            -
                      if a.fetch(:klass).api_resource == "synthetics/tests"
         
     | 
| 
       229 
     | 
    
         
            -
                        id_map.set(Kennel::Models::Monitor.api_resource, tracking_id, a.fetch(:monitor_id))
         
     | 
| 
       230 
     | 
    
         
            -
                      end
         
     | 
| 
       231 
     | 
    
         
            -
                    end
         
     | 
| 
       232 
     | 
    
         
            -
                  end
         
     | 
| 
       233 
     | 
    
         
            -
             
     | 
| 
       234 
     | 
    
         
            -
                  def resolve_as_much_as_possible(expected)
         
     | 
| 
       235 
     | 
    
         
            -
                    expected.each do |e|
         
     | 
| 
       236 
     | 
    
         
            -
                      e.resolve_linked_tracking_ids!(id_map, force: false)
         
     | 
| 
       237 
     | 
    
         
            -
                    end
         
     | 
| 
       238 
     | 
    
         
            -
                  end
         
     | 
| 
       239 
     | 
    
         
            -
             
     | 
| 
       240 
     | 
    
         
            -
                  # loop over items until everything is resolved or crash when we get stuck
         
     | 
| 
       241 
     | 
    
         
            -
                  # this solves cases like composite monitors depending on each other or monitor->monitor slo->slo monitor chains
         
     | 
| 
       242 
     | 
    
         
            -
                  def each_resolved(list)
         
     | 
| 
       243 
     | 
    
         
            -
                    list = list.dup
         
     | 
| 
       244 
     | 
    
         
            -
                    loop do
         
     | 
| 
       245 
     | 
    
         
            -
                      return if list.empty?
         
     | 
| 
       246 
     | 
    
         
            -
                      list.reject! do |item|
         
     | 
| 
       247 
     | 
    
         
            -
                        if resolved?(item.expected)
         
     | 
| 
       248 
     | 
    
         
            -
                          yield item
         
     | 
| 
       249 
     | 
    
         
            -
                          true
         
     | 
| 
       250 
     | 
    
         
            -
                        else
         
     | 
| 
       251 
     | 
    
         
            -
                          false
         
     | 
| 
       252 
     | 
    
         
            -
                        end
         
     | 
| 
       253 
     | 
    
         
            -
                      end ||
         
     | 
| 
       254 
     | 
    
         
            -
                        assert_resolved(list[0].expected) # resolve something or show a circular dependency error
         
     | 
| 
       255 
     | 
    
         
            -
                    end
         
     | 
| 
       256 
     | 
    
         
            -
                  end
         
     | 
| 
       257 
     | 
    
         
            -
             
     | 
| 
       258 
     | 
    
         
            -
                  private
         
     | 
| 
       259 
     | 
    
         
            -
             
     | 
| 
       260 
     | 
    
         
            -
                  attr_reader :id_map, :project_filter, :tracking_id_filter
         
     | 
| 
       261 
     | 
    
         
            -
             
     | 
| 
       262 
     | 
    
         
            -
                  # TODO: optimize by storing an instance variable if already resolved
         
     | 
| 
       263 
     | 
    
         
            -
                  def resolved?(e)
         
     | 
| 
       264 
     | 
    
         
            -
                    assert_resolved e
         
     | 
| 
       265 
     | 
    
         
            -
                    true
         
     | 
| 
       266 
     | 
    
         
            -
                  rescue UnresolvableIdError
         
     | 
| 
       267 
     | 
    
         
            -
                    false
         
     | 
| 
       268 
     | 
    
         
            -
                  end
         
     | 
| 
       269 
     | 
    
         
            -
             
     | 
| 
       270 
     | 
    
         
            -
                  # raises UnresolvableIdError when not resolved
         
     | 
| 
       271 
     | 
    
         
            -
                  def assert_resolved(e)
         
     | 
| 
       272 
     | 
    
         
            -
                    e.resolve_linked_tracking_ids!(id_map, force: true)
         
     | 
| 
       273 
     | 
    
         
            -
                  end
         
     | 
| 
       274 
     | 
    
         
            -
                end
         
     | 
| 
       275 
     | 
    
         
            -
             
     | 
| 
       276 
     | 
    
         
            -
                module MatchedExpected
         
     | 
| 
       277 
     | 
    
         
            -
                  class << self
         
     | 
| 
       278 
     | 
    
         
            -
                    def partition(expected, actual)
         
     | 
| 
       279 
     | 
    
         
            -
                      lookup_map = matching_expected_lookup_map(expected)
         
     | 
| 
       280 
     | 
    
         
            -
                      unmatched_expected = Set.new(expected) # for efficient deletion
         
     | 
| 
       281 
     | 
    
         
            -
                      unmatched_actual = []
         
     | 
| 
       282 
     | 
    
         
            -
                      matched = []
         
     | 
| 
       283 
     | 
    
         
            -
                      actual.each do |a|
         
     | 
| 
       284 
     | 
    
         
            -
                        e = matching_expected(a, lookup_map)
         
     | 
| 
       285 
     | 
    
         
            -
                        if e && unmatched_expected.delete?(e)
         
     | 
| 
       286 
     | 
    
         
            -
                          matched << [e, a]
         
     | 
| 
       287 
     | 
    
         
            -
                        else
         
     | 
| 
       288 
     | 
    
         
            -
                          unmatched_actual << a
         
     | 
| 
       289 
     | 
    
         
            -
                        end
         
     | 
| 
       290 
     | 
    
         
            -
                      end.compact
         
     | 
| 
       291 
     | 
    
         
            -
                      [matched, unmatched_expected.to_a, unmatched_actual]
         
     | 
| 
       292 
     | 
    
         
            -
                    end
         
     | 
| 
       293 
     | 
    
         
            -
             
     | 
| 
       294 
     | 
    
         
            -
                    private
         
     | 
| 
       295 
     | 
    
         
            -
             
     | 
| 
       296 
     | 
    
         
            -
                    # index list by all the thing we look up by: tracking id and actual id
         
     | 
| 
       297 
     | 
    
         
            -
                    def matching_expected_lookup_map(expected)
         
     | 
| 
       298 
     | 
    
         
            -
                      expected.each_with_object({}) do |e, all|
         
     | 
| 
       299 
     | 
    
         
            -
                        keys = [e.tracking_id]
         
     | 
| 
       300 
     | 
    
         
            -
                        keys << "#{e.class.api_resource}:#{e.id}" if e.id
         
     | 
| 
       301 
     | 
    
         
            -
                        keys.compact.each do |key|
         
     | 
| 
       302 
     | 
    
         
            -
                          raise "Lookup #{key} is duplicated" if all[key]
         
     | 
| 
       303 
     | 
    
         
            -
                          all[key] = e
         
     | 
| 
       304 
     | 
    
         
            -
                        end
         
     | 
| 
       305 
     | 
    
         
            -
                      end
         
     | 
| 
       306 
     | 
    
         
            -
                    end
         
     | 
| 
       307 
     | 
    
         
            -
             
     | 
| 
       308 
     | 
    
         
            -
                    def matching_expected(a, map)
         
     | 
| 
       309 
     | 
    
         
            -
                      klass = a.fetch(:klass)
         
     | 
| 
       310 
     | 
    
         
            -
                      map["#{klass.api_resource}:#{a.fetch(:id)}"] || map[a.fetch(:tracking_id)]
         
     | 
| 
       311 
     | 
    
         
            -
                    end
         
     | 
| 
       312 
     | 
    
         
            -
                  end
         
     | 
| 
       313 
     | 
    
         
            -
                end
         
     | 
| 
       314 
     | 
    
         
            -
             
     | 
| 
       315 
     | 
    
         
            -
                module Types
         
     | 
| 
       316 
     | 
    
         
            -
                  class PlannedChange
         
     | 
| 
       317 
     | 
    
         
            -
                    def initialize(klass, tracking_id)
         
     | 
| 
       318 
     | 
    
         
            -
                      @klass = klass
         
     | 
| 
       319 
     | 
    
         
            -
                      @tracking_id = tracking_id
         
     | 
| 
       320 
     | 
    
         
            -
                    end
         
     | 
| 
       321 
     | 
    
         
            -
             
     | 
| 
       322 
     | 
    
         
            -
                    def api_resource
         
     | 
| 
       323 
     | 
    
         
            -
                      klass.api_resource
         
     | 
| 
       324 
     | 
    
         
            -
                    end
         
     | 
| 
       325 
     | 
    
         
            -
             
     | 
| 
       326 
     | 
    
         
            -
                    def url(id = nil)
         
     | 
| 
       327 
     | 
    
         
            -
                      klass.url(id || self.id)
         
     | 
| 
       328 
     | 
    
         
            -
                    end
         
     | 
| 
       329 
     | 
    
         
            -
             
     | 
| 
       330 
     | 
    
         
            -
                    def change(id = nil)
         
     | 
| 
       331 
     | 
    
         
            -
                      Change.new(self.class::TYPE, api_resource, tracking_id, id)
         
     | 
| 
       332 
     | 
    
         
            -
                    end
         
     | 
| 
       333 
     | 
    
         
            -
             
     | 
| 
       334 
     | 
    
         
            -
                    attr_reader :klass, :tracking_id
         
     | 
| 
       335 
     | 
    
         
            -
                  end
         
     | 
| 
       336 
     | 
    
         
            -
             
     | 
| 
       337 
     | 
    
         
            -
                  class PlannedCreate < PlannedChange
         
     | 
| 
       338 
     | 
    
         
            -
                    TYPE = :create
         
     | 
| 
       339 
     | 
    
         
            -
             
     | 
| 
       340 
     | 
    
         
            -
                    def initialize(expected)
         
     | 
| 
       341 
     | 
    
         
            -
                      super(expected.class, expected.tracking_id)
         
     | 
| 
       342 
     | 
    
         
            -
                      @expected = expected
         
     | 
| 
       343 
     | 
    
         
            -
                    end
         
     | 
| 
       344 
     | 
    
         
            -
             
     | 
| 
       345 
     | 
    
         
            -
                    attr_reader :expected
         
     | 
| 
       346 
     | 
    
         
            -
                  end
         
     | 
| 
       347 
     | 
    
         
            -
             
     | 
| 
       348 
     | 
    
         
            -
                  class PlannedUpdate < PlannedChange
         
     | 
| 
       349 
     | 
    
         
            -
                    TYPE = :update
         
     | 
| 
       350 
     | 
    
         
            -
             
     | 
| 
       351 
     | 
    
         
            -
                    def initialize(expected, actual, diff)
         
     | 
| 
       352 
     | 
    
         
            -
                      super(expected.class, expected.tracking_id)
         
     | 
| 
       353 
     | 
    
         
            -
                      @expected = expected
         
     | 
| 
       354 
     | 
    
         
            -
                      @actual = actual
         
     | 
| 
       355 
     | 
    
         
            -
                      @diff = diff
         
     | 
| 
       356 
     | 
    
         
            -
                      @id = actual.fetch(:id)
         
     | 
| 
       357 
     | 
    
         
            -
                    end
         
     | 
| 
       358 
     | 
    
         
            -
             
     | 
| 
       359 
     | 
    
         
            -
                    def change
         
     | 
| 
       360 
     | 
    
         
            -
                      super(id)
         
     | 
| 
       361 
     | 
    
         
            -
                    end
         
     | 
| 
       362 
     | 
    
         
            -
             
     | 
| 
       363 
     | 
    
         
            -
                    attr_reader :expected, :actual, :diff, :id
         
     | 
| 
       364 
     | 
    
         
            -
                  end
         
     | 
| 
       365 
     | 
    
         
            -
             
     | 
| 
       366 
     | 
    
         
            -
                  class PlannedDelete < PlannedChange
         
     | 
| 
       367 
     | 
    
         
            -
                    TYPE = :delete
         
     | 
| 
       368 
     | 
    
         
            -
             
     | 
| 
       369 
     | 
    
         
            -
                    def initialize(actual)
         
     | 
| 
       370 
     | 
    
         
            -
                      super(actual.fetch(:klass), actual.fetch(:tracking_id))
         
     | 
| 
       371 
     | 
    
         
            -
                      @actual = actual
         
     | 
| 
       372 
     | 
    
         
            -
                      @id = actual.fetch(:id)
         
     | 
| 
       373 
     | 
    
         
            -
                    end
         
     | 
| 
       374 
     | 
    
         
            -
             
     | 
| 
       375 
     | 
    
         
            -
                    def change
         
     | 
| 
       376 
     | 
    
         
            -
                      super(id)
         
     | 
| 
       377 
     | 
    
         
            -
                    end
         
     | 
| 
      
 157 
     | 
    
         
            +
                  return unless filter.filtering? # minor optimization
         
     | 
| 
       378 
158 
     | 
    
         | 
| 
       379 
     | 
    
         
            -
             
     | 
| 
      
 159 
     | 
    
         
            +
                  actual.select! do |a|
         
     | 
| 
      
 160 
     | 
    
         
            +
                    tracking_id = a.fetch(:tracking_id)
         
     | 
| 
      
 161 
     | 
    
         
            +
                    tracking_id.nil? || filter.matches_tracking_id?(tracking_id)
         
     | 
| 
       380 
162 
     | 
    
         
             
                  end
         
     | 
| 
       381 
163 
     | 
    
         
             
                end
         
     | 
| 
       382 
164 
     | 
    
         
             
              end
         
     | 
    
        data/lib/kennel/version.rb
    CHANGED
    
    
    
        data/lib/kennel.rb
    CHANGED
    
    | 
         @@ -92,9 +92,8 @@ module Kennel 
     | 
|
| 
       92 
92 
     | 
    
         
             
                    preload
         
     | 
| 
       93 
93 
     | 
    
         
             
                    Syncer.new(
         
     | 
| 
       94 
94 
     | 
    
         
             
                      api, generated, definitions,
         
     | 
| 
       95 
     | 
    
         
            -
                       
     | 
| 
       96 
     | 
    
         
            -
                       
     | 
| 
       97 
     | 
    
         
            -
                      tracking_id_filter: filter.tracking_id_filter
         
     | 
| 
      
 95 
     | 
    
         
            +
                      filter: filter,
         
     | 
| 
      
 96 
     | 
    
         
            +
                      strict_imports: strict_imports
         
     | 
| 
       98 
97 
     | 
    
         
             
                    )
         
     | 
| 
       99 
98 
     | 
    
         
             
                  end
         
     | 
| 
       100 
99 
     | 
    
         
             
                end
         
     | 
    
        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.132.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: 2023-01- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2023-01-26 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: diff-lcs
         
     | 
| 
         @@ -112,6 +112,10 @@ files: 
     | 
|
| 
       112 
112 
     | 
    
         
             
            - lib/kennel/string_utils.rb
         
     | 
| 
       113 
113 
     | 
    
         
             
            - lib/kennel/subclass_tracking.rb
         
     | 
| 
       114 
114 
     | 
    
         
             
            - lib/kennel/syncer.rb
         
     | 
| 
      
 115 
     | 
    
         
            +
            - lib/kennel/syncer/matched_expected.rb
         
     | 
| 
      
 116 
     | 
    
         
            +
            - lib/kennel/syncer/plan_displayer.rb
         
     | 
| 
      
 117 
     | 
    
         
            +
            - lib/kennel/syncer/resolver.rb
         
     | 
| 
      
 118 
     | 
    
         
            +
            - lib/kennel/syncer/types.rb
         
     | 
| 
       115 
119 
     | 
    
         
             
            - lib/kennel/tasks.rb
         
     | 
| 
       116 
120 
     | 
    
         
             
            - lib/kennel/template_variables.rb
         
     | 
| 
       117 
121 
     | 
    
         
             
            - lib/kennel/unmuted_alerts.rb
         
     |