diff options
author | tylercloke <tylercloke@gmail.com> | 2015-06-01 12:18:36 -0700 |
---|---|---|
committer | tylercloke <tylercloke@gmail.com> | 2015-06-05 10:38:48 -0700 |
commit | b217055bdfdf80ff0cfd2c00abcd339f81b74d8d (patch) | |
tree | f1a2aaff4ab7af513ce3efdbfcdb6bdcbd29c4d4 | |
parent | 1120157edceb842587727f06f5ac681a87d2fea1 (diff) | |
download | chef-b217055bdfdf80ff0cfd2c00abcd339f81b74d8d.tar.gz |
API V1 support for client creation.
-rw-r--r-- | lib/chef/api_client.rb | 87 | ||||
-rw-r--r-- | lib/chef/exceptions.rb | 1 | ||||
-rw-r--r-- | lib/chef/knife/client_create.rb | 85 | ||||
-rw-r--r-- | lib/chef/knife/user_create.rb | 18 | ||||
-rw-r--r-- | lib/chef/user.rb | 5 | ||||
-rw-r--r-- | spec/unit/api_client_spec.rb | 99 | ||||
-rw-r--r-- | spec/unit/knife/client_create_spec.rb | 173 | ||||
-rw-r--r-- | spec/unit/knife/user_create_spec.rb | 68 | ||||
-rw-r--r-- | spec/unit/user_spec.rb | 130 |
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 |