class ChefCLI::Policyfile::DSL

Constants

RUN_LIST_ITEM_COMPONENT

Attributes

chef_config[R]
cookbook_location_specs[R]
default_source[W]
errors[R]
included_policies[R]
name[W]
named_run_lists[R]
node_attributes[R]
run_list[W]
storage_config[R]

Public Class Methods

new(storage_config, chef_config: nil) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 48
def initialize(storage_config, chef_config: nil)
  @name = nil
  @errors = []
  @run_list = []
  @named_run_lists = {}
  @included_policies = []
  @default_source = [ NullCookbookSource.new ]
  @cookbook_location_specs = {}
  @storage_config = storage_config
  @chef_config = chef_config

  @node_attributes = Chef::Node::Attribute.new({}, {}, {}, {})
end

Public Instance Methods

cookbook(name, *version_and_source_opts) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 106
def cookbook(name, *version_and_source_opts)
  source_options =
    if version_and_source_opts.last.is_a?(Hash)
      version_and_source_opts.pop
    else
      {}
    end

  constraint = version_and_source_opts.first || ">= 0.0.0"
  spec = CookbookLocationSpecification.new(name, constraint, source_options, storage_config)

  if ( existing_source = @cookbook_location_specs[name] )
    err = "Cookbook '#{name}' assigned to conflicting sources\n\n"
    err << "Previous source: #{existing_source.source_options.inspect}\n"
    err << "Conflicts with: #{source_options.inspect}\n"
    @errors << err
  else
    @cookbook_location_specs[name] = spec
    @errors += spec.errors
  end
end
default() click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 156
def default
  @node_attributes.default
end
default_source(source_type = nil, source_argument = nil, &block) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 87
def default_source(source_type = nil, source_argument = nil, &block)
  return @default_source if source_type.nil?

  case source_type
  when :community, :supermarket
    set_default_community_source(source_argument, &block)
  when :delivery_supermarket
    set_default_delivery_supermarket_source(source_argument, &block)
  when :chef_server
    set_default_chef_server_source(source_argument, &block)
  when :chef_repo
    set_default_chef_repo_source(source_argument, &block)
  when :artifactory
    set_default_artifactory_source(source_argument, &block)
  else
    @errors << "Invalid default_source type '#{source_type.inspect}'"
  end
end
eval_policyfile(policyfile_string) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 164
def eval_policyfile(policyfile_string)
  @policyfile_filename = policyfile_filename
  instance_eval(policyfile_string, policyfile_filename)
  validate!
  self
rescue SyntaxError => e
  @errors << "Invalid Ruby syntax in Policyfile '#{policyfile_filename}':\n\n#{e.message}"
rescue SignalException, SystemExit
  # allow signal from kill, ctrl-C, etc. to bubble up:
  raise
rescue Exception => e
  error_message = "Evaluation of policyfile '#{policyfile_filename}' raised an exception\n"
  error_message << "  Exception: #{e.class.name} \"#{e}\"\n\n"
  trace = filtered_bt(policyfile_filename, e)
  error_message << "  Relevant Code:\n"
  error_message << "    #{error_context(policyfile_string, policyfile_filename, e)}\n\n"
  unless trace.empty?
    error_message << "  Backtrace:\n"
    # TODO: need a way to disable filtering
    error_message << filtered_bt(policyfile_filename, e).inject("") { |formatted_trace, line| formatted_trace << "    #{line}\n" }
  end
  @errors << error_message
end
include_policy(name, source_options = {}) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 128
def include_policy(name, source_options = {})
  if ( existing = included_policies.find { |p| p.name == name } )
    err = "Included policy '#{name}' assigned conflicting locations or was already specified\n\n"
    err << "Previous source: #{existing.source_options.inspect}\n"
    err << "Conflicts with: #{source_options.inspect}\n"
    @errors << err
  else
    spec = PolicyfileLocationSpecification.new(name, source_options, storage_config, chef_config)
    included_policies << spec
    @errors += spec.errors
  end
end
metadata() click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 141
def metadata
  cookbook_root = storage_config.relative_paths_root
  unless File.exist?(File.join(cookbook_root, "metadata.rb")) || File.exist?(File.join(cookbook_root, "metadata.json"))
    raise PolicyfileMissingCookbookMetadata.new(cookbook_root)
  end

  begin
    cookbook_name = CookbookMetadata.from_path(cookbook_root).cookbook_name
  rescue Exception => e
    raise PolicyfileBadCookbookMetadata.new(cookbook_root, e)
  end
  name cookbook_name if name.nil?
  cookbook(cookbook_name, path: ".")
end
name(name = nil) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 62
def name(name = nil)
  unless name.nil?
    @name = name
  end
  @name
end
named_run_list(name, *run_list_items) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 78
def named_run_list(name, *run_list_items)
  run_list_items = run_list_items.flatten
  unless run_list_items.empty?
    validate_run_list_items(run_list_items, name)
    @named_run_lists[name] = run_list_items
  end
  @named_run_lists[name]
end
override() click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 160
def override
  @node_attributes.override
