class Bosh::Cli::Command::Biff

Constants

DIFF_COMMAND

Unified is so that we get the whole file diff not just sections.

DIFF_FAILED_KEEP_NEW_TEXT
KEEP_NEW_VERSION_TEXT

Attributes

errors[R]
ip_helper[RW]

Accessor for testing purposes.

template_output[RW]

Public Instance Methods

biff(template) click to toggle source
# File lib/cli/commands/biff.rb, line 19
def biff(template)
  setup(template)

  template_to_fill = ERB.new(File.read(@template_file), 0, "%<>-")
  begin
    @template_output = template_to_fill.result(binding)
  rescue ArgumentError => ex
    say(ex.message)
    say(ex.backtrace.join("\n"))
    err("Error rendering ERB")
  end

  if @errors == 0
    print_string_diff(File.read(@deployment_file), @template_output)
    keep_new_file unless @no_differences
  else
    err("There were #{@errors} errors.")
  end
ensure
  delete_temp_diff_files
end

Private Instance Methods

check_valid_network_config(netw, subnets) click to toggle source

Raises errors if there is something wrong with the user's deployment network configuration, since it's used to populate the rest of the template. @param [Hash] netw The user's network configuration as a ruby hash. @param [Array] subnets The subnets in the network.

# File lib/cli/commands/biff.rb, line 342
def check_valid_network_config(netw, subnets)
  if subnets.nil?
    err("You must have subnets in #{netw["name"]}")
  end
  unless subnets.length == 1
    err("Biff doesn't know how to deal with anything other than one " +
        "subnet in #{netw["name"]}")
  end
  if subnets.first["range"].nil? || subnets.first["dns"].nil?
    err("Biff requires each network to have range and dns entries.")
  end
  if subnets.first["gateway"] && subnets.first["gateway"].match(/.*\.1$/).nil?
    err("Biff only supports configurations where the gateway is the " +
        "first IP (e.g. 172.31.196.1).")
  end
end
create_ip_helper() click to toggle source

Creates the helper hash. Keys are the network name, values are the CIDR tool for generating IPs for jobs in that network. @return [Hash] The helper hash that has a CIDR instance for each network.

# File lib/cli/commands/biff.rb, line 321
def create_ip_helper
  helper = {}
  netw_arr = find("networks")
  if netw_arr.nil?
    err("Must have a network section.")
  end
  netw_arr.each do |netw|
    subnets = netw["subnets"]
    check_valid_network_config(netw, subnets)
    helper[netw["name"]] = {}
    helper[netw["name"]]["range"] = get_range(subnets)
    helper[netw["name"]]["static"] = get_static_ranges(subnets)
  end
  helper
end
delete_all_except(obj, name) click to toggle source

Used by print_the_template_path so that it can prettily print just the section of the template that the user is missing. E.x. if the user is missing the job 'ccdb' then we want to not just print out 'ccdb' and everything in it – we also want to print out it's heirarchy, aka the fact that it is under jobs. So, we delete everything else in jobs. @param [Object] obj Either a Hash or Array that is supposed to have

everything deleted out of it except for a key or object with
name = key depending on if it is a hash or array respectively.

@param [String] name They key to keep. @return [Object] The original containing object with only the named object

in it.
# File lib/cli/commands/biff.rb, line 170
def delete_all_except(obj, name)
  each_method = obj.is_a?(Hash) ? "each_key" : "each_index"
  obj.send(each_method) do |key|
    if key == name ||
       (obj[key].is_a?(Hash) && obj[key]["name"] == name)
      return_obj = nil
      if (obj.is_a?(Hash))
        return_obj = {}
        return_obj[name] = obj[key]
      else
        return_obj = [obj[key]]
      end
      return return_obj
    end
  end
end
delete_temp_diff_files() click to toggle source

Deletes the temporary files that were used.

# File lib/cli/commands/biff.rb, line 56
def delete_temp_diff_files
  # File.exists works for both files and directories.  Must use for 1.8
  # compat.
  if @dir_name && File.exists?(@dir_name)
    FileUtils.remove_entry_secure(@dir_name)
  end
end
find(path, obj = @deployment_obj) click to toggle source

Finds a path in the user's deployment configuration object. The reason we use this is to make the paths used in the template file better on the eyes. Instead of having find(‘jobs.static_ips') we have find('jobs.debian_nfs_server.static_ips'). Find will look through the jobs array and find the object that has name=debian_nfs_server. If jobs were a hash then find would get the hash key debian_nfs_server. @param [String] path The path to the object that the template wants to

