diff options
Diffstat (limited to 'lib')
97 files changed, 918 insertions, 478 deletions
diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb index bc941d5bfa..99667acc0a 100644 --- a/lib/chef/api_client/registration.rb +++ b/lib/chef/api_client/registration.rb @@ -17,7 +17,7 @@ # require 'chef/config' -require 'chef/rest' +require 'chef/server_api' require 'chef/exceptions' class Chef @@ -45,7 +45,7 @@ class Chef #-- # If client creation fails with a 5xx, it is retried up to 5 times. These # retries are on top of the retries with randomized exponential backoff - # built in to Chef::REST. The retries here are a workaround for failures + # built in to Chef::ServerAPI. The retries here are a workaround for failures # caused by resource contention in Hosted Chef when creating a very large # number of clients simultaneously, (e.g., spinning up 100s of ec2 nodes # at once). Future improvements to the affected component should make diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 0ee201f103..3ab68260d7 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -186,12 +186,12 @@ class Chef # Called prior to starting the application, by the run method def setup_application - raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application" + raise Chef::Exceptions::Application, "#{self}: you must override setup_application" end # Actually run the application def run_application - raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application" + raise Chef::Exceptions::Application, "#{self}: you must override run_application" end # Initializes Chef::Client instance and runs it @@ -307,7 +307,7 @@ class Chef class << self def debug_stacktrace(e) message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" - chef_stacktrace_out = "Generated at #{Time.now.to_s}\n" + chef_stacktrace_out = "Generated at #{Time.now}\n" chef_stacktrace_out += message Chef::FileCache.store("chef-stacktrace.out", chef_stacktrace_out) diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb index 2f938059ca..9becf4b33f 100644 --- a/lib/chef/application/windows_service.rb +++ b/lib/chef/application/windows_service.rb @@ -23,7 +23,7 @@ require 'chef/client' require 'chef/config' require 'chef/handler/error_report' require 'chef/log' -require 'chef/rest' +require 'chef/http' require 'mixlib/cli' require 'socket' require 'uri' @@ -308,7 +308,7 @@ class Chef begin case config[:config_file] when /^(http|https):\/\// - Chef::REST.new("", nil, nil).fetch(config[:config_file]) { |f| apply_config(f.path) } + Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) } else ::File::open(config[:config_file]) { |f| apply_config(f.path) } end diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb index d952d8a249..face24f1f5 100644 --- a/lib/chef/audit/audit_reporter.rb +++ b/lib/chef/audit/audit_reporter.rb @@ -127,10 +127,8 @@ class Chef end Chef::Log.debug "Audit Report:\n#{Chef::JSONCompat.to_json_pretty(run_data)}" - # Since we're posting compressed data we can not directly call post_rest which expects JSON begin - audit_url = rest_client.create_url(audit_history_url) - rest_client.post(audit_url, run_data, headers) + rest_client.post(audit_history_url, run_data, headers) rescue StandardError => e if e.respond_to? :response # 404 error code is OK. This means the version of server we're running against doesn't support diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb index 09181ac4b4..badb70ce50 100644 --- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb @@ -104,7 +104,7 @@ class Chef end def chef_rest - Chef::REST.new(chef_server_url, chef_username, chef_private_key) + Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key) end def api_path diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 6ac5cecbdf..ead804879f 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -22,7 +22,7 @@ require 'chef/config' require 'chef/mixin/params_validate' require 'chef/mixin/path_sanity' require 'chef/log' -require 'chef/rest' +require 'chef/server_api' require 'chef/api_client' require 'chef/api_client/registration' require 'chef/audit/runner' @@ -92,7 +92,7 @@ class Chef # # The rest object used to communicate with the Chef server. # - # @return [Chef::REST] + # @return [Chef::ServerAPI] # attr_reader :rest @@ -575,7 +575,7 @@ class Chef # If Chef::Config.client_key does not exist, we register the client with the # Chef server and fire the registration_start and registration_completed events. # - # @return [Chef::REST] The server connection object. + # @return [Chef::ServerAPI] The server connection object. # # @see Chef::Config#chef_server_url # @see Chef::Config#client_key @@ -601,7 +601,8 @@ class Chef events.registration_completed end # We now have the client key, and should use it from now on. - @rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key]) + @rest = Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name, + signing_key_filename: config[:client_key]) register_reporters rescue Exception => e # TODO this should probably only ever fire if we *started* registration. diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb index 7868430227..b118c75f9e 100644 --- a/lib/chef/cookbook/remote_file_vendor.rb +++ b/lib/chef/cookbook/remote_file_vendor.rb @@ -63,7 +63,7 @@ class Chef # (remote, per manifest), do the update. This will also execute if there # is no current checksum. if current_checksum != found_manifest_record['checksum'] - raw_file = @rest.get_rest(found_manifest_record[:url], true) + raw_file = @rest.get(found_manifest_record[:url], true) Chef::Log.debug("Storing updated #{cache_filename} in the cache.") Chef::FileCache.move_to(raw_file.path, cache_filename) diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb index fc8e739d73..b499963653 100644 --- a/lib/chef/cookbook/synchronizer.rb +++ b/lib/chef/cookbook/synchronizer.rb @@ -1,5 +1,6 @@ require 'chef/client' require 'chef/util/threaded_job_queue' +require 'chef/server_api' require 'singleton' class Chef @@ -274,7 +275,7 @@ class Chef # downloaded to the path +destination+ which is relative to the Chef file # cache root. def download_file(url, destination) - raw_file = server_api.get_rest(url, true) + raw_file = server_api.streaming_request(url) Chef::Log.info("Storing updated #{destination} in the cache.") cache.move_to(raw_file.path, destination) @@ -286,7 +287,7 @@ class Chef end def server_api - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb index 79005b1569..e783405b9b 100644 --- a/lib/chef/cookbook_loader.rb +++ b/lib/chef/cookbook_loader.rb @@ -106,7 +106,7 @@ class Chef if @cookbooks_by_name.has_key?(cookbook.to_sym) or load_cookbook(cookbook.to_sym) @cookbooks_by_name[cookbook.to_sym] else - raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)" + raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)" end end diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb index f24ce2cd56..64a8a4e168 100644 --- a/lib/chef/cookbook_uploader.rb +++ b/lib/chef/cookbook_uploader.rb @@ -9,6 +9,7 @@ require 'chef/cookbook/syntax_check' require 'chef/cookbook/file_system_file_vendor' require 'chef/util/threaded_job_queue' require 'chef/sandbox' +require 'chef/server_api' class Chef class CookbookUploader @@ -31,7 +32,7 @@ class Chef # uploading the cookbook. This allows frozen CookbookVersion # documents on the server to be overwritten (otherwise a 409 is # returned by the server) - # * :rest A Chef::REST object that you have configured the way you like it. + # * :rest A Chef::ServerAPI object that you have configured the way you like it. # If you don't provide this, one will be created using the values # in Chef::Config. # * :concurrency An integer that decided how many threads will be used to @@ -39,7 +40,7 @@ class Chef def initialize(cookbooks, opts={}) @opts = opts @cookbooks = Array(cookbooks) - @rest = opts[:rest] || Chef::REST.new(Chef::Config[:chef_server_url]) + @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url]) @concurrency = opts[:concurrency] || 10 @policy_mode = opts[:policy_mode] || false end diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 0e9617f98c..4eb118d3bc 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -25,6 +25,7 @@ require 'chef/cookbook/metadata' require 'chef/version_class' require 'chef/digester' require 'chef/cookbook_manifest' +require 'chef/server_api' class Chef @@ -459,11 +460,13 @@ class Chef end private :preferences_for_path - def self.json_create(o) - cookbook_version = new(o["cookbook_name"]) + def self.from_hash(o) + cookbook_version = new(o["cookbook_name"] || o["name"]) + # We want the Chef::Cookbook::Metadata object to always be inflated cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"]) cookbook_version.manifest = o + cookbook_version.identifier = o["identifier"] if o.key?("identifier") # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>) cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(cookbook_version.metadata)) @@ -472,13 +475,12 @@ class Chef cookbook_version end + def self.json_create(o) + from_hash(o) + end + def self.from_cb_artifact_data(o) - cookbook_version = new(o["name"]) - # We want the Chef::Cookbook::Metadata object to always be inflated - cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"]) - cookbook_version.manifest = o - cookbook_version.identifier = o["identifier"] - cookbook_version + from_hash(o) end # @deprecated This method was used by the Ruby Chef Server and is no longer @@ -543,22 +545,22 @@ class Chef end def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def destroy - chef_server_rest.delete_rest("cookbooks/#{name}/#{version}") + chef_server_rest.delete("cookbooks/#{name}/#{version}") self end def self.load(name, version="_latest") version = "_latest" if version == "latest" - chef_server_rest.get_rest("cookbooks/#{name}/#{version}") + from_hash(chef_server_rest.get("cookbooks/#{name}/#{version}")) end # The API returns only a single version of each cookbook in the result from the cookbooks method def self.list - chef_server_rest.get_rest('cookbooks') + chef_server_rest.get('cookbooks') end # Alias latest_cookbooks as list @@ -567,7 +569,7 @@ class Chef end def self.list_all_versions - chef_server_rest.get_rest('cookbooks?num_versions=all') + chef_server_rest.get('cookbooks?num_versions=all') end ## @@ -577,7 +579,7 @@ class Chef # [String]:: Array of cookbook versions, which are strings like 'x.y.z' # nil:: if the cookbook doesn't exist. an error will also be logged. def self.available_versions(cookbook_name) - chef_server_rest.get_rest("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb| + chef_server_rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb| cb["version"] end rescue Net::HTTPServerException => e diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb index 401ba6f63f..9d0dc53da5 100644 --- a/lib/chef/data_bag.rb +++ b/lib/chef/data_bag.rb @@ -24,6 +24,7 @@ require 'chef/mixin/from_file' require 'chef/data_bag_item' require 'chef/mash' require 'chef/json_compat' +require 'chef/server_api' class Chef class DataBag @@ -70,15 +71,19 @@ class Chef end def chef_server_rest - @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url]) + @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end # Create a Chef::Role from JSON def self.json_create(o) + from_hash(o) + end + + def self.from_hash(o) bag = new bag.name(o["name"]) bag @@ -104,7 +109,7 @@ class Chef response end else - Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data") + Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data") end end end @@ -120,7 +125,7 @@ class Chef end Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(path, name.to_s), "*.json")).inject({}) do |bag, f| - item = Chef::JSONCompat.from_json(IO.read(f)) + item = Chef::JSONCompat.parse(IO.read(f)) # Check if we have multiple items with similar names (ids) and raise if their content differs if data_bag.has_key?(item["id"]) && data_bag[item["id"]] != item @@ -132,12 +137,12 @@ class Chef end return data_bag else - Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data/#{name}") + Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{name}") end end def destroy - chef_server_rest.delete_rest("data/#{@name}") + chef_server_rest.delete("data/#{@name}") end # Save the Data Bag via RESTful API @@ -156,7 +161,7 @@ class Chef #create a data bag via RESTful API def create - chef_server_rest.post_rest("data", self) + chef_server_rest.post("data", self) self end diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb index 31c9b69330..7ef9fffe07 100644 --- a/lib/chef/data_bag_item.rb +++ b/lib/chef/data_bag_item.rb @@ -25,6 +25,7 @@ require 'chef/mixin/params_validate' require 'chef/mixin/from_file' require 'chef/data_bag' require 'chef/mash' +require 'chef/server_api' require 'chef/json_compat' class Chef @@ -58,11 +59,11 @@ class Chef end def chef_server_rest - @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url]) + @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def raw_data @@ -125,22 +126,23 @@ class Chef end def self.from_hash(h) + h.delete("chef_type") + h.delete("json_class") + h.delete("name") + item = new - item.raw_data = h + item.data_bag(h.delete("data_bag")) if h.key?("data_bag") + if h.key?("raw_data") + item.raw_data = Mash.new(h["raw_data"]) + else + item.raw_data = h + end item end # Create a Chef::DataBagItem from JSON def self.json_create(o) - bag_item = new - bag_item.data_bag(o["data_bag"]) - o.delete("data_bag") - o.delete("chef_type") - o.delete("json_class") - o.delete("name") - - bag_item.raw_data = Mash.new(o["raw_data"]) - bag_item + from_hash(o) end # Load a Data Bag Item by name via either the RESTful API or local data_bag_path if run in solo mode @@ -149,7 +151,7 @@ class Chef bag = Chef::DataBag.load(data_bag) item = bag[name] else - item = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data/#{data_bag}/#{name}") + item = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{data_bag}/#{name}") end if item.kind_of?(DataBagItem) @@ -162,7 +164,7 @@ class Chef end def destroy(data_bag=data_bag(), databag_item=name) - chef_server_rest.delete_rest("data/#{data_bag}/#{databag_item}") + chef_server_rest.delete("data/#{data_bag}/#{databag_item}") end # Save this Data Bag Item via RESTful API @@ -172,18 +174,18 @@ class Chef if Chef::Config[:why_run] Chef::Log.warn("In why-run mode, so NOT performing data bag item save.") else - r.put_rest("data/#{data_bag}/#{item_id}", self) + r.put("data/#{data_bag}/#{item_id}", self) end rescue Net::HTTPServerException => e raise e unless e.response.code == "404" - r.post_rest("data/#{data_bag}", self) + r.post("data/#{data_bag}", self) end self end # Create this Data Bag Item via RESTful API def create - chef_server_rest.post_rest("data/#{data_bag}", self) + chef_server_rest.post("data/#{data_bag}", self) self end diff --git a/lib/chef/environment.rb b/lib/chef/environment.rb index 7d4b410639..5612978a08 100644 --- a/lib/chef/environment.rb +++ b/lib/chef/environment.rb @@ -24,6 +24,7 @@ require 'chef/mash' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' require 'chef/version_constraint' +require 'chef/server_api' class Chef class Environment @@ -47,11 +48,11 @@ class Chef end def chef_server_rest - @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url]) + @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def name(arg=nil) @@ -216,6 +217,10 @@ class Chef end def self.json_create(o) + from_hash(o) + end + + def self.from_hash(o) environment = new environment.name(o["name"]) environment.description(o["description"]) @@ -233,7 +238,7 @@ class Chef end response else - chef_server_rest.get_rest("environments") + chef_server_rest.get("environments") end end @@ -241,7 +246,7 @@ class Chef if Chef::Config[:solo] load_from_file(name) else - chef_server_rest.get_rest("environments/#{name}") + chef_server_rest.get("environments/#{name}") end end @@ -267,26 +272,26 @@ class Chef end def destroy - chef_server_rest.delete_rest("environments/#{@name}") + chef_server_rest.delete("environments/#{@name}") end def save begin - chef_server_rest.put_rest("environments/#{@name}", self) + chef_server_rest.put("environments/#{@name}", self) rescue Net::HTTPServerException => e raise e unless e.response.code == "404" - chef_server_rest.post_rest("environments", self) + chef_server_rest.post("environments", self) end self end def create - chef_server_rest.post_rest("environments", self) + chef_server_rest.post("environments", self) self end def self.load_filtered_recipe_list(environment) - chef_server_rest.get_rest("environments/#{environment}/recipes") + chef_server_rest.get("environments/#{environment}/recipes") end def to_s diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb index 3862951f76..62db517e3e 100644 --- a/lib/chef/formatters/minimal.rb +++ b/lib/chef/formatters/minimal.rb @@ -157,7 +157,7 @@ class Chef puts "\n" puts "resources updated this run:" updated_resources.each do |resource| - puts "* #{resource.to_s}" + puts "* #{resource}" updates_by_resource[resource.name].flatten.each do |update| puts " - #{update}" end diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb index 8cff3bc032..a333028a22 100644 --- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb +++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb @@ -88,7 +88,7 @@ class Chef resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node) if resource_class.nil? - raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter.to_s} unknown for this platform" + raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter} unknown for this platform" end if ! resource_class.ancestors.include?(Chef::Resource::Execute) diff --git a/lib/chef/http.rb b/lib/chef/http.rb index 9f1eeed55f..a26e82f795 100644 --- a/lib/chef/http.rb +++ b/lib/chef/http.rb @@ -396,7 +396,7 @@ class Chef if Chef::Platform.windows? tf.binmode # required for binary files on Windows platforms end - Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}") + Chef::Log.debug("Streaming download from #{url} to tempfile #{tf.path}") # Stolen from http://www.ruby-forum.com/topic/166423 # Kudos to _why! diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb index 1baf5724ae..24cd99375d 100644 --- a/lib/chef/http/http_request.rb +++ b/lib/chef/http/http_request.rb @@ -127,7 +127,7 @@ class Chef # http://redmine.ruby-lang.org/issues/show/2758 if e.to_s =~ /#{Regexp.escape(%q|undefined method `closed?' for nil:NilClass|)}/ Chef::Log.debug("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http") - Chef::Log.debug("#{e.class.name}: #{e.to_s}") + Chef::Log.debug("#{e.class.name}: #{e}") Chef::Log.debug(e.backtrace.join("\n")) raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}" else diff --git a/lib/chef/http/simple.rb b/lib/chef/http/simple.rb index 8519554f2b..f59fcaa08b 100644 --- a/lib/chef/http/simple.rb +++ b/lib/chef/http/simple.rb @@ -1,3 +1,21 @@ +# +# Author:: Daniel DeLeo (<dan@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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/http' require 'chef/http/authenticator' require 'chef/http/decompressor' diff --git a/lib/chef/http/simple_json.rb b/lib/chef/http/simple_json.rb new file mode 100644 index 0000000000..5dfdfbb680 --- /dev/null +++ b/lib/chef/http/simple_json.rb @@ -0,0 +1,43 @@ +# +# Author:: Thom May (<thom@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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/http' +require 'chef/http/authenticator' +require 'chef/http/decompressor' +require 'chef/http/cookie_manager' +require 'chef/http/validate_content_length' + +class Chef + class HTTP + + class SimpleJSON < HTTP + + use JSONInput + use JSONOutput + use CookieManager + use Decompressor + use RemoteRequestID + + # ValidateContentLength should come after Decompressor + # because the order of middlewares is reversed when handling + # responses. + use ValidateContentLength + + end + end +end diff --git a/lib/chef/key.rb b/lib/chef/key.rb index be4be7f230..47bfe1fcee 100644 --- a/lib/chef/key.rb +++ b/lib/chef/key.rb @@ -19,6 +19,7 @@ require 'chef/json_compat' require 'chef/mixin/params_validate' require 'chef/exceptions' +require 'chef/server_api' class Chef # Class for interacting with a chef key object. Can be used to create new keys, @@ -31,7 +32,7 @@ class Chef # @attr [String] public_key the RSA string of this key # @attr [String] private_key the RSA string of the private key if returned via a POST or PUT # @attr [String] expiration_date the ISO formatted string YYYY-MM-DDTHH:MM:SSZ, i.e. 2020-12-24T21:00:00Z - # @attr [String] rest Chef::REST object, initialized and cached via chef_rest method + # @attr [String] rest Chef::ServerAPI object, initialized and cached via chef_rest method # @attr [string] api_base either "users" or "clients", initialized and cached via api_base method # # @attr_reader [String] actor_field_name must be either 'client' or 'user' @@ -60,9 +61,9 @@ class Chef def chef_rest @rest ||= if @actor_field_name == "user" - Chef::REST.new(Chef::Config[:chef_server_root]) + Chef::ServerAPI.new(Chef::Config[:chef_server_root]) else - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end @@ -151,7 +152,7 @@ class Chef payload['public_key'] = @public_key unless @public_key.nil? payload['create_key'] = @create_key if @create_key payload['expiration_date'] = @expiration_date unless @expiration_date.nil? - result = chef_rest.post_rest("#{api_base}/#{@actor}/keys", payload) + result = chef_rest.post("#{api_base}/#{@actor}/keys", payload) # append the private key to the current key if the server returned one, # since the POST endpoint just returns uri and private_key if needed. new_key = self.to_hash @@ -174,7 +175,7 @@ class Chef # to @name. put_name = @name if put_name.nil? - new_key = chef_rest.put_rest("#{api_base}/#{@actor}/keys/#{put_name}", to_hash) + new_key = chef_rest.put("#{api_base}/#{@actor}/keys/#{put_name}", to_hash) # if the server returned a public_key, remove the create_key field, as we now have a key if new_key["public_key"] self.delete_create_key @@ -197,7 +198,7 @@ class Chef raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when delete is called" end - chef_rest.delete_rest("#{api_base}/#{@actor}/keys/#{@name}") + chef_rest.delete("#{api_base}/#{@actor}/keys/#{@name}") end # Class methods @@ -226,22 +227,22 @@ class Chef end def self.list_by_user(actor, inflate=false) - keys = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys") + keys = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys") self.list(keys, actor, :load_by_user, inflate) end def self.list_by_client(actor, inflate=false) - keys = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys") + keys = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys") self.list(keys, actor, :load_by_client, inflate) end def self.load_by_user(actor, key_name) - response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys/#{key_name}") + response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys/#{key_name}") Chef::Key.from_hash(response.merge({"user" => actor})) end def self.load_by_client(actor, key_name) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys/#{key_name}") + response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys/#{key_name}") Chef::Key.from_hash(response.merge({"client" => actor})) end diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 2820f58e85..34e437c82f 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -26,14 +26,16 @@ require 'chef/mixin/path_sanity' require 'chef/knife/core/subcommand_loader' require 'chef/knife/core/ui' require 'chef/local_mode' -require 'chef/rest' +require 'chef/server_api' require 'chef/http/authenticator' +require 'chef/http/http_request' +require 'chef/http' require 'pp' class Chef class Knife - Chef::REST::RESTRequest.user_agent = "Chef Knife#{Chef::REST::RESTRequest::UA_COMMON}" + Chef::HTTP::HTTPRequest.user_agent = "Chef Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}" include Mixlib::CLI include Chef::Mixin::PathSanity @@ -551,15 +553,15 @@ class Chef def rest @rest ||= begin - require 'chef/rest' - Chef::REST.new(Chef::Config[:chef_server_url]) + require 'chef/server_api' + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end def noauth_rest @rest ||= begin - require 'chef/rest' - Chef::REST.new(Chef::Config[:chef_server_url], false, false) + require 'chef/http/simple_json' + Chef::HTTP::SimpleJSON.new(Chef::Config[:chef_server_url]) end end diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb index 6414ac5c72..f5a2ff2bb1 100644 --- a/lib/chef/knife/bootstrap/client_builder.rb +++ b/lib/chef/knife/bootstrap/client_builder.rb @@ -17,7 +17,7 @@ # require 'chef/node' -require 'chef/rest' +require 'chef/server_api' require 'chef/api_client/registration' require 'chef/api_client' require 'chef/knife/bootstrap' @@ -185,22 +185,22 @@ class Chef # @param relative_path [String] URI path relative to the chef organization # @return [Boolean] if the relative path exists or returns a 404 def resource_exists?(relative_path) - rest.get_rest(relative_path) + rest.get(relative_path) true rescue Net::HTTPServerException => e raise unless e.response.code == "404" false end - # @return [Chef::REST] REST client using the client credentials + # @return [Chef::ServerAPI] REST client using the client credentials def client_rest - @client_rest ||= Chef::REST.new(chef_server_url, node_name, client_path) + @client_rest ||= Chef::ServerAPI.new(chef_server_url, :client_name => node_name, :signing_key_filename => client_path) end - # @return [Chef::REST] REST client using the cli user's knife credentials + # @return [Chef::ServerAPI] REST client using the cli user's knife credentials # this uses the users's credentials def rest - @rest ||= Chef::REST.new(chef_server_url) + @rest ||= Chef::ServerAPI.new(chef_server_url) end end end diff --git a/lib/chef/knife/cookbook_bulk_delete.rb b/lib/chef/knife/cookbook_bulk_delete.rb index 65fa888486..ec0d06937f 100644 --- a/lib/chef/knife/cookbook_bulk_delete.rb +++ b/lib/chef/knife/cookbook_bulk_delete.rb @@ -60,9 +60,9 @@ class Chef cookbooks_names.each do |cookbook_name| - versions = rest.get_rest("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map {|v| v["version"]}.flatten + versions = rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map {|v| v["version"]}.flatten versions.each do |version| - object = rest.delete_rest("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}") + object = rest.delete("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}") ui.info("Deleted cookbook #{cookbook_name.ljust(25)} [#{version}]") end end diff --git a/lib/chef/knife/cookbook_delete.rb b/lib/chef/knife/cookbook_delete.rb index f436d270bd..5fe0e9664d 100644 --- a/lib/chef/knife/cookbook_delete.rb +++ b/lib/chef/knife/cookbook_delete.rb @@ -85,7 +85,7 @@ class Chef end def available_versions - @available_versions ||= rest.get_rest("cookbooks/#{@cookbook_name}").map do |name, url_and_version| + @available_versions ||= rest.get("cookbooks/#{@cookbook_name}").map do |name, url_and_version| url_and_version["versions"].map {|url_by_version| url_by_version["version"]} end.flatten rescue Net::HTTPServerException => e @@ -143,7 +143,7 @@ class Chef def delete_request(path) path += "?purge=true" if config[:purge] - rest.delete_rest(path) + rest.delete(path) end end diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb index cb8eeb8edf..6ba5fc7d6c 100644 --- a/lib/chef/knife/cookbook_download.rb +++ b/lib/chef/knife/cookbook_download.rb @@ -69,7 +69,7 @@ class Chef ui.info("Downloading #{@cookbook_name} cookbook version #{@version}") - cookbook = rest.get_rest("cookbooks/#{@cookbook_name}/#{@version}") + cookbook = Chef::CookbookVersion.load(@cookbook_name, @version) manifest = cookbook.manifest basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}") @@ -90,8 +90,7 @@ class Chef dest = File.join(basedir, segment_file['path'].gsub('/', File::SEPARATOR)) Chef::Log.debug("Downloading #{segment_file['path']} to #{dest}") FileUtils.mkdir_p(File.dirname(dest)) - rest.sign_on_redirect = false - tempfile = rest.get_rest(segment_file['url'], true) + tempfile = rest.streaming_request(segment_file['url']) FileUtils.mv(tempfile.path, dest) end end diff --git a/lib/chef/knife/cookbook_list.rb b/lib/chef/knife/cookbook_list.rb index 75f18a154b..dd78e854da 100644 --- a/lib/chef/knife/cookbook_list.rb +++ b/lib/chef/knife/cookbook_list.rb @@ -39,7 +39,7 @@ class Chef env = config[:environment] num_versions = config[:all_versions] ? "num_versions=all" : "num_versions=1" api_endpoint = env ? "/environments/#{env}/cookbooks?#{num_versions}" : "/cookbooks?#{num_versions}" - cookbook_versions = rest.get_rest(api_endpoint) + cookbook_versions = rest.get(api_endpoint) ui.output(format_cookbook_list_for_display(cookbook_versions)) end end diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb index 7c9cbebdb1..07f7684c27 100644 --- a/lib/chef/knife/cookbook_show.rb +++ b/lib/chef/knife/cookbook_show.rb @@ -67,9 +67,9 @@ class Chef cookbook_name, segment, filename = @name_args[0], @name_args[2], @name_args[3] cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1] - cookbook = rest.get_rest("cookbooks/#{cookbook_name}/#{cookbook_version}") + cookbook = rest.get("cookbooks/#{cookbook_name}/#{cookbook_version}") manifest_entry = cookbook.preferred_manifest_record(node, segment, filename) - temp_file = rest.get_rest(manifest_entry[:url], true) + temp_file = rest.get(manifest_entry[:url], true) # the temp file is cleaned up elsewhere temp_file.open if temp_file.closed? @@ -77,16 +77,16 @@ class Chef when 3 # We are showing a specific part of the cookbook cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1] - result = rest.get_rest("cookbooks/#{@name_args[0]}/#{cookbook_version}") + result = rest.get("cookbooks/#{@name_args[0]}/#{cookbook_version}") output(result.manifest[@name_args[2]]) when 2 # We are showing the whole cookbook data cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1] - output(rest.get_rest("cookbooks/#{@name_args[0]}/#{cookbook_version}")) + output(rest.get("cookbooks/#{@name_args[0]}/#{cookbook_version}")) when 1 # We are showing the cookbook versions (all of them) cookbook_name = @name_args[0] env = config[:environment] api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}" - output(format_cookbook_list_for_display(rest.get_rest(api_endpoint))) + output(format_cookbook_list_for_display(rest.get(api_endpoint))) when 0 show_usage ui.fatal("You must specify a cookbook name") diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb index 3e586e6542..72608f3a30 100644 --- a/lib/chef/knife/cookbook_site_download.rb +++ b/lib/chef/knife/cookbook_site_download.rb @@ -63,7 +63,7 @@ class Chef def current_cookbook_data @current_cookbook_data ||= begin - noauth_rest.get_rest "#{cookbooks_api_url}/#{@name_args[0]}" + noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}" end end @@ -79,14 +79,14 @@ class Chef specific_cookbook_version_url end - noauth_rest.get_rest uri + noauth_rest.get uri end end def download_cookbook ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}" noauth_rest.sign_on_redirect = false - tf = noauth_rest.get_rest desired_cookbook_data["file"], true + tf = noauth_rest.get desired_cookbook_data["file"], true ::FileUtils.cp tf.path, download_location ui.info "Cookbook saved: #{download_location}" diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb index 846123c867..b5354ed6e6 100644 --- a/lib/chef/knife/cookbook_site_list.rb +++ b/lib/chef/knife/cookbook_site_list.rb @@ -42,7 +42,7 @@ class Chef def get_cookbook_list(items=10, start=0, cookbook_collection={}) cookbooks_url = "https://supermarket.chef.io/api/v1/cookbooks?items=#{items}&start=#{start}" - cr = noauth_rest.get_rest(cookbooks_url) + cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook end diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb index 0baaf90f1c..decbf6c2c3 100644 --- a/lib/chef/knife/cookbook_site_search.rb +++ b/lib/chef/knife/cookbook_site_search.rb @@ -30,7 +30,7 @@ class Chef def search_cookbook(query, items=10, start=0, cookbook_collection={}) cookbooks_url = "https://supermarket.chef.io/api/v1/search?q=#{query}&items=#{items}&start=#{start}" - cr = noauth_rest.get_rest(cookbooks_url) + cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook end diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index beb98b71b8..043ca84a58 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -108,7 +108,7 @@ class Chef def get_category(cookbook_name) begin - data = noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") + data = noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") if !data["category"] && data["error_code"] ui.fatal("Received an error from Supermarket: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") exit(1) diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb index 6b65b62570..521a60eb36 100644 --- a/lib/chef/knife/cookbook_site_show.rb +++ b/lib/chef/knife/cookbook_site_show.rb @@ -31,15 +31,15 @@ class Chef def get_cookbook_data case @name_args.length when 1 - noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") + noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") when 2 - noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}") + noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}") end end def get_cookbook_list(items=10, start=0, cookbook_collection={}) cookbooks_url = "https://supermarket.chef.io/api/v1/cookbooks?items=#{items}&start=#{start}" - cr = noauth_rest.get_rest(cookbooks_url) + cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook end diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb index 77bb18322c..0c196c328a 100644 --- a/lib/chef/knife/cookbook_site_unshare.rb +++ b/lib/chef/knife/cookbook_site_unshare.rb @@ -41,7 +41,7 @@ class Chef confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}" begin - rest.delete_rest "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}" + rest.delete "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}" rescue Net::HTTPServerException => e raise e unless e.message =~ /Forbidden/ ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it." diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index f8a7619a8a..1becad88b9 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -51,7 +51,7 @@ class Chef # create the data bag begin - rest.post_rest("data", { "name" => @data_bag_name }) + rest.post("data", { "name" => @data_bag_name }) ui.info("Created data_bag[#{@data_bag_name}]") rescue Net::HTTPServerException => e raise unless e.to_s =~ /^409/ @@ -68,7 +68,7 @@ class Chef output end) item.data_bag(@data_bag_name) - rest.post_rest("data/#{@data_bag_name}", item) + rest.post("data/#{@data_bag_name}", item) end end end diff --git a/lib/chef/knife/data_bag_delete.rb b/lib/chef/knife/data_bag_delete.rb index 575e9d604d..a3215d4c54 100644 --- a/lib/chef/knife/data_bag_delete.rb +++ b/lib/chef/knife/data_bag_delete.rb @@ -32,11 +32,11 @@ class Chef def run if @name_args.length == 2 delete_object(Chef::DataBagItem, @name_args[1], "data_bag_item") do - rest.delete_rest("data/#{@name_args[0]}/#{@name_args[1]}") + rest.delete("data/#{@name_args[0]}/#{@name_args[1]}") end elsif @name_args.length == 1 delete_object(Chef::DataBag, @name_args[0], "data_bag") do - rest.delete_rest("data/#{@name_args[0]}") + rest.delete("data/#{@name_args[0]}") end else show_usage diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 6ef4b33f59..88c5669508 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -65,7 +65,7 @@ class Chef item_to_save = edited_item end - rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save) + rest.put("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save) stdout.puts("Saved data_bag_item[#{@name_args[1]}]") ui.output(edited_item) if config[:print_after] end diff --git a/lib/chef/knife/environment_compare.rb b/lib/chef/knife/environment_compare.rb index 792ec444ea..54f011f323 100644 --- a/lib/chef/knife/environment_compare.rb +++ b/lib/chef/knife/environment_compare.rb @@ -57,7 +57,7 @@ class Chef end # Get all cookbooks so we can compare them all - cookbooks = rest.get_rest("/cookbooks?num_versions=1") if config[:all] + cookbooks = rest.get("/cookbooks?num_versions=1") if config[:all] # display matrix view of in the requested format. if config[:format] == 'summary' diff --git a/lib/chef/knife/index_rebuild.rb b/lib/chef/knife/index_rebuild.rb index 4b9fcdd159..95b0dcaffb 100644 --- a/lib/chef/knife/index_rebuild.rb +++ b/lib/chef/knife/index_rebuild.rb @@ -38,7 +38,7 @@ class Chef else deprecated_server_message nag - output rest.post_rest("/search/reindex", {}) + output rest.post("/search/reindex", {}) end end @@ -50,7 +50,7 @@ class Chef # for a node we know won't exist; the 404 response that comes # back will give us what we want dummy_node = "knife_index_rebuild_test_#{rand(1000000)}" - rest.get_rest("/nodes/#{dummy_node}") + rest.get("/nodes/#{dummy_node}") rescue Net::HTTPServerException => exception r = exception.response parse_api_info(r) diff --git a/lib/chef/knife/raw.rb b/lib/chef/knife/raw.rb index 601cfcef9b..de8742deb9 100644 --- a/lib/chef/knife/raw.rb +++ b/lib/chef/knife/raw.rb @@ -1,4 +1,5 @@ require 'chef/knife' +require 'chef/http' class Chef class Knife diff --git a/lib/chef/knife/recipe_list.rb b/lib/chef/knife/recipe_list.rb index ed7d2a9509..46ad619f1d 100644 --- a/lib/chef/knife/recipe_list.rb +++ b/lib/chef/knife/recipe_list.rb @@ -22,7 +22,7 @@ class Chef::Knife::RecipeList < Chef::Knife banner "knife recipe list [PATTERN]" def run - recipes = rest.get_rest('cookbooks/_recipes') + recipes = rest.get('cookbooks/_recipes') if pattern = @name_args.first recipes = recipes.grep(Regexp.new(pattern)) end diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb index 1a61b035cb..e649c01ef4 100644 --- a/lib/chef/knife/status.rb +++ b/lib/chef/knife/status.rb @@ -76,7 +76,7 @@ class Chef time = Time.now.to_i # AND NOT is not valid lucene syntax, so don't use append_to_query @query << " " unless @query.empty? - @query << "NOT ohai_time:[#{(time - 60*60).to_s} TO #{time.to_s}]" + @query << "NOT ohai_time:[#{(time - 60*60)} TO #{time}]" end if config[:hide_by_mins] @@ -84,7 +84,7 @@ class Chef time = Time.now.to_i # AND NOT is not valid lucene syntax, so don't use append_to_query @query << " " unless @query.empty? - @query << "NOT ohai_time:[#{(time - hidemins*60).to_s} TO #{time.to_s}]" + @query << "NOT ohai_time:[#{(time - hidemins*60)} TO #{time}]" end @query = @query.empty? ? "*:*" : @query diff --git a/lib/chef/mixin/securable.rb b/lib/chef/mixin/securable.rb index aaedf0b9ba..396243693e 100644 --- a/lib/chef/mixin/securable.rb +++ b/lib/chef/mixin/securable.rb @@ -112,7 +112,7 @@ class Chef # equivalent to something like: # def rights(permissions=nil, principals=nil, args_hash=nil) define_method(name) do |permissions=nil, principals=nil, args_hash=nil| - rights = self.instance_variable_get("@#{name.to_s}".to_sym) + rights = self.instance_variable_get("@#{name}".to_sym) unless permissions.nil? input = { :permissions => permissions, diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 0c13e5474a..f6c7d68f74 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -27,7 +27,7 @@ require 'chef/mixin/deep_merge' require 'chef/dsl/include_attribute' require 'chef/dsl/platform_introspection' require 'chef/environment' -require 'chef/rest' +require 'chef/server_api' require 'chef/run_list' require 'chef/node/attribute' require 'chef/mash' @@ -99,10 +99,10 @@ class Chef # for saving node data we use validate_utf8: false which will not # raise an exception on bad utf8 data, but will replace the bad # characters and render valid JSON. - @chef_server_rest ||= Chef::REST.new( + @chef_server_rest ||= Chef::ServerAPI.new( Chef::Config[:chef_server_url], - Chef::Config[:node_name], - Chef::Config[:client_key], + client_name: Chef::Config[:node_name], + signing_key_filename: Chef::Config[:client_key], validate_utf8: false, ) end @@ -390,7 +390,7 @@ class Chef if attrs.key?("recipes") || attrs.key?("run_list") raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only." end - Chef::Log.info("Setting the run_list to #{new_run_list.to_s} from CLI options") + Chef::Log.info("Setting the run_list to #{new_run_list} from CLI options") run_list(new_run_list) end attrs @@ -532,6 +532,11 @@ class Chef # Create a Chef::Node from JSON def self.json_create(o) + from_hash(o) + end + + def self.from_hash(o) + return o if o.kind_of? Chef::Node node = new node.name(o["name"]) node.chef_environment(o["chef_environment"]) @@ -561,7 +566,7 @@ class Chef Chef::Search::Query.new.search(:node, "chef_environment:#{environment}") {|n| response[n.name] = n unless n.nil?} response else - Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("environments/#{environment}/nodes") + Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("environments/#{environment}/nodes") end end @@ -569,11 +574,12 @@ class Chef if inflate response = Hash.new Chef::Search::Query.new.search(:node) do |n| + n = Chef::Node.from_hash(n) response[n.name] = n unless n.nil? end response else - Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes") + Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes") end end @@ -594,12 +600,12 @@ class Chef # Load a node by name def self.load(name) - Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes/#{name}") + from_hash(Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes/#{name}")) end # Remove this node via the REST API def destroy - chef_server_rest.delete_rest("nodes/#{name}") + chef_server_rest.delete("nodes/#{name}") end # Save this node via the REST API @@ -610,11 +616,11 @@ class Chef if Chef::Config[:why_run] Chef::Log.warn("In why-run mode, so NOT performing node save.") else - chef_server_rest.put_rest("nodes/#{name}", data_for_save) + chef_server_rest.put("nodes/#{name}", data_for_save) end rescue Net::HTTPServerException => e if e.response.code == "404" - chef_server_rest.post_rest("nodes", data_for_save) + chef_server_rest.post("nodes", data_for_save) # Chef Server before 12.3 rejects node JSON with 'policy_name' or # 'policy_group' keys, but 'policy_name' will be detected first. # Backcompat can be removed in 13.0 @@ -629,14 +635,14 @@ class Chef # Create the node via the REST API def create - chef_server_rest.post_rest("nodes", data_for_save) + chef_server_rest.post("nodes", data_for_save) self rescue Net::HTTPServerException => e # Chef Server before 12.3 rejects node JSON with 'policy_name' or # 'policy_group' keys, but 'policy_name' will be detected first. # Backcompat can be removed in 13.0 if e.response.code == "400" && e.response.body.include?("Invalid key policy_name") - chef_server_rest.post_rest("nodes", data_for_save_without_policyfile_attrs) + chef_server_rest.post("nodes", data_for_save_without_policyfile_attrs) else raise end @@ -663,10 +669,10 @@ class Chef def save_without_policyfile_attrs trimmed_data = data_for_save_without_policyfile_attrs - chef_server_rest.put_rest("nodes/#{name}", trimmed_data) + chef_server_rest.put("nodes/#{name}", trimmed_data) rescue Net::HTTPServerException => e raise e unless e.response.code == "404" - chef_server_rest.post_rest("nodes", trimmed_data) + chef_server_rest.post("nodes", trimmed_data) end def data_for_save_without_policyfile_attrs diff --git a/lib/chef/org.rb b/lib/chef/org.rb index 41d74b6186..81eca6a991 100644 --- a/lib/chef/org.rb +++ b/lib/chef/org.rb @@ -18,7 +18,7 @@ require 'chef/json_compat' require 'chef/mixin/params_validate' -require 'chef/rest' +require 'chef/server_api' class Chef class Org @@ -35,7 +35,7 @@ class Chef end def chef_rest - @chef_rest ||= Chef::REST.new(Chef::Config[:chef_server_root]) + @chef_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root]) end def name(arg=nil) @@ -74,18 +74,18 @@ class Chef def create payload = {:name => self.name, :full_name => self.full_name} - new_org = chef_rest.post_rest("organizations", payload) + new_org = chef_rest.post("organizations", payload) Chef::Org.from_hash(self.to_hash.merge(new_org)) end def update payload = {:name => self.name, :full_name => self.full_name} - new_org = chef_rest.put_rest("organizations/#{name}", payload) + new_org = chef_rest.put("organizations/#{name}", payload) Chef::Org.from_hash(self.to_hash.merge(new_org)) end def destroy - chef_rest.delete_rest("organizations/#{@name}") + chef_rest.delete("organizations/#{@name}") end def save @@ -102,13 +102,13 @@ class Chef def associate_user(username) request_body = {:user => username} - response = chef_rest.post_rest "organizations/#{@name}/association_requests", request_body + response = chef_rest.post "organizations/#{@name}/association_requests", request_body association_id = response["uri"].split("/").last - chef_rest.put_rest "users/#{username}/association_requests/#{association_id}", { :response => 'accept' } + chef_rest.put "users/#{username}/association_requests/#{association_id}", { :response => 'accept' } end def dissociate_user(username) - chef_rest.delete_rest "organizations/#{name}/users/#{username}" + chef_rest.delete "organizations/#{name}/users/#{username}" end # Class methods @@ -129,12 +129,12 @@ class Chef end def self.load(org_name) - response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("organizations/#{org_name}") + response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("organizations/#{org_name}") Chef::Org.from_hash(response) end def self.list(inflate=false) - orgs = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest('organizations') + orgs = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get('organizations') if inflate orgs.inject({}) do |org_map, (name, _url)| org_map[name] = Chef::Org.load(name) diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 9b511f0237..1de6d61029 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -53,7 +53,7 @@ class Chef begin version_constraint = Chef::VersionConstraint::Platform.new(platform_version) if version_constraint.include?(version) - Chef::Log.debug("Platform #{name.to_s} version #{version} found") + Chef::Log.debug("Platform #{name} version #{version} found") provider_map.merge!(provider) end rescue Chef::Exceptions::InvalidPlatformVersion diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb index c9842ba532..d4b3df748e 100644 --- a/lib/chef/policy_builder/dynamic.rb +++ b/lib/chef/policy_builder/dynamic.rb @@ -19,7 +19,6 @@ require 'forwardable' require 'chef/log' -require 'chef/rest' require 'chef/run_context' require 'chef/config' require 'chef/node' diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb index 848dd00684..870351b6fb 100644 --- a/lib/chef/policy_builder/expand_node_object.rb +++ b/lib/chef/policy_builder/expand_node_object.rb @@ -20,7 +20,7 @@ # require 'chef/log' -require 'chef/rest' +require 'chef/server_api' require 'chef/run_context' require 'chef/config' require 'chef/node' @@ -198,7 +198,12 @@ class Chef begin events.cookbook_resolution_start(@expanded_run_list_with_versions) cookbook_hash = api_service.post("environments/#{node.chef_environment}/cookbook_versions", - {:run_list => @expanded_run_list_with_versions}) + {:run_list => @expanded_run_list_with_versions}) + + cookbook_hash = cookbook_hash.inject({}) do |memo, (key, value)| + memo[key] = Chef::CookbookVersion.from_hash(value) + memo + end rescue Exception => e # TODO: wrap/munge exception to provide helpful error output events.cookbook_resolution_failed(@expanded_run_list_with_versions, e) @@ -257,7 +262,7 @@ class Chef end def api_service - @api_service ||= Chef::REST.new(config[:chef_server_url]) + @api_service ||= Chef::ServerAPI.new(config[:chef_server_url]) end def config diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index 3633110d6c..249bebbd98 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -20,10 +20,10 @@ # require 'chef/log' -require 'chef/rest' require 'chef/run_context' require 'chef/config' require 'chef/node' +require 'chef/server_api' class Chef module PolicyBuilder @@ -455,7 +455,7 @@ class Chef # @api private def http_api - @api_service ||= Chef::REST.new(config[:chef_server_url]) + @api_service ||= Chef::ServerAPI.new(config[:chef_server_url]) end # @api private diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 68bc8d78bd..c53f4a6991 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -94,7 +94,7 @@ class Chef end def load_current_resource - raise Chef::Exceptions::Override, "You must override load_current_resource in #{self.to_s}" + raise Chef::Exceptions::Override, "You must override load_current_resource in #{self}" end def define_resource_requirements @@ -104,7 +104,7 @@ class Chef end def action_nothing - Chef::Log.debug("Doing nothing for #{@new_resource.to_s}") + Chef::Log.debug("Doing nothing for #{@new_resource}") true end @@ -209,7 +209,7 @@ class Chef else specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ") end - Chef::Log.debug("Skipping update of #{new_resource.to_s}: has not changed any of the specified properties #{properties_str}.") + Chef::Log.debug("Skipping update of #{new_resource}: has not changed any of the specified properties #{properties_str}.") return false end diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index c59200e717..fe6b288eda 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -365,7 +365,7 @@ class Chef end def release_slug - raise Chef::Exceptions::Override, "You must override release_slug in #{self.to_s}" + raise Chef::Exceptions::Override, "You must override release_slug in #{self}" end def install_gems diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb index fd25a14ea5..432fe3987f 100644 --- a/lib/chef/provider/dsc_resource.rb +++ b/lib/chef/provider/dsc_resource.rb @@ -144,7 +144,7 @@ class Chef def invoke_resource(method, output_format=:object) properties = translate_type(@new_resource.properties) - switches = "-Method #{method.to_s} -Name #{@new_resource.resource}"\ + switches = "-Method #{method} -Name #{@new_resource.resource}"\ " -Property #{properties} -Verbose" if module_name != :none switches += " -Module #{module_name}" diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index b2432132b7..8205910d5a 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -99,7 +99,7 @@ class Chef configuration_document = generate_configuration_document(config_directory, configuration_flags) @operations[operation].call(config_manager, configuration_document, shellout_flags) rescue Exception => e - Chef::Log.error("DSC operation failed: #{e.message.to_s}") + Chef::Log.error("DSC operation failed: #{e.message}") raise e ensure ::FileUtils.rm_rf(config_directory) diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb index cf75ff7d85..8bd7b3e6ca 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/env.rb @@ -48,7 +48,7 @@ class Chef end def env_value(key_name) - raise Chef::Exceptions::Env, "#{self.to_s} provider does not implement env_value!" + raise Chef::Exceptions::Env, "#{self} provider does not implement env_value!" end def env_key_exists(key_name) @@ -141,11 +141,11 @@ class Chef end def create_env - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :#{@new_resource.action}" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :#{@new_resource.action}" end def delete_env - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :delete" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :delete" end def modify_env diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb index 92bb8cb225..d0de538e58 100644 --- a/lib/chef/provider/group/aix.rb +++ b/lib/chef/provider/group/aix.rb @@ -72,7 +72,7 @@ class Chef { :gid => "id" }.sort { |a,b| a[0] <=> b[0] }.each do |field, option| if @current_resource.send(field) != @new_resource.send(field) if @new_resource.send(field) - Chef::Log.debug("#{@new_resource} setting #{field.to_s} to #{@new_resource.send(field)}") + Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}") opts << " '#{option}=#{@new_resource.send(field)}'" end end diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb index cb480aab54..b2ca8fd4fe 100644 --- a/lib/chef/provider/group/groupadd.rb +++ b/lib/chef/provider/group/groupadd.rb @@ -96,15 +96,15 @@ class Chef end def add_member(member) - raise Chef::Exceptions::Group, "you must override add_member in #{self.to_s}" + raise Chef::Exceptions::Group, "you must override add_member in #{self}" end def remove_member(member) - raise Chef::Exceptions::Group, "you must override remove_member in #{self.to_s}" + raise Chef::Exceptions::Group, "you must override remove_member in #{self}" end def set_members(members) - raise Chef::Exceptions::Group, "you must override set_members in #{self.to_s}" + raise Chef::Exceptions::Group, "you must override set_members in #{self}" end # Little bit of magic as per Adam's useradd provider to pull the assign the command line flags @@ -117,7 +117,7 @@ class Chef if @current_resource.send(field) != @new_resource.send(field) if @new_resource.send(field) opts << " #{option} '#{@new_resource.send(field)}'" - Chef::Log.debug("#{@new_resource} set #{field.to_s} to #{@new_resource.send(field)}") + Chef::Log.debug("#{@new_resource} set #{field} to #{@new_resource.send(field)}") end end end diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb index d78d42d6e1..e3d960280d 100644 --- a/lib/chef/provider/group/usermod.rb +++ b/lib/chef/provider/group/usermod.rb @@ -41,13 +41,13 @@ class Chef requirements.assert(:modify, :manage) do |a| a.assertion { @new_resource.members.empty? || @new_resource.append } - a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self.to_s}, must set append true in group" + a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self}, must set append true in group" # No whyrun alternative - this action is simply not supported. end requirements.assert(:all_actions) do |a| a.assertion { @new_resource.excluded_members.empty? } - a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self.to_s}" + a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self}" # No whyrun alternative - this action is simply not supported. end end @@ -62,7 +62,7 @@ class Chef add_member(member) end else - raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self.to_s}" + raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self}" end end @@ -73,7 +73,7 @@ class Chef def remove_member(member) # This provider only supports adding members with # append. This function should never be called. - raise Chef::Exceptions::UnsupportedAction, "Removing members members is not supported by #{self.to_s}" + raise Chef::Exceptions::UnsupportedAction, "Removing members members is not supported by #{self}" end def append_flags diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb index c811c13cdf..a5f9b0bb29 100644 --- a/lib/chef/provider/link.rb +++ b/lib/chef/provider/link.rb @@ -80,7 +80,7 @@ class Chef true end end - a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type.to_s} link." + a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type} link." a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created") end end diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb index 6bdfd5b867..dc0382c689 100644 --- a/lib/chef/provider/mount.rb +++ b/lib/chef/provider/mount.rb @@ -118,12 +118,12 @@ class Chef # should actually check if the filesystem is mounted (not just return current_resource) and return true/false def mounted? - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not implement #mounted?" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mounted?" end # should check new_resource against current_resource to see if mount options need updating, returns true/false def mount_options_unchanged? - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not implement #mount_options_unchanged?" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mount_options_unchanged?" end # @@ -134,28 +134,28 @@ class Chef # should implement mounting of the filesystem, raises if action does not succeed def mount_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :mount" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :mount" end # should implement unmounting of the filesystem, raises if action does not succeed def umount_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :umount" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :umount" end # should implement remounting of the filesystem (via a -o remount or some other atomic-ish action that isn't # simply a umount/mount style remount), raises if action does not succeed def remount_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remount" end # should implement enabling of the filesystem (e.g. in /etc/fstab), raises if action does not succeed def enable_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :enable" end # should implement disabling of the filesystem (e.g. in /etc/fstab), raises if action does not succeed def disable_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :disable" end private diff --git a/lib/chef/provider/osx_profile.rb b/lib/chef/provider/osx_profile.rb index ee355fd38e..085397efea 100644 --- a/lib/chef/provider/osx_profile.rb +++ b/lib/chef/provider/osx_profile.rb @@ -121,7 +121,7 @@ class Chef # file must exist in cookbook if new_profile.end_with?('.mobileconfig') unless cookbook_file_available?(new_profile) - error_string = "#{self.to_s}: '#{new_profile}' not found in cookbook" + error_string = "#{self}: '#{new_profile}' not found in cookbook" raise Chef::Exceptions::FileNotFound, error_string end cookbook_profile = cache_cookbook_profile(new_profile) diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index 8e98a103bf..2238dc8654 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -228,27 +228,27 @@ class Chef end def install_package(name, version) - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :install" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :install" end def upgrade_package(name, version) - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :upgrade" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :upgrade" end def remove_package(name, version) - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remove" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remove" end def purge_package(name, version) - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :purge" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :purge" end def preseed_package(file) - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support pre-seeding package install/upgrade instructions" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support pre-seeding package install/upgrade instructions" end def reconfig_package(name, version) - raise( Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reconfig" ) + raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" ) end # used by subclasses. deprecated. use #a_to_s instead. diff --git a/lib/chef/provider/package/chocolatey.rb b/lib/chef/provider/package/chocolatey.rb new file mode 100644 index 0000000000..7a9173e077 --- /dev/null +++ b/lib/chef/provider/package/chocolatey.rb @@ -0,0 +1,246 @@ +# +# Copyright:: Copyright (c) 2015 Chef Software, 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/provider/package' +require 'chef/resource/chocolatey_package' +require 'chef/mixin/powershell_out' + +class Chef + class Provider + class Package + class Chocolatey < Chef::Provider::Package + include Chef::Mixin::PowershellOut + + provides :chocolatey_package, os: "windows" + + # Declare that our arguments should be arrays + use_multipackage_api + + # Responsible for building the current_resource. + # + # @return [Chef::Resource::ChocolateyPackage] the current_resource + def load_current_resource + @current_resource = Chef::Resource::ChocolateyPackage.new(new_resource.name) + current_resource.package_name(new_resource.package_name) + current_resource.version(build_current_versions) + current_resource + end + + def define_resource_requirements + super + + # Chocolatey source attribute points to an alternate feed + # and not a package specific alternate source like other providers + # so we want to assert candidates exist for the alternate source + requirements.assert(:upgrade, :install) do |a| + a.assertion { candidates_exist_for_all_uninstalled? } + a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}") + a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured") + end + end + + # Lazy initializer for candidate_version. A nil value means that there is no candidate + # version and the package is not installable (generally an error). + # + # @return [Array] list of candidate_versions indexed same as new_resource.package_name/version + def candidate_version + @candidate_version ||= build_candidate_versions + end + + # Install multiple packages via choco.exe + # + # @param names [Array<String>] array of package names to install + # @param versions [Array<String>] array of versions to install + def install_package(names, versions) + name_versions_to_install = desired_name_versions.select { |n, v| names.include?(n) } + + name_nil_versions = name_versions_to_install.select { |n,v| v.nil? } + name_has_versions = name_versions_to_install.reject { |n,v| v.nil? } + + # choco does not support installing multiple packages with version pins + name_has_versions.each do |name, version| + choco_command("install -y -version", version, cmd_args, name) + end + + # but we can do all the ones without version pins at once + unless name_nil_versions.empty? + cmd_names = name_nil_versions.keys + choco_command("install -y", cmd_args, *cmd_names) + end + end + + # Upgrade multiple packages via choco.exe + # + # @param names [Array<String>] array of package names to install + # @param versions [Array<String>] array of versions to install + def upgrade_package(names, versions) + name_versions_to_install = desired_name_versions.select { |n, v| names.include?(n) } + + name_nil_versions = name_versions_to_install.select { |n,v| v.nil? } + name_has_versions = name_versions_to_install.reject { |n,v| v.nil? } + + # choco does not support installing multiple packages with version pins + name_has_versions.each do |name, version| + choco_command("upgrade -y -version", version, cmd_args, name) + end + + # but we can do all the ones without version pins at once + unless name_nil_versions.empty? + cmd_names = name_nil_versions.keys + choco_command("upgrade -y", cmd_args, *cmd_names) + end + end + + # Remove multiple packages via choco.exe + # + # @param names [Array<String>] array of package names to install + # @param versions [Array<String>] array of versions to install + def remove_package(names, versions) + choco_command("uninstall -y", cmd_args, *names) + end + + # Support :uninstall as an action in order for users to easily convert + # from the `chocolatey` provider in the cookbook. It is, however, + # already deprecated. + def action_uninstall + Chef::Log.deprecation "The use of action :uninstall on the chocolatey_package provider is deprecated, please use :remove" + action_remove + end + + # Choco does not have dpkg's distinction between purge and remove + alias_method :purge_package, :remove_package + + # Override the superclass check. The semantics for our new_resource.source is not files to + # install from, but like the rubygem provider's sources which are more like repos. + def check_resource_semantics! + end + + private + + # Magic to find where chocolatey is installed in the system, and to + # return the full path of choco.exe + # + # @return [String] full path of choco.exe + def choco_exe + @choco_exe ||= + ::File.join( + powershell_out!( + "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')" + ).stdout.chomp, + 'bin', + 'choco.exe' + ) + end + + # Helper to dispatch a choco command through shell_out using the timeout + # set on the new resource, with nice command formatting. + # + # @param args [String] variable number of string arguments + # @return [Mixlib::ShellOut] object returned from shell_out! + def choco_command(*args) + shell_out_with_timeout!(args_to_string(choco_exe, *args)) + end + + # Use the available_packages Hash helper to create an array suitable for + # using in candidate_version + # + # @return [Array] list of candidate_version, same index as new_resource.package_name/version + def build_candidate_versions + new_resource.package_name.map do |package_name| + available_packages[package_name.downcase] + end + end + + # Use the installed_packages Hash helper to create an array suitable for + # using in current_resource.version + # + # @return [Array] list of candidate_version, same index as new_resource.package_name/version + def build_current_versions + new_resource.package_name.map do |package_name| + installed_packages[package_name.downcase] + end + end + + # Helper to construct Hash of names-to-versions, requested on the new_resource. + # If new_resource.version is nil, then all values will be nil. + # + # @return [Hash] Mapping of requested names to versions + def desired_name_versions + desired_versions = new_resource.version || new_resource.package_name.map { nil } + Hash[*new_resource.package_name.zip(desired_versions).flatten] + end + + # Helper to construct optional args out of new_resource + # + # @return [String] options from new_resource or empty string + def cmd_args + cmd_args = [ new_resource.options ] + cmd_args.push( "-source #{new_resource.source}" ) if new_resource.source + args_to_string(*cmd_args) + end + + # Helper to nicely convert variable string args into a single command line. It + # will compact nulls or empty strings and join arguments with single spaces, without + # introducing any double-spaces for missing args. + # + # @param args [String] variable number of string arguments + # @return [String] nicely concatenated string or empty string + def args_to_string(*args) + args.reject {|i| i.nil? || i == "" }.join(" ") + end + + # Available packages in chocolatey as a Hash of names mapped to versions + # If pinning a package to a specific version, filter out all non matching versions + # (names are downcased for case-insensitive matching) + # + # @return [Hash] name-to-version mapping of available packages + def available_packages + @available_packages ||= + begin + cmd = [ "list -ar #{package_name_array.join ' '}" ] + cmd.push( "-source #{new_resource.source}" ) if new_resource.source + parse_list_output(*cmd).reject do |name,version| + desired_name_versions[name] && desired_name_versions[name] != version + end + end + end + + # Installed packages in chocolatey as a Hash of names mapped to versions + # (names are downcased for case-insensitive matching) + # + # @return [Hash] name-to-version mapping of installed packages + def installed_packages + @installed_packages ||= parse_list_output("list -l -r") + end + + # Helper to convert choco.exe list output to a Hash + # (names are downcased for case-insenstive matching) + # + # @param cmd [String] command to run + # @return [String] list output converted to ruby Hash + def parse_list_output(*args) + hash = {} + choco_command(*args).stdout.each_line do |line| + name, version = line.split('|') + hash[name.downcase] = version.chomp + end + hash + end + end + end + end +end diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index 729f755b2a..0ea739e8d1 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -137,7 +137,7 @@ class Chef spec.version else # This is probably going to end badly... - logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency.to_s}" } + logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency}" } nil end end @@ -445,7 +445,7 @@ class Chef logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" } gemspec else - logger.debug { "#{@new_resource} no installed version found for #{gem_dependency.to_s}"} + logger.debug { "#{@new_resource} no installed version found for #{gem_dependency}"} nil end end diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb index fc90079846..43972c933c 100644 --- a/lib/chef/provider/package/windows.rb +++ b/lib/chef/provider/package/windows.rb @@ -109,7 +109,7 @@ class Chef io.seek(io.tell() - overlap) end end - end + end # if file is named 'setup.exe' assume installshield if basename == 'setup.exe' diff --git a/lib/chef/provider/package/windows/exe.rb b/lib/chef/provider/package/windows/exe.rb index 3758b8b1e2..8f6cd1efa5 100644 --- a/lib/chef/provider/package/windows/exe.rb +++ b/lib/chef/provider/package/windows/exe.rb @@ -48,7 +48,7 @@ class Chef end def package_version - new_resource.version || install_file_version + new_resource.version end def install_package @@ -97,18 +97,6 @@ class Chef end end - def install_file_version - @install_file_version ||= begin - if !new_resource.source.nil? && ::File.exist?(new_resource.source) - version_info = Chef::ReservedNames::Win32::File.version_info(new_resource.source) - file_version = version_info.FileVersion || version_info.ProductVersion - file_version == '' ? nil : file_version - else - nil - end - end - end - # http://unattended.sourceforge.net/installers.php def unattended_flags case installer_type diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb index 4f450ce333..01193907a1 100644 --- a/lib/chef/provider/remote_file/content.rb +++ b/lib/chef/provider/remote_file/content.rb @@ -55,7 +55,7 @@ class Chef end raw_file = grab_file_from_uri(uri) rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError => e - Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e.to_s}") + Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e}") if source = sources.shift Chef::Log.info("#{@new_resource} trying to download from another mirror") retry diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index d72d135b09..311bb1082e 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -64,7 +64,7 @@ class Chef def define_resource_requirements requirements.assert(:reload) do |a| a.assertion { supports[:reload] || @new_resource.reload_command } - a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload" + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload" # if a service is not declared to support reload, that won't # typically change during the course of a run - so no whyrun # alternative here. @@ -143,27 +143,27 @@ class Chef end def enable_service - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :enable" end def disable_service - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :disable" end def start_service - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :start" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :start" end def stop_service - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :stop" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :stop" end def restart_service - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :restart" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :restart" end def reload_service - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload" + raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload" end protected diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb index 0a8fca4262..9c733da493 100644 --- a/lib/chef/provider/service/macosx.rb +++ b/lib/chef/provider/service/macosx.rb @@ -74,7 +74,7 @@ class Chef def define_resource_requirements requirements.assert(:reload) do |a| - a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload" + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload" end requirements.assert(:all_actions) do |a| diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb index d295513b42..bb3d819b3d 100644 --- a/lib/chef/provider/service/simple.rb +++ b/lib/chef/provider/service/simple.rb @@ -58,21 +58,21 @@ class Chef shared_resource_requirements requirements.assert(:start) do |a| a.assertion { @new_resource.start_command } - a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that start_command be set" + a.failure_message Chef::Exceptions::Service, "#{self} requires that start_command be set" end requirements.assert(:stop) do |a| a.assertion { @new_resource.stop_command } - a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that stop_command be set" + a.failure_message Chef::Exceptions::Service, "#{self} requires that stop_command be set" end requirements.assert(:restart) do |a| a.assertion { @new_resource.restart_command || ( @new_resource.start_command && @new_resource.stop_command ) } - a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires a restart_command or both start_command and stop_command be set in order to perform a restart" + a.failure_message Chef::Exceptions::Service, "#{self} requires a restart_command or both start_command and stop_command be set in order to perform a restart" end requirements.assert(:reload) do |a| a.assertion { @new_resource.reload_command } - a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} requires a reload_command be set in order to perform a reload" + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} requires a reload_command be set in order to perform a reload" end requirements.assert(:all_actions) do |a| diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb index 355ffafc2a..f5c370246c 100644 --- a/lib/chef/provider/service/windows.rb +++ b/lib/chef/provider/service/windows.rb @@ -47,6 +47,8 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service TIMEOUT = 60 + SERVICE_RIGHT = 'SeServiceLogonRight' + def whyrun_supported? false end @@ -78,10 +80,10 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service Win32::Service.configure(new_config) Chef::Log.info "#{@new_resource} configured with #{new_config.inspect}" - # it would be nice to check if the user already has the logon privilege, but that turns out to be - # nontrivial. if new_config.has_key?(:service_start_name) - grant_service_logon(new_config[:service_start_name]) + unless Chef::ReservedNames::Win32::Security.get_account_right(canonicalize_username(new_config[:service_start_name])).include?(SERVICE_RIGHT) + grant_service_logon(new_config[:service_start_name]) + end end state = current_state @@ -236,62 +238,15 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service end private - def make_policy_text(username) - text = <<-EOS -[Unicode] -Unicode=yes -[Privilege Rights] -SeServiceLogonRight = \\\\#{canonicalize_username(username)},*S-1-5-80-0 -[Version] -signature="$CHICAGO$" -Revision=1 -EOS - end - - def grant_logfile_name(username) - Chef::Util::PathHelper.canonical_path("#{Dir.tmpdir}/logon_grant-#{clean_username_for_path(username)}-#{$$}.log", prefix=false) - end - - def grant_policyfile_name(username) - Chef::Util::PathHelper.canonical_path("#{Dir.tmpdir}/service_logon_policy-#{clean_username_for_path(username)}-#{$$}.inf", prefix=false) - end - - def grant_dbfile_name(username) - "#{ENV['TEMP']}\\secedit.sdb" - end - def grant_service_logon(username) - logfile = grant_logfile_name(username) - policy_file = ::File.new(grant_policyfile_name(username), 'w') - policy_text = make_policy_text(username) - dbfile = grant_dbfile_name(username) # this is just an audit file. - begin - Chef::Log.debug "Policy file text:\n#{policy_text}" - policy_file.puts(policy_text) - policy_file.close # need to flush the buffer. - - # it would be nice to do this with APIs instead, but the LSA_* APIs are - # particularly onerous and life is short. - cmd = %Q{secedit.exe /configure /db "#{dbfile}" /cfg "#{policy_file.path}" /areas USER_RIGHTS SECURITYPOLICY SERVICES /log "#{logfile}"} - Chef::Log.debug "Granting logon-as-service privilege with: #{cmd}" - runner = shell_out(cmd) - - if runner.exitstatus != 0 - Chef::Log.fatal "Logon-as-service grant failed with output: #{runner.stdout}" - raise Chef::Exceptions::Service, <<-EOS -Logon-as-service grant failed with policy file #{policy_file.path}. -You can look at #{logfile} for details, or do `secedit /analyze #{dbfile}`. -The failed command was `#{cmd}`. -EOS - end - - Chef::Log.info "Grant logon-as-service to user '#{username}' successful." - - ::File.delete(dbfile) rescue nil - ::File.delete(policy_file) - ::File.delete(logfile) rescue nil # logfile is not always present at end. + Chef::ReservedNames::Win32::Security.add_account_right(canonicalize_username(username), SERVICE_RIGHT) + rescue Chef::Exceptions::Win32APIError => err + Chef::Log.fatal "Logon-as-service grant failed with output: #{err}" + raise Chef::Exceptions::Service, "Logon-as-service grant failed for #{username}: #{err}" end + + Chef::Log.info "Grant logon-as-service to user '#{username}' successful." true end @@ -300,8 +255,6 @@ EOS username.gsub(/[\/\\. ]+/, '_') end - # the security policy file only seems to accept \\username, so fix .\username or .\\username. - # TODO: this probably has to be fixed to handle various valid Windows names correctly. def canonicalize_username(username) username.sub(/^\.?\\+/, '') end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index f5e7a0f989..b4eeabd48d 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. +# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,6 +57,7 @@ require 'chef/provider/whyrun_safe_ruby_block' require 'chef/provider/env/windows' require 'chef/provider/package/apt' +require 'chef/provider/package/chocolatey' require 'chef/provider/package/dpkg' require 'chef/provider/package/easy_install' require 'chef/provider/package/freebsd/port' diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 4aec8cf1f6..6b56a0107a 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -658,7 +658,7 @@ class Chef def inspect ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS - ivars.inject("<#{to_s}") do |str, ivar| + ivars.inject("<#{self}") do |str, ivar| str << " #{ivar}: #{instance_variable_get(ivar).inspect}" end << ">" end @@ -1332,7 +1332,7 @@ class Chef if enclosing_provider && enclosing_provider.respond_to?(method_symbol) enclosing_provider.send(method_symbol, *args, &block) else - raise NoMethodError, "undefined method `#{method_symbol.to_s}' for #{self.class.to_s}" + raise NoMethodError, "undefined method `#{method_symbol}' for #{self.class}" end end diff --git a/lib/chef/resource/chocolatey_package.rb b/lib/chef/resource/chocolatey_package.rb new file mode 100644 index 0000000000..57a6bd2357 --- /dev/null +++ b/lib/chef/resource/chocolatey_package.rb @@ -0,0 +1,39 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008-2015 Chef Software, 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/resource/package' + +class Chef + class Resource + class ChocolateyPackage < Chef::Resource::Package + + provides :chocolatey_package, os: "windows" + + allowed_actions :install, :upgrade, :remove, :uninstall, :purge, :reconfig + + def initialize(name, run_context=nil) + super + @resource_name = :chocolatey_package + end + + property :package_name, [String, Array], coerce: proc { |x| [x].flatten } + + property :version, [String, Array], coerce: proc { |x| [x].flatten } + end + end +end diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb index 93cf41bc37..70c2dd89a3 100644 --- a/lib/chef/resource/cron.rb +++ b/lib/chef/resource/cron.rb @@ -123,7 +123,7 @@ class Chef end begin error_message = "You provided '#{arg}' as a weekday, acceptable values are " - error_message << Provider::Cron::WEEKDAY_SYMBOLS.map {|sym| ":#{sym.to_s}"}.join(', ') + error_message << Provider::Cron::WEEKDAY_SYMBOLS.map {|sym| ":#{sym}"}.join(', ') error_message << " and a string in crontab format" if (arg.is_a?(Symbol) && !Provider::Cron::WEEKDAY_SYMBOLS.include?(arg)) || (!arg.is_a?(Symbol) && integerize(arg) > 7) || diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb index 0664dc7d7f..6ffa0ca248 100644 --- a/lib/chef/resource/dsc_resource.rb +++ b/lib/chef/resource/dsc_resource.rb @@ -1,120 +1,120 @@ -#
-# Author:: Adam Edwards (<adamed@getchef.com>)
-#
-# Copyright:: 2014, Opscode, Inc.
-#
-# 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/dsl/powershell'
-
-class Chef
- class Resource
- class DscResource < Chef::Resource
- provides :dsc_resource, os: "windows"
-
- # This class will check if the object responds to
- # to_text. If it does, it will call that as opposed
- # to inspect. This is useful for properties that hold
- # objects such as PsCredential, where we do not want
- # to dump the actual ivars
- class ToTextHash < Hash
- def to_text
- descriptions = self.map do |(property, obj)|
- obj_text = if obj.respond_to?(:to_text)
- obj.to_text
- else
- obj.inspect
- end
- "#{property}=>#{obj_text}"
- end
- "{#{descriptions.join(', ')}}"
- end
- end
-
- include Chef::DSL::Powershell
-
- default_action :run
-
- def initialize(name, run_context)
- super
- @properties = ToTextHash.new
- @resource = nil
- @reboot_action = :nothing
- end
-
- def resource(value=nil)
- if value
- @resource = value
- else
- @resource
- end
- end
-
- def module_name(value=nil)
- if value
- @module_name = value
- else
- @module_name
- end
- end
-
- def property(property_name, value=nil)
- if not property_name.is_a?(Symbol)
- raise TypeError, "A property name of type Symbol must be specified, '#{property_name.to_s}' of type #{property_name.class.to_s} was given"
- end
-
- if value.nil?
- value_of(@properties[property_name])
- else
- @properties[property_name] = value
- end
- end
-
- def properties
- @properties.reduce({}) do |memo, (k, v)|
- memo[k] = value_of(v)
- memo
- end
- end
-
- # This property takes the action message for the reboot resource
- # If the set method of the DSC resource indicate that a reboot
- # is necessary, reboot_action provides the mechanism for a reboot to
- # be requested.
- def reboot_action(value=nil)
- if value
- @reboot_action = value
- else
- @reboot_action
- end
- end
-
- def timeout(arg=nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => [ Integer ]
- )
- end
- private
-
- def value_of(value)
- if value.is_a?(DelayedEvaluator)
- value.call
- else
- value
- end
- end
- end
- end
-end
+# +# Author:: Adam Edwards (<adamed@getchef.com>) +# +# Copyright:: 2014, Opscode, Inc. +# +# 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/dsl/powershell' + +class Chef + class Resource + class DscResource < Chef::Resource + provides :dsc_resource, os: "windows" + + # This class will check if the object responds to + # to_text. If it does, it will call that as opposed + # to inspect. This is useful for properties that hold + # objects such as PsCredential, where we do not want + # to dump the actual ivars + class ToTextHash < Hash + def to_text + descriptions = self.map do |(property, obj)| + obj_text = if obj.respond_to?(:to_text) + obj.to_text + else + obj.inspect + end + "#{property}=>#{obj_text}" + end + "{#{descriptions.join(', ')}}" + end + end + + include Chef::DSL::Powershell + + default_action :run + + def initialize(name, run_context) + super + @properties = ToTextHash.new + @resource = nil + @reboot_action = :nothing + end + + def resource(value=nil) + if value + @resource = value + else + @resource + end + end + + def module_name(value=nil) + if value + @module_name = value + else + @module_name + end + end + + def property(property_name, value=nil) + if not property_name.is_a?(Symbol) + raise TypeError, "A property name of type Symbol must be specified, '#{property_name}' of type #{property_name.class} was given" + end + + if value.nil? + value_of(@properties[property_name]) + else + @properties[property_name] = value + end + end + + def properties + @properties.reduce({}) do |memo, (k, v)| + memo[k] = value_of(v) + memo + end + end + + # This property takes the action message for the reboot resource + # If the set method of the DSC resource indicate that a reboot + # is necessary, reboot_action provides the mechanism for a reboot to + # be requested. + def reboot_action(value=nil) + if value + @reboot_action = value + else + @reboot_action + end + end + + def timeout(arg=nil) + set_or_return( + :timeout, + arg, + :kind_of => [ Integer ] + ) + end + private + + def value_of(value) + if value.is_a?(DelayedEvaluator) + value.call + else + value + end + end + end + end +end diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb index ba0bb08201..e7530ce628 100644 --- a/lib/chef/resource/file/verification.rb +++ b/lib/chef/resource/file/verification.rb @@ -28,7 +28,7 @@ class Chef # See RFC 027 for a full specification # # File verifications allow user-supplied commands a means of - # preventing file reosurce content deploys. Their intended use + # preventing file resource content deploys. Their intended use # is to verify the contents of a temporary file before it is # deployed onto the system. # diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb index 2bbd01d5aa..2dff4cde31 100644 --- a/lib/chef/resource/windows_script.rb +++ b/lib/chef/resource/windows_script.rb @@ -57,7 +57,7 @@ class Chef "cannot execute script with requested architecture 'i386' on Windows Nano Server" elsif ! node_supports_windows_architecture?(node, desired_architecture) raise Chef::Exceptions::Win32ArchitectureIncorrect, - "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'" + "cannot execute script with requested architecture '#{desired_architecture}' on a system with architecture '#{node_windows_architecture(node)}'" end end end diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb index cffabb6786..43350dda2b 100644 --- a/lib/chef/resource_definition.rb +++ b/lib/chef/resource_definition.rb @@ -62,7 +62,7 @@ class Chef end def to_s - "#{name.to_s}" + "#{name}" end end end diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 1175b0afb3..e2c71f7bd5 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -121,7 +121,7 @@ class Chef if reporting_enabled? begin resource_history_url = "reports/nodes/#{node_name}/runs" - server_response = @rest_client.post_rest(resource_history_url, {:action => :start, :run_id => run_id, + server_response = @rest_client.post(resource_history_url, {:action => :start, :run_id => run_id, :start_time => start_time.to_s}, headers) rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e handle_error_starting_run(e, resource_history_url) @@ -230,10 +230,9 @@ class Chef Chef::Log.debug run_data.inspect compressed_data = encode_gzip(Chef::JSONCompat.to_json(run_data)) Chef::Log.debug("Sending compressed run data...") - # Since we're posting compressed data we can not directly call post_rest which expects JSON - reporting_url = @rest_client.create_url(resource_history_url) + # Since we're posting compressed data we can not directly call post which expects JSON begin - @rest_client.raw_http_request(:POST, reporting_url, headers({'Content-Encoding' => 'gzip'}), compressed_data) + @rest_client.raw_request(:POST, resource_history_url, headers({'Content-Encoding' => 'gzip'}), compressed_data) rescue StandardError => e if e.respond_to? :response Chef::FileCache.store("failed-reporting-data.json", Chef::JSONCompat.to_json_pretty(run_data), 0640) diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index f699d95ace..cc5b9be8c6 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. +# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ require 'chef/resource/batch' require 'chef/resource/breakpoint' require 'chef/resource/cookbook_file' require 'chef/resource/chef_gem' +require 'chef/resource/chocolatey_package' require 'chef/resource/cron' require 'chef/resource/csh' require 'chef/resource/deploy' diff --git a/lib/chef/role.rb b/lib/chef/role.rb index c085d1d714..6984a8a245 100644 --- a/lib/chef/role.rb +++ b/lib/chef/role.rb @@ -24,6 +24,7 @@ require 'chef/mixin/from_file' require 'chef/run_list' require 'chef/mash' require 'chef/json_compat' +require 'chef/server_api' require 'chef/search/query' class Chef @@ -45,11 +46,11 @@ class Chef end def chef_server_rest - @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url]) + @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def name(arg=nil) @@ -170,6 +171,10 @@ class Chef # Create a Chef::Role from JSON def self.json_create(o) + from_hash(o) + end + + def self.from_hash(o) role = new role.name(o["name"]) role.description(o["description"]) @@ -199,42 +204,42 @@ class Chef end response else - chef_server_rest.get_rest("roles") + chef_server_rest.get("roles") end end # Load a role by name from the API def self.load(name) - chef_server_rest.get_rest("roles/#{name}") + from_hash(chef_server_rest.get("roles/#{name}")) end def environment(env_name) - chef_server_rest.get_rest("roles/#{@name}/environments/#{env_name}") + chef_server_rest.get("roles/#{@name}/environments/#{env_name}") end def environments - chef_server_rest.get_rest("roles/#{@name}/environments") + chef_server_rest.get("roles/#{@name}/environments") end # Remove this role via the REST API def destroy - chef_server_rest.delete_rest("roles/#{@name}") + chef_server_rest.delete("roles/#{@name}") end # Save this role via the REST API def save begin - chef_server_rest.put_rest("roles/#{@name}", self) + chef_server_rest.put("roles/#{@name}", self) rescue Net::HTTPServerException => e raise e unless e.response.code == "404" - chef_server_rest.post_rest("roles", self) + chef_server_rest.post("roles", self) end self end # Create the role via the REST API def create - chef_server_rest.post_rest("roles", self) + chef_server_rest.post("roles", self) self end @@ -258,7 +263,8 @@ class Chef if js_path && File.exists?(js_path) # from_json returns object.class => json_class in the JSON. - return Chef::JSONCompat.from_json(IO.read(js_path)) + hsh = Chef::JSONCompat.parse(IO.read(js_path)) + return from_hash(hsh) elsif rb_path && File.exists?(rb_path) role = Chef::Role.new role.name(name) diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb index 64e4326fb8..2f2d170446 100644 --- a/lib/chef/run_list/run_list_expansion.rb +++ b/lib/chef/run_list/run_list_expansion.rb @@ -21,7 +21,7 @@ require 'chef/mash' require 'chef/mixin/deep_merge' require 'chef/role' -require 'chef/rest' +require 'chef/server_api' require 'chef/json_compat' class Chef @@ -45,7 +45,7 @@ class Chef attr_reader :missing_roles_with_including_role # The data source passed to the constructor. Not used in this class. - # In subclasses, this is a couchdb or Chef::REST object pre-configured + # In subclasses, this is a Chef::ServerAPI object pre-configured # to fetch roles from their correct location. attr_reader :source @@ -214,11 +214,11 @@ class Chef class RunListExpansionFromAPI < RunListExpansion def rest - @rest ||= (source || Chef::REST.new(Chef::Config[:chef_server_url])) + @rest ||= (source || Chef::ServerAPI.new(Chef::Config[:chef_server_url])) end def fetch_role(name, included_by) - rest.get_rest("roles/#{name}") + Chef::Role.from_hash(rest.get("roles/#{name}")) rescue Net::HTTPServerException => e if e.message == '404 "Not Found"' role_not_found(name, included_by) diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb index 658af8779c..c5c6bc6ce0 100644 --- a/lib/chef/search/query.rb +++ b/lib/chef/search/query.rb @@ -18,7 +18,7 @@ require 'chef/config' require 'chef/exceptions' -require 'chef/rest' +require 'chef/server_api' require 'uri' @@ -35,7 +35,7 @@ class Chef end def rest - @rest ||= Chef::REST.new(@url || @config[:chef_server_url]) + @rest ||= Chef::ServerAPI.new(@url || @config[:chef_server_url]) end # Backwards compatability for cookbooks. @@ -150,12 +150,26 @@ WARNDEP query_string = create_query_string(type, query, rows, start, sort) if filter_result - response = rest.post_rest(query_string, filter_result) + response = rest.post(query_string, filter_result) # response returns rows in the format of # { "url" => url_to_node, "data" => filter_result_hash } response['rows'].map! { |row| row['data'] } else - response = rest.get_rest(query_string) + response = rest.get(query_string) + response['rows'].map! do |row| + case type.to_s + when 'node' + Chef::Node.from_hash(row) + when 'role' + Chef::Role.from_hash(row) + when 'environment' + Chef::Environment.from_hash(row) + when 'client' + Chef::ApiClient.from_hash(row) + else + Chef::DataBagItem.from_hash(row) + end + end end response diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb index 764296f8c8..6c864d53fb 100644 --- a/lib/chef/server_api.rb +++ b/lib/chef/server_api.rb @@ -23,6 +23,7 @@ require 'chef/http/decompressor' require 'chef/http/json_input' require 'chef/http/json_output' require 'chef/http/remote_request_id' +require 'chef/http/validate_content_length' class Chef class ServerAPI < Chef::HTTP @@ -31,6 +32,7 @@ class Chef options[:client_name] ||= Chef::Config[:node_name] options[:signing_key_filename] ||= Chef::Config[:client_key] options[:signing_key_filename] = nil if chef_zero_uri?(url) + options[:inflate_json_class] = false super(url, options) end @@ -40,6 +42,30 @@ class Chef use Chef::HTTP::Decompressor use Chef::HTTP::Authenticator use Chef::HTTP::RemoteRequestID + + # ValidateContentLength should come after Decompressor + # because the order of middlewares is reversed when handling + # responses. + use Chef::HTTP::ValidateContentLength + + # Makes an HTTP request to +path+ with the given +method+, +headers+, and + # +data+ (if applicable). Does not apply any middleware, besides that + # needed for Authentication. + def raw_request(method, path, headers={}, data=false) + url = create_url(path) + method, url, headers, data = Chef::HTTP::Authenticator.new(options).handle_request(method, url, headers, data) + method, url, headers, data = Chef::HTTP::RemoteRequestID.new(options).handle_request(method, url, headers, data) + response, rest_request, return_value = send_http_request(method, url, headers, data) + response.error! unless success_response?(response) + return_value + rescue Exception => exception + log_failed_request(response, return_value) unless response.nil? + + if exception.respond_to?(:chef_rest_request=) + exception.chef_rest_request = rest_request + end + raise + end end end diff --git a/lib/chef/shell/ext.rb b/lib/chef/shell/ext.rb index d516524765..ae013de76b 100644 --- a/lib/chef/shell/ext.rb +++ b/lib/chef/shell/ext.rb @@ -23,7 +23,7 @@ require 'chef/dsl/platform_introspection' require 'chef/version' require 'chef/shell/shell_session' require 'chef/shell/model_wrapper' -require 'chef/shell/shell_rest' +require 'chef/server_api' require 'chef/json_compat' module Shell @@ -331,7 +331,7 @@ E edited_data = Tempfile.open([filename, ".js"]) do |tempfile| tempfile.sync = true tempfile.puts Chef::JSONCompat.to_json(object) - system("#{Shell.editor.to_s} #{tempfile.path}") + system("#{Shell.editor} #{tempfile.path}") tempfile.rewind tempfile.read end @@ -536,7 +536,7 @@ E desc "A REST Client configured to authenticate with the API" def api - @rest = Shell::ShellREST.new(Chef::Config[:chef_server_url]) + @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end diff --git a/lib/chef/shell/shell_rest.rb b/lib/chef/shell/shell_rest.rb deleted file mode 100644 index a485a0a1a8..0000000000 --- a/lib/chef/shell/shell_rest.rb +++ /dev/null @@ -1,28 +0,0 @@ -#-- -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010 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. -# - -module Shell - class ShellREST < Chef::REST - - alias :get :get_rest - alias :put :put_rest - alias :post :post_rest - alias :delete :delete_rest - - end -end diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb index 73e6c34ebb..7b3939da69 100644 --- a/lib/chef/shell/shell_session.rb +++ b/lib/chef/shell/shell_session.rb @@ -201,7 +201,7 @@ module Shell def rebuild_context @run_status = Chef::RunStatus.new(@node, @events) - Chef::Cookbook::FileVendor.fetch_from_remote(Chef::REST.new(Chef::Config[:chef_server_url])) + Chef::Cookbook::FileVendor.fetch_from_remote(Chef::ServerAPI.new(Chef::Config[:chef_server_url])) cookbook_hash = @client.sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbook_hash) @run_context = Chef::RunContext.new(node, cookbook_collection, @events) @@ -253,7 +253,8 @@ module Shell end def register - @rest = Chef::REST.new(Chef::Config[:chef_server_url], Chef::Config[:node_name], Chef::Config[:client_key]) + @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url], :client_name => Chef::Config[:node_name], + :signing_key_filename => Chef::Config[:client_key]) end end diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb index 31cb0576a2..077fca50b9 100644 --- a/lib/chef/user_v1.rb +++ b/lib/chef/user_v1.rb @@ -140,7 +140,7 @@ class Chef def destroy # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) - Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}") + Chef::ServerAPI.new(Chef::Config[:chef_server_url]).delete("users/#{@username}") end def create @@ -287,7 +287,7 @@ class Chef end def self.list(inflate=false) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users') + response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get('users') users = if response.is_a?(Array) # EC 11 / CS 12 V0, V1 # GET /organizations/<org>/users @@ -312,7 +312,7 @@ class Chef def self.load(username) # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}") + response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("users/#{username}") Chef::UserV1.from_hash(response) end diff --git a/lib/chef/util/dsc/configuration_generator.rb b/lib/chef/util/dsc/configuration_generator.rb index 0d7296eae9..2041278e4a 100644 --- a/lib/chef/util/dsc/configuration_generator.rb +++ b/lib/chef/util/dsc/configuration_generator.rb @@ -72,7 +72,7 @@ class Chef::Util::DSC if configuration_flags configuration_flags.map do | switch, value | if merged_configuration_flags.key?(switch.to_s.downcase.to_sym) - raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch.to_s} that is disallowed." + raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch} that is disallowed." end merged_configuration_flags[switch.to_s.downcase.to_sym] = value end diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb index 47d63a2b85..7eab098c83 100644 --- a/lib/chef/util/powershell/cmdlet.rb +++ b/lib/chef/util/powershell/cmdlet.rb @@ -38,7 +38,7 @@ class Powershell when :object @json_format = true else - raise ArgumentError, "Invalid output format #{output_format.to_s} specified" + raise ArgumentError, "Invalid output format #{output_format} specified" end @cmdlet = cmdlet @@ -114,7 +114,7 @@ class Powershell def command_switches_string(switches) command_switches = switches.map do | switch_name, switch_value | if switch_name.class != Symbol - raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name.to_s}'. The switch must be specified as a Symbol'" + raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name}'. The switch must be specified as a Symbol'" end validate_switch_name!(switch_name) @@ -133,7 +133,7 @@ class Powershell when String switch_argument = escape_string_parameter_value(switch_value) else - raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name.to_s}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`" + raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`" end switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(' ').strip : '' diff --git a/lib/chef/version/platform.rb b/lib/chef/version/platform.rb index 81e7614646..d9028b5d15 100644 --- a/lib/chef/version/platform.rb +++ b/lib/chef/version/platform.rb @@ -34,7 +34,7 @@ class Chef when /^(\d+).(\d+)-[a-z]+\d?(-p(\d+))?$/i # Match FreeBSD [ $1.to_i, $2.to_i, ($4 ? $4.to_i : 0)] else - msg = "'#{str.to_s}' does not match 'x.y.z', 'x.y' or 'x'" + msg = "'#{str}' does not match 'x.y.z', 'x.y' or 'x'" raise Chef::Exceptions::InvalidPlatformVersion.new( msg ) end end diff --git a/lib/chef/version_class.rb b/lib/chef/version_class.rb index 01af6f1f55..913c50442d 100644 --- a/lib/chef/version_class.rb +++ b/lib/chef/version_class.rb @@ -61,7 +61,7 @@ class Chef when /^(\d+)\.(\d+)$/ [ $1.to_i, $2.to_i, 0 ] else - msg = "'#{str.to_s}' does not match 'x.y.z' or 'x.y'" + msg = "'#{str}' does not match 'x.y.z' or 'x.y'" raise Chef::Exceptions::InvalidCookbookVersion.new( msg ) end end diff --git a/lib/chef/version_constraint.rb b/lib/chef/version_constraint.rb index a78e32e94f..ba2169c8cb 100644 --- a/lib/chef/version_constraint.rb +++ b/lib/chef/version_constraint.rb @@ -50,7 +50,7 @@ class Chef end def inspect - "(#{to_s})" + "(#{self})" end def to_s diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb index 4786222bd4..de3381c5ba 100644 --- a/lib/chef/win32/api.rb +++ b/lib/chef/win32/api.rb @@ -147,6 +147,8 @@ class Chef host.typedef :long, :LRESULT # Signed result of message processing. WinDef.h: host.typedef LONG_PTR LRESULT; host.typedef :pointer, :LPWIN32_FIND_DATA # Pointer to WIN32_FIND_DATA struct host.typedef :pointer, :LPBY_HANDLE_FILE_INFORMATION # Point to a BY_HANDLE_FILE_INFORMATION struct + host.typedef :pointer, :LSA_HANDLE # A handle to a Policy object + host.typedef :ulong, :NTSTATUS # An NTSTATUS code returned by an LSA function call. host.typedef :pointer, :PBOOL # Pointer to a BOOL. host.typedef :pointer, :PBOOLEAN # Pointer to a BOOL. host.typedef :pointer, :PBYTE # Pointer to a BYTE. @@ -174,12 +176,16 @@ class Chef host.typedef :pointer, :PLONG_PTR # Pointer to a LONG_PTR. host.typedef :pointer, :PLONG32 # Pointer to a LONG32. host.typedef :pointer, :PLONG64 # Pointer to a LONG64. + host.typedef :pointer, :PLSA_HANDLE # Pointer to an LSA_HANDLE + host.typedef :pointer, :PLSA_OBJECT_ATTRIBUTES # Pointer to an LSA_OBJECT_ATTRIBUTES + host.typedef :pointer, :PLSA_UNICODE_STRING # Pointer to LSA_UNICODE_STRING host.typedef :pointer, :PLUID # Pointer to a LUID. host.typedef :pointer, :POINTER_32 # 32-bit pointer. On a 32-bit system, this is a native pointer. On a 64-bit system, this is a truncated 64-bit pointer. host.typedef :pointer, :POINTER_64 # 64-bit pointer. On a 64-bit system, this is a native pointer. On a 32-bit system, this is a sign-extended 32-bit pointer. host.typedef :pointer, :POINTER_SIGNED # A signed pointer. host.typedef :pointer, :POINTER_UNSIGNED # An unsigned pointer. host.typedef :pointer, :PSHORT # Pointer to a SHORT. + host.typedef :pointer, :PSID # Pointer to an account SID host.typedef :pointer, :PSIZE_T # Pointer to a SIZE_T. host.typedef :pointer, :PSSIZE_T # Pointer to a SSIZE_T. host.typedef :pointer, :PSTR # Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts. @@ -188,7 +194,6 @@ class Chef host.typedef :pointer, :PCRYPTPROTECT_PROMPTSTRUCT # Pointer to a CRYPTOPROTECT_PROMPTSTRUCT. host.typedef :pointer, :PDATA_BLOB # Pointer to a DATA_BLOB. host.typedef :pointer, :PTSTR # A PWSTR if UNICODE is defined, a PSTR otherwise. - host.typedef :pointer, :PSID host.typedef :pointer, :PUCHAR # Pointer to a UCHAR. host.typedef :pointer, :PUHALF_PTR # Pointer to a UHALF_PTR. host.typedef :pointer, :PUINT # Pointer to a UINT. diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb index 4c352a3554..4353c488f8 100644 --- a/lib/chef/win32/api/security.rb +++ b/lib/chef/win32/api/security.rb @@ -207,6 +207,21 @@ class Chef LOGON32_PROVIDER_WINNT40 = 2; LOGON32_PROVIDER_WINNT50 = 3; + # LSA access policy + POLICY_VIEW_LOCAL_INFORMATION = 0x00000001 + POLICY_VIEW_AUDIT_INFORMATION = 0x00000002 + POLICY_GET_PRIVATE_INFORMATION = 0x00000004 + POLICY_TRUST_ADMIN = 0x00000008 + POLICY_CREATE_ACCOUNT = 0x00000010 + POLICY_CREATE_SECRET = 0x00000020 + POLICY_CREATE_PRIVILEGE = 0x00000040 + POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080 + POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100 + POLICY_AUDIT_LOG_ADMIN = 0x00000200 + POLICY_SERVER_ADMIN = 0x00000400 + POLICY_LOOKUP_NAMES = 0x00000800 + POLICY_NOTIFICATION = 0x00001000 + ############################################### # Win32 API Bindings ############################################### @@ -381,6 +396,23 @@ class Chef end end + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721829(v=vs.85).aspx + class LSA_OBJECT_ATTRIBUTES < FFI::Struct + layout :Length, :ULONG, + :RootDirectory, :HANDLE, + :ObjectName, :pointer, + :Attributes, :ULONG, + :SecurityDescriptor, :PVOID, + :SecurityQualityOfService, :PVOID + end + + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721841(v=vs.85).aspx + class LSA_UNICODE_STRING < FFI::Struct + layout :Length, :USHORT, + :MaximumLength, :USHORT, + :Buffer, :PWSTR + end + ffi_lib "advapi32" safe_attach_function :AccessCheck, [:pointer, :HANDLE, :DWORD, :pointer, :pointer, :pointer, :pointer, :pointer], :BOOL @@ -415,6 +447,12 @@ class Chef safe_attach_function :LookupPrivilegeNameW, [ :LPCWSTR, :PLUID, :LPWSTR, :LPDWORD ], :BOOL safe_attach_function :LookupPrivilegeDisplayNameW, [ :LPCWSTR, :LPCWSTR, :LPWSTR, :LPDWORD, :LPDWORD ], :BOOL safe_attach_function :LookupPrivilegeValueW, [ :LPCWSTR, :LPCWSTR, :PLUID ], :BOOL + safe_attach_function :LsaAddAccountRights, [ :pointer, :pointer, :pointer, :ULONG ], :NTSTATUS + safe_attach_function :LsaClose, [ :LSA_HANDLE ], :NTSTATUS + safe_attach_function :LsaEnumerateAccountRights, [ :LSA_HANDLE, :PSID, :PLSA_UNICODE_STRING, :PULONG ], :NTSTATUS + safe_attach_function :LsaFreeMemory, [ :PVOID ], :NTSTATUS + safe_attach_function :LsaNtStatusToWinError, [ :NTSTATUS ], :ULONG + safe_attach_function :LsaOpenPolicy, [ :PLSA_UNICODE_STRING, :PLSA_OBJECT_ATTRIBUTES, :DWORD, :PLSA_HANDLE ], :NTSTATUS safe_attach_function :MakeAbsoluteSD, [ :pointer, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD], :BOOL safe_attach_function :MapGenericMask, [ :PDWORD, :PGENERICMAPPING ], :void safe_attach_function :OpenProcessToken, [ :HANDLE, :DWORD, :PHANDLE ], :BOOL diff --git a/lib/chef/win32/error.rb b/lib/chef/win32/error.rb index 2175608eeb..c638773d2a 100644 --- a/lib/chef/win32/error.rb +++ b/lib/chef/win32/error.rb @@ -57,8 +57,7 @@ class Chef # nil::: always returns nil when it does not raise # === Raises # Chef::Exceptions::Win32APIError::: - def self.raise!(message = nil) - code = get_last_error + def self.raise!(message = nil, code = get_last_error) msg = format_message(code).strip formatted_message = "" formatted_message << message if message diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb index bc80517d80..38694c9fec 100644 --- a/lib/chef/win32/security.rb +++ b/lib/chef/win32/security.rb @@ -104,6 +104,22 @@ class Chef end end + def self.add_account_right(name, privilege) + privilege_pointer = FFI::MemoryPointer.new LSA_UNICODE_STRING, 1 + privilege_lsa_string = LSA_UNICODE_STRING.new(privilege_pointer) + privilege_lsa_string[:Buffer] = FFI::MemoryPointer.from_string(privilege.to_wstring) + privilege_lsa_string[:Length] = privilege.length * 2 + privilege_lsa_string[:MaximumLength] = (privilege.length + 1) * 2 + + with_lsa_policy(name) do |policy_handle, sid| + result = LsaAddAccountRights(policy_handle.read_pointer, sid, privilege_pointer, 1) + win32_error = LsaNtStatusToWinError(result) + if win32_error != 0 + Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) + end + end + end + def self.adjust_token_privileges(token, privileges) token = token.handle if token.respond_to?(:handle) old_privileges_size = FFI::Buffer.new(:long).write_long(privileges.size_with_privileges) @@ -165,6 +181,29 @@ class Chef end end + def self.get_account_right(name) + privileges = [] + privilege_pointer = FFI::MemoryPointer.new(:pointer) + privilege_length = FFI::MemoryPointer.new(:ulong) + + with_lsa_policy(name) do |policy_handle, sid| + result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid, privilege_pointer, privilege_length) + win32_error = LsaNtStatusToWinError(result) + return [] if win32_error == 2 # FILE_NOT_FOUND - No rights assigned + if win32_error != 0 + Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) + end + + privilege_length.read_ulong.times do |i| + privilege = LSA_UNICODE_STRING.new(privilege_pointer.read_pointer + i * LSA_UNICODE_STRING.size) + privileges << privilege[:Buffer].read_wstring + end + LsaFreeMemory(privilege_pointer) + end + + privileges + end + def self.get_ace(acl, index) acl = acl.pointer if acl.respond_to?(:pointer) ace = FFI::Buffer.new :pointer @@ -548,6 +587,30 @@ class Chef end end + def self.with_lsa_policy(username) + sid = lookup_account_name(username)[1] + + access = 0 + access |= POLICY_CREATE_ACCOUNT + access |= POLICY_LOOKUP_NAMES + + policy_handle = FFI::MemoryPointer.new(:pointer) + result = LsaOpenPolicy(nil, LSA_OBJECT_ATTRIBUTES.new, access, policy_handle) + win32_error = LsaNtStatusToWinError(result) + if win32_error != 0 + Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) + end + + begin + yield policy_handle, sid.pointer + ensure + win32_error = LsaNtStatusToWinError(LsaClose(policy_handle.read_pointer)) + if win32_error != 0 + Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) + end + end + end + def self.with_privileges(*privilege_names) # Set privileges token = open_current_process_token(TOKEN_READ | TOKEN_ADJUST_PRIVILEGES) |