class ChefCore::TargetResolver

This class will resolve a provided target host name to one or more TargetHost instances. The target host name passed in to the constructor can contain up to two ranges in the form [0-9] and [a-z]. Multiple target host names can be passed in asa comma-separated list.

Use resolver_inst.targets to get the expanded list of TargetHost instances.

Constants

MAX_EXPANDED_TARGETS
SUPPORTED_PROTOCOLS
TARGET_WITH_RANGE

A string matching PREFIXPOSTFIX: POSTFIX can contain further ranges itself This uses a greedy match (.*) to get include every character up to the last “[” in PREFIX $1 - prefix; $2 - x, $3 - y, $4 unproccessed/remaining text

Public Class Methods

new(target, default_protocol, conn_options, max_expanded_targets: MAX_EXPANDED_TARGETS) click to toggle source
# File lib/chef_core/target_resolver.rb, line 32
def initialize(target, default_protocol, conn_options, max_expanded_targets: MAX_EXPANDED_TARGETS)
  @max_expanded_targets = max_expanded_targets
  @default_proto = default_protocol
  @unparsed_target = target
  @split_targets = @unparsed_target.split(",")
  @conn_options = conn_options.dup
  @default_password = @conn_options.delete(:password)
  @default_user = @conn_options.delete(:user)
end

Public Instance Methods

config_for_target(url) click to toggle source
# File lib/chef_core/target_resolver.rb, line 65
def config_for_target(url)
  prefix, target = prefix_from_target(url)

  inline_password = nil
  inline_user = nil
  host = target
  # Default greedy-scan of the regex means that
  # $2 will resolve to content after the final "@"
  # URL credentials will take precedence over the default :user
  # in @conn_opts
  if target =~ /(.*)@(.*)/
    inline_credentials = $1
    host = $2
    # We'll use a non-greedy match to grab everthinmg up to the first ':'
    # as username if there is no :, credentials is just the username
    if inline_credentials =~ /(.+?):(.*)/
      inline_user = $1
      inline_password = $2
    else
      inline_user = inline_credentials
    end
  end
  user, password = make_credentials(inline_user, inline_password)
  { url: "#{prefix}#{host}",
    user: user,
    password: password }
end
expand_targets(target) click to toggle source
# File lib/chef_core/target_resolver.rb, line 119
def expand_targets(target)
  @current_target = target # Hold onto this for error reporting
  do_parse([target.downcase])
end
make_credentials(inline_user, inline_password) click to toggle source

Merge the inline user/pass with the default user/pass, giving precedence to inline.

# File lib/chef_core/target_resolver.rb, line 95
def make_credentials(inline_user, inline_password)
  user = inline_user || @default_user
  user = nil if user && user.empty?
  password = (inline_password || @default_password)
  password = nil if password && password.empty?
  [user, password]
end
prefix_from_target(target) click to toggle source
# File lib/chef_core/target_resolver.rb, line 103
def prefix_from_target(target)
  if target =~ %r{^(.+?)://(.*)}
    # We'll store the existing prefix to avoid it interfering
    # with the check further below.
    if SUPPORTED_PROTOCOLS.include? $1.downcase
      prefix = "#{$1}://"
      target = $2
    else
      raise UnsupportedProtocol.new($1)
    end
  else
    prefix = "#{@default_proto}://"
  end
  [prefix, target]
end
targets() click to toggle source

Returns the list of targets as an array of TargetHost instances

# File lib/chef_core/target_resolver.rb, line 43
def targets
  return @targets unless @targets.nil?

  expanded_urls = []
  @split_targets.each do |target|
    expand_targets(target).each { |t| expanded_urls << t }
  end

  # Apply max_expanded_targets to the total list of resolved
  # targets, since multiple targets can be comma-separated.
  # Limiting it only in the expression resolver could still
  # yield more than the maximum.
  if expanded_urls.length > @max_expanded_targets
    raise TooManyTargets.new(expanded_urls.length, @max_expanded_targets)
  end

  @targets = expanded_urls.map do |url|
    config = @conn_options.merge(config_for_target(url))
    TargetHost.new(config.delete(:url), config)
  end
end

Private Instance Methods

do_parse(targets, depth = 0) click to toggle source
# File lib/chef_core/target_resolver.rb, line 133
def do_parse(targets, depth = 0)
  raise TooManyRanges.new(@current_target) if depth > 2

  new_targets = []
  done = false
  targets.each do |target|
    if TARGET_WITH_RANGE =~ target
      # $1 - prefix; $2 - x, $3 - y, $4 unprocessed/remaining text
      expand_range(new_targets, $1, $2, $3, $4)
    else
      # Nothing more to expand
      done = true
      new_targets << target
    end
  end
  if done
    new_targets
  else
    do_parse(new_targets, depth + 1)
  end
end
expand_range(dest, prefix, start, stop, suffix) click to toggle source
# File lib/chef_core/target_resolver.rb, line 155
def expand_range(dest, prefix, start, stop, suffix)
  prefix ||= ""
  suffix ||= ""
  start_is_int = Integer(start) >= 0 rescue false
  stop_is_int = Integer(stop) >= 0 rescue false

  if (start_is_int && !stop_is_int) || (stop_is_int && !start_is_int)
    raise InvalidRange.new(@current_target, "[#{start}:#{stop}]")
  end

  # Ensure that a numeric range doesn't get created as a string, which
  # would make the created Range further below fail to iterate for some values
  # because of ASCII sorting.
  if start_is_int
    start = Integer(start)
  end

  if stop_is_int
    stop = Integer(stop)
  end

  # For range to iterate correctly, the values must
  # be low,high
  if start > stop
    temp = stop; stop = start; start = temp
  end
  Range.new(start, stop).each do |value|
    # Ranges will resolve only numbers and letters,
    # not other ascii characters that happen to fall between.
    if start_is_int || /^[a-z0-9]/ =~ value
      dest << "#{prefix}#{value}#{suffix}"
    end
    # Stop expanding as soon as we go over limit to prevent
    # making the user wait for a massive accidental expansion
    if dest.length > @max_expanded_targets
      raise TooManyTargets.new(@split_targets.length, @max_expanded_targets)
    end
  end
end