class Batali::Command::Resolve

Resolve cookbooks

Public Instance Methods

execute!() click to toggle source

Resolve dependencies and constraints. Output results to stdout and dump serialized manifest

# File lib/batali/command/resolve.rb, line 11
def execute!
  system = Grimoire::System.new
  run_action "Loading sources" do
    UnitLoader.new(
      :file => batali_file,
      :system => system,
      :cache => cache_directory(:git),
      :auto_path_restrict => !infrastructure?,
    ).populate!
    nil
  end
  requirements = Grimoire::RequirementList.new(
    :name => :batali_resolv,
    :requirements => batali_file.cookbook.map { |ckbk|
      [ckbk.name, (ckbk.constraint.nil? || ckbk.constraint.empty? ? ["> 0"] : ckbk.constraint)]
    },
  )
  solv = Grimoire::Solver.new(
    :requirements => requirements,
    :system => system,
    :score_keeper => score_keeper,
  )
  if infrastructure?
    infrastructure_resolution(solv)
  else
    single_path_resolution(solv)
  end
end
infrastructure_resolution(solv) click to toggle source

Generate manifest comprised of units for entire infrastructure

@param solv [Grimoire::Solver] @return [TrueClass]

# File lib/batali/command/resolve.rb, line 125
def infrastructure_resolution(solv)
  ui.info "Performing infrastructure path resolution."
  if manifest.infrastructure == false
    ui.ask "Current manifest is resolved single path. Convert to infrastructure?"
  end
  run_action "Resolving dependency constraints" do
    solv.prune_world!
    nil
  end
  dry_run("manifest file write") do
    run_action "Writing infrastructure manifest file" do
      File.open(manifest.path, "w") do |file|
        manifest = Manifest.new(
          :cookbook => solv.world.units.values.flatten,
          :infrastructure => true,
        )
        file.write MultiJson.dump(manifest, :pretty => true)
        nil
      end
    end
  end
  ui.info "Infrastructure manifest solution:"

  solution_units = solv.world.units
  manifest_units = Smash.new.tap do |mu|
    manifest.cookbook.each do |unit|
      mu[unit.name] ||= []
      mu[unit.name] << unit
    end
  end
  (solution_units.keys + manifest_units.keys).compact.uniq.sort.each do |unit_name|
    if manifest_units[unit_name]
      if solution_units[unit_name]
        removed = manifest_units[unit_name].find_all do |m_unit|
          solution_units[unit_name].none? do |s_unit|
            m_unit.same?(s_unit)
          end
        end.map { |u| [u.version, :red] }
        added = solution_units[unit_name].find_all do |s_unit|
          manifest_units[unit_name].none? do |m_unit|
            s_unit.same?(m_unit)
          end
        end.map { |u| [u.version, :green] }
        persisted = solution_units[unit_name].find_all do |s_unit|
          manifest_units[unit_name].any? do |m_unit|
            s_unit.same?(m_unit)
          end
        end.map { |u| [u.version, nil] }
        unit_versions = (removed + added + persisted).sort_by(&:first).map do |uv|
          uv.last ? ui.color(uv.first.to_s, uv.last) : uv.first.to_s
        end
        unless added.empty? && removed.empty?
          ui.puts "#{ui.color(unit_name, :yellow)} #{ui.color("<", :yellow)}#{unit_versions.join(ui.color(", ", :yellow))}#{ui.color(">", :yellow)}" # rubocop:disable Metrics/LineLength
        else
          ui.puts "#{unit_name} <#{unit_versions.join(", ")}>"
        end
      else
        ui.puts ui.color("#{unit_name} <#{manifest_units[unit_name].map(&:version).sort.map(&:to_s).join(", ")}>", :red) # rubocop:disable Metrics/LineLength
      end
    else
      ui.puts ui.color("#{unit_name} <#{solution_units[unit_name].map(&:version).sort.map(&:to_s).join(", ")}>", :green) # rubocop:disable Metrics/LineLength
    end
  end
end
score_keeper() click to toggle source

@return [ScoreKeeper]

# File lib/batali/command/resolve.rb, line 41
def score_keeper
  memoize(:score_keeper) do
    sk_manifest = Manifest.new(:cookbook => manifest.cookbook)
    unless config[:least_impact]
      sk_manifest.cookbook.clear
    end
    sk_manifest.cookbook.delete_if do |unit|
      arguments.include?(unit.name)
    end
    ScoreKeeper.new(:manifest => sk_manifest)
  end
end
single_path_resolution(solv) click to toggle source

Generate manifest comprised of units for single path resolution

@param solv [Grimoire::Solver] @return [TrueClass]

# File lib/batali/command/resolve.rb, line 58
def single_path_resolution(solv)
  original_units = Smash[
    [manifest.cookbook].flatten.compact.map do |unit|
      [unit.name, unit.version]
    end
  ]
  ui.info "Performing single path resolution."
  if manifest.infrastructure
    ui.confirm "Current manifest is resolved for infrastucture. Convert to single path?"
  end
  results = []
  run_action "Resolving dependency constraints" do
    results = solv.generate!
    nil
  end
  if results.empty?
    ui.error "No solutions found defined requirements!"
  else
    ideal_solution = results.pop
    ui.debug "Full solution raw contents:"
    ideal_solution.units.each do |unit|
      ui.debug [unit.name, unit.version].join(" -> ")
    end
    dry_run("manifest file write") do
      run_action "Writing manifest" do
        manifest = Manifest.new(
          :cookbook => ideal_solution.units,
          :infrastructure => false,
        )
        File.open("batali.manifest", "w") do |file|
          file.write MultiJson.dump(manifest, :pretty => true)
        end
        nil
      end
    end
    # ui.info "Number of solutions collected for defined requirements: #{results.size + 1}"
    ui.info "Ideal solution:"
    solution_units = Smash[ideal_solution.units.map { |unit| [unit.name, unit] }]
    manifest_units = Smash[manifest.cookbook.map { |unit| [unit.name, unit] }]
    (solution_units.keys + manifest_units.keys).compact.uniq.sort.each do |unit_name|
      if manifest_units[unit_name]
        if solution_units[unit_name]
          if solution_units[unit_name].same?(manifest_units[unit_name])
            ui.puts "#{unit_name} <#{solution_units[unit_name].version}>"
          else
            u_diff = manifest_units[unit_name].diff(solution_units[unit_name])
            version_output = u_diff[:version] ? u_diff[:version].join(" -> ") : solution_units[unit_name].version
            u_diff.delete(:version)
            unless u_diff.empty?
              diff_output = "[#{u_diff.values.map { |v| v.join(" -> ") }.join(" | ")}]"
            end
            ui.puts ui.color("#{unit_name} <#{version_output}> #{diff_output}", :yellow)
          end
        else
          ui.puts ui.color("#{unit_name} <#{manifest_units[unit_name].version}>", :red)
        end
      else
        ui.puts ui.color("#{unit_name} <#{solution_units[unit_name].version}>", :green)
      end
    end
  end
end