diff options
author | sersut <serdar@opscode.com> | 2013-10-17 15:28:23 -0700 |
---|---|---|
committer | sersut <serdar@opscode.com> | 2013-10-17 15:28:23 -0700 |
commit | 180e9c1ec6cc324c598b3575a6ba2a209df44a24 (patch) | |
tree | 09bfce4f8c4e8f29a7c6c740f7ed86cec11d5737 /lib/chef | |
parent | c995e37339f37be82d7e5b1b7866bef64c1bd05a (diff) | |
parent | 05ba3e301794bd09173c5c3f13a62c2530d8b403 (diff) | |
download | chef-180e9c1ec6cc324c598b3575a6ba2a209df44a24.tar.gz |
Merge branch 'master' into 11-stable
Diffstat (limited to 'lib/chef')
268 files changed, 4317 insertions, 2095 deletions
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb index 32e1eee017..66cbd3f30e 100644 --- a/lib/chef/api_client.rb +++ b/lib/chef/api_client.rb @@ -36,6 +36,7 @@ class Chef @public_key = nil @private_key = nil @admin = false + @validator = false end # Gets or sets the client name. @@ -74,6 +75,19 @@ class Chef ) end + # Gets or sets whether this client is a validator. + # + # @params [Boolean] whether or not the client is a validator. If + # `nil`, retrieves the already-set value. + # @return [Boolean] The current value + def validator(arg=nil) + set_or_return( + :validator, + arg, + :kind_of => [TrueClass, FalseClass] + ) + end + # Gets or sets the private key. # # @params [Optional String] The string representation of the private key. @@ -94,6 +108,7 @@ class Chef result = { "name" => @name, "public_key" => @public_key, + "validator" => @validator, "admin" => @admin, 'json_class' => self.class.name, "chef_type" => "client" @@ -115,6 +130,7 @@ class Chef client.private_key(o["private_key"]) if o.key?("private_key") client.public_key(o["public_key"]) client.admin(o["admin"]) + client.validator(o["validator"]) client end @@ -160,11 +176,11 @@ class Chef # Save this client via the REST API, returns a hash including the private key def save begin - http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin}) + http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator}) rescue Net::HTTPServerException => e # If that fails, go ahead and try and update it if e.response.code == "404" - http_api.post("clients", {:name => self.name, :admin => self.admin }) + http_api.post("clients", {:name => self.name, :admin => self.admin, :validator => self.validator }) else raise e end @@ -172,7 +188,7 @@ class Chef end def reregister - reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :private_key => true }) + reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true }) if reregistered_self.respond_to?(:[]) private_key(reregistered_self["private_key"]) else @@ -192,7 +208,7 @@ class Chef end def inspect - "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' " + + "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " + "public_key:'#{public_key}' private_key:'#{private_key}'" end @@ -202,4 +218,3 @@ class Chef end end - diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 6522ba6e64..c1fc3a78a4 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -66,31 +66,26 @@ class Chef::Application run_application end - # Parse the configuration file + # Parse configuration (options and config file) def configure_chef parse_options + load_config_file + end - begin - case config[:config_file] - when /^(http|https):\/\// - Chef::REST.new("", nil, nil).fetch(config[:config_file]) { |f| apply_config(f.path) } - else - ::File::open(config[:config_file]) { |f| apply_config(f.path) } - end - rescue Errno::ENOENT => error + # Parse the config file + def load_config_file + config_fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail) + if config[:config_file].nil? + Chef::Log.warn("No config file found or specified on command line, using command line options.") + elsif config_fetcher.config_missing? Chef::Log.warn("*****************************************") Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") Chef::Log.warn("*****************************************") - - Chef::Config.merge!(config) - rescue SocketError => error - Chef::Application.fatal!("Error getting config file #{config[:config_file]}", 2) - rescue Chef::Exceptions::ConfigurationError => error - Chef::Application.fatal!("Error processing config file #{config[:config_file]} with error #{error.message}", 2) - rescue Exception => error - Chef::Application.fatal!("Unknown error processing config file #{config[:config_file]} with error #{error.message}", 2) + else + config_content = config_fetcher.read_config + apply_config(config_content, config[:config_file]) end - + Chef::Config.merge!(config) end # Initialize and configure the logger. @@ -172,23 +167,59 @@ class Chef::Application raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application" end + def self.setup_server_connectivity + if Chef::Config.chef_zero.enabled + destroy_server_connectivity + + require 'chef_zero/server' + require 'chef/chef_fs/chef_fs_data_store' + require 'chef/chef_fs/config' + + chef_fs = Chef::ChefFS::Config.new.local_fs + chef_fs.write_pretty_json = true + server_options = {} + server_options[:data_store] = Chef::ChefFS::ChefFSDataStore.new(chef_fs) + server_options[:log_level] = Chef::Log.level + server_options[:port] = Chef::Config.chef_zero.port + Chef::Log.info("Starting chef-zero on port #{Chef::Config.chef_zero.port} with repository at #{server_options[:data_store].chef_fs.fs_description}") + @chef_zero_server = ChefZero::Server.new(server_options) + @chef_zero_server.start_background + Chef::Config.chef_server_url = @chef_zero_server.url + end + end + + def self.destroy_server_connectivity + if @chef_zero_server + @chef_zero_server.stop + @chef_zero_server = nil + end + end + # Initializes Chef::Client instance and runs it def run_chef_client + Chef::Application.setup_server_connectivity + @chef_client = Chef::Client.new( - @chef_client_json, + @chef_client_json, :override_runlist => config[:override_runlist] ) @chef_client_json = nil @chef_client.run @chef_client = nil + + Chef::Application.destroy_server_connectivity end private - def apply_config(config_file_path) - Chef::Config.from_file(config_file_path) - Chef::Config.merge!(config) + def apply_config(config_content, config_file_path) + Chef::Config.from_string(config_content, config_file_path) + rescue Exception => error + Chef::Log.fatal("Configuration error #{error.class}: #{error.message}") + filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/) + filtered_trace.each {|line| Chef::Log.fatal(" " + line )} + Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2) end class << self diff --git a/lib/chef/application/agent.rb b/lib/chef/application/agent.rb index 353d45252e..66b0c25cbf 100644 --- a/lib/chef/application/agent.rb +++ b/lib/chef/application/agent.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index b08a795697..a04b4f1cf6 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -22,7 +22,7 @@ require 'chef/client' require 'chef/config' require 'chef/daemon' require 'chef/log' -require 'chef/rest' +require 'chef/config_fetcher' require 'chef/handler/error_report' @@ -34,7 +34,6 @@ class Chef::Application::Client < Chef::Application option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", - :default => Chef::Config.platform_specific_path("/etc/chef/client.rb"), :description => "The configuration file to use" option :formatter, @@ -197,6 +196,20 @@ class Chef::Application::Client < Chef::Application :description => "Enable reporting data collection for chef runs", :boolean => true + option :local_mode, + :short => "-z", + :long => "--local-mode", + :description => "Point chef-client at local repository", + :boolean => true + + option :chef_zero_port, + :long => "--chef-zero-port PORT", + :description => "Port to start chef-zero on" + + option :config_file_jail, + :long => "--config-file-jail PATH", + :description => "Directory under which config files are allowed to be loaded (no client.rb or knife.rb outside this path will be loaded)." + if Chef::Platform.windows? option :fatal_windows_admin_check, :short => "-A", @@ -219,6 +232,12 @@ class Chef::Application::Client < Chef::Application Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url + Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode) + if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path) + Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd) + end + Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port] + if Chef::Config[:daemonize] Chef::Config[:interval] ||= 1800 end @@ -229,31 +248,22 @@ class Chef::Application::Client < Chef::Application end if Chef::Config[:json_attribs] - begin - json_io = case Chef::Config[:json_attribs] - when /^(http|https):\/\// - @rest = Chef::REST.new(Chef::Config[:json_attribs], nil, nil) - @rest.get_rest(Chef::Config[:json_attribs], true).open - else - open(Chef::Config[:json_attribs]) - end - rescue SocketError => error - Chef::Application.fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2) - rescue Errno::ENOENT => error - Chef::Application.fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2) - rescue Errno::EACCES => error - Chef::Application.fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2) - rescue Exception => error - Chef::Application.fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2) - end + config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) + @chef_client_json = config_fetcher.fetch_json + end + end - begin - @chef_client_json = Chef::JSONCompat.from_json(json_io.read) - json_io.close unless json_io.closed? - rescue JSON::ParserError => error - Chef::Application.fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2) + def load_config_file + Chef::Config.config_file_jail = config[:config_file_jail] if config[:config_file_jail] + if !config.has_key?(:config_file) + if config[:local_mode] + require 'chef/knife' + config[:config_file] = Chef::Knife.locate_config_file + else + config[:config_file] = Chef::Config.platform_specific_path("/etc/chef/client.rb") end end + super end def configure_logging diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb index 13612a4bf3..3a9cd4e870 100644 --- a/lib/chef/application/knife.rb +++ b/lib/chef/application/knife.rb @@ -106,6 +106,16 @@ class Chef::Application::Knife < Chef::Application :description => "Which format to use for output", :default => "summary" + option :local_mode, + :short => "-z", + :long => "--local-mode", + :description => "Point knife commands at local repository instead of server", + :boolean => true + + option :chef_zero_port, + :long => "--chef-zero-port PORT", + :description => "Port to start chef-zero on" + option :version, :short => "-v", :long => "--version", diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb index 5e3fe90607..ba563ce3a8 100644 --- a/lib/chef/application/solo.rb +++ b/lib/chef/application/solo.rb @@ -23,7 +23,7 @@ require 'chef/config' require 'chef/daemon' require 'chef/log' require 'chef/rest' -require 'open-uri' +require 'chef/config_fetcher' require 'fileutils' class Chef::Application::Solo < Chef::Application @@ -31,7 +31,7 @@ class Chef::Application::Solo < Chef::Application option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", - :default => Chef::Config.platform_specfic_path('/etc/chef/solo.rb'), + :default => Chef::Config.platform_specific_path('/etc/chef/solo.rb'), :description => "The configuration file to use" option :formatter, @@ -160,6 +160,11 @@ class Chef::Application::Solo < Chef::Application :description => 'Enable whyrun mode', :boolean => true + option :environment, + :short => '-E ENVIRONMENT', + :long => '--environment ENVIRONMENT', + :description => 'Set the Chef Environment on the node' + attr_reader :chef_solo_json def initialize @@ -176,36 +181,13 @@ class Chef::Application::Solo < Chef::Application end if Chef::Config[:json_attribs] - begin - json_io = case Chef::Config[:json_attribs] - when /^(http|https):\/\// - @rest = Chef::REST.new(Chef::Config[:json_attribs], nil, nil) - @rest.get_rest(Chef::Config[:json_attribs], true).open - else - open(Chef::Config[:json_attribs]) - end - rescue SocketError => error - Chef::Application.fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2) - rescue Errno::ENOENT => error - Chef::Application.fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2) - rescue Errno::EACCES => error - Chef::Application.fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2) - rescue Exception => error - Chef::Application.fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2) - end - - begin - @chef_client_json = Chef::JSONCompat.from_json(json_io.read) - json_io.close unless json_io.closed? - rescue JSON::ParserError => error - Chef::Application.fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2) - end + config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) + @chef_solo_json = config_fetcher.fetch_json end if Chef::Config[:recipe_url] cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ } recipes_path = File.expand_path(File.join(cookbooks_path, '..')) - target_file = File.join(recipes_path, 'recipes.tgz') Chef::Log.debug "Creating path #{recipes_path} to extract recipes into" FileUtils.mkdir_p recipes_path diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb index 85a18c7a17..08403c7aa2 100644 --- a/lib/chef/application/windows_service.rb +++ b/lib/chef/application/windows_service.rb @@ -27,11 +27,13 @@ require 'chef/rest' require 'mixlib/cli' require 'socket' require 'win32/daemon' +require 'chef/mixin/shell_out' class Chef class Application class WindowsService < ::Win32::Daemon include Mixlib::CLI + include Chef::Mixin::ShellOut option :config_file, :short => "-c CONFIG", @@ -160,17 +162,29 @@ class Chef # Initializes Chef::Client instance and runs it def run_chef_client - @chef_client = Chef::Client.new( - @chef_client_json, - :override_runlist => config[:override_runlist] - ) - @chef_client_json = nil - - @chef_client.run - @chef_client = nil + # The chef client will be started in a new process. We have used shell_out to start the chef-client. + # The log_location and config_file of the parent process is passed to the new chef-client process. + # We need to add the --no-fork, as by default it is set to fork=true. + begin + Chef::Log.info "Starting chef-client in a new process" + # Pass config params to the new process + config_params = " --no-fork" + config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil? + config_params += " -L #{Chef::Config[:log_location]}" unless Chef::Config[:log_location] == STDOUT + # Starts a new process and waits till the process exits + result = shell_out("chef-client #{config_params}") + Chef::Log.debug "#{result.stdout}" + Chef::Log.debug "#{result.stderr}" + rescue Mixlib::ShellOut::ShellCommandFailed => e + Chef::Log.warn "Not able to start chef-client in new process (#{e})" + rescue => e + Chef::Log.error e + ensure + # Once process exits, we log the current process' pid + Chef::Log.info "Child process exited (pid: #{Process.pid})" + end end - def apply_config(config_file_path) Chef::Config.from_file(config_file_path) Chef::Config.merge!(config) @@ -241,7 +255,7 @@ class Chef def configure_chef(startup_parameters) # Bit of a hack ahead: # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX". - # It is also possible to specify startup parameters separately, either via the the Services manager + # It is also possible to specify startup parameters separately, either via the Services manager # or by using the registry (I think). # In order to accommodate all possible sources of parameterization, we first parse any command line diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb index 13bd2c5cd6..493f0dfc62 100644 --- a/lib/chef/application/windows_service_manager.rb +++ b/lib/chef/application/windows_service_manager.rb @@ -61,6 +61,14 @@ class Chef :show_options => true, :exit => 0 + option :version, + :short => "-v", + :long => "--version", + :description => "Show chef version", + :boolean => true, + :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"}, + :exit => 0 + def initialize(service_options) # having to call super in initialize is the most annoying # anti-pattern :( diff --git a/lib/chef/checksum/storage.rb b/lib/chef/checksum/storage.rb index 90aef57081..4a1b3d38df 100644 --- a/lib/chef/checksum/storage.rb +++ b/lib/chef/checksum/storage.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/checksum/storage/filesystem.rb b/lib/chef/checksum/storage/filesystem.rb index 7500a5ad85..94257f518b 100644 --- a/lib/chef/checksum/storage/filesystem.rb +++ b/lib/chef/checksum/storage/filesystem.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index c1c5a81e04..1fedc98380 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -135,7 +135,7 @@ class Chef if path[0] == 'cookbooks' && path.length >= 3 entry.delete(true) else - entry.delete + entry.delete(false) end end end diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb index dd5e62b755..d0183a5a2a 100644 --- a/lib/chef/chef_fs/command_line.rb +++ b/lib/chef/chef_fs/command_line.rb @@ -19,6 +19,7 @@ require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/operation_failed_error' require 'chef/chef_fs/file_system/operation_not_allowed_error' +require 'chef/util/diff' class Chef module ChefFS @@ -268,7 +269,7 @@ class Chef old_tempfile.write(old_value) old_tempfile.close - result = `diff -u #{old_tempfile.path} #{new_tempfile.path}` + result = Chef::Util::Diff.new.udiff(old_tempfile.path, new_tempfile.path) result = result.gsub(/^--- #{old_tempfile.path}/, "--- #{old_path}") result = result.gsub(/^\+\+\+ #{new_tempfile.path}/, "+++ #{new_path}") result diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb index ab4cea89f2..e08b976961 100644 --- a/lib/chef/chef_fs/config.rb +++ b/lib/chef/chef_fs/config.rb @@ -25,13 +25,24 @@ class Chef # Helpers to take Chef::Config and create chef_fs and local_fs from it # class Config - def initialize(chef_config = Chef::Config, cwd = Dir.pwd) + def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}) @chef_config = chef_config @cwd = cwd - configure_repo_paths + @cookbook_version = options[:cookbook_version] + + # Default to getting *everything* from the server. + if !@chef_config[:repo_mode] + if @chef_config[:chef_server_url] =~ /\/+organizations\/.+/ + @chef_config[:repo_mode] = 'hosted_everything' + else + @chef_config[:repo_mode] = 'everything' + end + end end - PATH_VARIABLES = %w(acl_path client_path cookbook_path container_path data_bag_path environment_path group_path node_path role_path user_path) + attr_reader :chef_config + attr_reader :cwd + attr_reader :cookbook_version def chef_fs @chef_fs ||= create_chef_fs @@ -39,7 +50,7 @@ class Chef def create_chef_fs require 'chef/chef_fs/file_system/chef_server_root_dir' - Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", @chef_config) + Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version) end def local_fs @@ -73,16 +84,14 @@ class Chef # If the path does not reach into ANY specified directory, nil is returned. def server_path(file_path) pwd = File.expand_path(Dir.pwd) - absolute_path = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd)) + absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd)) # Check all object paths (cookbooks_dir, data_bags_dir, etc.) object_paths.each_pair do |name, paths| paths.each do |path| realest_path = Chef::ChefFS::PathUtils.realest_path(path) - if absolute_path[0,realest_path.length] == realest_path && - (absolute_path.length == realest_path.length || - absolute_path[realest_path.length,1] =~ /#{PathUtils.regexp_path_separator}/) - relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_path, realest_path) + if PathUtils.descendant_of?(absolute_pwd, realest_path) + relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path) return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}" end end @@ -91,7 +100,7 @@ class Chef # Check chef_repo_path Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path| realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path) - if absolute_path == realest_chef_repo_path + if absolute_pwd == realest_chef_repo_path return '/' end end @@ -125,19 +134,10 @@ class Chef server_path end - def require_chef_repo_path - if !@chef_config[:chef_repo_path] - Chef::Log.error("Must specify either chef_repo_path or cookbook_path in Chef config file") - exit(1) - end - end - private def object_paths @object_paths ||= begin - require_chef_repo_path - result = {} case @chef_config[:repo_mode] when 'static' @@ -155,51 +155,6 @@ class Chef result end end - - def configure_repo_paths - # Smooth out some (for now) inappropriate defaults set by Chef - if @chef_config[:cookbook_path] == [ @chef_config.platform_specific_path("/var/chef/cookbooks"), - @chef_config.platform_specific_path("/var/chef/site-cookbooks") ] - @chef_config[:cookbook_path] = nil - end - if @chef_config[:data_bag_path] == @chef_config.platform_specific_path('/var/chef/data_bags') - @chef_config[:data_bag_path] = nil - end - if @chef_config[:node_path] == '/var/chef/node' - @chef_config[:node_path] = nil - end - if @chef_config[:role_path] == @chef_config.platform_specific_path('/var/chef/roles') - @chef_config[:role_path] = nil - end - - # Infer chef_repo_path from cookbook_path if not speciifed - if !@chef_config[:chef_repo_path] - if @chef_config[:cookbook_path] - @chef_config[:chef_repo_path] = Array(@chef_config[:cookbook_path]).flatten.map { |path| File.expand_path('..', path) } - end - end - - # Default to getting *everything* from the server. - if !@chef_config[:repo_mode] - if @chef_config[:chef_server_url] =~ /\/+organizations\/.+/ - @chef_config[:repo_mode] = 'hosted_everything' - else - @chef_config[:repo_mode] = 'everything' - end - end - - # Infer any *_path variables that are not specified - if @chef_config[:chef_repo_path] - PATH_VARIABLES.each do |variable_name| - chef_repo_paths = Array(@chef_config[:chef_repo_path]).flatten - variable = variable_name.to_sym - if !@chef_config[variable] - # cookbook_path -> cookbooks - @chef_config[variable] = chef_repo_paths.map { |path| File.join(path, "#{variable_name[0..-6]}s") } - end - end - end - end end end end diff --git a/lib/chef/chef_fs/data_handler/client_data_handler.rb b/lib/chef/chef_fs/data_handler/client_data_handler.rb index 2db3fa897f..4b6b8f5c79 100644 --- a/lib/chef/chef_fs/data_handler/client_data_handler.rb +++ b/lib/chef/chef_fs/data_handler/client_data_handler.rb @@ -9,12 +9,11 @@ class Chef defaults = { 'name' => remove_dot_json(entry.name), 'clientname' => remove_dot_json(entry.name), - 'orgname' => entry.org, 'admin' => false, 'validator' => false, 'chef_type' => 'client' } - if entry.org + if entry.respond_to?(:org) && entry.org defaults['orgname'] = entry.org end result = normalize_hash(client, defaults) diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb index a6e14e548c..f2478c4680 100644 --- a/lib/chef/chef_fs/file_system.rb +++ b/lib/chef/chef_fs/file_system.rb @@ -289,7 +289,7 @@ class Chef ui.output "Deleted extra entry #{dest_path} (purge is on)" if ui end else - Chef::Log.info("Not deleting extra entry #{dest_path} (purge is off)") if ui + ui.output ("Not deleting extra entry #{dest_path} (purge is off)") if ui end end @@ -407,7 +407,7 @@ class Chef parent = entry.parent if parent && !parent.exists? parent_path = format_path.call(parent) if ui - parent_parent = get_or_create_parent(entry.parent, options, ui, format_path) + parent_parent = get_or_create_parent(parent, options, ui, format_path) if options[:dry_run] ui.output "Would create #{parent_path}" if ui else diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/acl_entry.rb index 0be9076038..8edc02d5c5 100644 --- a/lib/chef/chef_fs/file_system/acl_entry.rb +++ b/lib/chef/chef_fs/file_system/acl_entry.rb @@ -40,7 +40,7 @@ class Chef acls = data_handler.normalize(JSON.parse(file_contents, :create_additions => false), self) PERMISSIONS.each do |permission| begin - rest.put_rest("#{api_path}/#{permission}", { permission => acls[permission] }) + rest.put("#{api_path}/#{permission}", { permission => acls[permission] }) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}" rescue Net::HTTPServerException => e diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb new file mode 100644 index 0000000000..7d2a930633 --- /dev/null +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb @@ -0,0 +1,37 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/chef_fs/file_system/chef_repository_file_system_entry' +require 'chef/chef_fs/file_system/acls_dir' +require 'chef/chef_fs/data_handler/acl_data_handler' + +class Chef + module ChefFS + module FileSystem + class ChefRepositoryFileSystemAclsDir < ChefRepositoryFileSystemEntry + def initialize(name, parent, path = nil) + super(name, parent, path, Chef::ChefFS::DataHandler::AclDataHandler.new) + end + + def can_have_child?(name, is_dir) + is_dir ? Chef::ChefFS::FileSystem::AclsDir::ENTITY_TYPES.include?(name) : name == 'organization.json' + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb index e3d8e47cf2..5203637012 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb @@ -18,6 +18,7 @@ require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry' require 'chef/chef_fs/file_system/cookbook_dir' +require 'chef/chef_fs/file_system/not_found_error' require 'chef/cookbook/chefignore' require 'chef/cookbook/cookbook_version_loader' @@ -51,13 +52,14 @@ class Chef end def children - Dir.entries(file_path).sort. - select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. - map do |child_name| - segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {} - ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive]) - end. - select { |entry| !(entry.dir? && entry.children.size == 0) } + begin + Dir.entries(file_path).sort. + select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. + map { |child_name| make_child(child_name) }. + select { |entry| !(entry.dir? && entry.children.size == 0) } + rescue Errno::ENOENT + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) + end end def can_have_child?(name, is_dir) @@ -65,7 +67,6 @@ class Chef # Only the given directories will be uploaded. return CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != 'root_files' end - super(name, is_dir) end @@ -79,6 +80,13 @@ class Chef def canonical_cookbook_name(entry_name) self.class.canonical_cookbook_name(entry_name) end + + protected + + def make_child(child_name) + segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {} + ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive]) + end end end end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb index d2fe3ed5f6..6541b07065 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb @@ -18,6 +18,7 @@ require 'chef/chef_fs/file_system/chef_repository_file_system_entry' require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir' +require 'chef/chef_fs/file_system/not_found_error' class Chef module ChefFS @@ -33,10 +34,14 @@ class Chef attr_reader :recursive def children - Dir.entries(file_path).sort. - select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. - map { |child_name| ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive) }. - select { |entry| !(entry.dir? && entry.children.size == 0) } + begin + Dir.entries(file_path).sort. + select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. + map { |child_name| make_child(child_name) }. + select { |entry| !(entry.dir? && entry.children.size == 0) } + rescue Errno::ENOENT + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) + end end def can_have_child?(name, is_dir) @@ -65,6 +70,16 @@ class Chef true end + + def write_pretty_json + false + end + + protected + + def make_child(child_name) + ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive) + end end end end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb index ba4ea0bd1f..6e16f18f24 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb @@ -26,29 +26,44 @@ class Chef class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry def initialize(name, parent, file_path) super(name, parent, file_path) - @chefignore = Chef::Cookbook::Chefignore.new(self.file_path) + begin + @chefignore = Chef::Cookbook::Chefignore.new(self.file_path) + rescue Errno::EISDIR + rescue Errno::EACCES + # Work around a bug in Chefignore when chefignore is a directory + end end attr_reader :chefignore def children - Dir.entries(file_path).sort. - select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. - map { |child_name| ChefRepositoryFileSystemCookbookDir.new(child_name, self) }. - select do |entry| - # empty cookbooks and cookbook directories are ignored - if entry.children.size == 0 - Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}") - false - else - true + begin + Dir.entries(file_path).sort. + select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. + map { |child_name| make_child(child_name) }. + select do |entry| + # empty cookbooks and cookbook directories are ignored + if entry.children.size == 0 + Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}") + false + else + true + end end - end + rescue Errno::ENOENT + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) + end end def can_have_child?(name, is_dir) is_dir && !name.start_with?('.') end + + protected + + def make_child(child_name) + ChefRepositoryFileSystemCookbookDir.new(child_name, self) + end end end end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb index fa4fda4eb6..3d3f58201e 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb @@ -18,6 +18,7 @@ # require 'chef/chef_fs/file_system/file_system_entry' +require 'chef/chef_fs/file_system/not_found_error' class Chef module ChefFS @@ -30,6 +31,10 @@ class Chef @data_handler = data_handler end + def write_pretty_json + root.write_pretty_json + end + def data_handler @data_handler || parent.data_handler end @@ -47,13 +52,36 @@ class Chef !is_dir && name[-5..-1] == '.json' end + def write(file_contents) + if file_contents && write_pretty_json && name[-5..-1] == '.json' + file_contents = minimize(file_contents, self) + end + super(file_contents) + end + + def minimize(file_contents, entry) + object = JSONCompat.from_json(file_contents, :create_additions => false) + object = data_handler.normalize(object, entry) + object = data_handler.minimize(object, entry) + JSONCompat.to_json_pretty(object) + end + def children # Except cookbooks and data bag dirs, all things must be json files - Dir.entries(file_path).sort. - select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. - map { |child_name| ChefRepositoryFileSystemEntry.new(child_name, self) } + begin + Dir.entries(file_path).sort. + select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }. + map { |child_name| make_child(child_name) } + rescue Errno::ENOENT + raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) + end end + protected + + def make_child(child_name) + ChefRepositoryFileSystemEntry.new(child_name, self) + end end end end diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb index 3523d85a4e..d615e0f415 100644 --- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb @@ -18,6 +18,7 @@ require 'chef/chef_fs/file_system/base_fs_dir' require 'chef/chef_fs/file_system/chef_repository_file_system_entry' +require 'chef/chef_fs/file_system/chef_repository_file_system_acls_dir' require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir' require 'chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir' require 'chef/chef_fs/file_system/multiplexed_dir' @@ -28,7 +29,6 @@ require 'chef/chef_fs/data_handler/role_data_handler' require 'chef/chef_fs/data_handler/user_data_handler' require 'chef/chef_fs/data_handler/group_data_handler' require 'chef/chef_fs/data_handler/container_data_handler' -require 'chef/chef_fs/data_handler/acl_data_handler' class Chef module ChefFS @@ -39,6 +39,8 @@ class Chef @child_paths = child_paths end + attr_accessor :write_pretty_json + attr_reader :child_paths def children @@ -51,9 +53,14 @@ class Chef def create_child(name, file_contents = nil) child_paths[name].each do |path| - Dir.mkdir(path) + begin + Dir.mkdir(path) + rescue Errno::EEXIST + end end - make_child_entry(name) + child = make_child_entry(name) + @children = nil + child end def json_class @@ -90,6 +97,8 @@ class Chef dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) } elsif name == 'data_bags' dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) } + elsif name == 'acls' + dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) } else data_handler = case name when 'clients' @@ -106,8 +115,6 @@ class Chef Chef::ChefFS::DataHandler::GroupDataHandler.new when 'containers' Chef::ChefFS::DataHandler::ContainerDataHandler.new - when 'acls' - Chef::ChefFS::DataHandler::AclDataHandler.new else raise "Unknown top level path #{name}" end 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 5eb72657c5..0083ee4cfa 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 @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/server_api' require 'chef/chef_fs/file_system/acls_dir' require 'chef/chef_fs/file_system/base_fs_dir' require 'chef/chef_fs/file_system/rest_list_dir' @@ -23,7 +24,6 @@ require 'chef/chef_fs/file_system/cookbooks_dir' require 'chef/chef_fs/file_system/data_bags_dir' require 'chef/chef_fs/file_system/nodes_dir' require 'chef/chef_fs/file_system/environments_dir' -require 'chef/rest' require 'chef/chef_fs/data_handler/client_data_handler' require 'chef/chef_fs/data_handler/role_data_handler' require 'chef/chef_fs/data_handler/user_data_handler' @@ -34,7 +34,7 @@ class Chef module ChefFS module FileSystem class ChefServerRootDir < BaseFSDir - def initialize(root_name, chef_config) + def initialize(root_name, chef_config, options = {}) super("", nil) @chef_server_url = chef_config[:chef_server_url] @chef_username = chef_config[:node_name] @@ -42,6 +42,7 @@ class Chef @environment = chef_config[:environment] @repo_mode = chef_config[:repo_mode] @root_name = root_name + @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version end attr_reader :chef_server_url @@ -49,12 +50,21 @@ class Chef attr_reader :chef_private_key attr_reader :environment attr_reader :repo_mode + attr_reader :cookbook_version def fs_description "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}" end def rest + Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true) + end + + def get_json(path) + Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path) + end + + def chef_rest Chef::REST.new(chef_server_url, chef_username, chef_private_key) end diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb index cae29a1690..d7411e1c74 100644 --- a/lib/chef/chef_fs/file_system/cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb @@ -41,6 +41,7 @@ class Chef end else @cookbook_name = name + @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff end end @@ -125,7 +126,7 @@ class Chef def delete(recurse) if recurse begin - rest.delete_rest(api_path) + rest.delete(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" rescue Net::HTTPServerException @@ -190,7 +191,7 @@ class Chef old_retry_count = Chef::Config[:http_retry_count] begin Chef::Config[:http_retry_count] = 0 - @chef_object ||= Chef::CookbookVersion.json_create(Chef::ChefFS::RawRequest.raw_json(rest, api_path)) + @chef_object ||= Chef::CookbookVersion.json_create(root.get_json(api_path)) ensure Chef::Config[:http_retry_count] = old_retry_count end diff --git a/lib/chef/chef_fs/file_system/cookbook_file.rb b/lib/chef/chef_fs/file_system/cookbook_file.rb index e05c4aa614..7868322590 100644 --- a/lib/chef/chef_fs/file_system/cookbook_file.rb +++ b/lib/chef/chef_fs/file_system/cookbook_file.rb @@ -17,6 +17,7 @@ # require 'chef/chef_fs/file_system/base_fs_object' +require 'chef/http/simple' require 'digest/md5' class Chef @@ -35,16 +36,12 @@ class Chef end def read - old_sign_on_redirect = rest.sign_on_redirect - rest.sign_on_redirect = false begin - tmpfile = rest.get_rest(file[:url], true) + tmpfile = rest.streaming_request(file[:url]) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading #{file[:url]}: #{e}" rescue Net::HTTPServerException => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "#{e.message} retrieving #{file[:url]}" - ensure - rest.sign_on_redirect = old_sign_on_redirect end begin diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb index c6af933290..a58bfdd1f2 100644 --- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb +++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb @@ -18,10 +18,10 @@ require 'chef/chef_fs/file_system/rest_list_dir' require 'chef/chef_fs/file_system/cookbook_dir' -require 'chef/chef_fs/raw_request' require 'chef/chef_fs/file_system/operation_failed_error' require 'chef/chef_fs/file_system/cookbook_frozen_error' require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir' +require 'chef/mixin/file_class' require 'tmpdir' @@ -29,6 +29,9 @@ class Chef module ChefFS module FileSystem class CookbooksDir < RestListDir + + include Chef::Mixin::FileClass + def initialize(parent) super("cookbooks", parent) end @@ -50,19 +53,20 @@ class Chef @children ||= begin if Chef::Config[:versioned_cookbooks] result = [] - Chef::ChefFS::RawRequest.raw_json(rest, "#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks| + root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks| cookbooks['versions'].each do |cookbook_version| result << CookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self, :exists => true) end end else - result = Chef::ChefFS::RawRequest.raw_json(rest, api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) } + result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) } end result.sort_by(&:name) end end def create_child_from(other, options = {}) + @children = nil upload_cookbook_from(other, options) end @@ -92,7 +96,7 @@ class Chef proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}" # Make a symlink - File.symlink other.file_path, proxy_cookbook_path + file_class.symlink other.file_path, proxy_cookbook_path # Instantiate a proxy loader using the temporary symlink proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore) @@ -102,18 +106,29 @@ class Chef cookbook_to_upload.freeze_version if options[:freeze] # Instantiate a new uploader based on the proxy loader - uploader = Chef::CookbookUploader.new(cookbook_to_upload, proxy_cookbook_path, :force => options[:force], :rest => rest) + uploader = Chef::CookbookUploader.new(cookbook_to_upload, proxy_cookbook_path, :force => options[:force], :rest => root.chef_rest) with_actual_cookbooks_dir(temp_cookbooks_path) do upload_cookbook!(uploader) end + + # + # When the temporary directory is being deleted on + # windows, the contents of the symlink under that + # directory is also deleted. So explicitly remove + # the symlink without removing the original contents if we + # are running on windows + # + if Chef::Platform.windows? + Dir.rmdir proxy_cookbook_path + end end end def upload_unversioned_cookbook(other, options) cookbook_to_upload = other.chef_object cookbook_to_upload.freeze_version if options[:freeze] - uploader = Chef::CookbookUploader.new(cookbook_to_upload, other.parent.file_path, :force => options[:force], :rest => rest) + uploader = Chef::CookbookUploader.new(cookbook_to_upload, other.parent.file_path, :force => options[:force], :rest => root.chef_rest) with_actual_cookbooks_dir(other.parent.file_path) do upload_cookbook!(uploader) diff --git a/lib/chef/chef_fs/file_system/data_bag_dir.rb b/lib/chef/chef_fs/file_system/data_bag_dir.rb index 3814b94fac..212f76fdb9 100644 --- a/lib/chef/chef_fs/file_system/data_bag_dir.rb +++ b/lib/chef/chef_fs/file_system/data_bag_dir.rb @@ -52,7 +52,7 @@ class Chef raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively" end begin - rest.delete_rest(api_path) + rest.delete(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" rescue Net::HTTPServerException => e diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb index 46c3c21cf6..6d0685d3b7 100644 --- a/lib/chef/chef_fs/file_system/data_bags_dir.rb +++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb @@ -34,7 +34,7 @@ class Chef def children begin - @children ||= Chef::ChefFS::RawRequest.raw_json(rest, api_path).keys.sort.map do |entry| + @children ||= root.get_json(api_path).keys.sort.map do |entry| DataBagDir.new(entry, self, true) end rescue Timeout::Error => e @@ -54,7 +54,7 @@ class Chef def create_child(name, file_contents) begin - rest.post_rest(api_path, { 'name' => name }) + rest.post(api_path, { 'name' => name }) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating child '#{name}': #{e}" rescue Net::HTTPServerException => e @@ -64,6 +64,7 @@ class Chef raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "HTTP error creating child '#{name}': #{e}" end end + @children = nil DataBagDir.new(name, self, true) end end diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb index 82c52deae8..46d4eb5538 100644 --- a/lib/chef/chef_fs/file_system/file_system_entry.rb +++ b/lib/chef/chef_fs/file_system/file_system_entry.rb @@ -40,20 +40,24 @@ class Chef def children begin - @children ||= Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| FileSystemEntry.new(entry, self) } + Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| make_child(entry) } rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end end def create_child(child_name, file_contents=nil) - result = FileSystemEntry.new(child_name, self) + child = make_child(child_name) if file_contents - result.write(file_contents) + child.write(file_contents) else - Dir.mkdir(result.file_path) + begin + Dir.mkdir(child.file_path) + rescue Errno::EEXIST + end end - result + @children = nil + child end def dir? @@ -84,6 +88,12 @@ class Chef file.write(content) end end + + protected + + def make_child(child_name) + FileSystemEntry.new(child_name, self) + end end end end diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb index a7a901e304..06d4af705d 100644 --- a/lib/chef/chef_fs/file_system/multiplexed_dir.rb +++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb @@ -17,7 +17,7 @@ class Chef end def children - @children ||= begin + begin result = [] seen = {} # If multiple things have the same name, the first one wins. @@ -40,6 +40,7 @@ class Chef end def create_child(name, file_contents = nil) + @children = nil write_dir.create_child(name, file_contents) end end diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb index 82683e81ac..c3c48377cd 100644 --- a/lib/chef/chef_fs/file_system/nodes_dir.rb +++ b/lib/chef/chef_fs/file_system/nodes_dir.rb @@ -32,7 +32,7 @@ class Chef # Identical to RestListDir.children, except supports environments def children begin - @children ||= Chef::ChefFS::RawRequest.raw_json(rest, env_api_path).keys.sort.map do |key| + @children ||= root.get_json(env_api_path).keys.sort.map do |key| _make_child_entry("#{key}.json", true) end rescue Timeout::Error => e diff --git a/lib/chef/chef_fs/file_system/operation_failed_error.rb b/lib/chef/chef_fs/file_system/operation_failed_error.rb index 1af2d2dcff..28d170d628 100644 --- a/lib/chef/chef_fs/file_system/operation_failed_error.rb +++ b/lib/chef/chef_fs/file_system/operation_failed_error.rb @@ -27,6 +27,14 @@ class Chef @operation = operation end + def message + if cause && cause.is_a?(Net::HTTPExceptions) && cause.response.code == "400" + "#{super} cause: #{cause.response.body}" + else + super + end + end + attr_reader :operation end end diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb index 594fec8ab6..b7ee51d284 100644 --- a/lib/chef/chef_fs/file_system/rest_list_dir.rb +++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb @@ -45,7 +45,7 @@ class Chef def children begin - @children ||= Chef::ChefFS::RawRequest.raw_json(rest, api_path).keys.sort.map do |key| + @children ||= root.get_json(api_path).keys.sort.map do |key| _make_child_entry("#{key}.json", true) end rescue Timeout::Error => e @@ -76,7 +76,7 @@ class Chef end begin - rest.post_rest(api_path, object) + rest.post(api_path, object) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating '#{name}': #{e}" rescue Net::HTTPServerException => e @@ -89,6 +89,8 @@ class Chef end end + @children = nil + result end diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb index 6e6ad12438..0d5557de1d 100644 --- a/lib/chef/chef_fs/file_system/rest_list_entry.rb +++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb @@ -19,7 +19,6 @@ require 'chef/chef_fs/file_system/base_fs_object' require 'chef/chef_fs/file_system/not_found_error' require 'chef/chef_fs/file_system/operation_failed_error' -require 'chef/chef_fs/raw_request' require 'chef/role' require 'chef/node' @@ -68,7 +67,7 @@ class Chef def delete(recurse) begin - rest.delete_rest(api_path) + rest.delete(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}" rescue Net::HTTPServerException => e @@ -86,7 +85,8 @@ class Chef def _read_hash begin - json = Chef::ChefFS::RawRequest.raw_request(rest, api_path) + # Minimize the value (get rid of defaults) so the results don't look terrible + minimize_value(root.get_json(api_path)) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}" rescue Net::HTTPServerException => e @@ -96,8 +96,6 @@ class Chef raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}" end end - # Minimize the value (get rid of defaults) so the results don't look terrible - minimize_value(JSON.parse(json, :create_additions => false)) end def chef_object @@ -160,7 +158,7 @@ class Chef end begin - rest.put_rest(api_path, object) + rest.put(api_path, object) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}" rescue Net::HTTPServerException => e diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb index 5900c29f61..652c728550 100644 --- a/lib/chef/chef_fs/knife.rb +++ b/lib/chef/chef_fs/knife.rb @@ -35,37 +35,40 @@ class Chef def self.inherited(c) super + # Ensure we always get to do our includes, whether subclass calls deps or not c.deps do end - option :repo_mode, - :long => '--repo-mode MODE', - :description => "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything" + c.options.merge!(options) + end - option :chef_repo_path, - :long => '--chef-repo-path PATH', - :description => 'Overrides the location of chef repo. Default is specified by chef_repo_path in the config' + option :repo_mode, + :long => '--repo-mode MODE', + :description => "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything" - option :concurrency, - :long => '--concurrency THREADS', - :description => 'Maximum number of simultaneous requests to send (default: 10)' - end + option :chef_repo_path, + :long => '--chef-repo-path PATH', + :description => 'Overrides the location of chef repo. Default is specified by chef_repo_path in the config' + + option :concurrency, + :long => '--concurrency THREADS', + :description => 'Maximum number of simultaneous requests to send (default: 10)' def configure_chef super Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode] Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency] - # --chef-repo-path overrides all other paths + # --chef-repo-path forcibly overrides all other paths if config[:chef_repo_path] Chef::Config[:chef_repo_path] = config[:chef_repo_path] - Chef::ChefFS::Config::PATH_VARIABLES.each do |variable_name| - Chef::Config[variable_name.to_sym] = chef_repo_paths.map { |path| File.join(path, "#{variable_name[0..-6]}s") } + %w(acl client cookbook container data_bag environment group node role user).each do |variable_name| + Chef::Config.delete("#{variable_name}_path".to_sym) end end - @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config) + @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config) Chef::ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1 end @@ -91,17 +94,18 @@ class Chef end def pattern_args_from(args) + args.map { |arg| pattern_arg_from(arg) } + end + + def pattern_arg_from(arg) # TODO support absolute file paths and not just patterns? Too much? # Could be super useful in a world with multiple repo paths - args.map do |arg| - if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg) - # Check if chef repo path is specified to give a better error message - @chef_fs_config.require_chef_repo_path - ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path") - exit(1) - end - Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg) + if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg) + # Check if chef repo path is specified to give a better error message + ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path") + exit(1) end + Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg) end def format_path(entry) @@ -111,6 +115,19 @@ class Chef def parallelize(inputs, options = {}, &block) Chef::ChefFS::Parallelizer.parallelize(inputs, options, &block) end + + def discover_repo_dir(dir) + %w(.chef cookbooks data_bags environments roles).each do |subdir| + return dir if File.directory?(File.join(dir, subdir)) + end + # If this isn't it, check the parent + parent = File.dirname(dir) + if parent && parent != dir + discover_repo_dir(parent) + else + nil + end + end end end end diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb index 805b092b3a..9ef75ce2e5 100644 --- a/lib/chef/chef_fs/path_utils.rb +++ b/lib/chef/chef_fs/path_utils.rb @@ -82,6 +82,11 @@ class Chef end end + def self.descendant_of?(path, ancestor) + path[0,ancestor.length] == ancestor && + (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/) + end + def self.is_absolute?(path) path =~ /^#{regexp_path_separator}/ end diff --git a/lib/chef/chef_fs/raw_request.rb b/lib/chef/chef_fs/raw_request.rb deleted file mode 100644 index 43907282f6..0000000000 --- a/lib/chef/chef_fs/raw_request.rb +++ /dev/null @@ -1,79 +0,0 @@ -class Chef - module ChefFS - module RawRequest - def self.raw_json(chef_rest, api_path) - JSON.parse(raw_request(chef_rest, api_path), :create_additions => false) - end - - def self.raw_request(chef_rest, api_path) - api_request(chef_rest, :GET, chef_rest.create_url(api_path), {}, false) - end - - def self.api_request(chef_rest, method, url, headers={}, data=false) - json_body = data - # json_body = data ? Chef::JSONCompat.to_json(data) : nil - # Force encoding to binary to fix SSL related EOFErrors - # cf. http://tickets.opscode.com/browse/CHEF-2363 - # http://redmine.ruby-lang.org/issues/5233 - # json_body.force_encoding(Encoding::BINARY) if json_body.respond_to?(:force_encoding) - headers = build_headers(chef_rest, method, url, headers, json_body) - - chef_rest.retriable_rest_request(method, url, json_body, headers) do |rest_request| - response = rest_request.call {|r| r.read_body} - - response_body = chef_rest.decompress_body(response) - - if response.kind_of?(Net::HTTPSuccess) - response_body - elsif redirect_location = redirected_to(response) - if [:GET, :HEAD].include?(method) - chef_rest.follow_redirect do - api_request(chef_rest, method, chef_rest.create_url(redirect_location)) - end - else - raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." - end - else - # have to decompress the body before making an exception for it. But the body could be nil. - response.body.replace(chef_rest.decompress_body(response)) if response.body.respond_to?(:replace) - - if response['content-type'] =~ /json/ - exception = response_body - msg = "HTTP Request Returned #{response.code} #{response.message}: " - msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s) - Chef::Log.info(msg) - end - response.error! - end - end - end - - private - - # Copied so that it does not automatically inflate an object - # This is also used by knife raw_essentials - - ACCEPT_ENCODING = "Accept-Encoding".freeze - ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze - - def self.redirected_to(response) - return nil unless response.kind_of?(Net::HTTPRedirection) - # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this - return nil if response.kind_of?(Net::HTTPNotModified) - response['location'] - end - - def self.build_headers(chef_rest, method, url, headers={}, json_body=false, raw=false) - # headers = @default_headers.merge(headers) - #headers['Accept'] = "application/json" unless raw - headers['Accept'] = "application/json" unless raw - headers["Content-Type"] = 'application/json' if json_body - headers['Content-Length'] = json_body.bytesize.to_s if json_body - headers[Chef::REST::RESTRequest::ACCEPT_ENCODING] = Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE - headers.merge!(chef_rest.authentication_headers(method, url, json_body)) if chef_rest.sign_requests? - headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers] - headers - end - end - end -end diff --git a/lib/chef/client.rb b/lib/chef/client.rb index ceb0873079..04d6799988 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -198,6 +198,7 @@ class Chef Chef::Log.debug "Forked instance now converging" do_run rescue Exception + Chef::Log.error $! exit 1 else exit 0 @@ -206,7 +207,7 @@ class Chef Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}" result = Process.waitpid2(pid) handle_child_exit(result) - Chef::Log.debug "Forked child successfully reaped (pid: #{pid})" + Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})" true else do_run @@ -367,7 +368,10 @@ class Chef # === Returns # rest<Chef::REST>:: returns Chef::REST connection object def register(client_name=node_name, config=Chef::Config) - if File.exists?(config[:client_key]) + if !config[:client_key] + @events.skipping_registration(client_name, config) + Chef::Log.debug("Client key is unspecified - skipping registration") + elsif File.exists?(config[:client_key]) @events.skipping_registration(client_name, config) Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration") else @@ -467,13 +471,15 @@ class Chef # === Returns # true:: Always returns true. def do_run - runlock = RunLock.new(Chef::Config) + runlock = RunLock.new(Chef::Config.lockfile) runlock.acquire # don't add code that may fail before entering this section to be sure to release lock begin + runlock.save_pid run_context = nil @events.run_start(Chef::VERSION) Chef::Log.info("*** Chef #{Chef::VERSION} ***") + Chef::Log.info "Chef-client pid: #{Process.pid}" enforce_path_sanity run_ohai @events.ohai_completed(node) diff --git a/lib/chef/config.rb b/lib/chef/config.rb index e3211d232e..feb7e4ea94 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -29,6 +29,13 @@ class Chef extend Mixlib::Config + # Evaluates the given string as config. + # + # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file. + def self.from_string(string, filename) + self.instance_eval(string, filename, 1) + end + # Manages the chef secret session key # === Returns # <newkey>:: A new or retrieved session key @@ -50,8 +57,32 @@ class Chef configuration.inspect end + def self.on_windows? + RUBY_PLATFORM =~ /mswin|mingw|windows/ + end + + BACKSLASH = '\\'.freeze + + def self.platform_path_separator + if on_windows? + File::ALT_SEPARATOR || BACKSLASH + else + File::SEPARATOR + end + end + + def self.path_join(*args) + args = args.flatten + args.inject do |joined_path, component| + unless joined_path[-1,1] == platform_path_separator + joined_path += platform_path_separator + end + joined_path += component + end + end + def self.platform_specific_path(path) - if RUBY_PLATFORM =~ /mswin|mingw|windows/ + if on_windows? # turns /etc/chef/client.rb into C:/chef/client.rb system_drive = ENV['SYSTEMDRIVE'] ? ENV['SYSTEMDRIVE'] : "" path = File.join(system_drive, path.split('/')[2..-1]) @@ -65,32 +96,35 @@ class Chef formatters << [name, file_path] end - def self.formatters - @formatters ||= [] + # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.) + configurable(:config_file) + + default(:config_dir) do + if local_mode + path_join(user_home, ".chef#{platform_path_separator}") + else + config_file && ::File.dirname(config_file) + end end + # No config file (client.rb / knife.rb / etc.) will be loaded outside this path. + # Major use case is tests, where we don't want to load the user's config files. + configurable(:config_file_jail) + + default :formatters, [] + # Override the config dispatch to set the value of multiple server options simultaneously # # === Parameters # url<String>:: String to be set for all of the chef-server-api URL's # - config_attr_writer :chef_server_url do |url| - url = url.strip - configure do |c| - c[:chef_server_url] = url - end - url - end + configurable(:chef_server_url).writes_value { |url| url.strip } # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel. # So while this is basically identical to what method_missing would do, we pull # it up here and get a real method written so that things get dispatched # properly. - config_attr_writer :daemonize do |v| - configure do |c| - c[:daemonize] = v - end - end + configurable(:daemonize).writes_value { |v| v } # Override the config dispatch to set the value of log_location configuration option # @@ -108,50 +142,148 @@ class Chef rescue Errno::ENOENT raise Chef::Exceptions::ConfigurationError, "Failed to open or create log file at #{location.to_str}" end - f + f + end + end + + # The root where all local chef object data is stored. cookbooks, data bags, + # environments are all assumed to be in separate directories under this. + # chef-solo uses these directories for input data. knife commands + # that upload or download files (such as knife upload, knife role from file, + # etc.) work. + default :chef_repo_path do + if self.configuration[:cookbook_path] + if self.configuration[:cookbook_path].kind_of?(String) + File.expand_path('..', self.configuration[:cookbook_path]) + else + self.configuration[:cookbook_path].map do |path| + File.expand_path('..', path) + end + end + else + platform_specific_path("/var/chef") + end + end + + def self.find_chef_repo_path(cwd) + # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it. + # This allows us to run config-free. + path = cwd + until File.directory?(path_join(path, "cookbooks")) + new_path = File.expand_path('..', path) + if new_path == path + Chef::Log.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.") + return Dir.pwd + end + path = new_path + end + Chef::Log.info("Auto-discovered chef repository at #{path}") + path + end + + def self.derive_path_from_chef_repo_path(child_path) + if chef_repo_path.kind_of?(String) + path_join(chef_repo_path, child_path) + else + chef_repo_path.map { |path| path_join(path, child_path)} end end + # Location of acls on disk. String or array of strings. + # Defaults to <chef_repo_path>/acls. + # Only applies to Enterprise Chef commands. + default(:acl_path) { derive_path_from_chef_repo_path('acls') } + + # Location of clients on disk. String or array of strings. + # Defaults to <chef_repo_path>/acls. + default(:client_path) { derive_path_from_chef_repo_path('clients') } + + # Location of cookbooks on disk. String or array of strings. + # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path + # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]). + default(:cookbook_path) do + if self.configuration[:chef_repo_path] + derive_path_from_chef_repo_path('cookbooks') + else + Array(derive_path_from_chef_repo_path('cookbooks')).flatten + + Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten + end + end + + # Location of containers on disk. String or array of strings. + # Defaults to <chef_repo_path>/containers. + # Only applies to Enterprise Chef commands. + default(:container_path) { derive_path_from_chef_repo_path('containers') } + + # Location of data bags on disk. String or array of strings. + # Defaults to <chef_repo_path>/data_bags. + default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') } + + # Location of environments on disk. String or array of strings. + # Defaults to <chef_repo_path>/environments. + default(:environment_path) { derive_path_from_chef_repo_path('environments') } + + # Location of groups on disk. String or array of strings. + # Defaults to <chef_repo_path>/groups. + # Only applies to Enterprise Chef commands. + default(:group_path) { derive_path_from_chef_repo_path('groups') } + + # Location of nodes on disk. String or array of strings. + # Defaults to <chef_repo_path>/nodes. + default(:node_path) { derive_path_from_chef_repo_path('nodes') } + + # Location of roles on disk. String or array of strings. + # Defaults to <chef_repo_path>/roles. + default(:role_path) { derive_path_from_chef_repo_path('roles') } + + # Location of users on disk. String or array of strings. + # Defaults to <chef_repo_path>/users. + # Does not apply to Enterprise Chef commands. + default(:user_path) { derive_path_from_chef_repo_path('users') } + # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity - enforce_path_sanity(true) + default :enforce_path_sanity, true # Formatted Chef Client output is a beta feature, disabled by default: - formatter "null" + default :formatter, "null" # The number of times the client should retry when registering with the server - client_registration_retries 5 - - # Where the cookbooks are located. Meaning is somewhat context dependent between - # knife, chef-client, and chef-solo. - cookbook_path [ platform_specific_path("/var/chef/cookbooks"), - platform_specific_path("/var/chef/site-cookbooks") ] + default :client_registration_retries, 5 # An array of paths to search for knife exec scripts if they aren't in the current directory - script_path [] + default :script_path, [] + + # The root of all caches (checksums, cache and backup). If local mode is on, + # this is under the user's home directory. + default(:cache_path) do + if local_mode + "#{config_dir}local-mode-cache" + else + platform_specific_path("/var/chef") + end + end # Where cookbook files are stored on the server (by content checksum) - checksum_path "/var/chef/checksums" + default(:checksum_path) { path_join(cache_path, "checksums") } # Where chef's cache files should be stored - file_cache_path platform_specific_path("/var/chef/cache") + default(:file_cache_path) { path_join(cache_path, "cache") } - # By default, chef-client (or solo) creates a lockfile in - # `file_cache_path`/chef-client-running.pid - # If `lockfile` is explicitly set, this path will be used instead. + # Where backups of chef-managed files should go + default(:file_backup_path) { path_join(cache_path, "backup") } + + # The chef-client (or solo) lockfile. # # If your `file_cache_path` resides on a NFS (or non-flock()-supporting # fs), it's recommended to set this to something like # '/tmp/chef-client-running.pid' - lockfile nil - - # Where backups of chef-managed files should go - file_backup_path platform_specific_path("/var/chef/backup") + default(:lockfile) { path_join(file_cache_path, "chef-client-running.pid") } ## Daemonization Settings ## # What user should Chef run as? - user nil - group nil - umask 0022 + default :user, nil + default :group, nil + default :umask, 0022 # Valid log_levels are: # * :debug @@ -164,57 +296,76 @@ class Chef # in a console), the log level is set to :warn, and output formatters are # used as the primary mode of output. When a tty is not available, the # logger is the primary mode of output, and the log level is set to :info - log_level :auto + default :log_level, :auto # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty - force_formatter false + default :force_formatter, false # Using `force_logger` causes chef to default to logger output when STDOUT is a tty - force_logger false - - http_retry_count 5 - http_retry_delay 5 - interval nil - json_attribs nil - log_location STDOUT + default :force_logger, false + + default :http_retry_count, 5 + default :http_retry_delay, 5 + default :interval, nil + default :once, nil + default :json_attribs, nil + default :log_location, STDOUT # toggle info level log items that can create a lot of output - verbose_logging true - node_name nil - diff_disabled false - diff_filesize_threshold 10000000 - diff_output_threshold 1000000 - - pid_file nil - - chef_server_url "https://localhost:443" - - rest_timeout 300 - yum_timeout 900 - solo false - splay nil - why_run false - color false - client_fork true - enable_reporting true - enable_reporting_url_fatals false + default :verbose_logging, true + default :node_name, nil + default :diff_disabled, false + default :diff_filesize_threshold, 10000000 + default :diff_output_threshold, 1000000 + default :local_mode, false + + default :pid_file, nil + + config_context :chef_zero do + config_strict_mode true + default(:enabled) { Chef::Config.local_mode } + default :port, 8889 + end + default :chef_server_url, "https://localhost:443" + + default :rest_timeout, 300 + default :yum_timeout, 900 + default :solo, false + default :splay, nil + default :why_run, false + default :color, false + default :client_fork, true + default :enable_reporting, true + default :enable_reporting_url_fatals, false # Set these to enable SSL authentication / mutual-authentication # with the server - ssl_client_cert nil - ssl_client_key nil - ssl_verify_mode :verify_none - ssl_ca_path nil - ssl_ca_file nil - # Where should chef-solo look for role files? - role_path platform_specific_path("/var/chef/roles") + # Client side SSL cert/key for mutual auth + default :ssl_client_cert, nil + default :ssl_client_key, nil + + # Whether or not to verify the SSL cert for all HTTPS requests. If set to + # :verify_peer, all HTTPS requests will be validated regardless of other + # SSL verification settings. + default :ssl_verify_mode, :verify_none - data_bag_path platform_specific_path("/var/chef/data_bags") + # Whether or not to verify the SSL cert for HTTPS requests to the Chef + # server API. If set to `true`, the server's cert will be validated + # regardless of the :ssl_verify_mode setting. + default :verify_api_cert, false - environment_path platform_specific_path("/var/chef/environments") + # Path to the default CA bundle files. + default :ssl_ca_path, nil + default :ssl_ca_file, nil + + # A directory that contains additional SSL certificates to trust. Any + # certificates in this directory will be added to whatever CA bundle ruby + # is using. Use this to add self-signed certs for your Chef Server or local + # HTTP file servers. + default(:trusted_certs_dir) { config_dir && path_join(config_dir, "trusted_certs") } # Where should chef-solo download recipes from? - recipe_url nil + default :recipe_url, nil # Sets the version of the signed header authentication protocol to use (see # the 'mixlib-authorization' project for more detail). Currently, versions @@ -230,7 +381,7 @@ class Chef # # In the future, this configuration option may be replaced with an # automatic negotiation scheme. - authentication_protocol_version "1.0" + default :authentication_protocol_version, "1.0" # This key will be used to sign requests to the Chef server. This location # must be writable by Chef during initial setup when generating a client @@ -238,17 +389,21 @@ class Chef # # The chef-server will look up the public key for the client using the # `node_name` of the client. - client_key platform_specific_path("/etc/chef/client.pem") + # + # If chef-zero is enabled, this defaults to nil (no authentication). + default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") } # This secret is used to decrypt encrypted data bag items. - encrypted_data_bag_secret platform_specific_path("/etc/chef/encrypted_data_bag_secret") - - # We have to check for the existence of the default file before setting it - # since +Chef::Config[:encrypted_data_bag_secret]+ is read by older - # bootstrap templates to determine if the local secret should be uploaded to - # node being bootstrapped. This should be removed in Chef 12. - unless File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret")) - encrypted_data_bag_secret(nil) + default(:encrypted_data_bag_secret) do + # We have to check for the existence of the default file before setting it + # since +Chef::Config[:encrypted_data_bag_secret]+ is read by older + # bootstrap templates to determine if the local secret should be uploaded to + # node being bootstrapped. This should be removed in Chef 12. + if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret")) + platform_specific_path("/etc/chef/encrypted_data_bag_secret") + else + nil + end end # As of Chef 11.0, version "1" is the default encrypted data bag item @@ -256,7 +411,7 @@ class Chef # To maintain compatibility, versions other than 1 must be opt-in. # # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure: - data_bag_encrypt_version 1 + default :data_bag_encrypt_version, 1 # When reading data bag items, any supported version is accepted. However, # if all encrypted data bags have been generated with the version 2 format, @@ -264,7 +419,7 @@ class Chef # security. For example, the version 2 format is identical to version 1 # except for the addition of an HMAC, so an attacker with MITM capability # could downgrade an encrypted data bag to version 1 as part of an attack. - data_bag_decrypt_minimum_version 0 + default :data_bag_decrypt_minimum_version, 0 # If there is no file in the location given by `client_key`, chef-client # will temporarily use the "validator" identity to generate one. If the @@ -272,43 +427,53 @@ class Chef # chef-client will not be able to authenticate to the server. # # The `validation_key` is never used if the `client_key` exists. - validation_key platform_specific_path("/etc/chef/validation.pem") - validation_client_name "chef-validator" + # + # If chef-zero is enabled, this defaults to nil (no authentication). + default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") } + default :validation_client_name, "chef-validator" # Zypper package provider gpg checks. Set to true to enable package # gpg signature checking. This will be default in the # future. Setting to false disables the warnings. # Leaving this set to nil or false is a security hazard! - zypper_check_gpg nil + default :zypper_check_gpg, nil # Report Handlers - report_handlers [] + default :report_handlers, [] # Exception Handlers - exception_handlers [] + default :exception_handlers, [] # Start handlers - start_handlers [] + default :start_handlers, [] # Syntax Check Cache. Knife keeps track of files that is has already syntax # checked by storing files in this directory. `syntax_check_cache_path` is # the new (and preferred) configuration setting. If not set, knife will - # fall back to using cache_options[:path]. - # - # Because many users will have knife configs with cache_options (generated - # by `knife configure`), the default for now is to *not* set - # syntax_check_cache_path, and thus fallback to cache_options[:path]. We - # leave that value to the same default as was previously set. - syntax_check_cache_path nil + # fall back to using cache_options[:path], which is deprecated but exists in + # many client configs generated by pre-Chef-11 bootstrappers. + default(:syntax_check_cache_path) { cache_options[:path] } # Deprecated: - cache_options({ :path => platform_specific_path("/var/chef/cache/checksums") }) + default(:cache_options) { { :path => path_join(file_cache_path, "checksums") } } # Set to false to silence Chef 11 deprecation warnings: - chef11_deprecation_warnings true - - # Arbitrary knife configuration data - knife Hash.new + default :chef11_deprecation_warnings, true + + # knife configuration data + config_context :knife do + default :ssh_port, nil + default :ssh_user, nil + default :ssh_attribute, nil + default :ssh_gateway, nil + default :bootstrap_version, nil + default :bootstrap_proxy, nil + default :identity_file, nil + default :host_key_verify, nil + default :forward_agent, nil + default :sort_status_reverse, nil + default :hints, {} + end # Those lists of regular expressions define what chef considers a # valid user and group name @@ -316,31 +481,31 @@ class Chef # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+' - user_valid_regex [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] - group_valid_regex [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] + default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] + default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] - fatal_windows_admin_check false + default :fatal_windows_admin_check, false else - user_valid_regex [ /^([-a-zA-Z0-9_.]+[\\@]?[-a-zA-Z0-9_.]+)$/, /^\d+$/ ] - group_valid_regex [ /^([-a-zA-Z0-9_.\\@^ ]+)$/, /^\d+$/ ] + default :user_valid_regex, [ /^([-a-zA-Z0-9_.]+[\\@]?[-a-zA-Z0-9_.]+)$/, /^\d+$/ ] + default :group_valid_regex, [ /^([-a-zA-Z0-9_.\\@^ ]+)$/, /^\d+$/ ] end # returns a platform specific path to the user home dir windows_home_path = ENV['SYSTEMDRIVE'] + ENV['HOMEPATH'] if ENV['SYSTEMDRIVE'] && ENV['HOMEPATH'] - user_home(ENV['HOME'] || windows_home_path || ENV['USERPROFILE']) + default :user_home, (ENV['HOME'] || windows_home_path || ENV['USERPROFILE']) # Enable file permission fixup for selinux. Fixup will be done # only if selinux is enabled in the system. - enable_selinux_file_permission_fixup true + default :enable_selinux_file_permission_fixup, true # Use atomic updates (i.e. move operation) while updating contents # of the files resources. When set to false copy operation is # used to update files. - file_atomic_update true + default :file_atomic_update, true # If false file staging is will be done via tempfiles that are # created under ENV['TMP'] otherwise tempfiles will be created in # the directory that files are going to reside. - file_staging_uses_destdir false + default :file_staging_uses_destdir, false end end diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb new file mode 100644 index 0000000000..80a313590b --- /dev/null +++ b/lib/chef/config_fetcher.rb @@ -0,0 +1,79 @@ +require 'chef/application' +require 'chef/chef_fs/path_utils' +require 'chef/http/simple' +require 'chef/json_compat' + +class Chef + class ConfigFetcher + + attr_reader :config_location + attr_reader :config_file_jail + + def initialize(config_location, config_file_jail) + @config_location = config_location + @config_file_jail = config_file_jail + end + + def fetch_json + config_data = read_config + begin + Chef::JSONCompat.from_json(config_data) + rescue JSON::ParserError => error + Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, 2) + end + end + + def read_config + if remote_config? + fetch_remote_config + else + read_local_config + end + end + + def fetch_remote_config + http.get("") + rescue SocketError, SystemCallError, Net::HTTPServerException => error + Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}", 2) + end + + def read_local_config + ::File.read(config_location) + rescue Errno::ENOENT => error + Chef::Application.fatal!("Cannot load configuration from #{config_location}", 2) + rescue Errno::EACCES => error + Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", 2) + end + + def config_missing? + return false if remote_config? + + # Check if the config file exists, and check if it is underneath the config file jail + begin + real_config_file = Pathname.new(config_location).realpath.to_s + rescue Errno::ENOENT + return true + end + + # If realpath succeeded, the file exists + return false if !config_file_jail + + begin + real_jail = Pathname.new(config_file_jail).realpath.to_s + rescue Errno::ENOENT + Chef::Log.warn("Config file jail #{config_file_jail} does not exist: will not load any config file.") + return true + end + + !Chef::ChefFS::PathUtils.descendant_of?(real_config_file, real_jail) + end + + def http + Chef::HTTP::Simple.new(config_location) + end + + def remote_config? + !!(config_location =~ %r{^(http|https)://}) + end + end +end diff --git a/lib/chef/cookbook/file_vendor.rb b/lib/chef/cookbook/file_vendor.rb index 38eab185ca..406f23ca25 100644 --- a/lib/chef/cookbook/file_vendor.rb +++ b/lib/chef/cookbook/file_vendor.rb @@ -7,9 +7,9 @@ # 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. @@ -27,14 +27,14 @@ class Chef def self.on_create(&block) @instance_creator = block end - + # Factory method that creates the appropriate kind of # Cookbook::FileVendor to serve the contents of the manifest def self.create_from_manifest(manifest) raise "Must call Chef::Cookbook::FileVendor.on_create before calling create_from_manifest factory" unless defined?(@instance_creator) @instance_creator.call(manifest) end - + # Gets the on-disk location for the given cookbook file. # # Subclasses are responsible for determining exactly how the @@ -42,7 +42,7 @@ class Chef def get_filename(filename) raise NotImplemented, "Subclasses must implement this method" end - + end end end diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb index 18368bd99f..b9b32c8224 100644 --- a/lib/chef/cookbook/metadata.rb +++ b/lib/chef/cookbook/metadata.rb @@ -110,8 +110,8 @@ class Chef @version = Version.new "0.0.0" if cookbook @recipes = cookbook.fully_qualified_recipe_names.inject({}) do |r, e| - e = self.name if e =~ /::default$/ - r[e] = "" + e = self.name.to_s if e =~ /::default$/ + r[e] ||= "" self.provides e r end diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb index 0e757074e3..59888e2ba3 100644 --- a/lib/chef/cookbook/syntax_check.rb +++ b/lib/chef/cookbook/syntax_check.rb @@ -6,9 +6,9 @@ # 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. @@ -39,19 +39,9 @@ class Chef attr_reader :cache_path # Create a new PersistentSet. Values in the set are persisted by - # creating a file in the +cache_path+ directory. If not given, the - # value of Chef::Config[:syntax_check_cache_path] is used; if that - # value is not configured, the value of - # Chef::Config[:cache_options][:path] is used. - #-- - # history: prior to Chef 11, the cache implementation was based on - # moneta and configured via cache_options[:path]. Knife configs - # generated with Chef 11 will have `syntax_check_cache_path`, but older - # configs will have `cache_options[:path]`. `cache_options` is marked - # deprecated in chef/config.rb but doesn't currently trigger a warning. - # See also: CHEF-3715 - def initialize(cache_path=nil) - @cache_path = cache_path || Chef::Config[:syntax_check_cache_path] || Chef::Config[:cache_options][:path] + # creating a file in the +cache_path+ directory. + def initialize(cache_path=Chef::Config[:syntax_check_cache_path]) + @cache_path = cache_path @cache_path_created = false end @@ -137,7 +127,7 @@ class Chef end def untested_template_files - template_files.reject do |file| + template_files.reject do |file| if validated?(file) Chef::Log.debug("Template #{file} is unchanged, skipping syntax check") true diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb index abb5499042..92193fee33 100644 --- a/lib/chef/cookbook_site_streaming_uploader.rb +++ b/lib/chef/cookbook_site_streaming_uploader.rb @@ -208,8 +208,11 @@ class Chef @parts.inject(0) {|size, part| size + part.size} end - def read(how_much) - return nil if @part_no >= @parts.size + def read(how_much, dst_buf = nil) + if @part_no >= @parts.size + dst_buf.replace('') if dst_buf + return dst_buf + end how_much_current_part = @parts[@part_no].size - @part_offset @@ -228,15 +231,16 @@ class Chef @part_no += 1 @part_offset = 0 next_part = read(how_much_next_part) - current_part + if next_part + result = current_part + if next_part next_part else '' end else @part_offset += how_much_current_part - current_part + result = current_part end + dst_buf ? dst_buf.replace(result || '') : result end end diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb index 9ba5b2bd8b..3ead26e56d 100644 --- a/lib/chef/cookbook_uploader.rb +++ b/lib/chef/cookbook_uploader.rb @@ -137,15 +137,17 @@ class Chef timestamp = Time.now.utc.iso8601 file_contents = File.open(file, "rb") {|f| f.read} # TODO - 5/28/2010, cw: make signing and sending the request streaming - sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object( - :http_method => :put, - :path => URI.parse(url).path, - :body => file_contents, - :timestamp => timestamp, - :user_id => rest.client_name - ) headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' } - headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key))) + if rest.signing_key + sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object( + :http_method => :put, + :path => URI.parse(url).path, + :body => file_contents, + :timestamp => timestamp, + :user_id => rest.client_name + ) + headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key))) + end begin RestClient::Resource.new(url, :headers=>headers, :timeout=>1800, :open_timeout=>1800).put(file_contents) diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 4e8b2a048e..5bd0ca064c 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -265,6 +265,18 @@ class Chef end end + # Query whether a template file +template_filename+ is available. File + # specificity for the given +node+ is obeyed in the lookup. + def has_template_for_node?(node, template_filename) + !!find_preferred_manifest_record(node, :templates, template_filename) + end + + # Query whether a cookbook_file file +cookbook_filename+ is available. File + # specificity for the given +node+ is obeyed in the lookup. + def has_cookbook_file_for_node?(node, cookbook_filename) + !!find_preferred_manifest_record(node, :files, cookbook_filename) + end + # Determine the most specific manifest record for the given # segment/filename, given information in the node. Throws # FileNotFound if there is no such segment and filename in the @@ -278,14 +290,7 @@ class Chef # :checksum => "1234" # } def preferred_manifest_record(node, segment, filename) - preferences = preferences_for_path(node, segment, filename) - - # ensure that we generate the manifest, which will also generate - # @manifest_records_by_path - manifest - - # in order of prefernce, look for the filename in the manifest - found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] } + found_pref = find_preferred_manifest_record(node, segment, filename) if found_pref @manifest_records_by_path[found_pref] else @@ -567,6 +572,17 @@ class Chef private + def find_preferred_manifest_record(node, segment, filename) + preferences = preferences_for_path(node, segment, filename) + + # ensure that we generate the manifest, which will also generate + # @manifest_records_by_path + manifest + + # in order of prefernce, look for the filename in the manifest + preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] } + end + # For each filename, produce a mapping of base filename (i.e. recipe name # or attribute file) to on disk location def filenames_by_name(filenames) diff --git a/lib/chef/daemon.rb b/lib/chef/daemon.rb index 9a3d5a884a..e5abca29d8 100644 --- a/lib/chef/daemon.rb +++ b/lib/chef/daemon.rb @@ -6,9 +6,9 @@ # 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. @@ -18,12 +18,14 @@ # I love you Merb (lib/merb-core/server.rb) require 'chef/config' +require 'chef/run_lock' require 'etc' class Chef class Daemon class << self attr_accessor :name + attr_accessor :runlock # Daemonize the current process, managing pidfiles and process uid/gid # @@ -32,9 +34,9 @@ class Chef # def daemonize(name) @name = name - pid = pid_from_file - unless running? - remove_pid_file() + @runlock = RunLock.new(pid_file) + if runlock.test + # We've acquired the daemon lock. Now daemonize. Chef::Log.info("Daemonizing..") begin exit if fork @@ -45,53 +47,15 @@ class Chef $stdin.reopen("/dev/null") $stdout.reopen("/dev/null", "a") $stderr.reopen($stdout) - save_pid_file - at_exit { remove_pid_file } + runlock.save_pid rescue NotImplementedError => e Chef::Application.fatal!("There is no fork: #{e.message}") end else - Chef::Application.fatal!("Chef is already running pid #{pid}") - end - end - - # Check if Chef is running based on the pid_file - # ==== Returns - # Boolean:: - # True if Chef is running - # False if Chef is not running - # - def running? - if pid_from_file.nil? - false - else - Process.kill(0, pid_from_file) - true - end - rescue Errno::ESRCH, Errno::ENOENT - false - rescue Errno::EACCES => e - Chef::Application.fatal!("You don't have access to the PID file at #{pid_file}: #{e.message}") - end - - # Check if this process if forked from a Chef daemon - # ==== Returns - # Boolean:: - # True if this process is forked - # False if this process is not forked - # - def forked? - if running? and Process.ppid == pid_from_file.to_i - # chef daemon is running and this process is a child of it - true - elsif not running? and Process.ppid == 1 - # an orphaned fork, its parent becomes init, launchd, etc. after chef daemon dies - true - else - false + Chef::Application.fatal!("Chef is already running pid #{pid_from_file}") end end - + # Gets the pid file for @name # ==== Returns # String:: @@ -99,7 +63,7 @@ class Chef def pid_file Chef::Config[:pid_file] or "/tmp/#{@name}.pid" end - + # Suck the pid out of pid_file # ==== Returns # Integer:: @@ -112,31 +76,6 @@ class Chef rescue Errno::ENOENT, Errno::EACCES nil end - - # Store the PID on the filesystem - # This uses the Chef::Config[:pid_file] option, or "/tmp/name.pid" otherwise - # - def save_pid_file - file = pid_file - begin - FileUtils.mkdir_p(File.dirname(file)) - rescue Errno::EACCES => e - Chef::Application.fatal!("Failed store pid in #{File.dirname(file)}, permission denied: #{e.message}") - end - - begin - File.open(file, "w") { |f| f.write(Process.pid.to_s) } - rescue Errno::EACCES => e - Chef::Application.fatal!("Couldn't write to pidfile #{file}, permission denied: #{e.message}") - end - end - - # Delete the PID from the filesystem - def remove_pid_file - if not forked? then - FileUtils.rm(pid_file) if File.exists?(pid_file) - end - end # Change process user/group to those specified in Chef::Config # @@ -151,7 +90,7 @@ class Chef _change_privilege(Chef::Config[:user]) end end - + # Change privileges of the process to be the specified user and group # # ==== Parameters @@ -170,14 +109,14 @@ class Chef Chef::Application.fatal!("Failed to get UID for user #{user}, does it exist? #{e.message}") return false end - + begin target_gid = Etc.getgrnam(group).gid rescue ArgumentError => e Chef::Application.fatal!("Failed to get GID for group #{group}, does it exist? #{e.message}") return false end - + if (uid != target_uid) or (gid != target_gid) Process.initgroups(user, target_gid) Process::GID.change_privilege(target_gid) diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb index dcc01ec743..5994e6f8d1 100644 --- a/lib/chef/data_bag.rb +++ b/lib/chef/data_bag.rb @@ -129,11 +129,10 @@ class Chef if Chef::Config[:why_run] Chef::Log.warn("In whyrun mode, so NOT performing data bag save.") else - chef_server_rest.put_rest("data/#{@name}", self) + create end rescue Net::HTTPServerException => e - raise e unless e.response.code == "404" - chef_server_rest.post_rest("data", self) + raise e unless e.response.code == "409" end self end diff --git a/lib/chef/dsl/include_recipe.rb b/lib/chef/dsl/include_recipe.rb index b4d076da10..fc95e38c75 100644 --- a/lib/chef/dsl/include_recipe.rb +++ b/lib/chef/dsl/include_recipe.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index 494d3cee79..82beefeec9 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -295,7 +295,7 @@ class Chef # Called when a provider makes an assumption after a failed assertion # in whyrun mode, in order to allow execution to continue - def whyrun_assumption(action, resource, message) + def whyrun_assumption(action, resource, message) end ## TODO: deprecation warning. this way we can queue them up and present diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb index 82da69a60c..c172a406d8 100644 --- a/lib/chef/event_dispatch/dispatcher.rb +++ b/lib/chef/event_dispatch/dispatcher.rb @@ -23,7 +23,7 @@ class Chef # define the forwarding in one go: # - # Define a method that will be forwarded to all + # Define a method that will be forwarded to all def self.def_forwarding_method(method_name) class_eval(<<-END_OF_METHOD, __FILE__, __LINE__) def #{method_name}(*args) diff --git a/lib/chef/file_access_control/windows.rb b/lib/chef/file_access_control/windows.rb index 35a16337ab..32ac2996bd 100644 --- a/lib/chef/file_access_control/windows.rb +++ b/lib/chef/file_access_control/windows.rb @@ -220,7 +220,7 @@ class Chef flags = 0 # - # Configure child inheritence only if the the resource is some + # Configure child inheritence only if the resource is some # type of a directory. # if resource.is_a? Chef::Resource::Directory diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index 300311819f..e403ed9f5b 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -188,13 +188,13 @@ class Chef def resource_update_applied(resource, action, update) prefix = Chef::Config[:why_run] ? "Would " : "" Array(update).each do |line| - next if line.nil? + next if line.nil? output_record line if line.kind_of? String @output.color "\n - #{prefix}#{line}", :green - elsif line.kind_of? Array - # Expanded output - delta - # @todo should we have a resource_update_delta callback? + elsif line.kind_of? Array + # Expanded output - delta + # @todo should we have a resource_update_delta callback? line.each do |detail| @output.color "\n #{detail}", :white end @@ -216,7 +216,7 @@ class Chef # Called when a provider makes an assumption after a failed assertion # in whyrun mode, in order to allow execution to continue - def whyrun_assumption(action, resource, message) + def whyrun_assumption(action, resource, message) return unless message [ message ].flatten.each do |line| @output.color("\n * #{line}", :yellow) diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb index bb5379ed3f..cb64955961 100644 --- a/lib/chef/formatters/error_inspectors/api_error_formatting.rb +++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb index 1fa8a70b52..93328adbe3 100644 --- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb index 054984a50e..56a55a296b 100644 --- a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb +++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb index 7168ac0edb..e257ee30c0 100644 --- a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb +++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb index 9ad56bb70b..6f1f71b8f9 100644 --- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb +++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb @@ -7,9 +7,9 @@ # 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. diff --git a/lib/chef/handler/json_file.rb b/lib/chef/handler/json_file.rb index 977c5a2c92..3473db1838 100644 --- a/lib/chef/handler/json_file.rb +++ b/lib/chef/handler/json_file.rb @@ -41,11 +41,11 @@ class Chef build_report_dir savetime = Time.now.strftime("%Y%m%d%H%M%S") File.open(File.join(config[:path], "chef-run-report-#{savetime}.json"), "w") do |file| - + #ensure start time and end time are output in the json properly in the event activesupport happens to be on the system run_data = data run_data[:start_time] = run_data[:start_time].to_s - run_data[:end_time] = run_data[:end_time].to_s + run_data[:end_time] = run_data[:end_time].to_s file.puts Chef::JSONCompat.to_json_pretty(run_data) end diff --git a/lib/chef/http.rb b/lib/chef/http.rb new file mode 100644 index 0000000000..2b2466843e --- /dev/null +++ b/lib/chef/http.rb @@ -0,0 +1,386 @@ +#-- +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Thom May (<thom@clearairturbulence.org>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'net/https' +require 'uri' +require 'chef/http/basic_client' +require 'chef/monkey_patches/string' +require 'chef/monkey_patches/net_http' +require 'chef/config' +require 'chef/exceptions' + +class Chef + + # == Chef::HTTP + # Basic HTTP client, with support for adding features via middleware + class HTTP + + # Class for applying middleware behaviors to streaming + # responses. Collects stream handlers (if any) from each + # middleware. When #handle_chunk is called, the chunk gets + # passed to all handlers in turn for processing. + class StreamHandler + def initialize(middlewares, response) + middlewares = middlewares.flatten + @stream_handlers = [] + middlewares.each do |middleware| + stream_handler = middleware.stream_response_handler(response) + @stream_handlers << stream_handler unless stream_handler.nil? + end + end + + def handle_chunk(next_chunk) + @stream_handlers.inject(next_chunk) do |chunk, handler| + handler.handle_chunk(chunk) + end + end + + end + + + def self.middlewares + @middlewares ||= [] + end + + def self.use(middleware_class) + middlewares << middleware_class + end + + attr_reader :url + attr_reader :sign_on_redirect + attr_reader :redirect_limit + + attr_reader :middlewares + + # Create a HTTP client object. The supplied +url+ is used as the base for + # all subsequent requests. For example, when initialized with a base url + # http://localhost:4000, a call to +get+ with 'nodes' will make an + # HTTP GET request to http://localhost:4000/nodes + def initialize(url, options={}) + @url = url + @default_headers = options[:headers] || {} + @sign_on_redirect = true + @redirects_followed = 0 + @redirect_limit = 10 + + @middlewares = [] + self.class.middlewares.each do |middleware_class| + @middlewares << middleware_class.new(options) + end + end + + # Send an HTTP HEAD request to the path + # + # === Parameters + # path:: path part of the request URL + def head(path, headers={}) + request(:HEAD, path, headers) + end + + # Send an HTTP GET request to the path + # + # === Parameters + # path:: The path to GET + def get(path, headers={}) + request(:GET, path, headers) + end + + # Send an HTTP PUT request to the path + # + # === Parameters + # path:: path part of the request URL + def put(path, json, headers={}) + request(:PUT, path, headers, json) + end + + # Send an HTTP POST request to the path + # + # === Parameters + # path:: path part of the request URL + def post(path, json, headers={}) + request(:POST, path, headers, json) + end + + # Send an HTTP DELETE request to the path + # + # === Parameters + # path:: path part of the request URL + def delete(path, headers={}) + request(:DELETE, path, headers) + end + + # Makes an HTTP request to +path+ with the given +method+, +headers+, and + # +data+ (if applicable). + def request(method, path, headers={}, data=false) + url = create_url(path) + method, url, headers, data = apply_request_middleware(method, url, headers, data) + + response, rest_request, return_value = send_http_request(method, url, headers, data) + response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value) + 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 + + # Makes a streaming download request, streaming the response body to a + # tempfile. If a block is given, the tempfile is passed to the block and + # the tempfile will automatically be unlinked after the block is executed. + # + # If no block is given, the tempfile is returned, which means it's up to + # you to unlink the tempfile when you're done with it. + def streaming_request(path, headers={}, &block) + url = create_url(path) + response, rest_request, return_value = nil, nil, nil + tempfile = nil + + method = :GET + method, url, headers, data = apply_request_middleware(method, url, headers, data) + + response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response| + if http_response.kind_of?(Net::HTTPSuccess) + tempfile = stream_to_tempfile(url, http_response) + if block_given? + begin + yield tempfile + ensure + tempfile && tempfile.close! + end + end + end + end + unless response.kind_of?(Net::HTTPSuccess) or response.kind_of?(Net::HTTPRedirection) + response.error! + end + tempfile + rescue Exception => e + log_failed_request(response, return_value) unless response.nil? + if e.respond_to?(:chef_rest_request=) + e.chef_rest_request = rest_request + end + raise + end + + def http_client(base_url=nil) + base_url ||= url + BasicClient.new(base_url) + end + + protected + + def create_url(path) + return path if path.is_a?(URI) + if path =~ /^(http|https):\/\// + URI.parse(path) + elsif path.nil? or path.empty? + URI.parse(@url) + else + URI.parse("#{@url}/#{path}") + end + end + + def apply_request_middleware(method, url, headers, data) + middlewares.inject([method, url, headers, data]) do |req_data, middleware| + middleware.handle_request(*req_data) + end + end + + def apply_response_middleware(response, rest_request, return_value) + middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware| + middleware.handle_response(*res_data) + end + end + + def log_failed_request(response, return_value) + return_value ||= {} + error_message = "HTTP Request Returned #{response.code} #{response.message}: " + error_message << (return_value["error"].respond_to?(:join) ? return_value["error"].join(", ") : return_value["error"].to_s) + Chef::Log.info(error_message) + end + + def success_response?(response) + response.kind_of?(Net::HTTPSuccess) || response.kind_of?(Net::HTTPRedirection) + end + + # Runs a synchronous HTTP request, with no middleware applied (use #request + # to have the middleware applied). The entire response will be loaded into memory. + def send_http_request(method, url, headers, body, &response_handler) + headers = build_headers(method, url, headers, body) + + retrying_http_errors(url) do + client = http_client(url) + return_value = nil + if block_given? + request, response = client.request(method, url, body, headers, &response_handler) + else + request, response = client.request(method, url, body, headers) {|r| r.read_body } + return_value = response.read_body + end + @last_response = response + + Chef::Log.debug("---- HTTP Status and Header Data: ----") + Chef::Log.debug("HTTP #{response.http_version} #{response.code} #{response.msg}") + + response.each do |header, value| + Chef::Log.debug("#{header}: #{value}") + end + Chef::Log.debug("---- End HTTP Status/Header Data ----") + + if response.kind_of?(Net::HTTPSuccess) + [response, request, return_value] + elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass. + [response, request, false] + elsif redirect_location = redirected_to(response) + if [:GET, :HEAD].include?(method) + follow_redirect do + send_http_request(method, create_url(redirect_location), headers, body, &response_handler) + end + else + raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." + end + else + [response, request, nil] + end + end + end + + + # Wraps an HTTP request with retry logic. + # === Arguments + # url:: URL of the request, used for error messages + def retrying_http_errors(url) + http_attempts = 0 + begin + http_attempts += 1 + + yield + + rescue SocketError, Errno::ETIMEDOUT => e + e.message.replace "Error connecting to #{url} - #{e.message}" + raise e + rescue Errno::ECONNREFUSED + if http_retry_count - http_attempts + 1 > 0 + Chef::Log.error("Connection refused connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") + sleep(http_retry_delay) + retry + end + raise Errno::ECONNREFUSED, "Connection refused connecting to #{url}, giving up" + rescue Timeout::Error + if http_retry_count - http_attempts + 1 > 0 + Chef::Log.error("Timeout connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") + sleep(http_retry_delay) + retry + end + raise Timeout::Error, "Timeout connecting to #{url}, giving up" + rescue Net::HTTPFatalError => e + if http_retry_count - http_attempts + 1 > 0 + sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts) + Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s") + sleep(sleep_time) + retry + end + raise + end + end + + def http_retry_delay + config[:http_retry_delay] + end + + def http_retry_count + config[:http_retry_count] + end + + def config + Chef::Config + end + + def follow_redirect + raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit + @redirects_followed += 1 + Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}") + + yield + ensure + @redirects_followed = 0 + end + + private + + def redirected_to(response) + return nil unless response.kind_of?(Net::HTTPRedirection) + # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this + return nil if response.kind_of?(Net::HTTPNotModified) + response['location'] + end + + def build_headers(method, url, headers={}, json_body=false) + headers = @default_headers.merge(headers) + headers['Content-Length'] = json_body.bytesize.to_s if json_body + headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers] + headers + end + + def stream_to_tempfile(url, response) + tf = Tempfile.open("chef-rest") + 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}") + # Stolen from http://www.ruby-forum.com/topic/166423 + # Kudos to _why! + + stream_handler = StreamHandler.new(middlewares, response) + + response.read_body do |chunk| + tf.write(stream_handler.handle_chunk(chunk)) + end + tf.close + tf + rescue Exception + tf.close! + raise + end + + + public + + ############################################################################ + # DEPRECATED + ############################################################################ + + # This is only kept around to provide access to cache control data in + # lib/chef/provider/remote_file/http.rb + # Find a better API. + def last_response + @last_response + end + + end +end + diff --git a/lib/chef/rest/auth_credentials.rb b/lib/chef/http/auth_credentials.rb index 00711c960d..bd73524b1f 100644 --- a/lib/chef/rest/auth_credentials.rb +++ b/lib/chef/http/auth_credentials.rb @@ -24,7 +24,7 @@ require 'chef/log' require 'mixlib/authentication/signedheaderauth' class Chef - class REST + class HTTP class AuthCredentials attr_reader :client_name, :key diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb new file mode 100644 index 0000000000..489675ad66 --- /dev/null +++ b/lib/chef/http/authenticator.rb @@ -0,0 +1,89 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/http/auth_credentials' +require 'chef/exceptions' +require 'openssl' + +class Chef + class HTTP + class Authenticator + + attr_reader :signing_key_filename + attr_reader :raw_key + attr_reader :attr_names + attr_reader :auth_credentials + + attr_accessor :sign_request + + def initialize(opts={}) + @raw_key = nil + @sign_request = true + @signing_key_filename = opts[:signing_key_filename] + @key = load_signing_key(opts[:signing_key_filename], opts[:raw_key]) + @auth_credentials = AuthCredentials.new(opts[:client_name], @key) + end + + def handle_request(method, url, headers={}, data=false) + headers.merge!(authentication_headers(method, url, data)) if sign_requests? + [method, url, headers, data] + end + + def handle_response(http_response, rest_request, return_value) + [http_response, rest_request, return_value] + end + + def stream_response_handler(response) + nil + end + + def sign_requests? + auth_credentials.sign_requests? && @sign_request + end + + def client_name + @auth_credentials.client_name + end + + def load_signing_key(key_file, raw_key = nil) + if (!!key_file) + @raw_key = IO.read(key_file).strip + elsif (!!raw_key) + @raw_key = raw_key.strip + else + return nil + end + @key = OpenSSL::PKey::RSA.new(@raw_key) + rescue SystemCallError, IOError => e + Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}" + raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!" + rescue OpenSSL::PKey::RSAError + msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key.\n" + msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'" + raise Chef::Exceptions::InvalidPrivateKey, msg + end + + def authentication_headers(method, url, json_body=nil) + request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"} + request_params[:body] ||= "" + auth_credentials.signature_headers(request_params) + end + + end + end +end diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb new file mode 100644 index 0000000000..5ee0fbf88d --- /dev/null +++ b/lib/chef/http/basic_client.rb @@ -0,0 +1,114 @@ +#-- +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Thom May (<thom@clearairturbulence.org>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2009, 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. +# +require 'uri' +require 'net/http' +require 'chef/http/ssl_policies' +require 'chef/http/http_request' + +class Chef + class HTTP + class BasicClient + + HTTPS = "https".freeze + + attr_reader :url + attr_reader :http_client + attr_reader :ssl_policy + + # Instantiate a BasicClient. + # === Arguments: + # url:: An URI for the remote server. + # === Options: + # ssl_policy:: The SSL Policy to use, defaults to DefaultSSLPolicy + def initialize(url, opts={}) + @url = url + @ssl_policy = opts[:ssl_policy] || DefaultSSLPolicy + @http_client = build_http_client + end + + def host + @url.host + end + + def port + @url.port + end + + def request(method, url, req_body, base_headers={}) + http_request = HTTPRequest.new(method, url, req_body, base_headers).http_request + Chef::Log.debug("Initiating #{method} to #{url}") + http_client.request(http_request) do |response| + yield response if block_given? + # http_client.request may not have the return signature we want, so + # force the issue: + return [http_request, response] + end + rescue OpenSSL::SSL::SSLError => e + Chef::Log.error("SSL Validation failure connecting to host: #{host} - #{e.message}") + raise + end + + #adapted from buildr/lib/buildr/core/transports.rb + def proxy_uri + proxy = Chef::Config["#{url.scheme}_proxy"] + proxy = URI.parse(proxy) if String === proxy + excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact + excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" } + return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") } + end + + def build_http_client + http_client = http_client_builder.new(host, port) + + if url.scheme == HTTPS + configure_ssl(http_client) + end + + http_client.read_timeout = config[:rest_timeout] + http_client + end + + def config + Chef::Config + end + + def http_client_builder + http_proxy = proxy_uri + if http_proxy.nil? + Net::HTTP + else + Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy") + user = Chef::Config["#{url.scheme}_proxy_user"] + pass = Chef::Config["#{url.scheme}_proxy_pass"] + Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass) + end + end + + def configure_ssl(http_client) + http_client.use_ssl = true + ssl_policy.apply_to(http_client) + end + + end + end +end diff --git a/lib/chef/rest/cookie_jar.rb b/lib/chef/http/cookie_jar.rb index e3137708a2..418fb1d352 100644 --- a/lib/chef/rest/cookie_jar.rb +++ b/lib/chef/http/cookie_jar.rb @@ -23,7 +23,7 @@ require 'singleton' class Chef - class REST + class HTTP class CookieJar < Hash include Singleton end diff --git a/lib/chef/http/cookie_manager.rb b/lib/chef/http/cookie_manager.rb new file mode 100644 index 0000000000..f6dcf9aa32 --- /dev/null +++ b/lib/chef/http/cookie_manager.rb @@ -0,0 +1,56 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/http/cookie_jar' + +class Chef + class HTTP + + # An HTTP middleware to manage storing/sending cookies in HTTP requests. + # Most HTTP communication in Chef does not need cookies, it was originally + # implemented to support OpenID, but it's not known who might be relying on + # it, so it's included with Chef::REST + class CookieManager + + def initialize(options={}) + @cookies = CookieJar.instance + end + + def handle_request(method, url, headers={}, data=false) + @host, @port = url.host, url.port + if @cookies.has_key?("#{@host}:#{@port}") + headers['Cookie'] = @cookies["#{@host}:#{@port}"] + end + [method, url, headers, data] + end + + def handle_response(http_response, rest_request, return_value) + if http_response['set-cookie'] + @cookies["#{@host}:#{@port}"] = http_response['set-cookie'] + end + [http_response, rest_request, return_value] + end + + def stream_response_handler(response) + nil + end + + + end + end +end diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb new file mode 100644 index 0000000000..6010ffa698 --- /dev/null +++ b/lib/chef/http/decompressor.rb @@ -0,0 +1,137 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'zlib' +require 'chef/http/http_request' + +class Chef + class HTTP + + # Middleware-esque class for handling compression in HTTP responses. + class Decompressor + class NoopInflater + def inflate(chunk) + chunk + end + alias :handle_chunk :inflate + end + + class GzipInflater < Zlib::Inflate + def initialize + super(Zlib::MAX_WBITS + 16) + end + alias :handle_chunk :inflate + end + + class DeflateInflater < Zlib::Inflate + def initialize + super + end + alias :handle_chunk :inflate + end + + CONTENT_ENCODING = "content-encoding".freeze + GZIP = "gzip".freeze + DEFLATE = "deflate".freeze + IDENTITY = "identity".freeze + + def initialize(opts={}) + @disable_gzip = false + handle_options(opts) + end + + def handle_request(method, url, headers={}, data=false) + headers[HTTPRequest::ACCEPT_ENCODING] = HTTPRequest::ENCODING_GZIP_DEFLATE unless gzip_disabled? + [method, url, headers, data] + end + + def handle_response(http_response, rest_request, return_value) + # temporary hack, skip processing if return_value is false + # needed to keep conditional get stuff working correctly. + return [http_response, rest_request, return_value] if return_value == false + response_body = decompress_body(http_response) + http_response.body.replace(response_body) if http_response.body.respond_to?(:replace) + [http_response, rest_request, return_value] + end + + def decompress_body(response) + if gzip_disabled? || response.body.nil? + response.body + else + case response[CONTENT_ENCODING] + when GZIP + Chef::Log.debug "decompressing gzip response" + Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body) + when DEFLATE + Chef::Log.debug "decompressing deflate response" + Zlib::Inflate.inflate(response.body) + else + response.body + end + end + end + + # This isn't used when this class is used as middleware; it returns an + # object you can use to unzip/inflate a streaming response. + def stream_response_handler(response) + if gzip_disabled? + NoopInflater.new + else + case response[CONTENT_ENCODING] + when GZIP + Chef::Log.debug "decompressing gzip stream" + GzipInflater.new + when DEFLATE + Chef::Log.debug "decompressing inflate stream" + DeflateInflater.new + else + NoopInflater.new + end + end + end + + + # gzip is disabled using the disable_gzip => true option in the + # constructor. When gzip is disabled, no 'Accept-Encoding' header will be + # set, and the response will not be decompressed, no matter what the + # Content-Encoding header of the response is. The intended use case for + # this is to work around situations where you request +file.tar.gz+, but + # the server responds with a content type of tar and a content encoding of + # gzip, tricking the client into decompressing the response so you end up + # with a tar archive (no gzip) named file.tar.gz + def gzip_disabled? + @disable_gzip + end + + private + + def handle_options(opts) + opts.each do |name, value| + case name.to_s + when 'disable_gzip' + @disable_gzip = value + end + end + end + + + end + end +end + + diff --git a/lib/chef/rest/rest_request.rb b/lib/chef/http/http_request.rb index ff9738de99..ec837f13f2 100644 --- a/lib/chef/rest/rest_request.rb +++ b/lib/chef/http/http_request.rb @@ -22,7 +22,6 @@ # require 'uri' require 'net/http' -require 'chef/rest/cookie_jar' # To load faster, we only want ohai's version string. # However, in ohai before 0.6.0, the version is defined @@ -36,8 +35,8 @@ end require 'chef/version' class Chef - class REST - class RESTRequest + class HTTP + class HTTPRequest engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby" @@ -72,8 +71,6 @@ class Chef def initialize(method, url, req_body, base_headers={}) @method, @url = method, url @request_body = nil - @cookies = CookieJar.instance - configure_http_client build_headers(base_headers) configure_http_request(req_body) end @@ -94,10 +91,10 @@ class Chef @url.path.empty? ? SLASH : @url.path end + # DEPRECATED. Call request on an HTTP client object instead. def call hide_net_http_bug do http_client.request(http_request) do |response| - store_cookie(response) yield response if block_given? response end @@ -108,6 +105,11 @@ class Chef Chef::Config end + # DEPRECATED. Call request on an HTTP client object instead. + def http_client + @http_client ||= BasicClient.new(url).http_client + end + private def hide_net_http_bug @@ -125,77 +127,12 @@ class Chef end end - def store_cookie(response) - if response['set-cookie'] - @cookies["#{host}:#{port}"] = response['set-cookie'] - end - end - def build_headers(headers) @headers = headers.dup - # TODO: need to set accept somewhere else - # headers.merge!('Accept' => "application/json") unless raw + # No response compression unless we asked for it explicitly: + @headers[HTTPRequest::ACCEPT_ENCODING] ||= "identity" @headers['X-Chef-Version'] = ::Chef::VERSION - @headers[ACCEPT_ENCODING] = ENCODING_GZIP_DEFLATE - - if @cookies.has_key?("#{host}:#{port}") - @headers['Cookie'] = @cookies["#{host}:#{port}"] - end - end - - #adapted from buildr/lib/buildr/core/transports.rb - def proxy_uri - proxy = Chef::Config["#{url.scheme}_proxy"] - proxy = URI.parse(proxy) if String === proxy - excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact - excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" } - return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") } - end - - def configure_http_client - http_proxy = proxy_uri - if http_proxy.nil? - @http_client = Net::HTTP.new(host, port) - else - Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy") - user = Chef::Config["#{url.scheme}_proxy_user"] - pass = Chef::Config["#{url.scheme}_proxy_pass"] - @http_client = Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass).new(host, port) - end - if url.scheme == HTTPS - @http_client.use_ssl = true - if config[:ssl_verify_mode] == :verify_none - @http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE - elsif config[:ssl_verify_mode] == :verify_peer - @http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER - end - if config[:ssl_ca_path] - unless ::File.exist?(config[:ssl_ca_path]) - raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist" - end - @http_client.ca_path = config[:ssl_ca_path] - elsif config[:ssl_ca_file] - unless ::File.exist?(config[:ssl_ca_file]) - raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist" - end - @http_client.ca_file = config[:ssl_ca_file] - end - if (config[:ssl_client_cert] || config[:ssl_client_key]) - unless (config[:ssl_client_cert] && config[:ssl_client_key]) - raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together" - end - unless ::File.exists?(config[:ssl_client_cert]) - raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist" - end - unless ::File.exists?(config[:ssl_client_key]) - raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist" - end - @http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert])) - @http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key])) - end - end - - @http_client.read_timeout = config[:rest_timeout] + @headers end @@ -225,6 +162,8 @@ class Chef password = URI.unescape(url.password) if url.password @http_request.basic_auth(user, password) end + + # Overwrite default UA @http_request[USER_AGENT] = self.class.user_agent end diff --git a/lib/chef/http/json_input.rb b/lib/chef/http/json_input.rb new file mode 100644 index 0000000000..741c48f5f6 --- /dev/null +++ b/lib/chef/http/json_input.rb @@ -0,0 +1,53 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/json_compat' + +class Chef + class HTTP + + # Middleware that takes json input and turns it into raw text + class JSONInput + + def initialize(opts={}) + end + + def handle_request(method, url, headers={}, data=false) + if data + headers["Content-Type"] = 'application/json' + data = Chef::JSONCompat.to_json(data) + # Force encoding to binary to fix SSL related EOFErrors + # cf. http://tickets.opscode.com/browse/CHEF-2363 + # http://redmine.ruby-lang.org/issues/5233 + data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding) + end + [method, url, headers, data] + end + + def handle_response(http_response, rest_request, return_value) + [http_response, rest_request, return_value] + end + + def stream_response_handler(response) + nil + end + + end + end +end diff --git a/lib/chef/http/json_output.rb b/lib/chef/http/json_output.rb new file mode 100644 index 0000000000..48407d933f --- /dev/null +++ b/lib/chef/http/json_output.rb @@ -0,0 +1,69 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/json_compat' +require 'chef/log' + +class Chef + class HTTP + + # Middleware that takes an HTTP response, parses it as JSON if possible. + class JSONOutput + + def initialize(opts={}) + @raw_output = opts[:raw_output] + @inflate_json_class = opts[:inflate_json_class] + end + + def handle_request(method, url, headers={}, data=false) + # Ideally this should always set Accept to application/json, but + # Chef::REST is sometimes used to make non-JSON requests, so it sets + # Accept to the desired value before middlewares get called. + headers['Accept'] ||= 'application/json' + [method, url, headers, data] + end + + def handle_response(http_response, rest_request, return_value) + # temporary hack, skip processing if return_value is false + # needed to keep conditional get stuff working correctly. + return [http_response, rest_request, return_value] if return_value == false + if http_response['content-type'] =~ /json/ + if @raw_output + return_value = http_response.body.to_s + else + if @inflate_json_class + return_value = Chef::JSONCompat.from_json(http_response.body.chomp) + else + return_value = Chef::JSONCompat.from_json(http_response.body.chomp, :create_additions => false) + end + end + [http_response, rest_request, return_value] + else + Chef::Log.warn("Expected JSON response, but got content-type '#{http_response['content-type']}'") + return [http_response, rest_request, http_response.body.to_s] + end + end + + def stream_response_handler(response) + nil + end + + end + end +end diff --git a/lib/chef/http/json_to_model_output.rb b/lib/chef/http/json_to_model_output.rb new file mode 100644 index 0000000000..9bc90a52ae --- /dev/null +++ b/lib/chef/http/json_to_model_output.rb @@ -0,0 +1,34 @@ +#-- +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/http/json_output' + +class Chef + class HTTP + + # A Middleware-ish thing that takes an HTTP response, parses it as JSON if + # possible, and converts it into an appropriate model object if it contains + # a `json_class` key. + class JSONToModelOutput < JSONOutput + def initialize(opts={}) + opts[:inflate_json_class] = true if !opts.has_key?(:inflate_json_class) + super + end + end + end +end diff --git a/lib/chef/http/simple.rb b/lib/chef/http/simple.rb new file mode 100644 index 0000000000..0ecb28846c --- /dev/null +++ b/lib/chef/http/simple.rb @@ -0,0 +1,16 @@ +require 'chef/http' +require 'chef/http/authenticator' +require 'chef/http/decompressor' + + +class Chef + class HTTP + + class Simple < HTTP + + use Decompressor + use CookieManager + + end + end +end diff --git a/lib/chef/http/ssl_policies.rb b/lib/chef/http/ssl_policies.rb new file mode 100644 index 0000000000..17b46a6762 --- /dev/null +++ b/lib/chef/http/ssl_policies.rb @@ -0,0 +1,121 @@ +#-- +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Thom May (<thom@clearairturbulence.org>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'openssl' + +class Chef + class HTTP + + # == Chef::HTTP::DefaultSSLPolicy + # Configures SSL behavior on an HTTP object via visitor pattern. + class DefaultSSLPolicy + + def self.apply_to(http_client) + new(http_client).apply + http_client + end + + attr_reader :http_client + + def initialize(http_client) + @http_client = http_client + end + + def apply + set_verify_mode + set_ca_store + set_custom_certs + set_client_credentials + end + + def set_verify_mode + if config[:ssl_verify_mode] == :verify_none + http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE + elsif config[:ssl_verify_mode] == :verify_peer + http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + end + + def set_ca_store + if config[:ssl_ca_path] + unless ::File.exist?(config[:ssl_ca_path]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist" + end + http_client.ca_path = config[:ssl_ca_path] + elsif config[:ssl_ca_file] + unless ::File.exist?(config[:ssl_ca_file]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist" + end + http_client.ca_file = config[:ssl_ca_file] + end + end + + def set_custom_certs + unless http_client.cert_store + http_client.cert_store = OpenSSL::X509::Store.new + http_client.cert_store.set_default_paths + end + if config.trusted_certs_dir + certs = Dir.glob(File.join(config.trusted_certs_dir, "*.{crt,pem}")) + certs.each do |cert_file| + cert = OpenSSL::X509::Certificate.new(File.read(cert_file)) + http_client.cert_store.add_cert(cert) + end + end + end + + def set_client_credentials + if (config[:ssl_client_cert] || config[:ssl_client_key]) + unless (config[:ssl_client_cert] && config[:ssl_client_key]) + raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together" + end + unless ::File.exists?(config[:ssl_client_cert]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist" + end + unless ::File.exists?(config[:ssl_client_key]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist" + end + http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert])) + http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key])) + end + end + + def config + Chef::Config + end + + end + + class APISSLPolicy < DefaultSSLPolicy + + def set_verify_mode + if config[:ssl_verify_mode] == :verify_peer or config[:verify_api_cert] + http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER + elsif config[:ssl_verify_mode] == :verify_none + http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + end + end + + end +end diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 9c48925216..341402242e 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -20,6 +20,7 @@ require 'forwardable' require 'chef/version' require 'mixlib/cli' +require 'chef/config_fetcher' require 'chef/mixin/convert_to_class_name' require 'chef/mixin/path_sanity' require 'chef/knife/core/subcommand_loader' @@ -314,7 +315,11 @@ class Chef config_file_settings end - def locate_config_file + def self.config_fetcher(candidate_config) + Chef::ConfigFetcher.new(candidate_config, Chef::Config.config_file_jail) + end + + def self.locate_config_file candidate_configs = [] # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine) @@ -326,8 +331,8 @@ class Chef candidate_configs << File.join(Dir.pwd, 'knife.rb') end # Look for $UPWARD/.chef/knife.rb - if self.class.chef_config_dir - candidate_configs << File.join(self.class.chef_config_dir, 'knife.rb') + if chef_config_dir + candidate_configs << File.join(chef_config_dir, 'knife.rb') end # Look for $HOME/.chef/knife.rb if ENV['HOME'] @@ -335,12 +340,12 @@ class Chef end candidate_configs.each do | candidate_config | - candidate_config = File.expand_path(candidate_config) - if File.exist?(candidate_config) - config[:config_file] = candidate_config - break + fetcher = config_fetcher(candidate_config) + if !fetcher.config_missing? + return candidate_config end end + return nil end # Apply Config in this order: @@ -379,6 +384,12 @@ class Chef Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url] Chef::Config[:environment] = config[:environment] if config[:environment] + Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode) + if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path) + Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd) + end + Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port] + # Expand a relative path from the config directory. Config from command # line should already be expanded, and absolute paths will be unchanged. if Chef::Config[:client_key] && config[:config_file] @@ -397,14 +408,20 @@ class Chef end def configure_chef - unless config[:config_file] - locate_config_file + if !config[:config_file] + located_config_file = self.class.locate_config_file + config[:config_file] = located_config_file if located_config_file end - # Don't try to load a knife.rb if it doesn't exist. + # Don't try to load a knife.rb if it wasn't specified. if config[:config_file] + fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail) + if fetcher.config_missing? + ui.error("Specified config file #{config[:config_file]} does not exist#{Chef::Config.config_file_jail ? " or is not under config file jail #{Chef::Config.config_file_jail}" : ""}!") + exit 1 + end Chef::Log.debug("Using configuration from #{config[:config_file]}") - read_config_file(config[:config_file]) + read_config(fetcher.read_config, config[:config_file]) else # ...but do log a message if no config was found. Chef::Config[:color] = config[:color] @@ -415,24 +432,24 @@ class Chef apply_computed_config end - def read_config_file(file) - Chef::Config.from_file(file) + def read_config(config_content, config_file_path) + Chef::Config.from_string(config_content, config_file_path) rescue SyntaxError => e - ui.error "You have invalid ruby syntax in your config file #{file}" + ui.error "You have invalid ruby syntax in your config file #{config_file_path}" ui.info(ui.color(e.message, :red)) - if file_line = e.message[/#{Regexp.escape(file)}:[\d]+/] + if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/] line = file_line[/:([\d]+)$/, 1].to_i - highlight_config_error(file, line) + highlight_config_error(config_file_path, line) end exit 1 rescue Exception => e - ui.error "You have an error in your config file #{file}" + ui.error "You have an error in your config file #{config_file_path}" ui.info "#{e.class.name}: #{e.message}" - filtered_trace = e.backtrace.grep(/#{Regexp.escape(file)}/) + filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/) filtered_trace.each {|line| ui.msg(" " + ui.color(line, :red))} if !filtered_trace.empty? - line_nr = filtered_trace.first[/#{Regexp.escape(file)}:([\d]+)/, 1] - highlight_config_error(file, line_nr.to_i) + line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1] + highlight_config_error(config_file_path, line_nr.to_i) end exit 1 @@ -458,14 +475,19 @@ class Chef stdout.puts("USAGE: " + self.opt_parser.to_s) end - def run_with_pretty_exceptions + def run_with_pretty_exceptions(raise_exception = false) unless self.respond_to?(:run) ui.error "You need to add a #run method to your knife command before you can use it" end enforce_path_sanity - run + Chef::Application.setup_server_connectivity + begin + run + ensure + Chef::Application.destroy_server_connectivity + end rescue Exception => e - raise if Chef::Config[:verbosity] == 2 + raise if raise_exception || Chef::Config[:verbosity] == 2 humanize_exception(e) exit 100 end diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 942a5681b8..e88bbc1f19 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -89,6 +89,11 @@ class Chef :description => "The proxy server for the node being bootstrapped", :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p } + option :bootstrap_no_proxy, + :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]", + :description => "Do not proxy locations for the node being bootstrapped", + :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np } + option :distro, :short => "-d DISTRO", :long => "--distro DISTRO", @@ -141,11 +146,13 @@ class Chef option :secret, :short => "-s SECRET", :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values" + :description => "The secret key to use to encrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } option :secret_file, :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values" + :description => "A file containing the secret key to use to encrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } def find_template(template=nil) # Are we bootstrapping using an already shipped template? @@ -244,7 +251,7 @@ class Chef command = render_template(read_template) if config[:use_sudo] - command = config[:use_sudo_password] ? "echo #{config[:ssh_password]} | sudo -S #{command}" : "sudo #{command}" + command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S #{command}" : "sudo #{command}" end command diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb index 974b522653..549ffaea8c 100644 --- a/lib/chef/knife/bootstrap/chef-full.erb +++ b/lib/chef/knife/bootstrap/chef-full.erb @@ -1,5 +1,13 @@ bash -c ' -<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> +<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> + +distro=`uname -s` + +if [ "X$distro" == "XSunOS" ]; then + if [ -e "/usr/sfw/bin" ]; then + export PATH=/usr/sfw/bin:$PATH + fi +fi exists() { if command -v $1 &>/dev/null diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb index 5b5078b574..285254aef0 100644 --- a/lib/chef/knife/client_create.rb +++ b/lib/chef/knife/client_create.rb @@ -61,7 +61,7 @@ class Chef # We only get a private_key on client creation, not on client update. if client['private_key'] ui.info("Created #{output}") - + if config[:file] File.open(config[:file], "w") do |f| f.print(client['private_key']) diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb index 8e725952b4..3ad4fac970 100644 --- a/lib/chef/knife/configure.rb +++ b/lib/chef/knife/configure.rb @@ -57,7 +57,7 @@ class Chef option :validation_key, :long => "--validation-key PATH", - :description => "The location of the location of the validation key (usually a file named validation.pem)" + :description => "The location of the validation key (usually a file named validation.pem)" def configure_chef # We are just faking out the system so that you can do this without a key specified diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index 4e6b8d0c1f..01bd8293f3 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -214,7 +214,7 @@ EOH TODO: Enter the cookbook description here. e.g. -This cookbook makes your favorite breakfast sandwhich. +This cookbook makes your favorite breakfast sandwich. == Requirements TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. @@ -224,7 +224,7 @@ e.g. - +toaster+ - #{cookbook_name} needs toaster to brown your bagel. == Attributes -TODO: List you cookbook attributes here. +TODO: List your cookbook attributes here. e.g. ==== #{cookbook_name}::default @@ -263,7 +263,7 @@ TODO: (optional) If this is a public cookbook, detail the process for contributi e.g. 1. Fork the repository on Github 2. Create a named feature branch (like `add_component_x`) -3. Write you change +3. Write your change 4. Write tests for your change (if applicable) 5. Run the tests, ensuring they all pass 6. Submit a Pull Request using Github @@ -278,7 +278,7 @@ EOH TODO: Enter the cookbook description here. e.g. -This cookbook makes your favorite breakfast sandwhich. +This cookbook makes your favorite breakfast sandwich. Requirements ------------ @@ -333,7 +333,7 @@ TODO: (optional) If this is a public cookbook, detail the process for contributi e.g. 1. Fork the repository on Github 2. Create a named feature branch (like `add_component_x`) -3. Write you change +3. Write your change 4. Write tests for your change (if applicable) 5. Run the tests, ensuring they all pass 6. Submit a Pull Request using Github @@ -349,7 +349,7 @@ EOH TODO: Enter the cookbook description here. e.g. - This cookbook makes your favorite breakfast sandwhich. + This cookbook makes your favorite breakfast sandwich. Requirements TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. @@ -386,7 +386,7 @@ Contributing e.g. 1. Fork the repository on Github 2. Create a named feature branch (like `add_component_x`) - 3. Write you change + 3. Write your change 4. Write tests for your change (if applicable) 5. Run the tests, ensuring they all pass 6. Submit a Pull Request using Github diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb index 6132c9dca0..cb8eeb8edf 100644 --- a/lib/chef/knife/cookbook_download.rb +++ b/lib/chef/knife/cookbook_download.rb @@ -7,9 +7,9 @@ # 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. diff --git a/lib/chef/knife/cookbook_metadata_from_file.rb b/lib/chef/knife/cookbook_metadata_from_file.rb index eb1afa8b11..8e26251d6e 100644 --- a/lib/chef/knife/cookbook_metadata_from_file.rb +++ b/lib/chef/knife/cookbook_metadata_from_file.rb @@ -9,9 +9,9 @@ # 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. diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb index 3545d20817..7c9cbebdb1 100644 --- a/lib/chef/knife/cookbook_show.rb +++ b/lib/chef/knife/cookbook_show.rb @@ -6,9 +6,9 @@ # 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. @@ -50,7 +50,7 @@ class Chef :long => "--with-uri", :description => "Show corresponding URIs" - def run + def run case @name_args.length when 4 # We are showing a specific file node = Hash.new diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb index d7a3bfd62c..913d171b3c 100644 --- a/lib/chef/knife/cookbook_site_install.rb +++ b/lib/chef/knife/cookbook_site_install.rb @@ -151,11 +151,11 @@ class Chef ui.info("Removing pre-existing version.") FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path) end - + def convert_path(upstream_file) if ENV['MSYSTEM'] == 'MINGW32' return upstream_file.sub(/^([[:alpha:]]):/, '/\1') - else + else return Shellwords.escape upstream_file end end diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb index 96c4ef0eed..fe83b71388 100644 --- a/lib/chef/knife/cookbook_site_list.rb +++ b/lib/chef/knife/cookbook_site_list.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb index 5df7d67327..b636276cba 100644 --- a/lib/chef/knife/cookbook_site_search.rb +++ b/lib/chef/knife/cookbook_site_search.rb @@ -5,9 +5,9 @@ # 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. @@ -36,7 +36,7 @@ class Chef end new_start = start + cr["items"].length if new_start < cr["total"] - search_cookbook(query, items, new_start, cookbook_collection) + search_cookbook(query, items, new_start, cookbook_collection) else cookbook_collection end diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb index a02dd61fc8..d15098e915 100644 --- a/lib/chef/knife/cookbook_site_show.rb +++ b/lib/chef/knife/cookbook_site_show.rb @@ -5,9 +5,9 @@ # 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. @@ -27,10 +27,10 @@ class Chef def run output(format_for_display(get_cookbook_data)) end - + def get_cookbook_data case @name_args.length - when 1 + when 1 noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}") when 2 noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}") @@ -45,7 +45,7 @@ class Chef end new_start = start + cr["items"].length if new_start < cr["total"] - get_cookbook_list(items, new_start, cookbook_collection) + get_cookbook_list(items, new_start, cookbook_collection) else cookbook_collection end diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 248d7c7a64..e1ad606c80 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -51,9 +51,9 @@ class Chef end def encrypted_data_bag_secret - @config[:secret] || begin - if @config[:secret_file] && File.exist?(@config[:secret_file]) - IO.read(File.expand_path(@config[:secret_file])) + knife_config[:secret] || begin + if knife_config[:secret_file] && File.exist?(knife_config[:secret_file]) + IO.read(File.expand_path(knife_config[:secret_file])) elsif @chef_config[:encrypted_data_bag_secret] && File.exist?(@chef_config[:encrypted_data_bag_secret]) IO.read(File.expand_path(@chef_config[:encrypted_data_bag_secret])) end @@ -78,6 +78,10 @@ CONFIG client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n} end + if knife_config[:bootstrap_no_proxy] + client_rb << %Q{no_proxy "#{knife_config[:bootstrap_no_proxy]}"\n} + end + if encrypted_data_bag_secret client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n} end diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb index a35baf2264..d1aab592ef 100644 --- a/lib/chef/knife/core/node_presenter.rb +++ b/lib/chef/knife/core/node_presenter.rb @@ -86,7 +86,7 @@ class Chef # Converts a Chef::Node object to a string suitable for output to a # terminal. If config[:medium_output] or config[:long_output] are set # the volume of output is adjusted accordingly. Uses colors if enabled - # in the the ui object. + # in the ui object. def summarize(data) if data.kind_of?(Chef::Node) node = data diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index 2475717441..91c0590121 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -6,9 +6,9 @@ # 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. @@ -112,7 +112,7 @@ class Chef check_spec_for_glob(spec, glob) end end.flatten - + files.concat gem_files files.uniq! if check_load_path @@ -133,9 +133,9 @@ class Chef else spec.require_paths.first end - + glob = File.join("#{spec.full_gem_path}/#{dirs}", glob) - + Dir[glob].map { |f| f.untaint } end end diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb index f18d30a039..d0bdaa7ac0 100644 --- a/lib/chef/knife/core/ui.rb +++ b/lib/chef/knife/core/ui.rb @@ -167,7 +167,7 @@ class Chef if (!config[:disable_editing]) filename = "knife-edit-" 0.upto(20) { filename += rand(9).to_s } - filename << ".js" + filename << ".json" filename = File.join(Dir.tmpdir, filename) tf = File.open(filename, "w") tf.sync = true diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index e644ab78d3..55c1c71798 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -32,13 +32,15 @@ class Chef category "data bag" option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values" + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values" + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } def read_secret if config[:secret] diff --git a/lib/chef/knife/data_bag_delete.rb b/lib/chef/knife/data_bag_delete.rb index f8e52018a6..575e9d604d 100644 --- a/lib/chef/knife/data_bag_delete.rb +++ b/lib/chef/knife/data_bag_delete.rb @@ -6,9 +6,9 @@ # 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. @@ -29,7 +29,7 @@ class Chef banner "knife data bag delete BAG [ITEM] (options)" category "data bag" - def run + 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]}") diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 234c77177d..b3f53af919 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -7,9 +7,9 @@ # 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. @@ -32,13 +32,15 @@ class Chef category "data bag" option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values" + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values" + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } def read_secret if config[:secret] diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb index 275cbeac52..4c90fe6c6c 100644 --- a/lib/chef/knife/data_bag_from_file.rb +++ b/lib/chef/knife/data_bag_from_file.rb @@ -35,18 +35,20 @@ class Chef category "data bag" option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values" + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values" + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } option :all, - :short => "-a", - :long => "--all", - :description => "Upload all data bags or all items for specified data bags" + :short => "-a", + :long => "--all", + :description => "Upload all data bags or all items for specified data bags" def read_secret if config[:secret] diff --git a/lib/chef/knife/data_bag_list.rb b/lib/chef/knife/data_bag_list.rb index 31dcf984f6..5e556b60bc 100644 --- a/lib/chef/knife/data_bag_list.rb +++ b/lib/chef/knife/data_bag_list.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 81b1425f78..519859ca2d 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -7,9 +7,9 @@ # 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. @@ -32,13 +32,15 @@ class Chef category "data bag" option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to decrypt data bag item values" + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to decrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to decrypt data bag item values" + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to decrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } def read_secret if config[:secret] diff --git a/lib/chef/knife/delete.rb b/lib/chef/knife/delete.rb index fb26b9ea35..0030c45026 100644 --- a/lib/chef/knife/delete.rb +++ b/lib/chef/knife/delete.rb @@ -5,6 +5,8 @@ class Chef class Delete < Chef::ChefFS::Knife banner "knife delete [PATTERN1 ... PATTERNn]" + category "path-based" + deps do require 'chef/chef_fs/file_system' end diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb index c4b3678ff8..b2a39a0725 100644 --- a/lib/chef/knife/deps.rb +++ b/lib/chef/knife/deps.rb @@ -5,6 +5,8 @@ class Chef class Deps < Chef::ChefFS::Knife banner "knife deps PATTERN1 [PATTERNn]" + category "path-based" + deps do require 'chef/chef_fs/file_system' require 'chef/run_list' diff --git a/lib/chef/knife/diff.rb b/lib/chef/knife/diff.rb index 5a3a80544d..e5eda5228c 100644 --- a/lib/chef/knife/diff.rb +++ b/lib/chef/knife/diff.rb @@ -5,6 +5,8 @@ class Chef class Diff < Chef::ChefFS::Knife banner "knife diff PATTERNS" + category "path-based" + deps do require 'chef/chef_fs/command_line' end @@ -30,6 +32,10 @@ class Chef :description => "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected." + option :cookbook_version, + :long => '--cookbook-version VERSION', + :description => 'Version of cookbook to download (if there are multiple versions and cookbook_versions is false)' + def run if config[:name_only] output_mode = :name_only diff --git a/lib/chef/knife/download.rb b/lib/chef/knife/download.rb index e8f26a74aa..5a432afd47 100644 --- a/lib/chef/knife/download.rb +++ b/lib/chef/knife/download.rb @@ -5,6 +5,8 @@ class Chef class Download < Chef::ChefFS::Knife banner "knife download PATTERNS" + category "path-based" + deps do require 'chef/chef_fs/command_line' end @@ -40,6 +42,10 @@ class Chef :default => true, :description => 'Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff' + option :cookbook_version, + :long => '--cookbook-version VERSION', + :description => 'Version of cookbook to download (if there are multiple versions and cookbook_versions is false)' + def run if name_args.length == 0 show_usage diff --git a/lib/chef/knife/edit.rb b/lib/chef/knife/edit.rb index ea068cb250..830da84a12 100644 --- a/lib/chef/knife/edit.rb +++ b/lib/chef/knife/edit.rb @@ -5,6 +5,8 @@ class Chef class Edit < Chef::ChefFS::Knife banner "knife edit [PATTERN1 ... PATTERNn]" + category "path-based" + deps do require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/not_found_error' diff --git a/lib/chef/knife/environment_from_file.rb b/lib/chef/knife/environment_from_file.rb index af72f84622..3812024c9c 100644 --- a/lib/chef/knife/environment_from_file.rb +++ b/lib/chef/knife/environment_from_file.rb @@ -62,7 +62,7 @@ class Chef ui.info("Updated Environment #{updated.name}") end - + def run if config[:all] == true load_all_environments @@ -72,7 +72,7 @@ class Chef ui.fatal("You must specify a file to load") exit 1 end - + @name_args.each do |arg| load_environment(arg) end diff --git a/lib/chef/knife/index_rebuild.rb b/lib/chef/knife/index_rebuild.rb index 3e97c7751b..4b9fcdd159 100644 --- a/lib/chef/knife/index_rebuild.rb +++ b/lib/chef/knife/index_rebuild.rb @@ -40,7 +40,7 @@ class Chef nag output rest.post_rest("/search/reindex", {}) end - + end def grab_api_info @@ -55,7 +55,7 @@ class Chef r = exception.response parse_api_info(r) end - + # Only Chef 11+ servers will have version information in their # headers, and only those servers will lack an API endpoint for # index rebuilding. diff --git a/lib/chef/knife/list.rb b/lib/chef/knife/list.rb index 83d5c5a8c4..4338e195bd 100644 --- a/lib/chef/knife/list.rb +++ b/lib/chef/knife/list.rb @@ -5,6 +5,8 @@ class Chef class List < Chef::ChefFS::Knife banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn]" + category "path-based" + deps do require 'chef/chef_fs/file_system' require 'highline' diff --git a/lib/chef/knife/raw.rb b/lib/chef/knife/raw.rb index ee22d1ade5..2756de1a5a 100644 --- a/lib/chef/knife/raw.rb +++ b/lib/chef/knife/raw.rb @@ -6,10 +6,13 @@ class Chef banner "knife raw REQUEST_PATH" deps do - require 'json' - require 'chef/rest' + require 'chef/json_compat' require 'chef/config' - require 'chef/chef_fs/raw_request' + require 'chef/http' + require 'chef/http/authenticator' + require 'chef/http/cookie_manager' + require 'chef/http/decompressor' + require 'chef/http/json_output' end option :method, @@ -29,6 +32,18 @@ class Chef :short => '-i FILE', :description => "Name of file to use for PUT or POST" + class RawInputServerAPI < Chef::HTTP + def initialize(options = {}) + options[:client_name] ||= Chef::Config[:node_name] + options[:signing_key_filename] ||= Chef::Config[:client_key] + super(Chef::Config[:chef_server_url], options) + end + use Chef::HTTP::JSONOutput + use Chef::HTTP::CookieManager + use Chef::HTTP::Decompressor + use Chef::HTTP::Authenticator + end + def run if name_args.length == 0 show_usage @@ -45,9 +60,20 @@ class Chef if config[:input] data = IO.read(config[:input]) end - chef_rest = Chef::REST.new(Chef::Config[:chef_server_url]) begin - output Chef::ChefFS::RawRequest.api_request(chef_rest, config[:method].to_sym, chef_rest.create_url(name_args[0]), {}, data) + method = config[:method].to_sym + + if config[:pretty] + chef_rest = RawInputServerAPI.new + result = chef_rest.request(method, name_args[0], {'Content-Type' => 'application/json'}, data) + unless result.is_a?(String) + result = Chef::JSONCompat.to_json_pretty(result) + end + else + chef_rest = RawInputServerAPI.new(:raw_output => true) + result = chef_rest.request(method, name_args[0], {'Content-Type' => 'application/json'}, data) + end + output result rescue Timeout::Error => e ui.error "Server timeout" exit 1 diff --git a/lib/chef/knife/show.rb b/lib/chef/knife/show.rb index b5e8aa9882..acf1996e96 100644 --- a/lib/chef/knife/show.rb +++ b/lib/chef/knife/show.rb @@ -5,6 +5,8 @@ class Chef class Show < Chef::ChefFS::Knife banner "knife show [PATTERN1 ... PATTERNn]" + category "path-based" + deps do require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/not_found_error' diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 8cc05bd433..e1090fcca0 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -147,7 +147,7 @@ class Chef @action_nodes = q.search(:node, @name_args[0])[0] @action_nodes.each do |item| # we should skip the loop to next iteration if the item returned by the search is nil - next if item.nil? + next if item.nil? # if a command line attribute was not passed, and we have a cloud public_hostname, use that. # see #configure_attribute for the source of config[:attribute] and config[:override_attribute] if !config[:override_attribute] && item[:cloud] and item[:cloud][:public_hostname] @@ -211,15 +211,28 @@ class Chef end def print_data(host, data) - if data =~ /\n/ - data.split(/\n/).each { |d| print_data(host, d) } + @buffers ||= {} + if leftover = @buffers[host] + @buffers[host] = nil + print_data(host, leftover + data) else - padding = @longest - host.length - str = ui.color(host, :cyan) + (" " * (padding + 1)) + data - ui.msg(str) + if newline_index = data.index("\n") + line = data.slice!(0...newline_index) + data.slice!(0) + print_line(host, line) + print_data(host, data) + else + @buffers[host] = data + end end end + def print_line(host, data) + padding = @longest - host.length + str = ui.color(host, :cyan) + (" " * (padding + 1)) + data + ui.msg(str) + end + def ssh_command(command, subsession=nil) exit_status = 0 subsession ||= session @@ -232,6 +245,7 @@ class Chef ch.on_data do |ichannel, data| print_data(ichannel[:host], data) if data =~ /^knife sudo password: / + print_data(ichannel[:host], "\n") ichannel.send_data("#{get_password}\n") end end @@ -383,7 +397,7 @@ class Chef # Thus we can differentiate between a config file value and a command line override at this point by checking config[:attribute] # We can tell here if fqdn was passed from the command line, rather than being the default, by checking config[:attribute] # However, after here, we cannot tell these things, so we must preserve config[:attribute] - config[:override_attribute] = config[:attribute] || Chef::Config[:knife][:ssh_attribute] + config[:override_attribute] = config[:attribute] || Chef::Config[:knife][:ssh_attribute] config[:attribute] = (Chef::Config[:knife][:ssh_attribute] || config[:attribute] || "fqdn").strip diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb index ceb394ce3a..5906a4a624 100644 --- a/lib/chef/knife/status.rb +++ b/lib/chef/knife/status.rb @@ -72,7 +72,7 @@ class Chef hours, minutes, seconds = time_difference_in_hms(node["ohai_time"]) hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" - run_list = ", #{node.run_list}." if config[:run_list] + run_list = "#{node.run_list}" if config[:run_list] if hours > 24 color = :red text = hours_text diff --git a/lib/chef/knife/upload.rb b/lib/chef/knife/upload.rb index f72b6ea616..8abd22b4dd 100644 --- a/lib/chef/knife/upload.rb +++ b/lib/chef/knife/upload.rb @@ -5,6 +5,8 @@ class Chef class Upload < Chef::ChefFS::Knife banner "knife upload PATTERNS" + category "path-based" + deps do require 'chef/chef_fs/command_line' end diff --git a/lib/chef/knife/xargs.rb b/lib/chef/knife/xargs.rb index be6db9d64f..dd8e848058 100644 --- a/lib/chef/knife/xargs.rb +++ b/lib/chef/knife/xargs.rb @@ -5,6 +5,8 @@ class Chef class Xargs < Chef::ChefFS::Knife banner "knife xargs [COMMAND]" + category "path-based" + deps do require 'chef/chef_fs/file_system' require 'chef/chef_fs/file_system/not_found_error' diff --git a/lib/chef/mixin/checksum.rb b/lib/chef/mixin/checksum.rb index 8217296915..1d9c99ec7e 100644 --- a/lib/chef/mixin/checksum.rb +++ b/lib/chef/mixin/checksum.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/mixin/command.rb b/lib/chef/mixin/command.rb index 55c028ff5f..2cc25e149e 100644 --- a/lib/chef/mixin/command.rb +++ b/lib/chef/mixin/command.rb @@ -6,9 +6,9 @@ # 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. @@ -41,23 +41,32 @@ class Chef # === Parameters # args<Hash>: A number of required and optional arguments - # command<String>, <Array>: A complete command with options to execute or a command and options as an Array + # command<String>, <Array>: A complete command with options to execute or a command and options as an Array # creates<String>: The absolute path to a file that prevents the command from running if it exists # cwd<String>: Working directory to execute command in, defaults to Dir.tmpdir # timeout<String>: How many seconds to wait for the command to execute before timing out # returns<String>: The single exit value command is expected to return, otherwise causes an exception # ignore_failure<Boolean>: Whether to raise an exception on failure, or just return the status # output_on_failure<Boolean>: Return output in raised exception regardless of Log.level - # + # # user<String>: The UID or user name of the user to execute the command as # group<String>: The GID or group name of the group to execute the command as # environment<Hash>: Pairs of environment variable names and their values to set before execution # # === Returns # Returns the exit status of args[:command] - def run_command(args={}) + def run_command(args={}) + status, stdout, stderr = run_command_and_return_stdout_stderr(args) + + status + end + + # works same as above, except that it returns stdout and stderr + # requirement => platforms like solaris 9,10 has wierd issues where + # even in command failure the exit code is zero, so we need to lookup stderr. + def run_command_and_return_stdout_stderr(args={}) command_output = "" - + args[:ignore_failure] ||= false args[:output_on_failure] ||= false @@ -68,28 +77,28 @@ class Chef return false end end - + status, stdout, stderr = output_of_command(args[:command], args) command_output << "STDOUT: #{stdout}" command_output << "STDERR: #{stderr}" handle_command_failures(status, command_output, args) - - status + + return status, stdout, stderr end - + def output_of_command(command, args) Chef::Log.debug("Executing #{command}") stderr_string, stdout_string, status = "", "", nil - + exec_processing_block = lambda do |pid, stdin, stdout, stderr| stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp end - + args[:cwd] ||= Dir.tmpdir unless ::File.directory?(args[:cwd]) raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory" end - + Dir.chdir(args[:cwd]) do if args[:timeout] begin @@ -103,17 +112,17 @@ class Chef else status = popen4(command, args, &exec_processing_block) end - + Chef::Log.debug("---- Begin output of #{command} ----") Chef::Log.debug("STDOUT: #{stdout_string}") Chef::Log.debug("STDERR: #{stderr_string}") Chef::Log.debug("---- End output of #{command} ----") Chef::Log.debug("Ran #{command} returned #{status.exitstatus}") end - + return status, stdout_string, stderr_string end - + def handle_command_failures(status, command_output, opts={}) unless opts[:ignore_failure] opts[:returns] ||= 0 @@ -129,7 +138,7 @@ class Chef end end end - + # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C. # # === Parameters diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb index 7b4ec7ad3f..ece16990a1 100644 --- a/lib/chef/mixin/convert_to_class_name.rb +++ b/lib/chef/mixin/convert_to_class_name.rb @@ -7,9 +7,9 @@ # 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. @@ -27,20 +27,20 @@ class Chef str.gsub!(/[^A-Za-z0-9_]/,'_') rname = nil regexp = %r{^(.+?)(_(.+))?$} - + mn = str.match(regexp) if mn rname = mn[1].capitalize while mn && mn[3] - mn = mn[3].match(regexp) + mn = mn[3].match(regexp) rname << mn[1].capitalize if mn end end rname end - + def convert_to_snake_case(str, namespace=nil) str = str.dup str.sub!(/^#{namespace}(\:\:)?/, '') if namespace @@ -49,17 +49,17 @@ class Chef str.sub!(/^\_/, "") str end - + def snake_case_basename(str) with_namespace = convert_to_snake_case(str) with_namespace.split("::").last.sub(/^_/, '') end - + def filename_to_qualified_string(base, filename) file_base = File.basename(filename, ".rb") base.to_s + (file_base == 'default' ? '' : "_#{file_base}") end - + end end end diff --git a/lib/chef/mixin/create_path.rb b/lib/chef/mixin/create_path.rb index 9b5dba14f2..9d1248e907 100644 --- a/lib/chef/mixin/create_path.rb +++ b/lib/chef/mixin/create_path.rb @@ -6,9 +6,9 @@ # 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. @@ -18,21 +18,21 @@ class Chef module Mixin module CreatePath - + # Creates a given path, including all directories that lead up to it. # Like mkdir_p, but without the leaking. # # === Parameters - # file_path<String, Array>:: A string that represents the path to create, + # file_path<String, Array>:: A string that represents the path to create, # or an Array with the path-parts. # # === Returns # The created file_path. def create_path(file_path) unless file_path.kind_of?(String) || file_path.kind_of?(Array) - raise ArgumentError, "file_path must be a string or an array!" + raise ArgumentError, "file_path must be a string or an array!" end - + if file_path.kind_of?(String) file_path = File.expand_path(file_path).split(File::SEPARATOR) file_path.shift if file_path[0] == '' @@ -41,17 +41,17 @@ class Chef file_path[0] = "#{File::SEPARATOR}#{file_path[0]}" end end - + file_path.each_index do |i| create_path = File.join(file_path[0, i + 1]) unless File.directory?(create_path) Chef::Log.debug("Creating directory #{create_path}") Dir.mkdir(create_path) - end + end end File.expand_path(File.join(file_path)) end - + end end end diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb index 1f2125be01..9fa86948b6 100644 --- a/lib/chef/mixin/deep_merge.rb +++ b/lib/chef/mixin/deep_merge.rb @@ -8,9 +8,9 @@ # 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. diff --git a/lib/chef/mixin/from_file.rb b/lib/chef/mixin/from_file.rb index 609fe1de55..0d1ddca4fa 100644 --- a/lib/chef/mixin/from_file.rb +++ b/lib/chef/mixin/from_file.rb @@ -7,9 +7,9 @@ # 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. @@ -20,9 +20,9 @@ class Chef module Mixin module FromFile - - # Loads a given ruby file, and runs instance_eval against it in the context of the current - # object. + + # Loads a given ruby file, and runs instance_eval against it in the context of the current + # object. # # Raises an IOError if the file cannot be found, or is not readable. def from_file(filename) @@ -33,7 +33,7 @@ class Chef end end - # Loads a given ruby file, and runs class_eval against it in the context of the current + # Loads a given ruby file, and runs class_eval against it in the context of the current # object. # # Raises an IOError if the file cannot be found, or is not readable. diff --git a/lib/chef/mixin/language_include_recipe.rb b/lib/chef/mixin/language_include_recipe.rb index b534b6628f..d85e5c682d 100644 --- a/lib/chef/mixin/language_include_recipe.rb +++ b/lib/chef/mixin/language_include_recipe.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index a9822df134..a9799f749c 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -6,9 +6,9 @@ # 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. @@ -20,7 +20,7 @@ class Chef end module Mixin module ParamsValidate - + # Takes a hash of options, along with a map to validate them. Returns the original # options hash, plus any changes that might have been made (through things like setting # default values in the validation map) @@ -28,19 +28,19 @@ class Chef # For example: # # validate({ :one => "neat" }, { :one => { :kind_of => String }}) - # + # # Would raise an exception if the value of :one above is not a kind_of? string. Valid # map options are: # # :default:: Sets the default value for this parameter. - # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid. + # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid. # The key will be inserted into the error message if the Proc does not return true: # "Option #{key}'s value #{value} #{message}!" - # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure + # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure # that the value is one of those types. # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of # method names. - # :required:: Raise an exception if this parameter is missing. Valid values are true or false, + # :required:: Raise an exception if this parameter is missing. Valid values are true or false, # by default, options are not required. # :regex:: Match the value of the paramater against a regular expression. # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any @@ -48,12 +48,12 @@ class Chef def validate(opts, map) #-- # validate works by taking the keys in the validation map, assuming it's a hash, and - # looking for _pv_:symbol as methods. Assuming it find them, it calls the right - # one. + # looking for _pv_:symbol as methods. Assuming it find them, it calls the right + # one. #++ raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash) - raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) - + raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) + map.each do |key, validation| unless key.kind_of?(Symbol) || key.kind_of?(String) raise ArgumentError, "Validation map keys must be symbols or strings!" @@ -76,7 +76,7 @@ class Chef end opts end - + def lazy(&block) DelayedEvaluator.new(&block) end @@ -99,9 +99,9 @@ class Chef self.instance_variable_set(iv_symbol, val) end end - + private - + # Return the value of a parameter, or nil if it doesn't exist. def _pv_opts_lookup(opts, key) if opts.has_key?(key.to_s) @@ -112,7 +112,7 @@ class Chef nil end end - + # Raise an exception if the parameter is not found. def _pv_required(opts, key, is_required=true) if is_required @@ -124,7 +124,7 @@ class Chef end end end - + def _pv_equal_to(opts, key, to_be) value = _pv_opts_lookup(opts, key) unless value.nil? @@ -137,7 +137,7 @@ class Chef end end end - + # Raise an exception if the parameter is not a kind_of?(to_be) def _pv_kind_of(opts, key, to_be) value = _pv_opts_lookup(opts, key) @@ -151,7 +151,7 @@ class Chef end end end - + # Raise an exception if the parameter does not respond to a given set of methods. def _pv_respond_to(opts, key, method_name_list) value = _pv_opts_lookup(opts, key) @@ -181,7 +181,7 @@ class Chef end end end - + # Assign a default value to a parameter. def _pv_default(opts, key, default_value) value = _pv_opts_lookup(opts, key) @@ -189,7 +189,7 @@ class Chef opts[key] = default_value end end - + # Check a parameter against a regular expression. def _pv_regex(opts, key, regex) value = _pv_opts_lookup(opts, key) @@ -207,7 +207,7 @@ class Chef end end end - + # Check a parameter against a hash of proc's. def _pv_callbacks(opts, key, callbacks) raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash) diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb index 4eaa509f8b..f0c2ba2000 100644 --- a/lib/chef/mixin/shell_out.rb +++ b/lib/chef/mixin/shell_out.rb @@ -15,13 +15,22 @@ # See the License for the specific language governing permissions and # limitations under the License. +# chef/shell_out has been deprecated in favor of mixlib/shellout +# chef/shell_out is still required here to ensure backward compatibility require 'chef/shell_out' + +require 'mixlib/shellout' require 'chef/config' class Chef module Mixin module ShellOut + # shell_out! runs a command on the system and will raise an error if the command fails, which is what you want + # for debugging, shell_out and shell_out! both will display command output to the tty when the log level is debug + # Generally speaking, 'extend Chef::Mixin::ShellOut' in your recipes and include 'Chef::Mixin::ShellOut' in your LWRPs + # You can also call Mixlib::Shellout.new directly, but you lose all of the above functionality + def shell_out(*command_args) cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args)) if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug? diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb index 9208ce057e..ae23336581 100644 --- a/lib/chef/mixin/template.rb +++ b/lib/chef/mixin/template.rb @@ -121,17 +121,6 @@ class Chef ### def _render_template(template, context) - # CHEF-2991 - # Erubis always emits unix line endings during template - # rendering. This results in automatic conversion of windows - # line endings to linux line endings if the original template - # contains windows line endings. In order to fix this we - # determine the line ending style of the template before - # rendering and convert the line endings of the output if needed - # If template contains any windows line endings we emit - # the template result with windows line endings. - windows_line_endings = template.include? "\r\n" - begin eruby = Erubis::Eruby.new(template) output = eruby.evaluate(context) @@ -139,11 +128,19 @@ class Chef raise TemplateError.new(e, template, context) end - if windows_line_endings - # Convert line endings from "\n" -> "\r\n". Also converts - # "\r\n" -> "\r\n". - # This makes the regex match on all of "\r\n", so we don't - # accidentally convert "\r\n" -> "\r\r\n". + # CHEF-4399 + # Erubis always emits unix line endings during template + # rendering. Chef used to convert line endings to the + # original line endings in the template. However this + # created problems in cases when cookbook developer is + # coding the cookbook on windows but using it on non-windows + # platforms. + # The safest solution is to make sure that native to the + # platform we are running on is used in order to minimize + # potential issues for the applications that will consume + # this template. + + if Chef::Platform.windows? output = output.gsub(/\r?\n/,"\r\n") end diff --git a/lib/chef/mixin/why_run.rb b/lib/chef/mixin/why_run.rb index 788e2fe2bc..d650e3332f 100644 --- a/lib/chef/mixin/why_run.rb +++ b/lib/chef/mixin/why_run.rb @@ -178,8 +178,8 @@ class Chef # when the requirement is not met and Chef is executing in why run # mode # - # If no failure_message is provided (above), then execution - # will be allowed to continue in both whyrun an dnon-whyrun + # If no failure_message is provided (above), then execution + # will be allowed to continue in both whyrun and non-whyrun # mode # # With a service resource that requires /etc/init.d/service-name to exist: @@ -196,16 +196,16 @@ class Chef @resource_modifier = resource_modifier end - # Prevents associated actions from being invoked in whyrun mode. - # This will also stop further processing of assertions for a given action. - # - # An example from the template provider: if the source template doesn't exist - # we can't parse it in the action_create block of template - something that we do - # even in whyrun mode. Because the soruce template may have been created in an earlier + # Prevents associated actions from being invoked in whyrun mode. + # This will also stop further processing of assertions for a given action. + # + # An example from the template provider: if the source template doesn't exist + # we can't parse it in the action_create block of template - something that we do + # even in whyrun mode. Because the source template may have been created in an earlier # step, we still want to keep going in whyrun mode. - # + # # assert(:create, :create_if_missing) do |a| - # a.assertion { File::exists?(@new_resource.source) } + # a.assertion { File::exists?(@new_resource.source) } # a.whyrun "Template source file does not exist, assuming it would have been created." # a.block_action! # end @@ -214,7 +214,7 @@ class Chef @block_action = true end - def block_action? + def block_action? @block_action end @@ -258,7 +258,7 @@ class Chef # Check to see if a given action is blocked by a failed assertion # # Takes the action name to be verified. - def action_blocked?(action) + def action_blocked?(action) @blocked_actions.include?(action) end @@ -296,9 +296,9 @@ class Chef # "You don't have sufficient privileges to delete #{@new_resource.path}") # end # - # A Template provider that will prevent action execution but continue the run in + # A Template provider that will prevent action execution but continue the run in # whyrun mode if the template source is not available. - # assert(:create, :create_if_missing) do |a| + # assert(:create, :create_if_missing) do |a| # a.assertion { File::exist?(@new_resource.source) } # a.failure_message Chef::Exceptions::TemplateError, "Template #{@new_resource.source} could not be found exist." # a.whyrun "Template source #{@new_resource.source} does not exist. Assuming it would have been created." @@ -318,9 +318,9 @@ class Chef # Run the assertion and assumption logic. def run(action) - @assertions[action.to_sym].each do |a| + @assertions[action.to_sym].each do |a| a.run(action, events, @resource) - if a.assertion_failed? and a.block_action? + if a.assertion_failed? and a.block_action? @blocked_actions << action return end diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb index 38c08e236d..c13278693f 100644 --- a/lib/chef/mixin/windows_architecture_helper.rb +++ b/lib/chef/mixin/windows_architecture_helper.rb @@ -6,9 +6,9 @@ # 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. @@ -17,7 +17,7 @@ # -require 'chef/exceptions' +require 'chef/exceptions' require 'win32/api' if Chef::Platform.windows? class Chef @@ -32,7 +32,7 @@ class Chef is_i386_windows_process? && node_windows_architecture(node) == :x86_64 && desired_architecture == :x86_64 - end + end def node_supports_windows_architecture?(node, desired_architecture) assert_valid_windows_architecture!(desired_architecture) @@ -85,7 +85,7 @@ class Chef end end end - + end end end diff --git a/lib/chef/mixin/xml_escape.rb b/lib/chef/mixin/xml_escape.rb index dac2f0c6af..ceb45df3e6 100644 --- a/lib/chef/mixin/xml_escape.rb +++ b/lib/chef/mixin/xml_escape.rb @@ -7,9 +7,9 @@ # 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. @@ -18,26 +18,26 @@ #-- # Portions of this code are adapted from Sam Ruby's xchar.rb -# http://intertwingly.net/stories/2005/09/28/xchar.rb +# http://intertwingly.net/stories/2005/09/28/xchar.rb # # Such code appears here under Sam's original MIT license, while portions of # this file are covered by the above Apache License. For a completely MIT # licensed version, please see Sam's original. # # Thanks, Sam! -# -# Copyright (c) 2005, Sam Ruby -# +# +# Copyright (c) 2005, Sam Ruby +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -99,7 +99,7 @@ class Chef } # http://www.w3.org/TR/REC-xml/#charsets - VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF), + VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF), (0xE000..0xFFFD), (0x10000..0x10FFFF)] def xml_escape(unescaped_str) @@ -118,7 +118,7 @@ class Chef char = PREDEFINED[char] || (char<128 ? char.chr : "&##{char};") end end - + module FastXS extend self diff --git a/lib/chef/monkey_patches/numeric.rb b/lib/chef/monkey_patches/numeric.rb index 1f5ff14209..f4612fdbf3 100644 --- a/lib/chef/monkey_patches/numeric.rb +++ b/lib/chef/monkey_patches/numeric.rb @@ -8,7 +8,7 @@ end # String elements referenced with [] <= 1.8.6 return a Fixnum. Cheat to allow # for the simpler "test"[2].ord construct -class Numeric +class Numeric def ord return self end diff --git a/lib/chef/monkey_patches/regexp.rb b/lib/chef/monkey_patches/regexp.rb index 9304209edf..8a7ee77cb5 100644 --- a/lib/chef/monkey_patches/regexp.rb +++ b/lib/chef/monkey_patches/regexp.rb @@ -1,5 +1,5 @@ # Copyright (c) 2009 Marc-Andre Lafortune -# +# # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including @@ -7,10 +7,10 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -31,4 +31,4 @@ class Regexp end end end -end
\ No newline at end of file +end diff --git a/lib/chef/monkey_patches/string.rb b/lib/chef/monkey_patches/string.rb index c77c5c8816..f91e27ddc5 100644 --- a/lib/chef/monkey_patches/string.rb +++ b/lib/chef/monkey_patches/string.rb @@ -6,9 +6,9 @@ # 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. @@ -39,7 +39,7 @@ class String end end -# <= 1.8.6 needs some ord! +# <= 1.8.6 needs some ord! class String unless method_defined?(:ord) def ord diff --git a/lib/chef/monkey_patches/tempfile.rb b/lib/chef/monkey_patches/tempfile.rb index 3135fb1a00..b9179f182b 100644 --- a/lib/chef/monkey_patches/tempfile.rb +++ b/lib/chef/monkey_patches/tempfile.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 6bd2226ac8..007bd3c560 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -290,6 +290,14 @@ class Chef normal[:tags] end + def tag(*tags) + tags.each do |tag| + self.normal[:tags].push(tag.to_s) unless self[:tags].include? tag.to_s + end + + self[:tags] + end + # Extracts the run list from +attrs+ and applies it. Returns the remaining attributes def consume_run_list(attrs) attrs = attrs ? attrs.dup : {} diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb index a417406721..66569cf0e1 100644 --- a/lib/chef/node/attribute.rb +++ b/lib/chef/node/attribute.rb @@ -243,7 +243,7 @@ class Chef end # Clears merged_attributes, which will cause it to be recomputed on the - # next access. + # next access. def reset_cache @merged_attributes = nil @combined_default = nil diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 1a282624c8..a252bdc100 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -173,6 +173,17 @@ class Chef :ifconfig => Chef::Provider::Ifconfig::Redhat } }, + :opensuse => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Zypper, + :group => Chef::Provider::Group::Suse + }, + ">= 12.3" => { + :group => Chef::Provider::Group::Usermod + } + }, :suse => { :default => { :service => Chef::Provider::Service::Redhat, @@ -180,6 +191,16 @@ class Chef :package => Chef::Provider::Package::Zypper, :group => Chef::Provider::Group::Suse }, + ############################################### + # TODO: Remove this after ohai update is released. + # Only OpenSuSE 12.3+ should use the Usermod group provider: + # Ohai before OHAI-339 is applied reports both OpenSuSE and SuSE + # Enterprise as "suse", Ohai after OHAI-339 will report OpenSuSE as + # "opensuse". + # + # In order to support OpenSuSE both before and after the Ohai + # change, I'm leaving this here. It needs to get removed before + # SuSE enterprise 12.3 ships. ">= 12.3" => { :group => Chef::Provider::Group::Usermod } @@ -320,7 +341,11 @@ class Chef }, :aix => { :default => { - :group => Chef::Provider::Group::Aix + :group => Chef::Provider::Group::Aix, + :mount => Chef::Provider::Mount::Aix, + :ifconfig => Chef::Provider::Ifconfig::Aix, + :cron => Chef::Provider::Cron::Aix, + :package => Chef::Provider::Package::Aix } }, :default => { diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb index e4b35b64f3..354a640e59 100644 --- a/lib/chef/provider/batch.rb +++ b/lib/chef/provider/batch.rb @@ -6,9 +6,9 @@ # 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. @@ -29,7 +29,7 @@ class Chef def flags @new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c' end - + end end end diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb index a7218fea5a..f6f062a410 100644 --- a/lib/chef/provider/cron.rb +++ b/lib/chef/provider/cron.rb @@ -40,11 +40,12 @@ class Chef def whyrun_supported? true end - + def load_current_resource crontab_lines = [] @current_resource = Chef::Resource::Cron.new(@new_resource.name) @current_resource.user(@new_resource.user) + @cron_exists = false if crontab = read_crontab cron_found = false crontab.each_line do |line| @@ -81,7 +82,7 @@ class Chef @current_resource end - + def cron_different? CRON_ATTRIBUTES.any? do |cron_var| !@new_resource.send(cron_var).nil? && @new_resource.send(cron_var) != @current_resource.send(cron_var) @@ -93,14 +94,7 @@ class Chef newcron = String.new cron_found = false - newcron << "# Chef Name: #{new_resource.name}\n" - [ :mailto, :path, :shell, :home ].each do |v| - newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v) - end - @new_resource.environment.each do |name, value| - newcron << "#{name}=#{value}\n" - end - newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" + newcron = get_crontab_entry if @cron_exists unless cron_different? @@ -171,7 +165,7 @@ class Chef end crontab << line end - description = cron_found ? "remove #{@new_resource.name} from crontab" : + description = cron_found ? "remove #{@new_resource.name} from crontab" : "save unmodified crontab" converge_by(description) do write_crontab crontab @@ -202,13 +196,33 @@ class Chef end def write_crontab(crontab) + write_exception = false status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| - stdin.write crontab + begin + stdin.write crontab + rescue Errno::EPIPE => e + # popen4 could yield while child has already died. + write_exception = true + Chef::Log.debug("#{e.message}") + end end - if status.exitstatus > 0 + if status.exitstatus > 0 || write_exception raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}" end end + + def get_crontab_entry + newcron = "" + newcron << "# Chef Name: #{new_resource.name}\n" + [ :mailto, :path, :shell, :home ].each do |v| + newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v) + end + @new_resource.environment.each do |name, value| + newcron << "#{name}=#{value}\n" + end + newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" + newcron + end end end end diff --git a/lib/chef/provider/cron/aix.rb b/lib/chef/provider/cron/aix.rb new file mode 100644 index 0000000000..473700bf2f --- /dev/null +++ b/lib/chef/provider/cron/aix.rb @@ -0,0 +1,48 @@ +# +# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/provider/cron/unix" + +class Chef + class Provider + class Cron + class Aix < Chef::Provider::Cron::Unix + + private + + # For AIX we ignore env vars/[ :mailto, :path, :shell, :home ] + def get_crontab_entry + if env_vars_are_set? + raise Chef::Exceptions::Cron, "Aix cron entry does not support environment variables. Please set them in script and use script in cron." + end + + newcron = "" + newcron << "# Chef Name: #{new_resource.name}\n" + newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday}" + + newcron << " #{@new_resource.command}\n" + newcron + end + + def env_vars_are_set? + @new_resource.environment.length > 0 || !@new_resource.mailto.nil? || !@new_resource.path.nil? || !@new_resource.shell.nil? || !@new_resource.home.nil? + end + end + end + end +end diff --git a/lib/chef/provider/cron/solaris.rb b/lib/chef/provider/cron/solaris.rb index e0811ba0ac..20fa7abcce 100644 --- a/lib/chef/provider/cron/solaris.rb +++ b/lib/chef/provider/cron/solaris.rb @@ -1,15 +1,13 @@ # -# Author:: Bryan McLellan (btm@loftninjas.org) -# Author:: Toomas Pelberg (toomasp@gmx.net) -# Copyright:: Copyright (c) 2009 Bryan McLellan -# Copyright:: Copyright (c) 2010 Toomas Pelberg +# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) +# Copyright:: Copyright (c) 2013 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 +# 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, @@ -18,39 +16,7 @@ # limitations under the License. # -require 'chef/log' -require 'chef/provider' +require "chef/provider/cron/unix" -class Chef - class Provider - class Cron - class Solaris < Chef::Provider::Cron - - private - - def read_crontab - crontab = nil - status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr| - crontab = stdout.read - end - if status.exitstatus > 1 - raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}" - end - crontab - end - - def write_crontab(crontab) - tempcron = Tempfile.new("chef-cron") - tempcron << crontab - tempcron.flush - tempcron.chmod(0644) - status = run_command(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user) - tempcron.close! - if status.exitstatus > 0 - raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}" - end - end - end - end - end -end +# Just to create an alias so 'Chef::Provider::Cron::Solaris' is exposed and accessible to existing consumers of class. +Chef::Provider::Cron::Solaris = Chef::Provider::Cron::Unix diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb new file mode 100644 index 0000000000..5cb1bcda41 --- /dev/null +++ b/lib/chef/provider/cron/unix.rb @@ -0,0 +1,76 @@ +# +# Author:: Bryan McLellan (btm@loftninjas.org) +# Author:: Toomas Pelberg (toomasp@gmx.net) +# Copyright:: Copyright (c) 2009 Bryan McLellan +# Copyright:: Copyright (c) 2010 Toomas Pelberg +# 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/log' +require 'chef/provider' + +class Chef + class Provider + class Cron + class Unix < Chef::Provider::Cron + + private + + def read_crontab + crontab = nil + status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr| + crontab = stdout.read + end + if status.exitstatus > 1 + raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}" + end + crontab + end + + def write_crontab(crontab) + tempcron = Tempfile.new("chef-cron") + tempcron << crontab + tempcron.flush + tempcron.chmod(0644) + exit_status = 0 + error_message = "" + begin + status, stdout, stderr = run_command_and_return_stdout_stderr(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user) + exit_status = status.exitstatus + # solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :( + if stderr && stderr.include?("errors detected in input, no crontab file generated") + error_message = stderr + exit_status = 1 + end + rescue Chef::Exceptions::Exec => e + Chef::Log.debug(e.message) + exit_status = 1 + error_message = e.message + rescue ArgumentError => e + # usually raised on invalid user. + Chef::Log.debug(e.message) + exit_status = 1 + error_message = e.message + end + tempcron.close! + if exit_status > 0 + raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{exit_status}, message: #{error_message}" + end + end + + end + end + end +end diff --git a/lib/chef/provider/deploy/timestamped.rb b/lib/chef/provider/deploy/timestamped.rb index 9c2d55b490..ce921161e0 100644 --- a/lib/chef/provider/deploy/timestamped.rb +++ b/lib/chef/provider/deploy/timestamped.rb @@ -6,9 +6,9 @@ # 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. @@ -20,9 +20,9 @@ class Chef class Provider class Deploy class Timestamped < Chef::Provider::Deploy - + protected - + def release_slug Time.now.utc.strftime("%Y%m%d%H%M%S") end diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb index 1ee1da500c..cdd494a243 100644 --- a/lib/chef/provider/erl_call.rb +++ b/lib/chef/provider/erl_call.rb @@ -32,7 +32,7 @@ class Chef def whyrun_supported? true end - + def load_current_resource true end diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index 8d2a7d997d..2907688e88 100644 --- a/lib/chef/provider/execute.rb +++ b/lib/chef/provider/execute.rb @@ -29,7 +29,7 @@ class Chef def load_current_resource true end - + def whyrun_supported? true end @@ -56,7 +56,7 @@ class Chef if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? opts[:live_stream] = STDOUT end - converge_by("execute #{@new_resource.command}") do + converge_by("execute #{@new_resource.command}") do result = shell_out!(@new_resource.command, opts) Chef::Log.info("#{@new_resource} ran successfully") end diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb index 7cda1a873a..b22004eda0 100644 --- a/lib/chef/provider/git.rb +++ b/lib/chef/provider/git.rb @@ -273,6 +273,7 @@ class Chef run_opts[:group] = @new_resource.group if @new_resource.group run_opts[:environment] = {"GIT_SSH" => @new_resource.ssh_wrapper} if @new_resource.ssh_wrapper run_opts[:log_tag] = @new_resource.to_s + run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout run_opts end diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb index c941ed72bd..eacb033492 100644 --- a/lib/chef/provider/group.rb +++ b/lib/chef/provider/group.rb @@ -6,9 +6,9 @@ # 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. @@ -35,11 +35,11 @@ class Chef super @group_exists = true end - + def load_current_resource @current_resource = Chef::Resource::Group.new(@new_resource.name) @current_resource.group_name(@new_resource.group_name) - + group_info = nil begin group_info = Etc.getgrnam(@new_resource.group_name) @@ -47,26 +47,26 @@ class Chef @group_exists = false Chef::Log.debug("#{@new_resource} group does not exist") end - + if group_info @new_resource.gid(group_info.gid) unless @new_resource.gid @current_resource.gid(group_info.gid) @current_resource.members(group_info.mem) end - + @current_resource end def define_resource_requirements - requirements.assert(:modify) do |a| - a.assertion { @group_exists } + requirements.assert(:modify) do |a| + a.assertion { @group_exists } a.failure_message(Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!") a.whyrun("Group #{@new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.") end end - - # Check to see if a group needs any changes. Populate - # @change_desc with a description of why a change must occur + + # Check to see if a group needs any changes. Populate + # @change_desc with a description of why a change must occur # # ==== Returns # <true>:: If a change is required @@ -77,7 +77,7 @@ class Chef @change_desc = "change gid #{@current_resource.gid} to #{@new_resource.gid}" return true end - + if(@new_resource.append) missing_members = [] @new_resource.members.each do |member| @@ -96,24 +96,24 @@ class Chef end return false end - + def action_create case @group_exists when false - converge_by("create #{@new_resource}") do + converge_by("create #{@new_resource}") do create_group Chef::Log.info("#{@new_resource} created") end - else + else if compare_group - converge_by(["alter group #{@new_resource}", @change_desc ]) do + converge_by(["alter group #{@new_resource}", @change_desc ]) do manage_group Chef::Log.info("#{@new_resource} altered") end end end end - + def action_remove if @group_exists converge_by("remove group #{@new_resource}") do @@ -122,16 +122,16 @@ class Chef end end end - + def action_manage if @group_exists && compare_group converge_by(["manage group #{@new_resource}", @change_desc]) do - manage_group + manage_group Chef::Log.info("#{@new_resource} managed") end end end - + def action_modify if compare_group converge_by(["modify group #{@new_resource}", @change_desc]) do @@ -140,7 +140,7 @@ class Chef end end end - + def create_group raise NotImplementedError, "subclasses of Chef::Provider::Group should define #create_group" end diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb index a8ba32641c..d0b2a4d499 100644 --- a/lib/chef/provider/group/dscl.rb +++ b/lib/chef/provider/group/dscl.rb @@ -6,9 +6,9 @@ # 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. @@ -38,7 +38,7 @@ class Chef raise(Chef::Exceptions::Group,"dscl error: #{result.inspect}") if result[2] =~ /No such key: / return result[2] end - + # This is handled in providers/group.rb by Etc.getgrnam() # def group_exists?(group) # groups = safe_dscl("list /Groups") @@ -86,8 +86,8 @@ class Chef def define_resource_requirements super - requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?("/usr/bin/dscl") } + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?("/usr/bin/dscl") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource.name}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end @@ -96,13 +96,13 @@ class Chef def load_current_resource super end - + def create_group dscl_create_group set_gid set_members end - + def manage_group if @new_resource.group_name && (@current_resource.group_name != @new_resource.group_name) dscl_create_group @@ -114,12 +114,12 @@ class Chef set_members end end - + def dscl_create_group safe_dscl("create /Groups/#{@new_resource.group_name}") safe_dscl("create /Groups/#{@new_resource.group_name} Password '*'") end - + def remove_group safe_dscl("delete /Groups/#{@new_resource.group_name}") end diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb index 7fb27a7777..2638b82383 100644 --- a/lib/chef/provider/group/gpasswd.rb +++ b/lib/chef/provider/group/gpasswd.rb @@ -6,9 +6,9 @@ # 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. @@ -32,9 +32,9 @@ class Chef def define_resource_requirements super - requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?("/usr/bin/gpasswd") } - a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}" + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?("/usr/bin/gpasswd") } + a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb index 544fee4304..45ae308612 100644 --- a/lib/chef/provider/group/groupadd.rb +++ b/lib/chef/provider/group/groupadd.rb @@ -6,9 +6,9 @@ # 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. @@ -20,7 +20,7 @@ class Chef class Provider class Group class Groupadd < Chef::Provider::Group - + def required_binaries [ "/usr/sbin/groupadd", "/usr/sbin/groupmod", @@ -34,9 +34,9 @@ class Chef def define_resource_requirements super required_binaries.each do |required_binary| - requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?(required_binary) } - a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}" + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?(required_binary) } + a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end @@ -48,9 +48,9 @@ class Chef command << set_options command << groupadd_options run_command(:command => command) - modify_group_members + modify_group_members end - + # Manage the group when it already exists def manage_group command = "groupmod" @@ -58,12 +58,12 @@ class Chef run_command(:command => command) modify_group_members end - + # Remove the group def remove_group run_command(:command => "groupdel #{@new_resource.group_name}") end - + def modify_group_members raise Chef::Exceptions::Group, "you must override modify_group_members in #{self.to_s}" end @@ -87,6 +87,7 @@ class Chef def groupadd_options opts = '' opts << " -r" if @new_resource.system + opts << " -o" if @new_resource.non_unique opts end diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb index 3bf67a515a..66da8281be 100644 --- a/lib/chef/provider/group/pw.rb +++ b/lib/chef/provider/group/pw.rb @@ -6,9 +6,9 @@ # 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. @@ -20,21 +20,21 @@ class Chef class Provider class Group class Pw < Chef::Provider::Group - + def load_current_resource super end - + def define_resource_requirements super - requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?("/usr/sbin/pw") } + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?("/usr/sbin/pw") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end - + # Create the group def create_group command = "pw groupadd" @@ -42,7 +42,7 @@ class Chef command << set_members_option run_command(:command => command) end - + # Manage the group when it already exists def manage_group command = "pw groupmod" @@ -50,12 +50,12 @@ class Chef command << set_members_option run_command(:command => command) end - + # Remove the group def remove_group run_command(:command => "pw groupdel #{@new_resource.group_name}") end - + # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags # # ==== Returns @@ -86,7 +86,7 @@ class Chef end opt end - + end end end diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb index 0b66c1f912..4c343bddf9 100644 --- a/lib/chef/provider/group/suse.rb +++ b/lib/chef/provider/group/suse.rb @@ -6,9 +6,9 @@ # 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. @@ -32,8 +32,8 @@ class Chef def define_resource_requirements super - requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?("/usr/sbin/groupmod") } + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?("/usr/sbin/groupmod") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb index 0d44d3940f..5788ac8fad 100644 --- a/lib/chef/provider/group/usermod.rb +++ b/lib/chef/provider/group/usermod.rb @@ -6,9 +6,9 @@ # 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. @@ -22,7 +22,7 @@ class Chef class Provider class Group class Usermod < Chef::Provider::Group::Groupadd - + def load_current_resource super end @@ -30,24 +30,24 @@ class Chef def define_resource_requirements super - requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?("/usr/sbin/usermod") } + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?("/usr/sbin/usermod") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end requirements.assert(:modify, :create) do |a| - a.assertion { @new_resource.members.empty? || @new_resource.append } + 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" # No whyrun alternative - this action is simply not supported. end end - + def modify_group_members case node[:platform] when "openbsd", "netbsd", "aix", "solaris2", "smartos" append_flags = "-G" - when "solaris", "suse" + when "solaris", "suse", "opensuse" append_flags = "-a -G" end diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb index 88280408cd..da12366329 100644 --- a/lib/chef/provider/group/windows.rb +++ b/lib/chef/provider/group/windows.rb @@ -6,9 +6,9 @@ # 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. @@ -25,16 +25,16 @@ class Chef class Provider class Group class Windows < Chef::Provider::Group - + def initialize(new_resource,run_context) super - @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.name) + @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name) end def load_current_resource @current_resource = Chef::Resource::Group.new(@new_resource.name) @current_resource.group_name(@new_resource.group_name) - + members = nil begin members = @net_group.local_get_members @@ -49,12 +49,12 @@ class Chef @current_resource end - + def create_group @net_group.local_add manage_group end - + def manage_group if @new_resource.append begin @@ -68,11 +68,11 @@ class Chef @net_group.local_set_members(@new_resource.members) end end - + def remove_group @net_group.local_delete end - + end end end diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb index a5bc3b5e04..1e0aa8b4a0 100644 --- a/lib/chef/provider/http_request.rb +++ b/lib/chef/provider/http_request.rb @@ -17,26 +17,27 @@ # require 'tempfile' +require 'chef/http/simple' class Chef class Provider class HttpRequest < Chef::Provider - attr_accessor :rest + attr_accessor :http def whyrun_supported? true end def load_current_resource - @rest = Chef::REST.new(@new_resource.url, nil, nil) + @http = Chef::HTTP::Simple.new(@new_resource.url) end # Send a HEAD request to @new_resource.url, with ?message=@new_resource.message def action_head message = check_message(@new_resource.message) # returns true from Chef::REST if returns 2XX (Net::HTTPSuccess) - modified = @rest.head( + modified = @http.head( "#{@new_resource.url}?message=#{message}", @new_resource.headers ) @@ -53,9 +54,8 @@ class Chef converge_by("#{@new_resource} GET to #{@new_resource.url}") do message = check_message(@new_resource.message) - body = @rest.get( + body = @http.get( "#{@new_resource.url}?message=#{message}", - false, @new_resource.headers ) Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful") @@ -67,7 +67,7 @@ class Chef def action_put converge_by("#{@new_resource} PUT to #{@new_resource.url}") do message = check_message(@new_resource.message) - body = @rest.put( + body = @http.put( "#{@new_resource.url}", message, @new_resource.headers @@ -81,7 +81,7 @@ class Chef def action_post converge_by("#{@new_resource} POST to #{@new_resource.url}") do message = check_message(@new_resource.message) - body = @rest.post( + body = @http.post( "#{@new_resource.url}", message, @new_resource.headers @@ -94,7 +94,7 @@ class Chef # Send a DELETE request to @new_resource.url def action_delete converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do - body = @rest.delete( + body = @http.delete( "#{@new_resource.url}", @new_resource.headers ) diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb index 0aacc9e616..31f88e5406 100644 --- a/lib/chef/provider/ifconfig.rb +++ b/lib/chef/provider/ifconfig.rb @@ -26,11 +26,11 @@ require 'erb' # # int = {Hash with your network settings...} # -# ifconfig int['ip'] do -# ignore_failure true -# device int['dev'] -# mask int['mask'] -# gateway int['gateway'] +# ifconfig int['ip'] do +# ignore_failure true +# device int['dev'] +# mask int['mask'] +# gateway int['gateway'] # mtu int['mtu'] # end @@ -76,7 +76,7 @@ class Chef @interface = @interfaces.fetch(@new_resource.device) @current_resource.target(@new_resource.target) - @current_resource.device(@int_name) + @current_resource.device(@new_resource.device) @current_resource.inet_addr(@interface["inet_addr"]) @current_resource.hwaddr(@interface["hwaddr"]) @current_resource.bcast(@interface["bcast"]) @@ -89,12 +89,12 @@ class Chef @current_resource end - def define_resource_requirements - requirements.assert(:all_actions) do |a| + def define_resource_requirements + requirements.assert(:all_actions) do |a| a.assertion { @status.exitstatus == 0 } a.failure_message Chef::Exceptions::Ifconfig, "ifconfig failed - #{@status.inspect}!" # no whyrun - if the base ifconfig used in load_current_resource fails - # there's no reasonable action that could have been taken in the course of + # there's no reasonable action that could have been taken in the course of # a chef run to fix it. end end @@ -102,40 +102,32 @@ class Chef def action_add # check to see if load_current_resource found interface in ifconfig unless @current_resource.inet_addr - unless @new_resource.device == "lo" - command = "ifconfig #{@new_resource.device} #{@new_resource.name}" - command << " netmask #{@new_resource.mask}" if @new_resource.mask - command << " metric #{@new_resource.metric}" if @new_resource.metric - command << " mtu #{@new_resource.mtu}" if @new_resource.mtu - end - converge_by ("run #{command} to add #{@new_resource}") do - run_command( - :command => command - ) - Chef::Log.info("#{@new_resource} added") + unless @new_resource.device == loopback_device + command = add_command + converge_by ("run #{command} to add #{@new_resource}") do + run_command( + :command => command + ) + Chef::Log.info("#{@new_resource} added") + # Write out the config files + generate_config + end end end - - # Write out the config files - generate_config end def action_enable # check to see if load_current_resource found ifconfig # enables, but does not manage config files unless @current_resource.inet_addr - unless @new_resource.device == "lo" - command = "ifconfig #{@new_resource.device} #{@new_resource.name}" - command << " netmask #{@new_resource.mask}" if @new_resource.mask - command << " metric #{@new_resource.metric}" if @new_resource.metric - command << " mtu #{@new_resource.mtu}" if @new_resource.mtu - end - - converge_by ("run #{command} to enable #{@new_resource}") do - run_command( - :command => command - ) - Chef::Log.info("#{@new_resource} enabled") + unless @new_resource.device == loopback_device + command = enable_command + converge_by ("run #{command} to enable #{@new_resource}") do + run_command( + :command => command + ) + Chef::Log.info("#{@new_resource} enabled") + end end end end @@ -143,7 +135,7 @@ class Chef def action_delete # check to see if load_current_resource found the interface if @current_resource.device - command = "ifconfig #{@new_resource.device} down" + command = delete_command converge_by ("run #{command} to delete #{@new_resource}") do run_command( :command => command @@ -160,7 +152,7 @@ class Chef # check to see if load_current_resource found the interface # disables, but leaves config files in place. if @current_resource.device - command = "ifconfig #{@new_resource.device} down" + command = disable_command converge_by ("run #{command} to disable #{@new_resource}") do run_command( :command => command @@ -199,6 +191,34 @@ class Chef Chef::Log.info("#{@new_resource} deleted configuration file") end + private + def add_command + command = "ifconfig #{@new_resource.device} #{@new_resource.name}" + command << " netmask #{@new_resource.mask}" if @new_resource.mask + command << " metric #{@new_resource.metric}" if @new_resource.metric + command << " mtu #{@new_resource.mtu}" if @new_resource.mtu + command + end + + def enable_command + command = "ifconfig #{@new_resource.device} #{@new_resource.name}" + command << " netmask #{@new_resource.mask}" if @new_resource.mask + command << " metric #{@new_resource.metric}" if @new_resource.metric + command << " mtu #{@new_resource.mtu}" if @new_resource.mtu + command + end + + def disable_command + "ifconfig #{@new_resource.device} down" + end + + def delete_command + "ifconfig #{@new_resource.device} down" + end + + def loopback_device + 'lo' + end end end end diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb new file mode 100644 index 0000000000..460b1ba7f2 --- /dev/null +++ b/lib/chef/provider/ifconfig/aix.rb @@ -0,0 +1,99 @@ +# +# Author:: Kaustubh Deorukhkar (kaustubh@clogeny.com) +# Copyright:: Copyright (c) 2013 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/provider/ifconfig' + +class Chef + class Provider + class Ifconfig + class Aix < Chef::Provider::Ifconfig + + def load_current_resource + @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name) + + @interface_exists = false + found_interface = false + interface = {} + + @status = popen4("ifconfig -a") do |pid, stdin, stdout, stderr| + stdout.each do |line| + + if !found_interface + if line =~ /^(\S+):\sflags=(\S+)/ + # We have interface name, if this is the interface for @current_resource, load info else skip till next interface is found. + if $1 == @new_resource.device + # Found interface + found_interface = true + @interface_exists = true + @current_resource.target(@new_resource.target) + @current_resource.device($1) + interface[:flags] = $2 + @current_resource.metric($1) if line =~ /metric\s(\S+)/ + end + end + else + # parse interface related information, stop when next interface is found. + if line =~ /^(\S+):\sflags=(\S+)/ + # we are done parsing interface info and hit another one, so stop. + found_interface = false + break + else + if found_interface + # read up interface info + @current_resource.inet_addr($1) if line =~ /inet\s(\S+)\s/ + @current_resource.bcast($1) if line =~ /broadcast\s(\S+)/ + @current_resource.mask(hex_to_dec_netmask($1)) if line =~ /netmask\s(\S+)\s/ + end + end + end + end + end + + @current_resource + end + + private + def add_command + # ifconfig changes are temporary, chdev persist across reboots. + raise Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action" if @new_resource.metric + command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}" + command << " -a netmask=#{@new_resource.mask}" if @new_resource.mask + command << " -a mtu=#{@new_resource.mtu}" if @new_resource.mtu + command + end + + def delete_command + # ifconfig changes are temporary, chdev persist across reboots. + "chdev -l #{@new_resource.device} -a state=down" + end + + def loopback_device + "lo0" + end + + def hex_to_dec_netmask(netmask) + # example '0xffff0000' -> '255.255.0.0' + dec = netmask[2..3].to_i(16).to_s(10) + [4,6,8].each { |n| dec = dec + "." + netmask[n..n+1].to_i(16).to_s(10) } + dec + end + + end + end + end +end diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb index 927ee72fcc..1c970cc888 100644 --- a/lib/chef/provider/log.rb +++ b/lib/chef/provider/log.rb @@ -6,9 +6,9 @@ # 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. @@ -32,7 +32,7 @@ class Chef def load_current_resource true end - + # Write the log to Chef's log # # === Return diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb index d93ff69c13..51c9b8d3c6 100644 --- a/lib/chef/provider/mdadm.rb +++ b/lib/chef/provider/mdadm.rb @@ -47,8 +47,9 @@ class Chef def action_create unless @current_resource.exists - converge_by("create RAID device #{new_resource.raid_device}") do - command = "yes | mdadm --create #{@new_resource.raid_device} --chunk=#{@new_resource.chunk} --level #{@new_resource.level}" + converge_by("create RAID device #{new_resource.raid_device}") do + command = "yes | mdadm --create #{@new_resource.raid_device} --level #{@new_resource.level}" + command << " --chunk=#{@new_resource.chunk}" unless @new_resource.level == 1 command << " --metadata=#{@new_resource.metadata}" command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}" @@ -63,7 +64,7 @@ class Chef def action_assemble unless @current_resource.exists - converge_by("assemble RAID device #{new_resource.raid_device}") do + converge_by("assemble RAID device #{new_resource.raid_device}") do command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}" Chef::Log.debug("#{@new_resource} mdadm command: #{command}") shell_out!(command) @@ -76,7 +77,7 @@ class Chef def action_stop if @current_resource.exists - converge_by("stop RAID device #{new_resource.raid_device}") do + converge_by("stop RAID device #{new_resource.raid_device}") do command = "yes | mdadm --stop #{@new_resource.raid_device}" Chef::Log.debug("#{@new_resource} mdadm command: #{command}") shell_out!(command) diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb index 6b9dd91ac8..5f58baa396 100644 --- a/lib/chef/provider/mount.rb +++ b/lib/chef/provider/mount.rb @@ -6,9 +6,9 @@ # 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. @@ -37,7 +37,7 @@ class Chef def action_mount unless @current_resource.mounted - converge_by("mount #{@current_resource.device} to #{@current_resource.mount_point}") do + converge_by("mount #{@current_resource.device} to #{@current_resource.mount_point}") do status = mount_fs() if status Chef::Log.info("#{@new_resource} mounted") @@ -50,7 +50,7 @@ class Chef def action_umount if @current_resource.mounted - converge_by("unmount #{@current_resource.device}") do + converge_by("unmount #{@current_resource.device}") do status = umount_fs() if status Chef::Log.info("#{@new_resource} unmounted") @@ -66,7 +66,7 @@ class Chef raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount" else if @current_resource.mounted - converge_by("remount #{@current_resource.device}") do + converge_by("remount #{@current_resource.device}") do status = remount_fs() if status Chef::Log.info("#{@new_resource} remounted") @@ -80,7 +80,7 @@ class Chef def action_enable unless @current_resource.enabled && mount_options_unchanged? - converge_by("remount #{@current_resource.device}") do + converge_by("remount #{@current_resource.device}") do status = enable_fs if status Chef::Log.info("#{@new_resource} enabled") @@ -93,7 +93,7 @@ class Chef def action_disable if @current_resource.enabled - converge_by("remount #{@current_resource.device}") do + converge_by("remount #{@current_resource.device}") do status = disable_fs if status Chef::Log.info("#{@new_resource} disabled") @@ -115,14 +115,14 @@ class Chef def remount_fs raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount" end - + def enable_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable" + raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable" end - + def disable_fs - raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable" - end + raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable" + end end end end diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb new file mode 100644 index 0000000000..0d7e11a1b8 --- /dev/null +++ b/lib/chef/provider/mount/aix.rb @@ -0,0 +1,179 @@ +# +# Author:: +# Copyright:: Copyright (c) 2009 Opscode, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/provider/mount' + +class Chef + class Provider + class Mount + class Aix < Chef::Provider::Mount::Mount + + # Override for aix specific handling + def initialize(new_resource, run_context) + super + # options and fstype are set to "defaults" and "auto" respectively in the Mount Resource class. These options are not valid for AIX, override them. + if @new_resource.options[0] == "defaults" + @new_resource.options.clear + end + if @new_resource.fstype == "auto" + @new_resource.fstype = nil + end + end + + def enabled? + # Check to see if there is an entry in /etc/filesystems. Last entry for a volume wins. Using command "lsfs" to fetch entries. + enabled = false + + # lsfs o/p = #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct + # search only for current mount point + shell_out("lsfs -c #{@new_resource.mount_point}").stdout.each_line do | line | + case line + when /^#\s/ + next + when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}:(\S+):(\[\S+\])?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/ + # mount point entry with ipv6 address for nodename (ipv6 address use ':') + enabled = true + @current_resource.fstype($1) + @current_resource.options($5) + Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems") + next + when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}::(\S+):(\S+)?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/ + # mount point entry with hostname or ipv4 address + enabled = true + @current_resource.fstype($1) + @current_resource.options($5) + Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems") + next + when /^#{Regexp.escape(@new_resource.mount_point)}/ + enabled=false + Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems") + end + end + @current_resource.enabled(enabled) + end + + def mounted? + mounted = false + shell_out!("mount").stdout.each_line do |line| + if network_device? + device_details = device_fstab.split(":") + search_device = device_details[1] + else + search_device = device_fstab_regex + end + case line + when /#{search_device}\s+#{Regexp.escape(@new_resource.mount_point)}/ + mounted = true + Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}") + when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/ + mounted = false + Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab") + end + end + @current_resource.mounted(mounted) + end + + def mount_fs + unless @current_resource.mounted + mountable? + command = "mount -v #{@new_resource.fstype}" + + if !(@new_resource.options.nil? || @new_resource.options.empty?) + command << " -o #{@new_resource.options.join(',')}" + end + + command << case @new_resource.device_type + when :device + " #{device_real}" + when :label + " -L #{@new_resource.device}" + when :uuid + " -U #{@new_resource.device}" + end + command << " #{@new_resource.mount_point}" + shell_out!(command) + Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}") + else + Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}") + end + end + + def remount_command + if !(@new_resource.options.nil? || @new_resource.options.empty?) + return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.device} #{@new_resource.mount_point}" + else + return "mount -o remount #{@new_resource.device} #{@new_resource.mount_point}" + end + end + + def enable_fs + if @current_resource.enabled && mount_options_unchanged? + Chef::Log.debug("#{@new_resource} is already enabled - nothing to do") + return nil + end + + if @current_resource.enabled + # The current options don't match what we have, so + # disable, then enable. + disable_fs + end + ::File.open("/etc/filesystems", "a") do |fstab| + fstab.puts("#{@new_resource.mount_point}:") + if network_device? + device_details = device_fstab.split(":") + fstab.puts("\tdev\t\t= #{device_details[1]}") + fstab.puts("\tnodename\t\t= #{device_details[0]}") + else + fstab.puts("\tdev\t\t= #{device_fstab}") + end + fstab.puts("\tvfs\t\t= #{@new_resource.fstype}") + fstab.puts("\tmount\t\t= false") + fstab.puts "\toptions\t\t= #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty? + Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}") + end + end + + def disable_fs + contents = [] + if @current_resource.enabled + found_device = false + ::File.open("/etc/filesystems", "r").each_line do |line| + case line + when /^\/.+:\s*$/ + if line =~ /#{Regexp.escape(@new_resource.mount_point)}+:/ + found_device = true + else + found_device = false + end + end + if !found_device + contents << line + end + end + ::File.open("/etc/filesystems", "w") do |fstab| + contents.each { |line| fstab.puts line} + end + else + Chef::Log.debug("#{@new_resource} is not enabled - nothing to do") + end + end + + end + end + end +end diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb index ec54831017..25dfd42725 100644 --- a/lib/chef/provider/mount/mount.rb +++ b/lib/chef/provider/mount/mount.rb @@ -39,7 +39,7 @@ class Chef mounted? enabled? end - + def mountable? # only check for existence of non-remote devices if (device_should_exist? && !::File.exists?(device_real) ) @@ -49,7 +49,7 @@ class Chef end return true end - + def enabled? # Check to see if there is a entry in /etc/fstab. Last entry for a volume wins. enabled = false @@ -72,17 +72,27 @@ class Chef end @current_resource.enabled(enabled) end - + def mounted? mounted = false + + # "mount" outputs the mount points as real paths. Convert + # the mount_point of the resource to a real path in case it + # contains symlinks in its parents dirs. + real_mount_point = if ::File.exists? @new_resource.mount_point + ::File.realpath(@new_resource.mount_point) + else + @new_resource.mount_point + end + shell_out!("mount").stdout.each_line do |line| case line - when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(@new_resource.mount_point)}/ + when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}/ mounted = true - Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}") - when /^([\/\w])+\son\s#{Regexp.escape(@new_resource.mount_point)}\s+/ + Chef::Log.debug("Special device #{device_logstring} mounted as #{real_mount_point}") + when /^([\/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+/ mounted = false - Chef::Log.debug("Special device #{$~[1]} mounted as #{@new_resource.mount_point}") + Chef::Log.debug("Special device #{$~[1]} mounted as #{real_mount_point}") end end @current_resource.mounted(mounted) @@ -118,9 +128,13 @@ class Chef end end + def remount_command + return "mount -o remount #{@new_resource.mount_point}" + end + def remount_fs if @current_resource.mounted and @new_resource.supports[:remount] - shell_out!("mount -o remount #{@new_resource.mount_point}") + shell_out!(remount_command) @new_resource.updated_by_last_action(true) Chef::Log.debug("#{@new_resource} is remounted at #{@new_resource.mount_point}") elsif @current_resource.mounted @@ -137,7 +151,7 @@ class Chef Chef::Log.debug("#{@new_resource} is already enabled - nothing to do") return nil end - + if @current_resource.enabled # The current options don't match what we have, so # disable, then enable. @@ -152,7 +166,7 @@ class Chef def disable_fs if @current_resource.enabled contents = [] - + found = false ::File.readlines("/etc/fstab").reverse_each do |line| if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}/ @@ -163,7 +177,7 @@ class Chef contents << line end end - + ::File.open("/etc/fstab", "w") do |fstab| contents.reverse_each { |line| fstab.puts line} end @@ -177,7 +191,7 @@ class Chef end def device_should_exist? - ( @new_resource.device != "none" ) && + ( @new_resource.device != "none" ) && ( not network_device? ) && ( not %w[ tmpfs fuse ].include? @new_resource.fstype ) end @@ -196,7 +210,7 @@ class Chef end def device_real - if @real_device == nil + if @real_device == nil if @new_resource.device_type == :device @real_device = @new_resource.device else @@ -243,14 +257,14 @@ class Chef device_fstab end end - + def mount_options_unchanged? @current_resource.fstype == @new_resource.fstype and @current_resource.options == @new_resource.options and @current_resource.dump == @new_resource.dump and @current_resource.pass == @new_resource.pass end - + end end end diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index a28a6f93fb..c7692a9746 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -50,7 +50,7 @@ class Chef requirements.assert(:upgrade) do |a| # Can't upgrade what we don't have - a.assertion { !(@current_resource.version.nil? && candidate_version.nil?) } + a.assertion { !(@current_resource.version.nil? && candidate_version.nil?) } a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{@new_resource.package_name}") a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured") end @@ -71,9 +71,9 @@ class Chef # We need to make sure we handle the preseed file if @new_resource.response_file if preseed_file = get_preseed_file(@new_resource.package_name, install_version) - converge_by("preseed package #{@new_resource.package_name}") do + converge_by("preseed package #{@new_resource.package_name}") do preseed_package(preseed_file) - end + end end end description = install_version ? "version #{install_version} of" : "" @@ -92,7 +92,7 @@ class Chef @new_resource.version(candidate_version) orig_version = @current_resource.version || "uninstalled" converge_by("upgrade package #{@new_resource.package_name} from #{orig_version} to #{candidate_version}") do - status = upgrade_package(@new_resource.package_name, candidate_version) + upgrade_package(@new_resource.package_name, candidate_version) Chef::Log.info("#{@new_resource} upgraded from #{orig_version} to #{candidate_version}") end end @@ -146,7 +146,7 @@ class Chef if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version) converge_by("reconfigure package #{@new_resource.package_name}") do preseed_package(preseed_file) - status = reconfig_package(@new_resource.package_name, @current_resource.version) + reconfig_package(@new_resource.package_name, @current_resource.version) Chef::Log.info("#{@new_resource} reconfigured") end else @@ -198,21 +198,21 @@ class Chef Chef::Log.debug("#{@new_resource} fetching preseed file to #{cache_seed_to}") - begin + + if template_available?(@new_resource.response_file) + Chef::Log.debug("#{@new_resource} fetching preseed file via Template") remote_file = Chef::Resource::Template.new(cache_seed_to, run_context) - remote_file.cookbook_name = @new_resource.cookbook_name - remote_file.source(@new_resource.response_file) - remote_file.backup(false) - provider = Chef::Platform.provider_for_resource(remote_file, :create) - provider.template_location - rescue - Chef::Log.debug("#{@new_resource} fetching preseed file via Template resource failed, fallback to CookbookFile resource") + elsif cookbook_file_available?(@new_resource.response_file) + Chef::Log.debug("#{@new_resource} fetching preseed file via cookbook_file") remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context) - remote_file.cookbook_name = @new_resource.cookbook_name - remote_file.source(@new_resource.response_file) - remote_file.backup(false) + else + message = "No template or cookbook file found for response file #{@new_resource.response_file}" + raise Chef::Exceptions::FileNotFound, message end + remote_file.cookbook_name = @new_resource.cookbook_name + remote_file.source(@new_resource.response_file) + remote_file.backup(false) remote_file end @@ -224,6 +224,16 @@ class Chef @new_resource.version == @current_resource.version end + private + + def template_available?(path) + run_context.has_template_in_cookbook?(@new_resource.cookbook_name, path) + end + + def cookbook_file_available?(path) + run_context.has_cookbook_file_in_cookbook?(@new_resource.cookbook_name, path) + end + end end end diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb new file mode 100644 index 0000000000..4df0ea7a33 --- /dev/null +++ b/lib/chef/provider/package/aix.rb @@ -0,0 +1,146 @@ +# +# Author:: Deepali Jagtap +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +require 'chef/provider/package' +require 'chef/mixin/command' +require 'chef/resource/package' +require 'chef/mixin/get_source_from_package' + +class Chef + class Provider + class Package + class Aix < Chef::Provider::Package + + include Chef::Mixin::GetSourceFromPackage + + def define_resource_requirements + super + requirements.assert(:install) do |a| + a.assertion { @new_resource.source } + a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install" + end + requirements.assert(:all_actions) do |a| + a.assertion { !@new_resource.source || @package_source_found } + a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" + a.whyrun "would assume #{@new_resource.source} would be have previously been made available" + end + end + + def load_current_resource + @current_resource = Chef::Resource::Package.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @new_resource.version(nil) + + if @new_resource.source + @package_source_found = ::File.exists?(@new_resource.source) + if @package_source_found + Chef::Log.debug("#{@new_resource} checking pkg status") + status = popen4("installp -L -d #{@new_resource.source}") do |pid, stdin, stdout, stderr| + package_found = false + stdout.each do |line| + case line + when /#{@new_resource.package_name}:/ + package_found = true + fields = line.split(":") + @new_resource.version(fields[2]) + end + end + end + end + end + + Chef::Log.debug("#{@new_resource} checking install state") + status = popen4("lslpp -lcq #{@current_resource.package_name}") do |pid, stdin, stdout, stderr| + stdout.each do |line| + case line + when /#{@current_resource.package_name}/ + fields = line.split(":") + Chef::Log.debug("#{@new_resource} version #{fields[2]} is already installed") + @current_resource.version(fields[2]) + end + end + end + + unless status.exitstatus == 0 || status.exitstatus == 1 + raise Chef::Exceptions::Package, "lslpp failed - #{status.inspect}!" + end + + @current_resource + end + + def candidate_version + return @candidate_version if @candidate_version + status = popen4("installp -L -d #{@new_resource.source}") do |pid, stdin, stdout, stderr| + stdout.each_line do |line| + case line + when /\w:{Regexp.escape(@new_resource.package_name)}:(.*)/ + fields = line.split(":") + @candidate_version = fields[2] + @new_resource.version(fields[2]) + Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}") + end + end + end + unless status.exitstatus == 0 + raise Chef::Exceptions::Package, "installp -L -d #{@new_resource.source} - #{status.inspect}!" + end + @candidate_version + end + + # + # The install/update action needs to be tested with various kinds of packages + # on AIX viz. packages with or without licensing file dependencies, packages + # with dependencies on other packages which will help to test additional + # options of installp. + # So far, the code has been tested only with standalone packages. + # + def install_package(name, version) + Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}") + if @new_resource.options.nil? + run_command_with_systems_locale( + :command => "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" + ) + Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") + else + run_command_with_systems_locale( + :command => "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" + ) + Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") + end + end + + alias_method :upgrade_package, :install_package + + def remove_package(name, version) + if @new_resource.options.nil? + run_command_with_systems_locale( + :command => "installp -u #{name}" + ) + Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") + else + run_command_with_systems_locale( + :command => "installp -u #{expand_options(@new_resource.options)} #{name}" + ) + Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") + end + end + + end + end + end +end diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb index e8939b494e..dc7b3f2086 100644 --- a/lib/chef/provider/package/apt.rb +++ b/lib/chef/provider/package/apt.rb @@ -65,7 +65,8 @@ class Chef @is_virtual_package = true showpkg = shell_out!("apt-cache showpkg #{package}").stdout providers = Hash.new - showpkg.rpartition(/Reverse Provides:? #{$/}/)[2].each_line do |line| + # Returns all lines after 'Reverse Provides:' + showpkg.rpartition(/Reverse Provides:\s*#{$/}/)[2].each_line do |line| provider, version = line.split providers[provider] = version end @@ -90,12 +91,7 @@ class Chef def install_package(name, version) package_name = "#{name}=#{version}" package_name = name if @is_virtual_package - run_command_with_systems_locale( - :command => "apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}", - :environment => { - "DEBIAN_FRONTEND" => "noninteractive" - } - ) + run_noninteractive("apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}") end def upgrade_package(name, version) @@ -104,41 +100,30 @@ class Chef def remove_package(name, version) package_name = "#{name}" - run_command_with_systems_locale( - :command => "apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}", - :environment => { - "DEBIAN_FRONTEND" => "noninteractive" - } - ) + run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}") end def purge_package(name, version) - run_command_with_systems_locale( - :command => "apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}", - :environment => { - "DEBIAN_FRONTEND" => "noninteractive" - } - ) + run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}") end def preseed_package(preseed_file) Chef::Log.info("#{@new_resource} pre-seeding package installation instructions") - run_command_with_systems_locale( - :command => "debconf-set-selections #{preseed_file}", - :environment => { - "DEBIAN_FRONTEND" => "noninteractive" - } - ) + run_noninteractive("debconf-set-selections #{preseed_file}") end def reconfig_package(name, version) Chef::Log.info("#{@new_resource} reconfiguring") - run_command_with_systems_locale( - :command => "dpkg-reconfigure #{name}", - :environment => { - "DEBIAN_FRONTEND" => "noninteractive" - } - ) + run_noninteractive("dpkg-reconfigure #{name}") + end + + private + + # Runs command via shell_out with magic environment to disable + # interactive prompts. Command is run with default localization rather + # than forcing locale to "C", so command output may not be stable. + def run_noninteractive(command) + shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil }) end end diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb index 795a7b308b..8ec1ad5878 100644 --- a/lib/chef/provider/package/dpkg.rb +++ b/lib/chef/provider/package/dpkg.rb @@ -6,9 +6,9 @@ # 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. @@ -32,14 +32,14 @@ class Chef include Chef::Mixin::GetSourceFromPackage def define_resource_requirements super - requirements.assert(:install) do |a| + requirements.assert(:install) do |a| a.assertion{ not @new_resource.source.nil? } a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install" end # TODO this was originally written for any action in which .source is provided # but would it make more sense to only look at source if the action is :install? - requirements.assert(:all_actions) do |a| + requirements.assert(:all_actions) do |a| a.assertion { @source_exists } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" a.whyrun "Assuming it would have been previously downloaded." @@ -53,7 +53,7 @@ class Chef @new_resource.version(nil) if @new_resource.source - @source_exists = ::File.exists?(@new_resource.source) + @source_exists = ::File.exists?(@new_resource.source) if @source_exists # Get information from the package if supplied Chef::Log.debug("#{@new_resource} checking dpkg status") @@ -71,7 +71,7 @@ class Chef end end - + # Check to see if it is installed package_installed = nil Chef::Log.debug("#{@new_resource} checking install state") @@ -92,10 +92,10 @@ class Chef unless status.exitstatus == 0 || status.exitstatus == 1 raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!" end - + @current_resource end - + def install_package(name, version) run_command_with_systems_locale( :command => "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}", @@ -113,7 +113,7 @@ class Chef } ) end - + def purge_package(name, version) run_command_with_systems_locale( :command => "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}", diff --git a/lib/chef/provider/package/freebsd.rb b/lib/chef/provider/package/freebsd.rb index afdd0d812e..f9cb5eb422 100644 --- a/lib/chef/provider/package/freebsd.rb +++ b/lib/chef/provider/package/freebsd.rb @@ -37,7 +37,7 @@ class Chef def current_installed_version pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1]) - pkg_info.stdout[/^#{package_name}-(.+)/, 1] + pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1] end def port_path @@ -52,7 +52,7 @@ class Chef # Otherwise look up the path to the ports directory using 'whereis' else whereis = shell_out!("whereis -s #{@new_resource.package_name}", :env => nil) - unless path = whereis.stdout[/^#{@new_resource.package_name}:\s+(.+)$/, 1] + unless path = whereis.stdout[/^#{Regexp.escape(@new_resource.package_name)}:\s+(.+)$/, 1] raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}" end path diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb index 5beb46a20a..2c6d98d81a 100644 --- a/lib/chef/provider/package/ips.rb +++ b/lib/chef/provider/package/ips.rb @@ -33,12 +33,12 @@ class Chef def define_resource_requirements super - + requirements.assert(:all_actions) do |a| a.assertion { ! @candidate_version.nil? } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found" a.whyrun "Assuming package #{@new_resource.package_name} would have been made available." - end + end end def load_current_resource @@ -52,7 +52,7 @@ class Chef Chef::Log.debug("Checking package status for #{package}") installed = false depends = false - + shell_out!("pkg info -r #{package}").stdout.each_line do |line| case line when /^\s+State: Installed/ diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb index fd33788944..6ef303ee4f 100644 --- a/lib/chef/provider/package/macports.rb +++ b/lib/chef/provider/package/macports.rb @@ -44,7 +44,7 @@ class Chef def install_package(name, version) unless @current_resource.version == version command = "port#{expand_options(@new_resource.options)} install #{name}" - command << " @#{version}" if version and !version.empty? + command << " @#{version}" if version and !version.empty? run_command_with_systems_locale( :command => command ) diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index f81486ae84..2e8bb7850b 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -6,9 +6,9 @@ # 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. @@ -24,7 +24,7 @@ class Chef class Provider class Package class Pacman < Chef::Provider::Package - + def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) @@ -84,27 +84,27 @@ class Chef @candidate_version end - + def install_package(name, version) run_command_with_systems_locale( :command => "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" ) end - + def upgrade_package(name, version) install_package(name, version) end - + def remove_package(name, version) run_command_with_systems_locale( :command => "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" ) end - + def purge_package(name, version) remove_package(name, version) end - + end end end diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb index 033ce8efb9..616a78a2f5 100644 --- a/lib/chef/provider/package/rpm.rb +++ b/lib/chef/provider/package/rpm.rb @@ -6,9 +6,9 @@ # 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. @@ -30,18 +30,18 @@ class Chef def define_resource_requirements super - requirements.assert(:all_actions) do |a| + requirements.assert(:all_actions) do |a| a.assertion { @package_source_exists } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" a.whyrun "Assuming package #{@new_resource.name} would have been made available." end - requirements.assert(:all_actions) do |a| - a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) } + requirements.assert(:all_actions) do |a| + a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) } a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}" a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}." end end - + def load_current_resource @package_source_provided = true @package_source_exists = true @@ -49,13 +49,13 @@ class Chef @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) @new_resource.version(nil) - + if @new_resource.source unless ::File.exists?(@new_resource.source) @package_source_exists = false return end - + Chef::Log.debug("#{@new_resource} checking rpm status") status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr| stdout.each do |line| @@ -72,7 +72,7 @@ class Chef return end end - + Chef::Log.debug("#{@new_resource} checking install state") @rpm_status = popen4("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") do |pid, stdin, stdout, stderr| stdout.each do |line| @@ -83,11 +83,11 @@ class Chef end end end - - + + @current_resource end - + def install_package(name, version) unless @current_resource.version run_command_with_systems_locale( @@ -99,9 +99,9 @@ class Chef ) end end - + alias_method :upgrade_package, :install_package - + def remove_package(name, version) if version run_command_with_systems_locale( diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index 28d332420b..b423c199a0 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -72,7 +72,7 @@ class Chef raise NotImplementedError end - ## + ## # A rubygems specification object containing the list of gemspecs for all # available gems in the gem installation. # Implemented by subclasses diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb index b17f6f2564..28d56ddc2c 100644 --- a/lib/chef/provider/package/smartos.rb +++ b/lib/chef/provider/package/smartos.rb @@ -64,14 +64,14 @@ class Chef pkg = shell_out!("/opt/local/bin/pkgin se #{new_resource.package_name}", :env => nil, :returns => [0,1]) pkg.stdout.each_line do |line| case line - when /^#{name}/ + when /^#{new_resource.package_name}/ name, version = line.split[0].split(/-([^-]+)$/) end end @candidate_version = version version end - + def install_package(name, version) Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}") package = "#{name}-#{version}" @@ -84,7 +84,7 @@ class Chef end def remove_package(name, version) - Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}") + Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}") package = "#{name}" out = shell_out!("/opt/local/bin/pkgin -y remove #{package}", :env => nil) end diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb index f502a0dc96..0f45b61e18 100644 --- a/lib/chef/provider/package/solaris.rb +++ b/lib/chef/provider/package/solaris.rb @@ -33,12 +33,12 @@ class Chef # end def define_resource_requirements super - requirements.assert(:install) do |a| + requirements.assert(:install) do |a| a.assertion { @new_resource.source } a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install" end - requirements.assert(:all_actions) do |a| - a.assertion { !@new_resource.source || @package_source_found } + requirements.assert(:all_actions) do |a| + a.assertion { !@new_resource.source || @package_source_found } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" a.whyrun "would assume #{@new_resource.source} would be have previously been made available" end @@ -51,7 +51,7 @@ class Chef if @new_resource.source @package_source_found = ::File.exists?(@new_resource.source) - if @package_source_found + if @package_source_found Chef::Log.debug("#{@new_resource} checking pkg status") status = popen4("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| stdout.each do |line| @@ -107,13 +107,23 @@ class Chef def install_package(name, version) Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}") if @new_resource.options.nil? + if ::File.directory?(@new_resource.source) # CHEF-4469 + command = "pkgadd -n -d #{@new_resource.source} #{@new_resource.package_name}" + else + command = "pkgadd -n -d #{@new_resource.source} all" + end run_command_with_systems_locale( - :command => "pkgadd -n -d #{@new_resource.source} all" + :command => command ) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") else + if ::File.directory?(@new_resource.source) # CHEF-4469 + command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" + else + command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all" + end run_command_with_systems_locale( - :command => "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all" + :command => command ) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") end diff --git a/lib/chef/provider/package/yum-dump.py b/lib/chef/provider/package/yum-dump.py index 407eb8f408..a8f3995e8c 100644 --- a/lib/chef/provider/package/yum-dump.py +++ b/lib/chef/provider/package/yum-dump.py @@ -6,9 +6,9 @@ # 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. @@ -249,8 +249,8 @@ def yum_dump(options): # Preserve order of enable/disable repo args like yum does def gather_repo_opts(option, opt, value, parser): - if getattr(parser.values, option.dest, None) is None: - setattr(parser.values, option.dest, []) + if getattr(parser.values, option.dest, None) is None: + setattr(parser.values, option.dest, []) getattr(parser.values, option.dest).append((opt, value.split(','))) def main(): diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index 233e949e12..f56d3140b6 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -667,7 +667,7 @@ class Chef @allow_multi_install = [] - @extra_repo_control = nil + @extra_repo_control = nil # these are for subsequent runs if we are on an interval Chef::Client.when_run_starts do @@ -1046,7 +1046,7 @@ class Chef end # At this point package_name could be: - # + # # 1) a package name, eg: "foo" # 2) a package name.arch, eg: "foo.i386" # 3) or a dependency, eg: "foo >= 1.1" @@ -1154,7 +1154,7 @@ class Chef # Hacky - better overall solution? Custom compare in Package provider? def action_upgrade # Could be uninstalled or have no candidate - if @current_resource.version.nil? || candidate_version.nil? + if @current_resource.version.nil? || candidate_version.nil? super # Ensure the candidate is newer elsif RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version) diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index 700eb88c11..c459cdf678 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -6,9 +6,9 @@ # 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. @@ -23,7 +23,7 @@ class Chef class PowershellScript < Chef::Provider::WindowsScript protected - + EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -eq $true) {exit 0} elseif ( $LASTEXITCODE -ne 0) {exit $LASTEXITCODE} else { exit 1 }" EXIT_STATUS_RESET_SCRIPT = "$LASTEXITCODE=0\n" @@ -41,12 +41,12 @@ class Chef end public - + def initialize (new_resource, run_context) super(new_resource, run_context, '.ps1') NormalizeScriptExitStatus(new_resource.code) end - + def flags default_flags = [ "-NoLogo", @@ -60,8 +60,8 @@ class Chef # file created by the base class that contains the script # code -- otherwise, powershell.exe does not propagate the # error status of a failed Windows process that ran at the - # end of the script, it gets changed to '1'. - "-File" + # end of the script, it gets changed to '1'. + "-File" ] interpreter_flags = default_flags.join(' ') diff --git a/lib/chef/provider/remote_file/ftp.rb b/lib/chef/provider/remote_file/ftp.rb index d75fb7a52c..7f3fdbf383 100644 --- a/lib/chef/provider/remote_file/ftp.rb +++ b/lib/chef/provider/remote_file/ftp.rb @@ -143,6 +143,7 @@ class Chef ftp.voidcmd("TYPE #{typecode.upcase}") end ftp.getbinaryfile(filename, tempfile.path) + tempfile.close if tempfile tempfile end diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb index 6ffd83f438..8949e6ab04 100644 --- a/lib/chef/provider/remote_file/http.rb +++ b/lib/chef/provider/remote_file/http.rb @@ -17,7 +17,7 @@ # limitations under the License. # -require 'chef/rest' +require 'chef/http/simple' require 'chef/digester' require 'chef/provider/remote_file' require 'chef/provider/remote_file/cache_control_data' @@ -58,9 +58,9 @@ class Chef def fetch tempfile = nil begin - rest = Chef::REST.new(uri, nil, nil, http_client_opts) - tempfile = rest.streaming_request(uri, headers) - update_cache_control_data(tempfile, rest.last_response) + http = Chef::HTTP::Simple.new(uri, http_client_opts) + tempfile = http.streaming_request(uri, headers) + update_cache_control_data(tempfile, http.last_response) rescue Net::HTTPRetriableError => e if e.response.is_a? Net::HTTPNotModified tempfile = nil @@ -68,7 +68,7 @@ class Chef raise e end end - + tempfile.close if tempfile tempfile end diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb index 87f498e053..b3b2301b81 100644 --- a/lib/chef/provider/remote_file/local_file.rb +++ b/lib/chef/provider/remote_file/local_file.rb @@ -38,6 +38,7 @@ class Chef tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile Chef::Log.debug("#{new_resource} staging #{uri.path} to #{tempfile.path}") FileUtils.cp(uri.path, tempfile.path) + tempfile.close if tempfile tempfile end diff --git a/lib/chef/provider/resource_update.rb b/lib/chef/provider/resource_update.rb index e2c6bffca4..54f25738ed 100644 --- a/lib/chef/provider/resource_update.rb +++ b/lib/chef/provider/resource_update.rb @@ -4,27 +4,27 @@ class Chef # { # "run_id" : "1000", - # "resource" : { + # "resource" : { # "type" : "file", - # "name" : "/etc/passwd", + # "name" : "/etc/passwd", # "start_time" : "2012-01-09T08:15:30-05:00", # "end_time" : "2012-01-09T08:15:30-05:00", # "status" : "modified", - # "initial_state" : "exists", - # "final_state" : "modified", - # "before" : { - # "group" : "root", + # "initial_state" : "exists", + # "final_state" : "modified", + # "before" : { + # "group" : "root", # "owner" : "root", # "checksum" : "xyz" # }, - # "after" : { - # "group" : "root", + # "after" : { + # "group" : "root", # "owner" : "root", # "checksum" : "abc" # }, # "delta" : "escaped delta goes here" # }, - # "event_data" : "" + # "event_data" : "" # } class ResourceUpdate diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb index 16908b0eff..b0d94a3f8d 100644 --- a/lib/chef/provider/ruby_block.rb +++ b/lib/chef/provider/ruby_block.rb @@ -7,9 +7,9 @@ # 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. @@ -29,7 +29,7 @@ class Chef end def action_run - converge_by("execute the ruby block #{@new_resource.name}") do + converge_by("execute the ruby block #{@new_resource.name}") do @new_resource.block.call Chef::Log.info("#{@new_resource} called") end diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb index 1b459f36cf..4aacf4f524 100644 --- a/lib/chef/provider/script.rb +++ b/lib/chef/provider/script.rb @@ -27,7 +27,7 @@ class Chef super @code = @new_resource.code end - + def action_run script_file.puts(@code) script_file.close diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index 8d76927676..968f9bff9c 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -35,7 +35,7 @@ class Chef end def load_new_resource_state - # If the user didn't specify a change in enabled state, + # If the user didn't specify a change in enabled state, # it will be the same as the old resource if ( @new_resource.enabled.nil? ) @new_resource.enabled(@current_resource.enabled) @@ -54,7 +54,7 @@ class Chef a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} 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. + # alternative here. end end diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index e2a0f60d91..d788d58c00 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -41,17 +41,17 @@ class Chef shared_resource_requirements requirements.assert(:all_actions) do |a| update_rcd = "/usr/sbin/update-rc.d" - a.assertion { ::File.exists? update_rcd } + a.assertion { ::File.exists? update_rcd } a.failure_message Chef::Exceptions::Service, "#{update_rcd} does not exist!" # no whyrun recovery - this is a base system component of debian - # distros and must be present - end + # distros and must be present + end requirements.assert(:all_actions) do |a| - a.assertion { @priority_success } + a.assertion { @priority_success } a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} failed - #{@rcd_status.inspect}" - # This can happen if the service is not yet installed,so we'll fake it. - a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.", + # This can happen if the service is not yet installed,so we'll fake it. + a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.", "Assigning temporary priorities to continue.", "If this service is not properly installed prior to this point, this will fail."] do temp_priorities = {"6"=>[:stop, "20"], @@ -74,7 +74,7 @@ class Chef [stdout, stderr].each do |iop| iop.each_line do |line| if UPDATE_RC_D_PRIORITIES =~ line - # priority[runlevel] = [ S|K, priority ] + # priority[runlevel] = [ S|K, priority ] # S = Start, K = Kill # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot priority[$1] = [($2 == "S" ? :start : :stop), $3] @@ -86,6 +86,12 @@ class Chef end end + # Reduce existing priority back to an integer if appropriate, picking + # runlevel 2 as a baseline + if priority[2] && [2..5].all? { |runlevel| priority[runlevel] == priority[2] } + priority = priority[2].last + end + unless @rcd_status.exitstatus == 0 @priority_success = false end @@ -105,14 +111,28 @@ class Chef enabled end - def enable_service() + # Override method from parent to ensure priority is up-to-date + def action_enable + if @current_resource.enabled && @current_resource.priority == @new_resource.priority + Chef::Log.debug("#{@new_resource} already enabled - nothing to do") + else + converge_by("enable service #{@new_resource}") do + enable_service + Chef::Log.info("#{@new_resource} enabled") + end + end + load_new_resource_state + @new_resource.enabled(true) + end + + def enable_service if @new_resource.priority.is_a? Integer run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}") elsif @new_resource.priority.is_a? Hash - # we call the same command regardless of we're enabling or disabling + # we call the same command regardless of we're enabling or disabling # users passing a Hash are responsible for setting their own start priorities - set_priority() + set_priority else # No priority, go with update-rc.d defaults run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults") @@ -120,23 +140,23 @@ class Chef end - def disable_service() + def disable_service if @new_resource.priority.is_a? Integer # Stop processes in reverse order of start using '100 - start_priority' run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .") elsif @new_resource.priority.is_a? Hash - # we call the same command regardless of we're enabling or disabling + # we call the same command regardless of we're enabling or disabling # users passing a Hash are responsible for setting their own stop priorities - set_priority() - else + set_priority + else # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .") end end - def set_priority() + def set_priority args = "" @new_resource.priority.each do |level, o| action = o[0] diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb index b875838ec2..cb0f6b0fc4 100644 --- a/lib/chef/provider/service/freebsd.rb +++ b/lib/chef/provider/service/freebsd.rb @@ -38,13 +38,13 @@ class Chef elsif ::File.exists?("/usr/local/etc/rc.d/#{current_resource.service_name}") @init_command = "/usr/local/etc/rc.d/#{current_resource.service_name}" else - @rcd_script_found = false + @rcd_script_found = false return end Chef::Log.debug("#{@current_resource} found at #{@init_command}") determine_current_status! # Default to disabled if the service doesn't currently exist - # at all + # at all var_name = service_enable_variable_name if ::File.exists?("/etc/rc.conf") && var_name read_rc_conf.each do |line| @@ -70,19 +70,19 @@ class Chef def define_resource_requirements shared_resource_requirements requirements.assert(:start, :enable, :reload, :restart) do |a| - a.assertion { @rcd_script_found } + a.assertion { @rcd_script_found } a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script" end - requirements.assert(:all_actions) do |a| - a.assertion { @enabled_state_found } - # for consistentcy with original behavior, this will not fail in non-whyrun mode; + requirements.assert(:all_actions) do |a| + a.assertion { @enabled_state_found } + # for consistentcy with original behavior, this will not fail in non-whyrun mode; # rather it will silently set enabled state=>false - a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled." + a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled." end requirements.assert(:start, :enable, :reload, :restart) do |a| - a.assertion { @rcd_script_found && service_enable_variable_name != nil } + a.assertion { @rcd_script_found && service_enable_variable_name != nil } a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{@init_command} and rcvar" # No recovery in whyrun mode - the init file is present but not correct. end @@ -133,7 +133,7 @@ class Chef # corresponding to this service # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need # to set mysql_enable="YES" in /etc/rc.conf$ - if @rcd_script_found + if @rcd_script_found ::File.open(@init_command) do |rcscript| rcscript.each_line do |line| if line =~ /^name="?(\w+)"?/ diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb index 45b5a21f9b..ba4edc5807 100644 --- a/lib/chef/provider/service/gentoo.rb +++ b/lib/chef/provider/service/gentoo.rb @@ -44,7 +44,7 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init def define_resource_requirements requirements.assert(:all_actions) do |a| - a.assertion { ::File.exists?("/sbin/rc-update") } + a.assertion { ::File.exists?("/sbin/rc-update") } a.failure_message Chef::Exceptions::Service, "/sbin/rc-update does not exist" # no whyrun recovery -t his is a core component whose presence is # unlikely to be affected by what we do in the course of a chef run @@ -52,15 +52,15 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init requirements.assert(:all_actions) do |a| a.assertion { @found_script } - # No failure, just informational output from whyrun + # No failure, just informational output from whyrun a.whyrun "Could not find service #{@new_resource.service_name} under any runlevel" end end - + def enable_service() run_command(:command => "/sbin/rc-update add #{@new_resource.service_name} default") end - + def disable_service() run_command(:command => "/sbin/rc-update del #{@new_resource.service_name} default") end diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb index 09f47e866f..63ba8fa6ab 100644 --- a/lib/chef/provider/service/init.rb +++ b/lib/chef/provider/service/init.rb @@ -47,7 +47,7 @@ class Chef end end end - + def start_service if @new_resource.start_command super diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index 32152376ee..cb9c28e17e 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -32,9 +32,9 @@ class Chef if Dir.glob("/etc/rc**/S*#{@current_resource.service_name}").empty? @current_resource.enabled false else - @current_resource.enabled true + @current_resource.enabled true end - + @current_resource end diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb index 69a17bb4fb..ee2719d75a 100644 --- a/lib/chef/provider/service/invokercd.rb +++ b/lib/chef/provider/service/invokercd.rb @@ -24,7 +24,7 @@ class Chef class Provider class Service class Invokercd < Chef::Provider::Service::Init - + def initialize(new_resource, run_context) super @init_command = "/usr/sbin/invoke-rc.d #{@new_resource.service_name}" diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index 629e4ee0c3..06be9f3207 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -48,7 +48,7 @@ class Chef requirements.assert(:start, :enable, :reload, :restart) do |a| a.assertion { !@service_missing } a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!" - a.whyrun "Assuming service would be disabled. The init script is not presently installed." + a.whyrun "Assuming service would be disabled. The init script is not presently installed." end end @@ -59,7 +59,7 @@ class Chef chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1]) @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON)) @service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING) - end + end @current_resource end diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb index bcb85230d0..288b5f5456 100644 --- a/lib/chef/provider/service/simple.rb +++ b/lib/chef/provider/service/simple.rb @@ -45,8 +45,8 @@ class Chef def shared_resource_requirements super - requirements.assert(:all_actions) do |a| - a.assertion { @status_load_success } + requirements.assert(:all_actions) do |a| + a.assertion { @status_load_success } a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."] end end @@ -74,12 +74,12 @@ class Chef end requirements.assert(:all_actions) do |a| - a.assertion { @new_resource.status_command or @new_resource.supports[:status] or - (!ps_cmd.nil? and !ps_cmd.empty?) } + a.assertion { @new_resource.status_command or @new_resource.supports[:status] or + (!ps_cmd.nil? and !ps_cmd.empty?) } a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute" end - requirements.assert(:all_actions) do |a| - a.assertion { !@ps_command_failed } + requirements.assert(:all_actions) do |a| + a.assertion { !@ps_command_failed } a.failure_message Chef::Exceptions::Service, "Command #{ps_cmd} failed to execute, cannot determine service current status" end end diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index 33a29da109..4bdb6fbfd1 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -31,7 +31,7 @@ class Chef @init_command = "/usr/sbin/svcadm" @status_command = "/bin/svcs -l" end - + def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index 59b4fe1564..89077c5feb 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -50,7 +50,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple def define_resource_requirements shared_resource_requirements requirements.assert(:all_actions) do |a| - a.assertion { @status_check_success } + a.assertion { @status_check_success } # We won't stop in any case, but in whyrun warn and tell what we're doing. a.whyrun ["Failed to determine status of #{@new_resource}, using command #{@new_resource.status_command}.", "Assuming service would have been installed and is disabled"] @@ -99,11 +99,11 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple def enable_service run_command_with_systems_locale(:command => "/bin/systemctl enable #{@new_resource.service_name}") - end + end def disable_service run_command_with_systems_locale(:command => "/bin/systemctl disable #{@new_resource.service_name}") - end + end def is_active? run_command_with_systems_locale({:command => "/bin/systemctl is-active #{@new_resource.service_name}", :ignore_failure => true}) == 0 diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index 763a2aa92b..67ca649b6d 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -26,7 +26,7 @@ class Chef class Service class Upstart < Chef::Provider::Service::Simple UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/ - + # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in. # In chef, when we ask a service to start, we expect it to have started before performing the next step # since we have top down dependencies. Which is to say we may follow witha resource next that requires @@ -40,17 +40,17 @@ class Chef # TODO: re-evaluate if this is needed after integrating cookbook fix raise ArgumentError, "run_context cannot be nil" unless run_context super - + run_context.node - + @job = @new_resource.service_name - + if @new_resource.parameters @new_resource.parameters.each do |key, value| @job << " #{key}=#{value}" end end - + platform, version = Chef::Platform.find_platform_and_version(run_context.node) if platform == "ubuntu" && (8.04..9.04).include?(version.to_f) @upstart_job_dir = "/etc/event.d" @@ -60,8 +60,8 @@ class Chef @upstart_conf_suffix = ".conf" end - @command_success = true # new_resource.status_command= false, means upstart used - @config_file_found = true + @command_success = true # new_resource.status_command= false, means upstart used + @config_file_found = true @upstart_command_success = true end @@ -70,18 +70,18 @@ class Chef shared_resource_requirements requirements.assert(:all_actions) do |a| if !@command_success - whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." : + whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." : "Could not determine upstart state for service" end a.assertion { @command_success } - # no failure here, just document the assumptions made. - a.whyrun "#{whyrun_msg} Assuming service installed and not running." + # no failure here, just document the assumptions made. + a.whyrun "#{whyrun_msg} Assuming service installed and not running." end - requirements.assert(:all_actions) do |a| - a.assertion { @config_file_found } - # no failure here, just document the assumptions made. - a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled." + requirements.assert(:all_actions) do |a| + a.assertion { @config_file_found } + # no failure here, just document the assumptions made. + a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled." end end @@ -176,7 +176,7 @@ class Chef super # Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start. # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883 - else @new_resource.supports[:restart] + else if @current_resource.running run_command_with_systems_locale(:command => "/sbin/restart #{@job}") else diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb index e1f87b4dd8..6ceb3e592a 100644 --- a/lib/chef/provider/subversion.rb +++ b/lib/chef/provider/subversion.rb @@ -17,7 +17,7 @@ # -#TODO subversion and git should both extend from a base SCM provider. +#TODO subversion and git should both extend from a base SCM provider. require 'chef/log' require 'chef/provider' @@ -52,7 +52,7 @@ class Chef # for why run, print a message explaining the potential error. parent_directory = ::File.dirname(@new_resource.destination) a.assertion { ::File.directory?(parent_directory) } - a.failure_message(Chef::Exceptions::MissingParentDirectory, + a.failure_message(Chef::Exceptions::MissingParentDirectory, "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{parent_directory} does not exist") a.whyrun("Directory #{parent_directory} does not exist, assuming it would have been created") end @@ -91,13 +91,13 @@ class Chef converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do run_command(run_options(:command => sync_command)) Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}" - end + end end else action_checkout end end - + def sync_command c = scm :update, @new_resource.svn_arguments, verbose, authentication, "-r#{revision_int}", @new_resource.destination Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}" @@ -156,6 +156,7 @@ class Chef def run_options(run_opts={}) run_opts[:user] = @new_resource.user if @new_resource.user run_opts[:group] = @new_resource.group if @new_resource.group + run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout run_opts end @@ -197,7 +198,7 @@ class Chef def scm(*args) ['svn', *args].compact.join(" ") end - + def target_dir_non_existent_or_empty? !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..'] diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb index e815ba9c9e..738f7660f8 100644 --- a/lib/chef/provider/user.rb +++ b/lib/chef/provider/user.rb @@ -6,9 +6,9 @@ # 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. @@ -79,9 +79,7 @@ class Chef end end - if @new_resource.gid - convert_group_name - end + convert_group_name if @new_resource.gid end @current_resource @@ -89,14 +87,14 @@ class Chef def define_resource_requirements requirements.assert(:all_actions) do |a| - a.assertion { @group_name_resolved } + a.assertion { @group_name_resolved } a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}" a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously." end requirements.assert(:all_actions) do |a| - a.assertion { @shadow_lib_ok } + a.assertion { @shadow_lib_ok } a.failure_message Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!" - a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." + + a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." + "Note that user update converge may report false-positive on the basis of mismatched password. " end requirements.assert(:modify, :lock, :unlock) do |a| @@ -112,9 +110,15 @@ class Chef # <true>:: If a change is required # <false>:: If the users are identical def compare_user - [ :uid, :gid, :comment, :home, :shell, :password ].any? do |user_attrib| + changed = [ :comment, :home, :shell, :password ].select do |user_attrib| !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib) end + + changed += [ :uid, :gid ].select do |user_attrib| + !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s + end + + changed.any? end def action_create diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb index 94e8420c43..b01931609e 100644 --- a/lib/chef/provider/user/dscl.rb +++ b/lib/chef/provider/user/dscl.rb @@ -6,9 +6,9 @@ # 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. @@ -25,10 +25,10 @@ class Chef class User class Dscl < Chef::Provider::User include Chef::Mixin::ShellOut - + NFS_HOME_DIRECTORY = %r{^NFSHomeDirectory: (.*)$} AUTHENTICATION_AUTHORITY = %r{^AuthenticationAuthority: (.*)$} - + def dscl(*args) shell_out("dscl . -#{args.join(' ')}") end @@ -80,7 +80,7 @@ class Chef return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?) if @new_resource.supports[:manage_home] validate_home_dir_specification! - + if (@current_resource.home == @new_resource.home) && !new_home_exists? ditto_home elsif !current_home_exists? && !new_home_exists? @@ -105,7 +105,7 @@ class Chef end def shadow_hash_set? - user_data = safe_dscl("read /Users/#{@new_resource.username}") + user_data = safe_dscl("read /Users/#{@new_resource.username}") if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/ true else @@ -116,7 +116,7 @@ class Chef def modify_password if @new_resource.password shadow_hash = nil - + Chef::Log.debug("#{new_resource} updating password") if osx_shadow_hash?(@new_resource.password) shadow_hash = @new_resource.password.upcase @@ -134,11 +134,11 @@ class Chef shadow_hash = String.new("00000000"*155) shadow_hash[168] = salted_sha1 end - + ::File.open("/var/db/shadow/hash/#{guid}",'w',0600) do |output| output.puts shadow_hash end - + unless shadow_hash_set? safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';ShadowHash;'") end @@ -159,7 +159,7 @@ class Chef dscl_set_shell modify_password end - + def manage_user dscl_create_user if diverged?(:username) dscl_create_comment if diverged?(:comment) @@ -169,15 +169,15 @@ class Chef dscl_set_shell if diverged?(:shell) modify_password if diverged?(:password) end - + def dscl_create_user - safe_dscl("create /Users/#{@new_resource.username}") + safe_dscl("create /Users/#{@new_resource.username}") end - + def dscl_create_comment safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'") end - + def dscl_set_gid unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/) begin @@ -189,7 +189,7 @@ class Chef end safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'") end - + def dscl_set_shell if @new_resource.password || ::File.exists?("#{@new_resource.shell}") safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'") @@ -197,10 +197,10 @@ class Chef safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'") end end - + def remove_user if @new_resource.supports[:manage_home] - user_info = safe_dscl("read /Users/#{@new_resource.username}") + user_info = safe_dscl("read /Users/#{@new_resource.username}") if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY) #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory") #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"") @@ -228,7 +228,7 @@ class Chef false end end - + def check_lock return @locked = locked? end @@ -236,27 +236,27 @@ class Chef def lock_user safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'") end - + def unlock_user auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority") auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"") safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'") end - + def validate_home_dir_specification! unless @new_resource.home =~ /^\// - raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'") + raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'") end end - + def current_home_exists? ::File.exist?("#{@current_resource.home}") end - + def new_home_exists? - ::File.exist?("#{@new_resource.home}") + ::File.exist?("#{@new_resource.home}") end - + def ditto_home skel = "/System/Library/User Template/English.lproj" raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel) @@ -266,7 +266,7 @@ class Chef def move_home Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}") - + src = @current_resource.home FileUtils.mkdir_p(@new_resource.home) files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."] @@ -274,11 +274,11 @@ class Chef ::FileUtils.rmdir(src) ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) end - + def diverged?(parameter) parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?) end - + def parameter_updated?(parameter) not (@new_resource.send(parameter) == @current_resource.send(parameter)) end diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb index 4f6393da89..9f7a169892 100644 --- a/lib/chef/provider/user/pw.rb +++ b/lib/chef/provider/user/pw.rb @@ -6,9 +6,9 @@ # 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. @@ -34,20 +34,20 @@ class Chef run_command(:command => command) modify_password end - + def manage_user command = "pw usermod" command << set_options run_command(:command => command) modify_password end - + def remove_user command = "pw userdel #{@new_resource.username}" command << " -r" if @new_resource.supports[:manage_home] run_command(:command => command) end - + def check_lock case @current_resource.password when /^\*LOCKED\*/ @@ -57,18 +57,18 @@ class Chef end @locked end - + def lock_user run_command(:command => "pw lock #{@new_resource.username}") end - + def unlock_user run_command(:command => "pw unlock #{@new_resource.username}") end - + def set_options opts = " #{@new_resource.username}" - + field_list = { 'comment' => "-c", 'home' => "-d", @@ -91,7 +91,7 @@ class Chef end opts end - + def modify_password if @current_resource.password != @new_resource.password Chef::Log.debug("#{new_resource} updating password") @@ -99,7 +99,7 @@ class Chef status = popen4(command, :waitlast => true) do |pid, stdin, stdout, stderr| stdin.puts "#{@new_resource.password}" end - + unless status.exitstatus == 0 raise Chef::Exceptions::User, "pw failed - #{status.inspect}!" end diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb index 41455007dc..1789a4e5ff 100644 --- a/lib/chef/provider/user/useradd.rb +++ b/lib/chef/provider/user/useradd.rb @@ -124,7 +124,7 @@ class Chef end def update_options(field, option, opts) - if @current_resource.send(field) != new_resource.send(field) + if @current_resource.send(field).to_s != new_resource.send(field).to_s if new_resource.send(field) Chef::Log.debug("#{new_resource} setting #{field} to #{new_resource.send(field)}") opts << option << new_resource.send(field).to_s diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb index 6bbb2a088c..350f3ff4c0 100644 --- a/lib/chef/provider/user/windows.rb +++ b/lib/chef/provider/user/windows.rb @@ -6,9 +6,9 @@ # 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. @@ -71,23 +71,23 @@ class Chef def create_user @net_user.add(set_options) end - + def manage_user @net_user.update(set_options) end - + def remove_user @net_user.delete end - + def check_lock @net_user.check_enabled end - + def lock_user @net_user.disable_account end - + def unlock_user @net_user.enable_account end diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb index 398e1aee6e..08a2ea74df 100644 --- a/lib/chef/provider/windows_script.rb +++ b/lib/chef/provider/windows_script.rb @@ -6,9 +6,9 @@ # 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. @@ -24,7 +24,7 @@ class Chef class WindowsScript < Chef::Provider::Script protected - + include Chef::Mixin::WindowsArchitectureHelper def initialize( new_resource, run_context, script_extension='') @@ -43,14 +43,14 @@ class Chef end public - + def action_run wow64_redirection_state = nil if @is_wow64 wow64_redirection_state = disable_wow64_file_redirection(@run_context.node) end - + begin super rescue @@ -61,11 +61,11 @@ class Chef end end end - + def script_file base_script_name = "chef-script" temp_file_arguments = [ base_script_name, @script_extension ] - + @script_file ||= Tempfile.open(temp_file_arguments) end end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index e0039428f2..50099e8afc 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -21,6 +21,7 @@ require 'chef/provider/breakpoint' require 'chef/provider/cookbook_file' require 'chef/provider/cron' require 'chef/provider/cron/solaris' +require 'chef/provider/cron/aix' require 'chef/provider/deploy' require 'chef/provider/directory' require 'chef/provider/env' @@ -64,6 +65,7 @@ require 'chef/provider/package/yum' require 'chef/provider/package/zypper' require 'chef/provider/package/solaris' require 'chef/provider/package/smartos' +require 'chef/provider/package/aix' require 'chef/provider/service/arch' require 'chef/provider/service/debian' @@ -97,6 +99,7 @@ require 'chef/provider/group/usermod' require 'chef/provider/group/windows' require 'chef/provider/mount/mount' +require 'chef/provider/mount/aix' require 'chef/provider/mount/windows' require 'chef/provider/deploy/revision' @@ -117,3 +120,4 @@ require 'chef/provider/template/content' require 'chef/provider/ifconfig/redhat' require 'chef/provider/ifconfig/debian' +require 'chef/provider/ifconfig/aix' diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index 6ea69360b8..0c688cb5f8 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -81,26 +81,9 @@ class Chef run_context.resource_collection.find(*args) end - # Sets a tag, or list of tags, for this node. Syntactic sugar for - # run_context.node[:tags]. - # - # With no arguments, returns the list of tags. - # - # === Parameters - # tags<Array>:: A list of tags to add - can be a single string - # - # === Returns - # tags<Array>:: The contents of run_context.node[:tags] + # This was moved to Chef::Node#tag, redirecting here for compatability def tag(*tags) - if tags.length > 0 - tags.each do |tag| - tag = tag.to_s - run_context.node.normal[:tags] << tag unless run_context.node[:tags].include?(tag) - end - run_context.node[:tags] - else - run_context.node[:tags] - end + run_context.node.tag(*tags) end # Returns true if the node is tagged with *all* of the supplied +tags+. diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb index 524abbb370..050cf838ae 100644 --- a/lib/chef/resource/apt_package.rb +++ b/lib/chef/resource/apt_package.rb @@ -6,9 +6,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/provider/package/apt' class Chef class Resource class AptPackage < Chef::Resource::Package - + def initialize(name, run_context=nil) super @resource_name = :apt_package diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb index 374bca9e11..c56de5fe20 100644 --- a/lib/chef/resource/bash.rb +++ b/lib/chef/resource/bash.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require 'chef/resource/script' class Chef class Resource class Bash < Chef::Resource::Script - + def initialize(name, run_context=nil) super @resource_name = :bash diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb index 705260bbce..576e6b4c6b 100644 --- a/lib/chef/resource/batch.rb +++ b/lib/chef/resource/batch.rb @@ -6,9 +6,9 @@ # 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. @@ -21,11 +21,11 @@ require 'chef/resource/windows_script' class Chef class Resource class Batch < Chef::Resource::WindowsScript - + def initialize(name, run_context=nil) super(name, run_context, :batch, "cmd.exe") end - + end end end diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb new file mode 100644 index 0000000000..2d78483e4b --- /dev/null +++ b/lib/chef/resource/bff_package.rb @@ -0,0 +1,36 @@ +# +# Author:: Deepali Jagtap (<deepali.jagtap@clogeny.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/resource/package' +require 'chef/provider/package/aix' + +class Chef + class Resource + class BffPackage < Chef::Resource::Package + + def initialize(name, run_context=nil) + super + @resource_name = :bff_package + @provider = Chef::Provider::Package::Aix + end + + end + end +end + + diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb index 34aeae6b47..83c397bd5b 100644 --- a/lib/chef/resource/breakpoint.rb +++ b/lib/chef/resource/breakpoint.rb @@ -6,9 +6,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class Breakpoint < Chef::Resource - + def initialize(action="break", *args) @name = caller.first super(@name, *args) diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb index 5f858cec81..dfbb91f80c 100644 --- a/lib/chef/resource/cron.rb +++ b/lib/chef/resource/cron.rb @@ -7,9 +7,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class Cron < Chef::Resource - + identity_attr :command state_attrs :minute, :hour, :day, :month, :weekday, :user @@ -186,9 +186,9 @@ class Chef :kind_of => Hash ) end - + private - + # On Ruby 1.8, Kernel#Integer will happily do this for you. On 1.9, no. def integerize(integerish) Integer(integerish) diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb index 6e871e8605..95aa8afd7a 100644 --- a/lib/chef/resource/csh.rb +++ b/lib/chef/resource/csh.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require 'chef/resource/script' class Chef class Resource class Csh < Chef::Resource::Script - + def initialize(name, run_context=nil) super @resource_name = :csh diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb index 8b614028bf..76478ed07c 100644 --- a/lib/chef/resource/deploy.rb +++ b/lib/chef/resource/deploy.rb @@ -50,9 +50,9 @@ class Chef # release directory. Callback files can contain chef code (resources, etc.) # class Deploy < Chef::Resource - + provider_base Chef::Provider::Deploy - + identity_attr :repository state_attrs :deploy_to, :revision @@ -389,7 +389,7 @@ class Chef arg ||= block set_or_return(:after_restart, arg, :kind_of => [Proc, String]) end - + def additional_remotes(arg=nil) set_or_return( :additional_remotes, @@ -398,6 +398,19 @@ class Chef ) end + # FIXME The Deploy resource may be passed to an SCM provider as its + # resource. The SCM provider knows that SCM resources can specify a + # timeout for SCM operations. The deploy resource must therefore support + # a timeout method, but the timeout it describes is for SCM operations, + # not the overall deployment. This is potentially confusing. + def timeout(arg=nil) + set_or_return( + :timeout, + arg, + :kind_of => Integer + ) + end + end end end diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/resource/deploy_revision.rb index 55a3e38130..ceac26e91a 100644 --- a/lib/chef/resource/deploy_revision.rb +++ b/lib/chef/resource/deploy_revision.rb @@ -6,9 +6,9 @@ # 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. @@ -18,9 +18,9 @@ class Chef class Resource - + # Convenience class for using the deploy resource with the revision - # deployment strategy (provider) + # deployment strategy (provider) class DeployRevision < Chef::Resource::Deploy def initialize(*args, &block) super @@ -28,13 +28,13 @@ class Chef @provider = Chef::Provider::Deploy::Revision end end - + class DeployBranch < Chef::Resource::DeployRevision def initialize(*args, &block) super @resource_name = :deploy_branch end end - + end end diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb index a5d5ea7366..423c0bbe27 100644 --- a/lib/chef/resource/directory.rb +++ b/lib/chef/resource/directory.rb @@ -25,11 +25,11 @@ require 'chef/mixin/securable' class Chef class Resource class Directory < Chef::Resource - + identity_attr :path state_attrs :group, :mode, :owner - + include Chef::Mixin::Securable provides :directory, :on_platforms => :all diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb index 02886e8649..2fb5b5c249 100644 --- a/lib/chef/resource/dpkg_package.rb +++ b/lib/chef/resource/dpkg_package.rb @@ -6,9 +6,9 @@ # 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. @@ -22,13 +22,13 @@ require 'chef/provider/package/dpkg' class Chef class Resource class DpkgPackage < Chef::Resource::Package - + def initialize(name, run_context=nil) super @resource_name = :dpkg_package @provider = Chef::Provider::Package::Dpkg end - + end end end diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb index 10e80bdd3b..f25e1ac22f 100644 --- a/lib/chef/resource/easy_install_package.rb +++ b/lib/chef/resource/easy_install_package.rb @@ -21,7 +21,7 @@ require 'chef/resource/package' class Chef class Resource class EasyInstallPackage < Chef::Resource::Package - + def initialize(name, run_context=nil) super @resource_name = :easy_install_package diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb index e0e38926bb..959856af66 100644 --- a/lib/chef/resource/erl_call.rb +++ b/lib/chef/resource/erl_call.rb @@ -24,7 +24,7 @@ class Chef class ErlCall < Chef::Resource # erl_call : http://erlang.org/doc/man/erl_call.html - + identity_attr :code def initialize(name, run_context=nil) diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb index 9a9a84900e..94286eae18 100644 --- a/lib/chef/resource/freebsd_package.rb +++ b/lib/chef/resource/freebsd_package.rb @@ -6,9 +6,9 @@ # 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. @@ -22,13 +22,13 @@ require 'chef/provider/package/freebsd' class Chef class Resource class FreebsdPackage < Chef::Resource::Package - + def initialize(name, run_context=nil) super @resource_name = :freebsd_package @provider = Chef::Provider::Package::Freebsd end - + end end end diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb index 76f3a779ae..17f14c8387 100644 --- a/lib/chef/resource/group.rb +++ b/lib/chef/resource/group.rb @@ -7,9 +7,9 @@ # 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. @@ -20,7 +20,7 @@ class Chef class Resource class Group < Chef::Resource - + identity_attr :group_name state_attrs :members @@ -33,9 +33,10 @@ class Chef @members = [] @action = :create @append = false + @non_unique = false @allowed_actions.push(:create, :remove, :modify, :manage) end - + def group_name(arg=nil) set_or_return( :group_name, @@ -43,7 +44,7 @@ class Chef :kind_of => [ String ] ) end - + def gid(arg=nil) set_or_return( :gid, @@ -62,7 +63,7 @@ class Chef end alias_method :users, :members - + def append(arg=nil) set_or_return( :append, @@ -78,6 +79,14 @@ class Chef :kind_of => [ TrueClass, FalseClass ] ) end + + def non_unique(arg=nil) + set_or_return( + :non_unique, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end end end end diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb index fc64121f4e..47f6286fb4 100644 --- a/lib/chef/resource/http_request.rb +++ b/lib/chef/resource/http_request.rb @@ -7,9 +7,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class HttpRequest < Chef::Resource - + identity_attr :url def initialize(name, run_context=nil) @@ -34,7 +34,7 @@ class Chef @headers = {} @allowed_actions.push(:get, :put, :post, :delete, :head, :options) end - + def url(args=nil) set_or_return( :url, @@ -42,7 +42,7 @@ class Chef :kind_of => String ) end - + def message(args=nil, &block) args = block if block_given? set_or_return( @@ -59,7 +59,7 @@ class Chef :kind_of => Hash ) end - + end end end diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb index daa8a572a0..c289ddadbe 100644 --- a/lib/chef/resource/ifconfig.rb +++ b/lib/chef/resource/ifconfig.rb @@ -7,9 +7,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class Ifconfig < Chef::Resource - + identity_attr :device state_attrs :inet_addr, :mask @@ -39,7 +39,7 @@ class Chef @bcast = nil @mtu = nil @metric = nil - @device = nil + @device = nil @onboot = nil @network = nil @bootproto = nil diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb index f82e0877df..88c6e9a538 100644 --- a/lib/chef/resource/ips_package.rb +++ b/lib/chef/resource/ips_package.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb index 30a5bb93c6..391c3b5393 100644 --- a/lib/chef/resource/log.rb +++ b/lib/chef/resource/log.rb @@ -7,9 +7,9 @@ # 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. @@ -19,7 +19,7 @@ class Chef class Resource class Log < Chef::Resource - + identity_attr :message # Sends a string from a recipe to a log provider @@ -27,16 +27,16 @@ class Chef # log "some string to log" do # level :info # (default) also supports :warn, :debug, and :error # end - # + # # === Example - # log "your string to log" + # log "your string to log" # - # or + # or # # log "a debug string" { level :debug } # - - # Initialize log resource with a name as the string to log + + # Initialize log resource with a name as the string to log # # === Parameters # name<String>:: Message to log @@ -47,6 +47,7 @@ class Chef @resource_name = :log @level = :info @action = :write + @allowed_actions.push(:write) @message = name end @@ -57,7 +58,7 @@ class Chef :kind_of => String ) end - + # <Symbol> Log level, one of :debug, :info, :warn, :error or :fatal def level(arg=nil) set_or_return( @@ -66,9 +67,9 @@ class Chef :equal_to => [ :debug, :info, :warn, :error, :fatal ] ) end - + end - end + end end diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb index 911d3c19cb..c9434c9e69 100644 --- a/lib/chef/resource/macports_package.rb +++ b/lib/chef/resource/macports_package.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb index ad68391fe4..49984630c0 100644 --- a/lib/chef/resource/mount.rb +++ b/lib/chef/resource/mount.rb @@ -7,9 +7,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class Mount < Chef::Resource - + identity_attr :device state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain @@ -46,7 +46,7 @@ class Chef @password = nil @domain = nil end - + def mount_point(arg=nil) set_or_return( :mount_point, @@ -54,7 +54,7 @@ class Chef :kind_of => [ String ] ) end - + def device(arg=nil) set_or_return( :device, @@ -62,7 +62,7 @@ class Chef :kind_of => [ String ] ) end - + def device_type(arg=nil) real_arg = arg.kind_of?(String) ? arg.to_sym : arg set_or_return( @@ -79,7 +79,7 @@ class Chef :kind_of => [ String ] ) end - + def options(arg=nil) if arg.is_a?(String) converted_arg = arg.gsub(/,/, ' ').split(/ /) @@ -92,7 +92,7 @@ class Chef :kind_of => [ Array ] ) end - + def dump(arg=nil) set_or_return( :dump, @@ -100,7 +100,7 @@ class Chef :kind_of => [ Integer, FalseClass ] ) end - + def pass(arg=nil) set_or_return( :pass, @@ -108,7 +108,7 @@ class Chef :kind_of => [ Integer, FalseClass ] ) end - + def mounted(arg=nil) set_or_return( :mounted, @@ -124,7 +124,7 @@ class Chef :kind_of => [ TrueClass, FalseClass ] ) end - + def supports(args={}) if args.is_a? Array args.each { |arg| @supports[arg] = true } @@ -163,4 +163,3 @@ class Chef end end - diff --git a/lib/chef/resource/ohai.rb b/lib/chef/resource/ohai.rb index 48e55e9f01..b567db40f9 100644 --- a/lib/chef/resource/ohai.rb +++ b/lib/chef/resource/ohai.rb @@ -7,9 +7,9 @@ # 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. @@ -20,7 +20,7 @@ class Chef class Resource class Ohai < Chef::Resource - + identity_attr :name state_attrs :plugin diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb index d66c93be66..2894e415ac 100644 --- a/lib/chef/resource/pacman_package.rb +++ b/lib/chef/resource/pacman_package.rb @@ -6,9 +6,9 @@ # 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. @@ -21,13 +21,13 @@ require 'chef/resource/package' class Chef class Resource class PacmanPackage < Chef::Resource::Package - + def initialize(name, run_context=nil) super @resource_name = :pacman_package @provider = Chef::Provider::Package::Pacman end - + end end end diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb index d3cf696cbb..546f639e1f 100644 --- a/lib/chef/resource/perl.rb +++ b/lib/chef/resource/perl.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require 'chef/resource/script' class Chef class Resource class Perl < Chef::Resource::Script - + def initialize(name, run_context=nil) super @resource_name = :perl diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb index fc72381482..42c03560b6 100644 --- a/lib/chef/resource/portage_package.rb +++ b/lib/chef/resource/portage_package.rb @@ -6,9 +6,9 @@ # 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. @@ -21,13 +21,13 @@ require 'chef/resource/package' class Chef class Resource class PortagePackage < Chef::Resource::Package - + def initialize(name, run_context=nil) super @resource_name = :portage_package @provider = Chef::Provider::Package::Portage end - + end end end diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb index e257eb2fb1..cbd81b1259 100644 --- a/lib/chef/resource/powershell_script.rb +++ b/lib/chef/resource/powershell_script.rb @@ -6,9 +6,9 @@ # 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. @@ -25,7 +25,7 @@ class Chef def initialize(name, run_context=nil) super(name, run_context, :powershell_script, "powershell.exe") end - + end end end diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb index 85a5348d27..f340afdb39 100644 --- a/lib/chef/resource/python.rb +++ b/lib/chef/resource/python.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require 'chef/resource/script' class Chef class Resource class Python < Chef::Resource::Script - + def initialize(name, run_context=nil) super @resource_name = :python diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb index c8680697af..942905d138 100644 --- a/lib/chef/resource/route.rb +++ b/lib/chef/resource/route.rb @@ -7,9 +7,9 @@ # 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. @@ -26,7 +26,7 @@ class Chef identity_attr :target state_attrs :netmask, :gateway - + def initialize(name, run_context=nil) super @resource_name = :route @@ -36,7 +36,7 @@ class Chef @netmask = nil @gateway = nil @metric = nil - @device = nil + @device = nil @route_type = :host @networking = nil @networking_ipv6 = nil diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb index 7ab1202ef2..200a9633ce 100644 --- a/lib/chef/resource/rpm_package.rb +++ b/lib/chef/resource/rpm_package.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb index 7617839bab..605d27b00d 100644 --- a/lib/chef/resource/ruby.rb +++ b/lib/chef/resource/ruby.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require 'chef/resource/script' class Chef class Resource class Ruby < Chef::Resource::Script - + def initialize(name, run_context=nil) super @resource_name = :ruby diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb index 296345bde3..d9b8954a90 100644 --- a/lib/chef/resource/ruby_block.rb +++ b/lib/chef/resource/ruby_block.rb @@ -7,9 +7,9 @@ # 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. @@ -20,7 +20,7 @@ class Chef class Resource class RubyBlock < Chef::Resource - + identity_attr :block_name def initialize(name, run_context=nil) diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb index 781e09a2c9..d9a372900e 100644 --- a/lib/chef/resource/scm.rb +++ b/lib/chef/resource/scm.rb @@ -23,8 +23,8 @@ class Chef class Resource class Scm < Chef::Resource - identity_attr :destination - + identity_attr :destination + state_attrs :revision def initialize(name, run_context=nil) @@ -146,6 +146,14 @@ class Chef ) end + def timeout(arg=nil) + set_or_return( + :timeout, + arg, + :kind_of => Integer + ) + end + end end end diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb index 6a7c8e0d5e..8cc9c6f0c5 100644 --- a/lib/chef/resource/script.rb +++ b/lib/chef/resource/script.rb @@ -7,9 +7,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource/execute' class Chef class Resource class Script < Chef::Resource::Execute - + identity_attr :command def initialize(name, run_context=nil) @@ -33,7 +33,7 @@ class Chef @interpreter = nil @flags = nil end - + def code(arg=nil) set_or_return( :code, @@ -41,7 +41,7 @@ class Chef :kind_of => [ String ] ) end - + def interpreter(arg=nil) set_or_return( :interpreter, diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb index ea43baa414..befa4be1c9 100644 --- a/lib/chef/resource/service.rb +++ b/lib/chef/resource/service.rb @@ -7,9 +7,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class Service < Chef::Resource - + identity_attr :service_name state_attrs :enabled, :running @@ -47,7 +47,7 @@ class Chef @supports = { :restart => false, :reload => false, :status => false } @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload) end - + def service_name(arg=nil) set_or_return( :service_name, @@ -55,7 +55,7 @@ class Chef :kind_of => [ String ] ) end - + # regex for match against ps -ef when !supports[:has_status] && status == nil def pattern(arg=nil) set_or_return( diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb index 315481bd93..0f4f6d8b0a 100644 --- a/lib/chef/resource/smartos_package.rb +++ b/lib/chef/resource/smartos_package.rb @@ -6,9 +6,9 @@ # 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. @@ -21,16 +21,18 @@ require 'chef/provider/package/smartos' class Chef class Resource - class SmartOSPackage < Chef::Resource::Package - + class SmartosPackage < Chef::Resource::Package + def initialize(name, run_context=nil) super @resource_name = :smartos_package @provider = Chef::Provider::Package::SmartOS end - + end end end - +# Backwards compatability +# @todo remove in Chef 12 +Chef::Resource::SmartOSPackage = Chef::Resource::SmartosPackage diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb index becf0236ad..3513703076 100644 --- a/lib/chef/resource/solaris_package.rb +++ b/lib/chef/resource/solaris_package.rb @@ -1,14 +1,15 @@ # # Author:: Toomas Pelberg (<toomasp@gmx.net>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. +# Author:: Prabhu Das (<prabhu.das@clogeny.com>) +# Copyright:: Copyright (c) 2013 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. @@ -22,13 +23,13 @@ require 'chef/provider/package/solaris' class Chef class Resource class SolarisPackage < Chef::Resource::Package - - def initialize(name, collection=nil, node=nil) - super(name, collection, node) + + def initialize(name, run_context=nil) + super @resource_name = :solaris_package @provider = Chef::Provider::Package::Solaris end - + end end end diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb index e3226d8b3b..04fec9b1d8 100644 --- a/lib/chef/resource/subversion.rb +++ b/lib/chef/resource/subversion.rb @@ -7,9 +7,9 @@ # 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. @@ -31,7 +31,7 @@ class Chef @provider = Chef::Provider::Subversion allowed_actions << :force_export end - + end end end diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/resource/timestamped_deploy.rb index d89274bb44..4032ae9854 100644 --- a/lib/chef/resource/timestamped_deploy.rb +++ b/lib/chef/resource/timestamped_deploy.rb @@ -6,9 +6,9 @@ # 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. @@ -18,9 +18,9 @@ class Chef class Resource - + # Convenience class for using the deploy resource with the timestamped - # deployment strategy (provider) + # deployment strategy (provider) class TimestampedDeploy < Chef::Resource::Deploy def initialize(*args, &block) super(*args, &block) diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb index 4d8c4ac11b..357d6d12ea 100644 --- a/lib/chef/resource/user.rb +++ b/lib/chef/resource/user.rb @@ -6,9 +6,9 @@ # 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. @@ -25,7 +25,7 @@ class Chef identity_attr :username state_attrs :uid, :gid, :home - + def initialize(name, run_context=nil) super @resource_name = :user @@ -40,13 +40,13 @@ class Chef @manage_home = false @non_unique = false @action = :create - @supports = { + @supports = { :manage_home => false, :non_unique => false } @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock) end - + def username(arg=nil) set_or_return( :username, @@ -54,7 +54,7 @@ class Chef :kind_of => [ String ] ) end - + def comment(arg=nil) set_or_return( :comment, @@ -62,7 +62,7 @@ class Chef :kind_of => [ String ] ) end - + def uid(arg=nil) set_or_return( :uid, @@ -70,7 +70,7 @@ class Chef :kind_of => [ String, Integer ] ) end - + def gid(arg=nil) set_or_return( :gid, @@ -78,9 +78,9 @@ class Chef :kind_of => [ String, Integer ] ) end - + alias_method :group, :gid - + def home(arg=nil) set_or_return( :home, @@ -88,7 +88,7 @@ class Chef :kind_of => [ String ] ) end - + def shell(arg=nil) set_or_return( :shell, @@ -96,7 +96,7 @@ class Chef :kind_of => [ String ] ) end - + def password(arg=nil) set_or_return( :password, @@ -128,7 +128,7 @@ class Chef :kind_of => [ TrueClass, FalseClass ] ) end - + end end end diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb index 5f2311a5bb..2b563f5bec 100644 --- a/lib/chef/resource/windows_script.rb +++ b/lib/chef/resource/windows_script.rb @@ -6,9 +6,9 @@ # 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. @@ -27,14 +27,14 @@ class Chef def initialize(name, run_context, resource_name, interpreter_command) super(name, run_context) - @interpreter = interpreter_command + @interpreter = interpreter_command @resource_name = resource_name end include Chef::Mixin::WindowsArchitectureHelper public - + def architecture(arg=nil) assert_architecture_compatible!(arg) if ! arg.nil? result = set_or_return( @@ -43,7 +43,7 @@ class Chef :kind_of => Symbol ) end - + protected def assert_architecture_compatible!(desired_architecture) @@ -56,7 +56,7 @@ class Chef def node run_context && run_context.node end - + end end end diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb index bcb1f65667..dff70bcf62 100644 --- a/lib/chef/resource/yum_package.rb +++ b/lib/chef/resource/yum_package.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb index 7460a185b4..a528a18aed 100644 --- a/lib/chef/resource_collection.rb +++ b/lib/chef/resource_collection.rb @@ -183,7 +183,7 @@ class Chef "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'" else raise Chef::Exceptions::InvalidResourceSpecification, - "The object `#{query_object.inspect}' is not valid for resource collection lookup. " + + "The object `#{query_object.inspect}' is not valid for resource collection lookup. " + "Use a String like `resource_type[resource_name]' or a Chef::Resource object" end end diff --git a/lib/chef/resource_collection/stepable_iterator.rb b/lib/chef/resource_collection/stepable_iterator.rb index ec1e244758..4d5fc1f497 100644 --- a/lib/chef/resource_collection/stepable_iterator.rb +++ b/lib/chef/resource_collection/stepable_iterator.rb @@ -5,9 +5,9 @@ # 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. @@ -18,94 +18,94 @@ class Chef class ResourceCollection class StepableIterator - + def self.for_collection(new_collection) instance = new(new_collection) instance end - + attr_accessor :collection attr_reader :position - + def initialize(collection=[]) @position = 0 @paused = false @collection = collection end - + def size collection.size end - + def each(&block) reset_iteration(block) @iterator_type = :element iterate end - + def each_index(&block) reset_iteration(block) @iterator_type = :index iterate end - + def each_with_index(&block) reset_iteration(block) @iterator_type = :element_with_index iterate end - + def paused? @paused end - + def pause @paused = true end - + def resume @paused = false iterate end - + def rewind @position = 0 end - + def skip_back(skips=1) @position -= skips end - + def skip_forward(skips=1) @position += skips end - + def step return nil if @position == size call_iterator_block @position += 1 end - + def iterate_on(iteration_type, &block) @iterator_type = iteration_type @iterator_block = block end - + private - + def reset_iteration(iterator_block) @iterator_block = iterator_block @position = 0 @paused = false end - + def iterate while @position < size && !paused? step end collection end - + def call_iterator_block case @iterator_type when :element @@ -118,7 +118,7 @@ class Chef raise "42error: someone forgot to set @iterator_type, wtf?" end end - + end end end diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb index a0160c5885..278114e209 100644 --- a/lib/chef/resource_definition.rb +++ b/lib/chef/resource_definition.rb @@ -6,9 +6,9 @@ # 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. @@ -21,19 +21,19 @@ require 'chef/mixin/params_validate' class Chef class ResourceDefinition - + include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - + attr_accessor :name, :params, :recipe, :node - + def initialize(node=nil) @name = nil @params = Hash.new @recipe = nil @node = node end - + def define(resource_name, prototype_params=nil, &block) unless resource_name.kind_of?(Symbol) raise ArgumentError, "You must use a symbol when defining a new resource!" @@ -52,14 +52,14 @@ class Chef end true end - + # When we do the resource definition, we're really just setting new values for # the paramaters we prototyped at the top. This method missing is as simple as # it gets. def method_missing(symbol, *args) @params[symbol] = args.length == 1 ? args[0] : args end - + def to_s "#{name.to_s}" end diff --git a/lib/chef/resource_definition_list.rb b/lib/chef/resource_definition_list.rb index b958624208..55014090d4 100644 --- a/lib/chef/resource_definition_list.rb +++ b/lib/chef/resource_definition_list.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 434150e83d..d29949086e 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -52,8 +52,8 @@ class Chef as_hash["type"] = new_resource.class.dsl_name as_hash["name"] = new_resource.name as_hash["id"] = new_resource.identity - as_hash["after"] = new_resource.state - as_hash["before"] = current_resource ? current_resource.state : {} + as_hash["after"] = state(new_resource) + as_hash["before"] = current_resource ? state(current_resource) : {} as_hash["duration"] = (elapsed_time * 1000).to_i.to_s as_hash["delta"] = new_resource.diff if new_resource.respond_to?("diff") as_hash["delta"] = "" if as_hash["delta"].nil? @@ -80,6 +80,12 @@ class Chef !self.exception end + def state(r) + r.class.state_attrs.inject({}) do |state_attrs, attr_name| + state_attrs[attr_name] = r.send(attr_name) + state_attrs + end + end end # End class ResouceReport attr_reader :updated_resources diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 55c6a0dbf3..d0a27d8922 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -56,6 +56,7 @@ require 'chef/resource/registry_key' require 'chef/resource/remote_directory' require 'chef/resource/remote_file' require 'chef/resource/rpm_package' +require 'chef/resource/solaris_package' require 'chef/resource/route' require 'chef/resource/ruby' require 'chef/resource/ruby_block' @@ -69,3 +70,4 @@ require 'chef/resource/timestamped_deploy' require 'chef/resource/user' require 'chef/resource/yum_package' require 'chef/resource/lwrp_base' +require 'chef/resource/bff_package' diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb index 6c74412839..4168cd63be 100644 --- a/lib/chef/rest.rb +++ b/lib/chef/rest.rb @@ -20,15 +20,18 @@ # limitations under the License. # -require 'zlib' -require 'net/https' -require 'uri' -require 'chef/json_compat' require 'tempfile' -require 'chef/rest/auth_credentials' -require 'chef/rest/rest_request' -require 'chef/monkey_patches/string' -require 'chef/monkey_patches/net_http' +require 'chef/http' +class Chef + class HTTP; end + class REST < HTTP; end +end + +require 'chef/http/authenticator' +require 'chef/http/decompressor' +require 'chef/http/json_input' +require 'chef/http/json_to_model_output' +require 'chef/http/cookie_manager' require 'chef/config' require 'chef/exceptions' require 'chef/platform/query_helpers' @@ -37,54 +40,53 @@ class Chef # == Chef::REST # Chef's custom REST client with built-in JSON support and RSA signed header # authentication. - class REST + class REST < HTTP - class NoopInflater - def inflate(chunk) - chunk - end - end + # Backwards compatibility for things that use + # Chef::REST::RESTRequest or its constants + RESTRequest = HTTP::HTTPRequest - attr_reader :auth_credentials attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit - CONTENT_ENCODING = "content-encoding".freeze - GZIP = "gzip".freeze - DEFLATE = "deflate".freeze - IDENTITY = "identity".freeze + attr_reader :authenticator # Create a REST client object. The supplied +url+ is used as the base for # all subsequent requests. For example, when initialized with a base url # http://localhost:4000, a call to +get_rest+ with 'nodes' will make an # HTTP GET request to http://localhost:4000/nodes def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={}) - @url = url - @cookies = CookieJar.instance - @default_headers = options[:headers] || {} - @signing_key_filename = signing_key_filename - @key = load_signing_key(@signing_key_filename, options[:raw_key]) - @auth_credentials = AuthCredentials.new(client_name, @key) - @sign_on_redirect, @sign_request = true, true - @redirects_followed = 0 - @redirect_limit = 10 - @disable_gzip = false - handle_options(options) + options[:client_name] = client_name + options[:signing_key_filename] = signing_key_filename + super(url, options) + + @decompressor = Decompressor.new(options) + @authenticator = Authenticator.new(options) + + @middlewares << JSONInput.new(options) + @middlewares << JSONToModelOutput.new(options) + @middlewares << CookieManager.new(options) + @middlewares << @decompressor + @middlewares << @authenticator end def signing_key_filename - @signing_key_filename + authenticator.signing_key_filename + end + + def auth_credentials + authenticator.auth_credentials end def client_name - @auth_credentials.client_name + authenticator.client_name end def signing_key - @raw_key + authenticator.raw_key end - def last_response - @last_response + def sign_requests? + authenticator.sign_requests? end # Send an HTTP GET request to the path @@ -97,37 +99,18 @@ class Chef # to JSON inflated. def get(path, raw=false, headers={}) if raw - streaming_request(create_url(path), headers) + streaming_request(path, headers) else - api_request(:GET, create_url(path), headers) + request(:GET, path, headers) end end - def head(path, headers={}) - api_request(:HEAD, create_url(path), headers) - end - alias :get_rest :get - # Send an HTTP DELETE request to the path - def delete(path, headers={}) - api_request(:DELETE, create_url(path), headers) - end - alias :delete_rest :delete - # Send an HTTP POST request to the path - def post(path, json, headers={}) - api_request(:POST, create_url(path), headers, json) - end - alias :post_rest :post - # Send an HTTP PUT request to the path - def put(path, json, headers={}) - api_request(:PUT, create_url(path), headers, json) - end - alias :put_rest :put # Streams a download to a tempfile, then yields the tempfile to a block. @@ -139,308 +122,59 @@ class Chef streaming_request(create_url(path), headers) {|tmp_file| yield tmp_file } end - def create_url(path) - if path =~ /^(http|https):\/\// - URI.parse(path) - else - URI.parse("#{@url}/#{path}") - end - end - - def sign_requests? - auth_credentials.sign_requests? && @sign_request - end - - # Runs an HTTP request to a JSON API with JSON body. File Download not supported. - def api_request(method, url, headers={}, data=false) - json_body = data ? Chef::JSONCompat.to_json(data) : nil - # Force encoding to binary to fix SSL related EOFErrors - # cf. http://tickets.opscode.com/browse/CHEF-2363 - # http://redmine.ruby-lang.org/issues/5233 - json_body.force_encoding(Encoding::BINARY) if json_body.respond_to?(:force_encoding) - raw_http_request(method, url, headers, json_body) - end - - # Runs an HTTP request to a JSON API with raw body. File Download not supported. - def raw_http_request(method, url, headers, body) - headers = build_headers(method, url, headers, body) - retriable_rest_request(method, url, body, headers) do |rest_request| - begin - response = rest_request.call {|r| r.read_body} - @last_response = response - - Chef::Log.debug("---- HTTP Status and Header Data: ----") - Chef::Log.debug("HTTP #{response.http_version} #{response.code} #{response.msg}") - - response.each do |header, value| - Chef::Log.debug("#{header}: #{value}") - end - Chef::Log.debug("---- End HTTP Status/Header Data ----") - - response_body = decompress_body(response) - - if response.kind_of?(Net::HTTPSuccess) - if response['content-type'] =~ /json/ - Chef::JSONCompat.from_json(response_body.chomp) - else - Chef::Log.warn("Expected JSON response, but got content-type '#{response['content-type']}'") - response_body.to_s - end - elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass. - false - elsif redirect_location = redirected_to(response) - if [:GET, :HEAD].include?(method) - follow_redirect {api_request(method, create_url(redirect_location))} - else - raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." - end - else - # have to decompress the body before making an exception for it. But the body could be nil. - response.body.replace(response_body) if response.body.respond_to?(:replace) - - if response['content-type'] =~ /json/ - exception = Chef::JSONCompat.from_json(response_body) - msg = "HTTP Request Returned #{response.code} #{response.message}: " - msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s) - Chef::Log.info(msg) - end - response.error! - end - rescue Exception => e - if e.respond_to?(:chef_rest_request=) - e.chef_rest_request = rest_request - end - raise - end - end - end - - def decompress_body(response) - if gzip_disabled? || response.body.nil? - response.body - else - case response[CONTENT_ENCODING] - when GZIP - Chef::Log.debug "decompressing gzip response" - Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body) - when DEFLATE - Chef::Log.debug "decompressing deflate response" - Zlib::Inflate.inflate(response.body) - else - response.body - end - end - end + alias :api_request :request - # Makes a streaming download request. <b>Doesn't speak JSON.</b> - # Streams the response body to a tempfile. If a block is given, it's - # passed to Tempfile.open(), which means that the tempfile will automatically - # be unlinked after the block is executed. - # - # If no block is given, the tempfile is returned, which means it's up to - # you to unlink the tempfile when you're done with it. - def streaming_request(url, headers, &block) - headers = build_headers(:GET, url, headers, nil, true) - retriable_rest_request(:GET, url, nil, headers) do |rest_request| - begin - tempfile = nil - response = rest_request.call do |r| - if block_given? && r.kind_of?(Net::HTTPSuccess) - begin - tempfile = stream_to_tempfile(url, r, &block) - yield tempfile - ensure - tempfile.close! - end - else - tempfile = stream_to_tempfile(url, r) - end - end - @last_response = response - if response.kind_of?(Net::HTTPSuccess) - tempfile - elsif redirect_location = redirected_to(response) - # TODO: test tempfile unlinked when following redirects. - tempfile && tempfile.close! - follow_redirect {streaming_request(create_url(redirect_location), {}, &block)} - else - tempfile && tempfile.close! - response.error! - end - rescue Exception => e - if e.respond_to?(:chef_rest_request=) - e.chef_rest_request = rest_request - end - raise - end - end - end + alias :raw_http_request :send_http_request - def retriable_rest_request(method, url, req_body, headers) - rest_request = Chef::REST::RESTRequest.new(method, url, req_body, headers) + # Deprecated: + # Responsibilities of this method have been split up. The #http_client is + # now responsible for making individual requests, while + # #retrying_http_errors handles error/retry logic. + def retriable_http_request(method, url, req_body, headers) + rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers) Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}") - http_attempts = 0 - - begin - http_attempts += 1 - + retrying_http_errors(url) do yield rest_request - - rescue SocketError, Errno::ETIMEDOUT => e - e.message.replace "Error connecting to #{url} - #{e.message}" - raise e - rescue Errno::ECONNREFUSED - if http_retry_count - http_attempts + 1 > 0 - Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") - sleep(http_retry_delay) - retry - end - raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up" - rescue Timeout::Error - if http_retry_count - http_attempts + 1 > 0 - Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") - sleep(http_retry_delay) - retry - end - raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up" - rescue Net::HTTPFatalError => e - if http_retry_count - http_attempts + 1 > 0 - sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts) - Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s") - sleep(sleep_time) - retry - end - raise end end - def authentication_headers(method, url, json_body=nil) - request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"} - request_params[:body] ||= "" - auth_credentials.signature_headers(request_params) - end - - def http_retry_delay - config[:http_retry_delay] - end - - def http_retry_count - config[:http_retry_count] + # Customized streaming behavior; sets the accepted content type to "*/*" + # if not otherwise specified for compatibility purposes + def streaming_request(url, headers, &block) + headers["Accept"] ||= "*/*" + super end - def config - Chef::Config - end + alias :retriable_rest_request :retriable_http_request def follow_redirect - raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit - @redirects_followed += 1 - Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}") - if @sign_on_redirect - yield - else - @sign_request = false - yield + unless @sign_on_redirect + @authenticator.sign_request = false end + super ensure - @redirects_followed = 0 - @sign_request = true + @authenticator.sign_request = true end - private + public :create_url - def redirected_to(response) - return nil unless response.kind_of?(Net::HTTPRedirection) - # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this - return nil if response.kind_of?(Net::HTTPNotModified) - response['location'] + def http_client(base_url=nil) + base_url ||= url + BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy) end - def build_headers(method, url, headers={}, json_body=false, raw=false) - headers = @default_headers.merge(headers) - #headers['Accept'] = "application/json" unless raw - headers['Accept'] = "application/json" unless raw - headers["Content-Type"] = 'application/json' if json_body - headers['Content-Length'] = json_body.bytesize.to_s if json_body - headers[RESTRequest::ACCEPT_ENCODING] = RESTRequest::ENCODING_GZIP_DEFLATE unless gzip_disabled? - headers.merge!(authentication_headers(method, url, json_body)) if sign_requests? - headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers] - headers - end - - def stream_to_tempfile(url, response) - tf = Tempfile.open("chef-rest") - 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}") - # Stolen from http://www.ruby-forum.com/topic/166423 - # Kudos to _why! - - inflater = if gzip_disabled? - NoopInflater.new - else - case response[CONTENT_ENCODING] - when GZIP - Chef::Log.debug "decompressing gzip stream" - Zlib::Inflate.new(Zlib::MAX_WBITS + 16) - when DEFLATE - Chef::Log.debug "decompressing inflate stream" - Zlib::Inflate.new - else - NoopInflater.new - end - end + ############################################################################ + # DEPRECATED + ############################################################################ - response.read_body do |chunk| - tf.write(inflater.inflate(chunk)) - end - tf.close - tf - rescue Exception - tf.close! - raise + def decompress_body(body) + @decompressor.decompress_body(body) end - # gzip is disabled using the disable_gzip => true option in the - # constructor. When gzip is disabled, no 'Accept-Encoding' header will be - # set, and the response will not be decompressed, no matter what the - # Content-Encoding header of the response is. The intended use case for - # this is to work around situations where you request +file.tar.gz+, but - # the server responds with a content type of tar and a content encoding of - # gzip, tricking the client into decompressing the response so you end up - # with a tar archive (no gzip) named file.tar.gz - def gzip_disabled? - @disable_gzip - end - - def handle_options(opts) - opts.each do |name, value| - case name.to_s - when 'disable_gzip' - @disable_gzip = value - end - end - end - - def load_signing_key(key_file, raw_key = nil) - if (!!key_file) - @raw_key = IO.read(key_file).strip - elsif (!!raw_key) - @raw_key = raw_key.strip - else - return nil - end - @key = OpenSSL::PKey::RSA.new(@raw_key) - rescue SystemCallError, IOError => e - Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}" - raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!" - rescue OpenSSL::PKey::RSAError - msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key.\n" - msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'" - raise Chef::Exceptions::InvalidPrivateKey, msg + def authentication_headers(method, url, json_body=nil) + authenticator.authentication_headers(method, url, json_body) end end diff --git a/lib/chef/role.rb b/lib/chef/role.rb index 78bbfadb88..6ad58b816d 100644 --- a/lib/chef/role.rb +++ b/lib/chef/role.rb @@ -233,20 +233,24 @@ class Chef # Load a role from disk - prefers to load the JSON, but will happily load # the raw rb files as well. def self.from_disk(name, force=nil) - js_file = File.join(Chef::Config[:role_path], "#{name}.json") - rb_file = File.join(Chef::Config[:role_path], "#{name}.rb") - - if File.exists?(js_file) || force == "json" - # from_json returns object.class => json_class in the JSON. - Chef::JSONCompat.from_json(IO.read(js_file)) - elsif File.exists?(rb_file) || force == "ruby" - role = Chef::Role.new - role.name(name) - role.from_file(rb_file) - role - else - raise Chef::Exceptions::RoleNotFound, "Role '#{name}' could not be loaded from disk" + paths = Array(Chef::Config[:role_path]) + + paths.each do |p| + js_file = File.join(p, "#{name}.json") + rb_file = File.join(p, "#{name}.rb") + + if File.exists?(js_file) || force == "json" + # from_json returns object.class => json_class in the JSON. + return Chef::JSONCompat.from_json(IO.read(js_file)) + elsif File.exists?(rb_file) || force == "ruby" + role = Chef::Role.new + role.name(name) + role.from_file(rb_file) + return role + end end + + raise Chef::Exceptions::RoleNotFound, "Role '#{name}' could not be loaded from disk" end end diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index 6477431967..4d431116f9 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -204,6 +204,20 @@ class Chef @loaded_attributes["#{cookbook}::#{attribute_file}"] = true end + ## + # Cookbook File Introspection + + def has_template_in_cookbook?(cookbook, template_name) + cookbook = cookbook_collection[cookbook] + cookbook.has_template_for_node?(node, template_name) + end + + def has_cookbook_file_in_cookbook?(cookbook, cb_file_name) + cookbook = cookbook_collection[cookbook] + cookbook.has_cookbook_file_for_node?(node, cb_file_name) + end + + private def loaded_recipe(cookbook, recipe) diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb index d1b93f6652..0a05061152 100644 --- a/lib/chef/run_context/cookbook_compiler.rb +++ b/lib/chef/run_context/cookbook_compiler.rb @@ -6,9 +6,9 @@ # 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. @@ -259,7 +259,7 @@ class Chef cookbook_collection[cookbook].segment_filenames(segment).sort end - # Yields the name, as a symbol, of each cookbook depended on by + # Yields the name, as a symbol, of each cookbook depended on by # +cookbook_name+ in lexical sort order. def each_cookbook_dep(cookbook_name, &block) cookbook = cookbook_collection[cookbook_name] diff --git a/lib/chef/run_lock.rb b/lib/chef/run_lock.rb index 50046c2396..972db1a4e1 100644 --- a/lib/chef/run_lock.rb +++ b/lib/chef/run_lock.rb @@ -17,6 +17,9 @@ require 'chef/mixin/create_path' require 'fcntl' +if Chef::Platform.windows? + require 'chef/win32/mutex' +end class Chef @@ -30,20 +33,16 @@ class Chef include Chef::Mixin::CreatePath attr_reader :runlock + attr_reader :mutex attr_reader :runlock_file # Create a new instance of RunLock # === Arguments - # * config::: This will generally be the `Chef::Config`, but any Hash-like - # object with Symbol keys will work. See 'Parameters' section. - # === Parameters/Config - # * :lockfile::: if set, this will be used as the full path to the lockfile. - # * :file_cache_path::: if `:lockfile` is not set, the lock file will be - # named "chef-client-running.pid" and be placed in the directory given by - # `:file_cache_path` - def initialize(config) - @runlock_file = config[:lockfile] || "#{config[:file_cache_path]}/chef-client-running.pid" + # * :lockfile::: the full path to the lockfile. + def initialize(lockfile) + @runlock_file = lockfile @runlock = nil + @mutex = nil end # Acquire the system-wide lock. Will block indefinitely if another process @@ -52,31 +51,73 @@ class Chef # Each call to acquire should have a corresponding call to #release. # # The implementation is based on File#flock (see also: flock(2)). + # + # Either acquire() or test() methods should be called in order to + # get the ownership of run_lock. def acquire + wait unless test + end + + # + # Tests and if successful acquires the system-wide lock. + # Returns true if the lock is acquired, false otherwise. + # + # Either acquire() or test() methods should be called in order to + # get the ownership of run_lock. + def test # ensure the runlock_file path exists create_path(File.dirname(runlock_file)) - @runlock = File.open(runlock_file,'w+') - # if we support FD_CLOEXEC (linux, !windows), then use it. - # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not ruby-1.8.7/1.9.3 - if Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC') - runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC) + @runlock = File.open(runlock_file,'a+') + + if Chef::Platform.windows? + acquire_win32_mutex + else + # If we support FD_CLOEXEC, then use it. + # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not + # ruby-1.8.7/1.9.3 + if Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC') + runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC) + end + # Flock will return 0 if it can acquire the lock otherwise it + # will return false + if runlock.flock(File::LOCK_NB|File::LOCK_EX) == 0 + true + else + false + end end - unless runlock.flock(File::LOCK_EX|File::LOCK_NB) - # Another chef client running... - runpid = runlock.read.strip.chomp - Chef::Log.warn("Chef client #{runpid} is running, will wait for it to finish and then run.") + end + + # + # Waits until acquiring the system-wide lock. + # + def wait + runpid = runlock.read.strip.chomp + Chef::Log.warn("Chef client #{runpid} is running, will wait for it to finish and then run.") + if Chef::Platform.windows? + mutex.wait + else runlock.flock(File::LOCK_EX) end - # We grabbed the run lock. Save the pid. + end + + def save_pid runlock.truncate(0) runlock.rewind # truncate doesn't reset position to 0. runlock.write(Process.pid.to_s) + # flush the file fsync flushes the system buffers + # in addition to ruby buffers + runlock.fsync end # Release the system-wide lock. def release if runlock - runlock.flock(File::LOCK_UN) + if Chef::Platform.windows? + mutex.release + else + runlock.flock(File::LOCK_UN) + end runlock.close # Don't unlink the pid file, if another chef-client was waiting, it # won't be recreated. Better to leave a "dead" pid file than not have @@ -89,8 +130,20 @@ class Chef def reset @runlock = nil + @mutex = nil end + # Since flock mechanism doesn't exist on windows we are using + # platform Mutex. + # We are creating a "Global" mutex here so that non-admin + # users can not DoS chef-client by creating the same named + # mutex we are using locally. + # Mutex name is case-sensitive contrary to other things in + # windows. "\" is the only invalid character. + def acquire_win32_mutex + @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.gsub(/[\\]/, "/").downcase}") + mutex.test + end end end diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb new file mode 100644 index 0000000000..e9e7593dd6 --- /dev/null +++ b/lib/chef/server_api.rb @@ -0,0 +1,41 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/http' +require 'chef/http/authenticator' +require 'chef/http/cookie_manager' +require 'chef/http/decompressor' +require 'chef/http/json_input' +require 'chef/http/json_output' + +class Chef + class ServerAPI < Chef::HTTP + + def initialize(url = Chef::Config[:chef_server_url], options = {}) + options[:client_name] ||= Chef::Config[:node_name] + options[:signing_key_filename] ||= Chef::Config[:client_key] + super(url, options) + end + + use Chef::HTTP::JSONInput + use Chef::HTTP::JSONOutput + use Chef::HTTP::CookieManager + use Chef::HTTP::Decompressor + use Chef::HTTP::Authenticator + end +end
\ No newline at end of file diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb index 4c86f96616..0788962e62 100644 --- a/lib/chef/shell.rb +++ b/lib/chef/shell.rb @@ -24,6 +24,7 @@ require 'chef' require 'chef/version' require 'chef/client' require 'chef/config' +require 'chef/config_fetcher' require 'chef/shell/shell_session' require 'chef/shell/ext' @@ -151,26 +152,9 @@ module Shell end def self.parse_json - # HACK: copied verbatim from chef/application/client, because it's not - # reusable as written there :( if Chef::Config[:json_attribs] - begin - json_io = open(Chef::Config[:json_attribs]) - rescue SocketError => error - fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2) - rescue Errno::ENOENT => error - fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2) - rescue Errno::EACCES => error - fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2) - rescue Exception => error - fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2) - end - - begin - @json_attribs = Chef::JSONCompat.from_json(json_io.read) - rescue JSON::ParserError => error - fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2) - end + config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) + @json_attribs = config_fetcher.fetch_json end end diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb index cf147e778d..2f4d9375eb 100644 --- a/lib/chef/shell/shell_session.rb +++ b/lib/chef/shell/shell_session.rb @@ -173,7 +173,7 @@ module Shell cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path]) cl.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cl) - @run_context = Chef::RunContext.new(node, cookbook_collection, @events) + @run_context = Chef::RunContext.new(node, cookbook_collection, @events) @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", [])) @run_status.run_context = run_context end diff --git a/lib/chef/streaming_cookbook_uploader.rb b/lib/chef/streaming_cookbook_uploader.rb index df90e0003d..9e638f6367 100644 --- a/lib/chef/streaming_cookbook_uploader.rb +++ b/lib/chef/streaming_cookbook_uploader.rb @@ -15,22 +15,23 @@ class Chef class StreamingCookbookUploader DefaultHeaders = { 'accept' => 'application/json', 'x-chef-version' => ::Chef::VERSION } - + class << self def post(to_url, user_id, secret_key_filename, params = {}, headers = {}) make_request(:post, to_url, user_id, secret_key_filename, params, headers) end - + def put(to_url, user_id, secret_key_filename, params = {}, headers = {}) make_request(:put, to_url, user_id, secret_key_filename, params, headers) end - + def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {}) + Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader instead.') boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ' parts = [] content_file = nil - + timestamp = Time.now.utc.iso8601 secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename)) @@ -53,31 +54,31 @@ class Chef end parts << StringPart.new("--" + boundary + "--\r\n") end - + body_stream = MultipartStream.new(parts) - + timestamp = Time.now.utc.iso8601 - + url = URI.parse(to_url) - + Chef::Log.logger.debug("Signing: method: #{http_verb}, path: #{url.path}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}") - + # We use the body for signing the request if the file parameter - # wasn't a valid file or wasn't included. Extract the body (with + # wasn't a valid file or wasn't included. Extract the body (with # multi-part delimiters intact) to sign the request. # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and # always hash the entire request body. In the file case it would just be # expanded multipart text - the entire body of the POST. content_body = parts.inject("") { |result,part| result + part.read(0, part.size) } content_file.rewind if content_file # we consumed the file for the above operation, so rewind it. - + signing_options = { :http_method=>http_verb, :path=>url.path, :user_id=>user_id, :timestamp=>timestamp} (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || "")) - + headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key)) content_file.rewind if content_file @@ -90,11 +91,11 @@ class Chef Net::HTTP::Put.new(url.path, headers) when :post Net::HTTP::Post.new(url.path, headers) - end + end req.content_length = body_stream.size req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty? req.body_stream = body_stream - + http = Net::HTTP.new(url.host, url.port) if url.scheme == "https" http.use_ssl = true @@ -107,30 +108,31 @@ class Chef # TODO: stop the following madness! class << res alias :to_s :body - + # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[]) def headers self end - + def status code.to_i end end res end - + end class StreamPart def initialize(stream, size) + Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader::StreamPart class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader::StreamPart instead.') @stream, @size = stream, size end - + def size @size end - + # read the specified amount from the stream def read(offset, how_much) @stream.read(how_much) @@ -139,9 +141,10 @@ class Chef class StringPart def initialize(str) + Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader::StringPart class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader::StringPart instead.') @str = str end - + def size @str.length end @@ -154,30 +157,31 @@ class Chef class MultipartStream def initialize(parts) + Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader::MultipartStream class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader::MultipartStream instead.') @parts = parts @part_no = 0 @part_offset = 0 end - + def size @parts.inject(0) {|size, part| size + part.size} end - + def read(how_much) return nil if @part_no >= @parts.size how_much_current_part = @parts[@part_no].size - @part_offset - + how_much_current_part = if how_much_current_part > how_much how_much else how_much_current_part end - + how_much_next_part = how_much - how_much_current_part current_part = @parts[@part_no].read(@part_offset, how_much_current_part) - + # recurse into the next part if the current one was not large enough if how_much_next_part > 0 @part_no += 1 @@ -194,7 +198,7 @@ class Chef end end end - + end diff --git a/lib/chef/tasks/chef_repo.rake b/lib/chef/tasks/chef_repo.rake index 6f839a486f..704557ebb3 100644 --- a/lib/chef/tasks/chef_repo.rake +++ b/lib/chef/tasks/chef_repo.rake @@ -6,9 +6,9 @@ # 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. @@ -51,7 +51,7 @@ task :update do pull = true if line =~ /\[remote "origin"\]/ end if pull - sh %{git pull} + sh %{git pull} else puts "* Skipping git pull, no origin specified" end @@ -86,14 +86,14 @@ end def create_cookbook(dir) raise "Must provide a COOKBOOK=" unless ENV["COOKBOOK"] puts "** Creating cookbook #{ENV["COOKBOOK"]}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "attributes")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "recipes")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "definitions")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "libraries")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "resources")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "providers")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "files", "default")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "templates", "default")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "attributes")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "recipes")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "definitions")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "libraries")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "resources")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "providers")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "files", "default")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "templates", "default")}" unless File.exists?(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb")) open(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb"), "w") do |file| file.puts <<-EOH @@ -110,9 +110,9 @@ EOH # 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. @@ -156,7 +156,7 @@ end def create_metadata(dir) raise "Must provide a COOKBOOK=" unless ENV["COOKBOOK"] puts "** Creating metadata for cookbook: #{ENV["COOKBOOK"]}" - + case NEW_COOKBOOK_LICENSE when :apachev2 license = "Apache 2.0" @@ -188,8 +188,8 @@ task :ssl_cert do fqdn =~ /^(.+?)\.(.+)$/ hostname = $1 domain = $2 - keyfile = fqdn.gsub("*", "wildcard") raise "Must provide FQDN!" unless fqdn && hostname && domain + keyfile = fqdn.gsub("*", "wildcard") puts "** Creating self signed SSL Certificate for #{fqdn}" sh("(cd #{CADIR} && openssl genrsa 2048 > #{keyfile}.key)") sh("(cd #{CADIR} && chmod 644 #{keyfile}.key)") @@ -288,7 +288,7 @@ namespace :databag do end end else - puts "ERROR: Could not find any databags, skipping it" + puts "ERROR: Could not find any databags, skipping it" end end @@ -327,7 +327,7 @@ EOH end else puts "ERROR: Could not find your databag (#{databag}), skipping it" - end + end end end diff --git a/lib/chef/util/backup.rb b/lib/chef/util/backup.rb index 95c85d9751..43e3434050 100644 --- a/lib/chef/util/backup.rb +++ b/lib/chef/util/backup.rb @@ -47,7 +47,8 @@ class Chef def backup_filename @backup_filename ||= begin time = Time.now - savetime = time.strftime("%Y%m%d%H%M%S") + nanoseconds = sprintf("%6f", time.to_f).split('.')[1] + savetime = time.strftime("%Y%m%d%H%M%S.#{nanoseconds}") backup_filename = "#{path}.chef-#{savetime}" backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows end diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb index 6f76a2fabd..7bce52d874 100644 --- a/lib/chef/util/diff.rb +++ b/lib/chef/util/diff.rb @@ -14,14 +14,38 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -require 'chef/mixin/shell_out' +# Some portions of this file are derived from material in the diff-lcs +# project licensed under the terms of the MIT license, provided below. +# +# Copyright:: Copyright (c) 2004-2013 Austin Ziegler +# License:: MIT +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of this Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE +# SOFTWARE. + +require 'diff/lcs' +require 'diff/lcs/hunk' class Chef class Util class Diff - include Chef::Mixin::ShellOut - # @todo: to_a, to_s, to_json, inspect defs, accessors for @diff and @error # @todo: move coercion to UTF-8 into to_json # @todo: replace shellout to diff -u with diff-lcs gem @@ -58,6 +82,43 @@ class Chef end end end + + # produces a unified-output-format diff with 3 lines of context + # ChefFS uses udiff() directly + def udiff(old_file, new_file) + diff_str = "" + file_length_difference = 0 + + old_data = IO.readlines(old_file).map { |e| e.chomp } + new_data = IO.readlines(new_file).map { |e| e.chomp } + diff_data = ::Diff::LCS.diff(old_data, new_data) + + return diff_str if old_data.empty? && new_data.empty? + return "No differences encountered\n" if diff_data.empty? + + # write diff header (standard unified format) + ft = File.stat(old_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z') + diff_str << "--- #{old_file}\t#{ft}\n" + ft = File.stat(new_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z') + diff_str << "+++ #{new_file}\t#{ft}\n" + + # loop over diff hunks. if a hunk overlaps with the last hunk, + # join them. otherwise, print out the old one. + old_hunk = hunk = nil + diff_data.each do |piece| + begin + hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference) + file_length_difference = hunk.file_length_difference + next unless old_hunk + next if hunk.merge(old_hunk) + diff_str << old_hunk.diff(:unified) << "\n" + ensure + old_hunk = hunk + end + end + diff_str << old_hunk.diff(:unified) << "\n" + return diff_str + end private @@ -78,13 +139,8 @@ class Chef return "(new content is binary, diff output suppressed)" if is_binary?(new_file) begin - # -u: Unified diff format - # LC_ALL: in ruby 1.9 we want to set nil which is a magic option to mixlib-shellout to - # pass through the LC_ALL locale. in ruby 1.8 we force to 7-bit 'C' locale - # (which is the mixlib-shellout default for all rubies all the time). Chef::Log.debug("running: diff -u #{old_file} #{new_file}") - locale = ( Object.const_defined? :Encoding ) ? nil : 'C' - result = shell_out("diff -u #{old_file} #{new_file}", :env => {'LC_ALL' => locale}) + diff_str = udiff(old_file, new_file) rescue Exception => e # Should *not* receive this, but in some circumstances it seems that @@ -92,34 +148,14 @@ class Chef return "Could not determine diff. Error: #{e.message}" end - # diff will set a non-zero return code even when there's - # valid stdout results, if it encounters something unexpected - # So as long as we have output, we'll show it. - # - # Also on some platforms (Solaris) diff outputs a single line - # when there are no differences found. Look for this line - # before analyzing diff output. - if !result.stdout.empty? && result.stdout != "No differences encountered\n" - if result.stdout.length > diff_output_threshold + if !diff_str.empty? && diff_str != "No differences encountered\n" + if diff_str.length > diff_output_threshold return "(long diff of over #{diff_output_threshold} characters, diff output suppressed)" else - diff_str = result.stdout - if Object.const_defined? :Encoding # ruby >= 1.9 - if ( diff_str.encoding == Encoding::ASCII_8BIT && - diff_str.encoding != Encoding.default_external && - RUBY_VERSION.to_f < 2.0 ) - # @todo mixlib-shellout under ruby 1.9 hands back an ASCII-8BIT encoded string, which needs to - # be fixed to the default external encoding -- this should be moved into mixlib-shellout - diff_str = diff_str.force_encoding(Encoding.default_external) - end - diff_str.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?') - end + diff_str = encode_diff_for_json(diff_str) @diff = diff_str.split("\n") - @diff.delete("\\ No newline at end of file") return "(diff available)" end - elsif !result.stderr.empty? - return "Could not determine diff. Error: #{result.stderr}" else return "(no diff)" end @@ -139,6 +175,13 @@ class Chef end end + def encode_diff_for_json(diff_str) + if Object.const_defined? :Encoding + diff_str.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?') + end + return diff_str + end + end end end diff --git a/lib/chef/util/windows.rb b/lib/chef/util/windows.rb index cba2c2a1b7..777fe4adbb 100644 --- a/lib/chef/util/windows.rb +++ b/lib/chef/util/windows.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb index 9da0dc6557..817e47efa8 100644 --- a/lib/chef/util/windows/net_group.rb +++ b/lib/chef/util/windows/net_group.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb index 1979e095bd..37efc02fcc 100644 --- a/lib/chef/util/windows/net_use.rb +++ b/lib/chef/util/windows/net_use.rb @@ -6,9 +6,9 @@ # 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. diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb index eb68f6cebc..5cca348c8e 100644 --- a/lib/chef/util/windows/net_user.rb +++ b/lib/chef/util/windows/net_user.rb @@ -6,9 +6,9 @@ # 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. @@ -198,7 +198,7 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows def enable_account user_modify do |user| user[:flags] &= ~UF_ACCOUNTDISABLE - #This does not set the password to nil. It (for some reason) means to ignore updating the field. + #This does not set the password to nil. It (for some reason) means to ignore updating the field. #See similar behavior for the logon_hours field documented at #http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx user[:password] = nil diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb index dff082e929..08c3a53793 100644 --- a/lib/chef/util/windows/volume.rb +++ b/lib/chef/util/windows/volume.rb @@ -6,9 +6,9 @@ # 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. @@ -35,7 +35,7 @@ class Chef::Util::Windows::Volume < Chef::Util::Windows name += "\\" unless name =~ /\\$/ #trailing slash required @name = name end - + def device buffer = 0.chr * 256 if GetVolumeNameForVolumeMountPoint(@name, buffer, buffer.size) diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 70a22f7d27..259c267035 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -6,9 +6,9 @@ # 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. @@ -17,7 +17,7 @@ class Chef CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) - VERSION = '11.6.0.hotfix.1' + VERSION = '11.8.0.alpha.0' end # NOTE: the Chef::Version class is defined in version_class.rb diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb index afa746efce..7a8dafd8b5 100644 --- a/lib/chef/win32/api/file.rb +++ b/lib/chef/win32/api/file.rb @@ -474,7 +474,7 @@ BOOL WINAPI DeviceIoControl( # Workaround for CHEF-4419: # Make sure paths starting with "/" has a drive letter # assigned from the current working diretory. - # Note: In chef 11.8 and beyond this issue will be fixed with a + # Note: With CHEF-4427 this issue will be fixed with a # broader fix to map all the paths starting with "/" to # SYSTEM_DRIVE on windows. path = ::File.expand_path(path) if path.start_with? "/" diff --git a/lib/chef/win32/api/synchronization.rb b/lib/chef/win32/api/synchronization.rb new file mode 100644 index 0000000000..9c148d7e2b --- /dev/null +++ b/lib/chef/win32/api/synchronization.rb @@ -0,0 +1,89 @@ +# +# Author:: Serdar Sutay (<serdar@opscode.com>) +# Copyright:: Copyright 2011 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/win32/api' + +class Chef + module ReservedNames::Win32 + module API + module Synchronization + extend Chef::ReservedNames::Win32::API + + ffi_lib 'kernel32' + + # Constant synchronization functions use to indicate wait + # forever. + INFINITE = 0xFFFFFFFF + + # Return codes + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx + WAIT_FAILED = 0xFFFFFFFF + WAIT_TIMEOUT = 0x00000102 + WAIT_OBJECT_0 = 0x00000000 + WAIT_ABANDONED = 0x00000080 + + # Security and access rights for synchronization objects + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms686670(v=vs.85).aspx + DELETE = 0x00010000 + READ_CONTROL = 0x00020000 + SYNCHRONIZE = 0x00100000 + WRITE_DAC = 0x00040000 + WRITE_OWNER = 0x00080000 + + # Mutex specific rights + MUTEX_ALL_ACCESS = 0x001F0001 + MUTEX_MODIFY_STATE = 0x00000001 + +=begin +HANDLE WINAPI CreateMutex( + _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, + _In_ BOOL bInitialOwner, + _In_opt_ LPCTSTR lpName +); +=end + safe_attach_function :CreateMutexW, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE + safe_attach_function :CreateMutexA, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE + +=begin +DWORD WINAPI WaitForSingleObject( + _In_ HANDLE hHandle, + _In_ DWORD dwMilliseconds +); +=end + safe_attach_function :WaitForSingleObject, [ :HANDLE, :DWORD ], :DWORD + +=begin +BOOL WINAPI ReleaseMutex( + _In_ HANDLE hMutex +); +=end + safe_attach_function :ReleaseMutex, [ :HANDLE ], :BOOL + +=begin +HANDLE WINAPI OpenMutex( + _In_ DWORD dwDesiredAccess, + _In_ BOOL bInheritHandle, + _In_ LPCTSTR lpName +); +=end + safe_attach_function :OpenMutexW, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE + safe_attach_function :OpenMutexA, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE + end + end + end +end diff --git a/lib/chef/win32/handle.rb b/lib/chef/win32/handle.rb index 3e92703db9..21a8fdf339 100644 --- a/lib/chef/win32/handle.rb +++ b/lib/chef/win32/handle.rb @@ -29,7 +29,7 @@ class Chef # See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx # The handle value returned by the GetCurrentProcess function is the pseudo handle (HANDLE)-1 (which is 0xFFFFFFFF) CURRENT_PROCESS_HANDLE = 4294967295 - + def initialize(handle) @handle = handle ObjectSpace.define_finalizer(self, Handle.close_handle_finalizer(handle)) diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb new file mode 100644 index 0000000000..b0a9ba210e --- /dev/null +++ b/lib/chef/win32/mutex.rb @@ -0,0 +1,94 @@ +# +# Author:: Serdar Sutay (<serdar@opscode.com>) +# Copyright:: Copyright 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/win32/api/synchronization' + +class Chef + module ReservedNames::Win32 + class Mutex + include Chef::ReservedNames::Win32::API::Synchronization + extend Chef::ReservedNames::Win32::API::Synchronization + + def initialize(name) + @name = name + # First check if there exists a mutex in the system with the + # given name. + + # In the initial creation of the mutex initial_owner is set to + # false so that mutex will not be acquired until someone calls + # acquire. + # In order to call "*W" windows apis, strings needs to be + # encoded as wide strings. + @handle = CreateMutexW(nil, false, name.to_wstring) + + # Fail early if we can't get a handle to the named mutex + if @handle == 0 + Chef::Log.error("Failed to create system mutex with name'#{name}'") + Chef::ReservedNames::Win32::Error.raise! + end + end + + attr_reader :handle + attr_reader :name + + ##################################################### + # Attempts to grab the mutex. + # Returns true if the mutex is grabbed or if it's already + # owned; false otherwise. + def test + WaitForSingleObject(handle, 0) == WAIT_OBJECT_0 + end + + ##################################################### + # Attempts to grab the mutex and waits until it is acquired. + def wait + wait_result = WaitForSingleObject(handle, INFINITE) + case wait_result + when WAIT_ABANDONED + # Previous owner of the mutex died before it can release the + # mutex. Log a warning and continue. + Chef::Log.debug "Existing owner of the mutex exited prematurely." + when WAIT_OBJECT_0 + # Mutex is successfully acquired. + else + Chef::Log.error("Failed to acquire system mutex '#{name}'. Return code: #{wait_result}") + Chef::ReservedNames::Win32::Error.raise! + end + end + + ##################################################### + # Releaes the mutex + def release + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685066(v=vs.85).aspx + # Note that release method needs to be called more than once + # if mutex is acquired more than once. + unless ReleaseMutex(handle) + # Don't fail things in here if we can't release the mutex. + # Because it will be automatically released when the owner + # of the process goes away and this class is only being used + # to synchronize chef-clients runs on a node. + Chef::Log.error("Can not release mutex '#{name}'. This might cause issues \ +if the mutex is attempted to be acquired by other threads.") + Chef::ReservedNames::Win32::Error.raise! + end + end + end + end +end + + diff --git a/lib/chef/win32/security/ace.rb b/lib/chef/win32/security/ace.rb index efd44b1c85..3aeae35532 100644 --- a/lib/chef/win32/security/ace.rb +++ b/lib/chef/win32/security/ace.rb @@ -122,4 +122,4 @@ class Chef end end end -end
\ No newline at end of file +end diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb index 7ca21eee79..e1b20224bb 100644 --- a/lib/chef/win32/security/sid.rb +++ b/lib/chef/win32/security/sid.rb @@ -196,4 +196,4 @@ class Chef end end end -end
\ No newline at end of file +end diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb index c8c923a6f2..62f817503e 100644 --- a/lib/chef/win32/version.rb +++ b/lib/chef/win32/version.rb @@ -30,14 +30,16 @@ class Chef # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx private - + def self.get_system_metrics(n_index) Win32API.new('user32', 'GetSystemMetrics', 'I', 'I').call(n_index) end public - + WIN_VERSIONS = { + "Windows 8.1" => {:major => 6, :minor => 3, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, + "Windows Server 2012 R2" => {:major => 6, :minor => 3, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, "Windows 8" => {:major => 6, :minor => 2, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, "Windows Server 2012" => {:major => 6, :minor => 2, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, "Windows 7" => {:major => 6, :minor => 1, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, @@ -68,7 +70,7 @@ class Chef # The get_product_info API is not supported on Win2k3, # use an alternative to identify datacenter skus @sku = get_datacenter_product_info_windows_server_2003(ver_info) - end + end end marketing_names = Array.new |