summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortylercloke <tylercloke@gmail.com>2015-05-27 14:32:24 -0700
committertylercloke <tylercloke@gmail.com>2015-06-05 10:38:48 -0700
commit9b9d376f2e86cd2738c2c2928f77bf48f1dd20ee (patch)
tree226227e7e32f421a0dde0f1ad2046059eff449c3
parent20f7e1c78c55d2a16d5033bc2bbe5904d225adb0 (diff)
downloadchef-9b9d376f2e86cd2738c2c2928f77bf48f1dd20ee.tar.gz
Added versioned Chef Object API support code and repaired Chef::User.create.
Also added Chef::User.create V1 API support. Chef::User.create will attempt to use V1 of the server API, and if it fails, it will fall back to V0.
-rw-r--r--lib/chef/exceptions.rb1
-rw-r--r--lib/chef/formatters/error_inspectors/api_error_formatting.rb15
-rw-r--r--lib/chef/http/authenticator.rb9
-rw-r--r--lib/chef/knife.rb20
-rw-r--r--lib/chef/knife/user_create.rb74
-rw-r--r--lib/chef/mixin/api_version_request_handling.rb53
-rw-r--r--lib/chef/rest.rb1
-rw-r--r--lib/chef/user.rb158
-rw-r--r--lib/chef/versioned_rest.rb27
-rw-r--r--spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb22
-rw-r--r--spec/unit/http/authenticator_spec.rb13
-rw-r--r--spec/unit/knife/user_create_spec.rb243
-rw-r--r--spec/unit/knife_spec.rb14
-rw-r--r--spec/unit/rest_spec.rb10
-rw-r--r--spec/unit/user_spec.rb411
15 files changed, 864 insertions, 207 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index c0f4158db4..197d720776 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -73,6 +73,7 @@ class Chef
class KeyCommandInputError < ArgumentError; end
class InvalidKeyArgument < ArgumentError; end
class InvalidKeyAttribute < ArgumentError; end
+ class InvalidUserAttribute < ArgumentError; end
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookFrozen < ArgumentError; end
diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
index ec25f4e903..d68b38e41f 100644
--- a/lib/chef/formatters/error_inspectors/api_error_formatting.rb
+++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
@@ -68,13 +68,16 @@ E
end
def describe_406_error(error_description, response)
- if Chef::JSONCompat.from_json(response.body)["error"] == "invalid-x-ops-server-api-version"
- min_version = Chef::JSONCompat.from_json(response.body)["min_version"]
- max_version = Chef::JSONCompat.from_json(response.body)["max_version"]
+ if response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
+
error_description.section("Incompatible server API version:",<<-E)
-This version of Chef is not supported by the Chef server you sent this request to
-This version of Chef requires a server API version of #{Chef::HTTP::Authenticator::SERVER_API_VERSION}
-The Chef server you sent the request to supports a min API version of #{min_version} and a max API version of #{max_version}
+This version of the API that this Chef request specified is not supported by the Chef server you sent this request to
+The Chef server you sent the request to supports a min API version of #{min_server_version} and a max API version of #{max_server_version}
+Chef just made a request with an API version of #{client_api_version}
Please either update your Chef client or server to be a compatible set
E
else
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 4ec35add34..b5367d4bc5 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -24,7 +24,7 @@ class Chef
class HTTP
class Authenticator
- SERVER_API_VERSION = "0"
+ DEFAULT_SERVER_API_VERSION = "0"
attr_reader :signing_key_filename
attr_reader :raw_key
@@ -39,11 +39,16 @@ class Chef
@signing_key_filename = opts[:signing_key_filename]
@key = load_signing_key(opts[:signing_key_filename], opts[:raw_key])
@auth_credentials = AuthCredentials.new(opts[:client_name], @key)
+ if opts[:api_version]
+ @api_version = opts[:api_version]
+ else
+ @api_version = DEFAULT_SERVER_API_VERSION
+ end
end
def handle_request(method, url, headers={}, data=false)
headers.merge!(authentication_headers(method, url, data)) if sign_requests?
- headers.merge!({'X-Ops-Server-API-Version' => SERVER_API_VERSION})
+ headers.merge!({'X-Ops-Server-API-Version' => @api_version})
[method, url, headers, data]
end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 71215e7fbf..4a93697a1b 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -487,11 +487,13 @@ class Chef
ui.error "Service temporarily unavailable"
ui.info "Response: #{format_rest_error(response)}"
when Net::HTTPNotAcceptable
- min_version = Chef::JSONCompat.from_json(response.body)["min_version"]
- max_version = Chef::JSONCompat.from_json(response.body)["max_version"]
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
ui.error "The version of Chef that Knife is using is not supported by the Chef server you sent this request to"
- ui.info "This version of Chef requires a server API version of #{Chef::HTTP::Authenticator::SERVER_API_VERSION}"
- ui.info "The Chef server you sent the request to supports a min API verson of #{min_version} and a max API version of #{max_version}"
+ ui.info "The request that Knife sent was using API version #{client_api_version}"
+ ui.info "The Chef server you sent the request to supports a min API verson of #{min_server_version} and a max API version of #{max_server_version}"
ui.info "Please either update your Chef client or server to be a compatible set"
else
ui.error response.message
@@ -549,6 +551,16 @@ class Chef
self.msg("Deleted #{obj_name}")
end
+ # helper method for testing if a field exists
+ # and returning the usage and proper error if not
+ def test_mandatory_field(field, fieldname)
+ if field.nil?
+ show_usage
+ ui.fatal("You must specify a #{fieldname}")
+ exit 1
+ end
+ end
+
def rest
@rest ||= begin
require 'chef/rest'
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 4130f06878..992bbbe3c9 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -1,6 +1,7 @@
#
-# Author:: Steven Danna (<steve@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve@chef.io>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +23,8 @@ class Chef
class Knife
class UserCreate < Knife
+ attr_accessor :user_field
+
deps do
require 'chef/user'
require 'chef/json_compat'
@@ -30,61 +33,78 @@ class Chef
option :file,
:short => "-f FILE",
:long => "--file FILE",
- :description => "Write the private key to a file"
+ :description => "Write the private key to a file, if returned by the server. A private key will be returned when both --user-key and --no-key are NOT passed. In that case, the server will generate a default key for you and return the private key it creates."
option :admin,
:short => "-a",
:long => "--admin",
- :description => "Create the user as an admin",
+ :description => "Create the user as an admin (only relevant for Open Source Chef Server 11).",
:boolean => true
- option :user_password,
- :short => "-p PASSWORD",
- :long => "--password PASSWORD",
- :description => "Password for newly created user",
- :default => ""
-
option :user_key,
:long => "--user-key FILENAME",
- :description => "Public key for newly created user. By default a key will be created for you."
+ :description => "Public key for newly created user. Path to a public key you provide instead of having the server generate one. If --user-key is not passed, the server will create a 'default' key for you, unless you passed --no-key. Note that --user-key cannot be passed with --no-key."
+
+ option :no_key,
+ :long => "--no-key",
+ :description => "Do not create a 'default' public key for this new user. This prevents the server generating a public key by default. Cannot be passed with --user-key (requires server API version 1)."
+
+ banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
- banner "knife user create USER (options)"
+ def user
+ @user_field ||= Chef::User.new
+ end
+
+ def create_user_from_hash(hash)
+ Chef::User.from_hash(hash).create
+ end
def run
- @user_name = @name_args[0]
+ test_mandatory_field(@name_args[0], "username")
+ user.username @name_args[0]
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
+ test_mandatory_field(@name_args[1], "display name")
+ user.display_name @name_args[1]
+
+ test_mandatory_field(@name_args[2], "first name")
+ user.first_name @name_args[2]
+
+ test_mandatory_field(@name_args[3], "last name")
+ user.last_name @name_args[3]
- if config[:user_password].length == 0
+ test_mandatory_field(@name_args[4], "email")
+ user.email @name_args[4]
+
+ test_mandatory_field(@name_args[5], "password")
+ user.password @name_args[5]
+
+ if config[:user_key] && config[:no_key]
show_usage
- ui.fatal("You must specify a non-blank password")
+ ui.fatal("You cannot pass --user-key and --no-key")
exit 1
end
- user = Chef::User.new
- user.name(@user_name)
user.admin(config[:admin])
- user.password config[:user_password]
+
+ unless config[:no_key]
+ user.create_key(true)
+ end
if config[:user_key]
user.public_key File.read(File.expand_path(config[:user_key]))
end
output = edit_data(user)
- user = Chef::User.from_hash(output).create
+ final_user = create_user_from_hash(output)
ui.info("Created #{user}")
- if user.private_key
+ if final_user.private_key
if config[:file]
File.open(config[:file], "w") do |f|
- f.print(user.private_key)
+ f.print(final_user.private_key)
end
else
- ui.msg user.private_key
+ ui.msg final_user.private_key
end
end
end
diff --git a/lib/chef/mixin/api_version_request_handling.rb b/lib/chef/mixin/api_version_request_handling.rb
new file mode 100644
index 0000000000..ea68afc6af
--- /dev/null
+++ b/lib/chef/mixin/api_version_request_handling.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright 2015 Chef Software, 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.
+#
+
+class Chef
+ module ApiVersionRequestHandling
+ # takes in an http exception, and a min and max supported API version and
+ # handles all the versioning cases
+ #
+ # it will raise an exception if there was a non-versioning related error
+ # or the server and the client are not compatible
+ #
+ # if the server does not support versioning, then it will not raise, and you
+ # can assume API v0 is safe to send
+ def handle_version_http_exception(exception, min_client_supported_version, max_client_supported_version)
+
+ # only rescue 406 Unacceptable
+ return false unless exception.response.code == "406" || !exception.response["x-ops-server-api-version"]
+
+ exception.response["x-ops-server-api-version"]
+
+ # if the version header doesn't exist, just assume API v0
+ if exception.response["x-ops-server-api-version"]
+ header = Chef::JSONCompat.from_json(exception.response["x-ops-server-api-version"])
+ min_server_version = Integer(header["min_version"])
+ max_server_version = Integer(header["max_version"])
+
+ # if the min API version the server supports is greater than the min version the client supports
+ # and the max API version the server supports is less than the max version the client supports
+ if min_server_version > min_client_supported_version || max_server_version < max_client_supported_version
+ # if it had x-ops-server-api-version header, it will error out properly, just raise it
+ return false
+ end
+ end
+ true
+ end
+
+ end
+end
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index 2612714a19..f87cec9b76 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -64,6 +64,7 @@ class Chef
options = options.dup
options[:client_name] = client_name
options[:signing_key_filename] = signing_key_filename
+
super(url, options)
@decompressor = Decompressor.new(options)
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 42fa6b5fa1..4e0d12e3b5 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -21,31 +21,96 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
+require 'chef/versioned_rest'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/exceptions'
class Chef
class User
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
+ include Chef::VersionedRest
+ include Chef::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
def initialize
- @name = ''
+ @username = nil
+ @display_name = nil
+ @first_name = nil
+ @middle_name = nil
+ @last_name = nil
+ @email = nil
+ @password = nil
@public_key = nil
@private_key = nil
+ @create_key = nil
@password = nil
@admin = false
end
- def name(arg=nil)
- set_or_return(:name, arg,
+ def chef_rest_v0
+ @chef_rest_v0 ||= get_versioned_rest_object(Chef::Config[:chef_server_url], "0")
+ end
+
+ def chef_rest_v1
+ @chef_rest_v1 ||= get_versioned_rest_object(Chef::Config[:chef_server_url], "1")
+ end
+
+ def chef_root_rest_v0
+ @chef_root_rest_v0 ||= get_versioned_rest_object(Chef::Config[:chef_server_root], "0")
+ end
+
+ def chef_root_rest_v1
+ @chef_root_rest_v1 ||= get_versioned_rest_object(Chef::Config[:chef_server_root], "1")
+ end
+
+ def username(arg=nil)
+ set_or_return(:username, arg,
:regex => /^[a-z0-9\-_]+$/)
end
+ def display_name(arg=nil)
+ set_or_return(:display_name,
+ arg, :kind_of => String)
+ end
+
+ def first_name(arg=nil)
+ set_or_return(:first_name,
+ arg, :kind_of => String)
+ end
+
+ def middle_name(arg=nil)
+ set_or_return(:middle_name,
+ arg, :kind_of => String)
+ end
+
+ def last_name(arg=nil)
+ set_or_return(:last_name,
+ arg, :kind_of => String)
+ end
+
+ def email(arg=nil)
+ set_or_return(:email,
+ arg, :kind_of => String)
+ end
+
+ def password(arg=nil)
+ set_or_return(:password,
+ arg, :kind_of => String)
+ end
+
def admin(arg=nil)
set_or_return(:admin,
arg, :kind_of => [TrueClass, FalseClass])
end
+ def create_key(arg=nil)
+ set_or_return(:create_key, arg,
+ :kind_of => [TrueClass, FalseClass])
+ end
+
def public_key(arg=nil)
set_or_return(:public_key,
arg, :kind_of => String)
@@ -63,12 +128,17 @@ class Chef
def to_hash
result = {
- "name" => @name,
- "public_key" => @public_key,
+ "username" => @username,
"admin" => @admin
}
- result["private_key"] = @private_key if @private_key
+ result["display_name"] = @display_name if @display_name
+ result["first_name"] = @first_name if @first_name
+ result["middle_name"] = @middle_name if @middle_name
+ result["last_name"] = @last_name if @last_name
+ result["email"] = @email if @email
result["password"] = @password if @password
+ result["public_key"] = @public_key if @public_key
+ result["private_key"] = @private_key if @private_key
result
end
@@ -77,21 +147,59 @@ class Chef
end
def destroy
- Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}")
+ Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}")
end
def create
- payload = {:name => self.name, :admin => self.admin, :password => self.password }
- payload[:public_key] = public_key if public_key
- new_user =Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("users", payload)
+ # try v1, fail back to v0 if v1 not supported
+ begin
+ payload = {
+ :username => @username,
+ :display_name => @display_name,
+ :first_name => @first_name,
+ :last_name => @last_name,
+ :email => @email,
+ :password => @password,
+ :admin => @admin
+ }
+ payload[:public_key] = @public_key if @public_key
+ payload[:create_key] = @create_key if @create_key
+ payload[:middle_name] = @middle_name if @middle_name
+ raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if @create_key && @public_key
+ new_user = chef_root_rest_v1.post("users", payload)
+
+ # get the private_key out of the chef_key hash if it exists
+ if new_user['chef_key']
+ if new_user['chef_key']['private_key']
+ new_user['private_key'] = new_user['chef_key']['private_key']
+ end
+ new_user['public_key'] = new_user['chef_key']['public_key']
+ new_user.delete('chef_key')
+ end
+ rescue Net::HTTPServerException => e
+ raise e unless handle_version_http_exception(e, SUPPORTED_API_VERSIONS[0], SUPPORTED_API_VERSIONS[-1])
+ payload = {
+ :username => @username,
+ :display_name => @display_name,
+ :first_name => @first_name,
+ :last_name => @last_name,
+ :email => @email,
+ :password => @password,
+ :admin => @admin
+ }
+ payload[:middle_name] = @middle_name if @middle_name
+ payload[:public_key] = @public_key if @public_key
+ new_user = chef_root_rest_v0.post("users", payload)
+ end
+
Chef::User.from_hash(self.to_hash.merge(new_user))
end
def update(new_key=false)
- payload = {:name => name, :admin => admin}
+ payload = {:username => username, :admin => admin}
payload[:private_key] = new_key if new_key
payload[:password] = password if password
- updated_user = Chef::REST.new(Chef::Config[:chef_server_url]).put_rest("users/#{name}", payload)
+ updated_user = Chef::REST.new(Chef::Config[:chef_server_url]).put("users/#{username}", payload)
Chef::User.from_hash(self.to_hash.merge(updated_user))
end
@@ -109,17 +217,17 @@ class Chef
def reregister
r = Chef::REST.new(Chef::Config[:chef_server_url])
- reregistered_self = r.put_rest("users/#{name}", { :name => name, :admin => admin, :private_key => true })
+ reregistered_self = r.put("users/#{username}", { :username => username, :admin => admin, :private_key => true })
private_key(reregistered_self["private_key"])
self
end
def to_s
- "user[#{@name}]"
+ "user[#{@username}]"
end
def inspect
- "Chef::User name:'#{name}' admin:'#{admin.inspect}'" +
+ "Chef::User username:'#{username}' admin:'#{admin.inspect}'" +
"public_key:'#{public_key}' private_key:#{private_key}"
end
@@ -127,11 +235,17 @@ class Chef
def self.from_hash(user_hash)
user = Chef::User.new
- user.name user_hash['name']
- user.private_key user_hash['private_key'] if user_hash.key?('private_key')
- user.password user_hash['password'] if user_hash.key?('password')
- user.public_key user_hash['public_key']
+ user.username user_hash['username']
user.admin user_hash['admin']
+ user.display_name user_hash['display_name'] if user_hash.key?('display_name')
+ user.first_name user_hash['first_name'] if user_hash.key?('first_name')
+ user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name')
+ user.last_name user_hash['last_name'] if user_hash.key?('last_name')
+ user.email user_hash['email'] if user_hash.key?('email')
+ user.password user_hash['password'] if user_hash.key?('password')
+ user.public_key user_hash['public_key'] if user_hash.key?('public_key')
+ user.private_key user_hash['private_key'] if user_hash.key?('private_key')
+ user.create_key user_hash['create_key'] if user_hash.key?('create_key')
user
end
@@ -144,7 +258,7 @@ class Chef
end
def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
users = if response.is_a?(Array)
transform_ohc_list_response(response) # OHC/OPC
else
@@ -160,8 +274,8 @@ class Chef
end
end
- def self.load(name)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
+ def self.load(username)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}")
Chef::User.from_hash(response)
end
diff --git a/lib/chef/versioned_rest.rb b/lib/chef/versioned_rest.rb
new file mode 100644
index 0000000000..b37a7e2249
--- /dev/null
+++ b/lib/chef/versioned_rest.rb
@@ -0,0 +1,27 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright 2015 Chef Software, 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.
+#
+
+class Chef
+ module VersionedRest
+ # Helper for getting a sane interface to passing an API version to Chef::REST
+ # api_version should be a string of an integer
+ def get_versioned_rest_object(url, api_version)
+ Chef::REST.new(url, Chef::Config[:node_name], Chef::Config[:client_key], {:api_version => api_version})
+ end
+ end
+end
diff --git a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
index 4b3b8bff44..b8c2de2b8b 100644
--- a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
+++ b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
@@ -29,19 +29,21 @@ describe Chef::Formatters::APIErrorFormatting do
context "when describe_406_error is called" do
- context "when response.body['error'] == 'invalid-x-ops-server-api-version'" do
+ context "when response['x-ops-server-api-version'] exists" do
let(:min_version) { "2" }
let(:max_version) { "5" }
+ let(:request_version) { "30" }
let(:return_hash) {
{
- "error" => "invalid-x-ops-server-api-version",
"min_version" => min_version,
- "max_version" => max_version
+ "max_version" => max_version,
+ "request_version" => request_version
}
}
before do
- allow(Chef::JSONCompat).to receive(:from_json).and_return(return_hash)
+ # mock out the header
+ allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash))
end
it "prints an error about client and server API version incompatibility with a min API version" do
@@ -53,17 +55,17 @@ describe Chef::Formatters::APIErrorFormatting do
expect(error_description).to receive(:section).with("Incompatible server API version:",/a max API version of #{max_version}/)
class_instance.describe_406_error(error_description, response)
end
+
+ it "prints an error describing the request API version" do
+ expect(error_description).to receive(:section).with("Incompatible server API version:",/a request with an API version of #{request_version}/)
+ class_instance.describe_406_error(error_description, response)
+ end
end
context "when response.body['error'] != 'invalid-x-ops-server-api-version'" do
- let(:return_hash) {
- {
- "error" => "some-other-error"
- }
- }
before do
- allow(Chef::JSONCompat).to receive(:from_json).and_return(return_hash)
+ allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(nil)
end
it "forwards the error_description to describe_http_error" do
diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb
index 82d38d6c2b..48bbdcf76c 100644
--- a/spec/unit/http/authenticator_spec.rb
+++ b/spec/unit/http/authenticator_spec.rb
@@ -32,10 +32,19 @@ describe Chef::HTTP::Authenticator do
context "when handle_request is called" do
shared_examples_for "merging the server API version into the headers" do
- it "merges X-Ops-Server-API-Version into the headers" do
+ it "merges the default version of X-Ops-Server-API-Version into the headers" do
# headers returned
expect(class_instance.handle_request(method, url, headers, data)[2]).
- to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION})
+ to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION})
+ end
+
+ context "when api_version is set to something other than the default" do
+ let(:class_instance) { Chef::HTTP::Authenticator.new({:api_version => '-10'}) }
+
+ it "merges the requested version of X-Ops-Server-API-Version into the headers" do
+ expect(class_instance.handle_request(method, url, headers, data)[2]).
+ to include({'X-Ops-Server-API-Version' => '-10'})
+ end
end
end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
index ad8821cd0e..dc2293396c 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/user_create_spec.rb
@@ -1,6 +1,7 @@
#
-# Author:: Steven Danna (<steve@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve@chef.io>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,68 +22,214 @@ require 'spec_helper'
Chef::Knife::UserCreate.load_deps
describe Chef::Knife::UserCreate do
- before(:each) do
- @knife = Chef::Knife::UserCreate.new
+ let(:knife) { Chef::Knife::UserCreate.new }
+ before(:each) do
@stdout = StringIO.new
@stderr = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
-
- @knife.name_args = [ 'a_user' ]
- @knife.config[:user_password] = "foobar"
- @user = Chef::User.new
- @user.name "a_user"
- @user_with_private_key = Chef::User.new
- @user_with_private_key.name "a_user"
- @user_with_private_key.private_key 'private_key'
- allow(@user).to receive(:create).and_return(@user_with_private_key)
- allow(Chef::User).to receive(:new).and_return(@user)
- allow(Chef::User).to receive(:from_hash).and_return(@user)
- allow(@knife).to receive(:edit_data).and_return(@user.to_hash)
+ allow(knife.ui).to receive(:stdout).and_return(@stdout)
+ allow(knife.ui).to receive(:stderr).and_return(@stderr)
end
- it "creates a new user" do
- expect(Chef::User).to receive(:new).and_return(@user)
- expect(@user).to receive(:create)
- @knife.run
- expect(@stderr.string).to match /created user.+a_user/i
+ shared_examples_for "mandatory field missing" do
+ context "when field is nil" do
+ before do
+ knife.name_args = name_args
+ end
+
+ it "exits 1" do
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to match /You must specify a #{fieldname}/
+ end
+ end
end
- it "sets the password" do
- @knife.config[:user_password] = "a_password"
- expect(@user).to receive(:password).with("a_password")
- @knife.run
+ context "when USERNAME isn't specified" do
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { [] }
+ let(:fieldname) { 'username' }
+ end
end
- it "exits with an error if password is blank" do
- @knife.config[:user_password] = ''
- expect { @knife.run }.to raise_error SystemExit
- expect(@stderr.string).to match /You must specify a non-blank password/
+ context "when DISPLAY_NAME isn't specified" do
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user'] }
+ let(:fieldname) { 'display name' }
+ end
end
- it "sets the user name" do
- expect(@user).to receive(:name).with("a_user")
- @knife.run
+ context "when FIRST_NAME isn't specified" do
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name'] }
+ let(:fieldname) { 'first name' }
+ end
end
- it "sets the public key if given" do
- @knife.config[:user_key] = "/a/filename"
- allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key")
- expect(@user).to receive(:public_key).with("a_key")
- @knife.run
+ context "when LAST_NAME isn't specified" do
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name', 'some_first_name'] }
+ let(:fieldname) { 'last name' }
+ end
end
- it "allows you to edit the data" do
- expect(@knife).to receive(:edit_data).with(@user)
- @knife.run
+ context "when EMAIL isn't specified" do
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name'] }
+ let(:fieldname) { 'email' }
+ end
end
- it "writes the private key to a file when --file is specified" do
- @knife.config[:file] = "/tmp/a_file"
- filehandle = double("filehandle")
- expect(filehandle).to receive(:print).with('private_key')
- expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
- @knife.run
+ context "when PASSWORD isn't specified" do
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email'] }
+ let(:fieldname) { 'password' }
+ end
end
+
+ context "when all mandatory fields are validly specified" do
+ before do
+ knife.name_args = ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email', 'some_password']
+ allow(knife).to receive(:edit_data).and_return(knife.user.to_hash)
+ allow(knife).to receive(:create_user_from_hash).and_return(knife.user)
+ end
+
+ before(:each) do
+ # reset the user field every run
+ knife.user_field = nil
+ end
+
+ it "sets all the mandatory fields" do
+ knife.run
+ expect(knife.user.username).to eq('some_user')
+ expect(knife.user.display_name).to eq('some_display_name')
+ expect(knife.user.first_name).to eq('some_first_name')
+ expect(knife.user.last_name).to eq('some_last_name')
+ expect(knife.user.email).to eq('some_email')
+ expect(knife.user.password).to eq('some_password')
+ end
+
+ context "when user_key and no_key are passed" do
+ before do
+ knife.config[:user_key] = "some_key"
+ knife.config[:no_key] = true
+ end
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to match /You cannot pass --user-key and --no-key/
+ end
+ end
+
+ context "when user_key and no_key are passed" do
+ before do
+ knife.config[:user_key] = "some_key"
+ knife.config[:no_key] = true
+ end
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to match /You cannot pass --user-key and --no-key/
+ end
+ end
+
+ context "when --admin is passed" do
+ before do
+ knife.config[:admin] = true
+ end
+
+ it "sets the admin field on the user to true" do
+ knife.run
+ expect(knife.user.admin).to be_truthy
+ end
+ end
+
+ context "when --admin is not passed" do
+ it "does not set the admin field to true" do
+ knife.run
+ expect(knife.user.admin).to be_falsey
+ end
+ end
+
+ context "when --no-key is passed" do
+ before do
+ knife.config[:no_key] = true
+ end
+
+ it "does not set user.create_key" do
+ knife.run
+ expect(knife.user.create_key).to be_falsey
+ end
+ end
+
+ context "when --no-key is not passed" do
+ it "sets user.create_key to true" do
+ knife.run
+ expect(knife.user.create_key).to be_truthy
+ end
+ end
+
+ context "when --user-key is passed" do
+ before do
+ knife.config[:user_key] = 'some_key'
+ allow(File).to receive(:read).and_return('some_key')
+ allow(File).to receive(:expand_path)
+ end
+
+ it "sets user.public_key" do
+ knife.run
+ expect(knife.user.public_key).to eq('some_key')
+ end
+ end
+
+ context "when --user-key is not passed" do
+ it "does not set user.public_key" do
+ knife.run
+ expect(knife.user.public_key).to be_nil
+ end
+ end
+
+ context "when a private_key is returned" do
+ before do
+ allow(knife).to receive(:create_user_from_hash).and_return(Chef::User.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"})))
+ end
+
+ context "when --file is passed" do
+ before do
+ knife.config[:file] = '/some/path'
+ end
+
+ it "creates a new file of the path passed" do
+ filehandle = double('filehandle')
+ expect(filehandle).to receive(:print).with('some_private_key')
+ expect(File).to receive(:open).with('/some/path', 'w').and_yield(filehandle)
+ knife.run
+ end
+ end
+
+ context "when --file is not passed" do
+ it "prints the private key to stdout" do
+ expect(knife.ui).to receive(:msg).with('some_private_key')
+ knife.run
+ end
+ end
+ end
+
+ end # when all mandatory fields are validly specified
end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
index 3e8a43eaf5..022256f370 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -140,7 +140,7 @@ describe Chef::Knife do
'X-Chef-Version' => Chef::VERSION,
"Host"=>"api.opscode.piab",
"X-REMOTE-REQUEST-ID"=>request_id,
- 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}}
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
let(:request_id) {"1234"}
@@ -399,11 +399,17 @@ describe Chef::Knife do
it "formats 406s (non-supported API version error) nicely" do
response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable")
response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone", :min_version => "0", :max_version => "1"))
+
+ # set the header
+ response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(:min_version => "0", :max_version => "1", :request_version => "10000000")
+
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone"))
allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("406 Not Acceptable", response))
+
knife.run_with_pretty_exceptions
- expect(stderr.string).to include('The version of Chef that Knife is using is not supported by the Chef server you sent this request to')
- expect(stderr.string).to include("This version of Chef requires a server API version of #{Chef::HTTP::Authenticator::SERVER_API_VERSION}")
+ expect(stderr.string).to include('The request that Knife sent was using API version 10000000')
+ expect(stderr.string).to include('The Chef server you sent the request to supports a min API verson of 0 and a max API version of 1')
+ expect(stderr.string).to include('Please either update your Chef client or server to be a compatible set')
end
it "formats 500s nicely" do
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
index b4f8f336a9..3b04981610 100644
--- a/spec/unit/rest_spec.rb
+++ b/spec/unit/rest_spec.rb
@@ -69,8 +69,8 @@ describe Chef::REST do
rest
end
- let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}}
- let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION}}
+ let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
+ let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
before(:each) do
Chef::Log.init(log_stringio)
@@ -292,7 +292,7 @@ describe Chef::REST do
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
'X-REMOTE-REQUEST-ID' => request_id,
- 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
}
end
@@ -575,7 +575,7 @@ describe Chef::REST do
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
'X-REMOTE-REQUEST-ID'=> request_id,
- 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
}
expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
rest.streaming_request(url, {})
@@ -587,7 +587,7 @@ describe Chef::REST do
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
'X-REMOTE-REQUEST-ID'=> request_id,
- 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::SERVER_API_VERSION
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
}
expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
rest.streaming_request(url, {})
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index d451531b16..7380484409 100644
--- a/spec/unit/user_spec.rb
+++ b/spec/unit/user_spec.rb
@@ -26,98 +26,146 @@ describe Chef::User do
@user = Chef::User.new
end
+ shared_examples_for "string fields with no contraints" do
+ it "should let you set the public key" do
+ expect(@user.send(method, "some_string")).to eq("some_string")
+ end
+
+ it "should return the current public key" do
+ @user.send(method, "some_string")
+ expect(@user.send(method)).to eq("some_string")
+ end
+
+ it "should throw an ArgumentError if you feed it something lame" do
+ expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
+ end
+ end
+
+ shared_examples_for "boolean fields with no constraints" do
+ it "should let you set the admin bit" do
+ expect(@user.send(method, true)).to eq(true)
+ end
+
+ it "should return the current admin value" do
+ @user.send(method, true)
+ expect(@user.send(method)).to eq(true)
+ end
+
+ it "should throw an ArgumentError if you feed it anything but true or false" do
+ expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
+ end
+ end
+
describe "initialize" do
it "should be a Chef::User" do
expect(@user).to be_a_kind_of(Chef::User)
end
end
- describe "name" do
- it "should let you set the name to a string" do
- expect(@user.name("ops_master")).to eq("ops_master")
+ describe "username" do
+ it "should let you set the username to a string" do
+ expect(@user.username("ops_master")).to eq("ops_master")
end
- it "should return the current name" do
- @user.name "ops_master"
- expect(@user.name).to eq("ops_master")
+ it "should return the current username" do
+ @user.username "ops_master"
+ expect(@user.username).to eq("ops_master")
end
# It is not feasible to check all invalid characters. Here are a few
# that we probably care about.
it "should not accept invalid characters" do
# capital letters
- expect { @user.name "Bar" }.to raise_error(ArgumentError)
+ expect { @user.username "Bar" }.to raise_error(ArgumentError)
# slashes
- expect { @user.name "foo/bar" }.to raise_error(ArgumentError)
+ expect { @user.username "foo/bar" }.to raise_error(ArgumentError)
# ?
- expect { @user.name "foo?" }.to raise_error(ArgumentError)
+ expect { @user.username "foo?" }.to raise_error(ArgumentError)
# &
- expect { @user.name "foo&" }.to raise_error(ArgumentError)
+ expect { @user.username "foo&" }.to raise_error(ArgumentError)
end
it "should not accept spaces" do
- expect { @user.name "ops master" }.to raise_error(ArgumentError)
+ expect { @user.username "ops master" }.to raise_error(ArgumentError)
end
it "should throw an ArgumentError if you feed it anything but a string" do
- expect { @user.name Hash.new }.to raise_error(ArgumentError)
+ expect { @user.username Hash.new }.to raise_error(ArgumentError)
end
end
- describe "admin" do
- it "should let you set the admin bit" do
- expect(@user.admin(true)).to eq(true)
+ describe "boolean fields" do
+ describe "admin" do
+ it_should_behave_like "boolean fields with no constraints" do
+ let(:method) { :admin }
+ end
+
+ it "should default to false" do
+ expect(@user.admin).to eq(false)
+ end
end
- it "should return the current admin value" do
- @user.admin true
- expect(@user.admin).to eq(true)
+ describe "create_key" do
+ it_should_behave_like "boolean fields with no constraints" do
+ let(:method) { :create_key }
+ end
end
+ end
- it "should default to false" do
- expect(@user.admin).to eq(false)
+ describe "string fields" do
+ describe "public_key" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :public_key }
+ end
end
- it "should throw an ArgumentError if you feed it anything but true or false" do
- expect { @user.name Hash.new }.to raise_error(ArgumentError)
+ describe "private_key" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :private_key }
+ end
end
- end
- describe "public_key" do
- it "should let you set the public key" do
- expect(@user.public_key("super public")).to eq("super public")
+ describe "display_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :display_name }
+ end
end
- it "should return the current public key" do
- @user.public_key("super public")
- expect(@user.public_key).to eq("super public")
+ describe "first_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :first_name }
+ end
end
- it "should throw an ArgumentError if you feed it something lame" do
- expect { @user.public_key Hash.new }.to raise_error(ArgumentError)
+ describe "middle_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :middle_name }
+ end
end
- end
- describe "private_key" do
- it "should let you set the private key" do
- expect(@user.private_key("super private")).to eq("super private")
+ describe "last_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :last_name }
+ end
end
- it "should return the private key" do
- @user.private_key("super private")
- expect(@user.private_key).to eq("super private")
+ describe "email" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :email }
+ end
end
- it "should throw an ArgumentError if you feed it something lame" do
- expect { @user.private_key Hash.new }.to raise_error(ArgumentError)
+ describe "password" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :password }
+ end
end
end
describe "when serializing to JSON" do
before(:each) do
- @user.name("black")
- @user.public_key("crowes")
+ @user.username("black")
@json = @user.to_json
end
@@ -125,18 +173,68 @@ describe Chef::User do
expect(@json).to match(/^\{.+\}$/)
end
- it "includes the name value" do
- expect(@json).to include(%q{"name":"black"})
- end
-
- it "includes the public key value" do
- expect(@json).to include(%{"public_key":"crowes"})
+ it "includes the username value" do
+ expect(@json).to include(%q{"username":"black"})
end
it "includes the 'admin' flag" do
expect(@json).to include(%q{"admin":false})
end
+ it "includes the display name when present" do
+ @user.display_name("get_displayed")
+ expect(@user.to_json).to include(%{"display_name":"get_displayed"})
+ end
+
+ it "does not include the display name if not present" do
+ expect(@json).not_to include("display_name")
+ end
+
+ it "includes the first name when present" do
+ @user.first_name("char")
+ expect(@user.to_json).to include(%{"first_name":"char"})
+ end
+
+ it "does not include the first name if not present" do
+ expect(@json).not_to include("first_name")
+ end
+
+ it "includes the middle name when present" do
+ @user.middle_name("man")
+ expect(@user.to_json).to include(%{"middle_name":"man"})
+ end
+
+ it "does not include the middle name if not present" do
+ expect(@json).not_to include("middle_name")
+ end
+
+ it "includes the last name when present" do
+ @user.last_name("der")
+ expect(@user.to_json).to include(%{"last_name":"der"})
+ end
+
+ it "does not include the last name if not present" do
+ expect(@json).not_to include("last_name")
+ end
+
+ it "includes the email when present" do
+ @user.email("charmander@pokemon.poke")
+ expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"})
+ end
+
+ it "does not include the email if not present" do
+ expect(@json).not_to include("email")
+ end
+
+ it "includes the public key when present" do
+ @user.public_key("crowes")
+ expect(@user.to_json).to include(%{"public_key":"crowes"})
+ end
+
+ it "does not include the public key if not present" do
+ expect(@json).not_to include("public_key")
+ end
+
it "includes the private key when present" do
@user.private_key("monkeypants")
expect(@user.to_json).to include(%q{"private_key":"monkeypants"})
@@ -162,11 +260,19 @@ describe Chef::User do
describe "when deserializing from JSON" do
before(:each) do
- user = { "name" => "mr_spinks",
+ user = {
+ "username" => "mr_spinks",
+ "admin" => true,
+ "display_name" => "displayed",
+ "first_name" => "char",
+ "middle_name" => "man",
+ "last_name" => "der",
+ "email" => "charmander@pokemon.poke",
+ "password" => "password",
"public_key" => "turtles",
"private_key" => "pandas",
- "password" => "password",
- "admin" => true }
+ "create_key" => true
+ }
@user = Chef::User.from_json(Chef::JSONCompat.to_json(user))
end
@@ -174,32 +280,192 @@ describe Chef::User do
expect(@user).to be_a_kind_of(Chef::User)
end
- it "preserves the name" do
- expect(@user.name).to eq("mr_spinks")
- end
-
- it "preserves the public key" do
- expect(@user.public_key).to eq("turtles")
+ it "preserves the username" do
+ expect(@user.username).to eq("mr_spinks")
end
it "preserves the admin status" do
expect(@user.admin).to be_truthy
end
- it "includes the private key if present" do
- expect(@user.private_key).to eq("pandas")
+ it "preserves the display name if present" do
+ expect(@user.display_name).to eq("displayed")
+ end
+
+ it "preserves the first name if present" do
+ expect(@user.first_name).to eq("char")
+ end
+
+ it "preserves the middle name if present" do
+ expect(@user.middle_name).to eq("man")
+ end
+
+ it "preserves the last name if present" do
+ expect(@user.last_name).to eq("der")
+ end
+
+ it "preserves the email if present" do
+ expect(@user.email).to eq("charmander@pokemon.poke")
end
it "includes the password if present" do
expect(@user.password).to eq("password")
end
+ it "preserves the public key if present" do
+ expect(@user.public_key).to eq("turtles")
+ end
+
+ it "includes the private key if present" do
+ expect(@user.private_key).to eq("pandas")
+ end
+
+ it "includes the create key status if present" do
+ expect(@user.create_key).to be_truthy
+ end
end
+ describe "Versioned API Interactions" do
+ before (:each) do
+ @user = Chef::User.new
+ allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object'))
+ allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object'))
+ end
+
+ describe "create" do
+ let(:payload) {
+ {
+ :username => "some_username",
+ :display_name => "some_display_name",
+ :first_name => "some_first_name",
+ :last_name => "some_last_name",
+ :email => "some_email",
+ :password => "some_password",
+ :admin => true
+ }
+ }
+ before do
+ @user.username "some_username"
+ @user.display_name "some_display_name"
+ @user.first_name "some_first_name"
+ @user.last_name "some_last_name"
+ @user.email "some_email"
+ @user.password "some_password"
+ @user.admin true
+ end
+
+ shared_examples_for "create valid user" do
+ it "creates a new user via the API" do
+ expect(chef_rest_object).to receive(:post).with("users", payload).and_return({})
+ @user.create
+ end
+
+ it "creates a new user via the API with a public_key when it exists" do
+ @user.public_key "some_public_key"
+ expect(chef_rest_object).to receive(:post).with("users", payload.merge({:public_key => "some_public_key"})).and_return({})
+ @user.create
+ end
+
+ it "creates a new user via the API with a middle_name when it exists" do
+ @user.middle_name "some_middle_name"
+ expect(chef_rest_object).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
+ @user.create
+ end
+ end
+
+ context "when server API V1 is valid on the Chef Server receiving the request" do
+ context "when create_key and public_key are both set" do
+ before do
+ @user.public_key "key"
+ @user.create_key true
+ end
+ it "rasies a Chef::Exceptions::InvalidUserAttribute" do
+ expect { @user.create }.to raise_error(Chef::Exceptions::InvalidUserAttribute)
+ end
+ end
+
+ it_should_behave_like "create valid user" do
+ let(:chef_rest_object) { @user.chef_root_rest_v1 }
+ end
+
+ it "creates a new user via the API with create_key == true when it exists" do
+ @user.create_key true
+ expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:create_key => true})).and_return({})
+ @user.create
+ end
+
+ context "when chef_key is returned by the server" do
+ let(:chef_key) {
+ {
+ "chef_key" => {
+ "public_key" => "some_public_key"
+ }
+ }
+ }
+
+ it "puts the public key into the user returned by create" do
+ expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload).and_return(payload.merge(chef_key))
+ new_user = @user.create
+ expect(new_user.public_key).to eq("some_public_key")
+ end
+
+ context "when private_key is returned in chef_key" do
+ let(:chef_key) {
+ {
+ "chef_key" => {
+ "public_key" => "some_public_key",
+ "private_key" => "some_private_key"
+ }
+ }
+ }
+
+ it "puts the private key into the user returned by create" do
+ expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload).and_return(payload.merge(chef_key))
+ new_user = @user.create
+ expect(new_user.private_key).to eq("some_private_key")
+ end
+ end
+
+ end # when chef_key is returned by the server
+ end # when server API V1 is valid on the Chef Server receiving the request
+
+ context "when server API V1 is not valid on the Chef Server receiving the request" do
+ let(:response_406) { OpenStruct.new(:code => '406') }
+ let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+
+ before do
+ allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406)
+ end
+
+ context "when the server does not support the min or max server API version that Chef::User supports" do
+ before do
+ allow(@user).to receive(:handle_version_http_exception).and_return(false)
+ end
+
+ it "raises the original exception" do
+ expect{ @user.create }.to raise_error(exception_406)
+ end
+ end # when the server does not support the min or max server API version that Chef::User supports
+
+ context "when the server supports API V0" do
+ before do
+ allow(@user).to receive(:handle_version_http_exception).and_return(true)
+ allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406)
+ end
+
+ it_should_behave_like "create valid user" do
+ let(:chef_rest_object) { @user.chef_root_rest_v0 }
+ end
+
+ end # when the server supports API V0
+ end # when server API V1 is not valid on the Chef Server receiving the request
+ end # create
+ end # Versioned API Interactions
+
describe "API Interactions" do
before (:each) do
@user = Chef::User.new
- @user.name "foobar"
+ @user.username "foobar"
@http_client = double("Chef::REST mock")
allow(Chef::REST).to receive(:new).and_return(@http_client)
end
@@ -214,41 +480,32 @@ describe Chef::User do
end
it "lists all clients on an OSC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
expect(Chef::User.list).to eq(@osc_response)
end
it "inflate all clients on an OSC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
expect(Chef::User.list(true)).to eq(@osc_inflated_response)
end
it "lists all clients on an OHC/OPC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
# We expect that Chef::User.list will give a consistent response
# so OHC API responses should be transformed to OSC-style output.
expect(Chef::User.list).to eq(@osc_response)
end
it "inflate all clients on an OHC/OPC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
expect(Chef::User.list(true)).to eq(@osc_inflated_response)
end
end
-
- describe "create" do
- it "creates a new user via the API" do
- @user.password "password"
- expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
- @user.create
- end
- end
-
describe "read" do
it "loads a named user from the API" do
- expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
+ expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"})
user = Chef::User.load("foobar")
- expect(user.name).to eq("foobar")
+ expect(user.username).to eq("foobar")
expect(user.admin).to eq(true)
expect(user.public_key).to eq("pubkey")
end
@@ -256,14 +513,14 @@ describe Chef::User do
describe "update" do
it "updates an existing user on via the API" do
- expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
+ expect(@http_client).to receive(:put).with("users/foobar", {:username => "foobar", :admin => false}).and_return({})
@user.update
end
end
describe "destroy" do
it "deletes the specified user via the API" do
- expect(@http_client).to receive(:delete_rest).with("users/foobar")
+ expect(@http_client).to receive(:delete).with("users/foobar")
@user.destroy
end
end