summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
authortylercloke <tylercloke@gmail.com>2015-06-01 12:18:36 -0700
committertylercloke <tylercloke@gmail.com>2015-06-05 10:38:48 -0700
commitb217055bdfdf80ff0cfd2c00abcd339f81b74d8d (patch)
treef1a2aaff4ab7af513ce3efdbfcdb6bdcbd29c4d4 /lib/chef
parent1120157edceb842587727f06f5ac681a87d2fea1 (diff)
downloadchef-b217055bdfdf80ff0cfd2c00abcd339f81b74d8d.tar.gz
API V1 support for client creation.
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/api_client.rb87
-rw-r--r--lib/chef/exceptions.rb1
-rw-r--r--lib/chef/knife/client_create.rb85
-rw-r--r--lib/chef/knife/user_create.rb18
-rw-r--r--lib/chef/user.rb5
5 files changed, 144 insertions, 52 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?