diff options
author | Bryan McLellan <btm@opscode.com> | 2013-05-23 08:42:55 -0700 |
---|---|---|
committer | Bryan McLellan <btm@opscode.com> | 2013-05-23 08:42:55 -0700 |
commit | c9f21f2ae307a7956d3a3b8777a52658e246d6b2 (patch) | |
tree | fde9853bd33d5e9d465f9ccfc30a09dd0f4dbadc | |
parent | 84eb717b974d23f02fc677ed642fc00ee98bde62 (diff) | |
parent | 664ee74decc3b069269b4f079a3e7a86613b0af1 (diff) | |
download | chef-c9f21f2ae307a7956d3a3b8777a52658e246d6b2.tar.gz |
Merge branch 'master' into 11-stable
CHEF-1707
CHEF-2467
CHEF-2694
CHEF-3348
CHEF-3364
CHEF-3367
CHEF-3386
CHEF-3432
CHEF-3452
CHEF-3516
CHEF-3615
CHEF-3683
CHEF-3735
CHEF-3772
CHEF-3803
CHEF-3804
CHEF-3819
CHEF-3847
CHEF-3858
CHEF-3872
CHEF-3895
CHEF-3919
CHEF-3920
CHEF-3932
CHEF-3937
CHEF-3938
CHEF-3963
CHEF-3967
CHEF-3987
CHEF-4010
CHEF-4011
CHEF-4065
CHEF-4106
CHEF-4117
CHEF-4118
CHEF-4123
CHEF-4157
CHEF-4176
95 files changed, 2747 insertions, 1137 deletions
@@ -1,4 +1,4 @@ -source :rubygems +source "https://rubygems.org" gemspec diff --git a/distro/debian/etc/init.d/chef-client b/distro/debian/etc/init.d/chef-client index 8f99748f59..c5dfea06fb 100755 --- a/distro/debian/etc/init.d/chef-client +++ b/distro/debian/etc/init.d/chef-client @@ -165,8 +165,10 @@ case "$1" in restart|force-reload) log_daemon_msg "Restarting $DESC" "$NAME" errcode=0 - stop_server || errcode=$? - [ -n "$DIETIME" ] && sleep $DIETIME + if running ; then + stop_server || errcode=$? + [ -n "$DIETIME" ] && sleep $DIETIME + fi start_server || errcode=$? [ -n "$STARTTIME" ] && sleep $STARTTIME running || errcode=$? diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index fca2e9a92d..393ff29834 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -58,8 +58,8 @@ class Chef::Application::Client < Chef::Application option :color, :long => '--[no-]color', :boolean => true, - :default => true, - :description => "Use colored output, defaults to enabled" + :default => !Chef::Platform.windows?, + :description => "Use colored output, defaults to false on Windows, true otherwise" option :log_level, :short => "-l LEVEL", @@ -184,10 +184,10 @@ class Chef::Application::Client < Chef::Application :long => '--why-run', :description => 'Enable whyrun mode', :boolean => true - + option :client_fork, :short => "-f", - :long => "--fork", + :long => "--[no-]fork", :description => "Fork client", :boolean => true @@ -217,9 +217,6 @@ class Chef::Application::Client < Chef::Application super Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url - unless Chef::Config[:exception_handlers].any? {|h| Chef::Handler::ErrorReport === h} - Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new - end if Chef::Config[:daemonize] Chef::Config[:interval] ||= 1800 diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb index 629cd9fc5f..13612a4bf3 100644 --- a/lib/chef/application/knife.rb +++ b/lib/chef/application/knife.rb @@ -43,8 +43,8 @@ class Chef::Application::Knife < Chef::Application option :color, :long => '--[no-]color', :boolean => true, - :default => true, - :description => "Use colored output, defaults to enabled" + :default => !Chef::Platform.windows?, + :description => "Use colored output, defaults to false on Windows, true otherwise" option :environment, :short => "-E ENVIRONMENT", diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb index d9b6c3d802..b2fe91baed 100644 --- a/lib/chef/application/solo.rb +++ b/lib/chef/application/solo.rb @@ -147,13 +147,13 @@ class Chef::Application::Solo < Chef::Application Chef::RunList::RunListItem.new(item) } } - + option :client_fork, :short => "-f", - :long => "--fork", + :long => "--[no-]fork", :description => "Fork client", :boolean => true - + option :why_run, :short => '-W', :long => '--why-run', diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 010f08acac..24f4448053 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -25,7 +25,7 @@ require 'chef/log' require 'chef/rest' require 'chef/api_client' require 'chef/api_client/registration' -require 'chef/platform' +require 'chef/platform/query_helpers' require 'chef/node' require 'chef/role' require 'chef/file_cache' @@ -103,6 +103,7 @@ class Chef self.class.run_start_notifications.each do |notification| notification.call(run_status) end + @events.run_started(run_status) end # Callback to fire notifications that the run completed successfully @@ -444,18 +445,18 @@ class Chef def do_run runlock = RunLock.new(Chef::Config) runlock.acquire + # don't add code that may fail before entering this section to be sure to release lock + begin + run_context = nil + @events.run_start(Chef::VERSION) + Chef::Log.info("*** Chef #{Chef::VERSION} ***") + enforce_path_sanity + run_ohai + @events.ohai_completed(node) + register unless Chef::Config[:solo] - run_context = nil - @events.run_start(Chef::VERSION) - Chef::Log.info("*** Chef #{Chef::VERSION} ***") - enforce_path_sanity - run_ohai - @events.ohai_completed(node) - register unless Chef::Config[:solo] - - load_node + load_node - begin build_node run_status.start_clock diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 8846aa92ac..562bc54888 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -74,14 +74,7 @@ class Chef config_attr_writer :chef_server_url do |url| url = url.strip configure do |c| - [ :registration_url, - :template_url, - :remotefile_url, - :search_url, - :chef_server_url, - :role_url ].each do |u| - c[u] = url - end + c[:chef_server_url] = url end url end @@ -116,26 +109,12 @@ class Chef end end - # Override the config dispatch to set the value of authorized_openid_providers when openid_providers (deprecated) is used - # - # === Parameters - # providers<Array>:: An array of openid providers that are authorized to login to the chef server - # - config_attr_writer :openid_providers do |providers| - configure { |c| c[:authorized_openid_providers] = providers } - providers - end - # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity enforce_path_sanity(true) # Formatted Chef Client output is a beta feature, disabled by default: formatter "null" - # Used when OpenID authentication is enabled in the Web UI - authorized_openid_identifiers nil - authorized_openid_providers nil - # The number of times the client should retry when registering with the server client_registration_retries 5 @@ -147,9 +126,6 @@ class Chef # An array of paths to search for knife exec scripts if they aren't in the current directory script_path [] - # Where files are stored temporarily during uploads - sandbox_path "/var/chef/sandboxes" - # Where cookbook files are stored on the server (by content checksum) checksum_path "/var/chef/checksums" @@ -174,7 +150,6 @@ class Chef group nil umask 0022 - # Valid log_levels are: # * :debug # * :info @@ -202,30 +177,20 @@ class Chef # toggle info level log items that can create a lot of output verbose_logging true node_name nil - node_path "/var/chef/node" diff_disabled false diff_filesize_threshold 10000000 diff_output_threshold 1000000 pid_file nil - chef_server_url "http://localhost:4000" - registration_url "http://localhost:4000" - template_url "http://localhost:4000" - role_url "http://localhost:4000" - remotefile_url "http://localhost:4000" - search_url "http://localhost:4000" - - client_url "http://localhost:4042" + chef_server_url "https://localhost:443" rest_timeout 300 - run_command_stderr_timeout 120 - run_command_stdout_timeout 120 solo false splay nil why_run false color false - client_fork false + client_fork true enable_reporting true enable_reporting_url_fatals false @@ -237,7 +202,6 @@ class Chef ssl_ca_path nil ssl_ca_file nil - # Where should chef-solo look for role files? role_path platform_specific_path("/var/chef/roles") @@ -270,6 +234,32 @@ class Chef # `node_name` of the client. client_key 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) + end + + # As of Chef 11.0, version "1" is the default encrypted data bag item + # format. Version "2" is available which adds encrypt-then-mac protection. + # 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 + + # When reading data bag items, any supported version is accepted. However, + # if all encrypted data bags have been generated with the version 2 format, + # it is recommended to disable support for earlier formats to improve + # 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 + # 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 # `client_key` is not present and the `validation_key` is also not present, @@ -278,24 +268,12 @@ class Chef # 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" - web_ui_client_name "chef-webui" - web_ui_key "/etc/chef/webui.pem" - web_ui_admin_user_name "admin" - web_ui_admin_default_password "p@ssw0rd1" - # Server Signing CA - # - # In truth, these don't even have to change - signing_ca_cert "/var/chef/ca/cert.pem" - signing_ca_key "/var/chef/ca/key.pem" - signing_ca_user nil - signing_ca_group nil - signing_ca_country "US" - signing_ca_state "Washington" - signing_ca_location "Seattle" - signing_ca_org "Chef User" - signing_ca_domain "opensource.opscode.com" - signing_ca_email "opensource-cert@opscode.com" + # 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 # Report Handlers report_handlers [] diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb index 69be03731e..9ba5b2bd8b 100644 --- a/lib/chef/cookbook_uploader.rb +++ b/lib/chef/cookbook_uploader.rb @@ -110,7 +110,16 @@ class Chef # files are uploaded, so save the manifest cookbooks.each do |cb| save_url = opts[:force] ? cb.force_save_url : cb.save_url - rest.put_rest(save_url, cb) + begin + rest.put_rest(save_url, cb) + rescue Net::HTTPServerException => e + case e.response.code + when "409" + raise Chef::Exceptions::CookbookFrozen, "Version #{cb.version} of cookbook #{cb.name} is frozen. Use --force to override." + else + raise + end + end end Chef::Log.info("Upload complete!") diff --git a/lib/chef/daemon.rb b/lib/chef/daemon.rb index bb5ccf753a..9a3d5a884a 100644 --- a/lib/chef/daemon.rb +++ b/lib/chef/daemon.rb @@ -74,6 +74,24 @@ class Chef 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 + end + end + # Gets the pid file for @name # ==== Returns # String:: @@ -112,12 +130,14 @@ class Chef 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 - FileUtils.rm(pid_file) if File.exists?(pid_file) - end - + 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 # def change_privilege diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb index 79e6019bdc..452a8224df 100644 --- a/lib/chef/encrypted_data_bag_item.rb +++ b/lib/chef/encrypted_data_bag_item.rb @@ -48,9 +48,11 @@ require 'open-uri' # such nodes in the infrastructure. # class Chef::EncryptedDataBagItem - DEFAULT_SECRET_FILE = "/etc/chef/encrypted_data_bag_secret" ALGORITHM = 'aes-256-cbc' + class UnacceptableEncryptedDataBagItemFormat < StandardError + end + class UnsupportedEncryptedDataBagItemFormat < StandardError end @@ -62,73 +64,117 @@ class Chef::EncryptedDataBagItem # Implementation class for converting plaintext data bag item values to an # encrypted value, including any necessary wrappers and metadata. - class Encryptor - - attr_reader :key - attr_reader :plaintext_data + module Encryptor - # Create a new Encryptor for +data+, which will be encrypted with the given - # +key+. + # "factory" method that creates an encryptor object with the proper class + # for the desired encrypted data bag format version. # - # === Arguments: - # * data: An object of any type that can be serialized to json - # * key: A String representing the desired passphrase - # * iv: The optional +iv+ parameter is intended for testing use only. When - # *not* supplied, Encryptor will use OpenSSL to generate a secure random - # IV, which is what you want. - def initialize(plaintext_data, key, iv=nil) - @plaintext_data = plaintext_data - @key = key - @iv = iv && Base64.decode64(iv) + # +Chef::Config[:data_bag_encrypt_version]+ determines which version is used. + def self.new(value, secret, iv=nil) + format_version = Chef::Config[:data_bag_encrypt_version] + case format_version + when 1 + Version1Encryptor.new(value, secret, iv) + when 2 + Version2Encryptor.new(value, secret, iv) + else + raise UnsupportedEncryptedDataBagItemFormat, + "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2'" + end end - # Returns a wrapped and encrypted version of +plaintext_data+ suitable for - # using as the value in an encrypted data bag item. - def for_encrypted_item - { - "encrypted_data" => encrypted_data, - "iv" => Base64.encode64(iv), - "version" => 1, - "cipher" => ALGORITHM - } - end + class Version1Encryptor + attr_reader :key + attr_reader :plaintext_data + + # Create a new Encryptor for +data+, which will be encrypted with the given + # +key+. + # + # === Arguments: + # * data: An object of any type that can be serialized to json + # * key: A String representing the desired passphrase + # * iv: The optional +iv+ parameter is intended for testing use only. When + # *not* supplied, Encryptor will use OpenSSL to generate a secure random + # IV, which is what you want. + def initialize(plaintext_data, key, iv=nil) + @plaintext_data = plaintext_data + @key = key + @iv = iv && Base64.decode64(iv) + end - # Generates or returns the IV. - def iv - # Generated IV comes from OpenSSL::Cipher::Cipher#random_iv - # This gets generated when +openssl_encryptor+ gets created. - openssl_encryptor if @iv.nil? - @iv - end + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for + # using as the value in an encrypted data bag item. + def for_encrypted_item + { + "encrypted_data" => encrypted_data, + "iv" => Base64.encode64(iv), + "version" => 1, + "cipher" => ALGORITHM + } + end - # Generates (and memoizes) an OpenSSL::Cipher::Cipher object and configures - # it for the specified iv and encryption key. - def openssl_encryptor - @openssl_encryptor ||= begin - encryptor = OpenSSL::Cipher::Cipher.new(ALGORITHM) - encryptor.encrypt - @iv ||= encryptor.random_iv - encryptor.iv = @iv - encryptor.key = Digest::SHA256.digest(key) - encryptor + # Generates or returns the IV. + def iv + # Generated IV comes from OpenSSL::Cipher::Cipher#random_iv + # This gets generated when +openssl_encryptor+ gets created. + openssl_encryptor if @iv.nil? + @iv + end + + # Generates (and memoizes) an OpenSSL::Cipher::Cipher object and configures + # it for the specified iv and encryption key. + def openssl_encryptor + @openssl_encryptor ||= begin + encryptor = OpenSSL::Cipher::Cipher.new(ALGORITHM) + encryptor.encrypt + @iv ||= encryptor.random_iv + encryptor.iv = @iv + encryptor.key = Digest::SHA256.digest(key) + encryptor + end + end + + # Encrypts and Base64 encodes +serialized_data+ + def encrypted_data + @encrypted_data ||= begin + enc_data = openssl_encryptor.update(serialized_data) + enc_data << openssl_encryptor.final + Base64.encode64(enc_data) + end end - end - # Encrypts and Base64 encodes +serialized_data+ - def encrypted_data - @encrypted_data ||= begin - enc_data = openssl_encryptor.update(serialized_data) - enc_data << openssl_encryptor.final - Base64.encode64(enc_data) + # Wraps the data in a single key Hash (JSON Object) and converts to JSON. + # The wrapper is required because we accept values (such as Integers or + # Strings) that do not produce valid JSON when serialized without the + # wrapper. + def serialized_data + Yajl::Encoder.encode(:json_wrapper => plaintext_data) end end - # Wraps the data in a single key Hash (JSON Object) and converts to JSON. - # The wrapper is required because we accept values (such as Integers or - # Strings) that do not produce valid JSON when serialized without the - # wrapper. - def serialized_data - Yajl::Encoder.encode(:json_wrapper => plaintext_data) + class Version2Encryptor < Version1Encryptor + + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for + # using as the value in an encrypted data bag item. + def for_encrypted_item + { + "encrypted_data" => encrypted_data, + "hmac" => hmac, + "iv" => Base64.encode64(iv), + "version" => 2, + "cipher" => ALGORITHM + } + end + + # Generates an HMAC-SHA2-256 of the encrypted data (encrypt-then-mac) + def hmac + @hmac ||= begin + digest = OpenSSL::Digest::Digest.new("sha256") + raw_hmac = OpenSSL::HMAC.digest(digest, key, encrypted_data) + Base64.encode64(raw_hmac) + end + end + end end @@ -145,7 +191,11 @@ class Chef::EncryptedDataBagItem # decryptor object for that version. Call #for_decrypted_item on the # resulting object to decrypt and deserialize it. def self.for(encrypted_value, key) - case format_version_of(encrypted_value) + format_version = format_version_of(encrypted_value) + assert_format_version_acceptable!(format_version) + case format_version + when 2 + Version2Decryptor.new(encrypted_value, key) when 1 Version1Decryptor.new(encrypted_value, key) when 0 @@ -164,6 +214,14 @@ class Chef::EncryptedDataBagItem end end + def self.assert_format_version_acceptable!(format_version) + unless format_version.kind_of?(Integer) and format_version >= Chef::Config[:data_bag_decrypt_minimum_version] + raise UnacceptableEncryptedDataBagItemFormat, + "The encrypted data bag item has format version `#{format_version}', " + + "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'" + end + end + class Version1Decryptor attr_reader :encrypted_data @@ -176,9 +234,13 @@ class Chef::EncryptedDataBagItem def for_decrypted_item Yajl::Parser.parse(decrypted_data)["json_wrapper"] + rescue Yajl::ParseError + # convert to a DecryptionFailure error because the most likely scenario + # here is that the decryption step was unsuccessful but returned bad + # data rather than raising an error. + raise DecryptionFailure, "Error decrypting data bag value. Most likely the provided key is incorrect" end - def encrypted_bytes Base64.decode64(@encrypted_data["encrypted_data"]) end @@ -219,6 +281,36 @@ class Chef::EncryptedDataBagItem end + class Version2Decryptor < Version1Decryptor + + def decrypted_data + validate_hmac! unless @decrypted_data + super + end + + def validate_hmac! + digest = OpenSSL::Digest::Digest.new("sha256") + raw_hmac = OpenSSL::HMAC.digest(digest, key, @encrypted_data["encrypted_data"]) + + if candidate_hmac_matches?(raw_hmac) + true + else + raise DecryptionFailure, "Error decrypting data bag value: invalid hmac. Most likely the provided key is incorrect" + end + end + + private + + def candidate_hmac_matches?(expected_hmac) + return false unless @encrypted_data["hmac"] + expected_bytes = expected_hmac.bytes.to_a + candidate_hmac_bytes = Base64.decode64(@encrypted_data["hmac"]).bytes.to_a + valid = expected_bytes.size ^ candidate_hmac_bytes.size + expected_bytes.zip(candidate_hmac_bytes) { |x, y| valid |= x ^ y.to_i } + valid == 0 + end + end + class Version0Decryptor attr_reader :encrypted_data @@ -297,7 +389,7 @@ class Chef::EncryptedDataBagItem end def self.load_secret(path=nil) - path = path || Chef::Config[:encrypted_data_bag_secret] || DEFAULT_SECRET_FILE + path ||= Chef::Config[:encrypted_data_bag_secret] secret = case path when /^\w+:\/\// # We have a remote key @@ -309,7 +401,7 @@ class Chef::EncryptedDataBagItem raise ArgumentError, "Remote key not found at '#{path}'" end else - if !File.exists?(path) + if !File.exist?(path) raise Errno::ENOENT, "file not found '#{path}'" end IO.read(path).strip diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index 232bf7f1b4..494d3cee79 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -32,6 +32,9 @@ class Chef def run_start(version) end + def run_started(run_status) + end + # Called at the end a successful Chef run. def run_completed(node) end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 40dec4dc2a..8d6eac7839 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -107,6 +107,7 @@ class Chef class CookbookVersionConflict < ArgumentError ; end # does not follow X.Y.Z format. ArgumentError? + class InvalidPlatformVersion < ArgumentError; end class InvalidCookbookVersion < ArgumentError; end # version constraint should be a string or array, or it doesn't diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 7812fd232f..74507a4319 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -201,12 +201,18 @@ class Chef subcommand_class || subcommand_not_found!(args) end + def self.dependency_loaders + @dependency_loaders ||= [] + end + def self.deps(&block) - @dependency_loader = block + dependency_loaders << block end def self.load_deps - @dependency_loader && @dependency_loader.call + dependency_loaders.each do |dep_loader| + dep_loader.call + end end private diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 53b31ad676..a05f48dfc9 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -94,6 +94,11 @@ class Chef :description => "Execute the bootstrap via sudo", :boolean => true + option :use_sudo_password, + :long => "--use-sudo-password", + :description => "Execute the bootstrap via sudo with password", + :boolean => false + option :template_file, :long => "--template-file TEMPLATE", :description => "Full path to location of template to use", @@ -127,6 +132,15 @@ class Chef name, path = h.split("=") Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new } + option :secret, + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values" + + option :secret_file, + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values" + def find_template(template=nil) # Are we bootstrapping using an already shipped template? if config[:template_file] @@ -166,11 +180,12 @@ class Chef def run validate_name_args! + warn_chef_config_secret_key @template_file = find_template(config[:bootstrap_template]) @node_name = Array(@name_args).first # back compat--templates may use this setting: config[:server_name] = @node_name - + $stdout.sync = true ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}") @@ -222,12 +237,34 @@ class Chef command = render_template(read_template) if config[:use_sudo] - command = "sudo #{command}" + command = config[:use_sudo_password] ? "echo #{config[:ssh_password]} | sudo -S #{command}" : "sudo #{command}" end command end + def warn_chef_config_secret_key + unless Chef::Config[:encrypted_data_bag_secret].nil? + ui.warn "* " * 40 + ui.warn(<<-WARNING) +Specifying the encrypted data bag secret key using an 'encrypted_data_bag_secret' +entry in 'knife.rb' is deprecated. Please see CHEF-4011 for more details. You +can supress this warning and still distribute the secret key to all bootstrapped +machines by adding the following to your 'knife.rb' file: + + knife[:secret_file] = "/path/to/your/secret" + +If you would like to selectively distribute a secret key during bootstrap +please use the '--secret' or '--secret-file' options of this command instead. + +#{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this +behavior will be removed and any 'encrypted_data_bag_secret' entries in +'knife.rb' will be ignored completely. +WARNING + ui.warn "* " * 40 + end + end + end end end diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb index 4b9cd07c8f..509c469f41 100644 --- a/lib/chef/knife/bootstrap/archlinux-gems.erb +++ b/lib/chef/knife/bootstrap/archlinux-gems.erb @@ -16,7 +16,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/bootstrap/centos5-gems.erb b/lib/chef/knife/bootstrap/centos5-gems.erb index 5db3c1a0e8..310e8eb0e1 100644 --- a/lib/chef/knife/bootstrap/centos5-gems.erb +++ b/lib/chef/knife/bootstrap/centos5-gems.erb @@ -33,7 +33,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb index b7d73b3442..356a7e2b52 100644 --- a/lib/chef/knife/bootstrap/chef-full.erb +++ b/lib/chef/knife/bootstrap/chef-full.erb @@ -31,7 +31,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/bootstrap/fedora13-gems.erb b/lib/chef/knife/bootstrap/fedora13-gems.erb index e555c0652a..c4073aef55 100644 --- a/lib/chef/knife/bootstrap/fedora13-gems.erb +++ b/lib/chef/knife/bootstrap/fedora13-gems.erb @@ -15,7 +15,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb index 93ec208ac3..60978f4484 100644 --- a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +++ b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb @@ -15,7 +15,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb index f5ecc5f7b9..864d790f9c 100644 --- a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +++ b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb @@ -19,7 +19,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb index 7d9549a125..2b1e06fc13 100644 --- a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb +++ b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb @@ -17,7 +17,7 @@ awk NF > /etc/chef/validation.pem <<'EOP' EOP chmod 0600 /etc/chef/validation.pem -<% if @chef_config[:encrypted_data_bag_secret] -%> +<% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb index eaf42544c0..8e725952b4 100644 --- a/lib/chef/knife/configure.rb +++ b/lib/chef/knife/configure.rb @@ -133,17 +133,17 @@ EOH def ask_user_for_config server_name = guess_servername - @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "http://#{server_name}:4000") + @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}:443") if config[:initial] @new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", :default => Etc.getlogin) @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => 'admin') - @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => '/etc/chef/admin.pem') + @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => '/etc/chef-server/admin.pem') @admin_client_key = File.expand_path(@admin_client_key) else @new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", :default => Etc.getlogin) end @validation_client_name = config[:validation_client_name] || ask_question("Please enter the validation clientname: ", :default => 'chef-validator') - @validation_key = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => '/etc/chef/validation.pem') + @validation_key = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => '/etc/chef-server/chef-validator.pem') @validation_key = File.expand_path(@validation_key) @chef_repo = config[:repository] || ask_question("Please enter the path to a chef repository (or leave blank): ") diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index 1e6797e4e3..4e6b8d0c1f 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -185,13 +185,14 @@ EOH unless File.exists?(File.join(dir,cookbook_name,'CHANGELOG.md')) open(File.join(dir, cookbook_name, 'CHANGELOG.md'),'w') do |file| file.puts <<-EOH -# CHANGELOG for #{cookbook_name} +#{cookbook_name} CHANGELOG +#{'='*"#{cookbook_name} CHANGELOG".length} -This file is used to list changes made in each version of #{cookbook_name}. +This file is used to list changes made in each version of the #{cookbook_name} cookbook. -## 0.1.0: - -* Initial release of #{cookbook_name} +0.1.0 +----- +- [your_name] - Initial release of #{cookbook_name} - - - Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb index f50296509b..d4a66e6925 100644 --- a/lib/chef/knife/cookbook_upload.rb +++ b/lib/chef/knife/cookbook_upload.rb @@ -19,6 +19,7 @@ # require 'chef/knife' +require 'chef/cookbook_uploader' class Chef class Knife @@ -228,15 +229,9 @@ WARNING check_for_dependencies!(cb) end Chef::CookbookUploader.new(cookbooks, config[:cookbook_path], :force => config[:force]).upload_cookbooks - rescue Net::HTTPServerException => e - case e.response.code - when "409" - ui.error "Version #{cookbook.version} of cookbook #{cookbook.name} is frozen. Use --force to override." - Log.debug(e) - raise Exceptions::CookbookFrozen - else - raise - end + rescue Chef::Exceptions::CookbookFrozen => e + ui.error e + raise end def check_for_broken_links!(cookbook) diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 0d3ff36867..248d7c7a64 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -25,7 +25,7 @@ class Chef # following instance variables: # * @config - a hash of knife's config values # * @run_list - the run list for the node to boostrap - # + # class BootstrapContext def initialize(config, run_list, chef_config) @@ -51,7 +51,13 @@ class Chef end def encrypted_data_bag_secret - IO.read(File.expand_path(@chef_config[:encrypted_data_bag_secret])) + @config[:secret] || begin + if @config[:secret_file] && File.exist?(@config[:secret_file]) + IO.read(File.expand_path(@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 + end end def config_content @@ -72,7 +78,7 @@ CONFIG client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n} end - if @chef_config[:encrypted_data_bag_secret] + if encrypted_data_bag_secret client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n} end @@ -94,7 +100,7 @@ CONFIG def chef_version knife_config[:bootstrap_version] || Chef::VERSION end - + def first_boot (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list) end diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb index 85e9612315..f18d30a039 100644 --- a/lib/chef/knife/core/ui.rb +++ b/lib/chef/knife/core/ui.rb @@ -19,7 +19,7 @@ # require 'forwardable' -require 'chef/platform' +require 'chef/platform/query_helpers' require 'chef/knife/core/generic_presenter' class Chef @@ -64,14 +64,24 @@ class Chef # Prints a message to stdout. Aliased as +info+ for compatibility with # the logger API. def msg(message) - stdout.puts message + begin + stdout.puts message + rescue Errno::EPIPE => e + raise e if @config[:verbosity] >= 2 + exit 0 + end end alias :info :msg # Prints a msg to stderr. Used for warn, error, and fatal. def err(message) - stderr.puts message + begin + stderr.puts message + rescue Errno::EPIPE => e + raise e if @config[:verbosity] >= 2 + exit 0 + end end # Print a warning message @@ -143,7 +153,12 @@ class Chef end def pretty_print(data) - stdout.puts data + begin + stdout.puts data + rescue Errno::EPIPE => e + raise e if @config[:verbosity] >= 2 + exit 0 + end end def edit_data(data, parse_output=true) diff --git a/lib/chef/mixin/language_include_recipe.rb b/lib/chef/mixin/language_include_recipe.rb index 861f2dee65..b534b6628f 100644 --- a/lib/chef/mixin/language_include_recipe.rb +++ b/lib/chef/mixin/language_include_recipe.rb @@ -23,7 +23,7 @@ class Chef module Mixin deprecate_constant(:LanguageIncludeRecipe, Chef::DSL::IncludeRecipe, <<-EOM) -Chef::Mixin::LanguageIncludeRecipe is deprecated, use Chef::DSL::Recipe +Chef::Mixin::LanguageIncludeRecipe is deprecated, use Chef::DSL::IncludeRecipe instead. EOM diff --git a/lib/chef/node.rb b/lib/chef/node.rb index b301045b28..6bd2226ac8 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -505,5 +505,9 @@ class Chef "node[#{name}]" end + def <=>(other_node) + self.name <=> other_node.name + end + end end diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb index b176e9c731..8f494731ab 100644 --- a/lib/chef/platform.rb +++ b/lib/chef/platform.rb @@ -16,508 +16,14 @@ # limitations under the License. # -require 'chef/config' -require 'chef/log' -require 'chef/mixin/params_validate' - -# This file depends on nearly every provider in chef, but requiring them -# directly causes circular requires resulting in uninitialized constant errors. -require 'chef/provider' -require 'chef/provider/log' -require 'chef/provider/user' -require 'chef/provider/group' -require 'chef/provider/mount' -require 'chef/provider/service' -require 'chef/provider/package' - +require 'chef/platform/provider_mapping' +require 'chef/platform/query_helpers' class Chef class Platform - class << self - attr_writer :platforms - - def platforms - @platforms ||= { - :mac_os_x => { - :default => { - :package => Chef::Provider::Package::Macports, - :service => Chef::Provider::Service::Macosx, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl - } - }, - :mac_os_x_server => { - :default => { - :package => Chef::Provider::Package::Macports, - :service => Chef::Provider::Service::Macosx, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl - } - }, - :freebsd => { - :default => { - :group => Chef::Provider::Group::Pw, - :package => Chef::Provider::Package::Freebsd, - :service => Chef::Provider::Service::Freebsd, - :user => Chef::Provider::User::Pw, - :cron => Chef::Provider::Cron - } - }, - :ubuntu => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :gcel => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :linaro => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :raspbian => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :linuxmint => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Upstart, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :debian => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - }, - "6.0" => { - :service => Chef::Provider::Service::Insserv - } - }, - :xenserver => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :centos => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :amazon => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :scientific => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :fedora => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :suse => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Zypper, - :group => Chef::Provider::Group::Suse - } - }, - :oracle => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :redhat => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :gentoo => { - :default => { - :package => Chef::Provider::Package::Portage, - :service => Chef::Provider::Service::Gentoo, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :arch => { - :default => { - :package => Chef::Provider::Package::Pacman, - :service => Chef::Provider::Service::Arch, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :mswin => { - :default => { - :env => Chef::Provider::Env::Windows, - :service => Chef::Provider::Service::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows - } - }, - :mingw32 => { - :default => { - :env => Chef::Provider::Env::Windows, - :service => Chef::Provider::Service::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows - } - }, - :windows => { - :default => { - :env => Chef::Provider::Env::Windows, - :service => Chef::Provider::Service::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows - } - }, - :solaris => {}, - :openindiana => { - :default => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - } - }, - :opensolaris => { - :default => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - } - }, - :nexentacore => { - :default => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Solaris, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - } - }, - :omnios => { - :default => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - } - }, - :solaris2 => { - :default => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - }, - "5.9" => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Solaris, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - }, - "5.10" => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::Solaris, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - } - }, - :smartos => { - :default => { - :service => Chef::Provider::Service::Solaris, - :package => Chef::Provider::Package::SmartOS, - :cron => Chef::Provider::Cron::Solaris, - :group => Chef::Provider::Group::Usermod - } - }, - :netbsd => { - :default => { - :service => Chef::Provider::Service::Freebsd, - :group => Chef::Provider::Group::Groupmod - } - }, - :openbsd => { - :default => { - :group => Chef::Provider::Group::Usermod - } - }, - :hpux => { - :default => { - :group => Chef::Provider::Group::Usermod - } - }, - :aix => { - :default => { - :group => Chef::Provider::Group::Aix - } - }, - :default => { - :file => Chef::Provider::File, - :directory => Chef::Provider::Directory, - :link => Chef::Provider::Link, - :template => Chef::Provider::Template, - :remote_directory => Chef::Provider::RemoteDirectory, - :execute => Chef::Provider::Execute, - :mount => Chef::Provider::Mount::Mount, - :script => Chef::Provider::Script, - :service => Chef::Provider::Service::Init, - :perl => Chef::Provider::Script, - :python => Chef::Provider::Script, - :ruby => Chef::Provider::Script, - :bash => Chef::Provider::Script, - :csh => Chef::Provider::Script, - :user => Chef::Provider::User::Useradd, - :group => Chef::Provider::Group::Gpasswd, - :http_request => Chef::Provider::HttpRequest, - :route => Chef::Provider::Route, - :ifconfig => Chef::Provider::Ifconfig, - :ruby_block => Chef::Provider::RubyBlock, - :erl_call => Chef::Provider::ErlCall, - :log => Chef::Provider::Log::ChefLog - } - } - end - - include Chef::Mixin::ParamsValidate - - def find(name, version) - provider_map = platforms[:default].clone - - name_sym = name - if name.kind_of?(String) - name.downcase! - name.gsub!(/\s/, "_") - name_sym = name.to_sym - end - - if platforms.has_key?(name_sym) - if platforms[name_sym].has_key?(version) - Chef::Log.debug("Platform #{name.to_s} version #{version} found") - if platforms[name_sym].has_key?(:default) - provider_map.merge!(platforms[name_sym][:default]) - end - provider_map.merge!(platforms[name_sym][version]) - elsif platforms[name_sym].has_key?(:default) - provider_map.merge!(platforms[name_sym][:default]) - end - else - Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)") - end - provider_map - end - - def find_platform_and_version(node) - platform = nil - version = nil - - if node[:platform] - platform = node[:platform] - elsif node.attribute?("os") - platform = node[:os] - end - - raise ArgumentError, "Cannot find a platform for #{node}" unless platform - - if node[:platform_version] - version = node[:platform_version] - elsif node[:os_version] - version = node[:os_version] - elsif node[:os_release] - version = node[:os_release] - end - - raise ArgumentError, "Cannot find a version for #{node}" unless version - - return platform, version - end - - def provider_for_resource(resource, action=:nothing) - node = resource.run_context && resource.run_context.node - raise ArgumentError, "Cannot find the provider for a resource with no run context set" unless node - provider = find_provider_for_node(node, resource).new(resource, resource.run_context) - provider.action = action - provider - end - - def provider_for_node(node, resource_type) - raise NotImplementedError, "#{self.class.name} no longer supports #provider_for_node" - find_provider_for_node(node, resource_type).new(node, resource_type) - end - - def find_provider_for_node(node, resource_type) - platform, version = find_platform_and_version(node) - provider = find_provider(platform, version, resource_type) - end - - def set(args) - validate( - args, - { - :platform => { - :kind_of => Symbol, - :required => false, - }, - :version => { - :kind_of => String, - :required => false, - }, - :resource => { - :kind_of => Symbol, - }, - :provider => { - :kind_of => [ String, Symbol, Class ], - } - } - ) - if args.has_key?(:platform) - if args.has_key?(:version) - if platforms.has_key?(args[:platform]) - if platforms[args[:platform]].has_key?(args[:version]) - platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] - else - platforms[args[:platform]][args[:version]] = { - args[:resource].to_sym => args[:provider] - } - end - else - platforms[args[:platform]] = { - args[:version] => { - args[:resource].to_sym => args[:provider] - } - } - end - else - if platforms.has_key?(args[:platform]) - if platforms[args[:platform]].has_key?(:default) - platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] - else - platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } - end - else - platforms[args[:platform]] = { - :default => { - args[:resource].to_sym => args[:provider] - } - } - end - end - else - if platforms.has_key?(:default) - platforms[:default][args[:resource].to_sym] = args[:provider] - else - platforms[:default] = { - args[:resource].to_sym => args[:provider] - } - end - end - end - - def find_provider(platform, version, resource_type) - pmap = Chef::Platform.find(platform, version) - provider_klass = explicit_provider(platform, version, resource_type) || - platform_provider(platform, version, resource_type) || - resource_matching_provider(platform, version, resource_type) - - raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil? - - provider_klass - end - - def windows? - if RUBY_PLATFORM =~ /mswin|mingw|windows/ - true - else - false - end - end - - def windows_server_2003? - return false unless windows? - - require 'ruby-wmi' - - host = WMI::Win32_OperatingSystem.find(:first) - (host.version && host.version.start_with?("5.2")) - end - - private - - def explicit_provider(platform, version, resource_type) - resource_type.kind_of?(Chef::Resource) ? resource_type.provider : nil - end - - def platform_provider(platform, version, resource_type) - pmap = Chef::Platform.find(platform, version) - rtkey = resource_type.kind_of?(Chef::Resource) ? resource_type.resource_name.to_sym : resource_type - pmap.has_key?(rtkey) ? pmap[rtkey] : nil - end - - def resource_matching_provider(platform, version, resource_type) - if resource_type.kind_of?(Chef::Resource) - begin - Chef::Provider.const_get(resource_type.class.to_s.split('::').last) - rescue NameError - nil - end - else - nil - end - end - - end + # Functionality for this class is defined in chef/platform/provider_mapping + # and chef/platform/query_helpers end end diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb new file mode 100644 index 0000000000..fb3530fab7 --- /dev/null +++ b/lib/chef/platform/provider_mapping.rb @@ -0,0 +1,516 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/config' +require 'chef/log' +require 'chef/mixin/params_validate' +require 'chef/version_constraint/platform' + +# This file depends on nearly every provider in chef, but requiring them +# directly causes circular requires resulting in uninitialized constant errors. +require 'chef/provider' +require 'chef/provider/log' +require 'chef/provider/user' +require 'chef/provider/group' +require 'chef/provider/mount' +require 'chef/provider/service' +require 'chef/provider/package' + + +class Chef + class Platform + + class << self + attr_writer :platforms + + def platforms + @platforms ||= { + :mac_os_x => { + :default => { + :package => Chef::Provider::Package::Macports, + :service => Chef::Provider::Service::Macosx, + :user => Chef::Provider::User::Dscl, + :group => Chef::Provider::Group::Dscl + } + }, + :mac_os_x_server => { + :default => { + :package => Chef::Provider::Package::Macports, + :service => Chef::Provider::Service::Macosx, + :user => Chef::Provider::User::Dscl, + :group => Chef::Provider::Group::Dscl + } + }, + :freebsd => { + :default => { + :group => Chef::Provider::Group::Pw, + :package => Chef::Provider::Package::Freebsd, + :service => Chef::Provider::Service::Freebsd, + :user => Chef::Provider::User::Pw, + :cron => Chef::Provider::Cron + } + }, + :ubuntu => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :gcel => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :linaro => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :raspbian => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :linuxmint => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Upstart, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :debian => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + }, + ">= 6.0" => { + :service => Chef::Provider::Service::Insserv + } + }, + :xenserver => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :xcp => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :centos => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :amazon => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :scientific => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :fedora => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :suse => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Zypper, + :group => Chef::Provider::Group::Suse + } + }, + :oracle => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :redhat => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :gentoo => { + :default => { + :package => Chef::Provider::Package::Portage, + :service => Chef::Provider::Service::Gentoo, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :arch => { + :default => { + :package => Chef::Provider::Package::Pacman, + :service => Chef::Provider::Service::Arch, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :mswin => { + :default => { + :env => Chef::Provider::Env::Windows, + :service => Chef::Provider::Service::Windows, + :user => Chef::Provider::User::Windows, + :group => Chef::Provider::Group::Windows, + :mount => Chef::Provider::Mount::Windows + } + }, + :mingw32 => { + :default => { + :env => Chef::Provider::Env::Windows, + :service => Chef::Provider::Service::Windows, + :user => Chef::Provider::User::Windows, + :group => Chef::Provider::Group::Windows, + :mount => Chef::Provider::Mount::Windows + } + }, + :windows => { + :default => { + :env => Chef::Provider::Env::Windows, + :service => Chef::Provider::Service::Windows, + :user => Chef::Provider::User::Windows, + :group => Chef::Provider::Group::Windows, + :mount => Chef::Provider::Mount::Windows + } + }, + :solaris => {}, + :openindiana => { + :default => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::Ips, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod + } + }, + :opensolaris => { + :default => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::Ips, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod + } + }, + :nexentacore => { + :default => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::Solaris, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod + } + }, + :omnios => { + :default => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::Ips, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod, + :user => Chef::Provider::User::Solaris, + } + }, + :solaris2 => { + :default => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::Ips, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod, + :user => Chef::Provider::User::Solaris, + }, + ">= 5.9" => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::Solaris, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod, + :user => Chef::Provider::User::Solaris, + } + }, + :smartos => { + :default => { + :service => Chef::Provider::Service::Solaris, + :package => Chef::Provider::Package::SmartOS, + :cron => Chef::Provider::Cron::Solaris, + :group => Chef::Provider::Group::Usermod + } + }, + :netbsd => { + :default => { + :service => Chef::Provider::Service::Freebsd, + :group => Chef::Provider::Group::Groupmod + } + }, + :openbsd => { + :default => { + :group => Chef::Provider::Group::Usermod + } + }, + :hpux => { + :default => { + :group => Chef::Provider::Group::Usermod + } + }, + :aix => { + :default => { + :group => Chef::Provider::Group::Aix + } + }, + :default => { + :file => Chef::Provider::File, + :directory => Chef::Provider::Directory, + :link => Chef::Provider::Link, + :template => Chef::Provider::Template, + :remote_directory => Chef::Provider::RemoteDirectory, + :execute => Chef::Provider::Execute, + :mount => Chef::Provider::Mount::Mount, + :script => Chef::Provider::Script, + :service => Chef::Provider::Service::Init, + :perl => Chef::Provider::Script, + :python => Chef::Provider::Script, + :ruby => Chef::Provider::Script, + :bash => Chef::Provider::Script, + :csh => Chef::Provider::Script, + :user => Chef::Provider::User::Useradd, + :group => Chef::Provider::Group::Gpasswd, + :http_request => Chef::Provider::HttpRequest, + :route => Chef::Provider::Route, + :ifconfig => Chef::Provider::Ifconfig, + :ruby_block => Chef::Provider::RubyBlock, + :erl_call => Chef::Provider::ErlCall, + :log => Chef::Provider::Log::ChefLog + } + } + end + + include Chef::Mixin::ParamsValidate + + def find(name, version) + provider_map = platforms[:default].clone + + name_sym = name + if name.kind_of?(String) + name.downcase! + name.gsub!(/\s/, "_") + name_sym = name.to_sym + end + + if platforms.has_key?(name_sym) + platform_versions = platforms[name_sym].select {|k, v| k != :default } + if platforms[name_sym].has_key?(:default) + provider_map.merge!(platforms[name_sym][:default]) + end + platform_versions.each do |platform_version, provider| + begin + version_constraint = Chef::VersionConstraint::Platform.new(platform_version) + if version_constraint.include?(version) + Chef::Log.debug("Platform #{name.to_s} version #{version} found") + provider_map.merge!(provider) + end + rescue Chef::Exceptions::InvalidPlatformVersion + Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}") + end + end + else + Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)") + end + provider_map + end + + def find_platform_and_version(node) + platform = nil + version = nil + + if node[:platform] + platform = node[:platform] + elsif node.attribute?("os") + platform = node[:os] + end + + raise ArgumentError, "Cannot find a platform for #{node}" unless platform + + if node[:platform_version] + version = node[:platform_version] + elsif node[:os_version] + version = node[:os_version] + elsif node[:os_release] + version = node[:os_release] + end + + raise ArgumentError, "Cannot find a version for #{node}" unless version + + return platform, version + end + + def provider_for_resource(resource, action=:nothing) + node = resource.run_context && resource.run_context.node + raise ArgumentError, "Cannot find the provider for a resource with no run context set" unless node + provider = find_provider_for_node(node, resource).new(resource, resource.run_context) + provider.action = action + provider + end + + def provider_for_node(node, resource_type) + raise NotImplementedError, "#{self.class.name} no longer supports #provider_for_node" + find_provider_for_node(node, resource_type).new(node, resource_type) + end + + def find_provider_for_node(node, resource_type) + platform, version = find_platform_and_version(node) + find_provider(platform, version, resource_type) + end + + def set(args) + validate( + args, + { + :platform => { + :kind_of => Symbol, + :required => false, + }, + :version => { + :kind_of => String, + :required => false, + }, + :resource => { + :kind_of => Symbol, + }, + :provider => { + :kind_of => [ String, Symbol, Class ], + } + } + ) + if args.has_key?(:platform) + if args.has_key?(:version) + if platforms.has_key?(args[:platform]) + if platforms[args[:platform]].has_key?(args[:version]) + platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] + else + platforms[args[:platform]][args[:version]] = { + args[:resource].to_sym => args[:provider] + } + end + else + platforms[args[:platform]] = { + args[:version] => { + args[:resource].to_sym => args[:provider] + } + } + end + else + if platforms.has_key?(args[:platform]) + if platforms[args[:platform]].has_key?(:default) + platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + else + platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } + end + else + platforms[args[:platform]] = { + :default => { + args[:resource].to_sym => args[:provider] + } + } + end + end + else + if platforms.has_key?(:default) + platforms[:default][args[:resource].to_sym] = args[:provider] + else + platforms[:default] = { + args[:resource].to_sym => args[:provider] + } + end + end + end + + def find_provider(platform, version, resource_type) + provider_klass = explicit_provider(platform, version, resource_type) || + platform_provider(platform, version, resource_type) || + resource_matching_provider(platform, version, resource_type) + + raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil? + + provider_klass + end + + private + + def explicit_provider(platform, version, resource_type) + resource_type.kind_of?(Chef::Resource) ? resource_type.provider : nil + end + + def platform_provider(platform, version, resource_type) + pmap = Chef::Platform.find(platform, version) + rtkey = resource_type.kind_of?(Chef::Resource) ? resource_type.resource_name.to_sym : resource_type + pmap.has_key?(rtkey) ? pmap[rtkey] : nil + end + + def resource_matching_provider(platform, version, resource_type) + if resource_type.kind_of?(Chef::Resource) + begin + Chef::Provider.const_get(resource_type.class.to_s.split('::').last) + rescue NameError + nil + end + else + nil + end + end + + end + end +end diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb new file mode 100644 index 0000000000..028a220a5d --- /dev/null +++ b/lib/chef/platform/query_helpers.rb @@ -0,0 +1,42 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 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. +# + +class Chef + class Platform + + class << self + def windows? + if RUBY_PLATFORM =~ /mswin|mingw|windows/ + true + else + false + end + end + + def windows_server_2003? + return false unless windows? + + require 'ruby-wmi' + + host = WMI::Win32_OperatingSystem.find(:first) + (host.version && host.version.start_with?("5.2")) + end + end + + end +end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 3fe057bd9b..90e417e182 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -162,7 +162,7 @@ class Chef # this block cannot interact with resources outside, e.g., # manipulating notifies. - converge_by ("would evaluate block and run any associated actions") do + converge_by ("evaluate block and run any associated actions") do saved_run_context = @run_context @run_context = @run_context.dup @run_context.resource_collection = Chef::ResourceCollection.new diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index 91f12bde90..2c915649a4 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -299,7 +299,7 @@ class Chef def run_symlinks_before_migrate links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ") - converge_by("make pre-migration symliinks: #{links_info}") do + converge_by("make pre-migration symlinks: #{links_info}") do @new_resource.symlink_before_migrate.each do |src, dest| begin FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}") diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index d6b2f91bab..8d2a7d997d 100644 --- a/lib/chef/provider/execute.rb +++ b/lib/chef/provider/execute.rb @@ -37,11 +37,9 @@ class Chef def action_run opts = {} - if sentinel_file = @new_resource.creates - if ::File.exists?(sentinel_file) - Chef::Log.debug("#{@new_resource} sentinel file #{sentinel_file} exists - nothing to do") - return false - end + if sentinel_file = sentinel_file_if_exists + Chef::Log.debug("#{@new_resource} sentinel file #{sentinel_file} exists - nothing to do") + return false end # original implementation did not specify a timeout, but ShellOut @@ -63,6 +61,25 @@ class Chef Chef::Log.info("#{@new_resource} ran successfully") end end + + private + + def sentinel_file_if_exists + if sentinel_file = @new_resource.creates + relative = Pathname(sentinel_file).relative? + cwd = @new_resource.cwd + if relative && !cwd + Chef::Log.warn "You have provided relative path for execute#creates (#{sentinel_file}) without execute#cwd (see CHEF-3819)" + end + + if ::File.exists?(sentinel_file) + sentinel_file + elsif cwd && relative + sentinel_file = ::File.join(cwd, sentinel_file) + sentinel_file if ::File.exists?(sentinel_file) + end + end + end end end end diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb index fe41997d39..6b9dd91ac8 100644 --- a/lib/chef/provider/mount.rb +++ b/lib/chef/provider/mount.rb @@ -79,7 +79,7 @@ class Chef end def action_enable - unless @current_resource.enabled + unless @current_resource.enabled && mount_options_unchanged? converge_by("remount #{@current_resource.device}") do status = enable_fs if status diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb index 9a85a9058a..280513cddc 100644 --- a/lib/chef/provider/mount/mount.rb +++ b/lib/chef/provider/mount/mount.rb @@ -225,7 +225,11 @@ class Chef # ignore trailing slash Regexp.escape(device_real)+"/?" elsif ::File.symlink?(device_real) - "(?:#{Regexp.escape(device_real)})|(?:#{Regexp.escape(::File.readlink(device_real))})" + # This regular expression tries to match device_real. If that does not match it will try to match the target of device_real. + # So given a symlink like this: + # /dev/mapper/vgroot-tmp.vol -> /dev/dm-9 + # First it will try to match "/dev/mapper/vgroot-tmp.vol". If there is no match it will try matching for "/dev/dm-9". + "(?:#{Regexp.escape(device_real)}|#{Regexp.escape(::File.readlink(device_real))})" else Regexp.escape(device_real) end diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb index a3ef1e5e86..b17f6f2564 100644 --- a/lib/chef/provider/package/smartos.rb +++ b/lib/chef/provider/package/smartos.rb @@ -2,6 +2,7 @@ # Authors:: Trevor O (trevoro@joyent.com) # Bryan McLellan (btm@loftninjas.org) # Matthew Landauer (matthew@openaustralia.org) +# Ben Rockwood (benr@joyent.com) # Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer # License:: Apache License, Version 2.0 # @@ -17,10 +18,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Notes -# -# * Supports installing using a local package name -# * Otherwise reverts to installing from the pkgsrc repositories URL require 'chef/provider/package' require 'chef/mixin/shell_out' @@ -36,47 +33,61 @@ class Chef def load_current_resource - Chef::Log.debug("#{@new_resource} loading current resource") - @current_resource = Chef::Resource::Package.new(@new_resource.name) - @current_resource.package_name(@new_resource.package_name) - @current_resource.version(nil) + Chef::Log.debug("#{@new_resource} loading current resource") + @current_resource = Chef::Resource::Package.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @current_resource.version(nil) check_package_state(@new_resource.package_name) - @current_resource # modified by check_package_state - end - - def check_package_state(name) - Chef::Log.debug("#{@new_resource} checking package #{name}") - # XXX - version = nil - info = shell_out!("pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1]) - - if info.stdout - version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1] + @current_resource # modified by check_package_state + end + + def check_package_state(name) + Chef::Log.debug("#{@new_resource} checking package #{name}") + version = nil + info = shell_out!("/opt/local/sbin/pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1]) + + if info.stdout + version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1] end - if !version - @current_resource.version(nil) - else - @current_resource.version(version) - end + if !version + @current_resource.version(nil) + else + @current_resource.version(version) + end end + def candidate_version + return @candidate_version if @candidate_version + name = nil + version = nil + 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}/ + 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}") - package = "#{name}-#{version}" - out = shell_out!("pkgin -y install #{package}", :env => nil) + Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}") + package = "#{name}-#{version}" + out = shell_out!("/opt/local/bin/pkgin -y install #{package}", :env => nil) end - def upgrade_package(name, version) - Chef::Log.debug("#{@new_resource} upgrading package #{name}-#{version}") - install_package(name, version) - end + def upgrade_package(name, version) + Chef::Log.debug("#{@new_resource} upgrading package #{name} version #{version}") + install_package(name, version) + end - def remove_package(name, version) - Chef::Log.debug("#{@new_resource} removing package #{name}-#{version}") - package = "#{name}-#{version}" - out = shell_out!("pkgin -y remove #{package}", :env => nil) - end + def remove_package(name, 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 end end diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb index 43727466e2..f547e566f0 100644 --- a/lib/chef/provider/package/zypper.rb +++ b/lib/chef/provider/package/zypper.rb @@ -91,11 +91,11 @@ class Chef ) elsif version run_command( - :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}" + :command => "zypper -n#{gpg_checks} install -l #{name}=#{version}" ) else run_command( - :command => "zypper -n --no-gpg-checks install -l #{name}" + :command => "zypper -n#{gpg_checks} install -l #{name}" ) end end @@ -107,11 +107,11 @@ class Chef ) elsif version run_command( - :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}" + :command => "zypper -n#{gpg_checks} install -l #{name}=#{version}" ) else run_command( - :command => "zypper -n --no-gpg-checks install -l #{name}" + :command => "zypper -n#{gpg_checks} install -l #{name}" ) end end @@ -123,21 +123,33 @@ class Chef ) elsif version run_command( - :command => "zypper -n --no-gpg-checks remove #{name}=#{version}" + :command => "zypper -n#{gpg_checks} remove #{name}=#{version}" ) else run_command( - :command => "zypper -n --no-gpg-checks remove #{name}" + :command => "zypper -n#{gpg_checks} remove #{name}" ) end - - end def purge_package(name, version) remove_package(name, version) end - + + private + def gpg_checks() + case Chef::Config[:zypper_check_gpg] + when true + "" + when false + " --no-gpg-checks" + when nil + Chef::Log.warn("Chef::Config[:zypper_check_gpg] was not set. " + + "All packages will be installed without gpg signature checks. " + + "This is a security hazard.") + " --no-gpg-checks" + end + end end end end diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index 364b6f6542..33a29da109 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/mixin/shell_out' require 'chef/provider/service' require 'chef/mixin/command' @@ -23,6 +24,7 @@ class Chef class Provider class Service class Solaris < Chef::Provider::Service + include Chef::Mixin::ShellOut def initialize(new_resource, run_context=nil) super @@ -42,23 +44,22 @@ class Chef end def enable_service - run_command(:command => "#{default_init_command} enable #{@new_resource.service_name}") - return service_status.enabled + shell_out!("#{default_init_command} enable -s #{@new_resource.service_name}") end def disable_service - run_command(:command => "#{default_init_command} disable #{@new_resource.service_name}") - return service_status.enabled + shell_out!("#{default_init_command} disable -s #{@new_resource.service_name}") end alias_method :stop_service, :disable_service alias_method :start_service, :enable_service def reload_service - run_command(:command => "#{default_init_command} refresh #{@new_resource.service_name}") + shell_out!("#{default_init_command} refresh #{@new_resource.service_name}") end def restart_service + ## svcadm restart doesn't supports sync(-s) option disable_service return enable_service end diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb new file mode 100644 index 0000000000..0658981908 --- /dev/null +++ b/lib/chef/provider/user/solaris.rb @@ -0,0 +1,90 @@ +# +# Author:: Stephen Nelson-Smith (<sns@opscode.com>) +# Author:: Jon Ramsey (<jonathon.ramsey@gmail.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. + +class Chef + class Provider + class User + class Solaris < Chef::Provider::User::Useradd + UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]] + + attr_writer :password_file + + def initialize(new_resource, run_context) + @password_file = "/etc/shadow" + super + end + + def create_user + super + manage_password + end + + def manage_user + manage_password + super + end + + private + + def manage_password + if @current_resource.password != @new_resource.password && @new_resource.password + Chef::Log.debug("#{@new_resource} setting password to #{@new_resource.password}") + write_shadow_file + end + end + + def write_shadow_file + buffer = Tempfile.new("shadow", "/etc") + ::File.open(@password_file) do |shadow_file| + shadow_file.each do |entry| + user = entry.split(":").first + if user == @new_resource.username + buffer.write(updated_password(entry)) + else + buffer.write(entry) + end + end + end + buffer.close + + # FIXME: mostly duplicates code with file provider deploying a file + mode = ::File.stat(@password_file).mode & 07777 + uid = ::File.stat(@password_file).uid + gid = ::File.stat(@password_file).gid + + FileUtils.chown uid, gid, buffer.path + FileUtils.chmod mode, buffer.path + + FileUtils.mv buffer.path, @password_file + end + + def updated_password(entry) + fields = entry.split(":") + fields[1] = @new_resource.password + fields[2] = days_since_epoch + fields.join(":") + end + + def days_since_epoch + (Time.now.to_i / 86400).floor + end + end + end + end +end + diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb index 489632f722..280676df24 100644 --- a/lib/chef/provider/user/useradd.rb +++ b/lib/chef/provider/user/useradd.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/provider/user' class Chef class Provider - class User + class User class Useradd < Chef::Provider::User UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]] @@ -32,21 +32,23 @@ class Chef end run_command(:command => command) end - + def manage_user - command = compile_command("usermod") do |u| - u << universal_options + if universal_options != "" + command = compile_command("usermod") do |u| + u << universal_options + end + run_command(:command => command) end - run_command(:command => command) end - + def remove_user command = "userdel" command << " -r" if managing_home_dir? command << " #{@new_resource.username}" run_command(:command => command) end - + def check_lock status = popen4("passwd -S #{@new_resource.username}") do |pid, stdin, stdout, stderr| status_line = stdout.gets.split(' ') @@ -80,11 +82,11 @@ class Chef @locked end - + def lock_user run_command(:command => "usermod -L #{@new_resource.username}") end - + def unlock_user run_command(:command => "usermod -U #{@new_resource.username}") end @@ -94,29 +96,36 @@ class Chef base_command << " #{@new_resource.username}" base_command end - + def universal_options - opts = '' - - UNIVERSAL_OPTIONS.each do |field, option| - if @current_resource.send(field) != @new_resource.send(field) - if @new_resource.send(field) - Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}") - opts << " #{option} '#{@new_resource.send(field)}'" + @universal_options ||= + begin + opts = '' + # magic allows UNIVERSAL_OPTIONS to be overridden in a subclass + self.class::UNIVERSAL_OPTIONS.each do |field, option| + update_options(field, option, opts) end + if updating_home? + if managing_home_dir? + Chef::Log.debug("#{@new_resource} managing the users home directory") + opts << " -m -d '#{@new_resource.home}'" + else + Chef::Log.debug("#{@new_resource} setting home to #{@new_resource.home}") + opts << " -d '#{@new_resource.home}'" + end + end + opts << " -o" if @new_resource.non_unique || @new_resource.supports[:non_unique] + opts end - end - if updating_home? - if managing_home_dir? - Chef::Log.debug("#{@new_resource} managing the users home directory") - opts << " -m -d '#{@new_resource.home}'" - else - Chef::Log.debug("#{@new_resource} setting home to #{@new_resource.home}") - opts << " -d '#{@new_resource.home}'" + end + + def update_options(field, option, opts) + if @current_resource.send(field) != @new_resource.send(field) + if @new_resource.send(field) + Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}") + opts << " #{option} '#{@new_resource.send(field)}'" end end - opts << " -o" if @new_resource.non_unique || @new_resource.supports[:non_unique] - opts end def useradd_options diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index ae95632eaa..aa14b92d28 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -84,6 +84,7 @@ require 'chef/provider/user/dscl' require 'chef/provider/user/pw' require 'chef/provider/user/useradd' require 'chef/provider/user/windows' +require 'chef/provider/user/solaris' require 'chef/provider/group/aix' require 'chef/provider/group/dscl' diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 729632a69c..eb8d7bbf05 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -129,10 +129,24 @@ F extend Chef::Mixin::ConvertToClassName + + if Module.method(:const_defined?).arity == 1 + def self.strict_const_defined?(const) + const_defined?(const) + end + else + def self.strict_const_defined?(const) + const_defined?(const, false) + end + end + # Track all subclasses of Resource. This is used so names can be looked up # when attempting to deserialize from JSON. (See: json_compat) def self.resource_classes - @resource_classes ||= [] + # Using a class variable here ensures we have one variable to track + # subclasses shared by the entire class hierarchy; without this, each + # subclass would have its own list of subclasses. + @@resource_classes ||= [] end # Callback when subclass is defined. Adds subclass to list of subclasses. diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 0b92f3332d..2aea4a9431 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -18,7 +18,7 @@ # require 'chef/resource' -require 'chef/platform' +require 'chef/platform/query_helpers' require 'chef/provider/file' require 'chef/mixin/securable' diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index 8a714e75b7..3309ee3cb8 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -39,8 +39,13 @@ class Chef # Add log entry if we override an existing light-weight resource. class_name = convert_to_class_name(rname) - overriding = Chef::Resource.const_defined?(class_name) - Chef::Log.info("#{class_name} light-weight resource already initialized -- overriding!") if overriding + if Resource.strict_const_defined?(class_name) + old_class = Resource.send(:remove_const, class_name) + # CHEF-3432 -- Chef::Resource keeps a list of subclasses; need to + # remove old ones from the list when replacing. + resource_classes.delete(old_class) + Chef::Log.info("#{class_name} light-weight resource already initialized -- overriding!") + end resource_class = Class.new(self) diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb index baf959c33b..5ab7fc608c 100644 --- a/lib/chef/resource_collection.rb +++ b/lib/chef/resource_collection.rb @@ -52,8 +52,12 @@ class Chef @resources << a @resources_by_name[a.to_s] = @resources.length - 1 end + self end + # 'push' is an alias method to << + alias_method :push, :<< + def insert(resource) is_chef_resource(resource) if @insert_after_idx @@ -73,14 +77,6 @@ class Chef end end - def push(*args) - args.flatten.each do |arg| - is_chef_resource(arg) - @resources.push(arg) - @resources_by_name[arg.to_s] = @resources.length - 1 - end - end - def each @resources.each do |resource| yield resource @@ -206,7 +202,7 @@ class Chef resource_name = "#{resource_type}[#{name}]" results << lookup(resource_name) else - raise ArgumentError, "You must have a string like resource_type[name]!" + raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!" end return results end diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb index 85d6c5eafe..92b7c38b39 100644 --- a/lib/chef/rest.rb +++ b/lib/chef/rest.rb @@ -31,7 +31,7 @@ require 'chef/monkey_patches/string' require 'chef/monkey_patches/net_http' require 'chef/config' require 'chef/exceptions' -require 'chef/platform' +require 'chef/platform/query_helpers' class Chef diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb index 7b8108a2d4..73665f39e7 100644 --- a/lib/chef/run_list/run_list_expansion.rb +++ b/lib/chef/run_list/run_list_expansion.rb @@ -173,7 +173,7 @@ class Chef class RunListExpansionFromAPI < RunListExpansion def rest - @rest ||= (source || Chef::REST.new(Chef::Config[:role_url])) + @rest ||= (source || Chef::REST.new(Chef::Config[:chef_server_url])) end def fetch_role(name, included_by) diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb index daa928e7a3..6125fe59e1 100644 --- a/lib/chef/runner.rb +++ b/lib/chef/runner.rb @@ -22,7 +22,6 @@ require 'chef/exceptions' require 'chef/mixin/params_validate' require 'chef/node' require 'chef/resource_collection' -require 'chef/platform' class Chef # == Chef::Runner diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb index d147bc0005..4869ec1484 100644 --- a/lib/chef/search/query.rb +++ b/lib/chef/search/query.rb @@ -31,7 +31,7 @@ class Chef attr_accessor :rest def initialize(url=nil) - @rest = Chef::REST.new(url ||Chef::Config[:search_url]) + @rest = Chef::REST.new(url ||Chef::Config[:chef_server_url]) end # Search Solr for objects of a given type, for a given query. If you give @@ -53,7 +53,7 @@ class Chef end def list_indexes - response = @rest.get_rest("search") + @rest.get_rest("search") end private diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb index 287cf0c166..cf147e778d 100644 --- a/lib/chef/shell/shell_session.rb +++ b/lib/chef/shell/shell_session.rb @@ -204,8 +204,8 @@ module Shell Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, Chef::REST.new(Chef::Config[:server_url])) } cookbook_hash = @client.sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbook_hash) - @run_context = Chef::RunContext.new(node, cookbook_collection, @events) - @run_context.load(Chef::RunList::RunListExpansionFromAPI.new("_default", [])) + @run_context = Chef::RunContext.new(node, cookbook_collection, @events) + @run_context.load(@node.run_list.expand(@node.chef_environment)) @run_status.run_context = run_context end diff --git a/lib/chef/version/platform.rb b/lib/chef/version/platform.rb new file mode 100644 index 0000000000..2921341cd2 --- /dev/null +++ b/lib/chef/version/platform.rb @@ -0,0 +1,42 @@ +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2013 Onddo Labs, SL. +# 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/version_class' + +class Chef + class Version + class Platform < Chef::Version + + protected + + def parse(str="") + @major, @minor, @patch = + case str.to_s + when /^(\d+)\.(\d+)\.(\d+)$/ + [ $1.to_i, $2.to_i, $3.to_i ] + when /^(\d+)\.(\d+)$/ + [ $1.to_i, $2.to_i, 0 ] + when /^(\d+)$/ + [ $1.to_i, 0, 0 ] + else + msg = "'#{str.to_s}' does not match 'x.y.z', 'x.y' or 'x'" + raise Chef::Exceptions::InvalidPlatformVersion.new( msg ) + end + end + + end + end +end diff --git a/lib/chef/version_class.rb b/lib/chef/version_class.rb index e9879274ad..01af6f1f55 100644 --- a/lib/chef/version_class.rb +++ b/lib/chef/version_class.rb @@ -51,7 +51,7 @@ class Chef other.is_a?(Version) && self == other end - private + protected def parse(str="") @major, @minor, @patch = diff --git a/lib/chef/version_constraint.rb b/lib/chef/version_constraint.rb index b6f1f08757..cc213abb3d 100644 --- a/lib/chef/version_constraint.rb +++ b/lib/chef/version_constraint.rb @@ -22,6 +22,7 @@ class Chef STANDARD_OPS = %w(< > <= >=) OPS = %w(< > = <= >= ~>) PATTERN = /^(#{OPS.join('|')}) (.+)$/ + VERSION_CLASS = Chef::Version attr_reader :op, :version @@ -41,9 +42,9 @@ class Chef def include?(v) version = if v.respond_to? :version # a CookbookVersion-like object - Chef::Version.new(v.version.to_s) + self.class::VERSION_CLASS.new(v.version.to_s) else - Chef::Version.new(v.to_s) + self.class::VERSION_CLASS.new(v.to_s) end do_op(version) end @@ -98,13 +99,13 @@ class Chef @missing_patch_level = false if str.index(" ").nil? && str =~ /^[0-9]/ # try for lone version, implied '=' - @version = Chef::Version.new(str) + @version = self.class::VERSION_CLASS.new(str) @op = "=" elsif PATTERN.match str @op = $1 raw_version = $2 - @version = Chef::Version.new(raw_version) - if raw_version.split('.').size == 2 + @version = self.class::VERSION_CLASS.new(raw_version) + if raw_version.split('.').size <= 2 @missing_patch_level = true end else diff --git a/lib/chef/version_constraint/platform.rb b/lib/chef/version_constraint/platform.rb new file mode 100644 index 0000000000..ada4f29b70 --- /dev/null +++ b/lib/chef/version_constraint/platform.rb @@ -0,0 +1,26 @@ +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2013 Onddo Labs, SL. +# 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/version_constraint' +require 'chef/version/platform' + +class Chef + class VersionConstraint + class Platform < Chef::VersionConstraint + VERSION_CLASS = Chef::Version::Platform + + end + end +end diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb index c30b59b1d7..c8c923a6f2 100644 --- a/lib/chef/win32/version.rb +++ b/lib/chef/win32/version.rb @@ -29,6 +29,14 @@ class Chef # http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx # 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" => {: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 }}, @@ -50,7 +58,17 @@ class Chef @suite_mask = ver_info[:w_suite_mask] @sp_major_version = ver_info[:w_service_pack_major] @sp_minor_version = ver_info[:w_service_pack_minor] - @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version) + + # Obtain sku information for the purpose of identifying + # datacenter, cluster, and core skus, the latter 2 only + # exist in releases after Windows Server 2003 + if ! Chef::Platform::windows_server_2003? + @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version) + else + # 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 marketing_names = Array.new @@ -79,11 +97,6 @@ class Chef (self.class.const_get(c) == @sku) && (c.to_s =~ /#{m}/i ) end - # if @sku - # !(PRODUCT_TYPE[@sku][:name] =~ /#{m}/i).nil? - # else - # false - # end end end @@ -112,8 +125,10 @@ class Chef out.get_uint(0) end - def get_system_metrics(n_index) - GetSystemMetrics(n_index) + def get_datacenter_product_info_windows_server_2003(ver_info) + # The intent is not to get the actual sku, just identify + # Windows Server 2003 datacenter + sku = (ver_info[:w_suite_mask] & VER_SUITE_DATACENTER) ? PRODUCT_DATACENTER_SERVER : 0 end end diff --git a/spec/data/bootstrap/encrypted_data_bag_secret b/spec/data/bootstrap/encrypted_data_bag_secret new file mode 100644 index 0000000000..ac88558a1a --- /dev/null +++ b/spec/data/bootstrap/encrypted_data_bag_secret @@ -0,0 +1 @@ +supersekret_from_file diff --git a/spec/data/bootstrap/secret.erb b/spec/data/bootstrap/secret.erb new file mode 100644 index 0000000000..e0ad41576d --- /dev/null +++ b/spec/data/bootstrap/secret.erb @@ -0,0 +1,9 @@ +bash -c ' +<% if encrypted_data_bag_secret -%> +awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' +<%= encrypted_data_bag_secret %> +EOP +chmod 0600 /etc/chef/encrypted_data_bag_secret +<% end -%> + +<%= config_content %>' diff --git a/spec/data/knife_subcommand/test_yourself.rb b/spec/data/knife_subcommand/test_yourself.rb index 4b3f6b039c..f18f565885 100644 --- a/spec/data/knife_subcommand/test_yourself.rb +++ b/spec/data/knife_subcommand/test_yourself.rb @@ -1,6 +1,14 @@ module KnifeSpecs class TestYourself < Chef::Knife + class << self + attr_reader :test_deps_loaded + end + + deps do + @test_deps_loaded = true + end + option :scro, :short => '-s SCRO', :long => '--scro SCRO', :description => 'a configurable setting' attr_reader :ran diff --git a/spec/data/lwrp_const_scoping/resources/conflict.rb b/spec/data/lwrp_const_scoping/resources/conflict.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/spec/data/lwrp_const_scoping/resources/conflict.rb diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb index 327f0f6262..569b0133ac 100644 --- a/spec/functional/win32/service_manager_spec.rb +++ b/spec/functional/win32/service_manager_spec.rb @@ -155,7 +155,7 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind context "and service is stopped" do ["delete", "uninstall"].each do |action| - it "#{action} => should remove the service" do + it "#{action} => should remove the service", :volatile do service_manager.run(["-a", action]) test_service_exists?.should be_false end @@ -170,7 +170,7 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind @service_manager_output.grep(/stopped/).length.should > 0 end - it "start should start the service" do + it "start should start the service", :volatile do service_manager.run(["-a", "start"]) test_service_state.should == "running" File.exists?(test_service_file).should be_true @@ -188,13 +188,13 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind end end - context "and service is started" do + context "and service is started", :volatile do before(:each) do service_manager.run(["-a", "start"]) end ["delete", "uninstall"].each do |action| - it "#{action} => should remove the service" do + it "#{action} => should remove the service", :volatile do service_manager.run(["-a", action]) test_service_exists?.should be_false end @@ -225,7 +225,7 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind end end - context "and service is paused" do + context "and service is paused", :volatile do before(:each) do service_manager.run(["-a", "start"]) service_manager.run(["-a", "pause"]) diff --git a/spec/functional/win32/versions_spec.rb b/spec/functional/win32/versions_spec.rb index be60cd6b48..a446a1cf21 100644 --- a/spec/functional/win32/versions_spec.rb +++ b/spec/functional/win32/versions_spec.rb @@ -24,13 +24,55 @@ end describe "Chef::ReservedNames::Win32::Version", :windows_only do before do + host = WMI::Win32_OperatingSystem.find(:first) - @current_os_version = host.caption + + # Use WMI to determine current OS version. + # On Win2k8R2 and later, we can dynamically obtain marketing + # names for comparison from WMI so the test should not + # need to be modified when new Windows releases arise. + # For Win2k3 and Win2k8, we use static names in this test + # based on the version number information from WMI. The names + # from WMI contain extended characters such as registered + # trademark on Win2k8 and Win2k3 that we're not using in our + # library, so we have to set the expectation statically. + if Chef::Platform::windows_server_2003? + @current_os_version = 'Windows Server 2003 R2' + elsif is_windows_server_2008?(host) + @current_os_version = 'Windows Server 2008' + else + # The name from WMI is actually what we want in Win2k8R2+. + # So this expectation sould continue to hold without modification + # as new versions of Windows are released. + @current_os_version = host.caption + end + @version = Chef::ReservedNames::Win32::Version.new end + context "Windows Operating System version" do it "should match the version from WMI" do @current_os_version.include?(@version.marketing_name).should == true end end + + def is_windows_server_2008?(wmi_host) + is_win2k8 = false + + os_version = wmi_host.send('Version') + + # The operating system version is a string in the following form + # that can be split into components based on the '.' delimiter: + # MajorVersionNumber.MinorVersionNumber.BuildNumber + os_version_components = os_version.split('.') + + if os_version_components.length < 2 + raise 'WMI returned a Windows version from Win32_OperatingSystem.Version ' + + 'with an unexpected format. The Windows version could not be determined.' + end + + # Windows 6.0 is Windows Server 2008, so test the major and + # minor version components + is_win2k8 = os_version_components[0] == '6' && os_version_components[1] == '0' + end end diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index c5480c4adc..04a86bee8d 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -134,3 +134,18 @@ describe Chef::Application::Client, "setup_application" do Chef::Config[:solo] = false end end + +describe Chef::Application::Client, "configure_chef" do + before do + @app = Chef::Application::Client.new + @app.configure_chef + end + + it "should set the colored output to false by default on windows and true otherwise" do + if windows? + Chef::Config[:color].should be_false + else + Chef::Config[:color].should be_true + end + end +end
\ No newline at end of file diff --git a/spec/unit/application/knife_spec.rb b/spec/unit/application/knife_spec.rb index 9560de8d13..16f94c7786 100644 --- a/spec/unit/application/knife_spec.rb +++ b/spec/unit/application/knife_spec.rb @@ -61,6 +61,18 @@ describe Chef::Application::Knife do end end + it "should set the colored output to false by default on windows and true otherwise" do + with_argv(*%w{noop knife command}) do + @knife.should_receive(:exit).with(0) + @knife.run + end + if windows? + Chef::Config[:color].should be_false + else + Chef::Config[:color].should be_true + end + end + describe "when given a path to the client key" do it "expands a relative path relative to the CWD" do relative_path = '.chef/client.pem' diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 02e30e0c3b..0568da6eaf 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -263,6 +263,25 @@ shared_examples_for Chef::Client do end end + it "should remove the run_lock on failure of #load_node" do + @run_lock = mock("Chef::RunLock", :acquire => true) + Chef::RunLock.stub!(:new).and_return(@run_lock) + + @events = mock("Chef::EventDispatch::Dispatcher").as_null_object + Chef::EventDispatch::Dispatcher.stub!(:new).and_return(@events) + + # @events is created on Chef::Client.new, so we need to recreate it after mocking + @client = Chef::Client.new + @client.stub!(:load_node).and_raise(Exception) + @run_lock.should_receive(:release) + if(Chef::Config[:client_fork]) + @client.should_receive(:fork) do |&block| + block.call + end + end + lambda { @client.run }.should raise_error(Exception) + end + describe "when notifying other objects of the status of the chef run" do before do Chef::Client.clear_notifications diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb index 7b5d383ba3..f35b47d50a 100644 --- a/spec/unit/config_spec.rb +++ b/spec/unit/config_spec.rb @@ -24,42 +24,36 @@ describe Chef::Config do @original_env = { 'HOME' => ENV['HOME'], 'SYSTEMDRIVE' => ENV['SYSTEMDRIVE'], 'HOMEPATH' => ENV['HOMEPATH'], 'USERPROFILE' => ENV['USERPROFILE'] } end - shared_examples_for "server URL" do - it "should set the registration url" do - Chef::Config.registration_url.should == "https://junglist.gen.nz" + describe "config attribute writer: chef_server_url" do + before do + Chef::Config.chef_server_url = "https://junglist.gen.nz" end - it "should set the template url" do - Chef::Config.template_url.should == "https://junglist.gen.nz" + it "sets the server url" do + Chef::Config.chef_server_url.should == "https://junglist.gen.nz" end - it "should set the remotefile url" do - Chef::Config.remotefile_url.should == "https://junglist.gen.nz" - end + context "when the url has a leading space" do + before do + Chef::Config.chef_server_url = " https://junglist.gen.nz" + end - it "should set the search url" do - Chef::Config.search_url.should == "https://junglist.gen.nz" - end + it "strips the space from the url when setting" do + Chef::Config.chef_server_url.should == "https://junglist.gen.nz" + end - it "should set the role url" do - Chef::Config.role_url.should == "https://junglist.gen.nz" end - end - describe "config attribute writer: chef_server_url" do - before do - Chef::Config.chef_server_url = "https://junglist.gen.nz" - end - - it_behaves_like "server URL" - end + context "when the url is a frozen string" do + before do + Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze + end - context "when the url has a leading space" do - before do - Chef::Config.chef_server_url = " https://junglist.gen.nz" + it "strips the space from the url when setting without raising an error" do + Chef::Config.chef_server_url.should == "https://junglist.gen.nz" + end end - - it_behaves_like "server URL" + end describe "when configuring formatters" do @@ -106,14 +100,6 @@ describe Chef::Config do end - context "when the url is a frozen string" do - before do - Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze - end - - it_behaves_like "server URL" - end - describe "class method: manage_secret_key" do before do Chef::FileCache.stub!(:load).and_return(true) @@ -131,7 +117,7 @@ describe Chef::Config do end it "should not generate and store a chef server cookie id" do - Chef::FileCache.should_not_receive(:store).with("chef_server_cookie_id", /\w{40}/).and_return(true) + Chef::FileCache.should_not_receive(:store).with("chef_server_cookie_id", /\w{40}/) Chef::Config.manage_secret_key end end @@ -165,19 +151,7 @@ describe Chef::Config do end end - describe "class method: openid_providers=" do - it "should not log an appropriate deprecation info message" do - Chef::Log.should_not_receive(:info).with("DEPRECATION: openid_providers will be removed, please use authorized_openid_providers").and_return(true) - Chef::Config.openid_providers = %w{opscode.com junglist.gen.nz} - end - - it "should internally configure authorized_openid_providers with the value given" do - Chef::Config.should_receive(:configure).and_return(%w{opscode.com junglist.gen.nz}) - Chef::Config.openid_providers = %w{opscode.com junglist.gen.nz} - end - end - - describe "class method: plaform_specific_path" do + describe "class method: plaform_specific_path" do it "should return given path on non-windows systems" do platform_mock :unix do path = "/etc/chef/cookbooks" @@ -230,12 +204,8 @@ describe Chef::Config do end it "Chef::Config[:data_bag_path] defaults to /var/chef/data_bags" do - data_bag_path = if windows? - "C:\\chef\\data_bags" - else - "/var/chef/data_bags" - end - + data_bag_path = + Chef::Config.platform_specific_path("/var/chef/data_bags") Chef::Config[:data_bag_path].should == data_bag_path end end @@ -261,6 +231,35 @@ describe Chef::Config do end end + describe "Chef::Config[:encrypted_data_bag_secret]" do + db_secret_default_path = + Chef::Config.platform_specific_path("/etc/chef/encrypted_data_bag_secret") + + let(:db_secret_default_path){ db_secret_default_path } + + before do + File.stub(:exist?).with(db_secret_default_path).and_return(secret_exists) + # ugh...the only way to properly test this since the conditional + # is evaluated at file load/require time. + $LOADED_FEATURES.delete_if{|f| f =~ /chef\/config\.rb/} + require 'chef/config' + end + + context "#{db_secret_default_path} exists" do + let(:secret_exists) { true } + it "sets the value to #{db_secret_default_path}" do + Chef::Config[:encrypted_data_bag_secret].should eq db_secret_default_path + end + end + + context "#{db_secret_default_path} does not exist" do + let(:secret_exists) { false } + it "sets the value to nil" do + Chef::Config[:encrypted_data_bag_secret].should be_nil + end + end + end + after(:each) do Chef::Config.configuration = @original_config end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index 1efdf2a2ad..5a0bbd6a47 100644 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -154,8 +154,34 @@ describe Chef::Daemon do FileUtils.should_receive(:rm).with("/var/run/chef/chef-client.pid") Chef::Daemon.remove_pid_file end + + end + describe "when the pid file exists and the process is forked" do + + before do + File.stub!(:exists?).with("/var/run/chef/chef-client.pid").and_return(true) + Chef::Daemon.stub!(:forked?) { true } + end + + it "should not remove the file" do + FileUtils.should_not_receive(:rm) + Chef::Daemon.remove_pid_file + end + + end + + describe "when the pid file exists and the process is not forked" do + before do + File.stub!(:exists?).with("/var/run/chef/chef-client.pid").and_return(true) + Chef::Daemon.stub!(:forked?) { false } + end + + it "should remove the file" do + FileUtils.should_receive(:rm) + Chef::Daemon.remove_pid_file + end end describe "when the pid file does not exist" do diff --git a/spec/unit/encrypted_data_bag_item_spec.rb b/spec/unit/encrypted_data_bag_item_spec.rb index 30f13c3f1d..9eda1633ab 100644 --- a/spec/unit/encrypted_data_bag_item_spec.rb +++ b/spec/unit/encrypted_data_bag_item_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' require 'chef/encrypted_data_bag_item' -module Version1Encryptor +module Version0Encryptor def self.encrypt_value(plaintext_data, key) data = plaintext_data.to_yaml @@ -34,205 +34,300 @@ end describe Chef::EncryptedDataBagItem::Encryptor do + subject(:encryptor) { described_class.new(plaintext_data, key) } + let(:plaintext_data) { {"foo" => "bar"} } + let(:key) { "passwd" } + + it "encrypts to format version 1 by default" do + encryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor) + end + describe "generating a random IV" do it "generates a new IV for each encryption pass" do - encryptor1 = Chef::EncryptedDataBagItem::Encryptor.new({"foo" => "bar"}, "passwd") - encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new({"foo" => "bar"}, "passwd") + encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) # No API in ruby OpenSSL to get the iv it used for the encryption back # out. Instead we test if the encrypted data is the same. If it *is* the # same, we assume the IV was the same each time. - encryptor1.encrypted_data.should_not == encryptor2.encrypted_data + encryptor.encrypted_data.should_not eq encryptor2.encrypted_data end end describe "when encrypting a non-hash non-array value" do + let(:plaintext_data) { 5 } it "serializes the value in a de-serializable way" do - encryptor = Chef::EncryptedDataBagItem::Encryptor.new(5, "passwd") - Chef::JSONCompat.from_json(encryptor.serialized_data)["json_wrapper"].should == 5 + Chef::JSONCompat.from_json(subject.serialized_data)["json_wrapper"].should eq 5 end end describe "wrapping secret values in an envelope" do it "wraps the encrypted data in an envelope with the iv and version" do - encryptor = Chef::EncryptedDataBagItem::Encryptor.new({"foo" => "bar"}, "passwd") final_data = encryptor.for_encrypted_item - final_data["encrypted_data"].should == encryptor.encrypted_data - final_data["iv"].should == Base64.encode64(encryptor.iv) - final_data["version"].should == 1 - final_data["cipher"].should == "aes-256-cbc" + final_data["encrypted_data"].should eq encryptor.encrypted_data + final_data["iv"].should eq Base64.encode64(encryptor.iv) + final_data["version"].should eq 1 + final_data["cipher"].should eq"aes-256-cbc" + end + end + + describe "when using version 2 format" do + + before do + @original_config = Chef::Config.hash_dup + Chef::Config[:data_bag_encrypt_version] = 2 end + after do + Chef::Config.configuration = @original_config + end + + it "creates a version 2 encryptor" do + encryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor) + end + + it "generates an hmac based on ciphertext including iv" do + encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) + encryptor.hmac.should_not eq(encryptor2.hmac) + end + + it "includes the hmac in the envelope" do + final_data = encryptor.for_encrypted_item + final_data["hmac"].should eq(encryptor.hmac) + end end end describe Chef::EncryptedDataBagItem::Decryptor do + + subject(:decryptor) { described_class.for(encrypted_value, decryption_key) } + let(:plaintext_data) { {"foo" => "bar"} } + let(:encryption_key) { "passwd" } + let(:decryption_key) { encryption_key } + + context "when decrypting a version 2 (JSON+aes-256-cbc+hmac-sha256+random iv) encrypted value" do + let(:encrypted_value) do + Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.new(plaintext_data, encryption_key).for_encrypted_item + end + + let(:bogus_hmac) do + digest = OpenSSL::Digest::Digest.new("sha256") + raw_hmac = OpenSSL::HMAC.digest(digest, "WRONG", encrypted_value["encrypted_data"]) + Base64.encode64(raw_hmac) + end + + it "rejects the data if the hmac is wrong" do + encrypted_value["hmac"] = bogus_hmac + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) + end + + it "rejects the data if the hmac is missing" do + encrypted_value.delete("hmac") + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) + end + + end + context "when decrypting a version 1 (JSON+aes-256-cbc+random iv) encrypted value" do - before do - @encryptor = Chef::EncryptedDataBagItem::Encryptor.new({"foo" => "bar"}, "passwd") - @encrypted_value = @encryptor.for_encrypted_item - @decryptor = Chef::EncryptedDataBagItem::Decryptor.for(@encrypted_value, "passwd") + let(:encrypted_value) do + Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, encryption_key).for_encrypted_item end it "selects the correct strategy for version 1" do - @decryptor.should be_a_kind_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor + decryptor.should be_a_kind_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor end it "decrypts the encrypted value" do - @decryptor.decrypted_data.should == {"json_wrapper" => {"foo" => "bar"}}.to_json + decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json) end it "unwraps the encrypted data and returns it" do - @decryptor.for_decrypted_item.should == {"foo" => "bar"} + decryptor.for_decrypted_item.should eq plaintext_data end - context "and the provided key is incorrect" do - before do - @decryptor = Chef::EncryptedDataBagItem::Decryptor.for(@encrypted_value, "wrong-passwd") + describe "and the decryption step returns invalid data" do + it "raises a decryption failure error" do + # Over a large number of tests on a variety of systems, we occasionally + # see the decryption step "succeed" but return invalid data (e.g., not + # the original plain text) [CHEF-3858] + decryptor.should_receive(:decrypted_data).and_return("lksajdf") + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end + end + + context "and the provided key is incorrect" do + let(:decryption_key) { "wrong-passwd" } it "raises a sensible error" do - lambda { @decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end end context "and the cipher is not supported" do - before do - @encrypted_value["cipher"] = "aes-256-foo" + let(:encrypted_value) do + ev = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, encryption_key).for_encrypted_item + ev["cipher"] = "aes-256-foo" + ev end it "raises a sensible error" do - lambda { @decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::UnsupportedCipher) + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::UnsupportedCipher) + end + end + + context "and version 2 format is required" do + before do + @original_config = Chef::Config.hash_dup + Chef::Config[:data_bag_decrypt_minimum_version] = 2 + end + + after do + Chef::Config.configuration = @original_config + end + + it "raises an error attempting to decrypt" do + lambda { decryptor }.should raise_error(Chef::EncryptedDataBagItem::UnacceptableEncryptedDataBagItemFormat) end + end end context "when decrypting a version 0 (YAML+aes-256-cbc+no iv) encrypted value" do - before do - @encrypted_value = Version1Encryptor.encrypt_value({"foo" => "bar"}, "passwd") - - @decryptor = Chef::EncryptedDataBagItem::Decryptor.for(@encrypted_value, "passwd") + let(:encrypted_value) do + Version0Encryptor.encrypt_value(plaintext_data, encryption_key) end it "selects the correct strategy for version 0" do - @decryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Decryptor::Version0Decryptor) + decryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Decryptor::Version0Decryptor) end it "decrypts the encrypted value" do - @decryptor.for_decrypted_item.should == {"foo" => "bar"} + decryptor.for_decrypted_item.should eq plaintext_data + end + + context "and version 1 format is required" do + before do + @original_config = Chef::Config.hash_dup + Chef::Config[:data_bag_decrypt_minimum_version] = 1 + end + + after do + Chef::Config.configuration = @original_config + end + + it "raises an error attempting to decrypt" do + lambda { decryptor }.should raise_error(Chef::EncryptedDataBagItem::UnacceptableEncryptedDataBagItemFormat) + end + end + end end describe Chef::EncryptedDataBagItem do - before(:each) do - @secret = "abc123SECRET" - @plain_data = { + subject { described_class } + let(:encrypted_data_bag_item) { subject.new(encoded_data, secret) } + let(:plaintext_data) {{ "id" => "item_name", "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} - } - @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, - @secret) - end - + }} + let(:secret) { "abc123SECRET" } + let(:encoded_data) { subject.encrypt_data_bag_item(plaintext_data, secret) } describe "encrypting" do - it "should not encrypt the 'id' key" do - @enc_data["id"].should == "item_name" + it "doesn't encrypt the 'id' key" do + encoded_data["id"].should eq "item_name" end - it "should encrypt non-collection objects" do - @enc_data["greeting"]["version"].should == 1 - @enc_data["greeting"].should have_key("iv") + it "encrypts non-collection objects" do + encoded_data["greeting"]["version"].should eq 1 + encoded_data["greeting"].should have_key("iv") - iv = @enc_data["greeting"]["iv"] - encryptor = Chef::EncryptedDataBagItem::Encryptor.new("hello", @secret, iv) + iv = encoded_data["greeting"]["iv"] + encryptor = Chef::EncryptedDataBagItem::Encryptor.new("hello", secret, iv) - @enc_data["greeting"]["encrypted_data"].should == encryptor.for_encrypted_item["encrypted_data"] + encoded_data["greeting"]["encrypted_data"].should eq(encryptor.for_encrypted_item["encrypted_data"]) end - it "should encrypt nested values" do - @enc_data["nested"]["version"].should == 1 - @enc_data["nested"].should have_key("iv") + it "encrypts nested values" do + encoded_data["nested"]["version"].should eq 1 + encoded_data["nested"].should have_key("iv") - iv = @enc_data["nested"]["iv"] - encryptor = Chef::EncryptedDataBagItem::Encryptor.new(@plain_data["nested"], @secret, iv) + iv = encoded_data["nested"]["iv"] + encryptor = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data["nested"], secret, iv) - @enc_data["nested"]["encrypted_data"].should == encryptor.for_encrypted_item["encrypted_data"] + encoded_data["nested"]["encrypted_data"].should eq(encryptor.for_encrypted_item["encrypted_data"]) end end describe "decrypting" do - before(:each) do - @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, - @secret) - @eh = Chef::EncryptedDataBagItem.new(@enc_data, @secret) - end it "doesn't try to decrypt 'id'" do - @eh["id"].should == @plain_data["id"] + encrypted_data_bag_item["id"].should eq(plaintext_data["id"]) end it "decrypts 'greeting'" do - @eh["greeting"].should == @plain_data["greeting"] + encrypted_data_bag_item["greeting"].should eq(plaintext_data["greeting"]) end it "decrypts 'nested'" do - @eh["nested"].should == @plain_data["nested"] + encrypted_data_bag_item["nested"].should eq(plaintext_data["nested"]) end it "decrypts everyting via to_hash" do - @eh.to_hash.should == @plain_data + encrypted_data_bag_item.to_hash.should eq(plaintext_data) end it "handles missing keys gracefully" do - @eh["no-such-key"].should be_nil + encrypted_data_bag_item["no-such-key"].should be_nil end end describe "loading" do it "should defer to Chef::DataBagItem.load" do - Chef::DataBagItem.stub(:load).with(:the_bag, "my_codes").and_return(@enc_data) - edbi = Chef::EncryptedDataBagItem.load(:the_bag, "my_codes", @secret) - edbi["greeting"].should == @plain_data["greeting"] + Chef::DataBagItem.stub(:load).with(:the_bag, "my_codes").and_return(encoded_data) + edbi = Chef::EncryptedDataBagItem.load(:the_bag, "my_codes", secret) + edbi["greeting"].should eq(plaintext_data["greeting"]) end end - describe "load_secret" do - it "should read from the default path" do - default_path = "/etc/chef/encrypted_data_bag_secret" - ::File.stub(:exists?).with(default_path).and_return(true) - IO.stub(:read).with(default_path).and_return("opensesame") - Chef::EncryptedDataBagItem.load_secret().should == "opensesame" + describe ".load_secret" do + subject(:loaded_secret) { Chef::EncryptedDataBagItem.load_secret(path) } + let(:path) { "/var/mysecret" } + let(:secret) { "opensesame" } + let(:stubbed_path) { path } + before do + ::File.stub(:exist?).with(stubbed_path).and_return(true) + IO.stub(:read).with(stubbed_path).and_return(secret) + Kernel.stub(:open).with(path).and_return(StringIO.new(secret)) end - it "should read from Chef::Config[:encrypted_data_bag_secret]" do - path = "/var/mysecret" - Chef::Config[:encrypted_data_bag_secret] = path - ::File.stub(:exists?).with(path).and_return(true) - IO.stub(:read).with(path).and_return("opensesame") - Chef::EncryptedDataBagItem.load_secret().should == "opensesame" + it "reads from a specified path" do + loaded_secret.should eq secret end - it "should read from a specified path" do - path = "/var/mysecret" - ::File.stub(:exists?).with(path).and_return(true) - IO.stub(:read).with(path).and_return("opensesame") - Chef::EncryptedDataBagItem.load_secret(path).should == "opensesame" + context "path argument is nil" do + let(:path) { nil } + let(:stubbed_path) { "/etc/chef/encrypted_data_bag_secret" } + + it "reads from Chef::Config[:encrypted_data_bag_secret]" do + Chef::Config[:encrypted_data_bag_secret] = stubbed_path + loaded_secret.should eq secret + end end - it "should read from a URL" do - path = "http://www.opscode.com/" - fake_file = StringIO.new("opensesame") - Kernel.stub(:open).with(path).and_return(fake_file) - Chef::EncryptedDataBagItem.load_secret(path).should == "opensesame" + context "path argument is a URL" do + let(:path) { "http://www.opscode.com/" } + + it "reads the URL" do + loaded_secret.should eq secret + end end end end diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb index 97f0c3395e..99232dff08 100644 --- a/spec/unit/environment_spec.rb +++ b/spec/unit/environment_spec.rb @@ -271,6 +271,7 @@ describe Chef::Environment do Chef::Environment.validate_cookbook_version(Chef::CookbookVersion.new("meta")).should == false Chef::Environment.validate_cookbook_version("= 1.2.3a").should == false Chef::Environment.validate_cookbook_version("= 1").should == false + Chef::Environment.validate_cookbook_version("= a").should == false Chef::Environment.validate_cookbook_version("= 1.2.3.4").should == false end end diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index bbb63f8c01..f2ffa812cf 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -128,13 +128,65 @@ describe Chef::Knife::Bootstrap do @knife.render_template(template_string).should match /\{\"foo\":\"bar\"\}/ end - it "should take the node name from ARGV" do @knife.name_args = ['barf'] @knife.name_args.first.should == "barf" end - describe "when configuring the underlying knife ssh command" + describe "specifying the encrypted data bag secret key" do + subject(:knife) { described_class.new } + let(:secret) { "supersekret" } + let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') } + let(:options) { [] } + let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) } + let(:rendered_template) do + knife.instance_variable_set("@template_file", template_file) + knife.parse_options(options) + template_string = knife.read_template + knife.render_template(template_string) + end + + context "via --secret" do + let(:options){ ["--secret", secret] } + + it "creates a secret file" do + rendered_template.should match(%r{#{secret}}) + end + + it "renders the client.rb with an encrypted_data_bag_secret entry" do + rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) + end + end + + context "via --secret-file" do + let(:options) { ["--secret-file", secret_file] } + let(:secret) { IO.read(secret_file) } + + it "creates a secret file" do + rendered_template.should match(%r{#{secret}}) + end + + it "renders the client.rb with an encrypted_data_bag_secret entry" do + rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) + end + end + + context "via Chef::Config[:encrypted_data_bag_secret]" do + before(:each) { Chef::Config[:encrypted_data_bag_secret] = secret_file } + let(:secret) { IO.read(secret_file) } + + it "creates a secret file" do + rendered_template.should match(%r{#{secret}}) + end + + it "renders the client.rb with an encrypted_data_bag_secret entry" do + rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) + end + after(:each) { Chef::Config.configuration = @original_config } + end + end + + describe "when configuring the underlying knife ssh command" do context "from the command line" do before do @knife.name_args = ["foo.example.com"] @@ -147,28 +199,52 @@ describe Chef::Knife::Bootstrap do @knife.stub!(:read_template).and_return("") @knife_ssh = @knife.knife_ssh end - + it "configures the hostname" do @knife_ssh.name_args.first.should == "foo.example.com" end - + it "configures the ssh user" do @knife_ssh.config[:ssh_user].should == 'rooty' end - + it "configures the ssh password" do @knife_ssh.config[:ssh_password].should == 'open_sesame' end - + it "configures the ssh port" do @knife_ssh.config[:ssh_port].should == '4001' end - + it "configures the ssh identity file" do @knife_ssh.config[:identity_file].should == '~/.ssh/me.rsa' end end + context "validating use_sudo_password" do + before do + @knife.config[:distro] = "ubuntu" + @knife.config[:ssh_password] = "password" + @knife.stub(:read_template).and_return(IO.read(@knife.find_template).chomp) + end + + it "use_sudo_password contains description and long params for help" do + @knife.options.should have_key(:use_sudo_password) \ + and @knife.options[:use_sudo_password][:description].to_s.should_not == ''\ + and @knife.options[:use_sudo_password][:long].to_s.should_not == '' + end + + it "uses the password from --ssh-password for sudo when --use-sudo-password is set" do + @knife.config[:use_sudo] = true + @knife.config[:use_sudo_password] = true + @knife.ssh_command.should include("echo #{@knife.config[:ssh_password]} | sudo -S") + end + it "should not honor --use-sudo-password when --use-sudo is not set" do + @knife.config[:use_sudo] = false + @knife.config[:use_sudo_password] = true + @knife.ssh_command.should_not include("echo #{@knife.config[:ssh_password]} | sudo -S") + end + end context "from the knife config file" do before do @knife.name_args = ["config.example.com"] @@ -185,19 +261,19 @@ describe Chef::Knife::Bootstrap do @knife.stub!(:read_template).and_return("") @knife_ssh = @knife.knife_ssh end - + it "configures the ssh user" do @knife_ssh.config[:ssh_user].should == 'curiosity' end - + it "configures the ssh port" do @knife_ssh.config[:ssh_port].should == '2430' end - + it "configures the ssh identity file" do @knife_ssh.config[:identity_file].should == '~/.ssh/you.rsa' end - + it "configures the ssh gateway" do @knife_ssh.config[:ssh_gateway].should == 'towel.blinkenlights.nl' end @@ -205,29 +281,30 @@ describe Chef::Knife::Bootstrap do it "configures the host key verify mode" do @knife_ssh.config[:host_key_verify].should == true end - end - - describe "when falling back to password auth when host key auth fails" do - before do - @knife.name_args = ["foo.example.com"] - @knife.config[:ssh_user] = "rooty" - @knife.config[:identity_file] = "~/.ssh/me.rsa" - @knife.stub!(:read_template).and_return("") - @knife_ssh = @knife.knife_ssh end - it "prompts the user for a password " do - @knife.stub!(:knife_ssh).and_return(@knife_ssh) - @knife_ssh.stub!(:get_password).and_return('typed_in_password') - alternate_knife_ssh = @knife.knife_ssh_with_password_auth - alternate_knife_ssh.config[:ssh_password].should == 'typed_in_password' - end + describe "when falling back to password auth when host key auth fails" do + before do + @knife.name_args = ["foo.example.com"] + @knife.config[:ssh_user] = "rooty" + @knife.config[:identity_file] = "~/.ssh/me.rsa" + @knife.stub!(:read_template).and_return("") + @knife_ssh = @knife.knife_ssh + end - it "configures knife not to use the identity file that didn't work previously" do - @knife.stub!(:knife_ssh).and_return(@knife_ssh) - @knife_ssh.stub!(:get_password).and_return('typed_in_password') - alternate_knife_ssh = @knife.knife_ssh_with_password_auth - alternate_knife_ssh.config[:identity_file].should be_nil + it "prompts the user for a password " do + @knife.stub!(:knife_ssh).and_return(@knife_ssh) + @knife_ssh.stub!(:get_password).and_return('typed_in_password') + alternate_knife_ssh = @knife.knife_ssh_with_password_auth + alternate_knife_ssh.config[:ssh_password].should == 'typed_in_password' + end + + it "configures knife not to use the identity file that didn't work previously" do + @knife.stub!(:knife_ssh).and_return(@knife_ssh) + @knife_ssh.stub!(:get_password).and_return('typed_in_password') + alternate_knife_ssh = @knife.knife_ssh_with_password_auth + alternate_knife_ssh.config[:identity_file].should be_nil + end end end @@ -262,6 +339,17 @@ describe Chef::Knife::Bootstrap do @knife.run end + context "Chef::Config[:encrypted_data_bag_secret] is set" do + let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') } + before { Chef::Config[:encrypted_data_bag_secret] = secret_file } + + it "warns the configuration option is deprecated" do + @knife_ssh.should_receive(:run) + @knife.ui.should_receive(:warn).at_least(3).times + @knife.run + end + end + end end diff --git a/spec/unit/knife/configure_spec.rb b/spec/unit/knife/configure_spec.rb index 8d5b51551d..87f123243d 100644 --- a/spec/unit/knife/configure_spec.rb +++ b/spec/unit/knife/configure_spec.rb @@ -21,20 +21,36 @@ describe Chef::Knife::Configure do @err = StringIO.new @knife.ui.stub!(:stderr).and_return(@err) - @ohai = Ohai::System.new - @ohai.stub(:require_plugin) - @ohai[:fqdn] = "foo.example.org" - Ohai::System.stub!(:new).and_return(@ohai) + Ohai::System.stub!(:new).and_return(ohai) end after do Chef::Config.configuration.replace(@original_config) end + + let(:fqdn) { "foo.example.org" } + + let(:ohai) do + o = {} + o.stub(:require_plugin) + o[:fqdn] = fqdn + o + end + + let(:default_admin_key) { "/etc/chef-server/admin.pem" } + let(:default_admin_key_win32) { "C:#{default_admin_key}" } + + let(:default_validator_key) { "/etc/chef-server/chef-validator.pem" } + let(:default_validator_key_win32) { "C:#{default_validator_key}" } + + let(:default_server_url) { "https://#{fqdn}:443" } + + it "asks the user for the URL of the chef server" do @knife.ask_user_for_config - @out.string.should match(Regexp.escape('Please enter the chef server URL: [http://foo.example.org:4000]')) - @knife.chef_server.should == 'http://foo.example.org:4000' + @out.string.should match(Regexp.escape("Please enter the chef server URL: [#{default_server_url}]")) + @knife.chef_server.should == default_server_url end it "asks the user for the clientname they want for the new client if -i is specified" do @@ -85,11 +101,11 @@ describe Chef::Knife::Configure do it "asks the user for the location of the existing admin key if -i is specified" do @knife.config[:initial] = true @knife.ask_user_for_config - @out.string.should match(Regexp.escape("Please enter the location of the existing admin's private key: [/etc/chef/admin.pem]")) + @out.string.should match(Regexp.escape("Please enter the location of the existing admin's private key: [#{default_admin_key}]")) if windows? - @knife.admin_client_key.should == 'C:/etc/chef/admin.pem' + @knife.admin_client_key.should == default_admin_key_win32 else - @knife.admin_client_key.should == '/etc/chef/admin.pem' + @knife.admin_client_key.should == default_admin_key end end @@ -107,11 +123,11 @@ describe Chef::Knife::Configure do it "should not ask the user for the location of the existing admin key if -i is not specified" do @knife.ask_user_for_config - @out.string.should_not match(Regexp.escape("Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]")) + @out.string.should_not match(Regexp.escape("Please enter the location of the existing admin client's private key: [#{default_admin_key}]")) if windows? - @knife.admin_client_key.should_not == 'C:/etc//chef/webui.pem' + @knife.admin_client_key.should_not == default_admin_key_win32 else - @knife.admin_client_key.should_not == '/etc/chef/webui.pem' + @knife.admin_client_key.should_not == default_admin_key end end @@ -136,11 +152,11 @@ describe Chef::Knife::Configure do it "asks the users for the location of the validation key" do @knife.ask_user_for_config - @out.string.should match(Regexp.escape("Please enter the location of the validation key: [/etc/chef/validation.pem]")) + @out.string.should match(Regexp.escape("Please enter the location of the validation key: [#{default_validator_key}]")) if windows? - @knife.validation_key.should == 'C:/etc/chef/validation.pem' + @knife.validation_key.should == default_validator_key_win32 else - @knife.validation_key.should == '/etc/chef/validation.pem' + @knife.validation_key.should == default_validator_key end end @@ -189,8 +205,8 @@ describe Chef::Knife::Configure do it "writes the new data to a config file" do File.stub!(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") File.stub!(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem") - File.stub!(:expand_path).with("/etc/chef/validation.pem").and_return("/etc/chef/validation.pem") - File.stub!(:expand_path).with("/etc/chef/webui.pem").and_return("/etc/chef/webui.pem") + File.stub!(:expand_path).with(default_validator_key).and_return(default_validator_key) + File.stub!(:expand_path).with(default_admin_key).and_return(default_admin_key) FileUtils.should_receive(:mkdir_p).with("/home/you/.chef") config_file = StringIO.new ::File.should_receive(:open).with("/home/you/.chef/knife.rb", "w").and_yield config_file @@ -199,16 +215,16 @@ describe Chef::Knife::Configure do config_file.string.should match(/^node_name[\s]+'#{Etc.getlogin}'$/) config_file.string.should match(%r{^client_key[\s]+'/home/you/.chef/#{Etc.getlogin}.pem'$}) config_file.string.should match(/^validation_client_name\s+'chef-validator'$/) - config_file.string.should match(%r{^validation_key\s+'/etc/chef/validation.pem'$}) - config_file.string.should match(%r{^chef_server_url\s+'http://foo.example.org:4000'$}) + config_file.string.should match(%r{^validation_key\s+'#{default_validator_key}'$}) + config_file.string.should match(%r{^chef_server_url\s+'#{default_server_url}'$}) config_file.string.should match(%r{cookbook_path\s+\[ '/home/you/chef-repo/cookbooks' \]}) end it "creates a new client when given the --initial option" do File.should_receive(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") File.should_receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem") - File.should_receive(:expand_path).with("/etc/chef/validation.pem").and_return("/etc/chef/validation.pem") - File.should_receive(:expand_path).with("/etc/chef/admin.pem").and_return("/etc/chef/admin.pem") + File.should_receive(:expand_path).with(default_validator_key).and_return(default_validator_key) + File.should_receive(:expand_path).with(default_admin_key).and_return(default_admin_key) Chef::Config[:node_name] = "webmonkey.example.com" user_command = Chef::Knife::UserCreate.new diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb index 4659e60371..d9f1b8f1ce 100644 --- a/spec/unit/knife/cookbook_upload_spec.rb +++ b/spec/unit/knife/cookbook_upload_spec.rb @@ -42,7 +42,8 @@ describe Chef::Knife::CookbookUpload do describe 'run' do before(:each) do - @knife.stub!(:upload).and_return(true) + @cookbook_uploader = stub(:upload_cookbooks => nil) + Chef::CookbookUploader.stub(:new => @cookbook_uploader) Chef::CookbookVersion.stub(:list_all_versions).and_return({}) end @@ -164,8 +165,11 @@ describe Chef::Knife::CookbookUpload do describe 'when a frozen cookbook exists on the server' do it 'should fail to replace it' do - @knife.stub!(:upload).and_raise(Chef::Exceptions::CookbookFrozen) - @knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/) + exception = Chef::Exceptions::CookbookFrozen.new + @cookbook_uploader.should_receive(:upload_cookbooks). + and_raise(exception) + @knife.ui.stub(:error) + @knife.ui.should_receive(:error).with(exception) lambda { @knife.run }.should raise_error(SystemExit) end @@ -180,4 +184,4 @@ describe Chef::Knife::CookbookUpload do end end end # run -end # Chef::Knife::CookbookUpload +end diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 65c02beff0..808a6f27ad 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -20,47 +20,29 @@ require 'spec_helper' require 'chef/knife/core/bootstrap_context' describe Chef::Knife::Core::BootstrapContext do - before do - @config = {:foo => :bar} - @run_list = Chef::RunList.new('recipe[tmux]', 'role[base]') - @chef_config = {:validation_key => File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem')} - @chef_config[:chef_server_url] = 'http://chef.example.com:4444' - @chef_config[:validation_client_name] = 'chef-validator-testing' - @context = Chef::Knife::Core::BootstrapContext.new(@config, @run_list, @chef_config) + let(:config) { {:foo => :bar} } + let(:run_list) { Chef::RunList.new('recipe[tmux]', 'role[base]') } + let(:chef_config) do + { + :validation_key => File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem'), + :chef_server_url => 'http://chef.example.com:4444', + :validation_client_name => 'chef-validator-testing' + } end + let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') } - describe "to support compatability with existing templates" do - it "sets the @config instance variable" do - @context.instance_eval { @config }.should == {:foo => :bar} - end - - it "sets the @run_list instance variable" do - @context.instance_eval { @run_list }.should equal(@run_list) - end - end + subject(:bootstrap_context) { described_class.new(config, run_list, chef_config) } it "installs the same version of chef on the remote host" do - @context.bootstrap_version_string.should == "--version #{Chef::VERSION}" + bootstrap_context.bootstrap_version_string.should eq "--version #{Chef::VERSION}" end it "runs chef with the first-boot.json in the _default environment" do - @context.start_chef.should == "chef-client -j /etc/chef/first-boot.json -E _default" - end - - it "it runs chef-client from another path when specified" do - @chef_config[:chef_client_path] = '/usr/local/bin/chef-client' - @context.start_chef.should == "/usr/local/bin/chef-client -j /etc/chef/first-boot.json -E _default" + bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default" end it "reads the validation key" do - @context.validation_key.should == IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem')) - end - - it "reads the validation key when it contains a ~" do - IO.should_receive(:read).with(File.expand_path("my.key", ENV['HOME'])) - @chef_config = {:validation_key => '~/my.key'} - @context = Chef::Knife::Core::BootstrapContext.new(@config, @run_list, @chef_config) - @context.validation_key + bootstrap_context.validation_key.should eq IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem')) end it "generates the config file data" do @@ -71,65 +53,100 @@ chef_server_url "http://chef.example.com:4444" validation_client_name "chef-validator-testing" # Using default node name (fqdn) EXPECTED - @context.config_content.should == expected + bootstrap_context.config_content.should eq expected end - describe "when an explicit node name is given" do - before do - @config[:chef_node_name] = 'foobar.example.com' + describe "alternate chef-client path" do + let(:chef_config){ {:chef_client_path => '/usr/local/bin/chef-client'} } + it "runs chef-client from another path when specified" do + bootstrap_context.start_chef.should eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json -E _default" end - it "sets the node name in the client.rb" do - @context.config_content.should match(/node_name "foobar\.example\.com"/) + end + + describe "validation key path that contains a ~" do + let(:chef_config){ {:validation_key => '~/my.key'} } + it "reads the validation key when it contains a ~" do + IO.should_receive(:read).with(File.expand_path("my.key", ENV['HOME'])) + bootstrap_context.validation_key end end - describe "when bootstrapping into a specific environment" do - before do - @chef_config[:environment] = "prodtastic" + describe "when an explicit node name is given" do + let(:config){ {:chef_node_name => 'foobar.example.com' }} + it "sets the node name in the client.rb" do + bootstrap_context.config_content.should match(/node_name "foobar\.example\.com"/) end + end + describe "when bootstrapping into a specific environment" do + let(:chef_config){ {:environment => "prodtastic"} } it "starts chef in the configured environment" do - @context.start_chef.should == 'chef-client -j /etc/chef/first-boot.json -E prodtastic' + bootstrap_context.start_chef.should == 'chef-client -j /etc/chef/first-boot.json -E prodtastic' end end describe "when installing a prerelease version of chef" do - before do - @config[:prerelease] = true - end + let(:config){ {:prerelease => true }} it "supplies --prerelease as the version string" do - @context.bootstrap_version_string.should == '--prerelease' + bootstrap_context.bootstrap_version_string.should eq '--prerelease' end end describe "when installing an explicit version of chef" do - before do - @context = Chef::Knife::Core::BootstrapContext.new(@config, @run_list, :knife => { :bootstrap_version => '123.45.678' }) + let(:chef_config) do + { + :knife => { :bootstrap_version => '123.45.678' } + } end - it "gives --version $VERSION as the version string" do - @context.bootstrap_version_string.should == '--version 123.45.678' + bootstrap_context.bootstrap_version_string.should eq '--version 123.45.678' end end - - describe "when JSON attributes are given" do - before do - conf = @config.dup - conf[:first_boot_attributes] = {:baz => :quux} - @context = Chef::Knife::Core::BootstrapContext.new(conf, @run_list, @chef_config) - end + describe "when JSON attributes are given" do + let(:config) { {:first_boot_attributes => {:baz => :quux}} } it "adds the attributes to first_boot" do - @context.first_boot.to_json.should == {:baz => :quux, :run_list => @run_list}.to_json + bootstrap_context.first_boot.to_json.should eq({:baz => :quux, :run_list => run_list}.to_json) end end - + describe "when JSON attributes are NOT given" do it "sets first_boot equal to run_list" do - @context.first_boot.to_json.should == {:run_list => @run_list}.to_json + bootstrap_context.first_boot.to_json.should eq({:run_list => run_list}.to_json) + end + end + + describe "when an encrypted_data_bag_secret is provided" do + context "via config[:secret]" do + let(:config){ {:secret => "supersekret" }} + it "reads the encrypted_data_bag_secret" do + bootstrap_context.encrypted_data_bag_secret.should eq "supersekret" + end + end + + context "via config[:secret_file]" do + let(:config){ {:secret_file => secret_file}} + it "reads the encrypted_data_bag_secret" do + bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file) + end + end + end + + describe "to support compatibility with existing templates" do + it "sets the @config instance variable" do + bootstrap_context.instance_variable_get(:@config).should eq config + end + + it "sets the @run_list instance variable" do + bootstrap_context.instance_variable_get(:@run_list).should eq run_list + end + + describe "accepts encrypted_data_bag_secret via Chef::Config" do + let(:chef_config) { {:encrypted_data_bag_secret => secret_file }} + it "reads the encrypted_data_bag_secret" do + bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file) + end end end - - end diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index 784ad1f0d7..1513a66797 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -40,7 +40,30 @@ describe Chef::Knife::UI do end end + shared_examples "an output mehthod handling IO exceptions" do |method| + it "should throw Errno::EIO exceptions" do + @out.stub(:puts).and_raise(Errno::EIO) + @err.stub(:puts).and_raise(Errno::EIO) + lambda {@ui.send(method, "hi")}.should raise_error(Errno::EIO) + end + + it "should ignore Errno::EPIPE exceptions (CHEF-3516)" do + @out.stub(:puts).and_raise(Errno::EPIPE) + @err.stub(:puts).and_raise(Errno::EPIPE) + lambda {@ui.send(method, "hi")}.should_not raise_error(Errno::EPIPE) + end + + it "should throw Errno::EPIPE exceptions with -VV (CHEF-3516)" do + @config[:verbosity] = 2 + @out.stub(:puts).and_raise(Errno::EPIPE) + @err.stub(:puts).and_raise(Errno::EPIPE) + lambda {@ui.send(method, "hi")}.should raise_error(Errno::EPIPE) + end + end + describe "output" do + it_behaves_like "an output mehthod handling IO exceptions", :output + it "formats strings appropriately" do @ui.output("hi") @out.string.should == "hi\n" @@ -187,6 +210,18 @@ EOM end end + describe "warn" do + it_behaves_like "an output mehthod handling IO exceptions", :warn + end + + describe "error" do + it_behaves_like "an output mehthod handling IO exceptions", :warn + end + + describe "fatal" do + it_behaves_like "an output mehthod handling IO exceptions", :warn + end + describe "format_for_display" do it "should return the raw data" do input = { :gi => :go } diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index d4d721a20d..4c5bb3e207 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -99,6 +99,9 @@ describe Chef::Knife do Chef::Knife.subcommands_by_category['cookbook site'].should include('test_explicit_category') end + it "has empty dependency_loader list by default" do + KnifeSpecs::TestNameMapping.dependency_loaders.should be_empty + end end describe "after loading all subcommands" do @@ -180,6 +183,21 @@ describe Chef::Knife do lambda {Chef::Knife.run(%w{fuuu uuuu fuuuu})}.should raise_error(SystemExit) { |e| e.status.should_not == 0 } end + it "loads lazy dependencies" do + command = Chef::Knife.run(%w{test yourself}) + KnifeSpecs::TestYourself.test_deps_loaded.should be_true + end + + it "loads lazy dependencies from multiple deps calls" do + other_deps_loaded = false + KnifeSpecs::TestYourself.class_eval do + deps { other_deps_loaded = true } + end + command = Chef::Knife.run(%w{test yourself}) + KnifeSpecs::TestYourself.test_deps_loaded.should be_true + other_deps_loaded.should be_true + end + describe "merging configuration options" do before do KnifeSpecs::TestYourself.option(:opt_with_default, @@ -228,6 +246,9 @@ describe Chef::Knife do @knife.name_args.should == %w{with some args} end + it "does not have lazy dependencies loaded" do + @knife.class.test_deps_loaded.should_not be_true + end end describe "when formatting exceptions" do diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index a158bce440..19d3504f3c 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -18,43 +18,68 @@ require 'spec_helper' -describe "override logging" do - before :each do - $stderr.stub!(:write) +module LwrpConstScopingConflict +end + +describe "LWRP" do + before do + @original_VERBOSE = $VERBOSE + $VERBOSE = nil end - it "should log if attempting to load resource of same name" do - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| - Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) - end + after do + $VERBOSE = @original_VERBOSE + end - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file| - Chef::Log.should_receive(:info).with(/overriding/) - Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) + describe "when overriding an existing class" do + before :each do + $stderr.stub!(:write) end - end - it "should log if attempting to load provider of same name" do - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*"))].each do |file| - Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) + it "should log if attempting to load resource of same name" do + Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| + Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) + end + + Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| + Chef::Log.should_receive(:info).with(/overriding/) + Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) + end end - Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "providers", "*"))].each do |file| - Chef::Log.should_receive(:info).with(/overriding/) - Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) + it "should log if attempting to load provider of same name" do + Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| + Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) + end + + Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| + Chef::Log.should_receive(:info).with(/overriding/) + Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) + end end - end -end + it "removes the old LRWP resource class from the list of resource subclasses [CHEF-3432]" do + # CHEF-3432 regression test: + # Chef::Resource keeps a list of all subclasses to assist class inflation + # for json parsing (see Chef::JSONCompat). When replacing LWRP resources, + # we need to ensure the old resource class is remove from that list. + Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| + Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) + end + first_lwr_foo_class = Chef::Resource::LwrpFoo + Chef::Resource.resource_classes.should include(first_lwr_foo_class) + Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| + Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) + end + Chef::Resource.resource_classes.should_not include(first_lwr_foo_class) + end -describe "LWRP" do - before do - @original_VERBOSE = $VERBOSE - $VERBOSE = nil - end + it "does not attempt to remove classes from higher up namespaces [CHEF-4117]" do + conflicting_lwrp_file = File.expand_path( "lwrp_const_scoping/resources/conflict.rb", CHEF_SPEC_DATA) + # The test is that this should not raise an error: + Chef::Resource::LWRPBase.build_from_file("lwrp_const_scoping", conflicting_lwrp_file, nil) + end - after do - $VERBOSE = @original_VERBOSE end describe "Lightweight Chef::Resource" do diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 3d32902b94..2906db34bc 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -35,6 +35,13 @@ describe Chef::Node do lambda{Chef::Node.build('solo node')}.should raise_error(Chef::Exceptions::ValidationFailed) end + it "should be sortable" do + n1 = Chef::Node.build('alpha') + n2 = Chef::Node.build('beta') + n3 = Chef::Node.build('omega') + [n3, n1, n2].sort.should == [n1, n2, n3] + end + describe "when the node does not exist on the server" do before do response = OpenStruct.new(:code => '404') diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb index 6897837a42..21bc4298b0 100644 --- a/spec/unit/platform_spec.rb +++ b/spec/unit/platform_spec.rb @@ -57,6 +57,9 @@ describe Chef::Platform do before(:each) do Chef::Platform.platforms = { :darwin => { + ">= 10.11" => { + :file => "new_darwinian" + }, "9.2.2" => { :file => "darwinian", :else => "thing" @@ -83,6 +86,12 @@ describe Chef::Platform do pmap[:file].should eql("darwinian") end + it "should allow you to look up a platform by name and version using \"greater than\" style operators" do + pmap = Chef::Platform.find("Darwin", "11.1.0") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql("new_darwinian") + end + it "should use the default providers for an os if the specific version does not exist" do pmap = Chef::Platform.find("Darwin", "1") pmap.should be_a_kind_of(Hash) @@ -135,6 +144,10 @@ describe Chef::Platform do Chef::Platform.find_provider_for_node(node, kitty).should eql("nice") end + it "should not throw an exception when the platform version has an unknown format" do + Chef::Platform.find_provider(:darwin, "bad-version", :file).should eql("old school") + end + it "should prefer an explicit provider" do kitty = Chef::Resource::Cat.new("loulou") kitty.stub!(:provider).and_return(Chef::Provider::File) diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb index a944793a89..bf913cca12 100644 --- a/spec/unit/provider/execute_spec.rb +++ b/spec/unit/provider/execute_spec.rb @@ -27,7 +27,7 @@ describe Chef::Provider::Execute do @new_resource = Chef::Resource::Execute.new("foo_resource", @run_context) @new_resource.timeout 3600 @new_resource.returns 0 - @new_resource.creates "foo_resource" + @new_resource.creates "/foo_resource" @provider = Chef::Provider::Execute.new(@new_resource, @run_context) @current_resource = Chef::Resource::Ifconfig.new("foo_resource", @run_context) @provider.current_resource = @current_resource @@ -46,6 +46,7 @@ describe Chef::Provider::Execute do opts[:log_tag] = @new_resource.to_s opts[:live_stream] = STDOUT @provider.should_receive(:shell_out!).with(@new_resource.command, opts) + Chef::Log.should_not_receive(:warn) @provider.run_action(:run) @new_resource.should be_updated @@ -55,6 +56,31 @@ describe Chef::Provider::Execute do @provider.stub!(:load_current_resource) File.should_receive(:exists?).with(@new_resource.creates).and_return(true) @provider.should_not_receive(:shell_out!) + Chef::Log.should_not_receive(:warn) + + @provider.run_action(:run) + @new_resource.should_not be_updated + end + + it "should respect cwd options for 'creates'" do + @new_resource.cwd "/tmp" + @new_resource.creates "foo_resource" + @provider.stub!(:load_current_resource) + File.should_receive(:exists?).with(@new_resource.creates).and_return(false) + File.should_receive(:exists?).with(File.join("/tmp", @new_resource.creates)).and_return(true) + Chef::Log.should_not_receive(:warn) + @provider.should_not_receive(:shell_out!) + + @provider.run_action(:run) + @new_resource.should_not be_updated + end + + it "should warn if user specified relative path without cwd" do + @new_resource.creates "foo_resource" + @provider.stub!(:load_current_resource) + Chef::Log.should_receive(:warn).with(/relative path/) + File.should_receive(:exists?).with(@new_resource.creates).and_return(true) + @provider.should_not_receive(:shell_out!) @provider.run_action(:run) @new_resource.should_not be_updated diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb index c497a08e40..1e52124343 100644 --- a/spec/unit/provider/mount/mount_spec.rb +++ b/spec/unit/provider/mount/mount_spec.rb @@ -216,6 +216,32 @@ describe Chef::Provider::Mount::Mount do @provider.load_current_resource @provider.current_resource.enabled.should be_false end + + it "should not mangle the mount options if the device in fstab is a symlink" do + target = "/dev/mapper/target" + options = "rw,noexec,noauto" + + ::File.stub!(:symlink?).with(@new_resource.device).and_return(true) + ::File.stub!(:readlink).with(@new_resource.device).and_return(target) + + fstab = "#{@new_resource.device} #{@new_resource.mount_point} #{@new_resource.fstype} #{options} 1 2\n" + ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab + @provider.load_current_resource + @provider.current_resource.options.should eq(options.split(',')) + end + + it "should not mangle the mount options if the symlink target is in fstab" do + target = "/dev/mapper/target" + options = "rw,noexec,noauto" + + ::File.stub!(:symlink?).with(@new_resource.device).and_return(true) + ::File.stub!(:readlink).with(@new_resource.device).and_return(target) + + fstab = "#{target} #{@new_resource.mount_point} #{@new_resource.fstype} #{options} 1 2\n" + ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab + @provider.load_current_resource + @provider.current_resource.options.should eq(options.split(',')) + end end context "after the mount's state has been discovered" do diff --git a/spec/unit/provider/mount_spec.rb b/spec/unit/provider/mount_spec.rb index 921bde4cc9..1a98f10cb0 100644 --- a/spec/unit/provider/mount_spec.rb +++ b/spec/unit/provider/mount_spec.rb @@ -51,7 +51,7 @@ describe Chef::Provider::Mount do it "should not mount the filesystem if it is mounted" do @current_resource.stub!(:mounted).and_return(true) - @provider.should_not_receive(:mount_fs).and_return(true) + @provider.should_not_receive(:mount_fs) @provider.run_action(:mount) @new_resource.should_not be_updated_by_last_action end @@ -68,7 +68,7 @@ describe Chef::Provider::Mount do it "should not umount the filesystem if it is not mounted" do @current_resource.stub!(:mounted).and_return(false) - @provider.should_not_receive(:umount_fs).and_return(true) + @provider.should_not_receive(:umount_fs) @provider.run_action(:umount) @new_resource.should_not be_updated_by_last_action end @@ -108,14 +108,24 @@ describe Chef::Provider::Mount do describe "when enabling the filesystem to be mounted" do it "should enable the mount if it isn't enable" do @current_resource.stub!(:enabled).and_return(false) - @provider.should_receive(:enable_fs).with.and_return(true) + @provider.should_not_receive(:mount_options_unchanged?) + @provider.should_receive(:enable_fs).and_return(true) @provider.run_action(:enable) @new_resource.should be_updated_by_last_action end - it "should not enable the mount if it is enabled" do + it "should enable the mount if it is enabled and mount options have changed" do @current_resource.stub!(:enabled).and_return(true) - @provider.should_not_receive(:enable_fs).with.and_return(true) + @provider.should_receive(:mount_options_unchanged?).and_return(false) + @provider.should_receive(:enable_fs).and_return(true) + @provider.run_action(:enable) + @new_resource.should be_updated_by_last_action + end + + it "should not enable the mount if it is enabled and mount options have not changed" do + @current_resource.stub!(:enabled).and_return(true) + @provider.should_receive(:mount_options_unchanged?).and_return(true) + @provider.should_not_receive(:enable_fs).and_return(true) @provider.run_action(:enable) @new_resource.should_not be_updated_by_last_action end @@ -131,7 +141,7 @@ describe Chef::Provider::Mount do it "should not disable the mount if it isn't enabled" do @current_resource.stub!(:enabled).and_return(false) - @provider.should_not_receive(:disable_fs).with.and_return(true) + @provider.should_not_receive(:disable_fs) @provider.run_action(:disable) @new_resource.should_not be_updated_by_last_action end diff --git a/spec/unit/provider/package/smartos_spec.rb b/spec/unit/provider/package/smartos_spec.rb index b4cfe7e409..47db00988c 100644 --- a/spec/unit/provider/package/smartos_spec.rb +++ b/spec/unit/provider/package/smartos_spec.rb @@ -1,5 +1,6 @@ # # Author:: Trevor O (trevoro@joyent.com) +# Author:: Yukihiko Sawanobori (sawanoboriyu@higanworks.com) # Copyright:: Copyright (c) 2012 Opscode # License:: Apache License, Version 2.0 # @@ -72,8 +73,8 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do it "run pkgin and install the package" do out = OpenStruct.new(:stdout => nil) - @provider.should_receive(:shell_out!).with("pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out) - @provider.should_receive(:shell_out!).with("pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out) + @provider.should_receive(:shell_out!).with("/opt/local/sbin/pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out) + @provider.should_receive(:shell_out!).with("/opt/local/bin/pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out) @provider.load_current_resource @provider.install_package("varnish", "2.1.5nb2") end diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb index fab78f4917..c0b2fe4658 100644 --- a/spec/unit/provider/package/zypper_spec.rb +++ b/spec/unit/provider/package/zypper_spec.rb @@ -92,8 +92,24 @@ describe Chef::Provider::Package::Zypper do describe "install_package" do it "should run zypper install with the package name and version" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(true) @provider.should_receive(:run_command).with({ - :command => "zypper -n --no-gpg-checks install -l emacs=1.0", + :command => "zypper -n install -l emacs=1.0", + }) + @provider.install_package("emacs", "1.0") + end + it "should run zypper install without gpg checks" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false) + @provider.should_receive(:run_command).with({ + :command => "zypper -n --no-gpg-checks install -l emacs=1.0", + }) + @provider.install_package("emacs", "1.0") + end + it "should warn about gpg checks on zypper install" do + Chef::Log.should_receive(:warn).with( + /All packages will be installed without gpg signature checks/) + @provider.should_receive(:run_command).with({ + :command => "zypper -n --no-gpg-checks install -l emacs=1.0", }) @provider.install_package("emacs", "1.0") end @@ -101,6 +117,22 @@ describe Chef::Provider::Package::Zypper do describe "upgrade_package" do it "should run zypper update with the package name and version" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(true) + @provider.should_receive(:run_command).with({ + :command => "zypper -n install -l emacs=1.0", + }) + @provider.upgrade_package("emacs", "1.0") + end + it "should run zypper update without gpg checks" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false) + @provider.should_receive(:run_command).with({ + :command => "zypper -n --no-gpg-checks install -l emacs=1.0", + }) + @provider.upgrade_package("emacs", "1.0") + end + it "should warn about gpg checks on zypper upgrade" do + Chef::Log.should_receive(:warn).with( + /All packages will be installed without gpg signature checks/) @provider.should_receive(:run_command).with({ :command => "zypper -n --no-gpg-checks install -l emacs=1.0", }) @@ -110,8 +142,24 @@ describe Chef::Provider::Package::Zypper do describe "remove_package" do it "should run zypper remove with the package name" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(true) + @provider.should_receive(:run_command).with({ + :command => "zypper -n remove emacs=1.0", + }) + @provider.remove_package("emacs", "1.0") + end + it "should run zypper remove without gpg checks" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false) + @provider.should_receive(:run_command).with({ + :command => "zypper -n --no-gpg-checks remove emacs=1.0", + }) + @provider.remove_package("emacs", "1.0") + end + it "should warn about gpg checks on zypper remove" do + Chef::Log.should_receive(:warn).with( + /All packages will be installed without gpg signature checks/) @provider.should_receive(:run_command).with({ - :command => "zypper -n --no-gpg-checks remove emacs=1.0", + :command => "zypper -n --no-gpg-checks remove emacs=1.0", }) @provider.remove_package("emacs", "1.0") end @@ -122,6 +170,21 @@ describe Chef::Provider::Package::Zypper do @provider.should_receive(:remove_package).with("emacs", "1.0") @provider.purge_package("emacs", "1.0") end + it "should run zypper purge without gpg checks" do + Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false) + @provider.should_receive(:run_command).with({ + :command => "zypper -n --no-gpg-checks remove emacs=1.0", + }) + @provider.purge_package("emacs", "1.0") + end + it "should warn about gpg checks on zypper purge" do + Chef::Log.should_receive(:warn).with( + /All packages will be installed without gpg signature checks/) + @provider.should_receive(:run_command).with({ + :command => "zypper -n --no-gpg-checks remove emacs=1.0", + }) + @provider.purge_package("emacs", "1.0") + end end describe "on an older zypper" do diff --git a/spec/unit/provider/remote_file_spec.rb b/spec/unit/provider/remote_file_spec.rb index 78d7e77121..50d1ad0f40 100644 --- a/spec/unit/provider/remote_file_spec.rb +++ b/spec/unit/provider/remote_file_spec.rb @@ -123,7 +123,7 @@ describe Chef::Provider::RemoteFile, "action_create" do end it "does not download the file" do - @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) + @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt") @provider.run_action(:create) end @@ -140,7 +140,7 @@ describe Chef::Provider::RemoteFile, "action_create" do end it "should not download the file if the checksum is a partial match from the beginning" do - @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) + @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt") @provider.run_action(:create) end diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb index 3ea2902755..5cda6ddb77 100644 --- a/spec/unit/provider/service/solaris_smf_service_spec.rb +++ b/spec/unit/provider/service/solaris_smf_service_spec.rb @@ -37,6 +37,8 @@ describe Chef::Provider::Service::Solaris do @pid = 2342 @stdout_string = "state disabled" @stdout.stub!(:gets).and_return(@stdout_string) + @status = mock("Status", :exitstatus => 0, :stdout => @stdout) + @provider.stub!(:shell_out!).and_return(@status) end it "should raise an error if /bin/svcs does not exist" do @@ -84,21 +86,22 @@ describe Chef::Provider::Service::Solaris do describe "when enabling the service" do before(:each) do - #@provider = Chef::Provider::Service::Solaris.new(@node, @new_resource) @provider.current_resource = @current_resource @current_resource.enabled(true) end - it "should call svcadm enable chef" do - @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm enable chef"}) - @provider.should_receive(:service_status).and_return(@current_resource) - @provider.enable_service.should be_true + it "should call svcadm enable -s chef" do + @new_resource.stub!(:enable_command).and_return("#{@new_resource.enable_command}") + @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status) + @provider.enable_service.should be_true + @current_resource.enabled.should be_true end - it "should call svcadm enable chef for start_service" do - @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm enable chef"}) - @provider.should_receive(:service_status).and_return(@current_resource) + it "should call svcadm enable -s chef for start_service" do + @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}") + @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status) @provider.start_service.should be_true + @current_resource.enabled.should be_true end end @@ -110,16 +113,16 @@ describe Chef::Provider::Service::Solaris do @current_resource.enabled(false) end - it "should call svcadm disable chef" do - @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm disable chef"}) - @provider.should_receive(:service_status).and_return(@current_resource) - @provider.disable_service.should be_false + it "should call svcadm disable -s chef" do + @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef").and_return(@status) + @provider.disable_service.should be_true + @current_resource.enabled.should be_false end - it "should call svcadm disable chef for stop_service" do - @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm disable chef"}) - @provider.should_receive(:service_status).and_return(@current_resource) - @provider.stop_service.should be_false + it "should call svcadm disable -s chef for stop_service" do + @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef") + @provider.stop_service.should be_true + @current_resource.enabled.should be_false end end @@ -131,8 +134,8 @@ describe Chef::Provider::Service::Solaris do end it "should call svcadm refresh chef" do - @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm refresh chef"}).and_return(@status) - @provider.reload_service.should be_true + @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm refresh chef").and_return(@status) + @provider.reload_service end end diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb index dbdedecd40..bca28a2d92 100644 --- a/spec/unit/provider/service/systemd_service_spec.rb +++ b/spec/unit/provider/service/systemd_service_spec.rb @@ -135,7 +135,7 @@ describe Chef::Provider::Service::Systemd do it "should not call '/bin/systemctl start service_name' if it is already running" do @current_resource.stub!(:running).and_return(true) - @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl start #{@new_resource.service_name}"}).and_return(0) + @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl start #{@new_resource.service_name}"}) @provider.start_service end @@ -180,7 +180,7 @@ describe Chef::Provider::Service::Systemd do it "should not call '/bin/systemctl stop service_name' if it is already stopped" do @current_resource.stub!(:running).and_return(false) - @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl stop #{@new_resource.service_name}"}).and_return(0) + @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl stop #{@new_resource.service_name}"}) @provider.stop_service end end diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb index 2fc49c7aa2..4604d1b697 100644 --- a/spec/unit/provider/service/upstart_service_spec.rb +++ b/spec/unit/provider/service/upstart_service_spec.rb @@ -247,7 +247,7 @@ describe Chef::Provider::Service::Upstart do it "should not call '/sbin/start service_name' if it is already running" do @current_resource.stub!(:running).and_return(true) - @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0) + @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}) @provider.start_service() end @@ -307,7 +307,7 @@ describe Chef::Provider::Service::Upstart do it "should not call '/sbin/stop service_name' if it is already stopped" do @current_resource.stub!(:running).and_return(false) - @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}).and_return(0) + @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}) @provider.stop_service() end end diff --git a/spec/unit/provider/service_spec.rb b/spec/unit/provider/service_spec.rb index 6b2b758f21..3719af56f2 100644 --- a/spec/unit/provider/service_spec.rb +++ b/spec/unit/provider/service_spec.rb @@ -60,7 +60,7 @@ describe Chef::Provider::Service do it "should not disable the service if already disabled" do @current_resource.stub!(:enabled).and_return(false) - @provider.should_not_receive(:disable_service).and_return(true) + @provider.should_not_receive(:disable_service) @provider.run_action(:disable) @provider.new_resource.should_not be_updated end @@ -92,7 +92,7 @@ describe Chef::Provider::Service do it "should not stop the service if it's already stopped" do @current_resource.stub!(:running).and_return(false) - @provider.should_not_receive(:stop_service).and_return(true) + @provider.should_not_receive(:stop_service) @provider.run_action(:stop) @provider.new_resource.should_not be_updated end @@ -137,7 +137,7 @@ describe Chef::Provider::Service do it "should not reload the service if it's stopped" do @current_resource.stub!(:running).and_return(false) - @provider.should_not_receive(:reload_service).and_return(true) + @provider.should_not_receive(:reload_service) @provider.run_action(:stop) @provider.new_resource.should_not be_updated end diff --git a/spec/unit/provider/user/solaris_spec.rb b/spec/unit/provider/user/solaris_spec.rb new file mode 100644 index 0000000000..436450b05f --- /dev/null +++ b/spec/unit/provider/user/solaris_spec.rb @@ -0,0 +1,414 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2008, 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 'spec_helper' + +describe Chef::Provider::User::Solaris do + before(:each) do + @node = Chef::Node.new + @events = Chef::EventDispatch::Dispatcher.new + @run_context = Chef::RunContext.new(@node, {}, @events) + + @new_resource = Chef::Resource::User.new("adam", @run_context) + @new_resource.comment "Adam Jacob" + @new_resource.uid 1000 + @new_resource.gid 1000 + @new_resource.home "/home/adam" + @new_resource.shell "/usr/bin/zsh" + @new_resource.password "abracadabra" + @new_resource.system false + @new_resource.manage_home false + @new_resource.non_unique false + @current_resource = Chef::Resource::User.new("adam", @run_context) + @current_resource.comment "Adam Jacob" + @current_resource.uid 1000 + @current_resource.gid 1000 + @current_resource.home "/home/adam" + @current_resource.shell "/usr/bin/zsh" + @current_resource.password "abracadabra" + @current_resource.system false + @current_resource.manage_home false + @current_resource.non_unique false + @current_resource.supports({:manage_home => false, :non_unique => false}) + @provider = Chef::Provider::User::Solaris.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + end + + describe "when setting option" do + field_list = { + 'comment' => "-c", + 'gid' => "-g", + 'uid' => "-u", + 'shell' => "-s" + } + + field_list.each do |attribute, option| + it "should check for differences in #{attribute} between the new and current resources" do + @current_resource.should_receive(attribute) + @new_resource.should_receive(attribute) + @provider.universal_options + end + + it "should set the option for #{attribute} if the new resources #{attribute} is not nil" do + @new_resource.stub!(attribute).and_return("hola") + @provider.universal_options.should eql(" #{option} 'hola'") + end + + it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management" do + @new_resource.stub!(:supports).and_return({:manage_home => false, + :non_unique => false}) + @new_resource.stub!(attribute).and_return("hola") + @provider.universal_options.should eql(" #{option} 'hola'") + end + + it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management (using real attributes)" do + @new_resource.stub!(:manage_home).and_return(false) + @new_resource.stub!(:non_unique).and_return(false) + @new_resource.stub!(attribute).and_return("hola") + @provider.universal_options.should eql(" #{option} 'hola'") + end + end + + it "should combine all the possible options" do + match_string = "" + field_list.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option| + @new_resource.stub!(attribute).and_return("hola") + match_string << " #{option} 'hola'" + end + @provider.universal_options.should eql(match_string) + end + + describe "when we want to set a password" do + before do + @new_resource.password "hocus-pocus" + end + + it "should use its own shadow file writer to set the password" do + @provider.should_receive(:write_shadow_file) + @provider.stub!(:run_command).and_return(true) + @provider.manage_user + end + + it "should write out a modified version of the password file" do + password_file = Tempfile.new("shadow") + password_file.puts "adam:existingpassword:15441::::::" + password_file.close + @provider.password_file = password_file.path + @provider.stub!(:run_command).and_return(true) + # may not be able to write to /etc for tests... + temp_file = Tempfile.new("shadow") + Tempfile.stub!(:new).with("shadow", "/etc").and_return(temp_file) + @new_resource.password "verysecurepassword" + @provider.manage_user + ::File.open(password_file.path, "r").read.should =~ /adam:verysecurepassword:/ + password_file.unlink + end + end + + describe "when we want to create a system user" do + before do + @new_resource.manage_home(true) + @new_resource.non_unique(false) + end + + it "should set useradd -r" do + @new_resource.system(true) + @provider.useradd_options.should == " -r" + end + end + + describe "when the resource has a different home directory and supports home directory management" do + before do + @new_resource.stub!(:home).and_return("/wowaweea") + @new_resource.stub!(:supports).and_return({:manage_home => true, + :non_unique => false}) + end + + it "should set -m -d /homedir" do + @provider.universal_options.should == " -m -d '/wowaweea'" + @provider.useradd_options.should == "" + end + end + + describe "when the resource has a different home directory and supports home directory management (using real attributes)" do + before do + @new_resource.stub!(:home).and_return("/wowaweea") + @new_resource.stub!(:manage_home).and_return(true) + @new_resource.stub!(:non_unique).and_return(false) + end + + it "should set -m -d /homedir" do + @provider.universal_options.should eql(" -m -d '/wowaweea'") + @provider.useradd_options.should == "" + end + end + + describe "when the resource supports non_unique ids" do + before do + @new_resource.stub!(:supports).and_return({:manage_home => false, + :non_unique => true}) + end + + it "should set -m -o" do + @provider.universal_options.should eql(" -o") + end + end + + describe "when the resource supports non_unique ids (using real attributes)" do + before do + @new_resource.stub!(:manage_home).and_return(false) + @new_resource.stub!(:non_unique).and_return(true) + end + + it "should set -m -o" do + @provider.universal_options.should eql(" -o") + end + end + end + + describe "when creating a user" do + before(:each) do + @current_resource = Chef::Resource::User.new(@new_resource.name, @run_context) + @current_resource.username(@new_resource.username) + @provider.current_resource = @current_resource + @provider.new_resource.manage_home true + @provider.new_resource.home "/Users/mud" + @provider.new_resource.gid '23' + end + + it "runs useradd with the computed command options" do + command = "useradd -c 'Adam Jacob' -g '23' -s '/usr/bin/zsh' -u '1000' -m -d '/Users/mud' adam" + @provider.should_receive(:run_command).with({ :command => command }).and_return(true) + @provider.should_receive(:manage_password).and_return(nil) + @provider.create_user + end + + describe "and home is not specified for new system user resource" do + + before do + @provider.new_resource.system true + # there is no public API to set attribute's value to nil + @provider.new_resource.instance_variable_set("@home", nil) + end + + it "should not include -m or -d in the command options" do + command = "useradd -c 'Adam Jacob' -g '23' -s '/usr/bin/zsh' -u '1000' -r adam" + @provider.should_receive(:run_command).with({ :command => command }).and_return(true) + @provider.should_receive(:manage_password).and_return(nil) + @provider.create_user + end + + end + + end + + describe "when managing a user" do + before(:each) do + @provider.new_resource.manage_home true + @provider.new_resource.home "/Users/mud" + @provider.new_resource.gid '23' + end + + # CHEF-3423, -m must come before the username + it "runs usermod with the computed command options" do + @provider.should_receive(:run_command).with({ :command => "usermod -g '23' -m -d '/Users/mud' adam" }).and_return(true) + @provider.manage_user + end + + it "does not set the -r option to usermod" do + @new_resource.system(true) + @provider.should_receive(:run_command).with({ :command => "usermod -g '23' -m -d '/Users/mud' adam" }).and_return(true) + @provider.manage_user + end + + it "CHEF-3429: does not set -m if we aren't changing the home directory" do + @provider.should_receive(:updating_home?).and_return(false) + @provider.should_receive(:run_command).with({ :command => "usermod -g '23' adam" }).and_return(true) + @provider.manage_user + end + end + + describe "when removing a user" do + + it "should run userdel with the new resources user name" do + @provider.should_receive(:run_command).with({ :command => "userdel #{@new_resource.username}" }).and_return(true) + @provider.remove_user + end + + it "should run userdel with the new resources user name and -r if manage_home is true" do + @new_resource.stub!(:supports).and_return({ :manage_home => true, + :non_unique => false}) + @provider.should_receive(:run_command).with({ :command => "userdel -r #{@new_resource.username}"}).and_return(true) + @provider.remove_user + end + + it "should run userdel with the new resources user name if non_unique is true" do + @new_resource.stub!(:supports).and_return({ :manage_home => false, + :non_unique => true}) + @provider.should_receive(:run_command).with({ :command => "userdel #{@new_resource.username}"}).and_return(true) + @provider.remove_user + end + end + + describe "when checking the lock" do + before(:each) do + # @node = Chef::Node.new + # @new_resource = mock("Chef::Resource::User", + # :nil_object => true, + # :username => "adam" + # ) + @status = mock("Status", :exitstatus => 0) + #@provider = Chef::Provider::User::Useradd.new(@node, @new_resource) + @provider.stub!(:popen4).and_return(@status) + @stdin = mock("STDIN", :nil_object => true) + @stdout = mock("STDOUT", :nil_object => true) + @stdout.stub!(:gets).and_return("root P 09/02/2008 0 99999 7 -1") + @stderr = mock("STDERR", :nil_object => true) + @pid = mock("PID", :nil_object => true) + end + + it "should call passwd -S to check the lock status" do + @provider.should_receive(:popen4).with("passwd -S #{@new_resource.username}").and_return(@status) + @provider.check_lock + end + + it "should get the first line of passwd -S STDOUT" do + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @stdout.should_receive(:gets).and_return("root P 09/02/2008 0 99999 7 -1") + @provider.check_lock + end + + it "should return false if status begins with P" do + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.check_lock.should eql(false) + end + + it "should return false if status begins with N" do + @stdout.stub!(:gets).and_return("root N") + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.check_lock.should eql(false) + end + + it "should return true if status begins with L" do + @stdout.stub!(:gets).and_return("root L") + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.check_lock.should eql(true) + end + + it "should raise a Chef::Exceptions::User if passwd -S fails on anything other than redhat/centos" do + @node.automatic_attrs[:platform] = 'ubuntu' + @status.should_receive(:exitstatus).and_return(1) + lambda { @provider.check_lock }.should raise_error(Chef::Exceptions::User) + end + + ['redhat', 'centos'].each do |os| + it "should not raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is version 0.73-1" do + @node.automatic_attrs[:platform] = os + @stdout.stub!(:gets).and_return("passwd-0.73-1\n") + @status.should_receive(:exitstatus).twice.and_return(1) + @provider.should_receive(:popen4).with("passwd -S #{@new_resource.username}") + @provider.should_receive(:popen4).with("rpm -q passwd").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + lambda { @provider.check_lock }.should_not raise_error(Chef::Exceptions::User) + end + + it "should raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is not version 0.73-1" do + @node.automatic_attrs[:platform] = os + @stdout.stub!(:gets).and_return("passwd-0.73-2\n") + @status.should_receive(:exitstatus).twice.and_return(1) + @provider.should_receive(:popen4).with("passwd -S #{@new_resource.username}") + @provider.should_receive(:popen4).with("rpm -q passwd").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + lambda { @provider.check_lock }.should raise_error(Chef::Exceptions::User) + end + + it "should raise a Chef::Exceptions::User if passwd -S exits with something other than 0 or 1 on #{os}" do + @node.automatic_attrs[:platform] = os + @status.should_receive(:exitstatus).twice.and_return(2) + lambda { @provider.check_lock }.should raise_error(Chef::Exceptions::User) + end + end + end + + describe "when locking the user" do + it "should run usermod -L with the new resources username" do + @provider.should_receive(:run_command).with({ :command => "usermod -L #{@new_resource.username}"}) + @provider.lock_user + end + end + + describe "when unlocking the user" do + it "should run usermod -L with the new resources username" do + @provider.should_receive(:run_command).with({ :command => "usermod -U #{@new_resource.username}"}) + @provider.unlock_user + end + end + + describe "when checking if home needs updating" do + [ + { + "action" => "should return false if home matches", + "current_resource_home" => [ "/home/laurent" ], + "new_resource_home" => [ "/home/laurent" ], + "expected_result" => false + }, + { + "action" => "should return true if home doesn't match", + "current_resource_home" => [ "/home/laurent" ], + "new_resource_home" => [ "/something/else" ], + "expected_result" => true + }, + { + "action" => "should return false if home only differs by trailing slash", + "current_resource_home" => [ "/home/laurent" ], + "new_resource_home" => [ "/home/laurent/", "/home/laurent" ], + "expected_result" => false + }, + { + "action" => "should return false if home is an equivalent path", + "current_resource_home" => [ "/home/laurent" ], + "new_resource_home" => [ "/home/./laurent", "/home/laurent" ], + "expected_result" => false + }, + ].each do |home_check| + it home_check["action"] do + @provider.current_resource.home home_check["current_resource_home"].first + @current_home_mock = mock("Pathname") + @provider.new_resource.home home_check["new_resource_home"].first + @new_home_mock = mock("Pathname") + + Pathname.should_receive(:new).with(@current_resource.home).and_return(@current_home_mock) + @current_home_mock.should_receive(:cleanpath).and_return(home_check["current_resource_home"].last) + Pathname.should_receive(:new).with(@new_resource.home).and_return(@new_home_mock) + @new_home_mock.should_receive(:cleanpath).and_return(home_check["new_resource_home"].last) + + @provider.updating_home?.should == home_check["expected_result"] + end + end + it "should return true if the current home does not exist but a home is specified by the new resource" do + @new_resource = Chef::Resource::User.new("adam", @run_context) + @current_resource = Chef::Resource::User.new("adam", @run_context) + @provider = Chef::Provider::User::Solaris.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + @current_resource.home nil + @new_resource.home "/home/kitten" + + @provider.updating_home?.should == true + end + end +end diff --git a/spec/unit/provider/user/useradd_spec.rb b/spec/unit/provider/user/useradd_spec.rb index ea6caf6e0a..6a8c82a1a9 100644 --- a/spec/unit/provider/user/useradd_spec.rb +++ b/spec/unit/provider/user/useradd_spec.rb @@ -177,7 +177,7 @@ describe Chef::Provider::User::Useradd do before do @provider.new_resource.system true - # there is no public API to set attribute's value to nil + # there is no public API to set attribute's value to nil @provider.new_resource.instance_variable_set("@home", nil) end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index ffcc7ae5b4..701481e882 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -35,6 +35,21 @@ describe Chef::Resource do @resource = Chef::Resource.new("funk", @run_context) end + describe "when inherited" do + + it "adds an entry to a list of subclasses" do + subclass = Class.new(Chef::Resource) + Chef::Resource.resource_classes.should include(subclass) + end + + it "keeps track of subclasses of subclasses" do + subclass = Class.new(Chef::Resource) + subclass_of_subclass = Class.new(subclass) + Chef::Resource.resource_classes.should include(subclass_of_subclass) + end + + end + describe "when declaring the identity attribute" do it "has no identity attribute by default" do Chef::Resource.identity_attr.should be_nil diff --git a/spec/unit/shell/shell_session_spec.rb b/spec/unit/shell/shell_session_spec.rb index 3d4081e583..a0fc3028f6 100644 --- a/spec/unit/shell/shell_session_spec.rb +++ b/spec/unit/shell/shell_session_spec.rb @@ -47,6 +47,19 @@ describe Shell::ShellSession do end +describe Shell::ClientSession do + it "builds the node's run_context with the proper environment" do + @session = Shell::ClientSession.instance + @node = Chef::Node.build("foo") + @session.node = @node + @session.instance_variable_set(:@client, stub(:sync_cookbooks => {})) + @expansion = Chef::RunList::RunListExpansion.new(@node.chef_environment, []) + + @node.run_list.should_receive(:expand).with(@node.chef_environment).and_return(@expansion) + @session.rebuild_context + end +end + describe Shell::StandAloneSession do before do @session = Shell::StandAloneSession.instance @@ -109,7 +122,7 @@ describe Shell::SoloSession do @session.resource_collection.should include(kitteh) end - it "returns definitions from it's compilation object" do + it "returns definitions from its compilation object" do @session.definitions.should == @run_context.definitions end @@ -119,7 +132,7 @@ describe Shell::SoloSession do #pending "1) keep attribs in an ivar 2) pass them to the node 3) feed them to the node on reset" end - it "generates it's resource collection from the compiled cookbooks and the ad hoc recipe" do + it "generates its resource collection from the compiled cookbooks and the ad hoc recipe" do @session.stub!(:node_built?).and_return(true) kitteh_cat = Chef::Resource::Cat.new("kitteh") @run_context.resource_collection << kitteh_cat diff --git a/spec/unit/version/platform_spec.rb b/spec/unit/version/platform_spec.rb new file mode 100644 index 0000000000..6245800f9d --- /dev/null +++ b/spec/unit/version/platform_spec.rb @@ -0,0 +1,61 @@ +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2013 Onddo Labs, SL. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'chef/version/platform' + +describe Chef::Version::Platform do + + it "is a subclass of Chef::Version" do + v = Chef::Version::Platform.new('1.1') + v.should be_an_instance_of(Chef::Version::Platform) + v.should be_a_kind_of(Chef::Version) + end + + it "should transform 1 to 1.0.0" do + Chef::Version::Platform.new("1").to_s.should == "1.0.0" + end + + describe "when creating valid Versions" do + good_versions = %w(1 1.2 1.2.3 1000.80.50000 0.300.25 001.02.00003) + good_versions.each do |v| + it "should accept '#{v}'" do + Chef::Version::Platform.new v + end + end + end + + describe "when given bogus input" do + bad_versions = ["1.2.3.4", "1.2.a4", "a", "1.2 3", "1.2 a", + "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"] + the_error = Chef::Exceptions::InvalidPlatformVersion + bad_versions.each do |v| + it "should raise #{the_error} when given '#{v}'" do + lambda { Chef::Version::Platform.new v }.should raise_error(the_error) + end + end + end + + describe "<=>" do + + it "should equate versions 1 and 1.0.0" do + Chef::Version::Platform.new("1").should == Chef::Version::Platform.new("1.0.0") + end + + end + +end + diff --git a/spec/unit/version_constraint/platform_spec.rb b/spec/unit/version_constraint/platform_spec.rb new file mode 100644 index 0000000000..a7749102f0 --- /dev/null +++ b/spec/unit/version_constraint/platform_spec.rb @@ -0,0 +1,46 @@ +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2013 Onddo Labs, SL. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'chef/version_constraint/platform' + +describe Chef::VersionConstraint::Platform do + + it "is a subclass of Chef::VersionConstraint" do + v = Chef::VersionConstraint::Platform.new + v.should be_an_instance_of(Chef::VersionConstraint::Platform) + v.should be_a_kind_of(Chef::VersionConstraint) + end + + it "should work with Chef::Version::Platform classes" do + vc = Chef::VersionConstraint::Platform.new("1.0") + vc.version.should be_an_instance_of(Chef::Version::Platform) + end + + describe "include?" do + + it "pessimistic ~> x" do + vc = Chef::VersionConstraint::Platform.new "~> 1" + vc.should include "1.3.3" + vc.should include "1.4" + + vc.should_not include "2.2" + vc.should_not include "0.3.0" + end + + end +end + diff --git a/spec/unit/version_constraint_spec.rb b/spec/unit/version_constraint_spec.rb index aea7001f2b..2c1246b776 100644 --- a/spec/unit/version_constraint_spec.rb +++ b/spec/unit/version_constraint_spec.rb @@ -59,6 +59,11 @@ describe Chef::VersionConstraint do Chef::VersionConstraint.new(nil).to_s.should == ">= 0.0.0" end + it "should work with Chef::Version classes" do + vc = Chef::VersionConstraint.new("1.0") + vc.version.should be_an_instance_of(Chef::Version) + end + describe "include?" do describe "handles various input data types" do before do |