summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McLellan <btm@opscode.com>2013-02-06 09:55:05 -0800
committerBryan McLellan <btm@opscode.com>2013-02-06 09:55:05 -0800
commit36a0bcc51360c4e3c603a84344ad945fca1ebbe7 (patch)
treecb57316129f47031f90bfeb152f40d12312a901c
parent13774733fba0ac483a7212c6ef55517e0301a4ff (diff)
parent85fc6fe19e351396ab19ccafa08d2767ad170ffb (diff)
downloadchef-36a0bcc51360c4e3c603a84344ad945fca1ebbe7.tar.gz
Merge branch 'CHEF-3707'
-rw-r--r--lib/chef/knife/configure.rb29
-rw-r--r--lib/chef/knife/user_create.rb93
-rw-r--r--lib/chef/knife/user_delete.rb46
-rw-r--r--lib/chef/knife/user_edit.rb53
-rw-r--r--lib/chef/knife/user_list.rb42
-rw-r--r--lib/chef/knife/user_reregister.rb59
-rw-r--r--lib/chef/knife/user_show.rb52
-rw-r--r--lib/chef/user.rb182
-rw-r--r--spec/unit/knife/configure_spec.rb49
-rw-r--r--spec/unit/knife/user_create_spec.rb86
-rw-r--r--spec/unit/knife/user_delete_spec.rb39
-rw-r--r--spec/unit/knife/user_edit_spec.rb42
-rw-r--r--spec/unit/knife/user_list_spec.rb32
-rw-r--r--spec/unit/knife/user_reregister_spec.rb53
-rw-r--r--spec/unit/knife/user_show_spec.rb41
-rw-r--r--spec/unit/user_spec.rb255
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