class ChefCLI::Policyfile::SolutionDependencies

Constants

Cookbook

Attributes

cookbook_dependencies[R]
policyfile_dependencies[R]

Public Class Methods

from_lock(lock_data) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 59
def self.from_lock(lock_data)
  new.tap { |e| e.consume_lock_data(lock_data) }
end
new() click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 67
def initialize
  @policyfile_dependencies = []
  @cookbook_dependencies = {}
end

Public Instance Methods

add_cookbook_dep(cookbook_name, version, dependency_list) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 76
def add_cookbook_dep(cookbook_name, version, dependency_list)
  cookbook = Cookbook.new(cookbook_name, version)
  add_cookbook_obj_dep(cookbook, dependency_list)
end
add_policyfile_dep(cookbook, constraint) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 72
def add_policyfile_dep(cookbook, constraint)
  @policyfile_dependencies << [ cookbook, Semverse::Constraint.new(constraint) ]
end
consume_lock_data(lock_data) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 86
def consume_lock_data(lock_data)
  unless lock_data.key?("Policyfile") && lock_data.key?("dependencies")
    msg = %Q|lockfile solution_dependencies must be a Hash of the form `{"Policyfile": [], "dependencies": {} }' (got: #{lock_data.inspect})|
    raise InvalidLockfile, msg
  end

  set_policyfile_deps_from_lock_data(lock_data)
  set_cookbook_deps_from_lock_data(lock_data)
end
cookbook_deps_for_lock() click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 115
def cookbook_deps_for_lock
  cookbook_dependencies.inject({}) do |map, (cookbook, deps)|
    map[cookbook.to_s] = deps.map do |name, constraint|
      [ name, constraint.to_s ]
    end
    map
  end.sort.to_h
end
policyfile_dependencies_for_lock() click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 109
def policyfile_dependencies_for_lock
  policyfile_dependencies.map do |name, constraint|
    [ name, constraint.to_s ]
  end.sort
end
test_conflict!(cookbook_name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 96
def test_conflict!(cookbook_name, version)
  unless have_cookbook_dep?(cookbook_name, version)
    raise CookbookNotInWorkingSet, "Cookbook #{cookbook_name} (#{version}) not in the working set, cannot test for conflicts"
  end

  assert_cookbook_version_valid!(cookbook_name, version)
  assert_cookbook_deps_valid!(cookbook_name, version)
end
to_lock() click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 105
def to_lock
  { "Policyfile" => policyfile_dependencies_for_lock, "dependencies" => cookbook_deps_for_lock }
end
transitive_deps(names) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 124
def transitive_deps(names)
  deps = Set.new
  to_explore = names.dup
  until to_explore.empty?
    ck_name = to_explore.shift
    next unless deps.add?(ck_name) # explore each ck only once

    my_deps = find_cookbook_dep_by_name(ck_name)
    dep_names = my_deps[1].map(&:first)
    to_explore += dep_names
  end
  deps.to_a.sort
end
update_cookbook_dep(cookbook_name, new_version, new_dependency_list) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 81
def update_cookbook_dep(cookbook_name, new_version, new_dependency_list)
  @cookbook_dependencies.delete_if { |cb, _deps| cb.name == cookbook_name }
  add_cookbook_dep(cookbook_name, new_version, new_dependency_list)
end

Private Instance Methods

add_cookbook_dep_from_lock_data(name_and_version, deps_list) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 269
def add_cookbook_dep_from_lock_data(name_and_version, deps_list)
  unless name_and_version.is_a?(String)
    show = "#{name_and_version.inspect} => #{deps_list.inspect}"
    msg = %Q{lockfile cookbook_dependencies entries must be of the form "$COOKBOOK_NAME ($VERSION)" => [ $dependency, ...] (got: #{show}) }
    raise InvalidLockfile, msg
  end

  unless Cookbook.valid_str?(name_and_version)
    msg = %Q{lockfile cookbook_dependencies entry keys must be of the form "$COOKBOOK_NAME ($VERSION)" (got: #{name_and_version.inspect}) }
    raise InvalidLockfile, msg
  end

  unless deps_list.is_a?(Array)
    msg = %Q{lockfile cookbook_dependencies entry values must be an Array like [ [ "$COOKBOOK_NAME", "$CONSTRAINT" ], ... ] (got: #{deps_list.inspect}) }
    raise InvalidLockfile, msg
  end

  deps_list.each do |entry|

    unless entry.is_a?(Array) && entry.size == 2
      msg = %Q{lockfile solution_dependencies dependencies entry must be like [ "$COOKBOOK_NAME", "$CONSTRAINT" ] (got: #{entry.inspect})}
      raise InvalidLockfile, msg
    end

    dep_name, constraint = entry

    unless dep_name.is_a?(String) && !dep_name.empty?
      msg = "malformed lockfile solution_dependencies dependencies entry. Cookbook name portion must be a string (got: #{entry.inspect})"
      raise InvalidLockfile, msg
    end

    unless constraint.is_a?(String) && !constraint.empty?
      msg = "malformed lockfile solution_dependencies dependencies entry. Version constraint portion must be a string (got: #{entry.inspect})"
      raise InvalidLockfile, msg
    end
  end

  cookbook = Cookbook.parse(name_and_version)
  add_cookbook_obj_dep(cookbook, deps_list)
end
add_cookbook_obj_dep(cookbook, dependency_map) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 140
def add_cookbook_obj_dep(cookbook, dependency_map)
  @cookbook_dependencies[cookbook] = dependency_map.map do |dep_name, constraint|
    [ dep_name, Semverse::Constraint.new(constraint) ]
  end
end
add_policyfile_dep_from_lock_data(entry) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 233
def add_policyfile_dep_from_lock_data(entry)
  unless entry.is_a?(Array) && entry.size == 2
    msg = %Q{lockfile solution_dependencies Policyfile dependencies entry must be like [ "$COOKBOOK_NAME", "$CONSTRAINT" ] (got: #{entry.inspect})}
    raise InvalidLockfile, msg
  end

  cookbook_name, constraint = entry

  unless cookbook_name.is_a?(String) && !cookbook_name.empty?
    msg = "lockfile solution_dependencies Policyfile dependencies entry. Cookbook name portion must be a string (got: #{entry.inspect})"
    raise InvalidLockfile, msg
  end

  unless constraint.is_a?(String) && !constraint.empty?
    msg = "malformed lockfile solution_dependencies Policyfile dependencies entry. Version constraint portion must be a string (got: #{entry.inspect})"
    raise InvalidLockfile, msg
  end
  add_policyfile_dep(cookbook_name, constraint)
rescue Semverse::InvalidConstraintFormat
  msg = "malformed lockfile solution_dependencies Policyfile dependencies entry. Version constraint portion must be a valid version constraint (got: #{entry.inspect})"
  raise InvalidLockfile, msg
end
assert_cookbook_deps_valid!(cookbook_name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 159
def assert_cookbook_deps_valid!(cookbook_name, version)
  dependency_conflicts = cookbook_deps_conflicts_for(cookbook_name, version)
  return false if dependency_conflicts.empty?

  message = "Cookbook #{cookbook_name} (#{version}) has dependency constraints that cannot be met by the existing cookbook set:\n"
  full_message = message + dependency_conflicts.join("\n")
  raise DependencyConflict, full_message
end
assert_cookbook_version_valid!(cookbook_name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 146
def assert_cookbook_version_valid!(cookbook_name, version)
  policyfile_conflicts = policyfile_conflicts_with(cookbook_name, version)
  cookbook_conflicts = cookbook_conflicts_with(cookbook_name, version)
  all_conflicts = policyfile_conflicts + cookbook_conflicts

  return false if all_conflicts.empty?

  details = all_conflicts.map { |source, name, constraint| "#{source} depends on #{name} #{constraint}" }
  message = "Cookbook #{cookbook_name} (#{version}) conflicts with other dependencies:\n"
  full_message = message + details.join("\n")
  raise DependencyConflict, full_message
end
cookbook_conflicts_with(cookbook_name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 180
def cookbook_conflicts_with(cookbook_name, version)
  cookbook_conflicts = []

  @cookbook_dependencies.each do |top_level_dep_name, dependencies|
    dependencies.each do |dep_name, constraint|
      if dep_name == cookbook_name && !constraint.satisfies?(version)
        cookbook_conflicts << [top_level_dep_name, dep_name, constraint]
      end
    end
  end

  cookbook_conflicts
end
cookbook_deps_conflicts_for(cookbook_name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 194
def cookbook_deps_conflicts_for(cookbook_name, version)
  conflicts = []
  transitive_deps = find_cookbook_dep_by_name_and_version(cookbook_name, version)
  transitive_deps.each do |name, constraint|
    existing_cookbook = find_cookbook_dep_by_name(name)
    if existing_cookbook.nil?
      conflicts << "Cookbook #{name} isn't included in the existing cookbook set."
    elsif !constraint.satisfies?(existing_cookbook[0].version)
      conflicts << "Dependency on #{name} #{constraint} conflicts with existing version #{existing_cookbook[0]}"
    end
  end
  conflicts
end
find_cookbook_dep_by_name(name) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 212
def find_cookbook_dep_by_name(name)
  @cookbook_dependencies.find { |k, v| k.name == name }
end
find_cookbook_dep_by_name_and_version(name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 216
def find_cookbook_dep_by_name_and_version(name, version)
  @cookbook_dependencies[Cookbook.new(name, version)]
end
have_cookbook_dep?(name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 208
def have_cookbook_dep?(name, version)
  @cookbook_dependencies.key?(Cookbook.new(name, version))
end
policyfile_conflicts_with(cookbook_name, version) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 168
def policyfile_conflicts_with(cookbook_name, version)
  policyfile_conflicts = []

  @policyfile_dependencies.each do |dep_name, constraint|
    if dep_name == cookbook_name && !constraint.satisfies?(version)
      policyfile_conflicts << ["Policyfile", dep_name, constraint]
    end
  end

  policyfile_conflicts
end
set_cookbook_deps_from_lock_data(lock_data) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 256
def set_cookbook_deps_from_lock_data(lock_data)
  cookbook_dependencies_data = lock_data["dependencies"]

  unless cookbook_dependencies_data.is_a?(Hash)
    msg = "lockfile solution_dependencies dependencies entry must be a Hash (JSON object) of dependencies (got: #{cookbook_dependencies_data.inspect})"
    raise InvalidLockfile, msg
  end

  cookbook_dependencies_data.each do |name_and_version, deps_list|
    add_cookbook_dep_from_lock_data(name_and_version, deps_list)
  end
end
set_policyfile_deps_from_lock_data(lock_data) click to toggle source
# File lib/chef-cli/policyfile/solution_dependencies.rb, line 220
def set_policyfile_deps_from_lock_data(lock_data)
  policyfile_deps_data = lock_data["Policyfile"]

  unless policyfile_deps_data.is_a?(Array)
    msg = "lockfile solution_dependencies Policyfile dependencies must be an array of cookbooks and constraints (got: #{policyfile_deps_data.inspect})"
    raise InvalidLockfile, msg
  end

  policyfile_deps_data.each do |entry|
    add_policyfile_dep_from_lock_data(entry)
  end
end