diff --git a/.travis.yml b/.travis.yml index b8eb2855618..7d6c61fcbcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,15 @@ before_install: - gem update --system 2.1.11 - gem --version -rvm: - - 1.8.7 - - 1.9.3 - - 2.0.0 - - 2.1.0 +matrix: + include: + - rvm: 1.8.7 + - rvm: 1.9.3 + - rvm: 2.0.0 + - rvm: 2.1.0 + - rvm: 2.1.0 + gemfile: pedant.gemfile + script: bundle exec rake pedant branches: only: diff --git a/Rakefile b/Rakefile index 2e0c203b499..b55ed8321ba 100644 --- a/Rakefile +++ b/Rakefile @@ -48,6 +48,10 @@ task :ship => :gem do end end +task :pedant do + require File.expand_path('spec/support/pedant/run_pedant') +end + begin require 'yard' DOC_FILES = [ "README.rdoc", "LICENSE", "spec/tiny_server.rb", "lib/**/*.rb" ] @@ -63,5 +67,3 @@ begin rescue LoadError puts "yard is not available. (sudo) gem install yard to generate yard documentation." end - - diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 1fedc983801..b84fc1945da 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -23,6 +23,7 @@ require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/file_system/memory_root' +require 'fileutils' class Chef module ChefFS @@ -43,7 +44,11 @@ def create_dir(path, name, *options) @memory_store.create_dir(path, name, *options) else with_dir(path) do |parent| - parent.create_child(chef_fs_filename(path + [name]), nil) + begin + parent.create_child(chef_fs_filename(path + [name]), nil) + rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e + raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e) + end end end end @@ -61,7 +66,11 @@ def create(path, name, data, *options) end with_dir(path) do |parent| - parent.create_child(chef_fs_filename(path + [name]), data) + begin + parent.create_child(chef_fs_filename(path + [name]), data) + rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e + raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e) + end end end end @@ -82,7 +91,13 @@ def get(path, request=nil) with_entry(path) do |entry| if path[0] == 'cookbooks' && path.length == 3 # get /cookbooks/NAME/version - result = entry.chef_object.to_hash + result = nil + begin + result = entry.chef_object.to_hash + rescue Chef::ChefFS::FileSystem::NotFoundError => e + raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) + end + result.each_pair do |key, value| if value.is_a?(Array) value.each do |file| @@ -102,7 +117,11 @@ def get(path, request=nil) JSON.pretty_generate(result) else - entry.read + begin + entry.read + rescue Chef::ChefFS::FileSystem::NotFoundError => e + raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) + end end end end @@ -121,7 +140,12 @@ def set(path, data, *options) write_cookbook(path, data, *options) else with_dir(path[0..-2]) do |parent| - parent.create_child(chef_fs_filename(path), data) + child = parent.child(chef_fs_filename(path)) + if child.exists? + child.write(data) + else + parent.create_child(chef_fs_filename(path), data) + end end end end @@ -132,10 +156,14 @@ def delete(path) @memory_store.delete(path) else with_entry(path) do |entry| - if path[0] == 'cookbooks' && path.length >= 3 - entry.delete(true) - else - entry.delete(false) + begin + if path[0] == 'cookbooks' && path.length >= 3 + entry.delete(true) + else + entry.delete(false) + end + rescue Chef::ChefFS::FileSystem::NotFoundError => e + raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end end @@ -146,7 +174,11 @@ def delete_dir(path, *options) @memory_store.delete_dir(path, *options) else with_entry(path) do |entry| - entry.delete(options.include?(:recursive)) + begin + entry.delete(options.include?(:recursive)) + rescue Chef::ChefFS::FileSystem::NotFoundError => e + raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) + end end end end @@ -172,10 +204,16 @@ def list(path) elsif path[0] == 'cookbooks' && path.length == 2 if Chef::Config.versioned_cookbooks - # list /cookbooks/name = filter /cookbooks/name-version down to name - entry.children.map { |child| split_name_version(child.name) }. - select { |name, version| name == path[1] }. - map { |name, version| version }.to_a + result = with_entry([ 'cookbooks' ]) do |entry| + # list /cookbooks/name = filter /cookbooks/name-version down to name + entry.children.map { |child| split_name_version(child.name) }. + select { |name, version| name == path[1] }. + map { |name, version| version } + end + if result.empty? + raise ChefZero::DataStore::DataNotFoundError.new(path) + end + result else # list /cookbooks/name = version = get_single_cookbook_version(path) @@ -186,12 +224,12 @@ def list(path) with_entry(path) do |entry| begin entry.children.map { |c| zero_filename(c) }.sort - rescue Chef::ChefFS::FileSystem::NotFoundError + rescue Chef::ChefFS::FileSystem::NotFoundError => e # /cookbooks, /data, etc. never return 404 if path_always_exists?(path) [] else - raise + raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end end @@ -223,12 +261,13 @@ def use_memory_store?(path) end def write_cookbook(path, data, *options) - # Create a little Chef::ChefFS memory filesystem with the data if Chef::Config.versioned_cookbooks - cookbook_path = "cookbooks/#{path[1]}-#{path[2]}" + cookbook_path = File.join('cookbooks', "#{path[1]}-#{path[2]}") else - cookbook_path = "cookbooks/#{path[1]}" + cookbook_path = File.join('cookbooks', path[1]) end + + # Create a little Chef::ChefFS memory filesystem with the data cookbook_fs = Chef::ChefFS::FileSystem::MemoryRoot.new('uploading') cookbook = JSON.parse(data, :create_additions => false) cookbook.each_pair do |key, value| @@ -236,15 +275,22 @@ def write_cookbook(path, data, *options) value.each do |file| if file.is_a?(Hash) && file.has_key?('checksum') file_data = @memory_store.get(['file_store', 'checksums', file['checksum']]) - cookbook_fs.add_file("#{cookbook_path}/#{file['path']}", file_data) + cookbook_fs.add_file(File.join(cookbook_path, file['path']), file_data) end end end end - # Use the copy/diff algorithm to copy it down so we don't destroy - # chefignored data. This is terribly un-thread-safe. - Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), cookbook_fs, chef_fs, nil, {:purge => true}) + # Create the .uploaded-cookbook-version.json + cookbooks = chef_fs.child('cookbooks') + if !cookbooks.exists? + cookbooks = chef_fs.create_child('cookbooks') + end + # We are calling a cookbooks-specific API, so get multiplexed_dirs out of the way if it is there + if cookbooks.respond_to?(:multiplexed_dirs) + cookbooks = cookbooks.write_dir + end + cookbooks.write_cookbook(cookbook_path, data, cookbook_fs) end def split_name_version(entry_name) @@ -343,8 +389,10 @@ def with_entry(path) end def with_dir(path) + # Do not automatically create data bags + create = !(path[0] == 'data' && path.size >= 2) begin - yield get_dir(_to_chef_fs_path(path), true) + yield get_dir(_to_chef_fs_path(path), create) rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb index 5203637012a..b151db69739 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb @@ -44,11 +44,17 @@ def chef_object end loader.load_cookbooks - return loader.cookbook_version - rescue - Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}") + cb = loader.cookbook_version + if !cb + Chef::Log.error("Cookbook #{file_path} empty.") + raise "Cookbook #{file_path} empty." + end + cb + rescue => e + Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{e}") + Chef::Log.error(e.backtrace.join("\n")) + raise end - nil end def children @@ -66,6 +72,8 @@ def can_have_child?(name, is_dir) if is_dir # Only the given directories will be uploaded. return CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != 'root_files' + elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE + return false end super(name, is_dir) end @@ -81,6 +89,14 @@ def canonical_cookbook_name(entry_name) self.class.canonical_cookbook_name(entry_name) end + def uploaded_cookbook_version_path + File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE) + end + + def can_upload? + File.exists?(uploaded_cookbook_version_path) || children.size > 0 + end + protected def make_child(child_name) diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb index 6e16f18f241..7c60b511140 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb @@ -43,7 +43,7 @@ def children map { |child_name| make_child(child_name) }. select do |entry| # empty cookbooks and cookbook directories are ignored - if entry.children.size == 0 + if !entry.can_upload? Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}") false else @@ -59,6 +59,25 @@ def can_have_child?(name, is_dir) is_dir && !name.start_with?('.') end + def write_cookbook(cookbook_path, cookbook_version_json, from_fs) + cookbook_name = File.basename(cookbook_path) + child = make_child(cookbook_name) + + # Use the copy/diff algorithm to copy it down so we don't destroy + # chefignored data. This is terribly un-thread-safe. + Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, child, nil, {:purge => true}) + + # Write out .uploaded-cookbook-version.json + cookbook_file_path = File.join(file_path, cookbook_name) + if !File.exists?(cookbook_file_path) + FileUtils.mkdir_p(cookbook_file_path) + end + uploaded_cookbook_version_path = File.join(cookbook_file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE) + File.open(uploaded_cookbook_version_path, 'w') do |file| + file.write(cookbook_version_json) + end + end + protected def make_child(child_name) diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb index 46d4eb55380..1af7e618de2 100644 --- a/lib/chef/chef_fs/file_system/file_system_entry.rb +++ b/lib/chef/chef_fs/file_system/file_system_entry.rb @@ -18,8 +18,9 @@ require 'chef/chef_fs/file_system/base_fs_dir' require 'chef/chef_fs/file_system/rest_list_dir' -require 'chef/chef_fs/file_system/not_found_error' +require 'chef/chef_fs/file_system/already_exists_error' require 'chef/chef_fs/file_system/must_delete_recursively_error' +require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/path_utils' require 'fileutils' @@ -48,15 +49,18 @@ def children def create_child(child_name, file_contents=nil) child = make_child(child_name) + if child.exists? + raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) + end if file_contents child.write(file_contents) else begin Dir.mkdir(child.file_path) rescue Errno::EEXIST + raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end end - @children = nil child end @@ -75,6 +79,10 @@ def delete(recurse) end end + def exists? + File.exists?(file_path) + end + def read begin File.open(file_path, "rb") {|f| f.read} diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index e98da77d7fa..eb67f3d7c58 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -17,13 +17,19 @@ class CookbookVersionLoader :resource_filenames, :provider_filenames] + UPLOADED_COOKBOOK_VERSION_FILE = ".uploaded-cookbook-version.json".freeze attr_reader :cookbook_name attr_reader :cookbook_settings + attr_reader :cookbook_paths attr_reader :metadata_filenames + attr_reader :frozen + attr_reader :uploaded_cookbook_version_file def initialize(path, chefignore=nil) - @cookbook_path = File.expand_path( path ) + @cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded + # We keep a list of all cookbook paths that have been merged in + @cookbook_paths = [ @cookbook_path ] @cookbook_name = File.basename( path ) @chefignore = chefignore @metadata = Hash.new @@ -56,12 +62,21 @@ def load_cookbooks remove_ignored_files + if File.exists?(File.join(@cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)) + @uploaded_cookbook_version_file = File.join(@cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE) + end + if File.exists?(File.join(@cookbook_path, "metadata.rb")) @metadata_filenames << File.join(@cookbook_path, "metadata.rb") elsif File.exists?(File.join(@cookbook_path, "metadata.json")) @metadata_filenames << File.join(@cookbook_path, "metadata.json") + elsif @uploaded_cookbook_version_file + @metadata_filenames << @uploaded_cookbook_version_file end + # Set frozen based on .uploaded-cookbook-version.json + set_frozen + if empty? Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping." end @@ -71,8 +86,7 @@ def load_cookbooks def cookbook_version return nil if empty? - Chef::CookbookVersion.new(@cookbook_name.to_sym).tap do |c| - c.root_dir = @cookbook_path + Chef::CookbookVersion.new(@cookbook_name.to_sym, *@cookbook_paths).tap do |c| c.attribute_filenames = cookbook_settings[:attribute_filenames].values c.definition_filenames = cookbook_settings[:definition_filenames].values c.recipe_filenames = cookbook_settings[:recipe_filenames].values @@ -84,6 +98,7 @@ def cookbook_version c.root_filenames = cookbook_settings[:root_filenames].values c.metadata_filenames = @metadata_filenames c.metadata = metadata(c) + c.freeze_version if @frozen end end @@ -94,6 +109,8 @@ def metadata(cookbook_version) case metadata_file when /\.rb$/ apply_ruby_metadata(metadata_file) + when @uploaded_cookbook_version_file + apply_json_cookbook_version_metadata(metadata_file) when /\.json$/ apply_json_metadata(metadata_file) else @@ -104,7 +121,7 @@ def metadata(cookbook_version) end def empty? - @cookbook_settings.values.all? { |files_hash| files_hash.empty? } + @cookbook_settings.values.all? { |files_hash| files_hash.empty? } && @metadata_filenames.size == 0 end def merge!(other_cookbook_loader) @@ -113,6 +130,8 @@ def merge!(other_cookbook_loader) file_list.merge!(other_cookbook_settings[file_type]) end @metadata_filenames.concat(other_cookbook_loader.metadata_filenames) + @cookbook_paths += other_cookbook_loader.cookbook_paths + @frozen = true if other_cookbook_loader.frozen end def chefignore @@ -122,6 +141,7 @@ def chefignore def load_root_files Dir.glob(File.join(@cookbook_path, '*'), File::FNM_DOTMATCH).each do |file| next if File.directory?(file) + next if File.basename(file) == UPLOADED_COOKBOOK_VERSION_FILE @cookbook_settings[:root_filenames][file[@relative_path, 1]] = file end end @@ -166,6 +186,27 @@ def apply_json_metadata(file) end end + def apply_json_cookbook_version_metadata(file) + begin + data = Chef::JSONCompat.from_json(IO.read(file), :create_additions => false) + @metadata.from_hash(data['metadata']) + rescue JSON::ParserError + Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in " + file) + raise + end + end + + def set_frozen + if uploaded_cookbook_version_file + begin + data = Chef::JSONCompat.from_json(IO.read(uploaded_cookbook_version_file), :create_additions => false) + @frozen = data['frozen?'] + rescue JSON::ParserError + Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in #{uploaded_cookbook_version_file}") + raise + end + end + end end end end diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 5bd0ca064ca..27dc8ef9e58 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -26,6 +26,8 @@ require 'chef/cookbook/file_vendor' require 'chef/cookbook/metadata' require 'chef/version_class' +require 'pathname' +require 'chef/monkey_patches/pathname' class Chef @@ -42,7 +44,7 @@ class CookbookVersion COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] - attr_accessor :root_dir + attr_accessor :root_paths attr_accessor :definition_filenames attr_accessor :template_filenames attr_accessor :file_filenames @@ -66,6 +68,11 @@ class CookbookVersion attr_reader :recipe_filenames_by_name attr_reader :attribute_filenames_by_short_filename + # The first root path is the primary cookbook dir, from which metadata is loaded + def root_dir + root_paths[0] + end + # This is the one and only method that knows how cookbook files' # checksums are generated. def self.checksum_cookbook_file(filepath) @@ -83,8 +90,9 @@ def self.cache # # === Returns # object:: Duh. :) - def initialize(name) + def initialize(name, *root_paths) @name = name + @root_paths = root_paths @frozen = false @attribute_filenames = Array.new @definition_filenames = Array.new @@ -96,7 +104,6 @@ def initialize(name) @resource_filenames = Array.new @provider_filenames = Array.new @metadata_filenames = Array.new - @root_dir = nil @root_filenames = Array.new @status = :ready @manifest = nil @@ -481,11 +488,11 @@ def generate_manifest_with_urls(&url_generator) end def metadata_json_file - File.join(root_dir, "metadata.json") + File.join(root_paths[0], "metadata.json") end def metadata_rb_file - File.join(root_dir, "metadata.rb") + File.join(root_paths[0], "metadata.rb") end def reload_metadata! @@ -605,42 +612,26 @@ def generate_manifest }) checksums_to_on_disk_paths = {} + if !root_paths || root_paths.size == 0 + Chef::Log.error("Cookbook #{name} does not have root_paths! Cannot generate manifest.") + raise "Cookbook #{name} does not have root_paths! Cannot generate manifest." + end + COOKBOOK_SEGMENTS.each do |segment| segment_filenames(segment).each do |segment_file| next if File.directory?(segment_file) - file_name = nil - path = nil - specificity = "default" - - if segment == :root_files - matcher = segment_file.match(".+/#{Regexp.escape(name.to_s)}/(.+)") - file_name = matcher[1] - path = file_name - elsif segment == :templates || segment == :files - matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+?)/(.+))") - unless matcher - Chef::Log.debug("Skipping file #{segment_file}, as it isn't in any of the proper directories (platform-version, platform or default)") - Chef::Log.debug("You probably need to move #{segment_file} into the 'default' sub-directory") - next - end - path = matcher[1] - specificity = matcher[2] - file_name = matcher[3] - else - matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+))") - path = matcher[1] - file_name = matcher[2] - end + path, specificity = parse_segment_file_from_root_paths(segment, segment_file) + file_name = File.basename(path) csum = self.class.checksum_cookbook_file(segment_file) checksums_to_on_disk_paths[csum] = segment_file rs = Mash.new({ :name => file_name, :path => path, - :checksum => csum + :checksum => csum, + :specificity => specificity }) - rs[:specificity] = specificity manifest[segment] << rs end @@ -656,6 +647,23 @@ def generate_manifest @manifest_records_by_path = extract_manifest_records_by_path(manifest) end + def parse_segment_file_from_root_paths(segment, segment_file) + root_paths.each do |root_path| + pathname = Pathname.new(segment_file).relative_path_from(Pathname.new(root_path)) + + parts = pathname.each_filename.take(2) + # Check if path is actually under root_path + next if parts[0] == '..' + if segment == :templates || segment == :files + return [ pathname.to_s, parts[1] ] + else + return [ pathname.to_s, 'default' ] + end + end + Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.") + raise "Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}." + end + def file_vendor unless @file_vendor @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest) diff --git a/lib/chef/monkey_patches/pathname.rb b/lib/chef/monkey_patches/pathname.rb new file mode 100644 index 00000000000..c0255ae7eab --- /dev/null +++ b/lib/chef/monkey_patches/pathname.rb @@ -0,0 +1,32 @@ +require 'pathname' + +if RUBY_VERSION.to_f < 1.9 + class Pathname + @@old_each_filename = instance_method(:each_filename) + + def each_filename(&block) + if block_given? + EachFilenameEnumerable.new(self).each(&block) + else + EachFilenameEnumerable.new(self) + end + end + + def old_each_filename(&block) + @@old_each_filename.bind(self).call(&block) + end + + class EachFilenameEnumerable + include Enumerable + attr_reader :pathname + + def initialize(pathname) + @pathname = pathname + end + + def each(&block) + @pathname.old_each_filename(&block) + end + end + end +end diff --git a/pedant.gemfile b/pedant.gemfile new file mode 100644 index 00000000000..cb11eb8aec0 --- /dev/null +++ b/pedant.gemfile @@ -0,0 +1,23 @@ +source "https://rubygems.org" +gemspec :name => "chef" + +gem 'rest-client', :github => 'opscode/rest-client' +gem 'chef-pedant', :github => 'opscode/chef-pedant', :ref => '4744d7f318b629ff60a0d22cf02296df36936397' +gem 'chef-zero', :github => 'opscode/chef-zero', :ref => 'c0c95d690e1bb7c17c2d141b4937a36c2072d800' +# TODO figure out how to grab this stuff from the main Gemfile +gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" + +group(:docgen) do + gem "yard" +end + +group(:development, :test) do + gem "simplecov" + gem 'rack', "~> 1.5.1" + + gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) +end + +# If you want to load debugging tools into the bundle exec sandbox, +# add these additional dependencies into chef/Gemfile.local +eval(IO.read(__FILE__ + '.local'), binding) if File.exists?(__FILE__ + '.local') diff --git a/spec/integration/knife/chef_fs_data_store_spec.rb b/spec/integration/knife/chef_fs_data_store_spec.rb index 255a7b66b7b..3bbe38fcc62 100644 --- a/spec/integration/knife/chef_fs_data_store_spec.rb +++ b/spec/integration/knife/chef_fs_data_store_spec.rb @@ -188,6 +188,7 @@ when_the_repository 'is empty' do context 'POST /TYPE/NAME' do file 'empty.json', { 'name' => 'z' } + file 'empty_x.json', { 'name' => 'x' } file 'empty_id.json', { 'id' => 'z' } file 'rolestuff.json', '{"description":"hi there","name":"x"}' file 'cookbooks_to_upload/z/metadata.rb', "version '1.0.0'" @@ -211,6 +212,7 @@ end it 'knife raw -z -i empty.json -m POST /data/x' do + knife("raw -z -i #{path_to('empty_x.json')} -m POST /data").should_succeed /uri/ knife("raw -z -i #{path_to('empty_id.json')} -m POST /data/x").should_succeed /"z"/ knife('list --local -Rfp /data_bags').should_succeed "/data_bags/x/\n/data_bags/x/z.json\n" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2de0cae0edd..09e7642d983 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -82,6 +82,7 @@ module Shell # Do not change the gsub. Dir["spec/support/**/*.rb"]. reject { |f| f =~ %r{^spec/support/platforms} }. + reject { |f| f =~ %r{^spec/support/pedant} }. map { |f| f.gsub(%r{.rb$}, '') }. map { |f| f.gsub(%r[spec/], '')}. each { |f| require f } diff --git a/spec/support/pedant/pedant_config.rb b/spec/support/pedant/pedant_config.rb new file mode 100644 index 00000000000..7c9ddd73f12 --- /dev/null +++ b/spec/support/pedant/pedant_config.rb @@ -0,0 +1,121 @@ +# Copyright: Copyright (c) 2012 Opscode, Inc. +# License: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This annotated Pedant configuration file details the various +# configuration settings available to you. It is separate from the +# actual Pedant::Config class because not all settings have sane +# defaults, and not all settings are appropriate in all settings. + +################################################################################ +# You MUST specify the address of the server the API requests will be +# sent to. Only specify protocol, hostname, and port. +chef_server 'http://127.0.0.1:8889' + +# If you are doing development testing, you can specify the address of +# the Solr server. The presence of this parameter will enable tests +# to force commits to Solr, greatly decreasing the amout of time +# needed for testing the search endpoint. This is only an +# optimization for development! If you are testing a "live" Chef +# Server, or otherwise do not have access to the Solr server from your +# testing location, you should not specify a value for this parameter. +# The tests will still run, albeit slower, as they will now need to +# poll for a period to ensure they are querying committed results. +#search_server "http://localhost:8983" + +# Related to the 'search_server' parameter, this specifies the maximum +# amout of time (in seconds) that search endpoint requests should be +# retried before giving up. If not explicitly set, it will default to +# 65 seconds; only set it if you know that your Solr commit interval +# differs significantly from this. +maximum_search_time 0 + +# OSC sends erchef a host header with a port, so this option needs +# # to be enabled for Pedant tests to work correctly +explicit_port_url true + +# We're starting to break tests up into groups based on different +# criteria. The proper API tests (the results of which are viewable +# to OPC customers) should be the only ones run by Pedant embedded in +# OPC installs. There are other specs that help us keep track of API +# cruft that we want to come back and fix later; these shouldn't be +# viewable to customers, but we should be able to run them in +# development and CI environments. If this parameter is missing or +# explicitly `false` only the customer-friendly tests will be run. +# +# This is mainly here for documentation purposes, since the +# command-line `opscode-pedant` utility ultimately determines this +# value. +include_internal false + +# Test users. The five users specified below are required; their +# names (:user, :non_org_user, etc.) are indicative of their role +# within the tests. All users must have a ':name' key. If they have +# a ':create_me' key, Pedant will create these users for you. If you +# are using pre-existing users, you must supply a ':key_file' key, +# which should be the fully-qualified path /on the machine Pedant is +# running on/ to a private key for that user. +key = 'spec/support/pedant/stickywicket.pem' +superuser_name 'admin' +superuser_key key +webui_key key + +# Set the platform_class +platform_class Pedant::OpenSourcePlatform + +requestors({ + :clients => { + # The the admin user, for the purposes of getting things rolling + :admin => { + :name => "pedant_admin_client", + :create_me => true, + :create_knife => true, + :admin => true + }, + :non_admin => { + :name => 'pedant_client', + :create_me => true, + :create_knife => true + }, + :bad => { + :name => 'bad_client', + :bogus => true + } + }, + :users => { + :admin => { + :name => "admin", + :key_file => key, + :create_me => false, + :create_knife => false, + :admin => true + }, + :non_admin => { + :name => "pedant_non_admin_user", + :create_me => true, + :create_knife => true, + :admin => false + }, + # A user for Knife tests. A knife.rb and key files will be set up + # for this user + :knife_user => { + :name => "knifey", + :create_me => true, + :create_knife => true + } + } +}) + +self[:tags] = [:validation, :authentication, :authorization] +verify_error_messages false diff --git a/spec/support/pedant/run_pedant.rb b/spec/support/pedant/run_pedant.rb new file mode 100644 index 00000000000..0c24cf59f88 --- /dev/null +++ b/spec/support/pedant/run_pedant.rb @@ -0,0 +1,63 @@ +#!/usr/bin/env ruby +require 'bundler' +require 'bundler/setup' +require 'chef_zero/server' +require 'rspec/core' +require 'chef/chef_fs/chef_fs_data_store' +require 'chef/chef_fs/config' +require 'tmpdir' +require 'fileutils' +require 'chef/version' + +def start_server(chef_repo_path) + Dir.mkdir(chef_repo_path) if !File.exists?(chef_repo_path) + + # 11.6 and below had a bug where it couldn't create the repo children automatically + if Chef::VERSION.to_f < 11.8 + %w(clients cookbooks data_bags environments nodes roles users).each do |child| + Dir.mkdir("#{chef_repo_path}/#{child}") if !File.exists?("#{chef_repo_path}/#{child}") + end + end + + # Start the new server + Chef::Config.repo_mode = 'everything' + Chef::Config.chef_repo_path = chef_repo_path + Chef::Config.versioned_cookbooks = true + chef_fs = Chef::ChefFS::Config.new.local_fs + data_store = Chef::ChefFS::ChefFSDataStore.new(chef_fs) + server = ChefZero::Server.new(:port => 8889, :data_store => data_store)#, :log_level => :debug) + server.start_background + server +end + +tmpdir = Dir.mktmpdir +begin + # Create chef repository + chef_repo_path = "#{tmpdir}/repo" + + # Capture setup data into master_chef_repo_path + server = start_server(chef_repo_path) + + require 'pedant' + require 'pedant/opensource' + + #Pedant::Config.rerun = true + + Pedant.config.suite = 'api' + Pedant.config[:config_file] = 'spec/support/pedant/pedant_config.rb' + Pedant.setup([ + '--skip-knife', + '--skip-validation', + '--skip-authentication', + '--skip-authorization', + '--skip-omnibus' + ]) + + result = RSpec::Core::Runner.run(Pedant.config.rspec_args) + + server.stop if server.running? +ensure + FileUtils.remove_entry_secure(tmpdir) if tmpdir +end + +exit(result) diff --git a/spec/support/pedant/stickywicket.pem b/spec/support/pedant/stickywicket.pem new file mode 100644 index 00000000000..ff09e73903f --- /dev/null +++ b/spec/support/pedant/stickywicket.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEApNCkX2k+lFGDWRVhX4uClaVQrumG9XXvk6X7M2izrIg7RzMP +Dk4thhZkpx5gr22By7PZQdMEjWC/Zo8MBjtoJ0GV0jw8npefbU1MGKs2dtpYgo0N +Fq8fX8MdFPu4h2W3g0dMEdhT8icc2H4EjhZmdeUhUn3RIEt2duCgp3YDYnUUZx3j +N7MHcTIdzD58ikr6zQrZzHOv+OOI86Xk9EpyEEQizOLoQxkICNrhqN7ElQDuvXaX +BSBrYDRKH2umBMMcXzvsR/SvkmqxoEESSpIlW8zeKAWQ+znNjDC0tmTg7jZmgSP7 +siKrwo4t4ebjcmjpIoi/JKww/nGN3Uhz1ZOZuwIDAQABAoIBAQCaJQD2s0nyEeKU +uKhfYe155Cl3zbWJcQnmv4AXbr9MiAVY6+oS6Q8ur1bn7kNjDzoruENjiuZhC7E3 +TGZklb8tp+tluyy+7vQOmBKpp8fClSfewekR5CultqhGbb8B8yIVR+NfdUHd4rLZ +z9KWyWB+txPZQQ8L80gSmrfmpzs3IuT7oPvmtBU1Wq9QapC4n/rUohHUpUV1du4G +0wCIF4zQTg6cbYW2YXozwVQvw+P7P3RVEqZt+aZlbVcy0fNr6jNao0hi1KFC9OH2 +VjjU+PioreoA/NU3aZPIUzmJpWtsu31yuOZxXmytAkYooCZgiEQNEHnJlNPv0RmC +6BPMzVoBAoGBAM7yZoSNJpzdP/q1/4+H3zyy7o4I0VTW9u/GqUzhnbjm5poK30X9 +YXh/7WOVV0OoVqdO6ljRKygP3Oggf41ZEbi1C6bbsO57pksBWgx9bD9V35XscZ0J +F1ERe//kMHwVQy74R8/cIuRwm75haLSBj5/fwGbLeeVDglJkCVqPjtuBAoGBAMvh +qsAGG5k9u6voTcXlFwS+B5YjULhK4NSxdJ2BnOxzYzxQ3IYQZMlb2xt8yZYx/ZZK +wjkr9rcAPEQIQZ2A6NUbGq6qCD7sSmg6UAi0CgiqTokQ/Wtag0UDvFMzwerdg/On +37uxffpxpte8z1jYi/MxRaoTYueuc1UVnqofVIM7AoGBALZJzwPzUY/bVAADUJmd +lYZiFsAGBF42/E05MOgH1GaK/ZWy/fkouDLsfK67XaK7JZk6ajLSDLG9R1kxRym6 +y2FoGFtiKPfo8xIenrNhx3gCrG/jVjB9UYyXWiKNXifukr9M8/SkdBfFGWsZYqGd +fmXVMiVaFoVcce8hLxwWWEABAoGBAKcyhKX/HEj6YFqlIoqkydDAylXs1jicZ27l +rF2yum8KXZpMMdzbutuKsdAD8Ql0K6NB4a+jByuiTMn5/11cJxUEqkgM9sArZQW+ +tH2+r+/VQpyTS0/rpXVGj/2nl2K1kI2T4R36e/aTl6CanWweAf9JK/lC9rxKyxg+ +p6SaFuObAoGACP6TKCkp2oymXlKgdUUgPrnsaz2VAw8jD5QHtx10U4wty0C8gxsk +MLe00h09iLPyFmvJpD+MgbxV/r6RrZeVdsKdU/5LG52YgiVSTaizyy+ciEfW7xoQ +CL5EtZd8Cn5OKinBEzzFpELqunlqepIKCIDOcLKz/cjR+3a+E6Zx5Wo= +-----END RSA PRIVATE KEY----- diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index d186ef01be3..f20bc3766a2 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -20,7 +20,7 @@ describe Chef::CookbookVersion do describe "when first created" do before do - @cookbook_version = Chef::CookbookVersion.new("tatft") + @cookbook_version = Chef::CookbookVersion.new("tatft", '/tmp/blah') end it "has a name" do @@ -96,156 +96,258 @@ end end - describe "after the cookbook has been loaded" do + describe "with a cookbook directory named tatft" do MD5 = /[0-9a-f]{32}/ before do - # Currently the cookbook loader finds all the files then tells CookbookVersion - # where they are. - @cookbook_version = Chef::CookbookVersion.new("tatft") - @cookbook = Hash.new { |hash, key| hash[key] = [] } - cookbook_root = File.join(CHEF_SPEC_DATA, 'cb_version_cookbooks', 'tatft') + @cookbook_root = File.join(CHEF_SPEC_DATA, 'cb_version_cookbooks', 'tatft') # Dunno if the paths here are representitive of what is set by CookbookLoader... - @cookbook[:attribute_filenames] = Dir[File.join(cookbook_root, 'attributes', '**', '*.rb')] - @cookbook[:definition_filenames] = Dir[File.join(cookbook_root, 'definitions', '**', '*.rb')] - @cookbook[:file_filenames] = Dir[File.join(cookbook_root, 'files', '**', '*.tgz')] - @cookbook[:recipe_filenames] = Dir[File.join(cookbook_root, 'recipes', '**', '*.rb')] - @cookbook[:template_filenames] = Dir[File.join(cookbook_root, 'templates', '**', '*.erb')] - @cookbook[:library_filenames] = Dir[File.join(cookbook_root, 'libraries', '**', '*.rb')] - @cookbook[:resource_filenames] = Dir[File.join(cookbook_root, 'resources', '**', '*.rb')] - @cookbook[:provider_filenames] = Dir[File.join(cookbook_root, 'providers', '**', '*.rb')] - @cookbook[:root_filenames] = Array(File.join(cookbook_root, 'README.rdoc')) - @cookbook[:metadata_filenames] = Array(File.join(cookbook_root, 'metadata.json')) - - @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] - @cookbook_version.definition_filenames = @cookbook[:definition_filenames] - @cookbook_version.recipe_filenames = @cookbook[:recipe_filenames] - @cookbook_version.template_filenames = @cookbook[:template_filenames] - @cookbook_version.file_filenames = @cookbook[:file_filenames] - @cookbook_version.library_filenames = @cookbook[:library_filenames] - @cookbook_version.resource_filenames = @cookbook[:resource_filenames] - @cookbook_version.provider_filenames = @cookbook[:provider_filenames] - @cookbook_version.root_filenames = @cookbook[:root_filenames] - @cookbook_version.metadata_filenames = @cookbook[:metadata_filenames] - - # Used to test file-specificity related file lookups - @node = Chef::Node.new - @node.set[:platform] = "ubuntu" - @node.set[:platform_version] = "13.04" - @node.name("testing") + @cookbook[:attribute_filenames] = Dir[File.join(@cookbook_root, 'attributes', '**', '*.rb')] + @cookbook[:definition_filenames] = Dir[File.join(@cookbook_root, 'definitions', '**', '*.rb')] + @cookbook[:file_filenames] = Dir[File.join(@cookbook_root, 'files', '**', '*.tgz')] + @cookbook[:recipe_filenames] = Dir[File.join(@cookbook_root, 'recipes', '**', '*.rb')] + @cookbook[:template_filenames] = Dir[File.join(@cookbook_root, 'templates', '**', '*.erb')] + @cookbook[:library_filenames] = Dir[File.join(@cookbook_root, 'libraries', '**', '*.rb')] + @cookbook[:resource_filenames] = Dir[File.join(@cookbook_root, 'resources', '**', '*.rb')] + @cookbook[:provider_filenames] = Dir[File.join(@cookbook_root, 'providers', '**', '*.rb')] + @cookbook[:root_filenames] = Array(File.join(@cookbook_root, 'README.rdoc')) + @cookbook[:metadata_filenames] = Array(File.join(@cookbook_root, 'metadata.json')) + end - it "generates a manifest containing the cookbook's files" do - manifest = @cookbook_version.manifest + describe "and a cookbook with the same name" do + before do + # Currently the cookbook loader finds all the files then tells CookbookVersion + # where they are. + @cookbook_version = Chef::CookbookVersion.new("tatft", @cookbook_root) + + @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] + @cookbook_version.definition_filenames = @cookbook[:definition_filenames] + @cookbook_version.recipe_filenames = @cookbook[:recipe_filenames] + @cookbook_version.template_filenames = @cookbook[:template_filenames] + @cookbook_version.file_filenames = @cookbook[:file_filenames] + @cookbook_version.library_filenames = @cookbook[:library_filenames] + @cookbook_version.resource_filenames = @cookbook[:resource_filenames] + @cookbook_version.provider_filenames = @cookbook[:provider_filenames] + @cookbook_version.root_filenames = @cookbook[:root_filenames] + @cookbook_version.metadata_filenames = @cookbook[:metadata_filenames] + + # Used to test file-specificity related file lookups + @node = Chef::Node.new + @node.set[:platform] = "ubuntu" + @node.set[:platform_version] = "13.04" + @node.name("testing") + end - manifest["metadata"].should == Chef::Cookbook::Metadata.new - manifest["cookbook_name"].should == "tatft" + it "generates a manifest containing the cookbook's files" do + manifest = @cookbook_version.manifest - manifest["recipes"].should have(1).recipe_file + manifest["metadata"].should == Chef::Cookbook::Metadata.new + manifest["cookbook_name"].should == "tatft" - recipe = manifest["recipes"].first - recipe["name"].should == "default.rb" - recipe["path"].should == "recipes/default.rb" - recipe["checksum"].should match(MD5) - recipe["specificity"].should == "default" + manifest["recipes"].should have(1).recipe_file - manifest["definitions"].should have(1).definition_file + recipe = manifest["recipes"].first + recipe["name"].should == "default.rb" + recipe["path"].should == "recipes/default.rb" + recipe["checksum"].should match(MD5) + recipe["specificity"].should == "default" - definition = manifest["definitions"].first - definition["name"].should == "runit_service.rb" - definition["path"].should == "definitions/runit_service.rb" - definition["checksum"].should match(MD5) - definition["specificity"].should == "default" + manifest["definitions"].should have(1).definition_file - manifest["libraries"].should have(1).library_file + definition = manifest["definitions"].first + definition["name"].should == "runit_service.rb" + definition["path"].should == "definitions/runit_service.rb" + definition["checksum"].should match(MD5) + definition["specificity"].should == "default" - library = manifest["libraries"].first - library["name"].should == "ownage.rb" - library["path"].should == "libraries/ownage.rb" - library["checksum"].should match(MD5) - library["specificity"].should == "default" + manifest["libraries"].should have(1).library_file - manifest["attributes"].should have(1).attribute_file + library = manifest["libraries"].first + library["name"].should == "ownage.rb" + library["path"].should == "libraries/ownage.rb" + library["checksum"].should match(MD5) + library["specificity"].should == "default" - attribute_file = manifest["attributes"].first - attribute_file["name"].should == "default.rb" - attribute_file["path"].should == "attributes/default.rb" - attribute_file["checksum"].should match(MD5) - attribute_file["specificity"].should == "default" + manifest["attributes"].should have(1).attribute_file - manifest["files"].should have(1).cookbook_file + attribute_file = manifest["attributes"].first + attribute_file["name"].should == "default.rb" + attribute_file["path"].should == "attributes/default.rb" + attribute_file["checksum"].should match(MD5) + attribute_file["specificity"].should == "default" - cookbook_file = manifest["files"].first - cookbook_file["name"].should == "giant_blob.tgz" - cookbook_file["path"].should == "files/default/giant_blob.tgz" - cookbook_file["checksum"].should match(MD5) - cookbook_file["specificity"].should == "default" + manifest["files"].should have(1).cookbook_file - manifest["templates"].should have(1).template + cookbook_file = manifest["files"].first + cookbook_file["name"].should == "giant_blob.tgz" + cookbook_file["path"].should == "files/default/giant_blob.tgz" + cookbook_file["checksum"].should match(MD5) + cookbook_file["specificity"].should == "default" - template = manifest["templates"].first - template["name"].should == "configuration.erb" - template["path"].should == "templates/default/configuration.erb" - template["checksum"].should match(MD5) - template["specificity"].should == "default" + manifest["templates"].should have(1).template - manifest["resources"].should have(1).lwr + template = manifest["templates"].first + template["name"].should == "configuration.erb" + template["path"].should == "templates/default/configuration.erb" + template["checksum"].should match(MD5) + template["specificity"].should == "default" - lwr = manifest["resources"].first - lwr["name"].should == "lwr.rb" - lwr["path"].should == "resources/lwr.rb" - lwr["checksum"].should match(MD5) - lwr["specificity"].should == "default" + manifest["resources"].should have(1).lwr - manifest["providers"].should have(1).lwp + lwr = manifest["resources"].first + lwr["name"].should == "lwr.rb" + lwr["path"].should == "resources/lwr.rb" + lwr["checksum"].should match(MD5) + lwr["specificity"].should == "default" - lwp = manifest["providers"].first - lwp["name"].should == "lwp.rb" - lwp["path"].should == "providers/lwp.rb" - lwp["checksum"].should match(MD5) - lwp["specificity"].should == "default" + manifest["providers"].should have(1).lwp - manifest["root_files"].should have(1).file_in_the_cookbook_root + lwp = manifest["providers"].first + lwp["name"].should == "lwp.rb" + lwp["path"].should == "providers/lwp.rb" + lwp["checksum"].should match(MD5) + lwp["specificity"].should == "default" - readme = manifest["root_files"].first - readme["name"].should == "README.rdoc" - readme["path"].should == "README.rdoc" - readme["checksum"].should match(MD5) - readme["specificity"].should == "default" - end + manifest["root_files"].should have(1).file_in_the_cookbook_root - it "determines whether a template is available for a given node" do - @cookbook_version.should have_template_for_node(@node, "configuration.erb") - @cookbook_version.should_not have_template_for_node(@node, "missing.erb") - end + readme = manifest["root_files"].first + readme["name"].should == "README.rdoc" + readme["path"].should == "README.rdoc" + readme["checksum"].should match(MD5) + readme["specificity"].should == "default" + end - it "determines whether a cookbook_file is available for a given node" do - @cookbook_version.should have_cookbook_file_for_node(@node, "giant_blob.tgz") - @cookbook_version.should_not have_cookbook_file_for_node(@node, "missing.txt") - end + it "determines whether a template is available for a given node" do + @cookbook_version.should have_template_for_node(@node, "configuration.erb") + @cookbook_version.should_not have_template_for_node(@node, "missing.erb") + end - describe "raises an error when attempting to load a missing cookbook_file and" do - before do - node = Chef::Node.new.tap do |n| - n.name("sample.node") - n.automatic_attrs[:fqdn] = "sample.example.com" - n.automatic_attrs[:platform] = "ubuntu" - n.automatic_attrs[:platform_version] = "10.04" + it "determines whether a cookbook_file is available for a given node" do + @cookbook_version.should have_cookbook_file_for_node(@node, "giant_blob.tgz") + @cookbook_version.should_not have_cookbook_file_for_node(@node, "missing.txt") + end + + describe "raises an error when attempting to load a missing cookbook_file and" do + before do + node = Chef::Node.new.tap do |n| + n.name("sample.node") + n.automatic_attrs[:fqdn] = "sample.example.com" + n.automatic_attrs[:platform] = "ubuntu" + n.automatic_attrs[:platform_version] = "10.04" + end + @attempt_to_load_file = lambda { @cookbook_version.preferred_manifest_record(node, :files, "no-such-thing.txt") } + end + + it "describes the cookbook and version" do + useful_explanation = Regexp.new(Regexp.escape("Cookbook 'tatft' (0.0.0) does not contain")) + @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation) + end + + it "lists suggested places to look" do + useful_explanation = Regexp.new(Regexp.escape("files/default/no-such-thing.txt")) + @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation) end - @attempt_to_load_file = lambda { @cookbook_version.preferred_manifest_record(node, :files, "no-such-thing.txt") } end + end - it "describes the cookbook and version" do - useful_explanation = Regexp.new(Regexp.escape("Cookbook 'tatft' (0.0.0) does not contain")) - @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation) + describe "and a cookbook_version with a different name" do + before do + # Currently the cookbook loader finds all the files then tells CookbookVersion + # where they are. + @cookbook_version = Chef::CookbookVersion.new("blarghle", @cookbook_root) + @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] + @cookbook_version.definition_filenames = @cookbook[:definition_filenames] + @cookbook_version.recipe_filenames = @cookbook[:recipe_filenames] + @cookbook_version.template_filenames = @cookbook[:template_filenames] + @cookbook_version.file_filenames = @cookbook[:file_filenames] + @cookbook_version.library_filenames = @cookbook[:library_filenames] + @cookbook_version.resource_filenames = @cookbook[:resource_filenames] + @cookbook_version.provider_filenames = @cookbook[:provider_filenames] + @cookbook_version.root_filenames = @cookbook[:root_filenames] + @cookbook_version.metadata_filenames = @cookbook[:metadata_filenames] end - it "lists suggested places to look" do - useful_explanation = Regexp.new(Regexp.escape("files/default/no-such-thing.txt")) - @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation) + it "generates a manifest containing the cookbook's files" do + manifest = @cookbook_version.manifest + + manifest["metadata"].should == Chef::Cookbook::Metadata.new + manifest["cookbook_name"].should == "blarghle" + + manifest["recipes"].should have(1).recipe_file + + recipe = manifest["recipes"].first + recipe["name"].should == "default.rb" + recipe["path"].should == "recipes/default.rb" + recipe["checksum"].should match(MD5) + recipe["specificity"].should == "default" + + manifest["definitions"].should have(1).definition_file + + definition = manifest["definitions"].first + definition["name"].should == "runit_service.rb" + definition["path"].should == "definitions/runit_service.rb" + definition["checksum"].should match(MD5) + definition["specificity"].should == "default" + + manifest["libraries"].should have(1).library_file + + library = manifest["libraries"].first + library["name"].should == "ownage.rb" + library["path"].should == "libraries/ownage.rb" + library["checksum"].should match(MD5) + library["specificity"].should == "default" + + manifest["attributes"].should have(1).attribute_file + + attribute_file = manifest["attributes"].first + attribute_file["name"].should == "default.rb" + attribute_file["path"].should == "attributes/default.rb" + attribute_file["checksum"].should match(MD5) + attribute_file["specificity"].should == "default" + + manifest["files"].should have(1).cookbook_file + + cookbook_file = manifest["files"].first + cookbook_file["name"].should == "giant_blob.tgz" + cookbook_file["path"].should == "files/default/giant_blob.tgz" + cookbook_file["checksum"].should match(MD5) + cookbook_file["specificity"].should == "default" + + manifest["templates"].should have(1).template + + template = manifest["templates"].first + template["name"].should == "configuration.erb" + template["path"].should == "templates/default/configuration.erb" + template["checksum"].should match(MD5) + template["specificity"].should == "default" + + manifest["resources"].should have(1).lwr + + lwr = manifest["resources"].first + lwr["name"].should == "lwr.rb" + lwr["path"].should == "resources/lwr.rb" + lwr["checksum"].should match(MD5) + lwr["specificity"].should == "default" + + manifest["providers"].should have(1).lwp + + lwp = manifest["providers"].first + lwp["name"].should == "lwp.rb" + lwp["path"].should == "providers/lwp.rb" + lwp["checksum"].should match(MD5) + lwp["specificity"].should == "default" + + manifest["root_files"].should have(1).file_in_the_cookbook_root + + readme = manifest["root_files"].first + readme["name"].should == "README.rdoc" + readme["path"].should == "README.rdoc" + readme["checksum"].should match(MD5) + readme["specificity"].should == "default" end end @@ -270,8 +372,8 @@ ["1.2", "2.1"] ] examples.each do |smaller, larger| - sm = Chef::CookbookVersion.new("foo") - lg = Chef::CookbookVersion.new("foo") + sm = Chef::CookbookVersion.new("foo", '/tmp/blah') + lg = Chef::CookbookVersion.new("foo", '/tmp/blah') sm.version = smaller lg.version = larger sm.should be < lg @@ -281,8 +383,8 @@ end it "should equate versions 1.2 and 1.2.0" do - a = Chef::CookbookVersion.new("foo") - b = Chef::CookbookVersion.new("foo") + a = Chef::CookbookVersion.new("foo", '/tmp/blah') + b = Chef::CookbookVersion.new("foo", '/tmp/blah') a.version = "1.2" b.version = "1.2.0" a.should == b @@ -290,9 +392,9 @@ it "should not allow you to sort cookbooks with different names" do - apt = Chef::CookbookVersion.new "apt" + apt = Chef::CookbookVersion.new "apt", '/tmp/blah' apt.version = "1.0" - god = Chef::CookbookVersion.new "god" + god = Chef::CookbookVersion.new "god", '/tmp/blah' god.version = "2.0" lambda {apt <=> god}.should raise_error(Chef::Exceptions::CookbookVersionNameMismatch) end @@ -300,7 +402,7 @@ describe "when you set a version" do before do - @cbv = Chef::CookbookVersion.new("version validation") + @cbv = Chef::CookbookVersion.new("version validation", '/tmp/blah') end it "should accept valid cookbook versions" do good_versions = %w(1.2 1.2.3 1000.80.50000 0.300.25) diff --git a/spec/unit/knife/cookbook_metadata_spec.rb b/spec/unit/knife/cookbook_metadata_spec.rb index 92db6504356..26ff43829c0 100644 --- a/spec/unit/knife/cookbook_metadata_spec.rb +++ b/spec/unit/knife/cookbook_metadata_spec.rb @@ -51,9 +51,9 @@ describe 'with -a or --all' do before(:each) do @knife.config[:all] = true - @foo = Chef::CookbookVersion.new('foo') + @foo = Chef::CookbookVersion.new('foo', '/tmp/blah') @foo.version = '1.0.0' - @bar = Chef::CookbookVersion.new('bar') + @bar = Chef::CookbookVersion.new('bar', '/tmp/blah') @bar.version = '2.0.0' @cookbook_loader = { "foo" => @foo, diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb index 5c7a4c11258..6a99a804595 100644 --- a/spec/unit/knife/cookbook_upload_spec.rb +++ b/spec/unit/knife/cookbook_upload_spec.rb @@ -23,7 +23,7 @@ require 'timeout' describe Chef::Knife::CookbookUpload do - let(:cookbook) { Chef::CookbookVersion.new('test_cookbook') } + let(:cookbook) { Chef::CookbookVersion.new('test_cookbook', '/tmp/blah.txt') } let(:cookbooks_by_name) do {cookbook.name => cookbook} @@ -39,7 +39,7 @@ let(:cookbook_uploader) { double(:upload_cookbooks => nil) } let(:output) { StringIO.new } - + let(:name_args) { ['test_cookbook'] } let(:knife) do @@ -58,7 +58,7 @@ it 'should upload cookbooks with predefined concurrency' do Chef::CookbookVersion.stub(:list_all_versions).and_return({}) knife.config[:concurrency] = 3 - test_cookbook = Chef::CookbookVersion.new('test_cookbook') + test_cookbook = Chef::CookbookVersion.new('test_cookbook', '/tmp/blah') cookbook_loader.stub(:each).and_yield("test_cookbook", test_cookbook) cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook"]) Chef::CookbookUploader.should_receive(:new).with( kind_of(Array), kind_of(Array), @@ -131,9 +131,9 @@ let(:cookbooks_by_name) do { - 'test_cookbook1' => Chef::CookbookVersion.new('test_cookbook1'), - 'test_cookbook2' => Chef::CookbookVersion.new('test_cookbook2'), - 'test_cookbook3' => Chef::CookbookVersion.new('test_cookbook3') + 'test_cookbook1' => Chef::CookbookVersion.new('test_cookbook1', '/tmp/blah'), + 'test_cookbook2' => Chef::CookbookVersion.new('test_cookbook2', '/tmp/blah'), + 'test_cookbook3' => Chef::CookbookVersion.new('test_cookbook3', '/tmp/blah') } end @@ -163,7 +163,7 @@ "test_cookbook3" => test_cookbook3 } end - let(:test_cookbook1) { Chef::CookbookVersion.new('test_cookbook1') } + let(:test_cookbook1) { Chef::CookbookVersion.new('test_cookbook1', '/tmp/blah') } let(:test_cookbook2) do c = Chef::CookbookVersion.new('test_cookbook2') @@ -191,7 +191,7 @@ end describe 'when specifying a cookbook name with missing dependencies' do - let(:cookbook_dependency) { Chef::CookbookVersion.new('dependency') } + let(:cookbook_dependency) { Chef::CookbookVersion.new('dependency', '/tmp/blah') } before(:each) do cookbook.metadata.depends("dependency") @@ -245,8 +245,8 @@ describe 'with -a or --all' do before(:each) do knife.config[:all] = true - @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1') - @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2') + @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1', '/tmp/blah') + @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2', '/tmp/blah') cookbook_loader.stub(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2) cookbook_loader.stub(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"]) end diff --git a/spec/unit/version_constraint_spec.rb b/spec/unit/version_constraint_spec.rb index 2c1246b7767..a49b082d9d8 100644 --- a/spec/unit/version_constraint_spec.rb +++ b/spec/unit/version_constraint_spec.rb @@ -76,7 +76,7 @@ @vc.should include Chef::Version.new("1.4") end it "Chef::CookbookVersion" do - cv = Chef::CookbookVersion.new("alice") + cv = Chef::CookbookVersion.new("alice", '/tmp/blah.txt') cv.version = "1.4" @vc.should include cv end