class PagerDuty::ChefServer::Sync

rubocop:disable Metrics/ClassLength

Attributes

cookbook_dir[R]
ignore_patterns[R]
ui[R]
why_run[R]

Public Class Methods

new(opts={}) click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 30
def initialize(opts={})
  require 'tempfile'
  require 'json'
  require 'mixlib/shellout'
  require 'chef/cookbook_version'
  require 'chef/data_bag_item'
  require 'chef/data_bag'
  require 'berkshelf'
  require 'berkshelf/berksfile'
  @cookbook_dir =  opts[:vendor_dir]
  @why_run = opts[:why_run]
  @ui = opts[:ui] || Chef::Knife.ui
  if File.exist?(ignore_file)
    @ignore_patterns = File.read(ignore_file).lines.map{|l| File.join(chef_repo_dir, l).strip}
  else
    ui.info('.pd-ignore absent, nothing will be ignored')
    @ignore_patterns = false
  end
end

Public Instance Methods

berkshelf_install() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 79
def berkshelf_install
  path = File.expand_path(File.join(chef_repo_dir, 'Berksfile'))
  ui.info(ui.color("using Berksfile: #{path} for berkshelf install", :yellow))
  berksfile = Berkshelf::Berksfile.from_file(path, { except: 'tests' } )
  FileUtils.rm_rf(cookbook_dir)
  berksfile.vendor(cookbook_dir)
end
cookbook_segments() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 105
def cookbook_segments
  Chef::CookbookVersion::COOKBOOK_SEGMENTS
end
diff(mf1, mf2) click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 162
def diff(mf1, mf2)
  diffs = Hash.new{|h, k| h[k]= []}
  mf2 = {} if mf2.nil?
  mf1 = {} if mf1.nil?
  segments = (mf1.keys + mf2.keys).sort.uniq
  different_parts = segments.select{|segment| mf1[segment]!= mf2[segment]}
  different_parts.each do |segment|
    files = (Array(mf1[segment]) + Array(mf2[segment])).map{|f| f['name']}.uniq
    files.each do |file|
      f1 = Array(mf1[segment]).detect{|f|f['name'] == file} || {}
      f2 = Array(mf2[segment]).detect{|f|f['name'] == file} || {}
      unless f1['checksum'] == f2['checksum']
        diffs[segment] << file #unless file =~ /metadata\.(rb|json)/
      end
    end
  end
  diffs
end
different_cookbook?(cb) click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 181
def different_cookbook?(cb)
  !diff(local_checksums[cb], remote_checksums[cb]).empty?
end
local_checksums() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 130
def local_checksums
  @local_checksums ||= begin
    c = {}
    cbl = Chef::CookbookLoader.new(Array(cookbook_dir))
    cbl.load_cookbooks
    cbl_sort = cbl.values.map(&:name).map(&:to_s).sort

    cbl_sort.each do |cb|
      print "#{cb} => "
      c[cb] = {}
      cookbook_segments.each do |m|
        cbm_sort = cbl[cb].manifest[m].sort { |x, y| x['name'] <=> y['name'] }
        cbm_sort = cbm_sort.sort { |x, y| x['checksum'] <=> y['checksum'] }
        cbm_sort.each do |file|
          file.delete(:path)
          file.delete(:specificity)
        end
        c[cb][m] = cbm_sort
      end
      diff = diff(c[cb], remote_checksums[cb])
      if diff.empty?
        ui.info(ui.color( 'match', :green))
      else
        ui.info(ui.color( 'mismatch', :yellow))
        ui.output(diff(c[cb], remote_checksums[cb]))
      end
      sleep 0.1 # was printing too fast to be useful :(
    end
    c
  end
end
local_cookbooks() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 87
def local_cookbooks
  local_checksums.keys.sort
end
new_cookbooks() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 185
def new_cookbooks
  local_cookbooks - remote_cookbooks
end
remote_checksums() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 109
def remote_checksums
  @remote_checksums ||= begin
    c = {}
    remote_cookbooks.each do |cb|
      c[cb] = {}
      cbm = Chef::CookbookVersion.load(cb).manifest
      cookbook_segments.each do |m|
        cbm_sort = cbm[m].sort { |x, y| x['name'] <=> y['name'] }
        cbm_sort = cbm_sort.sort { |x, y| x['checksum'] <=> y['checksum'] }
        cbm_sort.each do |file|
          file.delete(:url)
          file.delete(:path)
          file.delete(:specificity)
        end
        c[cb][m] = cbm_sort
      end
    end
    c
  end
end
remote_commit() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 95
def remote_commit
  @remote_commit ||= begin
    if Chef::DataBag.list.keys.include?('metadata')
      Chef::DataBagItem.load('metadata', 'commit').raw_data['commit']
    else
      {}
    end
  end
end
remote_cookbooks() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 91
def remote_cookbooks
  @remote_cookbooks ||= Chef::CookbookVersion.list.keys.sort
end
replace_cookbook(cb) click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 199
def replace_cookbook(cb)
  converge_by "Replace cookbook #{cb}" do
    delete_cookbook(cb)
    upload_cookbook(cb)
  end
end
run() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 50
def run
  berkshelf_install
  sync_cookbooks
  upload_databags
  upload_environments
  upload_roles
end
stale_cookbooks() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 189
def stale_cookbooks
  remote_cookbooks - local_cookbooks
end
sync_cookbooks() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 58
def sync_cookbooks
  altered_cookbooks = Hash.new{|h, k| h[k] = []}

  if remote_cookbooks.empty?
    upload_all_cookbooks
  else
    unless new_cookbooks.empty?
      upload_cookbooks(new_cookbooks)
      altered_cookbooks[:added] = new_cookbooks
    end
    stale_cookbooks.each do |cb|
      delete_cookbook(cb)
      altered_cookbooks[:deleted] << cb
    end
    updated_cookbooks.each do |cb|
      replace_cookbook(cb)
      altered_cookbooks[:updated] << cb
    end
  end
end
updated_cookbooks() click to toggle source
# File lib/pagerduty/chef_server/sync.rb, line 193
def updated_cookbooks
  (local_cookbooks & remote_cookbooks).select do |cb|
    different_cookbook?(cb)
  end
end