retrieve from the user's config and substitute in.

@param [Object] obj Either a hash or array which is the user's deployment

config to be looked through.

@return [Object] The found object.

# File lib/cli/commands/biff.rb, line 122
def find(path, obj = @deployment_obj)
  starting_obj = obj
  path_split = path.split(".")
  found_so_far = []
  path_split.each do |path_part|
    obj = lookup(path_part, obj)

    unless obj
      @errors += 1
      say("Could not find #{path.make_red}.")
      say("'#{@template_file}' has it but '#{@deployment_file}' does not.")
      #say("\nIt should exist in \n#{obj.to_yaml}\n")
      if starting_obj == @deployment_obj
        # To cut down on complexity, we don't print out the section of code
        # from the template YAML that the user needs if the find method was
        # called with any other starting object other than deployment_obj.
        # The reason for this is because we'd have to recursively find the
        # path to the starting object so that it can be found in the
        # template.
        print_the_template_path(path.split('.'), found_so_far)
      end
      break
    end
    found_so_far << path_part
  end
  obj
end
find_in(path, obj) click to toggle source

Alias for find. It is used to find within a given object, not the default deployment_obj @param [String] path The path to the object that the template wants to

retrieve from the user's config and substitute in.

@param [Object] obj Either a hash or array which is the user's deployment

config to be looked through.

@return [Object] The found object.

# File lib/cli/commands/biff.rb, line 107
def find_in(path, obj)
  find(path, obj)
end
get_first_last_from_range(range, netw_cidr) click to toggle source

Helper function for getting the first and last number from a range, but also taking into account negative ranges and the network size. @param [Range] range The range. @param [String] netw_name The cidr network object. @return [Array] The first and last number.

# File lib/cli/commands/biff.rb, line 231
def get_first_last_from_range(range, netw_cidr)
  first = (range.first >= 0) ? range.first :
      netw_cidr.size + range.first
  last = (range.last >= 0) ? range.last :
      netw_cidr.size + range.last
  return [first, last]
end
get_helper(netw_name) click to toggle source

Returns the array of IPs for a network name. @param [String] netw_name The name of the network, such as default.static. @return [Array|CIDR] An array or CIDR object that behaves like an array.

# File lib/cli/commands/biff.rb, line 276
def get_helper(netw_name)
  netw_name, type = netw_name.split(".")
  type ||= "range"
  @ip_helper[netw_name][type]
end
get_network_and_mask(netw_name) click to toggle source

Gets the network's network/mask for configuring things such as the nfs_server properties. E.x. 192.168.1.0/22 @param [String] netw_name The name of the network to get the network/mast

from.

@return [String] The network/mask.

# File lib/cli/commands/biff.rb, line 221
def get_network_and_mask(netw_name)
  netw_cidr = get_helper(netw_name)
  "#{netw_cidr.network}#{netw_cidr.netmask}"
end
get_range(subnets) click to toggle source

Gets the range section out of the user's deployment config and creates a CIDR object. This is used for calculating IPs in the .erb template that uses the ip and ip_range methods. @param [Array] subnets The subnets in a network configuration. @return [CIDR] A CIDR object.

# File lib/cli/commands/biff.rb, line 287
def get_range(subnets)
  NetAddr::CIDR.create(subnets.first["range"])
end
get_static_ranges(subnets) click to toggle source

Gets the static ranges out of the user's deployment config and creates an array of CIDR objects. This is used for calculating IPs in the .erb template that uses the ip and ip_range methods. @param [Array] subnets The subnets in a network configuration. @return [Array] An array of CIDR objects.

# File lib/cli/commands/biff.rb, line 296
def get_static_ranges(subnets)
  static_ranges = subnets.first["static"]
  if !static_ranges || static_ranges.empty?
    return nil
  end
  static_ips = []
  static_ranges.each do |static_range|
    range_split = static_range.split("-")
    if range_split.size == 1
      static_ips.push(range_split[0])
      next
    end

    start_range = NetAddr::CIDR.create(range_split[0].strip)
    end_range = NetAddr::CIDR.create(range_split[1].strip)
    (start_range..end_range).each do |ip_entry|
      static_ips.push(ip_entry)
    end
  end
  static_ips
end
ip(ip_num, netw_name) click to toggle source

Used by the template to specify IPs for jobs. It uses the CIDR tool to get them. Netw_name can include .range or .static, such as default.static to choose what range the IPs are based on. @param [Integer] ip_num The nth IP number to get. @param [String] netw_name The name of the network to get the IP from. @return [String] An IP in the network.

