require "tempfile" require "chef_zero/server" require "chef_zero/rest_request" module ChefZero module RSpec module RSpecClassMethods attr_accessor :server attr_accessor :client_key attr_reader :request_log def clear_request_log @request_log = [] end def set_server_options(chef_server_options) if server && chef_server_options != server.options server.stop self.server = nil end unless server # TODO: can this be logged easily? # pp :zero_opts => chef_server_options # Set up configuration so that clients will point to the server self.server = ChefZero::Server.new(chef_server_options) self.client_key = Tempfile.new(["chef_zero_client_key", ".pem"]) client_key.write(ChefZero::PRIVATE_KEY) client_key.close # Start the server server.start_background server.on_response do |request, response| request_log << [ request, response ] end else server.clear_data end clear_request_log end end extend RSpecClassMethods def when_the_chef_server(description, *tags, &block) context "When the #{ChefZero::Dist::PRODUCT} server #{description}", *tags do extend WhenTheChefServerClassMethods include WhenTheChefServerInstanceMethods # Take the passed-in options # rubocop:disable Lint/UnderscorePrefixedVariableName define_singleton_method(:chef_server_options) do @chef_server_options ||= begin _chef_server_options = { port: 8900, signals: false, log_requests: true } _chef_server_options = _chef_server_options.merge(tags.last) if tags.last.is_a?(Hash) _chef_server_options = _chef_server_options.freeze end end # rubocop:enable Lint/UnderscorePrefixedVariableName # Merge in chef_server_options from let(:chef_server_options) def chef_server_options # rubocop:disable Lint/NestedMethodDefinition chef_server_options = self.class.chef_server_options.dup chef_server_options = chef_server_options.merge(chef_zero_opts) if respond_to?(:chef_zero_opts) chef_server_options end before chef_server_options[:server_scope] do if chef_server_options[:server_scope] != self.class.chef_server_options[:server_scope] raise "server_scope: #{chef_server_options[:server_scope]} will not be honored: it can only be set on when_the_chef_server!" end Log.info("Starting #{ChefZero::Dist::PRODUCT} server with options #{chef_server_options}") ChefZero::RSpec.set_server_options(chef_server_options) if chef_server_options[:organization] organization chef_server_options[:organization] end if defined?(Chef::Config) @old_chef_server_url = Chef::Config.chef_server_url @old_node_name = Chef::Config.node_name @old_client_key = Chef::Config.client_key if chef_server_options[:organization] Chef::Config.chef_server_url = "#{ChefZero::RSpec.server.url}/organizations/#{chef_server_options[:organization]}" else Chef::Config.chef_server_url = ChefZero::RSpec.server.url end Chef::Config.node_name = "admin" Chef::Config.client_key = ChefZero::RSpec.client_key.path Chef::Config.http_retry_count = 0 end end if defined?(Chef::Config) after chef_server_options[:server_scope] do Chef::Config.chef_server_url = @old_chef_server_url Chef::Config.node_name = @old_node_name Chef::Config.client_key = @old_client_key end end instance_eval(&block) end end module WhenTheChefServerClassMethods def organization(name, org = "{}", &block) before(chef_server_options[:server_scope]) { organization(name, org, &block) } end def acl_for(path, data) before(chef_server_options[:server_scope]) { acl_for(path, data) } end def client(name, data, &block) before(chef_server_options[:server_scope]) { client(name, data, &block) } end def container(name, data, &block) before(chef_server_options[:server_scope]) { container(name, data, &block) } end def cookbook(name, version, data = {}, options = {}, &block) before(chef_server_options[:server_scope]) do cookbook(name, version, data, &block) end end def cookbook_artifact(name, identifier, data = {}, &block) before(chef_server_options[:server_scope]) { cookbook_artifact(name, identifier, data, &block) } end def data_bag(name, data, &block) before(chef_server_options[:server_scope]) { data_bag(name, data, &block) } end def environment(name, data, &block) before(chef_server_options[:server_scope]) { environment(name, data, &block) } end def group(name, data, &block) before(chef_server_options[:server_scope]) { group(name, data, &block) } end def node(name, data, &block) before(chef_server_options[:server_scope]) { node(name, data, &block) } end def org_invite(*usernames) before(chef_server_options[:server_scope]) { org_invite(*usernames) } end def org_member(*usernames) before(chef_server_options[:server_scope]) { org_member(*usernames) } end def policy(name, data, &block) before(chef_server_options[:server_scope]) { policy(name, data, &block) } end def policy_group(name, data, &block) before(chef_server_options[:server_scope]) { policy_group(name, data, &block) } end def role(name, data, &block) before(chef_server_options[:server_scope]) { role(name, data, &block) } end def sandbox(name, data, &block) before(chef_server_options[:server_scope]) { sandbox(name, data, &block) } end def user(name, data, &block) before(chef_server_options[:server_scope]) { user(name, data, &block) } end end module WhenTheChefServerInstanceMethods def organization(name, org = "{}", &block) ChefZero::RSpec.server.data_store.set([ "organizations", name, "org" ], dejsonize(org), :create_dir, :create) prev_org_name = @current_org @current_org = name prev_object_path = @current_object_path @current_object_path = "organizations/#{name}" if block_given? begin instance_eval(&block) ensure @current_org = prev_org_name @current_object_path = prev_object_path end end end def acl_for(path, data) ChefZero::RSpec.server.load_data({ "acls" => { path => data } }, current_org) end def acl(data) acl_for(@current_object_path, data) end def client(name, data, &block) with_object_path("clients/#{name}") do ChefZero::RSpec.server.load_data({ "clients" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def container(name, data, &block) with_object_path("containers/#{name}") do ChefZero::RSpec.server.load_data({ "containers" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def cookbook(name, version, data = {}, options = {}, &block) with_object_path("cookbooks/#{name}") do # If you didn't specify metadata.rb, we generate it for you. If you # explicitly set it to nil, that means you don't want it at all. if data.key?("metadata.rb") if data["metadata.rb"].nil? data.delete("metadata.rb") end else data["metadata.rb"] = "name #{name.inspect}; version #{version.inspect}" end ChefZero::RSpec.server.load_data({ "cookbooks" => { "#{name}-#{version}" => data.merge(options) } }, current_org) instance_eval(&block) if block_given? end end def cookbook_artifact(name, identifier, data = {}, &block) with_object_path("cookbook_artifacts/#{name}") do # If you didn't specify metadata.rb, we generate it for you. If you # explicitly set it to nil, that means you don't want it at all. if data.key?("metadata.rb") if data["metadata.rb"].nil? data.delete("metadata.rb") end else data["metadata.rb"] = "name #{name.inspect}" end ChefZero::RSpec.server.load_data({ "cookbook_artifacts" => { "#{name}-#{identifier}" => data } }, current_org) instance_eval(&block) if block_given? end end def data_bag(name, data, &block) with_object_path("data/#{name}") do ChefZero::RSpec.server.load_data({ "data" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def environment(name, data, &block) with_object_path("environments/#{name}") do ChefZero::RSpec.server.load_data({ "environments" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def group(name, data, &block) with_object_path("groups/#{name}") do ChefZero::RSpec.server.load_data({ "groups" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def node(name, data, &block) with_object_path("nodes/#{name}") do ChefZero::RSpec.server.load_data({ "nodes" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def org_invite(*usernames) ChefZero::RSpec.server.load_data({ "invites" => usernames }, current_org) end def org_member(*usernames) ChefZero::RSpec.server.load_data({ "members" => usernames }, current_org) end def policy(name, version, data, &block) with_object_path("policies/#{name}") do ChefZero::RSpec.server.load_data({ "policies" => { name => { version => data } } }, current_org) instance_eval(&block) if block_given? end end def policy_group(name, data, &block) with_object_path("policy_groups/#{name}") do ChefZero::RSpec.server.load_data({ "policy_groups" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def role(name, data, &block) with_object_path("roles/#{name}") do ChefZero::RSpec.server.load_data({ "roles" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def sandbox(name, data, &block) with_object_path("sandboxes/#{name}") do ChefZero::RSpec.server.load_data({ "sandboxes" => { name => data } }, current_org) instance_eval(&block) if block_given? end end def user(name, data, &block) if ChefZero::RSpec.server.options[:osc_compat] with_object_path("users/#{name}") do ChefZero::RSpec.server.load_data({ "users" => { name => data } }, current_org) instance_eval(&block) if block_given? end else old_object_path = @current_object_path @current_object_path = "users/#{name}" begin ChefZero::RSpec.server.load_data({ "users" => { name => data } }, current_org) instance_eval(&block) if block_given? ensure @current_object_path = old_object_path end end end def dejsonize(data) if data.is_a?(String) data else FFI_Yajl::Encoder.encode(data, pretty: true) end end def current_org @current_org || ChefZero::RSpec.server.options[:single_org] || nil end def with_object_path(object_path) old_object_path = @current_object_path @current_object_path = object_path begin yield if block_given? end @current_object_path = old_object_path end end end end