class Batali::BFile

Custom file loading class

Attributes

cache[R]

@return [String] path to cache

Public Class Methods

cookbook_coerce() click to toggle source

@return [Proc] cookbook convert

# File lib/batali/b_file.rb, line 126
def self.cookbook_coerce
  proc do |v|
    v = [v].flatten.compact
    if v.size == 1 && v.first.is_a?(Hash)
      Cookbook.new(v.first)
    else
      name, args = v.first, v.slice(1, v.size)
      if args.empty?
        args = Smash.new
      elsif args.size == 1 && args.first.is_a?(Hash)
        args = args.first
      else
        args = Smash.new(:constraint => args.map(&:to_s))
      end
      Cookbook.new(Smash.new(:name => name).merge(args))
    end
  end
end
new(b_file, cache_path) click to toggle source

Create a new BFile instance

@param b_file [String] path to file @param cache_path [String] path to cache directory @return [self]

Calls superclass method
# File lib/batali/b_file.rb, line 119
def initialize(b_file, cache_path)
  b_file = Utility.clean_path(b_file)
  @cache = cache_path
  super(b_file)
end

Public Instance Methods

auto_discover!(environment = nil) click to toggle source

Search environments for cookbooks and restraints

@return [TrueClass]

# File lib/batali/b_file.rb, line 216
def auto_discover!(environment = nil)
  debug "Starting cookbook auto-discovery"
  unless discover
    raise "Attempting to perform auto-discovery but auto-discovery is not enabled!"
  end
  environment_items = Dir.glob(Utility.join_path(File.dirname(path), "environments", "*.{json,rb}")).map do |e_path|
    result = parse_environment(e_path)
    if result[:name] && result[:cookbooks]
      Smash.new(
        result[:name] => result[:cookbooks],
      )
    end
  end.compact.inject(Smash.new) { |m, n| m.merge(n) }
  environment_items.each do |e_name, items|
    next if environment && e_name != environment
    debug "Discovery processing of environment: #{e_name}"
    items.each do |ckbk_name, constraints|
      ckbk = cookbook.detect do |c|
        c.name == ckbk_name
      end
      if ckbk
        unless ckbk.constraint
          debug "Skipping constraint merging due to lack of original constraints: #{ckbk.inspect}"
          next
        end
        new_constraints = ckbk.constraint.dup
        new_constraints += constraints
        requirement = UnitRequirement.new(*new_constraints)
        new_constraints = flatten_constraints(requirement.requirements)
        debug "Discovery merged constraints for #{ckbk.name}: #{new_constraints.inspect}"
        ckbk.constraint.replace(new_constraints)
      else
        debug "Discovery added cookbook #{ckbk_name}: #{constraints.inspect}"
        cookbook.push(
          Cookbook.new(
            :name => ckbk_name,
            :constraint => constraints,
          )
        )
      end
    end
  end
  debug "Completed cookbook auto-discovery"
  true
end

Protected Instance Methods

convert_constraint(constraint) click to toggle source

Convert constraint for merging

@param constraint [String] @param [Array<String>]

# File lib/batali/b_file.rb, line 268
def convert_constraint(constraint)
  comp, ver = constraint.split(" ", 2).map(&:strip)
  if comp == "~>"
    ver = UnitVersion.new(ver)
    [">= #{ver}", "< #{ver.bump}"]
  else
    [constraint]
  end
end
debug(s) click to toggle source

Proxy debug output

# File lib/batali/b_file.rb, line 364
def debug(s)
  Batali.debug(s)
end
flatten_constraints(constraints) click to toggle source

Consume list of constraints and generate compressed list that satisfies all defined constraints.

@param constraints [Array<Array<String, UnitVersion>>] @return [Array<Array<String, UnitVersion>>] @note if an explict constraint is provided, only it will be returned

# File lib/batali/b_file.rb, line 285
def flatten_constraints(constraints)
  grouped = constraints.group_by(&:first)
  grouped = Smash[
    grouped.map do |comp, items|
      versions = items.map(&:last)
      if comp.start_with?(">")
        [comp, [versions.min]]
      elsif comp.start_with?("<")
        [comp, [versions.max]]
      else
        [comp, versions]
      end
    end
  ]
  if grouped["="]
    grouped[">="] ||= []
    grouped["<="] ||= []
    grouped["="].each do |ver|
      grouped[">="] << ver
      grouped["<="] << ver
    end
    grouped.delete("=")
  end
  if grouped[">"] || grouped[">="]
    if grouped[">="] && (grouped[">"].nil? || grouped[">="].min <= grouped[">"].min)
      grouped[">="] = [grouped[">="].min]
      grouped.delete(">")
    else
      grouped[">"] = [grouped[">"].min]
      grouped.delete(">=")
    end
  end
  if grouped["<"] || grouped["<="]
    if grouped["<="] && (grouped["<"].nil? || grouped["<="].max >= grouped["<"].max)
      grouped["<="] = [grouped["<="].max]
      grouped.delete("<")
    else
      grouped["<"] = [grouped["<"].max]
      grouped.delete("<=")
    end
  end
  grouped.map do |comp, vers|
    vers.map do |version|
      "#{comp} #{version}"
    end
  end.flatten
end
parse_environment(path) click to toggle source

Read environment file and return defined cookbook constraints

@param path [String] path to environment @return [Smash]

# File lib/batali/b_file.rb, line 337
def parse_environment(path)
  path = Utility.clean_path(path)
  case File.extname(path)
  when ".json"
    env = MultiJson.load(
      File.read(path)
    ).to_smash
  when ".rb"
    struct = Struct.new
    struct.set_state!(:value_collapse => true)
    struct.instance_eval(File.read(path), path, 1)
    env = struct._dump.to_smash
  else
    raise "Unexpected file format encountered! (#{File.extname(path)})"
  end
  Smash.new(
    :name => env[:name],
    :cookbooks => Smash[
      env.fetch(
        :cookbook_versions,
        Smash.new
      ).map { |k, v| [k, v.to_s.split(",")] }
    ],
  )
end