diff options
author | Bryan McLellan <btm@opscode.com> | 2013-02-06 09:55:05 -0800 |
---|---|---|
committer | Bryan McLellan <btm@opscode.com> | 2013-02-06 09:55:05 -0800 |
commit | 36a0bcc51360c4e3c603a84344ad945fca1ebbe7 (patch) | |
tree | cb57316129f47031f90bfeb152f40d12312a901c | |
parent | 13774733fba0ac483a7212c6ef55517e0301a4ff (diff) | |
parent | 85fc6fe19e351396ab19ccafa08d2767ad170ffb (diff) | |
download | chef-36a0bcc51360c4e3c603a84344ad945fca1ebbe7.tar.gz |
Merge branch 'CHEF-3707'
-rw-r--r-- | lib/chef/knife/configure.rb | 29 | ||||
-rw-r--r-- | lib/chef/knife/user_create.rb | 93 | ||||
-rw-r--r-- | lib/chef/knife/user_delete.rb | 46 | ||||
-rw-r--r-- | lib/chef/knife/user_edit.rb | 53 | ||||
-rw-r--r-- | lib/chef/knife/user_list.rb | 42 | ||||
-rw-r--r-- | lib/chef/knife/user_reregister.rb | 59 | ||||
-rw-r--r-- | lib/chef/knife/user_show.rb | 52 | ||||
-rw-r--r-- | lib/chef/user.rb | 182 | ||||
-rw-r--r-- | spec/unit/knife/configure_spec.rb | 49 | ||||
-rw-r--r-- | spec/unit/knife/user_create_spec.rb | 86 | ||||
-rw-r--r-- | spec/unit/knife/user_delete_spec.rb | 39 | ||||
-rw-r--r-- | spec/unit/knife/user_edit_spec.rb | 42 | ||||
-rw-r--r-- | spec/unit/knife/user_list_spec.rb | 32 | ||||
-rw-r--r-- | spec/unit/knife/user_reregister_spec.rb | 53 | ||||
-rw-r--r-- | spec/unit/knife/user_show_spec.rb | 41 | ||||
-rw-r--r-- | spec/unit/user_spec.rb | 255 |
16 files changed, 1114 insertions, 39 deletions
diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb index e818239a89..eaf42544c0 100644 --- a/lib/chef/knife/configure.rb +++ b/lib/chef/knife/configure.rb @@ -27,6 +27,7 @@ class Chef deps do require 'ohai' Chef::Knife::ClientCreate.load_deps + Chef::Knife::UserCreate.load_deps end banner "knife configure (options)" @@ -40,15 +41,15 @@ class Chef :short => "-i", :long => "--initial", :boolean => true, - :description => "Create an initial API Client" + :description => "Create an initial API User" option :admin_client_name, :long => "--admin-client-name NAME", - :description => "The existing admin clientname (usually chef-webui)" + :description => "The existing admin clientname (usually admin)" option :admin_client_key, :long => "--admin-client-key PATH", - :description => "The path to the admin client's private key (usually a file named webui.pem)" + :description => "The path to the admin client's private key (usually a file named admin.pem)" option :validation_client_name, :long => "--validation-client-name NAME", @@ -93,13 +94,15 @@ EOH Chef::Config[:chef_server_url] = chef_server Chef::Config[:node_name] = admin_client_name Chef::Config[:client_key] = admin_client_key - client_create = Chef::Knife::ClientCreate.new - client_create.name_args = [ new_client_name ] - client_create.config[:admin] = true - client_create.config[:file] = new_client_key - client_create.config[:yes] = true - client_create.config[:disable_editing] = true - client_create.run + user_create = Chef::Knife::UserCreate.new + user_create.name_args = [ new_client_name ] + user_create.config[:user_password] = config[:user_password] || + ui.ask("Please enter a password for the new user: ") {|q| q.echo = false} + user_create.config[:admin] = true + user_create.config[:file] = new_client_key + user_create.config[:yes] = true + user_create.config[:disable_editing] = true + user_create.run else ui.msg("*****") ui.msg("") @@ -132,9 +135,9 @@ EOH server_name = guess_servername @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "http://#{server_name}:4000") if config[:initial] - @new_client_name = config[:node_name] || ask_question("Please enter a clientname for the new client: ", :default => Etc.getlogin) - @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin clientname: ", :default => 'chef-webui') - @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin client's private key: ", :default => '/etc/chef/webui.pem') + @new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", :default => Etc.getlogin) + @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => 'admin') + @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => '/etc/chef/admin.pem') @admin_client_key = File.expand_path(@admin_client_key) else @new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", :default => Etc.getlogin) diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb new file mode 100644 index 0000000000..fa889f29ec --- /dev/null +++ b/lib/chef/knife/user_create.rb @@ -0,0 +1,93 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'chef/knife' + +class Chef + class Knife + class UserCreate < Knife + + deps do + require 'chef/user' + require 'chef/json_compat' + end + + option :file, + :short => "-f FILE", + :long => "--file FILE", + :description => "Write the private key to a file" + + option :admin, + :short => "-a", + :long => "--admin", + :description => "Create the user as an admin", + :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." + + banner "knife user create USER (options)" + + def run + @user_name = @name_args[0] + + if @user_name.nil? + show_usage + ui.fatal("You must specify a user name") + exit 1 + end + + if config[:user_password].length == 0 + show_usage + ui.fatal("You must specify a non-blank password") + exit 1 + end + + user = Chef::User.new + user.name(@user_name) + user.admin(config[:admin]) + user.password config[:user_password] + + 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 + + ui.info("Created #{user}") + if user.private_key + if config[:file] + File.open(config[:file], "w") do |f| + f.print(user.private_key) + end + else + puts user.private_key + end + end + end + end + end +end diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb new file mode 100644 index 0000000000..b7af11bec8 --- /dev/null +++ b/lib/chef/knife/user_delete.rb @@ -0,0 +1,46 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'chef/knife' + +class Chef + class Knife + class UserDelete < Knife + + deps do + require 'chef/user' + require 'chef/json_compat' + end + + banner "knife user delete USER (options)" + + def run + @user_name = @name_args[0] + + if @user_name.nil? + show_usage + ui.fatal("You must specify a user name") + exit 1 + end + + delete_object(Chef::User, @user_name) + end + + end + end +end diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb new file mode 100644 index 0000000000..ae319c8872 --- /dev/null +++ b/lib/chef/knife/user_edit.rb @@ -0,0 +1,53 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'chef/knife' + +class Chef + class Knife + class UserEdit < Knife + + deps do + require 'chef/user' + require 'chef/json_compat' + end + + banner "knife user edit USER (options)" + + def run + @user_name = @name_args[0] + + if @user_name.nil? + show_usage + ui.fatal("You must specify a user name") + exit 1 + end + + original_user = Chef::User.load(@user_name).to_hash + edited_user = edit_data(original_user) + if original_user != edited_user + user = Chef::User.from_hash(edited_user) + user.update + ui.msg("Saved #{user}.") + else + ui.msg("User unchaged, not saving.") + end + end + end + end +end diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb new file mode 100644 index 0000000000..5d2e735a73 --- /dev/null +++ b/lib/chef/knife/user_list.rb @@ -0,0 +1,42 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'chef/knife' + +class Chef + class Knife + class UserList < Knife + + deps do + require 'chef/user' + require 'chef/json_compat' + end + + banner "knife user list (options)" + + option :with_uri, + :short => "-w", + :long => "--with-uri", + :description => "Show corresponding URIs" + + def run + output(format_list_for_display(Chef::User.list)) + end + end + end +end diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb new file mode 100644 index 0000000000..946150e6e4 --- /dev/null +++ b/lib/chef/knife/user_reregister.rb @@ -0,0 +1,59 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'chef/knife' + +class Chef + class Knife + class UserReregister < Knife + + deps do + require 'chef/user' + require 'chef/json_compat' + end + + banner "knife user reregister USER (options)" + + option :file, + :short => "-f FILE", + :long => "--file FILE", + :description => "Write the private key to a file" + + def run + @user_name = @name_args[0] + + if @user_name.nil? + show_usage + ui.fatal("You must specify a user name") + exit 1 + end + + user = Chef::User.load(@user_name).reregister + Chef::Log.debug("Updated user data: #{user.inspect}") + key = user.private_key + if config[:file] + File.open(config[:file], "w") do |f| + f.print(key) + end + else + ui.msg key + end + end + end + end +end diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb new file mode 100644 index 0000000000..5088210b4d --- /dev/null +++ b/lib/chef/knife/user_show.rb @@ -0,0 +1,52 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2009 Opscode, 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. +# + +require 'chef/knife' + +class Chef + class Knife + class UserShow < Knife + + deps do + require 'chef/user' + require 'chef/json_compat' + end + + banner "knife user show USER (options)" + + option :attribute, + :short => "-a ATTR", + :long => "--attribute ATTR", + :description => "Show only one attribute" + + def run + @user_name = @name_args[0] + + if @user_name.nil? + show_usage + ui.fatal("You must specify a user name") + exit 1 + end + + user = Chef::User.load(@user_name) + output(format_for_display(user)) + end + + end + end +end diff --git a/lib/chef/user.rb b/lib/chef/user.rb new file mode 100644 index 0000000000..3f592e4b65 --- /dev/null +++ b/lib/chef/user.rb @@ -0,0 +1,182 @@ +# +# Author:: Steven Danna (steve@opscode.com) +# Copyright:: Copyright 2012 Opscode, 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. +# +require 'chef/config' +require 'chef/mixin/params_validate' +require 'chef/mixin/from_file' +require 'chef/mash' +require 'chef/json_compat' +require 'chef/search/query' + +class Chef + class User + + include Chef::Mixin::FromFile + include Chef::Mixin::ParamsValidate + + def initialize + @name = '' + @public_key = nil + @private_key = nil + @password = nil + @admin = false + end + + def name(arg=nil) + set_or_return(:name, arg, + :regex => /^[a-z0-9\-_]+$/) + end + + def admin(arg=nil) + set_or_return(:admin, + arg, :kind_of => [TrueClass, FalseClass]) + end + + def public_key(arg=nil) + set_or_return(:public_key, + arg, :kind_of => String) + end + + def private_key(arg=nil) + set_or_return(:private_key, + arg, :kind_of => String) + end + + def password(arg=nil) + set_or_return(:password, + arg, :kind_of => String) + end + + def to_hash + result = { + "name" => @name, + "public_key" => @public_key, + "admin" => @admin + } + result["private_key"] = @private_key if @private_key + result["password"] = @password if @password + result + end + + def to_json(*a) + to_hash.to_json(*a) + end + + def destroy + Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}") + 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) + Chef::User.from_hash(self.to_hash.merge(new_user)) + end + + def update(new_key=false) + payload = {:name => name, :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) + Chef::User.from_hash(self.to_hash.merge(updated_user)) + end + + def save(new_key=false) + begin + create + rescue Net::HTTPServerException => e + if e.response.code == "409" + update(new_key) + else + raise e + end + end + end + + 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 }) + private_key(reregistered_self["private_key"]) + self + end + + def to_s + "user[#{@name}]" + end + + def inspect + "Chef::User name:'#{name}' admin:'#{admin.inspect}'" + + "public_key:'#{public_key}' private_key:#{private_key}" + end + + # Class Methods + + 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.admin user_hash['admin'] + user + end + + def self.from_json(json) + Chef::User.from_hash(Chef::JSONCompat.from_json(json)) + end + + class << self + alias_method :json_create, :from_json + end + + def self.list(inflate=false) + response = if inflate + users = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users') + users.map do |name| + Chef::User.load(name) + end + else + Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users') + end + if response.is_a? Array + transform_ohc_list_response(response) + else + response + end + end + + def self.load(name) + response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}") + Chef::User.from_hash(response) + end + + private + + # Gross. Transforms an API response in the form of: + # [ { "user" => { "username" => USERNAME }}, ...] + # into the form + # { "USERNAME" => "URI" } + def self.transform_ohc_list_response(response) + new_response = Hash.new + response.each do |u| + name = u['user']['username'] + new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}" + end + new_response + end + end +end diff --git a/spec/unit/knife/configure_spec.rb b/spec/unit/knife/configure_spec.rb index 692fc5ea15..8d5b51551d 100644 --- a/spec/unit/knife/configure_spec.rb +++ b/spec/unit/knife/configure_spec.rb @@ -41,7 +41,7 @@ describe Chef::Knife::Configure do @knife.config[:initial] = true Etc.stub!(:getlogin).and_return("a-new-user") @knife.ask_user_for_config - @out.string.should match(Regexp.escape("Please enter a clientname for the new client: [a-new-user]")) + @out.string.should match(Regexp.escape("Please enter a name for the new user: [a-new-user]")) @knife.new_client_name.should == Etc.getlogin end @@ -50,7 +50,7 @@ describe Chef::Knife::Configure do @knife.config[:node_name] = 'testnode' Etc.stub!(:getlogin).and_return("a-new-user") @knife.ask_user_for_config - @out.string.should_not match(Regexp.escape("Please enter a clientname for the new client")) + @out.string.should_not match(Regexp.escape("Please enter a name for the new user")) @knife.new_client_name.should == 'testnode' end @@ -64,32 +64,32 @@ describe Chef::Knife::Configure do it "asks the user for the existing admin client's name if -i is specified" do @knife.config[:initial] = true @knife.ask_user_for_config - @out.string.should match(Regexp.escape("Please enter the existing admin clientname: [chef-webui]")) - @knife.admin_client_name.should == 'chef-webui' + @out.string.should match(Regexp.escape("Please enter the existing admin name: [admin]")) + @knife.admin_client_name.should == 'admin' end it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do @knife.config[:initial] = true @knife.config[:admin_client_name] = 'my-webui' @knife.ask_user_for_config - @out.string.should_not match(Regexp.escape("Please enter the existing admin clientname:")) + @out.string.should_not match(Regexp.escape("Please enter the existing admin:")) @knife.admin_client_name.should == 'my-webui' end it "should not ask the user for the existing admin client's name if -i is not specified" do @knife.ask_user_for_config - @out.string.should_not match(Regexp.escape("Please enter the existing admin clientname: [chef-webui]")) - @knife.admin_client_name.should_not == 'chef-webui' + @out.string.should_not match(Regexp.escape("Please enter the existing admin: [admin]")) + @knife.admin_client_name.should_not == 'admin' end it "asks the user for the location of the existing admin key if -i is specified" do @knife.config[:initial] = true @knife.ask_user_for_config - @out.string.should match(Regexp.escape("Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]")) + @out.string.should match(Regexp.escape("Please enter the location of the existing admin's private key: [/etc/chef/admin.pem]")) if windows? - @knife.admin_client_key.should == 'C:/etc/chef/webui.pem' + @knife.admin_client_key.should == 'C:/etc/chef/admin.pem' else - @knife.admin_client_key.should == '/etc/chef/webui.pem' + @knife.admin_client_key.should == '/etc/chef/admin.pem' end end @@ -111,7 +111,7 @@ describe Chef::Knife::Configure do if windows? @knife.admin_client_key.should_not == 'C:/etc//chef/webui.pem' else - @knife.admin_client_key.should_not == '/etc/chef/webui.pem' + @knife.admin_client_key.should_not == '/etc/chef/webui.pem' end end @@ -140,7 +140,7 @@ describe Chef::Knife::Configure do if windows? @knife.validation_key.should == 'C:/etc/chef/validation.pem' else - @knife.validation_key.should == '/etc/chef/validation.pem' + @knife.validation_key.should == '/etc/chef/validation.pem' end end @@ -208,28 +208,25 @@ describe Chef::Knife::Configure do File.should_receive(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") File.should_receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem") File.should_receive(:expand_path).with("/etc/chef/validation.pem").and_return("/etc/chef/validation.pem") - File.should_receive(:expand_path).with("/etc/chef/webui.pem").and_return("/etc/chef/webui.pem") + File.should_receive(:expand_path).with("/etc/chef/admin.pem").and_return("/etc/chef/admin.pem") Chef::Config[:node_name] = "webmonkey.example.com" - - client_command_config = {} - - client_command = mock("knife client create command", :config => client_command_config) - client_command.should_receive(:name_args=).with(["a-new-user"]) - client_command.stub!(:name_args).and_return(["a-new-user"]) - client_command.should_receive(:run) + user_command = Chef::Knife::UserCreate.new + user_command.should_receive(:run) Etc.stub!(:getlogin).and_return("a-new-user") - Chef::Knife::ClientCreate.stub!(:new).and_return(client_command) + Chef::Knife::UserCreate.stub!(:new).and_return(user_command) FileUtils.should_receive(:mkdir_p).with("/home/you/.chef") ::File.should_receive(:open).with("/home/you/.chef/knife.rb", "w") @knife.config[:initial] = true + @knife.config[:user_password] = "blah" @knife.run - client_command.name_args.should == Array("a-new-user") - client_command.config[:admin].should be_true - client_command.config[:file].should == "/home/you/.chef/a-new-user.pem" - client_command.config[:yes].should be_true - client_command.config[:disable_editing].should be_true + user_command.name_args.should == Array("a-new-user") + user_command.config[:user_password].should == "blah" + user_command.config[:admin].should be_true + user_command.config[:file].should == "/home/you/.chef/a-new-user.pem" + user_command.config[:yes].should be_true + user_command.config[:disable_editing].should be_true end end diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb new file mode 100644 index 0000000000..5bc9738313 --- /dev/null +++ b/spec/unit/knife/user_create_spec.rb @@ -0,0 +1,86 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +Chef::Knife::UserCreate.load_deps + +describe Chef::Knife::UserCreate do + before(:each) do + @knife = Chef::Knife::UserCreate.new + @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' + @user.stub!(:create).and_return(@user_with_private_key) + Chef::User.stub!(:new).and_return(@user) + Chef::User.stub!(:from_hash).and_return(@user) + @knife.stub!(:edit_data).and_return(@user.to_hash) + @stdout = StringIO.new + @stderr = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:stderr).and_return(@stderr) + end + + it "creates a new user" do + Chef::User.should_receive(:new).and_return(@user) + @user.should_receive(:create) + @knife.run + @stdout.string.should match /created user.+a_user/i + end + + it "sets the password" do + @knife.config[:user_password] = "a_password" + @user.should_receive(:password).with("a_password") + @knife.run + end + + it "exits with an error if password is blank" do + @knife.config[:user_password] = '' + lambda { @knife.run }.should raise_error SystemExit + @stderr.string.should match /You must specify a non-blank password/ + end + + it "sets the user name" do + @user.should_receive(:name).with("a_user") + @knife.run + end + + it "sets the public key if given" do + @knife.config[:user_key] = "/a/filename" + File.stub(:read).with("/a/filename").and_return("a_key") + @user.should_receive(:public_key).with("a_key") + @knife.run + end + + it "allows you to edit the data" do + @knife.should_receive(:edit_data).with(@user) + @knife.run + end + + it "writes the private key to a file when --file is specified" do + @knife.config[:file] = "/tmp/a_file" + filehandle = mock("filehandle") + filehandle.should_receive(:print).with('private_key') + File.should_receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) + @knife.run + end +end diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb new file mode 100644 index 0000000000..be027e5128 --- /dev/null +++ b/spec/unit/knife/user_delete_spec.rb @@ -0,0 +1,39 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +describe Chef::Knife::UserDelete do + before(:each) do + Chef::Knife::UserDelete.load_deps + @knife = Chef::Knife::UserDelete.new + @knife.name_args = [ 'my_user' ] + end + + it 'deletes the user' do + @knife.should_receive(:delete_object).with(Chef::User, 'my_user') + @knife.run + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end +end diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb new file mode 100644 index 0000000000..d5b380a12f --- /dev/null +++ b/spec/unit/knife/user_edit_spec.rb @@ -0,0 +1,42 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +describe Chef::Knife::UserEdit do + before(:each) do + Chef::Knife::UserEdit.load_deps + @knife = Chef::Knife::UserEdit.new + @knife.name_args = [ 'my_user' ] + @knife.config[:disable_editing] = true + end + + it 'loads and edits the user' do + data = { :name => "my_user" } + Chef::User.stub(:load).with("my_user").and_return(data) + @knife.should_receive(:edit_data).with(data).and_return(data) + @knife.run + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end +end diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb new file mode 100644 index 0000000000..7a47f9ddba --- /dev/null +++ b/spec/unit/knife/user_list_spec.rb @@ -0,0 +1,32 @@ +# +# Author:: Steven Danna +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +describe Chef::Knife::UserList do + before(:each) do + Chef::Knife::UserList.load_deps + @knife = Chef::Knife::UserList.new + end + + it 'lists the users' do + Chef::User.should_receive(:list) + @knife.should_receive(:format_list_for_display) + @knife.run + end +end diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb new file mode 100644 index 0000000000..fddab57467 --- /dev/null +++ b/spec/unit/knife/user_reregister_spec.rb @@ -0,0 +1,53 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +describe Chef::Knife::UserReregister do + before(:each) do + Chef::Knife::UserReregister.load_deps + @knife = Chef::Knife::UserReregister.new + @knife.name_args = [ 'a_user' ] + @user_mock = mock('user_mock', :private_key => "private_key") + Chef::User.stub!(:load).and_return(@user_mock) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'reregisters the user and prints the key' do + @user_mock.should_receive(:reregister).and_return(@user_mock) + @knife.run + @stdout.string.should match( /private_key/ ) + end + + it 'writes the private key to a file when --file is specified' do + @user_mock.should_receive(:reregister).and_return(@user_mock) + @knife.config[:file] = '/tmp/a_file' + filehandle = StringIO.new + File.should_receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle) + @knife.run + filehandle.string.should == "private_key" + end +end diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb new file mode 100644 index 0000000000..f2bd959d15 --- /dev/null +++ b/spec/unit/knife/user_show_spec.rb @@ -0,0 +1,41 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +describe Chef::Knife::UserShow do + before(:each) do + Chef::Knife::UserShow.load_deps + @knife = Chef::Knife::UserShow.new + @knife.name_args = [ 'my_user' ] + @user_mock = mock('user_mock') + end + + it 'loads and displays the user' do + Chef::User.should_receive(:load).with('my_user').and_return(@user_mock) + @knife.should_receive(:format_for_display).with(@user_mock) + @knife.run + end + + it 'prints usage and exits when a user name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end +end diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb new file mode 100644 index 0000000000..9121babeed --- /dev/null +++ b/spec/unit/user_spec.rb @@ -0,0 +1,255 @@ +# +# Author:: Steven Danna (steve@opscode.com) +# Copyright:: Copyright (c) 2012 Opscode, 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. +# + +require 'spec_helper' + +require 'chef/user' +require 'tempfile' + +describe Chef::User do + before(:each) do + @user = Chef::User.new + end + + describe "initialize" do + it "should be a Chef::User" do + @user.should be_a_kind_of(Chef::User) + end + end + + describe "name" do + it "should let you set the name to a string" do + @user.name("ops_master").should == "ops_master" + end + + it "should return the current name" do + @user.name "ops_master" + @user.name.should == "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 + lambda { @user.name "Bar" }.should raise_error(ArgumentError) + # slashes + lambda { @user.name "foo/bar" }.should raise_error(ArgumentError) + # ? + lambda { @user.name "foo?" }.should raise_error(ArgumentError) + # & + lambda { @user.name "foo&" }.should raise_error(ArgumentError) + end + + + it "should not accept spaces" do + lambda { @user.name "ops master" }.should raise_error(ArgumentError) + end + + it "should throw an ArgumentError if you feed it anything but a string" do + lambda { @user.name Hash.new }.should raise_error(ArgumentError) + end + end + + describe "admin" do + it "should let you set the admin bit" do + @user.admin(true).should == true + end + + it "should return the current admin value" do + @user.admin true + @user.admin.should == true + end + + it "should default to false" do + @user.admin.should == false + end + + it "should throw an ArgumentError if you feed it anything but true or false" do + lambda { @user.name Hash.new }.should raise_error(ArgumentError) + end + end + + describe "public_key" do + it "should let you set the public key" do + @user.public_key("super public").should == "super public" + end + + it "should return the current public key" do + @user.public_key("super public") + @user.public_key.should == "super public" + end + + it "should throw an ArgumentError if you feed it something lame" do + lambda { @user.public_key Hash.new }.should raise_error(ArgumentError) + end + end + + describe "private_key" do + it "should let you set the private key" do + @user.private_key("super private").should == "super private" + end + + it "should return the private key" do + @user.private_key("super private") + @user.private_key.should == "super private" + end + + it "should throw an ArgumentError if you feed it something lame" do + lambda { @user.private_key Hash.new }.should raise_error(ArgumentError) + end + end + + describe "when serializing to JSON" do + before(:each) do + @user.name("black") + @user.public_key("crowes") + @json = @user.to_json + end + + it "serializes as a JSON object" do + @json.should match(/^\{.+\}$/) + end + + it "includes the name value" do + @json.should include(%q{"name":"black"}) + end + + it "includes the public key value" do + @json.should include(%{"public_key":"crowes"}) + end + + it "includes the 'admin' flag" do + @json.should include(%q{"admin":false}) + end + + it "includes the private key when present" do + @user.private_key("monkeypants") + @user.to_json.should include(%q{"private_key":"monkeypants"}) + end + + it "does not include the private key if not present" do + @json.should_not include("private_key") + end + + it "includes the password if present" do + @user.password "password" + @user.to_json.should include(%q{"password":"password"}) + end + + it "does not include the password if not present" do + @json.should_not include("password") + end + end + + describe "when deserializing from JSON" do + before(:each) do + user = { "name" => "mr_spinks", + "public_key" => "turtles", + "private_key" => "pandas", + "password" => "password", + "admin" => true } + @user = Chef::User.from_json(user.to_json) + end + + it "should deserialize to a Chef::User object" do + @user.should be_a_kind_of(Chef::User) + end + + it "preserves the name" do + @user.name.should == "mr_spinks" + end + + it "preserves the public key" do + @user.public_key.should == "turtles" + end + + it "preserves the admin status" do + @user.admin.should be_true + end + + it "includes the private key if present" do + @user.private_key.should == "pandas" + end + + it "includes the password if present" do + @user.password.should == "password" + end + + end + + describe "API Interactions" do + before (:each) do + @user = Chef::User.new + @user.name "foobar" + @http_client = mock("Chef::REST mock") + Chef::REST.stub!(:new).and_return(@http_client) + end + + describe "list" do + before(:each) do + Chef::Config[:chef_server_url] = "http://www.example.com" + @osc_response = { "admin" => "http://www.example.com/users/admin"} + @ohc_response = [ { "user" => { "username" => "admin" }} ] + end + + it "lists all clients on an OSC server" do + @http_client.stub!(:get_rest).with("users").and_return(@osc_response) + Chef::User.list.should == @osc_response + end + + it "lists all clients on an OHC/OPC server" do + @http_client.stub!(:get_rest).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. + Chef::User.list.should == @osc_response + end + end + + describe "create" do + it "creates a new user via the API" do + @user.password "password" + @http_client.should_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 + @http_client.should_receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"}) + user = Chef::User.load("foobar") + user.name.should == "foobar" + user.admin.should == true + user.public_key.should == "pubkey" + end + end + + describe "update" do + it "updates an existing user on via the API" do + @http_client.should_receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({}) + @user.update + end + end + + describe "destroy" do + it "deletes the specified user via the API" do + @http_client.should_receive(:delete_rest).with("users/foobar") + @user.destroy + end + end + end +end |