diff options
author | Jordan Running <jr@getchef.com> | 2016-01-28 16:56:30 -0600 |
---|---|---|
committer | Jordan Running <jr@getchef.com> | 2016-02-10 17:06:57 -0600 |
commit | c8b79e62f628bd7dccc63b04313e044b13600841 (patch) | |
tree | 17df8584ca268a0e75e7f9e52f5244a3b343cf6c | |
parent | 96a82ea1050e1dd360035b760c19faca5678251c (diff) | |
download | chef-zero-c8b79e62f628bd7dccc63b04313e044b13600841.tar.gz |
Make client keys Pedant specs pass
- Implement client keys in:
- ActorKeysEndpoint#get, #post
- ActorKeyEndpoint#get
- ActorsEndpoint#get, #post
- ActorEndpoint#delete, #put, #populate_defaults
- OrganizationsEndpoint
- #post
- Extract validator client creation into method.
- Store the public key generated for the validator client in the
client_keys store.
- OrganizationEndpoint
- #delete: Delete the validator client and client keys.
- OrganizationUserEndpoint
- #get, #delete: Don't retrieve default user public key (now handled
by `DataNormalizer.normalize_user`; see below).
- SearchEndpoint
- Simplify `#search_container`.
- RestBase
- Fix RestErrorResponse exceptions to report actual `rest_path`
instead associated with the failed data store operation instead of
`request.rest_path`.
- DataNormalizer
- Fetch client and user default public_key in
`DataNormalizer.normalize_{client,user}` instead of doing this in
multiple other places. Requires passing `data_store` as argument.
- RestRouter
- Clean up logging
- Print request methods, paths and bodies more readably for
log_level >= INFO.
- Pretty-print RestRequest objects (only printed when log_level ==
DEBUG).
- Server
- Change default log_level to WARN (to enable logging cleanup above).
- Rakefile, spec/run_oc_pedant.rb
- Consume RSpec, Pedant options from `ENV['RSPEC_OPTS']`,
`ENV['PEDANT_OPTS']` (see `rake -D`).
- Consume `ENV['LOG_LEVEL'` (see `rake -D`).
- Clean up ChefZero::Server default opts and move duplicated logic to
`start_chef_server` method.
-rw-r--r-- | Rakefile | 36 | ||||
-rw-r--r-- | lib/chef_zero/chef_data/data_normalizer.rb | 25 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/actor_endpoint.rb | 116 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/actor_key_endpoint.rb | 15 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/actor_keys_endpoint.rb | 44 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/actors_endpoint.rb | 30 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/authenticate_user_endpoint.rb | 2 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/organization_endpoint.rb | 27 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/organization_user_endpoint.rb | 48 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/organizations_endpoint.rb | 55 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/search_endpoint.rb | 39 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/system_recovery_endpoint.rb | 2 | ||||
-rw-r--r-- | lib/chef_zero/rest_base.rb | 14 | ||||
-rw-r--r-- | lib/chef_zero/rest_router.rb | 61 | ||||
-rw-r--r-- | lib/chef_zero/server.rb | 2 | ||||
-rw-r--r-- | spec/run_oc_pedant.rb | 104 |
16 files changed, 407 insertions, 213 deletions
@@ -3,33 +3,43 @@ require 'bundler/gem_tasks' require 'chef_zero/version' +def run_oc_pedant(env={}) + ENV.update(env) + require File.expand_path('spec/run_oc_pedant') +end + +ENV_DOCS = <<END +Environment: + - RSPEC_OPTS Options to pass to RSpec + e.g. RSPEC_OPTS="--fail-fast --profile 5" + - PEDANT_OPTS Options to pass to oc-chef-pedant + e.g. PEDANT_OPTS="--focus-keys --skip-users" + - LOG_LEVEL Set the log level (default: warn) + e.g. LOG_LEVEL=debug +END + task :default => :pedant -desc "run specs" +desc "Run specs" task :spec do system('rspec spec/*_spec.rb') end -desc "run oc pedant" -task :pedant do - require File.expand_path('spec/run_oc_pedant') -end +desc "Run oc-chef-pedant\n\n#{ENV_DOCS}" +task :pedant => :oc_pedant -desc "run pedant with CHEF_FS set" +desc "Run oc-chef-pedant with CHEF_FS set\n\n#{ENV_DOCS}" task :cheffs do - ENV['CHEF_FS'] = "yes" - require File.expand_path('spec/run_oc_pedant') + run_oc_pedant('CHEF_FS' => 'yes') end -desc "run pedant with FILE_STORE set" +desc "Run oc-chef-pedant with FILE_STORE set\n\n#{ENV_DOCS}" task :filestore do - ENV['FILE_STORE'] = "yes" - require File.expand_path('spec/run_oc_pedant') + run_oc_pedant('FILE_STORE' => 'yes') end -desc "run oc pedant" task :oc_pedant do - require File.expand_path('spec/run_oc_pedant') + run_oc_pedant end task :chef_spec do diff --git a/lib/chef_zero/chef_data/data_normalizer.rb b/lib/chef_zero/chef_data/data_normalizer.rb index 554bd70..3ab20fa 100644 --- a/lib/chef_zero/chef_data/data_normalizer.rb +++ b/lib/chef_zero/chef_data/data_normalizer.rb @@ -5,6 +5,8 @@ require 'chef_zero/chef_data/default_creator' module ChefZero module ChefData class DataNormalizer + DEFAULT_PUBLIC_KEY_NAME = "default" + def self.normalize_acls(acls) ChefData::DefaultCreator::PERMISSIONS.each do |perm| acls[perm] ||= {} @@ -14,11 +16,14 @@ module ChefZero acls end - def self.normalize_client(client, name, orgname = nil) + def self.normalize_client(data_store, client, name, orgname = nil) + unless client['public_key'] + client['public_key'] = get_default_public_key(data_store, :client, name) + end + client['name'] ||= name client['clientname'] ||= name client['admin'] = !!client['admin'] if client.has_key?('admin') - client['public_key'] ||= PUBLIC_KEY client['orgname'] ||= orgname client['validator'] ||= false client['validator'] = !!client['validator'] @@ -34,7 +39,11 @@ module ChefZero container end - def self.normalize_user(user, name, identity_keys, osc_compat, method=nil) + def self.normalize_user(data_store, user, name, identity_keys, osc_compat, method=nil) + unless user['public_key'] + user['public_key'] = get_default_public_key(data_store, :user, name) + end + user[identity_keys.first] ||= name user['admin'] ||= false user['admin'] = !!user['admin'] @@ -221,6 +230,16 @@ module ChefZero end }.uniq end + + private + + def self.get_default_public_key(data_store, user_or_client, user_or_client_name) + key_json = data_store.get([ "#{user_or_client}_keys", user_or_client_name, "keys", DEFAULT_PUBLIC_KEY_NAME ]) + return unless key_json + + FFI_Yajl::Parser.parse(key_json, create_additions: false)["public_key"] + rescue DataStore::DataNotFoundError + end end end end diff --git a/lib/chef_zero/endpoints/actor_endpoint.rb b/lib/chef_zero/endpoints/actor_endpoint.rb index a491965..46b9082 100644 --- a/lib/chef_zero/endpoints/actor_endpoint.rb +++ b/lib/chef_zero/endpoints/actor_endpoint.rb @@ -13,22 +13,18 @@ module ChefZero def delete(request) result = super - username = request.rest_path[1] + client_or_user_name = request.rest_path.last if request.rest_path[0] == 'users' list_data(request, [ 'organizations' ]).each do |org| begin - delete_data(request, [ 'organizations', org, 'users', username ], :data_store_exceptions) + delete_data(request, [ 'organizations', org, 'users', client_or_user_name ], :data_store_exceptions) rescue DataStore::DataNotFoundError end end - - begin - path = [ 'user_keys', username ] - delete_data_dir(request, path, :data_store_exceptions) - rescue DataStore::DataNotFoundError - end end + + delete_actor_keys!(request, client_or_user_name) result end @@ -65,32 +61,29 @@ module ChefZero # Inject private_key into response, delete public_key/password if applicable if result[0] == 200 || result[0] == 201 - username = identity_key_value(request) || request.rest_path[-1] + client_or_user_name = identity_key_value(request) || request.rest_path[-1] - # TODO Implement for clients - if request.rest_path[2] != 'clients' && is_rename?(request) - rename_user_keys!(request, username) + if is_rename?(request) + rename_keys!(request, client_or_user_name) end if request.rest_path[0] == 'users' response = { - 'uri' => build_uri(request.base_uri, [ 'users', username ]) + 'uri' => build_uri(request.base_uri, [ 'users', client_or_user_name ]) } else response = FFI_Yajl::Parser.parse(result[2], :create_additions => false) end - if request.rest_path[2] == 'clients' + if updating_public_key + update_default_public_key!(request, client_or_user_name, public_key) + response['public_key'] = public_key + end + + if client?(request) response['private_key'] = private_key ? private_key : false else response['private_key'] = private_key if private_key - - if updating_public_key - update_user_default_key!(request, public_key) - end - end - - if request.rest_path[2] == 'users' && !updating_public_key response.delete('public_key') end @@ -107,61 +100,66 @@ module ChefZero def populate_defaults(request, response_json) response = FFI_Yajl::Parser.parse(response_json, :create_additions => false) - response = - if request.rest_path[2] == 'clients' - ChefData::DataNormalizer.normalize_client(response, request.rest_path[3], request.rest_path[1]) + client_or_user_name = + if client?(request) + response["name"] else - public_key = get_user_default_public_key(request, response['username']) - - if public_key - response['public_key'] = public_key - end + response["username"] + end || request.rest_path.last - ChefData::DataNormalizer.normalize_user(response, request.rest_path[3], identity_keys, server.options[:osc_compat], request.method) + response = + if client?(request) + ChefData::DataNormalizer.normalize_client( + data_store, + response, + client_or_user_name, + request.rest_path[1] + ) + else + ChefData::DataNormalizer.normalize_user( + data_store, + response, + client_or_user_name, + identity_keys, + server.options[:osc_compat], + request.method + ) end FFI_Yajl::Encoder.encode(response, :pretty => true) end - # Returns the user's default public_key from user_keys store - def get_user_default_public_key(request, username) - path = [ "user_keys", username, "keys", DEFAULT_PUBLIC_KEY_NAME ] - key_json = get_data(request, path, :nil) - return unless key_json - - key_data = FFI_Yajl::Parser.parse(key_json, create_additions: false) - key_data && key_data["public_key"] - end - # Move key data to new path - def rename_user_keys!(request, new_username) - orig_username = request.rest_path[-1] - orig_user_keys_path = [ 'user_keys', orig_username, 'keys' ] - new_user_keys_path = [ 'user_keys', new_username, 'keys' ] + def rename_keys!(request, new_client_or_user_name) + orig_client_or_user_name = request.rest_path.last + + path_root = "#{client_or_user(request)}_keys" + orig_keys_path = [ path_root, orig_client_or_user_name, "keys" ] + new_keys_path = [ path_root, new_client_or_user_name, "keys" ] - user_key_names = list_data(request, orig_user_keys_path, :data_store_exceptions) + key_names = list_data(request, orig_keys_path, :data_store_exceptions) - user_key_names.each do |key_name| + key_names.each do |key_name| # Get old data - orig_path = orig_user_keys_path + [ key_name ] + orig_path = orig_keys_path + [ key_name ] data = get_data(request, orig_path, :data_store_exceptions) # Copy data to new path create_data( request, - new_user_keys_path, key_name, + new_keys_path, key_name, data, :create_dir ) end # Delete original data - delete_data_dir(request, orig_user_keys_path, :data_store_exceptions) + delete_data_dir(request, orig_keys_path, :recursive, :data_store_exceptions) end - def update_user_default_key!(request, public_key) - username = request.rest_path[1] - path = [ "user_keys", username, "keys", DEFAULT_PUBLIC_KEY_NAME ] + def update_default_public_key!(request, client_or_user_name, public_key) + path = [ "#{client_or_user(request)}_keys", client_or_user_name, + "keys", DEFAULT_PUBLIC_KEY_NAME ] data = FFI_Yajl::Encoder.encode( "name" => DEFAULT_PUBLIC_KEY_NAME, @@ -171,6 +169,20 @@ module ChefZero set_data(request, path, data, :create, :data_store_exceptions) end + + def delete_actor_keys!(request, client_or_user_name) + path = [ "#{client_or_user(request)}_keys", client_or_user_name ] + delete_data_dir(request, path, :recursive, :data_store_exceptions) + rescue DataStore::DataNotFoundError + end + + def client_or_user(request) + request.rest_path[2] == "clients" ? :client : :user + end + + def client?(request) + client_or_user(request) == :client + end end end end diff --git a/lib/chef_zero/endpoints/actor_key_endpoint.rb b/lib/chef_zero/endpoints/actor_key_endpoint.rb index 4cfd4b2..98229fc 100644 --- a/lib/chef_zero/endpoints/actor_key_endpoint.rb +++ b/lib/chef_zero/endpoints/actor_key_endpoint.rb @@ -7,12 +7,12 @@ module ChefZero # /organizations/ORG/clients/CLIENT/keys/NAME class ActorKeyEndpoint < RestBase def get(request) - path = [ "user_keys", *request.rest_path[1..-1] ] + path = data_path(request) already_json_response(200, get_data(request, path)) end def delete(request) - path = [ "user_keys", *request.rest_path[1..-1] ] + path = data_path(request) data = get_data(request, path) delete_data(request, path) @@ -21,13 +21,18 @@ module ChefZero end def put(request) - path = [ "user_keys", *request.rest_path[1..-1] ] - # We grab the old data to trigger a 404 if it doesn't exist - get_data(request, path) + get_data(request, data_path(request)) set_data(request, path, request.body) end + + private + + def data_path(request) + root = request.rest_path[2] == "clients" ? "client_keys" : "user_keys" + [root, *request.rest_path.last(3) ] + end end end end diff --git a/lib/chef_zero/endpoints/actor_keys_endpoint.rb b/lib/chef_zero/endpoints/actor_keys_endpoint.rb index 405a927..ed3f828 100644 --- a/lib/chef_zero/endpoints/actor_keys_endpoint.rb +++ b/lib/chef_zero/endpoints/actor_keys_endpoint.rb @@ -9,8 +9,12 @@ module ChefZero DATE_FORMAT = "%FT%TZ" # e.g. 2015-12-24T21:00:00Z def get(request) - username = request.rest_path[1] - path = [ "user_keys", username, "keys" ] + path = + if client?(request) + [ "client_keys", request.rest_path[3], "keys" ] + else + [ "user_keys", request.rest_path[1], "keys" ] + end result = list_data(request, path).map do |key_name| list_key(request, [ *path, key_name ]) @@ -20,10 +24,10 @@ module ChefZero end def post(request) - username = request.rest_path[1] + client_or_user_name = client?(request) ? request.rest_path[3] : request.rest_path[1] request_body = FFI_Yajl::Parser.parse(request.body) - validate_user!(request) + validate_client_or_user!(request) generate_keys = request_body["public_key"].nil? @@ -34,7 +38,7 @@ module ChefZero end key_name = request_body["name"] - path = [ "user_keys", username, "keys" ] + path = [ "#{client_or_user(request)}_keys", client_or_user_name, "keys" ] data = FFI_Yajl::Encoder.encode( "name" => key_name, @@ -44,10 +48,7 @@ module ChefZero create_data(request, path, key_name, data, :create_dir) - response_body = { - "uri" => build_uri(request.base_uri, - [ "users", username, "keys", key_name ]) - } + response_body = { "uri" => key_uri(request, key_name) } response_body["private_key"] = private_key if generate_keys json_response(201, response_body, @@ -58,7 +59,7 @@ module ChefZero def list_key(request, data_path) data = FFI_Yajl::Parser.parse(get_data(request, data_path), create_additions: false) - uri = build_uri(request.base_uri, [ "users", *data_path[1..-1] ]) + key_name = data["name"] expiration_date = if data["expiration_date"] == "infinity" Float::INFINITY @@ -66,14 +67,27 @@ module ChefZero DateTime.strptime(data["expiration_date"], DATE_FORMAT) end - { "name" => data_path[-1], - "uri" => uri, + { "name" => key_name, + "uri" => key_uri(request, key_name), "expired" => DateTime.now > expiration_date } end - def validate_user!(request) - # Try loading the user so a 404 is returned if the user doesn't - get_data(request, request.rest_path[0, 2]) + def validate_client_or_user!(request) + # Try loading the client or user so a 404 is returned if it doesn't exist + path = client?(request) ? request.rest_path[0..3] : request.rest_path[0..1] + get_data(request, path) + end + + def client_or_user(request) + request.rest_path[2] == "clients" ? :client : :user + end + + def client?(request) + client_or_user(request) == :client + end + + def key_uri(request, key_name) + build_uri(request.base_uri, [ *request.rest_path, key_name ]) end end end diff --git a/lib/chef_zero/endpoints/actors_endpoint.rb b/lib/chef_zero/endpoints/actors_endpoint.rb index df6111c..f8d65cb 100644 --- a/lib/chef_zero/endpoints/actors_endpoint.rb +++ b/lib/chef_zero/endpoints/actors_endpoint.rb @@ -8,6 +8,7 @@ module ChefZero DEFAULT_PUBLIC_KEY_NAME = "default" def get(request) + # TODO Refactor this response = super(request) if request.query_params['email'] @@ -23,13 +24,13 @@ module ChefZero response[2] = FFI_Yajl::Encoder.encode(new_results, :pretty => true) end - if request.query_params['verbose'] + if request.query_params['verbose'] && !client?(request) results = FFI_Yajl::Parser.parse(response[2], :create_additions => false) results.each do |name, url| record = get_data(request, request.rest_path + [ name ], :nil) if record record = FFI_Yajl::Parser.parse(record, :create_additions => false) - record = ChefData::DataNormalizer.normalize_user(record, name, identity_keys, server.options[:osc_compat]) + record = ChefData::DataNormalizer.normalize_user(data_store, record, name, identity_keys, server.options[:osc_compat]) results[name] = record end end @@ -40,7 +41,7 @@ module ChefZero def post(request) request_body = FFI_Yajl::Parser.parse(request.body, :create_additions => false) - username = request_body['username'] + client_or_user_name = request_body[ client?(request) ? "name" : "username" ] public_key = request_body["public_key"] @@ -49,18 +50,14 @@ module ChefZero private_key, public_key = server.gen_key_pair end - if request.rest_path[2] == "clients" - request_body['public_key'] = public_key - else - request_body.delete('public_key') - end + request_body.delete('public_key') request.body = FFI_Yajl::Encoder.encode(request_body, :pretty => true) result = super(request) if result[0] == 201 # Store the received or generated public key - create_user_default_key!(request, username, public_key) + store_default_public_key!(request, client_or_user_name, public_key) # If we generated a key, stuff it in the response. response = FFI_Yajl::Parser.parse(result[2], :create_additions => false) @@ -75,11 +72,8 @@ module ChefZero private # Store the public key in user_keys - def create_user_default_key!(request, username, public_key) - # TODO Implement for clients - return if request.rest_path[2] == "clients" - - path = [ "user_keys", username, "keys" ] + def store_default_public_key!(request, client_or_user_name, public_key) + path = [ "#{client_or_user(request)}_keys", client_or_user_name, "keys" ] data = FFI_Yajl::Encoder.encode( "name" => DEFAULT_PUBLIC_KEY_NAME, @@ -89,6 +83,14 @@ module ChefZero create_data(request, path, DEFAULT_PUBLIC_KEY_NAME, data, :create_dir) end + + def client_or_user(request) + request.rest_path[2] == "clients" ? :client : :user + end + + def client?(request) + client_or_user(request) == :client + end end end end diff --git a/lib/chef_zero/endpoints/authenticate_user_endpoint.rb b/lib/chef_zero/endpoints/authenticate_user_endpoint.rb index 5d5bb3b..469e997 100644 --- a/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +++ b/lib/chef_zero/endpoints/authenticate_user_endpoint.rb @@ -15,7 +15,7 @@ module ChefZero raise RestErrorResponse.new(401, "Bad username or password") end user = FFI_Yajl::Parser.parse(user, :create_additions => false) - user = ChefData::DataNormalizer.normalize_user(user, name, [ 'username' ], server.options[:osc_compat]) + user = ChefData::DataNormalizer.normalize_user(data_store, user, name, [ 'username' ], server.options[:osc_compat]) if user['password'] != password raise RestErrorResponse.new(401, "Bad username or password") end diff --git a/lib/chef_zero/endpoints/organization_endpoint.rb b/lib/chef_zero/endpoints/organization_endpoint.rb index a5512db..c600f88 100644 --- a/lib/chef_zero/endpoints/organization_endpoint.rb +++ b/lib/chef_zero/endpoints/organization_endpoint.rb @@ -33,6 +33,9 @@ module ChefZero def delete(request) org = get_data(request, request.rest_path + [ 'org' ]) delete_data_dir(request, request.rest_path, :recursive) + + delete_validator_client!(request, request.rest_path[-1]) + already_json_response(200, populate_defaults(request, org)) end @@ -41,6 +44,30 @@ module ChefZero org = ChefData::DataNormalizer.normalize_organization(org, request.rest_path[1]) FFI_Yajl::Encoder.encode(org, :pretty => true) end + + private + + def validator_name(org_name) + "#{org_name}-validator" + end + + def delete_validator_client!(request, org_name) + client_path = [ *request.rest_path, 'clients', validator_name(org_name) ] + client_data = get_data(request, client_path, :nil) + + if client_data + delete_data(request, client_path, :data_store_exceptions) + end + + delete_validator_client_keys!(request, org_name) + rescue DataStore::DataNotFoundError + end + + def delete_validator_client_keys!(request, org_name) + keys_path = [ "client_keys", validator_name(org_name) ] + delete_data_dir(request, keys_path, :recursive, :data_store_exceptions) + rescue DataStore::DataNotFoundError + end end end end diff --git a/lib/chef_zero/endpoints/organization_user_endpoint.rb b/lib/chef_zero/endpoints/organization_user_endpoint.rb index d0c5b3c..db1fb3d 100644 --- a/lib/chef_zero/endpoints/organization_user_endpoint.rb +++ b/lib/chef_zero/endpoints/organization_user_endpoint.rb @@ -5,39 +5,43 @@ module ChefZero module Endpoints # /organizations/ORG/users/NAME class OrganizationUserEndpoint < RestBase - DEFAULT_PUBLIC_KEY_NAME = "default" - def get(request) username = request.rest_path[3] get_data(request) # 404 if user is not in org - user = get_data(request, [ 'users', username ]) - user = FFI_Yajl::Parser.parse(user, :create_additions => false) - - user["public_key"] = get_user_default_public_key(request, username) - - json_response(200, ChefData::DataNormalizer.normalize_user(user, username, ['username'], server.options[:osc_compat], request.method)) + user_data = get_data(request, [ 'users', username ]) + user = FFI_Yajl::Parser.parse(user_data, :create_additions => false) + + json_response(200, + ChefData::DataNormalizer.normalize_user( + data_store, + user, + username, + ['username'], + server.options[:osc_compat], + request.method + ) + ) end def delete(request) - user = get_data(request) + user_data = get_data(request) delete_data(request) - user = FFI_Yajl::Parser.parse(user, :create_additions => false) - json_response(200, ChefData::DataNormalizer.normalize_user(user, request.rest_path[3], ['username'], server.options[:osc_compat])) + + user = FFI_Yajl::Parser.parse(user_data, :create_additions => false) + + json_response(200, + ChefData::DataNormalizer.normalize_user( + data_store, + user, + request.rest_path[3], + ['username'], + server.options[:osc_compat] + ) + ) end # Note: post to a named org user is not permitted, allow invalid method handling (405) - - private - # Returns the user's default public_key from user_keys store - def get_user_default_public_key(request, username) - path = [ "user_keys", username, "keys", DEFAULT_PUBLIC_KEY_NAME ] - key_json = get_data(request, path, :nil) - return unless key_json - - key_data = FFI_Yajl::Parser.parse(key_json, create_additions: false) - key_data && key_data["public_key"] - end end end end diff --git a/lib/chef_zero/endpoints/organizations_endpoint.rb b/lib/chef_zero/endpoints/organizations_endpoint.rb index 41bf03b..9464ae7 100644 --- a/lib/chef_zero/endpoints/organizations_endpoint.rb +++ b/lib/chef_zero/endpoints/organizations_endpoint.rb @@ -1,11 +1,14 @@ require 'ffi_yajl' require 'chef_zero/rest_base' +require 'chef_zero/chef_data/data_normalizer' require 'uuidtools' module ChefZero module Endpoints # /organizations class OrganizationsEndpoint < RestBase + DEFAULT_PUBLIC_KEY_NAME = "default" + def get(request) result = {} data_store.list(request.rest_path).each do |name| @@ -31,32 +34,60 @@ module ChefZero "guid" => UUIDTools::UUID.random_create.to_s.gsub('-', ''), "assigned_at" => Time.now.to_s }.merge(contents) + org_path = request.rest_path + [ name ] set_data(request, org_path + [ 'org' ], FFI_Yajl::Encoder.encode(org, :pretty => true)) if server.generate_real_keys? - # Create the validator client - validator_name = "#{name}-validator" - validator_path = org_path + [ 'clients', validator_name ] - private_key, public_key = server.gen_key_pair - validator = FFI_Yajl::Encoder.encode({ - 'validator' => true, - 'public_key' => public_key - }, :pretty => true) - set_data(request, validator_path, validator) + private_key = create_validator_client!(request, org_path) end - json_response(201, { - "uri" => "#{build_uri(request.base_uri, org_path)}", + "uri" => build_uri(request.base_uri, org_path), "name" => name, "org_type" => org["org_type"], "full_name" => full_name, - "clientname" => validator_name, + "clientname" => validator_name(name), "private_key" => private_key }) end end + + private + + def validator_name(org_name) + "#{org_name}-validator" + end + + def create_validator_client!(request, org_path) + name = validator_name(org_path.last) + validator_path = [ *org_path, 'clients', name ] + + private_key, public_key = server.gen_key_pair + + validator = FFI_Yajl::Encoder.encode({ + 'validator' => true, + }, :pretty => true) + + set_data(request, validator_path, validator) + + store_default_public_key!(request, name, public_key) + + private_key + end + + # Store the validator client's public key in client_keys + def store_default_public_key!(request, client_name, public_key) + path = [ "client_keys", client_name, "keys" ] + + data = FFI_Yajl::Encoder.encode( + "name" => DEFAULT_PUBLIC_KEY_NAME, + "public_key" => public_key, + "expiration_date" => "infinity" + ) + + create_data(request, path, DEFAULT_PUBLIC_KEY_NAME, data, :create_dir) + end end end end diff --git a/lib/chef_zero/endpoints/search_endpoint.rb b/lib/chef_zero/endpoints/search_endpoint.rb index a9ad2bf..200a1ad 100644 --- a/lib/chef_zero/endpoints/search_endpoint.rb +++ b/lib/chef_zero/endpoints/search_endpoint.rb @@ -48,22 +48,21 @@ module ChefZero private def search_container(request, index, orgname) - relative_parts, normalize_proc = case index - when 'client' - [ ['clients'], Proc.new { |client, name| ChefData::DataNormalizer.normalize_client(client, name, orgname) } ] - when 'node' - [ ['nodes'], Proc.new { |node, name| ChefData::DataNormalizer.normalize_node(node, name) } ] - when 'environment' - [ ['environments'], Proc.new { |environment, name| ChefData::DataNormalizer.normalize_environment(environment, name) } ] - when 'role' - [ ['roles'], Proc.new { |role, name| ChefData::DataNormalizer.normalize_role(role, name) } ] - else - [ ['data', index], Proc.new { |data_bag_item, id| ChefData::DataNormalizer.normalize_data_bag_item(data_bag_item, index, id, 'DELETE') } ] - end - [ - request.rest_path[0..1] + relative_parts, - normalize_proc - ] + *relative_parts, normalize_proc = + case index + when 'client' + [ 'clients', method(:normalize_client).to_proc.curry[orgname] ] + when 'node' + [ 'nodes', ChefData::DataNormalizer.method(:normalize_node) ] + when 'environment' + [ 'environments', ChefData::DataNormalizer.method(:normalize_environment) ] + when 'role' + [ 'roles', ChefData::DataNormalizer.method(:normalize_role) ] + else + [ 'data', index, method(:normalize_data_bag_item).to_proc.curry[index] ] + end + + [ request.rest_path[0..1] + relative_parts, normalize_proc ] end def expand_for_indexing(value, index, id) @@ -189,6 +188,14 @@ module ChefZero dest end # deep_merge! + def normalize_client(orgname, client, name) + ChefData::DataNormalizer.normalize_client(data_store, client, name, orgname) + end + + def normalize_data_bag_item(index, data_bag_item, id) + ChefData::DataNormalizer + .normalize_data_bag_item(data_bag_item, index, id, 'DELETE') + end end end end diff --git a/lib/chef_zero/endpoints/system_recovery_endpoint.rb b/lib/chef_zero/endpoints/system_recovery_endpoint.rb index be438f8..fa3589e 100644 --- a/lib/chef_zero/endpoints/system_recovery_endpoint.rb +++ b/lib/chef_zero/endpoints/system_recovery_endpoint.rb @@ -15,7 +15,7 @@ module ChefZero end user = FFI_Yajl::Parser.parse(user, :create_additions => false) - user = ChefData::DataNormalizer.normalize_user(user, name, [ 'username' ], server.options[:osc_compat]) + user = ChefData::DataNormalizer.normalize_user(data_store, user, name, [ 'username' ], server.options[:osc_compat]) if !user['recovery_authentication_enabled'] raise RestErrorResponse.new(403, "Only users with recovery_authentication_enabled=true may use /system_recovery to log in") end diff --git a/lib/chef_zero/rest_base.rb b/lib/chef_zero/rest_base.rb index f276426..6a9bd2e 100644 --- a/lib/chef_zero/rest_base.rb +++ b/lib/chef_zero/rest_base.rb @@ -114,7 +114,7 @@ module ChefZero if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}") + raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}") end end @@ -133,7 +133,7 @@ module ChefZero if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}") + raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}") end end @@ -152,7 +152,7 @@ module ChefZero if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}") + raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}") end end end @@ -165,13 +165,13 @@ module ChefZero if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, request.rest_path)}") + raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}") end rescue DataStore::DataAlreadyExistsError if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, request.rest_path + [name])}") + raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}") end end end @@ -184,13 +184,13 @@ module ChefZero if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, request.rest_path)}") + raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}") end rescue DataStore::DataAlreadyExistsError if options.include?(:data_store_exceptions) raise else - raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, request.rest_path + [name])}") + raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}") end end end diff --git a/lib/chef_zero/rest_router.rb b/lib/chef_zero/rest_router.rb index f2770d3..889c810 100644 --- a/lib/chef_zero/rest_router.rb +++ b/lib/chef_zero/rest_router.rb @@ -1,3 +1,5 @@ +require 'pp' + module ChefZero class RestRouter def initialize(routes) @@ -15,24 +17,18 @@ module ChefZero attr_accessor :not_found def call(request) - begin - ChefZero::Log.debug(request) - ChefZero::Log.debug(request.body) if request.body - - clean_path = "/" + request.rest_path.join("/") - - response = find_endpoint(clean_path).call(request) - ChefZero::Log.debug([ - "", - "--- RESPONSE (#{response[0]}) ---", - response[2], - "--- END RESPONSE ---", - ].join("\n")) - return response - rescue - ChefZero::Log.error("#{$!.inspect}\n#{$!.backtrace.join("\n")}") - [500, {"Content-Type" => "text/plain"}, "Exception raised! #{$!.inspect}\n#{$!.backtrace.join("\n")}"] + log_request(request) + + clean_path = "/" + request.rest_path.join("/") + + find_endpoint(clean_path).call(request).tap do |response| + log_response(response) end + rescue => ex + exception = "#{ex.inspect}\n#{ex.backtrace.join("\n")}" + + ChefZero::Log.error(exception) + [ 500, { "Content-Type" => "text/plain" }, "Exception raised! #{exception}" ] end private @@ -41,5 +37,36 @@ module ChefZero _, endpoint = routes.find { |route, endpoint| route.match(clean_path) } endpoint || not_found end + + def log_request(request) + ChefZero::Log.info do + "#{request.method} /#{request.rest_path.join("/")}".tap do |msg| + next unless request.method =~ /^(POST|PUT)$/ + + if request.body.nil? || request.body.empty? + msg << " (no body)" + else + msg << [ + "", + "--- #{request.method} BODY ---", + request.body.chomp, + "--- END #{request.method} BODY ---" + ].join("\n") + end + end + end + + ChefZero::Log.debug { request.pretty_inspect } + end + + def log_response(response) + ChefZero::Log.info { + [ "", + "--- RESPONSE (#{response[0]}) ---", + response[2].chomp, + "--- END RESPONSE ---", + ].join("\n") + } + end end end diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb index 45d36c3..da277b6 100644 --- a/lib/chef_zero/server.rb +++ b/lib/chef_zero/server.rb @@ -109,7 +109,7 @@ module ChefZero DEFAULT_OPTIONS = { :host => '127.0.0.1', :port => 8889, - :log_level => :info, + :log_level => :warn, :generate_real_keys => true, :single_org => 'chef', :ssl => false diff --git a/spec/run_oc_pedant.rb b/spec/run_oc_pedant.rb index 25672fb..e4e8475 100644 --- a/spec/run_oc_pedant.rb +++ b/spec/run_oc_pedant.rb @@ -5,6 +5,42 @@ require 'bundler/setup' require 'chef_zero/server' require 'rspec/core' +# This file runs oc-chef-pedant specs and is invoked by `rake pedant` +# and other Rake tasks. Run `rake -T` to list tasks. +# +# Options for oc-chef-pedant and rspec can be specified via +# ENV['PEDANT_OPTS'] and ENV['RSPEC_OPTS'], respectively. +# +# The log level can be specified via ENV['LOG_LEVEL']. +# +# Example: +# +# $ PEDANT_OPTS="--focus-users --skip-keys" \ +# > RSPEC_OPTS="--fail-fast --profile 5" \ +# > LOG_LEVEL=debug \ +# > rake pedant +# + +DEFAULT_SERVER_OPTIONS = { + port: 8889, + single_org: false, +}.freeze + +DEFAULT_LOG_LEVEL = :warn + +def log_level + return ENV['LOG_LEVEL'].downcase.to_sym if ENV['LOG_LEVEL'] + return :debug if ENV['DEBUG'] + DEFAULT_LOG_LEVEL +end + +def start_chef_server(opts={}) + opts = DEFAULT_SERVER_OPTIONS.merge(opts) + opts[:log_level] = log_level + + ChefZero::Server.new(opts).tap {|server| server.start_background } +end + def start_cheffs_server(chef_repo_path) require 'chef/version' require 'chef/config' @@ -34,37 +70,42 @@ def start_cheffs_server(chef_repo_path) data_store.set(%w(organizations pedant-testorg groups admins), '{ "users": [ "pivotal" ] }') data_store.set(%w(organizations pedant-testorg groups users), '{ "users": [ "pivotal" ] }') - server = ChefZero::Server.new( - port: 8889, - data_store: data_store, - single_org: false, - #log_level: :debug - ) - server.start_background - server + start_chef_server(data_store: data_store) +end + +def pedant_args_from_env + args_from_env('PEDANT_OPTS') +end + +def rspec_args_from_env + args_from_env('RSPEC_OPTS') end -tmpdir = nil +def args_from_env(key) + return [] unless ENV[key] + ENV[key].split +end begin - if ENV['FILE_STORE'] - require 'tmpdir' - require 'chef_zero/data_store/raw_file_store' - tmpdir = Dir.mktmpdir - data_store = ChefZero::DataStore::RawFileStore.new(tmpdir, true) - data_store = ChefZero::DataStore::DefaultFacade.new(data_store, false, false) - server = ChefZero::Server.new(:port => 8889, :single_org => false, :data_store => data_store) - server.start_background - - elsif ENV['CHEF_FS'] - require 'tmpdir' - tmpdir = Dir.mktmpdir - server = start_cheffs_server(tmpdir) + tmpdir = nil + server = + if ENV['FILE_STORE'] + require 'tmpdir' + require 'chef_zero/data_store/raw_file_store' + tmpdir = Dir.mktmpdir + data_store = ChefZero::DataStore::RawFileStore.new(tmpdir, true) + data_store = ChefZero::DataStore::DefaultFacade.new(data_store, false, false) + + start_chef_server(data_store: data_store) + + elsif ENV['CHEF_FS'] + require 'tmpdir' + tmpdir = Dir.mktmpdir + start_cheffs_server(tmpdir) - else - server = ChefZero::Server.new(:port => 8889, :single_org => false)#, :log_level => :debug) - server.start_background - end + else + start_chef_server + end require 'rspec/core' require 'pedant' @@ -104,7 +145,6 @@ begin # are turned off" - @jkeiser # # ...but we're not there yet - '--skip-client-keys', '--skip-controls', '--skip-acl', @@ -145,14 +185,10 @@ begin default_skips + chef_fs_skips + %w{ --skip-knife } end - pedant_args << "--focus-client-keys" - - Pedant.setup(pedant_args) - - # fail_fast = [] - fail_fast = ["--fail-fast"] + Pedant.setup(pedant_args + pedant_args_from_env) - result = RSpec::Core::Runner.run(Pedant.config.rspec_args + fail_fast) + rspec_args = Pedant.config.rspec_args + rspec_args_from_env + result = RSpec::Core::Runner.run(rspec_args) server.stop if server.running? ensure |