summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortylercloke <tylercloke@gmail.com>2015-06-01 12:18:36 -0700
committertylercloke <tylercloke@gmail.com>2015-06-05 10:38:48 -0700
commitb217055bdfdf80ff0cfd2c00abcd339f81b74d8d (patch)
treef1a2aaff4ab7af513ce3efdbfcdb6bdcbd29c4d4
parent1120157edceb842587727f06f5ac681a87d2fea1 (diff)
downloadchef-b217055bdfdf80ff0cfd2c00abcd339f81b74d8d.tar.gz
API V1 support for client creation.
-rw-r--r--lib/chef/api_client.rb87
-rw-r--r--lib/chef/exceptions.rb1
-rw-r--r--lib/chef/knife/client_create.rb85
-rw-r--r--lib/chef/knife/user_create.rb18
-rw-r--r--lib/chef/user.rb5
-rw-r--r--spec/unit/api_client_spec.rb99
-rw-r--r--spec/unit/knife/client_create_spec.rb173
-rw-r--r--spec/unit/knife/user_create_spec.rb68
-rw-r--r--spec/unit/user_spec.rb130
9 files changed, 410 insertions, 256 deletions
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index ce9ceb312c..5ad7a581a2 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -23,12 +23,19 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
+require 'chef/exceptions'
+require 'chef/versioned_rest'
+require 'chef/mixin/api_version_request_handling'
class Chef
class ApiClient
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
+ include Chef::VersionedRest
+ include Chef::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
# Create a new Chef::ApiClient object.
def initialize
@@ -37,6 +44,15 @@ class Chef
@private_key = nil
@admin = false
@validator = false
+ @create_key = nil
+ end
+
+ 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
# Gets or sets the client name.
@@ -88,7 +104,8 @@ class Chef
)
end
- # Gets or sets the private key.
+ # Private key. The server will return it as a string.
+ # Set to true under API V0 to have the server regenerate the default key.
#
# @params [Optional String] The string representation of the private key.
# @return [String] The current value.
@@ -96,7 +113,19 @@ class Chef
set_or_return(
:private_key,
arg,
- :kind_of => [String, FalseClass]
+ :kind_of => [String, TrueClass, FalseClass]
+ )
+ end
+
+ # Used to ask server to generate key pair under api V1
+ #
+ # @params [Optional True/False] Should be true or false - default is false.
+ # @return [True/False] The current value
+ def create_key(arg=nil)
+ set_or_return(
+ :create_key,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
)
end
@@ -107,13 +136,14 @@ class Chef
def to_hash
result = {
"name" => @name,
- "public_key" => @public_key,
"validator" => @validator,
"admin" => @admin,
'json_class' => self.class.name,
"chef_type" => "client"
}
result["private_key"] = @private_key if @private_key
+ result["public_key"] = @public_key if @public_key
+ result["create_key"] = @create_key if @create_key
result
end
@@ -127,10 +157,11 @@ class Chef
def self.from_hash(o)
client = Chef::ApiClient.new
client.name(o["name"] || o["clientname"])
- client.private_key(o["private_key"]) if o.key?("private_key")
- client.public_key(o["public_key"])
client.admin(o["admin"])
client.validator(o["validator"])
+ client.private_key(o["private_key"]) if o.key?("private_key")
+ client.public_key(o["public_key"]) if o.key?("public_key")
+ client.create_key(o["create_key"]) if o.key?("create_key")
client
end
@@ -205,7 +236,42 @@ class Chef
# Create the client via the REST API
def create
- http_api.post("clients", self)
+ payload = {
+ :name => name,
+ :validator => validator,
+ # this field is ignored in API V1, but left for backwards-compat,
+ # can remove after OSC 11 support is finished?
+ :admin => admin
+ }
+ begin
+ # try API V1
+ raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if create_key && public_key
+
+ payload[:public_key] = public_key if public_key
+ payload[:create_key] = create_key if create_key
+
+ new_client = chef_rest_v1.post("clients", payload)
+
+ # get the private_key out of the chef_key hash if it exists
+ if new_client['chef_key']
+ if new_client['chef_key']['private_key']
+ new_client['private_key'] = new_client['chef_key']['private_key']
+ end
+ new_client['public_key'] = new_client['chef_key']['public_key']
+ new_client.delete('chef_key')
+ end
+
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ raise e unless handle_version_http_exception(e, SUPPORTED_API_VERSIONS[0], SUPPORTED_API_VERSIONS[-1])
+
+ # under API V0, a key pair will always be created unless public_key is
+ # passed on initial POST
+ payload[:public_key] = public_key if public_key
+
+ new_client = chef_rest_v0.post("clients", payload)
+ end
+ Chef::ApiClient.from_hash(self.to_hash.merge(new_client))
end
# As a string
@@ -213,11 +279,12 @@ class Chef
"client[#{@name}]"
end
- def inspect
- "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " +
- "public_key:'#{public_key}' private_key:'#{private_key}'"
- end
+ # def inspect
+ # "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " +
+ # "public_key:'#{public_key}' private_key:'#{private_key}'"
+ # end
+ # TODO delete?
def http_api
@http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index ef5314423a..dd0bac3cf9 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -74,6 +74,7 @@ class Chef
class InvalidKeyArgument < ArgumentError; end
class InvalidKeyAttribute < ArgumentError; end
class InvalidUserAttribute < ArgumentError; end
+ class InvalidClientAttribute < ArgumentError; end
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookFrozen < ArgumentError; end
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 477a400e8a..17c1204c2d 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -28,58 +28,81 @@ class Chef
end
option :file,
- :short => "-f FILE",
- :long => "--file FILE",
- :description => "Write the key to a file"
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file if the server generated one."
option :admin,
- :short => "-a",
- :long => "--admin",
- :description => "Create the client as an admin",
- :boolean => true
+ :short => "-a",
+ :long => "--admin",
+ :description => "Open Source Chef 11 only. Create the client as an admin.",
+ :boolean => true
option :validator,
- :long => "--validator",
- :description => "Create the client as a validator",
- :boolean => true
+ :long => "--validator",
+ :description => "Create the client as a validator.",
+ :boolean => true
- banner "knife client create CLIENT (options)"
+ option :public_key,
+ :short => "-p FILE",
+ :long => "--public-key",
+ :description => "Set the initial default key for the client from a file on disk (cannot pass with --create-key)."
+
+ option :prevent_keygen,
+ :short => "-k",
+ :long => "--prevent-keygen",
+ :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --public-key.",
+ :boolean => true
+
+ banner "knife client create CLIENTNAME (options)"
+
+ def client
+ @client_field ||= Chef::ApiClient.new
+ end
+
+ def create_client(client)
+ client.create
+ end
def run
- @client_name = @name_args[0]
+ test_mandatory_field(@name_args[0], "client name")
+ client.name @name_args[0]
- if @client_name.nil?
+ if config[:public_key] && config[:prevent_keygen]
show_usage
- ui.fatal("You must specify a client name")
+ ui.fatal("You cannot pass --public-key and --prevent-keygen")
exit 1
end
- client_hash = {
- "name" => @client_name,
- "admin" => !!config[:admin],
- "validator" => !!config[:validator]
- }
+ unless config[:prevent_keygen]
+ client.create_key(true)
+ end
+
+ if config[:admin]
+ client.admin(true)
+ end
- output = Chef::ApiClient.from_hash(edit_hash(client_hash))
+ if config[:validator]
+ client.validator(true)
+ end
- # Chef::ApiClient.save will try to create a client and if it
- # exists will update it instead silently.
- client = output.save
+ if config[:public_key]
+ client.public_key File.read(File.expand_path(config[:public_key]))
+ end
- # We only get a private_key on client creation, not on client update.
- if client['private_key']
- ui.info("Created #{output}")
+ output = edit_data(client)
+ final_client = create_client(output)
+ ui.info("Created #{output}")
+ # output private_key if one
+ if final_client.private_key
if config[:file]
File.open(config[:file], "w") do |f|
- f.print(client['private_key'])
+ f.print(final_client.private_key)
end
else
- puts client['private_key']
+ puts final_client.private_key
end
- else
- ui.error "Client '#{client['name']}' already exists"
- exit 1
end
end
end
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 6b06153bf8..18addda741 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -33,15 +33,17 @@ class Chef
option :file,
:short => "-f FILE",
:long => "--file 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."
+ :description => "Write the private key to a file if the server generated one."
option :user_key,
:long => "--user-key FILENAME",
- :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."
+ :description => "Set the initial default key for the user from a file on disk (cannot pass with --create-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)."
+ option :prevent_keygen,
+ :short => "-k",
+ :long => "--prevent-keygen",
+ :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
+ :boolean => true
banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
@@ -72,13 +74,13 @@ class Chef
test_mandatory_field(@name_args[5], "password")
user.password @name_args[5]
- if config[:user_key] && config[:no_key]
+ if config[:user_key] && config[:prevent_keygen]
show_usage
- ui.fatal("You cannot pass --user-key and --no-key")
+ ui.fatal("You cannot pass --user-key and --prevent-keygen")
exit 1
end
- unless config[:no_key]
+ unless config[:prevent_keygen]
user.create_key(true)
end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 3620266ac2..9159d162f4 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -161,6 +161,7 @@ class Chef
new_user.delete('chef_key')
end
rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
raise e unless handle_version_http_exception(e, SUPPORTED_API_VERSIONS[0], SUPPORTED_API_VERSIONS[-1])
payload = {
:username => @username,
@@ -172,6 +173,7 @@ class Chef
}
payload[:middle_name] = @middle_name if @middle_name
payload[:public_key] = @public_key if @public_key
+ # under API V0, the server will create a key pair if public_key isn't passed
new_user = chef_root_rest_v0.post("users", payload)
end
@@ -197,9 +199,6 @@ class Chef
if e.response.code == "400"
# if a 400 is returned but the error message matches the error related to private / public key fields, try V0
# else, raise the 400
- puts "halp"*100
- puts e.response.body
- puts e.response.body.class
error = Chef::JSONCompat.from_json(e.response.body)["error"].first
error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error)
if error_match.nil?
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index 7668e31f5a..d39a90946b 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_spec.rb
@@ -53,6 +53,20 @@ describe Chef::ApiClient do
expect { @client.admin(Hash.new) }.to raise_error(ArgumentError)
end
+ it "has an create_key flag attribute" do
+ @client.create_key(true)
+ expect(@client.create_key).to be_truthy
+ end
+
+ it "create_key defaults to false" do
+ expect(@client.create_key).to be_falsey
+ end
+
+ it "allows only boolean values for the create_key flag" do
+ expect { @client.create_key(false) }.not_to raise_error
+ expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError)
+ end
+
it "has a 'validator' flag attribute" do
@client.validator(true)
expect(@client.validator).to be_truthy
@@ -115,6 +129,12 @@ describe Chef::ApiClient do
expect(@json).to include(%q{"validator":false})
end
+ it "includes the 'create_key' flag when present" do
+ @client.create_key(true)
+ @json = @client.to_json
+ expect(@json).to include(%q{"create_key":true})
+ end
+
it "includes the private key when present" do
@client.private_key("monkeypants")
expect(@client.to_json).to include(%q{"private_key":"monkeypants"})
@@ -131,7 +151,7 @@ describe Chef::ApiClient do
describe "when deserializing from JSON (string) using ApiClient#from_json" do
let(:client_string) do
- "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true}"
+ "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}"
end
let(:client) do
@@ -158,6 +178,10 @@ describe Chef::ApiClient do
expect(client.admin).to be_truthy
end
+ it "preserves the create_key status" do
+ expect(client.create_key).to be_truthy
+ end
+
it "preserves the 'validator' status" do
expect(client.validator).to be_truthy
end
@@ -175,6 +199,7 @@ describe Chef::ApiClient do
"private_key" => "monkeypants",
"admin" => true,
"validator" => true,
+ "create_key" => true,
"json_class" => "Chef::ApiClient"
}
end
@@ -199,6 +224,10 @@ describe Chef::ApiClient do
expect(client.admin).to be_truthy
end
+ it "preserves the create_key status" do
+ expect(client.create_key).to be_truthy
+ end
+
it "preserves the 'validator' status" do
expect(client.validator).to be_truthy
end
@@ -214,14 +243,16 @@ describe Chef::ApiClient do
before(:each) do
client = {
- "name" => "black",
- "clientname" => "black",
- "public_key" => "crowes",
- "private_key" => "monkeypants",
- "admin" => true,
- "validator" => true,
- "json_class" => "Chef::ApiClient"
+ "name" => "black",
+ "clientname" => "black",
+ "public_key" => "crowes",
+ "private_key" => "monkeypants",
+ "admin" => true,
+ "create_key" => true,
+ "validator" => true,
+ "json_class" => "Chef::ApiClient"
}
+
@http_client = double("Chef::REST mock")
allow(Chef::REST).to receive(:new).and_return(@http_client)
expect(@http_client).to receive(:get).with("clients/black").and_return(client)
@@ -244,6 +275,10 @@ describe Chef::ApiClient do
expect(@client.admin).to be_a_kind_of(TrueClass)
end
+ it "preserves the create_key status" do
+ expect(@client.create_key).to be_a_kind_of(TrueClass)
+ end
+
it "preserves the 'validator' status" do
expect(@client.validator).to be_a_kind_of(TrueClass)
end
@@ -345,4 +380,52 @@ describe Chef::ApiClient do
end
end
+
+ describe "Versioned API Interactions" do
+ let(:response_406) { OpenStruct.new(:code => '406') }
+ let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+
+ before (:each) do
+ @client = Chef::ApiClient.new
+ allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object'))
+ allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object'))
+ end
+
+ describe "create" do
+ let(:payload) {
+ {
+ :name => "some_name",
+ :validator => true,
+ :admin => true
+ }
+ }
+
+ before do
+ @client.name "some_name"
+ @client.validator true
+ @client.admin true
+ end
+
+ # from spec/support/shared/unit/user_and_client_shared.rb
+ it_should_behave_like "user or client create" do
+ let(:object) { @client }
+ let(:error) { Chef::Exceptions::InvalidClientAttribute }
+ let(:rest_v0) { @client.chef_rest_v0 }
+ let(:rest_v1) { @client.chef_rest_v1 }
+ let(:url) { "clients" }
+ end
+
+ context "when API V1 is not supported by the server" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "version handling" do
+ let(:object) { @client }
+ let(:method) { :create }
+ let(:http_verb) { :post }
+ let(:rest_v1) { @client.chef_rest_v1 }
+ end
+ end
+
+ end # create
+
+ end
end
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
index 10d386b5ff..8fecfc885f 100644
--- a/spec/unit/knife/client_create_spec.rb
+++ b/spec/unit/knife/client_create_spec.rb
@@ -22,6 +22,8 @@ Chef::Knife::ClientCreate.load_deps
describe Chef::Knife::ClientCreate do
let(:stderr) { StringIO.new }
+ let(:stdout) { StringIO.new }
+
let(:default_client_hash) do
{
@@ -32,84 +34,153 @@ describe Chef::Knife::ClientCreate do
end
let(:client) do
- c = double("Chef::ApiClient")
- allow(c).to receive(:save).and_return({"private_key" => ""})
- allow(c).to receive(:to_s).and_return("client[adam]")
- c
+ Chef::ApiClient.new
end
let(:knife) do
k = Chef::Knife::ClientCreate.new
- k.name_args = [ "adam" ]
- k.ui.config[:disable_editing] = true
+ k.name_args = []
+ allow(k).to receive(:client).and_return(client)
+ allow(k).to receive(:edit_data).with(client).and_return(client)
allow(k.ui).to receive(:stderr).and_return(stderr)
- allow(k.ui).to receive(:stdout).and_return(stderr)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
k
end
+ before do
+ allow(client).to receive(:to_s).and_return("client[adam]")
+ allow(knife).to receive(:create_client).and_return(client)
+ end
+
before(:each) do
Chef::Config[:node_name] = "webmonkey.example.com"
end
describe "run" do
- it "should create and save the ApiClient" do
- expect(Chef::ApiClient).to receive(:from_hash).and_return(client)
- expect(client).to receive(:save)
- knife.run
+ context "when nothing is passed" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { [] }
+ let(:fieldname) { 'client name' }
+ end
end
- it "should print a message upon creation" do
- expect(Chef::ApiClient).to receive(:from_hash).and_return(client)
- expect(client).to receive(:save)
- knife.run
- expect(stderr.string).to match /Created client.*adam/i
- end
+ context "when clientname is passed" do
+ before do
+ knife.name_args = ['adam']
+ end
- it "should set the Client name" do
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("name" => "adam")).and_return(client)
- knife.run
- end
+ context "when public_key and prevent_keygen are passed" do
+ before do
+ knife.config[:public_key] = "some_key"
+ knife.config[:prevent_keygen] = 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 --public-key and --prevent-keygen/
+ end
+ end
- it "by default it is not an admin" do
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => false)).and_return(client)
- knife.run
- end
+ it "should create the ApiClient" do
+ expect(knife).to receive(:create_client)
+ knife.run
+ end
- it "by default it is not a validator" do
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => false)).and_return(client)
- knife.run
- end
+ it "should print a message upon creation" do
+ expect(knife).to receive(:create_client)
+ knife.run
+ expect(stderr.string).to match /Created client.*adam/i
+ end
- it "should allow you to edit the data" do
- expect(knife).to receive(:edit_hash).with(default_client_hash).and_return(default_client_hash)
- allow(Chef::ApiClient).to receive(:from_hash).and_return(client)
- knife.run
- end
+ it "should set the Client name" do
+ knife.run
+ expect(client.name).to eq("adam")
+ end
- describe "with -f or --file" do
- it "should write the private key to a file" do
- knife.config[:file] = "/tmp/monkeypants"
- allow_any_instance_of(Chef::ApiClient).to receive(:save).and_return({ 'private_key' => "woot" })
- filehandle = double("Filehandle")
- expect(filehandle).to receive(:print).with('woot')
- expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+ it "by default it is not an admin" do
knife.run
+ expect(client.admin).to be_falsey
end
- end
- describe "with -a or --admin" do
- it "should create an admin client" do
- knife.config[:admin] = true
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => true)).and_return(client)
+ it "by default it is not a validator" do
knife.run
+ expect(client.admin).to be_falsey
end
- end
- describe "with --validator" do
- it "should create an validator client" do
- knife.config[:validator] = true
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => true)).and_return(client)
+ it "by default it should set create_key to true" do
knife.run
+ expect(client.create_key).to be_truthy
+ end
+
+ it "should allow you to edit the data" do
+ expect(knife).to receive(:edit_data).with(client).and_return(client)
+ knife.run
+ end
+
+ describe "with -f or --file" do
+ before do
+ client.private_key "woot"
+ end
+
+ it "should write the private key to a file" do
+ knife.config[:file] = "/tmp/monkeypants"
+ filehandle = double("Filehandle")
+ expect(filehandle).to receive(:print).with('woot')
+ expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+ knife.run
+ end
+ end
+
+ describe "with -a or --admin" do
+ before do
+ knife.config[:admin] = true
+ end
+
+ it "should create an admin client" do
+ knife.run
+ expect(client.admin).to be_truthy
+ end
+ end
+
+ describe "with -p or --public-key" do
+ before do
+ knife.config[:public_key] = 'some_key'
+ allow(File).to receive(:read).and_return('some_key')
+ allow(File).to receive(:expand_path)
+ end
+
+ it "sets the public key" do
+ knife.run
+ expect(client.public_key).to eq('some_key')
+ end
+ end
+
+ describe "with -k or --prevent-keygen" do
+ before do
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "does not set create_key" do
+ knife.run
+ expect(client.create_key).to be_falsey
+ end
+ end
+
+ describe "with --validator" do
+ before do
+ knife.config[:validator] = true
+ end
+
+ it "should create an validator client" do
+ knife.run
+ expect(client.validator).to be_truthy
+ end
end
end
end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
index 629a082d00..879d4d4889 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/user_create_spec.rb
@@ -24,36 +24,21 @@ Chef::Knife::UserCreate.load_deps
describe Chef::Knife::UserCreate do
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)
- end
-
- shared_examples_for "mandatory field missing" do
- context "when field is nil" do
- before do
- knife.name_args = name_args
- end
+ let(:stderr) {
+ StringIO.new
+ }
- it "exits 1" do
- expect { knife.run }.to raise_error(SystemExit)
- end
+ let(:stdout) {
+ StringIO.new
+ }
- 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
+ before(:each) do
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife.ui).to receive(:stderr).and_return(stderr)
end
context "when USERNAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
it_should_behave_like "mandatory field missing" do
let(:name_args) { [] }
let(:fieldname) { 'username' }
@@ -61,6 +46,7 @@ describe Chef::Knife::UserCreate do
end
context "when DISPLAY_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
it_should_behave_like "mandatory field missing" do
let(:name_args) { ['some_user'] }
let(:fieldname) { 'display name' }
@@ -68,6 +54,7 @@ describe Chef::Knife::UserCreate do
end
context "when FIRST_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
it_should_behave_like "mandatory field missing" do
let(:name_args) { ['some_user', 'some_display_name'] }
let(:fieldname) { 'first name' }
@@ -75,6 +62,7 @@ describe Chef::Knife::UserCreate do
end
context "when LAST_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
it_should_behave_like "mandatory field missing" do
let(:name_args) { ['some_user', 'some_display_name', 'some_first_name'] }
let(:fieldname) { 'last name' }
@@ -82,6 +70,7 @@ describe Chef::Knife::UserCreate do
end
context "when EMAIL isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
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' }
@@ -89,6 +78,7 @@ describe Chef::Knife::UserCreate do
end
context "when PASSWORD isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
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' }
@@ -117,26 +107,10 @@ describe Chef::Knife::UserCreate do
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
+ context "when user_key and prevent_keygen are passed" do
before do
knife.config[:user_key] = "some_key"
- knife.config[:no_key] = true
+ knife.config[:prevent_keygen] = true
end
it "prints the usage" do
expect(knife).to receive(:show_usage)
@@ -145,13 +119,13 @@ describe Chef::Knife::UserCreate do
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/
+ expect(stderr.string).to match /You cannot pass --user-key and --prevent-keygen/
end
end
- context "when --no-key is passed" do
+ context "when --prevent-keygen is passed" do
before do
- knife.config[:no_key] = true
+ knife.config[:prevent_keygen] = true
end
it "does not set user.create_key" do
@@ -160,7 +134,7 @@ describe Chef::Knife::UserCreate do
end
end
- context "when --no-key is not passed" do
+ context "when --prevent-keygen is not passed" do
it "sets user.create_key to true" do
knife.run
expect(knife.user.create_key).to be_truthy
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index 146230fa1c..394378e86c 100644
--- a/spec/unit/user_spec.rb
+++ b/spec/unit/user_spec.rb
@@ -316,22 +316,6 @@ describe Chef::User do
allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object'))
end
- shared_examples_for "version handling" do
- before do
- allow(@user.chef_root_rest_v1).to receive(http_verb).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.send(method) }.to raise_error(exception_406)
- end
- end # when the server does not support the min or max server API version that Chef::User supports
- end # version handling
-
describe "update" do
before do
# populate all fields that are valid between V0 and V1
@@ -429,9 +413,12 @@ describe Chef::User do
end # when the server returns a 400
context "when the server returns a 406" do
+ # from spec/support/shared/unit/api_versioning.rb
it_should_behave_like "version handling" do
- let(:method) { (:update) }
- let(:http_verb) { (:put) }
+ let(:object) { @user }
+ let(:method) { :update }
+ let(:http_verb) { :put }
+ let(:rest_v1) { @user.chef_root_rest_v1 }
end
context "when the server supports API V0" do
@@ -470,99 +457,46 @@ describe Chef::User do
@user.password "some_password"
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
+ # from spec/support/shared/unit/user_and_client_shared.rb
+ it_should_behave_like "user or client create" do
+ let(:object) { @user }
+ let(:error) { Chef::Exceptions::InvalidUserAttribute }
+ let(:rest_v0) { @user.chef_root_rest_v0 }
+ let(:rest_v1) { @user.chef_root_rest_v1 }
+ let(:url) { "users" }
+ end
+ context "when handling API V1" do
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({})
+ expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
@user.create
end
- end
+ end # when server API V1 is valid on the Chef Server receiving the request
- 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
+ context "when API V1 is not supported by the server" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "version handling" do
+ let(:object) { @user }
+ let(:method) { :create }
+ let(:http_verb) { :post }
+ let(:rest_v1) { @user.chef_root_rest_v1 }
end
+ end
- it_should_behave_like "create valid user" do
- let(:chef_rest_object) { @user.chef_root_rest_v1 }
+ context "when handling 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 "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({})
+ it "creates a new user via the API with a middle_name when it exists" do
+ @user.middle_name "some_middle_name"
+ expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).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
- it_should_behave_like "version handling" do
- let(:method) { (:create) }
- let(:http_verb) { (:post) }
- end
-
- 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