end
run_list(*run_list_items) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 69
def run_list(*run_list_items)
  run_list_items = run_list_items.flatten
  unless run_list_items.empty?
    validate_run_list_items(run_list_items)
    @run_list = run_list_items
  end
  @run_list
end

Private Instance Methods

culprit_line_number(policyfile_filename, exception) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 290
def culprit_line_number(policyfile_filename, exception)
  if ( most_proximate_backtrace_line = filtered_bt(policyfile_filename, exception).first )
    most_proximate_backtrace_line[/^(?:.\:)?[^:]+:([\d]+)/, 1].to_i
  else
    nil
  end
end
error_context(policyfile_string, policyfile_filename, exception) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 281
def error_context(policyfile_string, policyfile_filename, exception)
  if ( line_number_to_show = culprit_line_number(policyfile_filename, exception) )
    code = policyfile_string.lines.to_a[line_number_to_show - 1].strip
    "#{line_number_to_show}: #{code}"
  else
    "Could not find relevant code from backtrace"
  end
end
filtered_bt(policyfile_filename, exception) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 298
def filtered_bt(policyfile_filename, exception)
  policyfile_filename_matcher = /^#{Regexp.escape(policyfile_filename)}/
  exception.backtrace.select { |line| line =~ policyfile_filename_matcher }
end
handle_preferred_cookbooks_conflicts() click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 266
def handle_preferred_cookbooks_conflicts
  conflicting_source_messages = []
  default_source.combination(2).each do |source_a, source_b|
    conflicting_preferences = source_a.preferred_cookbooks & source_b.preferred_cookbooks
    next if conflicting_preferences.empty?

    conflicting_source_messages << "#{source_a.desc} and #{source_b.desc} are both set as the preferred source for cookbook(s) '#{conflicting_preferences.join(", ")}'"
  end
  unless conflicting_source_messages.empty?
    msg = "Multiple sources are marked as the preferred source for some cookbooks. Only one source can be preferred for a cookbook.\n"
    msg << conflicting_source_messages.join("\n") << "\n"
    @errors << msg
  end
end
set_default_artifactory_source(source_uri, &block) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 210
def set_default_artifactory_source(source_uri, &block)
  if source_uri.nil?
    @errors << "You must specify the server's URI when using a default_source :artifactory"
  else
    set_default_source(ArtifactoryCookbookSource.new(source_uri, chef_config: chef_config, &block))
  end
end
set_default_chef_repo_source(path, &block) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 218
def set_default_chef_repo_source(path, &block)
  if path.nil?
    @errors << "You must specify the path to the chef-repo when using a default_source :chef_repo"
  else
    set_default_source(ChefRepoCookbookSource.new(File.expand_path(path, storage_config.relative_paths_root), &block))
  end
end
set_default_chef_server_source(source_uri, &block) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 202
def set_default_chef_server_source(source_uri, &block)
  if source_uri.nil?
    @errors << "You must specify the server's URI when using a default_source :chef_server"
  else
    set_default_source(ChefServerCookbookSource.new(source_uri, chef_config: chef_config, &block))
  end
end
set_default_community_source(source_uri, &block) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 190
def set_default_community_source(source_uri, &block)
  set_default_source(CommunityCookbookSource.new(source_uri, &block))
end
set_default_delivery_supermarket_source(source_uri, &block) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 194
def set_default_delivery_supermarket_source(source_uri, &block)
  if source_uri.nil?
    @errors << "You must specify the server's URI when using a default_source :delivery_supermarket"
  else
    set_default_source(DeliverySupermarketSource.new(source_uri, &block))
  end
end
set_default_source(source) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 226
def set_default_source(source)
  @default_source.delete_at(0) if @default_source[0].null?
  @default_source << source
end
validate!() click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 231
def validate!
  if @run_list.empty?
    @errors << "Invalid run_list. run_list cannot be empty"
  end

  handle_preferred_cookbooks_conflicts
end
validate_run_list_items(items, run_list_name = nil) click to toggle source
# File lib/chef-cli/policyfile/dsl.rb, line 239
def validate_run_list_items(items, run_list_name = nil)
  items.each do |item|

    run_list_desc = run_list_name.nil? ? "Run List Item '#{item}'" : "Named Run List '#{run_list_name}' Item '#{item}'"

    item_name = Chef::RunList::RunListItem.new(item).name
    cookbook, separator, recipe = item_name.partition("::")

    if RUN_LIST_ITEM_COMPONENT.match(cookbook).nil?
      message = "#{run_list_desc} has invalid cookbook name '#{cookbook}'.\nCookbook names can only contain alphanumerics, hyphens, and underscores."

      # Special case when there's only one colon instead of two:
      if /[^:]:[^:]/.match?(cookbook)
        message << "\nDid you mean '#{item.sub(":", "::")}'?"
      end

      @errors << message
    end
    unless separator.empty?
      # we have a cookbook and recipe
      if RUN_LIST_ITEM_COMPONENT.match(recipe).nil?
        @errors << "#{run_list_desc} has invalid recipe name '#{recipe}'.\nRecipe names can only contain alphanumerics, hyphens, and underscores."
      end
    end
  end
end