summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsersut <serdar@opscode.com>2013-05-14 11:49:16 -0700
committersersut <serdar@opscode.com>2013-05-14 11:49:16 -0700
commiteef83d8ab5943af1dd3b25ccc94ef71d772d945a (patch)
treef1e3125e4ba6fdcfb08e0f5114758f0524494112
parent564ac7a3761fd870eb867b985b01273510115cf2 (diff)
parent664ee74decc3b069269b4f079a3e7a86613b0af1 (diff)
downloadchef-eef83d8ab5943af1dd3b25ccc94ef71d772d945a.tar.gz
Merge branch 'master' into file-refactor
Conflicts: spec/unit/knife/configure_spec.rb spec/unit/provider/remote_file_spec.rb
-rw-r--r--Gemfile2
-rw-r--r--lib/chef/application/client.rb7
-rw-r--r--lib/chef/application/knife.rb4
-rw-r--r--lib/chef/client.rb23
-rw-r--r--lib/chef/config.rb88
-rw-r--r--lib/chef/cookbook_uploader.rb11
-rw-r--r--lib/chef/daemon.rb28
-rw-r--r--lib/chef/encrypted_data_bag_item.rb214
-rw-r--r--lib/chef/event_dispatch/base.rb3
-rw-r--r--lib/chef/exceptions.rb1
-rw-r--r--lib/chef/knife.rb10
-rw-r--r--lib/chef/knife/bootstrap.rb32
-rw-r--r--lib/chef/knife/bootstrap/archlinux-gems.erb2
-rw-r--r--lib/chef/knife/bootstrap/centos5-gems.erb2
-rw-r--r--lib/chef/knife/bootstrap/chef-full.erb2
-rw-r--r--lib/chef/knife/bootstrap/fedora13-gems.erb2
-rw-r--r--lib/chef/knife/bootstrap/ubuntu10.04-apt.erb2
-rw-r--r--lib/chef/knife/bootstrap/ubuntu10.04-gems.erb2
-rw-r--r--lib/chef/knife/bootstrap/ubuntu12.04-gems.erb2
-rw-r--r--lib/chef/knife/configure.rb6
-rw-r--r--lib/chef/knife/cookbook_create.rb11
-rw-r--r--lib/chef/knife/cookbook_upload.rb13
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb14
-rw-r--r--lib/chef/knife/core/ui.rb23
-rw-r--r--lib/chef/mixin/language_include_recipe.rb2
-rw-r--r--lib/chef/platform.rb502
-rw-r--r--lib/chef/platform/provider_mapping.rb516
-rw-r--r--lib/chef/platform/query_helpers.rb42
-rw-r--r--lib/chef/provider.rb2
-rw-r--r--lib/chef/provider/deploy.rb2
-rw-r--r--lib/chef/provider/execute.rb27
-rw-r--r--lib/chef/provider/mount.rb2
-rw-r--r--lib/chef/provider/mount/mount.rb6
-rw-r--r--lib/chef/provider/package/zypper.rb30
-rw-r--r--lib/chef/provider/service/solaris.rb11
-rw-r--r--lib/chef/provider/user/solaris.rb90
-rw-r--r--lib/chef/provider/user/useradd.rb67
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--lib/chef/resource.rb16
-rw-r--r--lib/chef/resource/file.rb2
-rw-r--r--lib/chef/resource/lwrp_base.rb9
-rw-r--r--lib/chef/resource_collection.rb14
-rw-r--r--lib/chef/rest.rb2
-rw-r--r--lib/chef/run_list/run_list_expansion.rb2
-rw-r--r--lib/chef/runner.rb1
-rw-r--r--lib/chef/search/query.rb4
-rw-r--r--lib/chef/shell/shell_session.rb4
-rw-r--r--lib/chef/version/platform.rb42
-rw-r--r--lib/chef/version_class.rb2
-rw-r--r--lib/chef/version_constraint.rb11
-rw-r--r--lib/chef/version_constraint/platform.rb26
-rw-r--r--lib/chef/win32/version.rb31
-rw-r--r--spec/data/bootstrap/encrypted_data_bag_secret1
-rw-r--r--spec/data/bootstrap/secret.erb9
-rw-r--r--spec/data/knife_subcommand/test_yourself.rb8
-rw-r--r--spec/data/lwrp_const_scoping/resources/conflict.rb0
-rw-r--r--spec/functional/win32/service_manager_spec.rb10
-rw-r--r--spec/functional/win32/versions_spec.rb44
-rw-r--r--spec/unit/application/client_spec.rb15
-rw-r--r--spec/unit/application/knife_spec.rb12
-rw-r--r--spec/unit/client_spec.rb19
-rw-r--r--spec/unit/config_spec.rb107
-rw-r--r--spec/unit/daemon_spec.rb26
-rw-r--r--spec/unit/encrypted_data_bag_item_spec.rb271
-rw-r--r--spec/unit/environment_spec.rb1
-rw-r--r--spec/unit/knife/bootstrap_spec.rb108
-rw-r--r--spec/unit/knife/configure_spec.rb60
-rw-r--r--spec/unit/knife/cookbook_upload_spec.rb12
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb139
-rw-r--r--spec/unit/knife/core/ui_spec.rb35
-rw-r--r--spec/unit/knife_spec.rb21
-rw-r--r--spec/unit/lwrp_spec.rb77
-rw-r--r--spec/unit/platform_spec.rb13
-rw-r--r--spec/unit/provider/execute_spec.rb28
-rw-r--r--spec/unit/provider/mount/mount_spec.rb26
-rw-r--r--spec/unit/provider/mount_spec.rb22
-rw-r--r--spec/unit/provider/package/zypper_spec.rb67
-rw-r--r--spec/unit/provider/remote_file_spec.rb344
-rw-r--r--spec/unit/provider/service/solaris_smf_service_spec.rb39
-rw-r--r--spec/unit/provider/service/systemd_service_spec.rb4
-rw-r--r--spec/unit/provider/service/upstart_service_spec.rb4
-rw-r--r--spec/unit/provider/service_spec.rb6
-rw-r--r--spec/unit/provider/user/solaris_spec.rb414
-rw-r--r--spec/unit/provider/user/useradd_spec.rb2
-rw-r--r--spec/unit/resource_spec.rb15
-rw-r--r--spec/unit/shell/shell_session_spec.rb17
-rw-r--r--spec/unit/version/platform_spec.rb61
-rw-r--r--spec/unit/version_constraint/platform_spec.rb46
-rw-r--r--spec/unit/version_constraint_spec.rb5
89 files changed, 2903 insertions, 1155 deletions
diff --git a/Gemfile b/Gemfile
index dffdef67e8..306a0b18b0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,4 @@
-source :rubygems
+source "https://rubygems.org"
gemspec
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index ca8055cf34..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",
@@ -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/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 cda8f0930c..04bf93536b 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -75,14 +75,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
@@ -117,26 +110,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
@@ -148,9 +127,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"
@@ -175,7 +151,6 @@ class Chef
group nil
umask 0022
-
# Valid log_levels are:
# * :debug
# * :info
@@ -203,25 +178,15 @@ 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
@@ -238,7 +203,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")
@@ -271,6 +235,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,
@@ -279,24 +269,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 1facc75faa..c8088ec16e 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 062e15aafb..a05f48dfc9 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -132,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]
@@ -171,6 +180,7 @@ 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:
@@ -233,6 +243,28 @@ class Chef
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/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 2d1c28ab1a..6bdb3ed342 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -169,7 +169,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/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 526d443234..d01ee2ef23 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 e76233f683..b1851c9015 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 904c758779..931f94ef79 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 6bf4a32f62..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"]
@@ -229,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
@@ -286,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 9e373380ea..1a0dc3ce52 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 match %r{^[A-Za-z]:/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,8 +123,12 @@ 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]"))
- @knife.admin_client_key.should be_nil
+ @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 == default_admin_key_win32
+ else
+ @knife.admin_client_key.should_not == default_admin_key
+ end
end
it "asks the user for the location of a chef repo" do
@@ -132,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 match %r{^[A-Za-z]:/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
@@ -185,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
@@ -195,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/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/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 37c94789bf..a328bd1e64 100644
--- a/spec/unit/provider/remote_file_spec.rb
+++ b/spec/unit/provider/remote_file_spec.rb
@@ -55,82 +55,270 @@ describe Chef::Provider::RemoteFile do
it_behaves_like Chef::Provider::File
-#describe Chef::Provider::RemoteFile, "action_create" do
-# before(:each) do
-# @resource = Chef::Resource::RemoteFile.new("seattle")
-# @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "seattle.txt")))
-# @resource.source("http://foo")
-# @node = Chef::Node.new
-# @node.name "latte"
-#
-# @events = Chef::EventDispatch::Dispatcher.new
-# @run_context = Chef::RunContext.new(@node, {}, @events)
-#
-# @provider = Chef::Provider::RemoteFile.new(@resource, @run_context)
-# #To prevent the current_resource.checksum from being overridden.
-# @provider.stub!(:load_current_resource)
-# end
-
-# describe "when fetching the file from the remote" do
-# before(:each) do
-# #@tempfile = Tempfile.new("chef-rspec-remote_file_spec-line#{__LINE__}--")
-#
-# #@rest = mock(Chef::REST, { })
-# #Chef::REST.stub!(:new).and_return(@rest)
-# #@rest.stub!(:streaming_request).and_return(@tempfile)
-# #@rest.stub!(:last_response).and_return({})
-# resource.cookbook_name = "monkey"
-# resource.source("http://opscode.com/seattle.txt")
-#
-# provider.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
-# provider.current_resource = resource.clone
-# provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
-# #File.stub!(:exists?).and_return(true)
-# #FileUtils.stub!(:cp).and_return(true)
-# #Chef::Platform.stub!(:find_platform_and_version).and_return([ :mac_os_x, "10.5.1" ])
-# setup_normal_file
-# end
-#
-# after do
-# #@tempfile.close!
-# end
-#
-# # XXX: move to http
-#
-# # CHEF-3140
-# # Some servers return tarballs as content type tar and encoding gzip, which
-# # is totally wrong. When this happens and gzip isn't disabled, Chef::REST
-# # will decompress the file for you, which is not at all what you expected
-# # to happen (you end up with an uncomressed tar archive instead of the
-# # gzipped tar archive you expected). To work around this behavior, we
-# # detect when users are fetching gzipped files and turn off gzip in
-# # Chef::REST.
-#
-# context "and the source appears to be a tarball" do
-# before do
-# @resource.source("http://example.com/tarball.tgz")
-# Chef::REST.should_receive(:new).with(URI.parse("http://example.com/tarball.tgz"), nil, nil, :disable_gzip => true).and_return(@rest)
-# end
-#
-# it "disables gzip in the http client" do
-# @provider.action_create
-# end
-# end
-#
-# # XXX: move to http
-# it "should raise an exception if it's any other kind of retriable response than 304" do
-# r = Net::HTTPMovedPermanently.new("one", "two", "three")
-# e = Net::HTTPRetriableError.new("301", r)
-# @rest.stub!(:streaming_request).and_raise(e)
-# lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPRetriableError)
-# end
-#
-# it "should raise an exception if anything else happens" do
-# r = Net::HTTPBadRequest.new("one", "two", "three")
-# e = Net::HTTPServerException.new("fake exception", r)
-# @rest.stub!(:streaming_request).and_raise(e)
-# lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPServerException)
-# end
-#
-# end
+ describe "when fetching the file from the remote" do
+ before(:each) do
+ @tempfile = Tempfile.new("chef-rspec-remote_file_spec-line#{__LINE__}--")
+
+ @rest = mock(Chef::REST, { })
+ Chef::REST.stub!(:new).and_return(@rest)
+ @rest.stub!(:streaming_request).and_return(@tempfile)
+ @rest.stub!(:create_url) { |url| url }
+ @resource.cookbook_name = "monkey"
+
+ @provider.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ @provider.current_resource = @resource.clone
+ @provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ File.stub!(:exists?).and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ Chef::Platform.stub!(:find_platform_and_version).and_return([ :mac_os_x, "10.5.1" ])
+ end
+
+ after do
+ @tempfile.close!
+ end
+
+ before do
+ @resource.source("http://opscode.com/seattle.txt")
+ end
+
+ describe "and the target location's enclosing directory does not exist" do
+ before do
+ @resource.path("/tmp/this/path/does/not/exist/file.txt")
+ end
+
+ it "raises a specific error describing the problem" do
+ lambda {@provider.run_action(:create)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+ end
+
+ shared_examples_for "source specified with multiple URIs" do
+ it "should try to download the next URI when the first one fails" do
+ @rest.should_receive(:streaming_request).with("http://foo", {}).once.and_raise(SocketError)
+ @rest.should_receive(:streaming_request).with("http://bar", {}).once.and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+
+ it "should raise an exception when all the URIs fail" do
+ @rest.should_receive(:streaming_request).with("http://foo", {}).once.and_raise(SocketError)
+ @rest.should_receive(:streaming_request).with("http://bar", {}).once.and_raise(SocketError)
+ lambda { @provider.run_action(:create) }.should raise_error(SocketError)
+ end
+
+ it "should download from only one URI when the first one works" do
+ @rest.should_receive(:streaming_request).once.and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+
+ end
+
+ describe "and the source specifies multiple URIs using multiple arguments" do
+ it_should_behave_like "source specified with multiple URIs"
+
+ before(:each) do
+ @resource.source("http://foo", "http://bar")
+ end
+ end
+
+ describe "and the source specifies multiple URIs using an array" do
+ it_should_behave_like "source specified with multiple URIs"
+
+ before(:each) do
+ @resource.source([ "http://foo", "http://bar" ])
+ end
+ end
+
+ describe "and the resource specifies a checksum" do
+
+ describe "and the existing file matches the checksum exactly" do
+ before do
+ @resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ end
+
+ it "does not download the file" do
+ @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt")
+ @provider.run_action(:create)
+ end
+
+ it "does not update the resource" do
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+
+ end
+
+ describe "and the existing file matches the given partial checksum" do
+ before do
+ @resource.checksum("0fd012fd")
+ 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")
+ @provider.run_action(:create)
+ end
+
+ it "does not update the resource" do
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+
+ end
+
+ describe "and the existing file doesn't match the given checksum" do
+ it "downloads the file" do
+ @resource.checksum("this hash doesn't match")
+ @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ end
+
+ it "does not consider the checksum a match if the matching string is offset" do
+ # i.e., the existing file is "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa"
+ @resource.checksum("fd012fd")
+ @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ end
+ end
+
+ end
+
+ describe "and the resource doesn't specify a checksum" do
+ it "should download the file from the remote URL" do
+ @resource.checksum(nil)
+ @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+ end
+
+ # CHEF-3140
+ # Some servers return tarballs as content type tar and encoding gzip, which
+ # is totally wrong. When this happens and gzip isn't disabled, Chef::REST
+ # will decompress the file for you, which is not at all what you expected
+ # to happen (you end up with an uncomressed tar archive instead of the
+ # gzipped tar archive you expected). To work around this behavior, we
+ # detect when users are fetching gzipped files and turn off gzip in
+ # Chef::REST.
+
+ context "and the target file is a tarball" do
+ before do
+ @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "seattle.tar.gz")))
+ Chef::REST.should_receive(:new).with("http://opscode.com/seattle.txt", nil, nil, :disable_gzip => true).and_return(@rest)
+ end
+
+ it "disables gzip in the http client" do
+ @provider.action_create
+ end
+
+ end
+
+ context "and the source appears to be a tarball" do
+ before do
+ @resource.source("http://example.com/tarball.tgz")
+ Chef::REST.should_receive(:new).with("http://example.com/tarball.tgz", nil, nil, :disable_gzip => true).and_return(@rest)
+ end
+
+ it "disables gzip in the http client" do
+ @provider.action_create
+ end
+ end
+
+ it "should raise an exception if it's any other kind of retriable response than 304" do
+ r = Net::HTTPMovedPermanently.new("one", "two", "three")
+ e = Net::HTTPRetriableError.new("301", r)
+ @rest.stub!(:streaming_request).and_raise(e)
+ lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPRetriableError)
+ end
+
+ it "should raise an exception if anything else happens" do
+ r = Net::HTTPBadRequest.new("one", "two", "three")
+ e = Net::HTTPServerException.new("fake exception", r)
+ @rest.stub!(:streaming_request).and_raise(e)
+ lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPServerException)
+ end
+
+ it "should checksum the raw file" do
+ @provider.should_receive(:checksum).with(@tempfile.path).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ @provider.run_action(:create)
+ end
+
+ describe "when the target file does not exist" do
+ before do
+ ::File.stub!(:exists?).with(@resource.path).and_return(false)
+ @provider.stub!(:get_from_server).and_return(@tempfile)
+ end
+
+ it "should copy the raw file to the new resource" do
+ FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ end
+
+ it "should set the new resource to updated" do
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ @resource.should be_updated
+ end
+
+ describe "and create_if_missing is invoked" do
+ it "should invoke action_create" do
+ @provider.should_receive(:action_create)
+ @provider.run_action(:create_if_missing)
+ end
+ end
+ end
+
+ describe "when the target file already exists" do
+ before do
+ ::File.stub!(:exists?).with(@resource.path).and_return(true)
+ @provider.stub!(:diff_current).and_return([
+ "--- /tmp/foo 2012-08-30 21:28:17.632782551 +0000",
+ "+++ /tmp/bar 2012-08-30 21:28:20.816975437 +0000",
+ "@@ -1 +1 @@",
+ "-foo bar",
+ "+bar foo"
+ ])
+ @provider.stub!(:get_from_server).and_return(@tempfile)
+ end
+
+ describe "and create_if_missing is invoked" do
+ it "should take no action" do
+ @provider.should_not_receive(:action_create)
+ @provider.run_action(:create_if_missing)
+ end
+ end
+
+ describe "and the file downloaded from the remote is identical to the current" do
+ it "shouldn't backup the original file" do
+ @provider.should_not_receive(:backup).with(@resource.path)
+ @provider.run_action(:create)
+ end
+
+ it "doesn't mark the resource as updated" do
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+ describe "and the checksum doesn't match" do
+ before do
+ sha2_256 = "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa-NO_MATCHY"
+ @provider.current_resource.checksum(sha2_256)
+ end
+
+ it "should backup the original file" do
+ @provider.stub!(:update_new_file_state)
+ @provider.should_receive(:backup).with(@resource.path).and_return(true)
+ @provider.run_action(:create)
+ end
+
+ it "should copy the raw file to the new resource" do
+ @provider.stub!(:update_new_file_state)
+ FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true)
+ @provider.run_action(:create)
+ end
+ end
+
+ end
+ end
+
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