From e908aa35f5b62f646557a9b932189e184fc093e1 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Mon, 21 Apr 2014 07:36:46 -0700 Subject: Support ChefFSDataStore from prior versions --- lib/chef_zero/data_store/interface_v1.rb | 49 +++++++++++ lib/chef_zero/data_store/interface_v2.rb | 18 +++++ lib/chef_zero/data_store/memory_store.rb | 8 +- lib/chef_zero/data_store/v1_to_v2_adapter.rb | 117 +++++++++++++++++++++++++++ lib/chef_zero/server.rb | 16 +++- spec/run.rb | 106 ++++++++++++++++++------ 6 files changed, 284 insertions(+), 30 deletions(-) create mode 100644 lib/chef_zero/data_store/interface_v1.rb create mode 100644 lib/chef_zero/data_store/interface_v2.rb create mode 100644 lib/chef_zero/data_store/v1_to_v2_adapter.rb diff --git a/lib/chef_zero/data_store/interface_v1.rb b/lib/chef_zero/data_store/interface_v1.rb new file mode 100644 index 0000000..7568fdc --- /dev/null +++ b/lib/chef_zero/data_store/interface_v1.rb @@ -0,0 +1,49 @@ +module ChefZero + module DataStore + class InterfaceV1 + def interface_version + 1 + end + + def clear + raise "clear not implemented by class #{self.class}" + end + + def create_dir(path, name, *options) + raise "create_dir not implemented by class #{self.class}" + end + + def create(path, name, data, *options) + raise "create_dir not implemented by class #{self.class}" + end + + def get(path, request=nil) + raise "create_dir not implemented by class #{self.class}" + end + + def set(path, data, *options) + raise "create_dir not implemented by class #{self.class}" + end + + def delete(path) + raise "create_dir not implemented by class #{self.class}" + end + + def delete_dir(path, *options) + raise "create_dir not implemented by class #{self.class}" + end + + def list(path) + raise "create_dir not implemented by class #{self.class}" + end + + def exists?(path) + raise "create_dir not implemented by class #{self.class}" + end + + def exists_dir?(path) + raise "create_dir not implemented by class #{self.class}" + end + end + end +end diff --git a/lib/chef_zero/data_store/interface_v2.rb b/lib/chef_zero/data_store/interface_v2.rb new file mode 100644 index 0000000..fa1ba41 --- /dev/null +++ b/lib/chef_zero/data_store/interface_v2.rb @@ -0,0 +1,18 @@ +require 'chef_zero/data_store/interface_v1' + +module ChefZero + module DataStore + # V2 assumes paths starting with /organizations/ORGNAME. It also REQUIRES that + # new organizations have these defaults: + # chef-validator client: '{ "validator": true }', + # chef-webui client: '{ "admin": true }' + # _default environment: '{ "description": "The default Chef environment" }' + # admin user: '{ "admin": "true" }' + + class InterfaceV2 < ChefZero::DataStore::InterfaceV1 + def interface_version + 2 + end + end + end +end diff --git a/lib/chef_zero/data_store/memory_store.rb b/lib/chef_zero/data_store/memory_store.rb index c7e5b7f..aba052d 100644 --- a/lib/chef_zero/data_store/memory_store.rb +++ b/lib/chef_zero/data_store/memory_store.rb @@ -18,14 +18,19 @@ require 'chef_zero/data_store/data_already_exists_error' require 'chef_zero/data_store/data_not_found_error' +require 'chef_zero/data_store/interface_v2' module ChefZero module DataStore - class MemoryStore + class MemoryStore < ChefZero::DataStore::InterfaceV2 def initialize clear end + def interface_version + 2 + end + def clear @data = {} @@ -33,7 +38,6 @@ module ChefZero end def create_org - # Create containers org = { 'clients' => { 'chef-validator' => '{ "validator": true }', diff --git a/lib/chef_zero/data_store/v1_to_v2_adapter.rb b/lib/chef_zero/data_store/v1_to_v2_adapter.rb new file mode 100644 index 0000000..aaa3b64 --- /dev/null +++ b/lib/chef_zero/data_store/v1_to_v2_adapter.rb @@ -0,0 +1,117 @@ +require 'chef_zero/data_store/interface_v2' + +module ChefZero + module DataStore + class V1ToV2Adapter < ChefZero::DataStore::InterfaceV2 + def initialize(real_store, single_org) + @real_store = real_store + @single_org = single_org + # Handle defaults per V2 specification + @defaults = { + 'clients' => { + 'chef-validator' => '{ "validator": true }', + 'chef-webui' => '{ "admin": true }' + }, + 'environments' => { + '_default' => '{ "description": "The default Chef environment" }' + }, + 'users' => { + 'admin' => '{ "admin": "true" }' + } + } + end + + attr_reader :real_store + attr_reader :single_org + + def clear + real_store.clear + end + + def create_dir(path, name, *options) + return nil if skip_organizations(path, name) + real_store.create_dir(path[2..-1], name, *options) + end + + def create(path, name, data, *options) + return nil if skip_organizations(path, name) + remove_default(path, name) + real_store.create(path[2..-1], name, data, *options) + end + + def get(path, request=nil) + return nil if skip_organizations(path) + begin + real_store.get(path[2..-1], request) + rescue DataNotFoundError + if path.size == 2 && @defaults[path[0]] && @defaults[path[0]][path[1]] + @defaults[path[0]][path[1]] + else + raise + end + end + end + + def set(path, data, *options) + return nil if skip_organizations(path) + remove_default(path, name) + real_store.set(path[2..-1], data, *options) + end + + def delete(path) + return nil if skip_organizations(path) + remove_default(path) + real_store.delete(path[2..-1]) + end + + def delete_dir(path, *options) + return nil if skip_organizations(path) + real_store.delete_dir(path[2..-1], *options) + end + + def list(path) + return nil if skip_organizations(path) + real_store.list(path[2..-1]) + end + + def exists?(path) + return nil if skip_organizations(path) + if path.size == 2 && @defaults[path[0]] && @defaults[path[0]][path[1]] + @defaults[path[0]][path[1]] + else + real_store.exists?(path[2..-1]) + end + end + + def exists_dir?(path) + return nil if skip_organizations(path) + real_store.exists_dir?(path[2..-1]) + end + + private + + def remove_default(path, name = nil) + path = path + [name] if name + if path.size == 2 && @defaults[path[0]] && @defaults[path[0]][path[1]] + @defaults[path[0]].delete(path[1]) + end + end + + def skip_organizations(path, name = nil) + if path == [] + raise "" if name == nil || name != 'organizations' + true + elsif path == ['organizations'] + raise "" if name == nil || name != single_org + true + else + raise "Path #{path} must start with /organizations/#{single_org}" if path[0..1] != [ 'organizations', single_org ] + if !name + raise "Path #{path} must start with /organizations/#{single_org}/" if path.size <= 2 + end + false + end + end + end + end +end diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb index a5c0c0d..be628af 100644 --- a/lib/chef_zero/server.rb +++ b/lib/chef_zero/server.rb @@ -29,6 +29,7 @@ require 'chef_zero' require 'chef_zero/cookbook_data' require 'chef_zero/rest_router' require 'chef_zero/data_store/memory_store' +require 'chef_zero/data_store/v1_to_v2_adapter' require 'chef_zero/version' require 'chef_zero/endpoints/authenticate_user_endpoint' @@ -103,14 +104,23 @@ module ChefZero # # The data store for this server (default is in-memory). # - # @return [~ChefZero::DataStore] + # @return [ChefZero::DataStore] # def data_store @data_store ||= begin result = @options[:data_store] || DataStore::MemoryStore.new if options[:single_org] - result.create_dir([ 'organizations' ], options[:single_org]) + if result.respond_to?(:interface_version) && result.interface_version >= 2 && result.interface_version < 3 + result.create_dir([ 'organizations' ], options[:single_org]) + else + result = ChefZero::DataStore::V1ToV2Adapter.new(result, options[:single_org]) + end + else + if !(result.respond_to?(:interface_version) && result.interface_version >= 2 && result.interface_version < 3) + raise "Multi-org not supported by data store #{result}!" + end end + result end end @@ -407,7 +417,7 @@ module ChefZero def dejsonize_children(hash) result = {} hash.each_pair do |key, value| - result[key] = value.is_a?(Hash) ? JSON.pretty_generate(value) : value + result[key] = value.is_a?(Hash) ? JSON.pretty_generate(value) : value end result end diff --git a/spec/run.rb b/spec/run.rb index 01dffe9..d538c2f 100644 --- a/spec/run.rb +++ b/spec/run.rb @@ -5,33 +5,89 @@ require 'bundler/setup' require 'chef_zero/server' require 'rspec/core' -server = ChefZero::Server.new(:port => 8889) -server.start_background - -unless ENV['SKIP_PEDANT'] - require 'pedant' - require 'pedant/opensource' - - Pedant.config.suite = 'api' - Pedant.config[:config_file] = 'spec/support/pedant.rb' - Pedant.setup([ - '--skip-validation', - '--skip-authentication', - '--skip-authorization', - '--skip-omnibus' - ]) - - result = RSpec::Core::Runner.run(Pedant.config.rspec_args) -else - require 'net/http' - response = Net::HTTP.new('127.0.0.1', 8889).get("/environments", { 'Accept' => 'application/json'}).body - if response =~ /_default/ - result = 0 +tmpdir = nil + +def start_server(chef_repo_path) + # Create the chef repo + Dir.mkdir(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}") + end + end + + # Start the new server + Chef::Config.repo_mode = 'everything' + Chef::Config.chef_repo_path = chef_repo_path + 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) + server.start_background + server +end + +begin + if ENV['CHEF_FS'] + require 'chef/chef_fs/chef_fs_data_store' + require 'chef/chef_fs/config' + require 'tmpdir' + require 'fileutils' + require 'chef/version' + + # Create chef repository + tmpdir = Dir.mktmpdir + chef_repo_path = "#{tmpdir}/repo" + server = start_server(chef_repo_path) + + # Delete everything before each test + RSpec.configure do |config| + config.before(:each) do + # Stop the old server + if server + server.stop + server = nil + FileUtils.rm_rf(chef_repo_path) + end + + server = start_server(chef_repo_path) + end + end + else - puts "GET /environments returned #{response}. Expected _default!" - result = 1 + server = ChefZero::Server.new(:port => 8889) + server.start_background end + + unless ENV['SKIP_PEDANT'] + require 'pedant' + require 'pedant/opensource' + + Pedant.config.suite = 'api' + Pedant.config[:config_file] = 'spec/support/pedant.rb' + Pedant.setup([ + '--skip-knife', + '--skip-validation', + '--skip-authentication', + '--skip-authorization', + '--skip-omnibus' + ]) + + result = RSpec::Core::Runner.run(Pedant.config.rspec_args) + else + require 'net/http' + response = Net::HTTP.new('127.0.0.1', 8889).get("/environments", { 'Accept' => 'application/json'}).body + if response =~ /_default/ + result = 0 + else + puts "GET /environments returned #{response}. Expected _default!" + result = 1 + end + end + + server.stop +ensure + FileUtils.remove_entry_secure(tmpdir) if tmpdir end -server.stop exit(result) -- cgit v1.2.1