class ChefCLI::Policyfile::DSL
def cookbook(name, *version_and_source_opts)
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
def culprit_line_number(policyfile_filename, exception)
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
def default
def default @node_attributes.default end
def default_source(source_type = nil, source_argument = nil, &block)
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
def error_context(policyfile_string, policyfile_filename, exception)
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
def eval_policyfile(policyfile_string)
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
def filtered_bt(policyfile_filename, exception)
def filtered_bt(policyfile_filename, exception) policyfile_filename_matcher = /^#{Regexp.escape(policyfile_filename)}/ exception.backtrace.select { |line| line =~ policyfile_filename_matcher } end
def handle_preferred_cookbooks_conflicts
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
def include_policy(name, source_options = {})
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
def initialize(storage_config, chef_config: nil)
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
def name(name = nil)
def name(name = nil) unless name.nil? @name = name end @name end
def named_run_list(name, *run_list_items)
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
def override
def override @node_attributes.override end
def run_list(*run_list_items)
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
def set_default_artifactory_source(source_uri, &block)
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
def set_default_chef_repo_source(path, &block)
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
def set_default_chef_server_source(source_uri, &block)
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
def set_default_community_source(source_uri, &block)
def set_default_community_source(source_uri, &block) set_default_source(CommunityCookbookSource.new(source_uri, &block)) end
def set_default_delivery_supermarket_source(source_uri, &block)
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
def set_default_source(source)
def set_default_source(source) @default_source.delete_at(0) if @default_source[0].null? @default_source << source end
def validate!
def validate! if @run_list.empty? @errors << "Invalid run_list. run_list cannot be empty" end handle_preferred_cookbooks_conflicts end
def validate_run_list_items(items, run_list_name = nil)
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 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