# File lib/cli/commands/biff.rb, line 245
def ip(ip_num, netw_name)
  ip_range((ip_num..ip_num), netw_name)
end
ip_range(range, netw_name) click to toggle source

Used by the template to specify IP ranges for jobs. It uses the CIDR tool to get them. Accepts negative ranges. @param [Range] range The range of IPs to return, such as 10..24 @param [String] netw_name The name of the network to get the IPs from. @return [String] An IP return in the network.

# File lib/cli/commands/biff.rb, line 254
def ip_range(range, netw_name)
  netw_cidr = get_helper(netw_name)
  first, last = get_first_last_from_range(range, netw_cidr)
  raise_range_err = false
  begin
    unless netw_cidr[first] and netw_cidr[last]
      raise_range_err = true
    end
  rescue NetAddr::BoundaryError => e
    raise_range_err = true
  end
  if raise_range_err
    err("IP range '#{range}' is not within the bounds of network " +
        "'#{netw_name}', which only has #{netw_cidr.size} IPs.")
  end
  first == last ? "#{netw_cidr[first].ip}" :
      "#{netw_cidr[first].ip} - #{netw_cidr[last].ip}"
end
keep_new_file() click to toggle source

Asks if the user would like to keep the new template and copies it over their existing template if yes. This is its own function for testing.

# File lib/cli/commands/biff.rb, line 361
def keep_new_file
  copy_to_file = @diff_works ? @deployment_file : @deployment_file + ".new"
  agree_text = @diff_works ?
      KEEP_NEW_VERSION_TEXT : (DIFF_FAILED_KEEP_NEW_TEXT % copy_to_file)
  if non_interactive? or agree(agree_text)
    say("New version copied to '#{copy_to_file}'")
    FileUtils.cp(@temp_file_path_2, copy_to_file)
  end
end
load_template_as_yaml() click to toggle source

Loads the template file as YAML. First, it replaces all of the ruby syntax. This file is used so that when there is an error, biff can report what the user's deployment needs according to this template. @return [String] The loaded template file as a ruby object.

# File lib/cli/commands/biff.rb, line 209
def load_template_as_yaml
  temp_data = File.read(@template_file)
  temp_data.gsub!(/<%=.*%>/, "INSERT_DATA_HERE")
  temp_data.gsub!(/[ ]*<%.*%>[ ]*\n/, "")
  Psych::load(temp_data)
end
lookup(path, obj) click to toggle source
# File lib/cli/commands/biff.rb, line 150
def lookup(path, obj)
  case obj
    when Array
      obj.find { |value| path == value['name'] }
    when Hash
      obj[path] if obj.has_key?(path)
  end
end
print_string_diff(str1, str2) click to toggle source

Takes two strings and prints the diff of them. @param [String] str1 The first string to diff. @param [String] str2 The string to diff against.

print_the_template_path(looking_for_path, users_farthest_found_path) click to toggle source

Tries to print out some helpful output from the template to let the user know what they're missing. For instance, if the user doesn't have a job and the template needs to pull some data from the job then it will print out what the job looks like in the template. This method can't be used if the path is a relative path. A relative path is when find_in was used. @param [Array] looking_for_path The path that is being looked for in the

user's deployment config but does not exist.

@param [Array] users_farthest_found_path The farthest that 'find' got in

finding the looking_for_path.
random_string(length, name=nil) click to toggle source

Generate a random string for passwords and tokens. Length is the length of the string. name is an optional name of a previously generated string. This is used to allow setting the same password for different components.

# File lib/cli/commands/biff.rb, line 391
def random_string(length, name=nil)
  random_string = SecureRandom.hex(length)[0...length]

  @random_cache ||= {}
  if name
    @random_cache[name] ||= random_string
  else
    random_string
  end
end
setup(template) click to toggle source

Sets up a few instance variables. @param [String] template The string path to the template that should be

used.
# File lib/cli/commands/biff.rb, line 374
def setup(template)
  @errors = 0
  @template_file = template
  @deployment_file = deployment
  err("Deployment not set.") if @deployment_file.nil?
  @deployment_obj = load_yaml_file(deployment)
  @template_obj = load_template_as_yaml
  @ip_helper = create_ip_helper
  @dir_name = Dir.mktmpdir
  @temp_file_path_1 = "#{@dir_name}/bosh_biff_1"
  @temp_file_path_2 = "#{@dir_name}/bosh_biff_2"
end