diff options
author | Jay Mundrawala <jdmundrawala@gmail.com> | 2015-03-23 17:23:01 -0700 |
---|---|---|
committer | Jay Mundrawala <jdmundrawala@gmail.com> | 2015-03-23 17:23:01 -0700 |
commit | e03f116e52dac5aad1df80131b0d8ea84b95dbaf (patch) | |
tree | 137a153cf401054fdd4c7db9d006b87e4ed61661 | |
parent | 17014504259f8ccb19d782bd3b63b24657e4c9ca (diff) | |
parent | d45ee988bc5550a912a03a8e05aa3a8ffc115de6 (diff) | |
download | chef-e03f116e52dac5aad1df80131b0d8ea84b95dbaf.tar.gz |
Merge remote-tracking branch 'origin/master' into jdm/prepare-12.2.0
* origin/master: (168 commits)
DOC_CHANGES for #2881
Update Changelog for #2881
Updated changelog for #3109
Added more specs for openbsd provider unit tests
Add guard to only run dsc_resource specs on valid powershell
Added basic functional tests using File dsc resource
Useful error message when dsc resource is not found
Added skeleton for function dsc_resource spec
spec to assert failure in the case where RefreshMode is not set to Disabled
spec for when Invoke-DscResource is available and RefreshMode=Disabled
Added test for when powershell does not support Invoke-DscResource
Added skeleton of dsc_resource provider unit spec
Remove unused node object in ps_credential_spec
Added spec for resource_store
Don't test the current state of the resource until the action is run
ResourceStore does not deal with exceptions
Better error message in define_resource_requirements
Use correct minimum powershell version in spec
Guard ps_credential import
Better formatting for ps_credential spec
...
Conflicts:
DOC_CHANGES.md
RELEASE_NOTES.md
lib/chef/version.rb
114 files changed, 2467 insertions, 690 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d959832c0..7c448adbda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,30 @@ * [**Ranjib Dey**](https://github.com/ranjib): [Issue 3019](https://github.com/chef/chef/issues/3019) Fix data fetching when explicit attributes are passed +* make deploy resource attributes nillable (`symlink_before_migrate nil`) works now +* mixin the LWRP attribute DSL method into Chef::Resource directly +* make all LWRP attributes nillable +* `knife ssh` now has an --exit-on-error option that allows users to + fail-fast rather than moving on to the next machine. +* migrate macosx, windows, openbsd, and netbsd resources to dynamic resolution +* migrate cron and mdadm resources to dynamic resolution +* [Issue 3096](https://github.com/chef/chef/issues/3096) Fix OpenBSD package provider installation issues +* New `dsc_resource` resource to invoke Powershell DSC resources + +## 12.1.2 +* [Issue 3022](https://github.com/chef/chef/issues/3022): Homebrew Cask install fails + FIXME (remove on 12.2.0 release): 3022 was only merged to 12-stable and #3077 or its descendant should fix this +* [Issue 3059](https://github.com/chef/chef/issues/3059): Chef 12.1.1 yum_package silently fails +* [Issue 3078](https://github.com/chef/chef/issues/3078): Compat break in audit-mode changes + +## 12.1.1 +* [**Phil Dibowitz**](https://github.com/jaymzh): + [Issue 3008](https://github.com/chef/chef/issues/3008) Allow people to pass in `source` to package +* [Issue 3011](https://github.com/chef/chef/issues/3011) `package` provider base should include + `Chef::Mixin::Command` as there are still providers that use it. +* [**Ranjib Dey**](https://github.com/ranjib): + [Issue 3019](https://github.com/chef/chef/issues/3019) Fix data fetching when explicit attributes are passed + ## 12.1.0 * [**Andre Elizondo**](https://github.com/andrewelizondo) diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index 6b58871418..de7fe6bf92 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -5,3 +5,80 @@ Example Doc Change: ### Headline for the required change Description of the required change. --> + +### knife ssh has --exit-on-error option +`knife ssh` now has an --exit-on-error option that will cause it to +fail immediately in the face of an SSH connection error. The default +behavior is move on to the next node. + +### DSC Resource + +The `dsc_resource` resource for Windows systems that allows cookbook authors to invoke [PowerShell Desired +State Configuration](http://technet.microsoft.com/en-us/library/dn249912.aspx) resources in Chef DSL. + +#### Prerequisites + +* **Windows Management Framework 5** February Preview +* **Local Configuration Manager** must be set to have a `RefreshMode` of `Disabled` + +#### Syntax + +```ruby +dsc_resource "description" do + resource "resource_name" + property :property_name, property_value + ... + property :property_name, property_value +end +``` + +#### Attributes + +- `resource`: The friendly name of the DSC resource + +- `property`: `:property_name`, `property_value` pair for each property that must be set for the DSC resource. +`property_name` must be of the `Symbol`. The following types are supported for `property_value`, along with +their conversion into Powershell: + +| Ruby Type | Powershell Type | +|-------------------------------------|-----------------| +| Fixnum | Integer | +| Float | Double | +| FalseClass | bool($false) | +| TrueClass | bool($true) | +| Chef::Util::Powershell:PSCredential | PSCredential | +| Hash | Hashtable | +| Array | Object[] | + +- `module_name` is the name of the module that the DSC resource comes from. If it is not provided, it will + be inferred. + +#### Actions + +|Action|Description| +|------|------------------------| +|`:run`| Invoke the DSC resource| + +#### Example + +```ruby +dsc_resource "demogroupremove" do + resource :group + property :groupname, 'demo1' + property :ensure, 'present' +end + +dsc_resource "useradd" do + resource :user + property :username, "Foobar1" + property :fullname, "Foobar1" + property :password, ps_credential("P@assword!") + property :ensure, 'present' +end + +dsc_resource "AddFoobar1ToUsers" do + resource :Group + property :GroupName, "demo1" + property :MembersToInclude, ["Foobar1"] +end +``` diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 883e882a64..65eb9b69fd 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -21,6 +21,7 @@ infrastructure, and the Chef applications. Includes anything not covered by another component. ### Lieutenant +* [Thom May](https://github.com/thommay) ### Maintainers @@ -34,6 +35,7 @@ another component. * [Bryan McLellan](http://github.com/btm) * [Ranjib Dey](http://github.com/ranjib) * [AJ Christensen](https://github.com/fujin) +* [Steven Murawski](https://github.com/smurawski) ## Dev Tools @@ -70,10 +72,12 @@ The specific components of Chef related to a given platform - including (but not ### Lieutenant +* [Jon Cowie](http://github.com/jonlives) + ### Maintainers -* [Jon Cowie](http://github.com/jonlives) * [Lamont Granquist](http://github.com/lamont-granquist) +* [Phil Dibowitz](https://github.com/jaymzh) ## Ubuntu @@ -83,6 +87,7 @@ The specific components of Chef related to a given platform - including (but not * [Lamont Granquist](http://github.com/lamont-granquist) * [Ranjib Dey](http://github.com/ranjib) +* [Thom May](https://github.com/thommay) ## Windows diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5076ec0f4a..2a59d97736 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,2 +1,145 @@ -# Chef Client Release Notes 12.1.1: +# Chef Client Release Notes 12.2.0: +## Desired State Configuration (DSC) Resource + +If you are using `Windows Management Framework(WMF) 5`, you can now take advantage of the new `dsc_resource`. +This new functionality takes advantage of WMF 5's `Invoke-DscResource` cmdlet to +directly invoke resources. + +### Prerequisites + +To use this new resource, you must have the February preview of WMF 5. +This can be installed using the Powershell cookbook. It is also required that +the Local Configuration Manager(LCM) be configured with a `RefreshMode` of `Disabled`. +Doing this will preclude you from using `dsc_script`. Below we provide an example +DSC configuration: + +```powershell +# create a configuration command to generate a meta.mof to set Local Configuration Manager settings + +Configuration LCMSettings { + Node localhost { + LocalConfigurationManager { + RefreshMode = 'Disabled' + } + } +} + +# Run the configuration command and generate the meta.mof to configure a local configuration manager +LCMSettings +# Apply the local configuration manager settings found in the LCMSettings folder (by default configurations are generated +# to a folder in the current working directory named for the configuration command name +Set-DscLocalConfigurationManager -path ./LCMSettings +``` + +Running this script tells the LCM not to do document management, allowing Chef to +take over that role. While you may be able to switch this to other values mid-run, +you should not be doing this to run both `dsc_script` and `dsc_resource` resources. + +### Usage + +Once the LCM is correctly configured, you can begin using `dsc_resource` in your recipes. +You can get a list of available by running the `Get-DscResource` command. You will be +able to use any resource that does not have an `ImplementedAs` property with value +`Composite`. + +As an example, let's consider the `User` dsc resource. Start by taking a look +at what a DSC `User` resource would look like + +``` +> Get-DscResource User + +ImplementedAs Name Module Properties +------------- ---- ------ ---------- +PowerShell User PSDesiredStateConfiguration {UserName, DependsOn, Descr... + +``` + +We see here that is `ImplementedAs` is not equal to `Composite`, so it is a resource that can +be used with `dsc_resource`. We can what properties are accpeted by the `User` resource by +running + +``` +> Get-DscResource User -Syntax + +User [string] #ResourceName +{ + UserName = [string] + [ DependsOn = [string[]] ] + [ Description = [string] ] + [ Disabled = [bool] ] + [ Ensure = [string] { Absent | Present } ] + [ FullName = [string] ] + [ Password = [PSCredential] ] + [ PasswordChangeNotAllowed = [bool] ] + [ PasswordChangeRequired = [bool] ] + [ PasswordNeverExpires = [bool] ] +} +``` + +From above, the `User` resource has a require property `UserName`, however we're probably +also going to want to prover at the very least a `Password`. From above, we can see the `UserName` +property must be of type string, and `Password` needs to be of type `PSCredential`. Since there +is no native Ruby type that maps to a Powershell PSCredential, a dsl method `ps_credential` is +provided that makes creating this simple. `ps_credential` can be called as `ps_credential(password)` +or `ps_credential(username, password)`. Under the hood, this creates a +`Chef::Util::Powershell::PSCredential` which gets serialized into a Powershell PSCredential. + +The following type translations are supported: + +| Ruby Type | Powershell Type | +|-------------------------------------|-----------------| +| Fixnum | Integer | +| Float | Double | +| FalseClass | bool($false) | +| TrueClass | bool($true) | +| Chef::Util::Powershell:PSCredential | PSCredential | +| Hash | Hashtable | +| Array | Object[] | + +With this information in hand, we can now construct a Chef `dsc_resource` resource that creates +a user. + +```ruby +dsc_resource 'create foo user' do + resource :User + property :UserName, 'FooUser' + property :Password, ps_credential("P@ssword!") + property :Ensure, 'Present' +end +``` + +#### Third Party Resources +`dsc_resource` also supports the use of 3rd party DSC resources, for example the DSC Resource Kit. These +resources can be used just like you would use any `PSDesiredStateConfiguration` resource like `User`. Since +the implementation of `dsc_resource` knows how to talk to DSC resources that are visible through the +`Get-DscResource` cmdlet, it should just work. For example, if we wanted to use `xSmbShare`, we could +construct the powershell resource as + +```ruby +dsc_resource 'create smb share' do + resource :xSmbShare + property :Name, 'Foo' + property :Path, 'C:\Foo' +end +``` + +This would execute + +``` +> Get-DscResource xSmbShare + +ImplementedAs Name Module Properties +------------- ---- ------ ---------- +PowerShell xSmbShare xSmbShare {Name, Path, ChangeAccess, ... +``` + +to look up the module name, and in this case use `xSmbShare`. However, this lookup process can slow down +the process. It is also possible that there are multiple DSC resources with that name. To address these +cases, `dsc_resource` provides an aditional attribute `module_name`. You can pass the name of the module +that the resource comes from, and `dsc_resource` will make sure that it uses that module. This will +short-circuit any logic to lookup the module name, shortening the time it takes to execute the resource. + +## Notes + +- The implementation of `dsc_resource` is base on the experimental Invoke-DscResource cmdlet diff --git a/appveyor.yml b/appveyor.yml index 4efdeadf84..5ba6d30b81 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ install: - bundler --version build_script: - - bundle install + - bundle install || bundle install || bundle install test_script: - SET SPEC_OPTS=--format progress diff --git a/chef.gemspec b/chef.gemspec index d32e0005d6..17861bc4a9 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0" s.add_dependency "ohai", "~> 8.0" - s.add_dependency "ffi-yajl", "~> 1.2" + s.add_dependency "ffi-yajl", ">= 1.2", "< 3.0" s.add_dependency "net-ssh", "~> 2.6" s.add_dependency "net-ssh-multi", "~> 1.1" # CHEF-3027: The knife-cloud plugins require newer features from highline, core chef should not. diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index d5dc936f83..03fd07e9f0 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -451,9 +451,9 @@ class Chef::Application::Client < Chef::Application def audit_mode_experimental_message msg = if Chef::Config[:audit_mode] == :audit_only - "Chef-client has been configured to skip converge and run only audits." + "Chef-client has been configured to skip converge and only audit." else - "Chef-client has been configured to run audits after it converges." + "Chef-client has been configured to audit after it converges." end msg += " Audit mode is an experimental feature currently under development. API changes may occur. Use at your own risk." msg += audit_mode_settings_explaination diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb index a5dd9a6c48..a4f84ed7eb 100644 --- a/lib/chef/audit/audit_reporter.rb +++ b/lib/chef/audit/audit_reporter.rb @@ -105,7 +105,7 @@ class Chef end unless run_status - Chef::Log.debug("Run failed before audits were initialized, not sending audit report to server") + Chef::Log.debug("Run failed before audit mode was initialized, not sending audit report to server") return end diff --git a/lib/chef/audit/runner.rb b/lib/chef/audit/runner.rb index e7d1657d69..13c2823dca 100644 --- a/lib/chef/audit/runner.rb +++ b/lib/chef/audit/runner.rb @@ -45,6 +45,10 @@ class Chef RSpec.world.reporter.examples.size end + def exclusion_pattern + Regexp.new(".+[\\\/]lib[\\\/]chef[\\\/]") + end + private # Prepare to run audits: # - Require files @@ -75,11 +79,15 @@ class Chef require 'rspec' require 'rspec/its' require 'specinfra' + require 'specinfra/helper' + require 'specinfra/helper/set' require 'serverspec/helper' require 'serverspec/matcher' require 'serverspec/subject' require 'chef/audit/audit_event_proxy' require 'chef/audit/rspec_formatter' + + Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set) end # Configure RSpec just the way we like it: @@ -96,6 +104,7 @@ class Chef RSpec.configure do |c| c.color = Chef::Config[:color] c.expose_dsl_globally = false + c.backtrace_exclusion_patterns << exclusion_pattern end end @@ -131,9 +140,13 @@ class Chef end end - # Set up the backend for Specinfra/Serverspec. :exec is the local system. + # Set up the backend for Specinfra/Serverspec. :exec is the local system; on Windows, it is :cmd def configure_specinfra - Specinfra.configuration.backend = :exec + if Chef::Platform.windows? + Specinfra.configuration.backend = :cmd + else + Specinfra.configuration.backend = :exec + end end # Iterates through the control groups registered to this run_context, builds an diff --git a/lib/chef/client.rb b/lib/chef/client.rb index db7cb1bbab..f5cde4bfb3 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -451,7 +451,7 @@ class Chef if Chef::Config[:why_run] == true # why_run should probably be renamed to why_converge - Chef::Log.debug("Not running audits in 'why_run' mode - this mode is used to see potential converge changes") + Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes") elsif Chef::Config[:audit_mode] != :disabled audit_error = run_audits(run_context) end diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 2eb9870a64..a9fa9f1552 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -497,7 +497,8 @@ class Chef default(:syntax_check_cache_path) { cache_options[:path] } # Deprecated: - default(:cache_options) { { :path => PathHelper.join(file_cache_path, "checksums") } } + # Move this to the default value of syntax_cache_path when this is removed. + default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } } # Whether errors should be raised for deprecation warnings. When set to # `false` (the default setting), a warning is emitted but code using @@ -570,11 +571,12 @@ class Chef end def self.windows_home_path - env['SYSTEMDRIVE'] + env['HOMEPATH'] if env['SYSTEMDRIVE'] && env['HOMEPATH'] + Chef::Log.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.") + PathHelper.home end # returns a platform specific path to the user home dir if set, otherwise default to current directory. - default( :user_home ) { env['HOME'] || windows_home_path || env['USERPROFILE'] || Dir.pwd } + default( :user_home ) { PathHelper.home || Dir.pwd } # Enable file permission fixup for selinux. Fixup will be done # only if selinux is enabled in the system. @@ -627,7 +629,7 @@ class Chef # default :no_lazy_load, true - # Default for the chef_gem compile_time attribute. Nil is the same as false but will emit + # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to # true then the user will get backcompat behavior but with a single nag warning that cookbooks # may break with this setting in the future. The false setting is the recommended setting and diff --git a/lib/chef/dsl/powershell.rb b/lib/chef/dsl/powershell.rb new file mode 100644 index 0000000000..a17971c689 --- /dev/null +++ b/lib/chef/dsl/powershell.rb @@ -0,0 +1,29 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/util/powershell/ps_credential' + +class Chef + module DSL + module Powershell + def ps_credential(username='placeholder', password) + Chef::Util::Powershell::PSCredential.new(username, password) + end + end + end +end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index ecd84c5ba5..eea6a2f239 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -399,18 +399,18 @@ class Chef class AuditControlGroupDuplicate < RuntimeError def initialize(name) - super "Audit control group with name '#{name}' has already been defined" + super "Control group with name '#{name}' has already been defined" end end class AuditNameMissing < RuntimeError; end class NoAuditsProvided < RuntimeError def initialize - super "You must provide a block with audits" + super "You must provide a block with controls" end end class AuditsFailed < RuntimeError def initialize(num_failed, num_total) - super "Audit phase found failures - #{num_failed}/#{num_total} audits failed" + super "Audit phase found failures - #{num_failed}/#{num_total} controls failed" end end @@ -442,5 +442,20 @@ class Chef super "PID file and lockfile are not permitted to match. Specify a different location with --pid or --lockfile" end end + + class MultipleDscResourcesFound < RuntimeError + attr_reader :resources_found + def initialize(resources_found) + @resources_found = resources_found + matches_info = @resources_found.each do |r| + if r['Module'].nil? + "Resource #{r['Name']} was found in #{r['Module']['Name']}" + else + "Resource #{r['Name']} is a binary resource" + end + end + super "Found multiple matching resources. #{matches_info.join("\n")}" + end + end end end diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index 489888db8f..7144d00b5d 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -47,7 +47,7 @@ class Chef else puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time} seconds" if total_audits > 0 - puts_line " #{successful_audits}/#{total_audits} Audits succeeded" + puts_line " #{successful_audits}/#{total_audits} controls succeeded" end end end @@ -59,7 +59,7 @@ class Chef else puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time} seconds" if total_audits > 0 - puts_line " #{successful_audits} Audits succeeded" + puts_line " #{successful_audits} controls succeeded" end end end diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index e168a6bd9b..64d1d0c378 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -21,6 +21,7 @@ require 'chef/knife/data_bag_secret_options' require 'erubis' require 'chef/knife/bootstrap/chef_vault_handler' require 'chef/knife/bootstrap/client_builder' +require 'chef/util/path_helper' class Chef class Knife @@ -268,7 +269,7 @@ class Chef bootstrap_files = [] bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb") bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir - bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{template}.erb") if ENV['HOME'] + Chef::Util::PathHelper.home('.chef', 'bootstrap', "#{template}.erb") {|p| bootstrap_files << p} bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb")) bootstrap_files.flatten! diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb index 17d7a9e3b5..a87ab8e544 100644 --- a/lib/chef/knife/bootstrap/templates/chef-full.erb +++ b/lib/chef/knife/bootstrap/templates/chef-full.erb @@ -22,7 +22,7 @@ exists() { <% if knife_config[:bootstrap_install_command] %> <%= knife_config[:bootstrap_install_command] %> <% else %> - install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.chef.io/chef/install.sh" %>" + install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>" if ! exists /usr/bin/chef-client; then echo "Installing Chef Client..." if exists wget; then diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index f9b8f5008e..1f59515f44 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -28,9 +28,15 @@ class Chef attr_reader :chef_config_dir attr_reader :env - def initialize(chef_config_dir, env=ENV) - @chef_config_dir, @env = chef_config_dir, env + def initialize(chef_config_dir, env=nil) + @chef_config_dir = chef_config_dir @forced_activate = {} + + # Deprecated and un-used instance variable. + @env = env + unless env.nil? + Chef::Log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") + end end # Load all the sub-commands @@ -49,7 +55,9 @@ class Chef end # finally search ~/.chef/plugins/knife/*.rb - user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(env['HOME'], '.chef', 'plugins', 'knife'), '*.rb')) if env['HOME'] + Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p| + user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb')) + end user_specific_files end @@ -140,7 +148,7 @@ class Chef end def have_plugin_manifest? - ENV["HOME"] && File.exist?(plugin_manifest_path) + plugin_manifest_path && File.exist?(plugin_manifest_path) end def plugin_manifest @@ -148,7 +156,7 @@ class Chef end def plugin_manifest_path - File.join(ENV['HOME'], '.chef', 'plugin_manifest.json') + Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json') end private diff --git a/lib/chef/knife/exec.rb b/lib/chef/knife/exec.rb index 3e8196c616..ace4ee2300 100644 --- a/lib/chef/knife/exec.rb +++ b/lib/chef/knife/exec.rb @@ -17,6 +17,7 @@ # require 'chef/knife' +require 'chef/util/path_helper' class Chef::Knife::Exec < Chef::Knife @@ -42,7 +43,7 @@ class Chef::Knife::Exec < Chef::Knife # Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts config[:script_path] << File.join(Chef::Knife.chef_config_dir, 'scripts') if Chef::Knife.chef_config_dir - config[:script_path] << File.join(ENV['HOME'], '.chef', 'scripts') if ENV['HOME'] + Chef::Util::PathHelper.home('.chef', 'scripts') { |p| config[:script_path] << p } scripts = Array(name_args) context = Object.new diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 4569cc097e..50fedd0e49 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -31,6 +31,7 @@ class Chef require 'chef/search/query' require 'chef/mixin/shell_out' require 'chef/mixin/command' + require 'chef/util/path_helper' require 'mixlib/shellout' end @@ -103,6 +104,13 @@ class Chef :boolean => true, :default => true + option :on_error, + :short => '-e', + :long => '--exit-on-error', + :description => "Immediately exit if an error is encountered", + :boolean => true, + :proc => Proc.new { :raise } + def session config[:on_error] ||= :skip ssh_error_handler = Proc.new do |server| @@ -335,8 +343,10 @@ class Chef def screen tf = Tempfile.new("knife-ssh-screen") - if File.exist? "#{ENV["HOME"]}/.screenrc" - tf.puts("source #{ENV["HOME"]}/.screenrc") + Chef::Util::PathHelper.home('.screenrc') do |screenrc_path| + if File.exist? screenrc_path + tf.puts("source #{screenrc_path}") + end end tf.puts("caption always '%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<'") tf.puts("hardstatus alwayslastline 'knife ssh #{@name_args[0]}'") diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index 78d72dc801..baf210bfc5 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -81,34 +81,58 @@ class Chef DelayedEvaluator.new(&block) end - def set_or_return(symbol, arg, validation) + NULL_ARG = Object.new + + def nillable_set_or_return(symbol, arg, validation) iv_symbol = "@#{symbol.to_s}".to_sym - if arg == nil && self.instance_variable_defined?(iv_symbol) == true - ivar = self.instance_variable_get(iv_symbol) - if(ivar.is_a?(DelayedEvaluator)) - validate({ symbol => ivar.call }, { symbol => validation })[symbol] + if NULL_ARG.equal?(arg) + if self.instance_variable_defined?(iv_symbol) == true + get_ivar(iv_symbol, symbol, validation) else - ivar + # on access we create the iv and set it to nil for back-compat + set_ivar(iv_symbol, symbol, nil, validation) end else - if(arg.is_a?(DelayedEvaluator)) - val = arg - else - val = validate({ symbol => arg }, { symbol => validation })[symbol] + set_ivar(iv_symbol, symbol, arg, validation) + end + end - # Handle the case where the "default" was a DelayedEvaluator. In - # this case, the block yields an optional parameter of +self+, - # which is the equivalent of "new_resource" - if val.is_a?(DelayedEvaluator) - val = val.call(self) - end - end - self.instance_variable_set(iv_symbol, val) + def set_or_return(symbol, arg, validation) + iv_symbol = "@#{symbol.to_s}".to_sym + if arg == nil && self.instance_variable_defined?(iv_symbol) == true + get_ivar(iv_symbol, symbol, validation) + else + set_ivar(iv_symbol, symbol, arg, validation) end end private + def get_ivar(iv_symbol, symbol, validation) + ivar = self.instance_variable_get(iv_symbol) + if(ivar.is_a?(DelayedEvaluator)) + validate({ symbol => ivar.call }, { symbol => validation })[symbol] + else + ivar + end + end + + def set_ivar(iv_symbol, symbol, arg, validation) + if(arg.is_a?(DelayedEvaluator)) + val = arg + else + val = validate({ symbol => arg }, { symbol => validation })[symbol] + + # Handle the case where the "default" was a DelayedEvaluator. In + # this case, the block yields an optional parameter of +self+, + # which is the equivalent of "new_resource" + if val.is_a?(DelayedEvaluator) + val = val.call(self) + end + end + self.instance_variable_set(iv_symbol, val) + end + # Return the value of a parameter, or nil if it doesn't exist. def _pv_opts_lookup(opts, key) if opts.has_key?(key.to_s) @@ -239,4 +263,3 @@ class Chef end end end - diff --git a/lib/chef/mixin/powershell_type_coercions.rb b/lib/chef/mixin/powershell_type_coercions.rb new file mode 100644 index 0000000000..75b3276c84 --- /dev/null +++ b/lib/chef/mixin/powershell_type_coercions.rb @@ -0,0 +1,82 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Author:: Jay Mundrawala (<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + module Mixin + module PowershellTypeCoercions + + def type_coercions + @type_coercions ||= { + Fixnum => { :type => lambda { |x| x.to_s }}, + Float => { :type => lambda { |x| x.to_s }}, + FalseClass => { :type => lambda { |x| '$false' }}, + TrueClass => { :type => lambda { |x| '$true' }}, + Hash => {:type => Proc.new { |x| translate_hash(x)}}, + Array => {:type => Proc.new { |x| translate_array(x)}} + } + end + + def translate_type(value) + translation = type_coercions[value.class] + + if translation + translation[:type].call(value) + elsif value.respond_to? :to_psobject + "(#{value.to_psobject})" + else + safe_string(value.to_s) + end + end + + private + + def translate_hash(x) + translated = x.inject([]) do |memo, (k,v)| + memo << "#{k}=#{translate_type(v)}" + end + "@{#{translated.join(';')}}" + end + + def translate_array(x) + translated = x.map do |v| + translate_type(v) + end + "@(#{translated.join(',')})" + end + + def unsafe?(s) + ["'", '#', '`', '"'].any? do |x| + s.include? x + end + end + + def safe_string(s) + # do we need to worry about binary data? + if unsafe?(s) + encoded_str = Base64.strict_encode64(s.encode("UTF-8")) + "([System.Text.Encoding]::UTF8.GetString("\ + "[System.Convert]::FromBase64String('#{encoded_str}')"\ + "))" + else + "'#{s}'" + end + end + end + end +end diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb index 65ad042910..a0ac34f627 100644 --- a/lib/chef/mixin/windows_architecture_helper.rb +++ b/lib/chef/mixin/windows_architecture_helper.rb @@ -43,6 +43,14 @@ class Chef end def with_os_architecture(node) + node ||= begin + os_arch = ENV['PROCESSOR_ARCHITEW6432'] || + ENV['PROCESSOR_ARCHITECTURE'] + Hash.new.tap do |n| + n[:kernel] = Hash.new + n[:kernel][:machine] = os_arch == 'AMD64' ? :x86_64 : :i386 + end + end wow64_redirection_state = nil if wow64_architecture_override_required?(node, node_windows_architecture(node)) diff --git a/lib/chef/node.rb b/lib/chef/node.rb index f9f6416f14..9823185ede 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -323,7 +323,7 @@ class Chef if attrs.key?("recipes") || attrs.key?("run_list") raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only." end - Chef::Log.info("Setting the run_list to #{new_run_list.inspect} from CLI options") + Chef::Log.info("Setting the run_list to #{new_run_list.to_s} from CLI options") run_list(new_run_list) end attrs diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 3d8212e24f..0d7285729f 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -38,33 +38,16 @@ class Chef require 'chef/providers' { - :mac_os_x => { - :default => { - :package => Chef::Provider::Package::Homebrew, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl - } - }, - :mac_os_x_server => { - :default => { - :package => Chef::Provider::Package::Homebrew, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl - } - }, :freebsd => { :default => { :group => Chef::Provider::Group::Pw, :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 }, ">= 11.10" => { :ifconfig => Chef::Provider::Ifconfig::Debian @@ -79,40 +62,30 @@ class Chef :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 @@ -124,25 +97,19 @@ class Chef :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::Systemd, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat }, "< 7" => { @@ -152,17 +119,13 @@ class Chef :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::Systemd, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm }, "< 7" => { :service => Chef::Provider::Service::Redhat @@ -171,9 +134,7 @@ class Chef :fedora => { :default => { :service => Chef::Provider::Service::Systemd, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat }, "< 15" => { @@ -183,7 +144,6 @@ class Chef :opensuse => { :default => { :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Zypper, :group => Chef::Provider::Group::Suse }, @@ -195,7 +155,6 @@ class Chef :suse => { :default => { :service => Chef::Provider::Service::Systemd, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Zypper, :group => Chef::Provider::Group::Gpasswd }, @@ -207,9 +166,7 @@ class Chef :oracle => { :default => { :service => Chef::Provider::Service::Systemd, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm }, "< 7" => { :service => Chef::Provider::Service::Redhat @@ -218,9 +175,7 @@ class Chef :redhat => { :default => { :service => Chef::Provider::Service::Systemd, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat }, "< 7" => { @@ -230,27 +185,21 @@ class Chef :ibm_powerkvm => { :default => { :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat } }, :cloudlinux => { :default => { :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat } }, :parallels => { :default => { :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm, :ifconfig => Chef::Provider::Ifconfig::Redhat } }, @@ -258,46 +207,12 @@ class Chef :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::Systemd, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :mswin => { - :default => { - :env => Chef::Provider::Env::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows, - :batch => Chef::Provider::Batch, - :powershell_script => Chef::Provider::PowershellScript - } - }, - :mingw32 => { - :default => { - :env => Chef::Provider::Env::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows, - :batch => Chef::Provider::Batch, - :powershell_script => Chef::Provider::PowershellScript - } - }, - :windows => { - :default => { - :env => Chef::Provider::Env::Windows, - :user => Chef::Provider::User::Windows, - :group => Chef::Provider::Group::Windows, - :mount => Chef::Provider::Mount::Windows, - :batch => Chef::Provider::Batch, - :powershell_script => Chef::Provider::PowershellScript } }, :solaris => {}, @@ -305,7 +220,6 @@ class Chef :default => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod } }, @@ -313,7 +227,6 @@ class Chef :default => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod } }, @@ -321,7 +234,6 @@ class Chef :default => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::Solaris, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod } }, @@ -329,7 +241,6 @@ class Chef :default => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod, :user => Chef::Provider::User::Solaris, } @@ -338,14 +249,12 @@ class Chef :default => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::Ips, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod, :user => Chef::Provider::User::Solaris, }, "< 5.11" => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::Solaris, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod, :user => Chef::Provider::User::Solaris, } @@ -354,22 +263,9 @@ class Chef :default => { :mount => Chef::Provider::Mount::Solaris, :package => Chef::Provider::Package::SmartOS, - :cron => Chef::Provider::Cron::Solaris, :group => Chef::Provider::Group::Usermod } }, - :netbsd => { - :default => { - :group => Chef::Provider::Group::Groupmod - } - }, - :openbsd => { - :default => { - :group => Chef::Provider::Group::Usermod, - :package => Chef::Provider::Package::Openbsd, - :service => Chef::Provider::Service::Openbsd - } - }, :hpux => { :default => { :group => Chef::Provider::Group::Usermod @@ -380,7 +276,6 @@ class Chef :group => Chef::Provider::Group::Aix, :mount => Chef::Provider::Mount::Aix, :ifconfig => Chef::Provider::Ifconfig::Aix, - :cron => Chef::Provider::Cron::Aix, :package => Chef::Provider::Package::Aix, :user => Chef::Provider::User::Aix, :service => Chef::Provider::Service::Aix @@ -390,8 +285,6 @@ class Chef :default => { :package => Chef::Provider::Package::Paludis, :service => Chef::Provider::Service::Systemd, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm } }, :default => { diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index ff83c871fa..f7c85fbe23 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -47,6 +47,13 @@ class Chef node[:languages] && node[:languages][:powershell] && node[:languages][:powershell][:version].to_i >= 4 end + + def supports_dsc_invoke_resource?(node) + require 'rubygems' + supports_dsc?(node) && + Gem::Version.new(node[:languages][:powershell][:version]) >= + Gem::Version.new("5.0.10018.0") + end end end end diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb index 354a640e59..b6b386e5a8 100644 --- a/lib/chef/provider/batch.rb +++ b/lib/chef/provider/batch.rb @@ -22,6 +22,8 @@ class Chef class Provider class Batch < Chef::Provider::WindowsScript + provides :batch, os: "windows" + def initialize (new_resource, run_context) super(new_resource, run_context, '.bat') end diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb index 1590c624f6..4b7836947e 100644 --- a/lib/chef/provider/cron.rb +++ b/lib/chef/provider/cron.rb @@ -25,6 +25,8 @@ class Chef class Cron < Chef::Provider include Chef::Mixin::Command + provides :cron, os: "!aix" + SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly] CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment] WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday] diff --git a/lib/chef/provider/cron/aix.rb b/lib/chef/provider/cron/aix.rb index 473700bf2f..9cacbc6ec9 100644 --- a/lib/chef/provider/cron/aix.rb +++ b/lib/chef/provider/cron/aix.rb @@ -23,6 +23,8 @@ class Chef class Cron class Aix < Chef::Provider::Cron::Unix + provides :cron, os: "aix" + private # For AIX we ignore env vars/[ :mailto, :path, :shell, :home ] diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index 19e7c01ab1..4449e44689 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -42,18 +42,38 @@ class Chef # @configuration is not used by Deploy, it is only for backwards compat with # chef-deploy or capistrano hooks that might use it to get environment information - @configuration = @new_resource.to_hash + @configuration = new_resource.to_hash @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"] end + # @return [Array] create_dirs_before_symlink parameter set on the resource + def create_dirs_before_symlink + new_resource.create_dirs_before_symlink || [] + end + + # @return [Array] purge_before_symlink paremeter set on the resource + def purge_before_symlink + new_resource.purge_before_symlink || [] + end + + # @return [Hash] symlinks parameter set on the resource + def symlinks + new_resource.symlinks || {} + end + + # @return [Hash] symlink_before_migrate parameter set on the resource + def symlink_before_migrate + new_resource.symlink_before_migrate || {} + end + def whyrun_supported? true end def load_current_resource - @scm_provider.load_current_resource - @release_path = @new_resource.deploy_to + "/releases/#{release_slug}" - @shared_path = @new_resource.shared_path + scm_provider.load_current_resource + @release_path = new_resource.deploy_to + "/releases/#{release_slug}" + @shared_path = new_resource.shared_path end def sudo(command,&block) @@ -62,10 +82,10 @@ class Chef def run(command, &block) exec = execute(command, &block) - exec.user(@new_resource.user) if @new_resource.user - exec.group(@new_resource.group) if @new_resource.group + exec.user(new_resource.user) if new_resource.user + exec.group(new_resource.group) if new_resource.group exec.cwd(release_path) unless exec.cwd - exec.environment(@new_resource.environment) unless exec.environment + exec.environment(new_resource.environment) unless exec.environment converge_by("execute #{command}") do exec end @@ -78,8 +98,8 @@ class Chef #There is no reason to assume 2 deployments in a single chef run, hence fails in whyrun. end - [ @new_resource.before_migrate, @new_resource.before_symlink, - @new_resource.before_restart, @new_resource.after_restart ].each do |script| + [ new_resource.before_migrate, new_resource.before_symlink, + new_resource.before_restart, new_resource.after_restart ].each do |script| requirements.assert(:deploy, :force_deploy) do |a| callback_file = "#{release_path}/#{script}" a.assertion do @@ -100,7 +120,7 @@ class Chef save_release_state if deployed?(release_path ) if current_release?(release_path ) - Chef::Log.debug("#{@new_resource} is the latest version") + Chef::Log.debug("#{new_resource} is the latest version") else rollback_to release_path end @@ -117,7 +137,7 @@ class Chef converge_by("delete deployed app at #{release_path} prior to force-deploy") do Chef::Log.info("Already deployed app at #{release_path}, forcing.") FileUtils.rm_rf(release_path) - Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}") + Chef::Log.info("#{new_resource} forcing deploy of already deployed app at #{release_path}") end end @@ -143,7 +163,7 @@ class Chef releases_to_nuke.each do |i| converge_by("roll back by removing release #{i}") do - Chef::Log.info "#{@new_resource} removing release: #{i}" + Chef::Log.info "#{new_resource} removing release: #{i}" FileUtils.rm_rf i end release_deleted(i) @@ -157,21 +177,21 @@ class Chef copy_cached_repo install_gems enforce_ownership - callback(:before_migrate, @new_resource.before_migrate) + callback(:before_migrate, new_resource.before_migrate) migrate - callback(:before_symlink, @new_resource.before_symlink) + callback(:before_symlink, new_resource.before_symlink) symlink - callback(:before_restart, @new_resource.before_restart) + callback(:before_restart, new_resource.before_restart) restart - callback(:after_restart, @new_resource.after_restart) + callback(:after_restart, new_resource.after_restart) cleanup! - Chef::Log.info "#{@new_resource} deployed to #{@new_resource.deploy_to}" + Chef::Log.info "#{new_resource} deployed to #{new_resource.deploy_to}" end def rollback - Chef::Log.info "#{@new_resource} rolling back to previous release #{release_path}" + Chef::Log.info "#{new_resource} rolling back to previous release #{release_path}" symlink - Chef::Log.info "#{@new_resource} restarting with previous release" + Chef::Log.info "#{new_resource} restarting with previous release" restart end @@ -179,7 +199,7 @@ class Chef @collection = Chef::ResourceCollection.new case callback_code when Proc - Chef::Log.info "#{@new_resource} running callback #{what}" + Chef::Log.info "#{new_resource} running callback #{what}" recipe_eval(&callback_code) when String run_callback_from_file("#{release_path}/#{callback_code}") @@ -191,17 +211,17 @@ class Chef def migrate run_symlinks_before_migrate - if @new_resource.migrate + if new_resource.migrate enforce_ownership - environment = @new_resource.environment + environment = new_resource.environment env_info = environment && environment.map do |key_and_val| "#{key_and_val.first}='#{key_and_val.last}'" end.join(" ") - converge_by("execute migration command #{@new_resource.migration_command}") do - Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}" - run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info)) + converge_by("execute migration command #{new_resource.migration_command}") do + Chef::Log.info "#{new_resource} migrating #{new_resource.user} with environment #{env_info}" + run_command(run_options(:command => new_resource.migration_command, :cwd=>release_path, :log_level => :info)) end end end @@ -210,18 +230,18 @@ class Chef purge_tempfiles_from_current_release link_tempfiles_to_current_release link_current_release_to_production - Chef::Log.info "#{@new_resource} updated symlinks" + Chef::Log.info "#{new_resource} updated symlinks" end def restart - if restart_cmd = @new_resource.restart_command + if restart_cmd = new_resource.restart_command if restart_cmd.kind_of?(Proc) - Chef::Log.info("#{@new_resource} restarting app with embedded recipe") + Chef::Log.info("#{new_resource} restarting app with embedded recipe") recipe_eval(&restart_cmd) else - converge_by("restart app using command #{@new_resource.restart_command}") do - Chef::Log.info("#{@new_resource} restarting app") - run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path)) + converge_by("restart app using command #{new_resource.restart_command}") do + Chef::Log.info("#{new_resource} restarting app") + run_command(run_options(:command => new_resource.restart_command, :cwd => new_resource.current_path)) end end end @@ -232,10 +252,10 @@ class Chef release_created(release_path) end - chop = -1 - @new_resource.keep_releases + chop = -1 - new_resource.keep_releases all_releases[0..chop].each do |old_release| converge_by("remove old release #{old_release}") do - Chef::Log.info "#{@new_resource} removing old release #{old_release}" + Chef::Log.info "#{new_resource} removing old release #{old_release}" FileUtils.rm_rf(old_release) end release_deleted(old_release) @@ -243,11 +263,11 @@ class Chef end def all_releases - Dir.glob(Chef::Util::PathHelper.escape_glob(@new_resource.deploy_to) + "/releases/*").sort + Dir.glob(Chef::Util::PathHelper.escape_glob(new_resource.deploy_to) + "/releases/*").sort end def update_cached_repo - if @new_resource.svn_force_export + if new_resource.svn_force_export # TODO assertion, non-recoverable - @scm_provider must be svn if force_export? svn_force_export else @@ -256,95 +276,92 @@ class Chef end def run_scm_sync - @scm_provider.run_action(:sync) + scm_provider.run_action(:sync) end def svn_force_export - Chef::Log.info "#{@new_resource} exporting source repository" - @scm_provider.run_action(:force_export) + Chef::Log.info "#{new_resource} exporting source repository" + scm_provider.run_action(:force_export) end def copy_cached_repo - target_dir_path = @new_resource.deploy_to + "/releases" + target_dir_path = new_resource.deploy_to + "/releases" converge_by("deploy from repo to #{target_dir_path} ") do FileUtils.rm_rf(release_path) if ::File.exist?(release_path) FileUtils.mkdir_p(target_dir_path) - FileUtils.cp_r(::File.join(@new_resource.destination, "."), release_path, :preserve => true) - Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}" + FileUtils.cp_r(::File.join(new_resource.destination, "."), release_path, :preserve => true) + Chef::Log.info "#{new_resource} copied the cached checkout to #{release_path}" end end def enforce_ownership - converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do - FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to) - Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user - Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group + converge_by("force ownership of #{new_resource.deploy_to} to #{new_resource.group}:#{new_resource.user}") do + FileUtils.chown_R(new_resource.user, new_resource.group, new_resource.deploy_to) + Chef::Log.info("#{new_resource} set user to #{new_resource.user}") if new_resource.user + Chef::Log.info("#{new_resource} set group to #{new_resource.group}") if new_resource.group end end def verify_directories_exist - create_dir_unless_exists(@new_resource.deploy_to) - create_dir_unless_exists(@new_resource.shared_path) + create_dir_unless_exists(new_resource.deploy_to) + create_dir_unless_exists(new_resource.shared_path) end def link_current_release_to_production - converge_by(["remove existing link at #{@new_resource.current_path}", - "link release #{release_path} into production at #{@new_resource.current_path}"]) do - FileUtils.rm_f(@new_resource.current_path) + converge_by(["remove existing link at #{new_resource.current_path}", + "link release #{release_path} into production at #{new_resource.current_path}"]) do + FileUtils.rm_f(new_resource.current_path) begin - FileUtils.ln_sf(release_path, @new_resource.current_path) + FileUtils.ln_sf(release_path, new_resource.current_path) rescue => e raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}") end - Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}" + Chef::Log.info "#{new_resource} linked release #{release_path} into production at #{new_resource.current_path}" end enforce_ownership end def run_symlinks_before_migrate - links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ") + links_info = symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ") converge_by("make pre-migration symlinks: #{links_info}") do - @new_resource.symlink_before_migrate.each do |src, dest| + symlink_before_migrate.each do |src, dest| begin - FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}") + FileUtils.ln_sf(new_resource.shared_path + "/#{src}", release_path + "/#{dest}") rescue => e - raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}") + raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}") end end - Chef::Log.info "#{@new_resource} made pre-migration symlinks" + Chef::Log.info "#{new_resource} made pre-migration symlinks" end end def link_tempfiles_to_current_release - dirs_info = @new_resource.create_dirs_before_symlink.join(",") - @new_resource.create_dirs_before_symlink.each do |dir| + dirs_info = create_dirs_before_symlink.join(",") + create_dirs_before_symlink.each do |dir| create_dir_unless_exists(release_path + "/#{dir}") end - Chef::Log.info("#{@new_resource} created directories before symlinking: #{dirs_info}") + Chef::Log.info("#{new_resource} created directories before symlinking: #{dirs_info}") - links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ") + links_info = symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ") converge_by("link shared paths into current release: #{links_info}") do - @new_resource.symlinks.each do |src, dest| + symlinks.each do |src, dest| begin - FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest)) + FileUtils.ln_sf(::File.join(new_resource.shared_path, src), ::File.join(release_path, dest)) rescue => e - raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}") + raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}") end end - Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}") + Chef::Log.info("#{new_resource} linked shared paths into current release: #{links_info}") end run_symlinks_before_migrate enforce_ownership end - def create_dirs_before_symlink - end - def purge_tempfiles_from_current_release - log_info = @new_resource.purge_before_symlink.join(", ") + log_info = purge_before_symlink.join(", ") converge_by("purge directories in checkout #{log_info}") do - @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") } - Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}") + purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") } + Chef::Log.info("#{new_resource} purged directories in checkout #{log_info}") end end @@ -394,10 +411,10 @@ class Chef end def run_options(run_opts={}) - run_opts[:user] = @new_resource.user if @new_resource.user - run_opts[:group] = @new_resource.group if @new_resource.group - run_opts[:environment] = @new_resource.environment if @new_resource.environment - run_opts[:log_tag] = @new_resource.to_s + run_opts[:user] = new_resource.user if new_resource.user + run_opts[:group] = new_resource.group if new_resource.group + run_opts[:environment] = new_resource.environment if new_resource.environment + run_opts[:log_tag] = new_resource.to_s run_opts[:log_level] ||= :debug if run_opts[:log_level] == :info if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? @@ -408,7 +425,7 @@ class Chef end def run_callback_from_file(callback_file) - Chef::Log.info "#{@new_resource} queueing checkdeploy hook #{callback_file}" + Chef::Log.info "#{new_resource} queueing checkdeploy hook #{callback_file}" recipe_eval do Dir.chdir(release_path) do from_file(callback_file) if ::File.exist?(callback_file) @@ -418,20 +435,20 @@ class Chef def create_dir_unless_exists(dir) if ::File.directory?(dir) - Chef::Log.debug "#{@new_resource} not creating #{dir} because it already exists" + Chef::Log.debug "#{new_resource} not creating #{dir} because it already exists" return false end converge_by("create new directory #{dir}") do begin FileUtils.mkdir_p(dir) - Chef::Log.debug "#{@new_resource} created directory #{dir}" - if @new_resource.user - FileUtils.chown(@new_resource.user, nil, dir) - Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}") + Chef::Log.debug "#{new_resource} created directory #{dir}" + if new_resource.user + FileUtils.chown(new_resource.user, nil, dir) + Chef::Log.debug("#{new_resource} set user to #{new_resource.user} for #{dir}") end - if @new_resource.group - FileUtils.chown(nil, @new_resource.group, dir) - Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}") + if new_resource.group + FileUtils.chown(nil, new_resource.group, dir) + Chef::Log.debug("#{new_resource} set group to #{new_resource.group} for #{dir}") end rescue => e raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}") @@ -442,7 +459,7 @@ class Chef def with_rollback_on_error yield rescue ::Exception => e - if @new_resource.rollback_on_error + if new_resource.rollback_on_error Chef::Log.warn "Error on deploying #{release_path}: #{e.message}" failed_release = release_path @@ -461,8 +478,8 @@ class Chef end def save_release_state - if ::File.exists?(@new_resource.current_path) - release = ::File.readlink(@new_resource.current_path) + if ::File.exists?(new_resource.current_path) + release = ::File.readlink(new_resource.current_path) @previous_release_path = release if ::File.exists?(release) end end diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb new file mode 100644 index 0000000000..fabb695803 --- /dev/null +++ b/lib/chef/provider/dsc_resource.rb @@ -0,0 +1,157 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# +# Copyright:: 2014, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/util/powershell/cmdlet' +require 'chef/util/dsc/local_configuration_manager' +require 'chef/mixin/powershell_type_coercions' +require 'chef/util/dsc/resource_store' + +class Chef + class Provider + class DscResource < Chef::Provider + include Chef::Mixin::PowershellTypeCoercions + + provides :dsc_resource, os: "windows" + + def initialize(new_resource, run_context) + super + @new_resource = new_resource + @module_name = new_resource.module_name + end + + def action_run + if ! test_resource + converge_by(generate_description) do + result = set_resource + end + end + end + + def load_current_resource + end + + def whyrun_supported? + true + end + + def define_resource_requirements + requirements.assert(:run) do |a| + a.assertion { supports_dsc_invoke_resource? } + err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."] + a.failure_message Chef::Exceptions::NoProviderAvailable, + err + a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."] + a.block_action! + end + requirements.assert(:run) do |a| + a.assertion { + meta_configuration['RefreshMode'] == 'Disabled' + } + err = ["The LCM must have its RefreshMode set to Disabled. "] + a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ') + a.whyrun err + ["Assuming a previous resource sets the RefreshMode."] + a.block_action! + end + end + + protected + + def local_configuration_manager + @local_configuration_manager ||= Chef::Util::DSC::LocalConfigurationManager.new( + node, + nil + ) + end + + def resource_store + Chef::Util::DSC::ResourceStore.instance + end + + def supports_dsc_invoke_resource? + run_context && Chef::Platform.supports_dsc_invoke_resource?(node) + end + + def generate_description + @converge_description + end + + def dsc_resource_name + new_resource.resource.to_s + end + + def module_name + @module_name ||= begin + found = resource_store.find(dsc_resource_name) + + r = case found.length + when 0 + raise Chef::Exceptions::ResourceNotFound, + "Could not find #{dsc_resource_name}. Check to make "\ + "sure that it shows up when running Get-DscResource" + when 1 + if found[0]['Module'].nil? + :none + else + found[0]['Module'] + end + else + raise Chef::Exceptions::MultipleDscResourcesFound, found + end + end + end + + def test_resource + result = invoke_resource(:test) + # We really want this information from the verbose stream, + # however Invoke-DscResource is not correctly writing to that + # stream and instead just dumping to stdout + @converge_description = result.stdout + result.return_value[0]["InDesiredState"] + end + + def set_resource + result = invoke_resource(:set) + result.return_value + end + + def invoke_resource(method, output_format=:object) + properties = translate_type(@new_resource.properties) + switches = "-Method #{method.to_s} -Name #{@new_resource.resource}"\ + " -Property #{properties} -Verbose" + + if module_name != :none + switches += " -Module #{module_name}" + end + + cmdlet = Chef::Util::Powershell::Cmdlet.new( + node, + "Invoke-DscResource #{switches}", + output_format + ) + cmdlet.run! + end + + def meta_configuration + cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) + result = cmdlet.run! + result.return_value + end + + end + end +end diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb index 815a19bc0c..cf75ff7d85 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/env.rb @@ -26,6 +26,8 @@ class Chef include Chef::Mixin::Command attr_accessor :key_exists + provides :env, os: "!windows" + def initialize(new_resource, run_context) super @key_exists = true diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb index dd7cb1bc46..56cebdb888 100644 --- a/lib/chef/provider/env/windows.rb +++ b/lib/chef/provider/env/windows.rb @@ -24,6 +24,8 @@ class Chef class Windows < Chef::Provider::Env include Chef::Mixin::WindowsEnvHelper + provides :env, os: "windows" + def create_env obj = env_obj(@new_resource.key_name) unless obj diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb index 8418f22933..693dd3f2b2 100644 --- a/lib/chef/provider/git.rb +++ b/lib/chef/provider/git.rb @@ -39,6 +39,10 @@ class Chef end end + def additional_remotes + new_resource.additional_remotes || {} + end + def define_resource_requirements # Parent directory of the target must exist. requirements.assert(:checkout, :sync) do |a| diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb index 29738cc046..a802758dce 100644 --- a/lib/chef/provider/group.rb +++ b/lib/chef/provider/group.rb @@ -125,13 +125,13 @@ class Chef def action_create case @group_exists when false - converge_by("create #{@new_resource}") do + converge_by("create #{@new_resource.group_name}") do create_group Chef::Log.info("#{@new_resource} created") end else if compare_group - converge_by(["alter group #{@new_resource}"] + change_desc) do + converge_by(["alter group #{@new_resource.group_name}"] + change_desc) do manage_group Chef::Log.info("#{@new_resource} altered") end @@ -141,7 +141,7 @@ class Chef def action_remove if @group_exists - converge_by("remove group #{@new_resource}") do + converge_by("remove group #{@new_resource.group_name}") do remove_group Chef::Log.info("#{@new_resource} removed") end @@ -150,7 +150,7 @@ class Chef def action_manage if @group_exists && compare_group - converge_by(["manage group #{@new_resource}"] + change_desc) do + converge_by(["manage group #{@new_resource.group_name}"] + change_desc) do manage_group Chef::Log.info("#{@new_resource} managed") end @@ -159,7 +159,7 @@ class Chef def action_modify if compare_group - converge_by(["modify group #{@new_resource}"] + change_desc) do + converge_by(["modify group #{@new_resource.group_name}"] + change_desc) do manage_group Chef::Log.info("#{@new_resource} modified") end diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb index a59a94aa98..d7e8f2e827 100644 --- a/lib/chef/provider/group/dscl.rb +++ b/lib/chef/provider/group/dscl.rb @@ -21,6 +21,8 @@ class Chef class Group class Dscl < Chef::Provider::Group + provides :group, os: "darwin" + def dscl(*args) host = "." stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}" diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb index 7ad762af8d..f9299546c8 100644 --- a/lib/chef/provider/group/groupmod.rb +++ b/lib/chef/provider/group/groupmod.rb @@ -21,6 +21,8 @@ class Chef class Group class Groupmod < Chef::Provider::Group + provides :group, os: "netbsd" + def load_current_resource super [ "group", "user" ].each do |binary| diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb index e9dcc38b43..e50e13c443 100644 --- a/lib/chef/provider/group/usermod.rb +++ b/lib/chef/provider/group/usermod.rb @@ -23,6 +23,8 @@ class Chef class Group class Usermod < Chef::Provider::Group::Groupadd + provides :group, os: "openbsd" + def load_current_resource super end diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb index c9c3da29e0..54e49b0e06 100644 --- a/lib/chef/provider/group/windows.rb +++ b/lib/chef/provider/group/windows.rb @@ -26,6 +26,8 @@ class Chef class Group class Windows < Chef::Provider::Group + provides :group, os: "windows" + def initialize(new_resource,run_context) super @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name) diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb index d156e49d48..325f1b5977 100644 --- a/lib/chef/provider/mdadm.rb +++ b/lib/chef/provider/mdadm.rb @@ -23,6 +23,8 @@ class Chef class Provider class Mdadm < Chef::Provider + provides :mdadm + def popen4 raise Exception, "deprecated" end diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb index 02aa78919a..87873474b3 100644 --- a/lib/chef/provider/mount/windows.rb +++ b/lib/chef/provider/mount/windows.rb @@ -27,6 +27,8 @@ class Chef class Mount class Windows < Chef::Provider::Mount + provides :mount, os: "windows" + def is_volume(name) name =~ /^\\\\\?\\Volume\{[\w-]+\}\\$/ ? true : false end diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb index e043c01f56..603899646f 100644 --- a/lib/chef/provider/package/homebrew.rb +++ b/lib/chef/provider/package/homebrew.rb @@ -27,7 +27,7 @@ class Chef class Homebrew < Chef::Provider::Package provides :homebrew_package - provides :package, os: ["mac_os_x", "darwin"] + provides :package, os: "darwin" include Chef::Mixin::HomebrewUser diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb index f0931d7555..82048c3bd4 100644 --- a/lib/chef/provider/package/openbsd.rb +++ b/lib/chef/provider/package/openbsd.rb @@ -24,6 +24,7 @@ require 'chef/resource/package' require 'chef/provider/package' require 'chef/mixin/shell_out' require 'chef/mixin/get_source_from_package' +require 'chef/exceptions' class Chef class Provider @@ -37,25 +38,42 @@ class Chef def initialize(*args) super - @current_resource = Chef::Resource::Package.new(@new_resource.name) - @new_resource.source(pkg_path) if !@new_resource.source + @current_resource = Chef::Resource::Package.new(new_resource.name) end def load_current_resource - @current_resource.package_name(@new_resource.package_name) + @current_resource.package_name(new_resource.package_name) @current_resource.version(installed_version) @current_resource end + def define_resource_requirements + super + + # Below are incomplete/missing features for this package provider + requirements.assert(:all_actions) do |a| + a.assertion { !new_resource.source } + a.failure_message(Chef::Exceptions::Package, 'The openbsd package provider does not support the source attribute') + end + requirements.assert(:all_actions) do |a| + a.assertion do + if new_resource.package_name =~ /^(.+?)--(.+)/ + !new_resource.version + else + true + end + end + a.failure_message(Chef::Exceptions::Package, 'The openbsd package provider does not support providing a version and flavor') + end + end + def install_package(name, version) unless @current_resource.version - version_string = '' - version_string += "-#{version}" if version if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add name = parts[1] end - shell_out!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => @new_resource.source}).status - Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}") + shell_out!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status + Chef::Log.debug("#{new_resource.package_name} installed") end end @@ -71,32 +89,45 @@ class Chef private def installed_version - if parts = @new_resource.package_name.match(/^(.+?)--(.+)/) + if parts = new_resource.package_name.match(/^(.+?)--(.+)/) name = parts[1] else - name = @new_resource.package_name + name = new_resource.package_name end pkg_info = shell_out!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1]) result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1] - Chef::Log.debug("installed_version of '#{@new_resource.package_name}' is '#{result}'") + Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'") result end def candidate_version @candidate_version ||= begin - version_string = '' - version_string += "-#{version}" if @new_resource.version - pkg_info = shell_out!("pkg_info -I \"#{@new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]) - if parts = @new_resource.package_name.match(/^(.+?)--(.+)/) - result = pkg_info.stdout[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1] + results = [] + shell_out!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line| + if parts = new_resource.package_name.match(/^(.+?)--(.+)/) + results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1] + else + results << line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1] + end + end + results = results.reject(&:nil?) + Chef::Log.debug("candidate versions of '#{new_resource.package_name}' are '#{results}'") + case results.length + when 0 + [] + when 1 + results[0] else - result = pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+?)\s/, 1] + raise Chef::Exceptions::Package, "#{new_resource.name} has multiple matching candidates. Please use a more specific name" if results.length > 1 end - Chef::Log.debug("candidate_version of '#{@new_resource.package_name}' is '#{result}'") - result end end + def version_string + ver = '' + ver += "-#{new_resource.version}" if new_resource.version + end + def pkg_path ENV['PKG_PATH'] || "http://ftp.OpenBSD.org/pub/#{node.kernel.name}/#{node.kernel.release}/packages/#{node.kernel.machine}/" end diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index 8c79b384e9..f9dcd6d80c 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -22,6 +22,8 @@ class Chef class Provider class PowershellScript < Chef::Provider::WindowsScript + provides :powershell_script, os: "windows" + protected EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE ) {exit $LASTEXITCODE} else { exit 1 }}".freeze diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb index 10ad1aa29d..df5be54fda 100644 --- a/lib/chef/provider/service/macosx.rb +++ b/lib/chef/provider/service/macosx.rb @@ -33,8 +33,7 @@ class Chef /Library/LaunchDaemons /System/Library/LaunchAgents /System/Library/LaunchDaemons } - - locations << "#{ENV['HOME']}/Library/LaunchAgents" if ENV['HOME'] + Chef::Util::PathHelper.home('Library', 'LaunchAgents') { |p| locations << p } locations end diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb index debf36f771..0c0c85e18b 100644 --- a/lib/chef/provider/user/dscl.rb +++ b/lib/chef/provider/user/dscl.rb @@ -44,6 +44,8 @@ class Chef # This provider only supports Mac OSX versions 10.7 and above class Dscl < Chef::Provider::User + provides :user, os: "darwin" + def define_resource_requirements super @@ -650,7 +652,11 @@ user password using shadow hash.") def run_plutil(*args) result = shell_out("plutil -#{args.join(' ')}") raise(Chef::Exceptions::PlistUtilCommandFailed,"plutil error: #{result.inspect}") unless result.exitstatus == 0 - result.stdout + if result.stdout.encoding == Encoding::ASCII_8BIT + result.stdout.encode("utf-8", "binary", :undef => :replace, :invalid => :replace, :replace => '?') + else + result.stdout + end end def convert_binary_plist_to_xml(binary_plist_string) diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb index 66ef30c349..e282a11d45 100644 --- a/lib/chef/provider/user/windows.rb +++ b/lib/chef/provider/user/windows.rb @@ -27,6 +27,8 @@ class Chef class User class Windows < Chef::Provider::User + provides :user, os: "windows" + def initialize(new_resource,run_context) super @net_user = Chef::Util::Windows::NetUser.new(@new_resource.username) diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 796a0f8fa6..a5f5386de3 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -25,6 +25,7 @@ require 'chef/provider/cron/aix' require 'chef/provider/deploy' require 'chef/provider/directory' require 'chef/provider/dsc_script' +require 'chef/provider/dsc_resource' require 'chef/provider/env' require 'chef/provider/erl_call' require 'chef/provider/execute' diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index 91f7f30aa9..b4d37c2d61 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -25,6 +25,7 @@ require 'chef/dsl/include_recipe' require 'chef/dsl/registry_helper' require 'chef/dsl/reboot_pending' require 'chef/dsl/audit' +require 'chef/dsl/powershell' require 'chef/mixin/from_file' @@ -42,6 +43,7 @@ class Chef include Chef::DSL::RegistryHelper include Chef::DSL::RebootPending include Chef::DSL::Audit + include Chef::DSL::Powershell include Chef::Mixin::FromFile include Chef::Mixin::Deprecation diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index ea220b6c70..2df73a52e8 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -997,6 +997,15 @@ class Chef end # + # DSL method used to define attribute on a resource wrapping params_validate + # + def self.attribute(attr_name, validation_opts={}) + define_method(attr_name) do |arg=NULL_ARG| + nillable_set_or_return(attr_name.to_sym, arg, validation_opts) + end + end + + # # The cookbook in which this Resource was defined (if any). # # @return Chef::CookbookVersion The cookbook in which this Resource was defined. diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb index 576e6b4c6b..c091ec56b6 100644 --- a/lib/chef/resource/batch.rb +++ b/lib/chef/resource/batch.rb @@ -22,6 +22,8 @@ class Chef class Resource class Batch < Chef::Resource::WindowsScript + provides :batch, os: "windows" + def initialize(name, run_context=nil) super(name, run_context, :batch, "cmd.exe") end diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb index 9c04658bf3..cb16506012 100644 --- a/lib/chef/resource/cron.rb +++ b/lib/chef/resource/cron.rb @@ -27,6 +27,8 @@ class Chef state_attrs :minute, :hour, :day, :month, :weekday, :user + provides :cron + def initialize(name, run_context=nil) super @resource_name = :cron @@ -138,7 +140,7 @@ class Chef :kind_of => [String, Symbol] ) end - + def time(arg=nil) set_or_return( :time, @@ -214,5 +216,3 @@ class Chef end end end - - diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb index 4252aa230f..fade82a972 100644 --- a/lib/chef/resource/deploy.rb +++ b/lib/chef/resource/deploy.rb @@ -63,6 +63,7 @@ class Chef @deploy_to = name @environment = nil @repository_cache = 'cached-copy' + # XXX: if copy_exclude is a kind_of String why is initialized to an array??? @copy_exclude = [] @purge_before_symlink = %w{log tmp/pids public/system} @create_dirs_before_symlink = %w{tmp public config} @@ -78,10 +79,11 @@ class Chef @scm_provider = Chef::Provider::Git @svn_force_export = false @allowed_actions.push(:force_deploy, :deploy, :rollback) - @additional_remotes = Hash[] + @additional_remotes = {} @keep_releases = 5 @enable_checkout = true @checkout_branch = "deploy" + @timeout = nil end # where the checked out/cloned code goes @@ -104,42 +106,18 @@ class Chef end # note: deploy_to is your application "meta-root." - def deploy_to(arg=nil) - set_or_return( - :deploy_to, - arg, - :kind_of => [ String ] - ) - end + attribute :deploy_to, :kind_of => [ String ] - def repo(arg=nil) - set_or_return( - :repo, - arg, - :kind_of => [ String ] - ) - end + attribute :repo, :kind_of => [ String ] alias :repository :repo - def remote(arg=nil) - set_or_return( - :remote, - arg, - :kind_of => [ String ] - ) - end + attribute :remote, :kind_of => [ String ] - def role(arg=nil) - set_or_return( - :role, - arg, - :kind_of => [ String ] - ) - end + attribute :role, :kind_of => [ String ] - def restart_command(arg=nil, &block) - arg ||= block - set_or_return( + def restart_command(arg=NULL_ARG, &block) + arg = block if block_given? + nillable_set_or_return( :restart_command, arg, :kind_of => [ String, Proc ] @@ -147,155 +125,60 @@ class Chef end alias :restart :restart_command - def migrate(arg=nil) - set_or_return( - :migrate, - arg, - :kind_of => [ TrueClass, FalseClass ] - ) - end + attribute :migrate, :kind_of => [ TrueClass, FalseClass ] - def migration_command(arg=nil) - set_or_return( - :migration_command, - arg, - :kind_of => [ String ] - ) - end + attribute :migration_command, kind_of: String - def rollback_on_error(arg=nil) - set_or_return( - :rollback_on_error, - arg, - :kind_of => [ TrueClass, FalseClass ] - ) - end + attribute :rollback_on_error, :kind_of => [ TrueClass, FalseClass ] - def user(arg=nil) - set_or_return( - :user, - arg, - :kind_of => [ String ] - ) - end + attribute :user, kind_of: String - def group(arg=nil) - set_or_return( - :group, - arg, - :kind_of => [ String ] - ) - end + attribute :group, kind_of: [ String ] - def enable_submodules(arg=nil) - set_or_return( - :enable_submodules, - arg, - :kind_of => [ TrueClass, FalseClass ] - ) - end + attribute :enable_submodules, kind_of: [ TrueClass, FalseClass ] - def shallow_clone(arg=nil) - set_or_return( - :shallow_clone, - arg, - :kind_of => [ TrueClass, FalseClass ] - ) - end + attribute :shallow_clone, kind_of: [ TrueClass, FalseClass ] - def repository_cache(arg=nil) - set_or_return( - :repository_cache, - arg, - :kind_of => [ String ] - ) - end + attribute :repository_cache, kind_of: String - def copy_exclude(arg=nil) - set_or_return( - :copy_exclude, - arg, - :kind_of => [ String ] - ) - end + attribute :copy_exclude, kind_of: String - def revision(arg=nil) - set_or_return( - :revision, - arg, - :kind_of => [ String ] - ) - end + attribute :revision, kind_of: String alias :branch :revision - def git_ssh_wrapper(arg=nil) - set_or_return( - :git_ssh_wrapper, - arg, - :kind_of => [ String ] - ) - end + attribute :git_ssh_wrapper, kind_of: String alias :ssh_wrapper :git_ssh_wrapper - def svn_username(arg=nil) - set_or_return( - :svn_username, - arg, - :kind_of => [ String ] - ) - end + attribute :svn_username, kind_of: String - def svn_password(arg=nil) - set_or_return( - :svn_password, - arg, - :kind_of => [ String ] - ) - end + attribute :svn_password, kind_of: String - def svn_arguments(arg=nil) - set_or_return( - :svn_arguments, - arg, - :kind_of => [ String ] - ) - end + attribute :svn_arguments, kind_of: String - def svn_info_args(arg=nil) - set_or_return( - :svn_arguments, - arg, - :kind_of => [ String ]) - end + attribute :svn_info_args, kind_of: String - def scm_provider(arg=nil) + def scm_provider(arg=NULL_ARG) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) lookup_provider_constant(arg) else arg end - set_or_return( + nillable_set_or_return( :scm_provider, klass, :kind_of => [ Class ] ) end - def svn_force_export(arg=nil) - set_or_return( - :svn_force_export, - arg, - :kind_of => [ TrueClass, FalseClass ] - ) - end + attribute :svn_force_export, kind_of: [ TrueClass, FalseClass ] - def environment(arg=nil) + def environment(arg=NULL_ARG) if arg.is_a?(String) Chef::Log.debug "Setting RAILS_ENV, RACK_ENV, and MERB_ENV to `#{arg}'" Chef::Log.warn "[DEPRECATED] please modify your deploy recipe or attributes to set the environment using a hash" arg = {"RAILS_ENV"=>arg,"MERB_ENV"=>arg,"RACK_ENV"=>arg} end - set_or_return( + nillable_set_or_return( :environment, arg, :kind_of => [ Hash ] @@ -303,8 +186,8 @@ class Chef end # The number of old release directories to keep around after cleanup - def keep_releases(arg=nil) - [set_or_return( + def keep_releases(arg=NULL_ARG) + [nillable_set_or_return( :keep_releases, arg, :kind_of => [ Integer ]), 1].max @@ -314,13 +197,7 @@ class Chef # SCM clone/checkout before symlinking. Use this to get rid of files and # directories you want to be shared between releases. # Default: ["log", "tmp/pids", "public/system"] - def purge_before_symlink(arg=nil) - set_or_return( - :purge_before_symlink, - arg, - :kind_of => Array - ) - end + attribute :purge_before_symlink, kind_of: Array # An array of paths, relative to your app's root, where you expect dirs to # exist before symlinking. This runs after #purge_before_symlink, so you @@ -330,13 +207,7 @@ class Chef # then specify tmp here so that the tmp directory will exist when you # symlink the pids directory in to the current release. # Default: ["tmp", "public", "config"] - def create_dirs_before_symlink(arg=nil) - set_or_return( - :create_dirs_before_symlink, - arg, - :kind_of => Array - ) - end + attribute :create_dirs_before_symlink, kind_of: Array # A Hash of shared/dir/path => release/dir/path. This attribute determines # which files and dirs in the shared directory get symlinked to the current @@ -344,13 +215,7 @@ class Chef # $shared/pids that you would like to symlink as $current_release/tmp/pids # you specify it as "pids" => "tmp/pids" # Default {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"} - def symlinks(arg=nil) - set_or_return( - :symlinks, - arg, - :kind_of => Hash - ) - end + attribute :symlinks, kind_of: Hash # A Hash of shared/dir/path => release/dir/path. This attribute determines # which files in the shared directory get symlinked to the current release @@ -359,74 +224,44 @@ class Chef # For a rails/merb app, this is used to link in a known good database.yml # (with the production db password) before running migrate. # Default {"config/database.yml" => "config/database.yml"} - def symlink_before_migrate(arg=nil) - set_or_return( - :symlink_before_migrate, - arg, - :kind_of => Hash - ) - end + attribute :symlink_before_migrate, kind_of: Hash # Callback fires before migration is run. - def before_migrate(arg=nil, &block) - arg ||= block - set_or_return(:before_migrate, arg, :kind_of => [Proc, String]) + def before_migrate(arg=NULL_ARG, &block) + arg = block if block_given? + nillable_set_or_return(:before_migrate, arg, kind_of: [Proc, String]) end # Callback fires before symlinking - def before_symlink(arg=nil, &block) - arg ||= block - set_or_return(:before_symlink, arg, :kind_of => [Proc, String]) + def before_symlink(arg=NULL_ARG, &block) + arg = block if block_given? + nillable_set_or_return(:before_symlink, arg, kind_of: [Proc, String]) end # Callback fires before restart - def before_restart(arg=nil, &block) - arg ||= block - set_or_return(:before_restart, arg, :kind_of => [Proc, String]) + def before_restart(arg=NULL_ARG, &block) + arg = block if block_given? + nillable_set_or_return(:before_restart, arg, kind_of: [Proc, String]) end # Callback fires after restart - def after_restart(arg=nil, &block) - arg ||= block - set_or_return(:after_restart, arg, :kind_of => [Proc, String]) + def after_restart(arg=NULL_ARG, &block) + arg = block if block_given? + nillable_set_or_return(:after_restart, arg, kind_of: [Proc, String]) end - def additional_remotes(arg=nil) - set_or_return( - :additional_remotes, - arg, - :kind_of => Hash - ) - end + attribute :additional_remotes, kind_of: Hash - def enable_checkout(arg=nil) - set_or_return( - :enable_checkout, - arg, - :kind_of => [TrueClass, FalseClass] - ) - end + attribute :enable_checkout, kind_of: [ TrueClass, FalseClass ] - def checkout_branch(arg=nil) - set_or_return( - :checkout_branch, - arg, - :kind_of => String - ) - end + attribute :checkout_branch, kind_of: String # FIXME The Deploy resource may be passed to an SCM provider as its # resource. The SCM provider knows that SCM resources can specify a # timeout for SCM operations. The deploy resource must therefore support # a timeout method, but the timeout it describes is for SCM operations, # not the overall deployment. This is potentially confusing. - def timeout(arg=nil) - set_or_return( - :timeout, - arg, - :kind_of => Integer - ) - end + attribute :timeout, kind_of: Integer end end diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb new file mode 100644 index 0000000000..912b683434 --- /dev/null +++ b/lib/chef/resource/dsc_resource.rb @@ -0,0 +1,83 @@ +#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+#
+# Copyright:: 2014, Opscode, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef/dsl/powershell'
+
+class Chef
+ class Resource
+ class DscResource < Chef::Resource
+
+ provides :dsc_resource, os: "windows"
+
+ include Chef::DSL::Powershell
+
+ def initialize(name, run_context)
+ super
+ @properties = {}
+ @resource_name = :dsc_resource
+ @resource = nil
+ @allowed_actions.push(:run)
+ @action = :run
+ end
+
+ def resource(value=nil)
+ if value
+ @resource = value
+ else
+ @resource
+ end
+ end
+
+ def module_name(value=nil)
+ if value
+ @module_name = value
+ else
+ @module_name
+ end
+ end
+
+ def property(property_name, value=nil)
+ if not property_name.is_a?(Symbol)
+ raise TypeError, "A property name of type Symbol must be specified, '#{property_name.to_s}' of type #{property_name.class.to_s} was given"
+ end
+
+ if value.nil?
+ value_of(@properties[property_name])
+ else
+ @properties[property_name] = value
+ end
+ end
+
+ def properties
+ @properties.reduce({}) do |memo, (k, v)|
+ memo[k] = value_of(v)
+ memo
+ end
+ end
+
+ private
+
+ def value_of(value)
+ if value.is_a?(DelayedEvaluator)
+ value.call
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb index 4b5fe6cc09..2072ae5d80 100644 --- a/lib/chef/resource/env.rb +++ b/lib/chef/resource/env.rb @@ -25,6 +25,8 @@ class Chef state_attrs :value + provides :env, os: "windows" + def initialize(name, run_context=nil) super @resource_name = :env diff --git a/lib/chef/resource/git.rb b/lib/chef/resource/git.rb index 7156873315..306efe633d 100644 --- a/lib/chef/resource/git.rb +++ b/lib/chef/resource/git.rb @@ -27,7 +27,7 @@ class Chef def initialize(name, run_context=nil) super @resource_name = :git - @additional_remotes = Hash[] + @additional_remotes = {} end def additional_remotes(arg=nil) diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb index daf851fac6..9e8f1309b0 100644 --- a/lib/chef/resource/group.rb +++ b/lib/chef/resource/group.rb @@ -25,6 +25,8 @@ class Chef state_attrs :members + provides :group + def initialize(name, run_context=nil) super @resource_name = :group diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb index 3bd5dc16dc..73409b13ac 100644 --- a/lib/chef/resource/homebrew_package.rb +++ b/lib/chef/resource/homebrew_package.rb @@ -26,7 +26,7 @@ class Chef class HomebrewPackage < Chef::Resource::Package provides :homebrew_package - provides :package, os: ["mac_os_x", "darwin"] + provides :package, os: "darwin" def initialize(name, run_context=nil) super diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb index ce72e98028..a777c511f0 100644 --- a/lib/chef/resource/lwrp_base.rb +++ b/lib/chef/resource/lwrp_base.rb @@ -70,14 +70,6 @@ class Chef alias_method :resource_name=, :resource_name end - # Define an attribute on this resource, including optional validation - # parameters. - def self.attribute(attr_name, validation_opts={}) - define_method(attr_name) do |arg=nil| - set_or_return(attr_name.to_sym, arg, validation_opts) - end - end - # Sets the default action def self.default_action(action_name=NULL_ARG) unless action_name.equal?(NULL_ARG) diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb index 46a85b2475..971b6c51b4 100644 --- a/lib/chef/resource/mdadm.rb +++ b/lib/chef/resource/mdadm.rb @@ -27,6 +27,8 @@ class Chef state_attrs :devices, :level, :chunk + provides :mdadm + def initialize(name, run_context=nil) super @resource_name = :mdadm diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb index 275c069f61..142dec87f7 100644 --- a/lib/chef/resource/mount.rb +++ b/lib/chef/resource/mount.rb @@ -27,6 +27,8 @@ class Chef state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain + provides :mount + def initialize(name, run_context=nil) super @resource_name = :mount diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb index 1b8aef94a2..43aafe4df2 100644 --- a/lib/chef/resource/powershell_script.rb +++ b/lib/chef/resource/powershell_script.rb @@ -21,6 +21,8 @@ class Chef class Resource class PowershellScript < Chef::Resource::WindowsScript + provides :powershell_script, os: "windows" + def initialize(name, run_context=nil) super(name, run_context, :powershell_script, "powershell.exe") @convert_boolean_return = false diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb index 9d6e857de7..7d2ec25596 100644 --- a/lib/chef/resource/user.rb +++ b/lib/chef/resource/user.rb @@ -26,6 +26,8 @@ class Chef state_attrs :uid, :gid, :home + provides :user + def initialize(name, run_context=nil) super @resource_name = :user diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 680b393741..40b12a7c5f 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -29,6 +29,7 @@ require 'chef/resource/deploy_revision' require 'chef/resource/directory' require 'chef/resource/dpkg_package' require 'chef/resource/dsc_script' +require 'chef/resource/dsc_resource' require 'chef/resource/easy_install_package' require 'chef/resource/env' require 'chef/resource/erl_call' diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index fc54506407..4f0215bfd4 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -50,7 +50,7 @@ class Chef # recipes, which is triggered by #load. (See also: CookbookCompiler) attr_accessor :resource_collection - # The list of audits (control groups) to execute during the audit phase + # The list of control groups to execute during the audit phase attr_accessor :audits # A Hash containing the immediate notifications triggered by resources diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb index fed32b3795..ee4fe78808 100644 --- a/lib/chef/shell.rb +++ b/lib/chef/shell.rb @@ -29,6 +29,7 @@ require 'chef/config_fetcher' require 'chef/shell/shell_session' require 'chef/shell/ext' require 'chef/json_compat' +require 'chef/util/path_helper' # = Shell # Shell is Chef in an IRB session. Shell can interact with a Chef server via the @@ -101,7 +102,7 @@ module Shell end def self.configure_irb - irb_conf[:HISTORY_FILE] = "~/.chef/chef_shell_history" + irb_conf[:HISTORY_FILE] = Chef::Util::PathHelper.home(".chef", "chef_shell_history") irb_conf[:SAVE_HISTORY] = 1000 irb_conf[:IRB_RC] = lambda do |conf| @@ -295,18 +296,19 @@ FOOTER private def config_file_for_shell_mode(environment) + dot_chef_dir = Chef::Util::PathHelper.home('.chef') if config[:config_file] config[:config_file] - elsif environment && ENV['HOME'] + elsif environment Shell.env = environment - config_file_to_try = ::File.join(ENV['HOME'], '.chef', environment, 'chef_shell.rb') + config_file_to_try = ::File.join(dot_chef_dir, environment, 'chef_shell.rb') unless ::File.exist?(config_file_to_try) puts "could not find chef-shell config for environment #{environment} at #{config_file_to_try}" exit 1 end config_file_to_try - elsif ENV['HOME'] && ::File.exist?(File.join(ENV['HOME'], '.chef', 'chef_shell.rb')) - File.join(ENV['HOME'], '.chef', 'chef_shell.rb') + elsif dot_chef_dir && ::File.exist?(File.join(dot_chef_dir, 'chef_shell.rb')) + File.join(dot_chef_dir, 'chef_shell.rb') elsif config[:solo] Chef::Config.platform_specific_path("/etc/chef/solo.rb") elsif config[:client] diff --git a/lib/chef/util/dsc/resource_store.rb b/lib/chef/util/dsc/resource_store.rb new file mode 100644 index 0000000000..fdcecc2b3c --- /dev/null +++ b/lib/chef/util/dsc/resource_store.rb @@ -0,0 +1,110 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/util/powershell/cmdlet' +require 'chef/util/powershell/cmdlet_result' +require 'chef/exceptions' + +class Chef +class Util +class DSC + class ResourceStore + + def self.instance + @@instance ||= ResourceStore.new.tap do |store| + store.send(:populate_cache) + end + end + + def resources + @resources ||= [] + end + + def find(name, module_name=nil) + found = find_resources(name, module_name, resources) + + # We don't have it, query for the resource...it might + # have been added since we last queried + if found.length == 0 + rs = query_resource(name) + add_resources(rs) + found = find_resources(name, module_name, rs) + end + + found + end + + private + + def add_resource(new_r) + count = resources.count do |r| + r['ResourceType'].casecmp(new_r['ResourceType']) == 0 + end + if count == 0 + resources << new_r + end + end + + def add_resources(rs) + rs.each do |r| + add_resource(r) + end + end + + def populate_cache + @resources = query_resources + end + + def find_resources(name, module_name, rs) + found = rs.find_all do |r| + name_matches = r['Name'].casecmp(name) == 0 + if name_matches + module_name == nil || (r['Module'] and r['Module']['Name'].casecmp(module_name) == 0) + else + false + end + end + end + + + # Returns a list of dsc resources + def query_resources + cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, 'get-dscresource', + :object) + result = cmdlet.run + result.return_value + end + + # Returns a list of dsc resources matching the provided name + def query_resource(resource_name) + cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource #{resource_name}", + :object) + result = cmdlet.run + ret_val = result.return_value + if ret_val.nil? + [] + elsif ret_val.is_a? Array + ret_val + else + [ret_val] + end + end + end +end +end +end diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb index 1ae489598d..17c7175331 100644 --- a/lib/chef/util/path_helper.rb +++ b/lib/chef/util/path_helper.rb @@ -142,6 +142,82 @@ class Chef def self.relative_path_from(from, to) pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from))) end + + # Retrieves the "home directory" of the current user while trying to ascertain the existence + # of said directory. The path returned uses / for all separators (the ruby standard format). + # If the home directory doesn't exist or an error is otherwise encountered, nil is returned. + # + # If a set of path elements is provided, they are appended as-is to the home path if the + # homepath exists. + # + # If an optional block is provided, the joined path is passed to that block if the home path is + # valid and the result of the block is returned instead. + # + # Home-path discovery is performed once. If a path is discovered, that value is memoized so + # that subsequent calls to home_dir don't bounce around. + # + # See self.all_homes. + def self.home(*args) + @@home_dir ||= self.all_homes { |p| break p } + if @@home_dir + path = File.join(@@home_dir, *args) + block_given? ? (yield path) : path + end + end + + # See self.home. This method performs a similar operation except that it yields all the different + # possible values of 'HOME' that one could have on this platform. Hence, on windows, if + # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice. + # This method goes out and checks the existence of each location at the time of the call. + # + # The return is a list of all the returned values from each block invocation or a list of paths + # if no block is provided. + def self.all_homes(*args) + paths = [] + if Chef::Platform.windows? + # By default, Ruby uses the the following environment variables to determine Dir.home: + # HOME + # HOMEDRIVE HOMEPATH + # USERPROFILE + # Ruby only checks to see if the variable is specified - not if the directory actually exists. + # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive) + # while USERPROFILE points to the location where the user application settings and profile are stored. HOME + # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is + # HOMESHARE instead of HOMEDRIVE. + # + # We instead walk down the following and only include paths that actually exist. + # HOME + # HOMEDRIVE HOMEPATH + # HOMESHARE HOMEPATH + # USERPROFILE + + paths << ENV['HOME'] + paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] + paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH'] + paths << ENV['USERPROFILE'] + end + paths << Dir.home + + # Depending on what environment variables we're using, the slashes can go in any which way. + # Just change them all to / to keep things consistent. + # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on + # the particular brand of kool-aid you consume. This code assumes that \ and / are both + # path separators on any system being used. + paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path } + + # Filter out duplicate paths and paths that don't exist. + valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) } + valid_paths = valid_paths.uniq + + # Join all optional path elements at the end. + # If a block is provided, invoke it - otherwise just return what we've got. + joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) } + if block_given? + joined_paths.each { |p| yield p } + else + joined_paths + end + end end end end diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb index 40edbb13c6..47d63a2b85 100644 --- a/lib/chef/util/powershell/cmdlet.rb +++ b/lib/chef/util/powershell/cmdlet.rb @@ -20,7 +20,9 @@ require 'mixlib/shellout' require 'chef/mixin/windows_architecture_helper' require 'chef/util/powershell/cmdlet_result' -class Chef::Util::Powershell +class Chef +class Util +class Powershell class Cmdlet def initialize(node, cmdlet, output_format=nil, output_format_options={}) @output_format = output_format @@ -46,6 +48,10 @@ class Chef::Util::Powershell attr_reader :output_format def run(switches={}, execution_options={}, *arguments) + streams = { :json => CmdletStream.new('json'), + :verbose => CmdletStream.new('verbose'), + } + arguments_string = arguments.join(' ') switches_string = command_switches_string(switches) @@ -56,21 +62,25 @@ class Chef::Util::Powershell json_depth = @output_format_options[:depth] end - json_command = @json_format ? " | convertto-json -compress -depth #{json_depth}" : "" - command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive -command \"trap [Exception] {write-error -exception ($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} #{arguments_string}#{json_command}\";if ( ! $? ) { exit 1 }" + json_command = @json_format ? " | convertto-json -compress -depth #{json_depth} "\ + "> #{streams[:json].path}" : "" + redirections = "4> '#{streams[:verbose].path}'" + command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive "\ + "-command \"trap [Exception] {write-error -exception "\ + "($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} "\ + "#{arguments_string} #{redirections}"\ + "#{json_command}\";if ( ! $? ) { exit 1 }" augmented_options = {:returns => [0], :live_stream => false}.merge(execution_options) command = Mixlib::ShellOut.new(command_string, augmented_options) - os_architecture = "#{ENV['PROCESSOR_ARCHITEW6432']}" == 'AMD64' ? :x86_64 : :i386 - status = nil with_os_architecture(@node) do status = command.run_command end - CmdletResult.new(status, @output_format) + CmdletResult.new(status, streams, @output_format) end def run!(switches={}, execution_options={}, *arguments) @@ -131,6 +141,30 @@ class Chef::Util::Powershell command_switches.join(' ') end + + class CmdletStream + def initialize(name) + @filename = Dir::Tmpname.create(name) {} + ObjectSpace.define_finalizer(self, self.class.destroy(@filename)) + end + + def path + @filename + end + + def read + if File.exist? @filename + File.open(@filename, 'rb:bom|UTF-16LE') do |f| + f.read.encode('UTF-8') + end + end + end + + def self.destroy(name) + proc { File.delete(name) if File.exists? name } + end + end end end - +end +end diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb index 246701a7bc..f1fdd968b1 100644 --- a/lib/chef/util/powershell/cmdlet_result.rb +++ b/lib/chef/util/powershell/cmdlet_result.rb @@ -18,22 +18,35 @@ require 'chef/json_compat' -class Chef::Util::Powershell +class Chef +class Util +class Powershell class CmdletResult attr_reader :output_format - def initialize(status, output_format) + def initialize(status, streams, output_format) @status = status @output_format = output_format + @streams = streams end + def stdout + @status.stdout + end + def stderr @status.stderr end + def stream(name) + @streams[name].read + end + def return_value if output_format == :object - Chef::JSONCompat.parse(@status.stdout) + Chef::JSONCompat.parse(stream(:json)) + elsif output_format == :json + stream(:json) else @status.stdout end @@ -44,3 +57,5 @@ class Chef::Util::Powershell end end end +end +end diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb new file mode 100644 index 0000000000..01f8c27b6c --- /dev/null +++ b/lib/chef/util/powershell/ps_credential.rb @@ -0,0 +1,38 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/win32/crypto' if Chef::Platform.windows? + +class Chef::Util::Powershell + class PSCredential + def initialize(username, password) + @username = username + @password = password + end + + def to_psobject + "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))" + end + + private + + def encrypt(str) + Chef::ReservedNames::Win32::Crypto.encrypt(str) + end + end +end diff --git a/lib/chef/version.rb b/lib/chef/version.rb index e56720fd33..5763f6a138 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -17,7 +17,7 @@ class Chef CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) - VERSION = '12.1.2' + VERSION = '12.2.0.rc.0' end # diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb index 8b81947c9b..efa632f454 100644 --- a/lib/chef/win32/api.rb +++ b/lib/chef/win32/api.rb @@ -184,6 +184,8 @@ class Chef host.typedef :pointer, :PSTR # Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts. host.typedef :pointer, :PTBYTE # Pointer to a TBYTE. host.typedef :pointer, :PTCHAR # Pointer to a TCHAR. + host.typedef :pointer, :PCRYPTPROTECT_PROMPTSTRUCT # Pointer to a CRYPTOPROTECT_PROMPTSTRUCT. + host.typedef :pointer, :PDATA_BLOB # Pointer to a DATA_BLOB. host.typedef :pointer, :PTSTR # A PWSTR if UNICODE is defined, a PSTR otherwise. host.typedef :pointer, :PUCHAR # Pointer to a UCHAR. host.typedef :pointer, :PUHALF_PTR # Pointer to a UHALF_PTR. diff --git a/lib/chef/win32/api/crypto.rb b/lib/chef/win32/api/crypto.rb new file mode 100644 index 0000000000..1837a57557 --- /dev/null +++ b/lib/chef/win32/api/crypto.rb @@ -0,0 +1,63 @@ +#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/api'
+
+class Chef
+ module ReservedNames::Win32
+ module API
+ module Crypto
+ extend Chef::ReservedNames::Win32::API
+
+ ###############################################
+ # Win32 API Bindings
+ ###############################################
+
+ ffi_lib 'Crypt32'
+
+ CRYPTPROTECT_UI_FORBIDDEN = 0x1
+ CRYPTPROTECT_LOCAL_MACHINE = 0x4
+ CRYPTPROTECT_AUDIT = 0x10
+
+ class CRYPT_INTEGER_BLOB < FFI::Struct
+ layout :cbData, :DWORD, # Count, in bytes, of data
+ :pbData, :pointer # Pointer to data buffer
+ def initialize(str=nil)
+ super(nil)
+ if str
+ self[:pbData] = FFI::MemoryPointer.from_string(str)
+ self[:cbData] = str.bytesize
+ end
+ end
+
+ end
+
+ safe_attach_function :CryptProtectData, [
+ :PDATA_BLOB,
+ :LPCWSTR,
+ :PDATA_BLOB,
+ :pointer,
+ :PCRYPTPROTECT_PROMPTSTRUCT,
+ :DWORD,
+ :PDATA_BLOB
+ ], :BOOL
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/win32/api/installer.rb b/lib/chef/win32/api/installer.rb index 745802d260..6864a26e7d 100644 --- a/lib/chef/win32/api/installer.rb +++ b/lib/chef/win32/api/installer.rb @@ -108,7 +108,7 @@ UINT MsiCloseHandle( end msi_close_handle(pkg_ptr.read_pointer) - return buffer + return buffer.chomp(0.chr) end # Opens a Microsoft Installer (MSI) file from an absolute path and returns a pointer to a handle diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb new file mode 100644 index 0000000000..79cf51b002 --- /dev/null +++ b/lib/chef/win32/crypto.rb @@ -0,0 +1,49 @@ +#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/error'
+require 'chef/win32/api/memory'
+require 'chef/win32/api/crypto'
+require 'digest'
+
+class Chef
+ module ReservedNames::Win32
+ class Crypto
+ include Chef::ReservedNames::Win32::API::Crypto
+ extend Chef::ReservedNames::Win32::API::Crypto
+
+ def self.encrypt(str, &block)
+ data_blob = CRYPT_INTEGER_BLOB.new
+ unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, 0, data_blob)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ bytes = data_blob[:pbData].get_bytes(0, data_blob[:cbData])
+ if block
+ block.call(bytes)
+ else
+ Digest.hexencode(bytes)
+ end
+ ensure
+ unless data_blob[:pbData].null?
+ Chef::ReservedNames::Win32::Memory.local_free(data_blob[:pbData])
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb index dd02ad9a66..2454c9cccf 100644 --- a/lib/chef/workstation_config_loader.rb +++ b/lib/chef/workstation_config_loader.rb @@ -19,6 +19,7 @@ require 'chef/config_fetcher' require 'chef/config' require 'chef/null_logger' +require 'chef/util/path_helper' class Chef @@ -112,9 +113,9 @@ class Chef candidate_configs << File.join(chef_config_dir, 'knife.rb') end # Look for $HOME/.chef/knife.rb - if env['HOME'] - candidate_configs << File.join(env['HOME'], '.chef', 'config.rb') - candidate_configs << File.join(env['HOME'], '.chef', 'knife.rb') + Chef::Util::PathHelper.home('.chef') do |dot_chef_dir| + candidate_configs << File.join(dot_chef_dir, 'config.rb') + candidate_configs << File.join(dot_chef_dir, 'knife.rb') end candidate_configs.find do | candidate_config | diff --git a/spec/functional/file_content_management/deploy_strategies_spec.rb b/spec/functional/file_content_management/deploy_strategies_spec.rb index 03a6c504c1..8a995d0e41 100644 --- a/spec/functional/file_content_management/deploy_strategies_spec.rb +++ b/spec/functional/file_content_management/deploy_strategies_spec.rb @@ -43,7 +43,7 @@ shared_examples_for "a content deploy strategy" do ## # UNIX Context - let(:default_mode) { normalize_mode(0100666 - File.umask) } + let(:default_mode) { normalize_mode(0666 & ~File.umask) } it "touches the file to create it (UNIX)", :unix_only do content_deployer.create(target_file_path) diff --git a/spec/functional/resource/cookbook_file_spec.rb b/spec/functional/resource/cookbook_file_spec.rb index 7797ed0041..6d4c5b4a8f 100644 --- a/spec/functional/resource/cookbook_file_spec.rb +++ b/spec/functional/resource/cookbook_file_spec.rb @@ -32,7 +32,7 @@ describe Chef::Resource::CookbookFile do content end - let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) } + let(:default_mode) { (0666 & ~File.umask).to_s(8) } it_behaves_like "a securable resource with reporting" diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb index e5f5341fcd..bd45e32771 100644 --- a/spec/functional/resource/deploy_revision_spec.rb +++ b/spec/functional/resource/deploy_revision_spec.rb @@ -201,6 +201,41 @@ describe Chef::Resource::DeployRevision, :unix_only => true do end end + describe "setting default parameters to nil" do + before do + FileUtils.mkdir_p(rel_path("releases")) + FileUtils.mkdir_p(rel_path("shared")) + end + + it "supports setting symlink_before_migrate to nil" do + deploy_to_latest_rev.symlink_before_migrate(nil) + expect(deploy_to_latest_rev.symlink_before_migrate).to eql(nil) + deploy_to_latest_rev.run_action(:deploy) + expect(deploy_to_latest_rev).to be_updated_by_last_action + end + + it "supports setting symlinks to nil" do + deploy_to_latest_rev.symlinks(nil) + expect(deploy_to_latest_rev.symlinks).to eql(nil) + deploy_to_latest_rev.run_action(:deploy) + expect(deploy_to_latest_rev).to be_updated_by_last_action + end + + it "supports setting purge_before_symlink to nil" do + deploy_to_latest_rev.purge_before_symlink(nil) + expect(deploy_to_latest_rev.purge_before_symlink).to eql(nil) + deploy_to_latest_rev.run_action(:deploy) + expect(deploy_to_latest_rev).to be_updated_by_last_action + end + + it "supports setting create_dirs_before_symlink to nil" do + deploy_to_latest_rev.create_dirs_before_symlink(nil) + expect(deploy_to_latest_rev.create_dirs_before_symlink).to eql(nil) + deploy_to_latest_rev.run_action(:deploy) + expect(deploy_to_latest_rev).to be_updated_by_last_action + end + end + describe "back to a previously deployed revision, with the directory structure precreated" do before do FileUtils.mkdir_p(rel_path("releases")) diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb index 2c4025f83e..88a810964f 100644 --- a/spec/functional/resource/directory_spec.rb +++ b/spec/functional/resource/directory_spec.rb @@ -23,7 +23,7 @@ describe Chef::Resource::Directory do let(:directory_base) { "directory_spec" } - let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) } + let(:default_mode) { (0777 & ~File.umask).to_s(8) } def create_resource events = Chef::EventDispatch::Dispatcher.new diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb new file mode 100644 index 0000000000..6f453eeb9f --- /dev/null +++ b/spec/functional/resource/dsc_resource_spec.rb @@ -0,0 +1,93 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe Chef::Resource::DscResource, :windows_powershell_dsc_only do + before(:all) do + @ohai = Ohai::System.new + @ohai.all_plugins(['platform', 'os', 'languages/powershell']) + end + + let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } + + let(:node) { + Chef::Node.new.tap do |n| + n.consume_external_attrs(@ohai.data, {}) + end + } + + let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } + + let(:new_resource) { + Chef::Resource::DscResource.new("dsc_resource_test", run_context) + } + + context 'when Powershell does not support Invoke-DscResource' + context 'when Powershell supports Invoke-DscResource' do + before do + if !Chef::Platform.supports_dsc_invoke_resource?(node) + skip 'Requires Powershell >= 5.0.10018.0' + end + end + context 'with an invalid dsc resource' do + it 'raises an exception if the resource is not found' do + new_resource.resource 'thisdoesnotexist' + expect { new_resource.run_action(:run) }.to raise_error( + Chef::Exceptions::ResourceNotFound) + end + end + + context 'with a valid dsc resource' do + let(:tmp_file_name) { Dir::Tmpname.create('tmpfile') {} } + let(:test_text) { "'\"!@#$%^&*)(}{][\u2713~n"} + + before do + new_resource.resource :File + new_resource.property :Contents, test_text + new_resource.property :DestinationPath, tmp_file_name + end + + after do + File.delete(tmp_file_name) if File.exists? tmp_file_name + end + + it 'converges the resource if it is not converged' do + new_resource.run_action(:run) + contents = File.open(tmp_file_name, 'rb:bom|UTF-16LE') do |f| + f.read.encode('UTF-8') + end + expect(contents).to eq(test_text) + expect(new_resource).to be_updated + end + + it 'does not converge the resource if it is already converged' do + new_resource.run_action(:run) + expect(new_resource).to be_updated + reresource = + Chef::Resource::DscResource.new("dsc_resource_retest", run_context) + reresource.resource :File + reresource.property :Contents, test_text + reresource.property :DestinationPath, tmp_file_name + reresource.run_action(:run) + expect(reresource).not_to be_updated + end + end + + end +end diff --git a/spec/functional/resource/env_spec.rb b/spec/functional/resource/env_spec.rb index 16caec14bf..b9dcd7b33a 100755 --- a/spec/functional/resource/env_spec.rb +++ b/spec/functional/resource/env_spec.rb @@ -29,14 +29,15 @@ describe Chef::Resource::Env, :windows_only do let(:env_value_expandable) { '%SystemRoot%' } let(:test_run_context) { node = Chef::Node.new + node.default['os'] = 'windows' node.default['platform'] = 'windows' node.default['platform_version'] = '6.1' empty_events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, empty_events) } - let(:test_resource) { - Chef::Resource::Env.new('unknown', test_run_context) - } + let(:test_resource) { + Chef::Resource::Env.new('unknown', test_run_context) + } before(:each) do resource_lower = Chef::Resource::Env.new(chef_env_test_lower_case, test_run_context) diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb index cf70c349fb..f1a290dea4 100644 --- a/spec/functional/resource/file_spec.rb +++ b/spec/functional/resource/file_spec.rb @@ -64,7 +64,7 @@ describe Chef::Resource::File do provider.current_resource end - let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) } + let(:default_mode) { (0666 & ~File.umask).to_s(8) } it_behaves_like "a file resource" diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_spec.rb index 1b3ac844e0..56a905efe7 100644 --- a/spec/functional/resource/powershell_spec.rb +++ b/spec/functional/resource/powershell_spec.rb @@ -56,7 +56,8 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do resource.run_action(:run) end - it "returns the -27 for a powershell script that exits with -27" do + it "returns the -27 for a powershell script that exits with -27", :windows_powershell_dsc_only do + # This is broken on Powershell < 4.0 file = Tempfile.new(['foo', '.ps1']) begin file.write "exit -27" diff --git a/spec/functional/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb index bcafca7399..37ffbbc971 100644 --- a/spec/functional/resource/remote_directory_spec.rb +++ b/spec/functional/resource/remote_directory_spec.rb @@ -22,7 +22,7 @@ describe Chef::Resource::RemoteDirectory do include_context Chef::Resource::Directory let(:directory_base) { "directory_spec" } - let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) } + let(:default_mode) { (0777 & ~File.umask).to_s(8) } def create_resource cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb index 29091fd785..4fbcd2d24b 100644 --- a/spec/functional/resource/remote_file_spec.rb +++ b/spec/functional/resource/remote_file_spec.rb @@ -52,7 +52,7 @@ describe Chef::Resource::RemoteFile do create_resource end - let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) } + let(:default_mode) { (0666 & ~File.umask).to_s(8) } context "when fetching files over HTTP" do before(:all) do diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb index d7b35e7450..35c5166e31 100644 --- a/spec/functional/resource/template_spec.rb +++ b/spec/functional/resource/template_spec.rb @@ -58,7 +58,7 @@ describe Chef::Resource::Template do create_resource end - let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) } + let(:default_mode) { (0666 & ~File.umask).to_s(8) } it_behaves_like "a file resource" diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb index 45b6754453..5d960daf11 100644 --- a/spec/functional/resource/user/dscl_spec.rb +++ b/spec/functional/resource/user/dscl_spec.rb @@ -19,9 +19,8 @@ require 'spec_helper' require 'chef/mixin/shell_out' metadata = { - :unix_only => true, + :mac_osx_only => true, :requires_root => true, - :provider => {:user => Chef::Provider::User::Dscl}, :not_supported_on_mac_osx_106 => true, } diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb index 9ac88d7b60..3e4e4e7604 100644 --- a/spec/functional/resource/user/useradd_spec.rb +++ b/spec/functional/resource/user/useradd_spec.rb @@ -32,6 +32,7 @@ end metadata = { :unix_only => true, :requires_root => true, + :not_supported_on_mac_osx => true, :provider => {:user => user_provider_for_platform} } @@ -76,9 +77,22 @@ describe Chef::Provider::User::Useradd, metadata do end end + def try_cleanup + ['/home/cheftestfoo', '/home/cheftestbar'].each do |f| + FileUtils.rm_rf(f) if File.exists? f + end + + ['cf-test'].each do |u| + r = Chef::Resource::User.new("DELETE USER", run_context) + r.username('cf-test') + r.run_action(:remove) + end + end + before do # Silence shell_out live stream Chef::Log.level = :warn + try_cleanup end after do @@ -386,18 +400,18 @@ describe Chef::Provider::User::Useradd, metadata do end context "and home directory is updated" do - let(:existing_home) { "/home/foo" } - let(:home) { "/home/bar" } + let(:existing_home) { "/home/cheftestfoo" } + let(:home) { "/home/cheftestbar" } it "ensures the home directory is set to the desired value" do - expect(pw_entry.home).to eq("/home/bar") + expect(pw_entry.home).to eq("/home/cheftestbar") end context "and manage_home is enabled" do let(:existing_manage_home) { true } let(:manage_home) { true } it "moves the home directory to the new location" do - expect(File).not_to exist("/home/foo") - expect(File).to exist("/home/bar") + expect(File).not_to exist("/home/cheftestfoo") + expect(File).to exist("/home/cheftestbar") end end @@ -409,19 +423,19 @@ describe Chef::Provider::User::Useradd, metadata do # Inconsistent behavior. See: CHEF-2205 it "created the home dir b/c of CHEF-2205 so it still exists" do # This behavior seems contrary to expectation and non-convergent. - expect(File).not_to exist("/home/foo") - expect(File).to exist("/home/bar") + expect(File).not_to exist("/home/cheftestfoo") + expect(File).to exist("/home/cheftestbar") end elsif ohai[:platform] == "aix" it "creates the home dir in the desired location" do - expect(File).not_to exist("/home/foo") - expect(File).to exist("/home/bar") + expect(File).not_to exist("/home/cheftestfoo") + expect(File).to exist("/home/cheftestbar") end else it "does not create the home dir in the desired location (XXX)" do # This behavior seems contrary to expectation and non-convergent. - expect(File).not_to exist("/home/foo") - expect(File).not_to exist("/home/bar") + expect(File).not_to exist("/home/cheftestfoo") + expect(File).not_to exist("/home/cheftestbar") end end end @@ -432,8 +446,8 @@ describe Chef::Provider::User::Useradd, metadata do it "leaves the old home directory around (XXX)" do # Would it be better to remove the old home? - expect(File).to exist("/home/foo") - expect(File).not_to exist("/home/bar") + expect(File).to exist("/home/cheftestfoo") + expect(File).not_to exist("/home/cheftestbar") end end end diff --git a/spec/functional/win32/crypto_spec.rb b/spec/functional/win32/crypto_spec.rb new file mode 100644 index 0000000000..1492995886 --- /dev/null +++ b/spec/functional/win32/crypto_spec.rb @@ -0,0 +1,57 @@ +# +# Author:: Jay Mundrawala(<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +if Chef::Platform.windows? + require 'chef/win32/crypto' +end + +describe 'Chef::ReservedNames::Win32::Crypto', :windows_only do + describe '#encrypt' do + before(:all) do + ohai_reader = Ohai::System.new + ohai_reader.all_plugins("platform") + + new_node = Chef::Node.new + new_node.consume_external_attrs(ohai_reader.data,{}) + + events = Chef::EventDispatch::Dispatcher.new + + @run_context = Chef::RunContext.new(new_node, {}, events) + end + + let (:plaintext) { 'p@assword' } + + it 'can be decrypted by powershell' do + encrypted = Chef::ReservedNames::Win32::Crypto.encrypt(plaintext) + resource = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context) + resource.code <<-EOF +$encrypted = '#{encrypted}' | ConvertTo-SecureString +$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($encrypted) +$plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) +if ($plaintext -ne '#{plaintext}') { + Write-Error 'Got: ' $plaintext + exit 1 +} +exit 0 + EOF + resource.returns(0) + resource.run_action(:run) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8888efc424..fb284c721b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -110,10 +110,13 @@ RSpec.configure do |config| # Tests that randomly fail, but may have value. config.filter_run_excluding :volatile => true config.filter_run_excluding :volatile_on_solaris => true if solaris? + config.filter_run_excluding :volatile_from_verify => false # Add jruby filters here config.filter_run_excluding :windows_only => true unless windows? config.filter_run_excluding :not_supported_on_mac_osx_106 => true if mac_osx_106? + config.filter_run_excluding :not_supported_on_mac_osx=> true if mac_osx? + config.filter_run_excluding :mac_osx_only=> true if !mac_osx? config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3? config.filter_run_excluding :not_supported_on_solaris => true if solaris? config.filter_run_excluding :win2k3_only => true unless windows_win2k3? diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index a412fe38e1..da0313758b 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -88,6 +88,20 @@ def mac_osx_106? false end +def mac_osx? + if File.exists? "/usr/bin/sw_vers" + result = ShellHelpers.shell_out("/usr/bin/sw_vers") + result.stdout.each_line do |line| + if line =~ /^ProductName:\sMac OS X.*$/ + return true + end + end + end + + false +end + + # detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system) def windows64? windows? && ( ENV['PROCESSOR_ARCHITECTURE'] == 'AMD64' || ENV['PROCESSOR_ARCHITEW6432'] == 'AMD64' ) diff --git a/spec/support/shared/functional/securable_resource_with_reporting.rb b/spec/support/shared/functional/securable_resource_with_reporting.rb index 8a2ceed837..37fc538801 100644 --- a/spec/support/shared/functional/securable_resource_with_reporting.rb +++ b/spec/support/shared/functional/securable_resource_with_reporting.rb @@ -35,7 +35,7 @@ shared_examples_for "a securable resource with reporting" do # Default mode varies based on implementation. Providers that use a tempfile # will default to 0600. Providers that use File.open will default to 0666 - # umask - # let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) } + # let(:default_mode) { (0666 & ~File.umask).to_s(8) } describe "reading file security metadata for reporting on unix", :unix_only => true do # According to POSIX standard created files get either the @@ -185,7 +185,7 @@ shared_examples_for "a securable resource with reporting" do # TODO: most stable way to specify? expect(current_resource.owner).to eq(Etc.getpwuid(Process.uid).name) expect(current_resource.group).to eq(@expected_group_name) - expect(current_resource.mode).to eq("0#{((0100666 - File.umask) & 07777).to_s(8)}") + expect(current_resource.mode).to eq("0#{(0666 & ~File.umask).to_s(8)}") end end @@ -239,8 +239,8 @@ shared_examples_for "a securable resource with reporting" do end context "and mode is specified as a String" do - let(:default_create_mode) { (0100666 - File.umask) } - let(:expected_mode) { "0#{(default_create_mode & 07777).to_s(8)}" } + let(:default_create_mode) { 0666 & ~File.umask } + let(:expected_mode) { "0#{default_create_mode.to_s(8)}" } before do resource.mode(expected_mode) @@ -252,7 +252,7 @@ shared_examples_for "a securable resource with reporting" do end context "and mode is specified as an Integer" do - let(:set_mode) { (0100666 - File.umask) & 07777 } + let(:set_mode) { 0666 & ~File.umask } let(:expected_mode) { "0#{set_mode.to_s(8)}" } before do diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index ea2ad473e5..501251b7fe 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -305,7 +305,7 @@ describe Chef::Application::Client, "run_application", :unix_only do allow(Chef::Daemon).to receive(:daemonize).and_return(true) end - it "should exit hard with exitstatus 3" do + it "should exit hard with exitstatus 3", :volatile do pid = fork do @app.run_application end @@ -320,9 +320,9 @@ describe Chef::Application::Client, "run_application", :unix_only do end expect(@pipe[0].gets).to eq("started\n") Process.kill("TERM", pid) - Process.wait - sleep 1 # Make sure we give the converging child process enough time to finish - expect(IO.select([@pipe[0]], nil, nil, 0)).not_to be_nil + Process.wait(pid) + # The timeout value needs to be large enough for the child process to finish + expect(IO.select([@pipe[0]], nil, nil, 15)).not_to be_nil expect(@pipe[0].gets).to eq("finished\n") end end diff --git a/spec/unit/audit/audit_reporter_spec.rb b/spec/unit/audit/audit_reporter_spec.rb index 84d7ea82f0..4bf889510a 100644 --- a/spec/unit/audit/audit_reporter_spec.rb +++ b/spec/unit/audit/audit_reporter_spec.rb @@ -203,7 +203,7 @@ describe Chef::Audit::AuditReporter do it "doesn't send reports" do expect(reporter).to receive(:auditing_enabled?).and_return(true) expect(reporter).to receive(:run_status).and_return(nil) - expect(Chef::Log).to receive(:debug).with("Run failed before audits were initialized, not sending audit report to server") + expect(Chef::Log).to receive(:debug).with("Run failed before audit mode was initialized, not sending audit report to server") reporter.run_completed(node) end diff --git a/spec/unit/audit/runner_spec.rb b/spec/unit/audit/runner_spec.rb index 801147bdb9..0bd4c18388 100644 --- a/spec/unit/audit/runner_spec.rb +++ b/spec/unit/audit/runner_spec.rb @@ -36,6 +36,15 @@ describe Chef::Audit::Runner do RSpec::Core::Sandbox.sandboxed { ex.run } end + context "when we run in audit mode" do + let(:paths) { [ "/opt/chef/lib/chef/", 'C:\windows/here/lib/chef/' , "/opt/chef/extra/folders/lib/chef/"] } + it "excludes the current path from backtrace" do + paths.each do |path| + expect(runner.exclusion_pattern).to match(path) + end + end + end + describe "#initialize" do it "correctly sets the run_context during initialization" do expect(runner.instance_variable_get(:@run_context)).to eq(run_context) @@ -72,6 +81,7 @@ describe Chef::Audit::Runner do expect(RSpec.configuration.color).to eq(color) expect(RSpec.configuration.expose_dsl_globally?).to eq(false) + expect(RSpec.configuration.backtrace_exclusion_patterns).to include(runner.exclusion_pattern) expect(Specinfra.configuration.backend).to eq(:exec) end diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb index 06178f7733..6ea67246b5 100644 --- a/spec/unit/config_spec.rb +++ b/spec/unit/config_spec.rb @@ -360,18 +360,12 @@ describe Chef::Config do describe "Chef::Config[:user_home]" do it "should set when HOME is provided" do expected = to_platform("/home/kitten") - allow(Chef::Config).to receive(:env).and_return({ 'HOME' => expected }) - expect(Chef::Config[:user_home]).to eq(expected) - end - - it "should be set when only USERPROFILE is provided" do - expected = to_platform("/users/kitten") - allow(Chef::Config).to receive(:env).and_return({ 'USERPROFILE' => expected }) + allow(Chef::Util::PathHelper).to receive(:home).and_return(expected) expect(Chef::Config[:user_home]).to eq(expected) end it "falls back to the current working directory when HOME and USERPROFILE is not set" do - allow(Chef::Config).to receive(:env).and_return({}) + allow(Chef::Util::PathHelper).to receive(:home).and_return(nil) expect(Chef::Config[:user_home]).to eq(Dir.pwd) end end diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index 848af11db5..f1ca510ed3 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -115,7 +115,7 @@ describe Chef::Knife::Bootstrap do end def configure_env_home - ENV['HOME'] = "/env/home" + allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path) end def configure_gem_files @@ -123,15 +123,9 @@ describe Chef::Knife::Bootstrap do end before(:each) do - @original_home = ENV['HOME'] - ENV['HOME'] = nil expect(File).to receive(:exists?).with(bootstrap_template).and_return(false) end - after(:each) do - ENV['HOME'] = @original_home - end - context "when file is available everywhere" do before do configure_chef_config_dir @@ -161,7 +155,7 @@ describe Chef::Knife::Bootstrap do end end - context "when file is available in ENV['HOME']" do + context "when file is available in home directory" do before do configure_chef_config_dir configure_env_home @@ -180,7 +174,25 @@ describe Chef::Knife::Bootstrap do context "when file is available in Gem files" do before do configure_chef_config_dir + configure_env_home + configure_gem_files + + expect(File).to receive(:exists?).with(builtin_template_path).and_return(false) + expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false) + expect(File).to receive(:exists?).with(env_home_template_path).and_return(false) + expect(File).to receive(:exists?).with(gem_files_template_path).and_return(true) + end + + it "should load the template from Gem files" do + expect(knife.find_template).to eq(gem_files_template_path) + end + end + + context "when file is available in Gem files and home dir doesn't exist" do + before do + configure_chef_config_dir configure_gem_files + allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil) expect(File).to receive(:exists?).with(builtin_template_path).and_return(false) expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false) diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb index df42cff2ea..7f9308b28a 100644 --- a/spec/unit/knife/core/subcommand_loader_spec.rb +++ b/spec/unit/knife/core/subcommand_loader_spec.rb @@ -19,23 +19,29 @@ require 'spec_helper' describe Chef::Knife::SubcommandLoader do + let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) } + let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') } + let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') } + before do allow(Chef::Platform).to receive(:windows?) { false } - @home = File.join(CHEF_SPEC_DATA, 'knife-home') - @env = {'HOME' => @home} - @loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env) + Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) + end + + after do + Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) end it "builds a list of the core subcommand file require paths" do - expect(@loader.subcommand_files).not_to be_empty - @loader.subcommand_files.each do |require_path| + expect(loader.subcommand_files).not_to be_empty + loader.subcommand_files.each do |require_path| expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/) end end it "finds files installed via rubygems" do - expect(@loader.find_subcommands_via_rubygems).to include('chef/knife/node_create') - @loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])} + expect(loader.find_subcommands_via_rubygems).to include('chef/knife/node_create') + loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])} end it "finds files from latest version of installed gems" do @@ -54,23 +60,23 @@ describe Chef::Knife::SubcommandLoader do expect(gems[0]).to receive(:full_gem_path).and_return('/usr/lib/ruby/gems/knife-ec2-0.5.12') expect(Dir).to receive(:[]).with('/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb').and_return(gem_files) end - expect(@loader).to receive(:find_subcommands_via_dirglob).and_return({}) - expect(@loader.find_subcommands_via_rubygems.values.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files) + expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) + expect(loader.find_subcommands_via_rubygems.values.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files) end it "finds files using a dirglob when rubygems is not available" do - expect(@loader.find_subcommands_via_dirglob).to include('chef/knife/node_create') - @loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])} + expect(loader.find_subcommands_via_dirglob).to include('chef/knife/node_create') + loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])} end it "finds user-specific subcommands in the user's ~/.chef directory" do - expected_command = File.join(@home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb') - expect(@loader.site_subcommands).to include(expected_command) + expected_command = File.join(home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb') + expect(loader.site_subcommands).to include(expected_command) end it "finds repo specific subcommands by searching for a .chef directory" do expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb') - expect(@loader.site_subcommands).to include(expected_command) + expect(loader.site_subcommands).to include(expected_command) end # https://github.com/opscode/chef-dk/issues/227 @@ -137,25 +143,19 @@ describe Chef::Knife::SubcommandLoader do end before do - expect(@loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands) - expect(@loader).to receive(:find_subcommands_via_dirglob).and_return({}) + expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands) + expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) end it "ignores commands from the non-matching gem install" do - expect(@loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands) + expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands) end end describe "finding 3rd party plugins" do - let(:env_home) { "/home/alice" } - let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" } - - before do - env_dup = ENV.to_hash - allow(ENV).to receive(:[]) { |key| env_dup[key] } - allow(ENV).to receive(:[]).with("HOME").and_return(env_home) - end + let(:home) { "/home/alice" } + let(:manifest_path) { home + "/.chef/plugin_manifest.json" } context "when there is not a ~/.chef/plugin_manifest.json file" do before do @@ -168,14 +168,14 @@ describe Chef::Knife::SubcommandLoader do else expect(Gem.source_index).to receive(:latest_specs).and_call_original end - @loader.subcommand_files.each do |require_path| + loader.subcommand_files.each do |require_path| expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/) end end context "and HOME environment variable is not set" do before do - allow(ENV).to receive(:[]).with("HOME").and_return(nil) + allow(Chef::Util::PathHelper).to receive(:home).and_return(nil) end it "searches rubygems for plugins" do @@ -184,7 +184,7 @@ describe Chef::Knife::SubcommandLoader do else expect(Gem.source_index).to receive(:latest_specs).and_call_original end - @loader.subcommand_files.each do |require_path| + loader.subcommand_files.each do |require_path| expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/) end end @@ -215,7 +215,7 @@ describe Chef::Knife::SubcommandLoader do it "uses paths from the manifest instead of searching gems" do expect(Gem::Specification).not_to receive(:latest_specs).and_call_original - expect(@loader.subcommand_files).to include(ec2_server_create_plugin) + expect(loader.subcommand_files).to include(ec2_server_create_plugin) end end diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb index 85e1c1abab..1b61f9b238 100644 --- a/spec/unit/mixin/params_validate_spec.rb +++ b/spec/unit/mixin/params_validate_spec.rb @@ -339,69 +339,83 @@ describe Chef::Mixin::ParamsValidate do end.to raise_error(Chef::Exceptions::ValidationFailed) end - it "should set and return a value, then return the same value" do - value = "meow" - expect(@vo.set_or_return(:test, value, {}).object_id).to eq(value.object_id) - expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id) + def self.test_set_or_return_method(method) + # caller is responsible for passing in the right arg to get 'return' behavior + return_arg = method == :nillable_set_or_return ? TinyClass::NULL_ARG : nil + + it "#{method} should set and return a value, then return the same value" do + value = "meow" + expect(@vo.send(method,:test, value, {}).object_id).to eq(value.object_id) + expect(@vo.send(method,:test, return_arg, {}).object_id).to eq(value.object_id) + end + + it "#{method} should set and return a default value when the argument is nil, then return the same value" do + value = "meow" + expect(@vo.send(method,:test, return_arg, { :default => value }).object_id).to eq(value.object_id) + expect(@vo.send(method,:test, return_arg, {}).object_id).to eq(value.object_id) + end + + it "#{method} should raise an ArgumentError when argument is nil and required is true" do + expect { + @vo.send(method,:test, return_arg, { :required => true }) + }.to raise_error(ArgumentError) + end + + it "#{method} should not raise an error when argument is nil and required is false" do + expect { + @vo.send(method,:test, return_arg, { :required => false }) + }.not_to raise_error + end + + it "#{method} should set and return @name, then return @name for foo when argument is nil" do + value = "meow" + expect(@vo.send(method,:name, value, { }).object_id).to eq(value.object_id) + expect(@vo.send(method,:foo, return_arg, { :name_attribute => true }).object_id).to eq(value.object_id) + end + + it "#{method} should allow DelayedEvaluator instance to be set for value regardless of restriction" do + value = Chef::DelayedEvaluator.new{ 'test' } + @vo.send(method,:test, value, {:kind_of => Numeric}) + end + + it "#{method} should raise an error when delayed evaluated attribute is not valid" do + value = Chef::DelayedEvaluator.new{ 'test' } + @vo.send(method,:test, value, {:kind_of => Numeric}) + expect do + @vo.send(method,:test, return_arg, {:kind_of => Numeric}) + end.to raise_error(Chef::Exceptions::ValidationFailed) + end + + it "#{method} should create DelayedEvaluator instance when #lazy is used" do + @vo.send(method,:delayed, @vo.lazy{ 'test' }, {}) + expect(@vo.instance_variable_get(:@delayed)).to be_a(Chef::DelayedEvaluator) + end + + it "#{method} should execute block on each call when DelayedEvaluator" do + value = 'fubar' + @vo.send(method,:test, @vo.lazy{ value }, {}) + expect(@vo.send(method,:test, return_arg, {})).to eq('fubar') + value = 'foobar' + expect(@vo.send(method,:test, return_arg, {})).to eq('foobar') + value = 'fauxbar' + expect(@vo.send(method,:test, return_arg, {})).to eq('fauxbar') + end + + it "#{method} should not evaluate non DelayedEvaluator instances" do + value = lambda{ 'test' } + @vo.send(method,:test, value, {}) + expect(@vo.send(method,:test, return_arg, {}).object_id).to eq(value.object_id) + expect(@vo.send(method,:test, return_arg, {})).to be_a(Proc) + end end - it "should set and return a default value when the argument is nil, then return the same value" do - value = "meow" - expect(@vo.set_or_return(:test, nil, { :default => value }).object_id).to eq(value.object_id) - expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id) - end - - it "should raise an ArgumentError when argument is nil and required is true" do - expect { - @vo.set_or_return(:test, nil, { :required => true }) - }.to raise_error(ArgumentError) - end - - it "should not raise an error when argument is nil and required is false" do - expect { - @vo.set_or_return(:test, nil, { :required => false }) - }.not_to raise_error - end - - it "should set and return @name, then return @name for foo when argument is nil" do - value = "meow" - expect(@vo.set_or_return(:name, value, { }).object_id).to eq(value.object_id) - expect(@vo.set_or_return(:foo, nil, { :name_attribute => true }).object_id).to eq(value.object_id) - end + test_set_or_return_method(:set_or_return) + test_set_or_return_method(:nillable_set_or_return) - it "should allow DelayedEvaluator instance to be set for value regardless of restriction" do - value = Chef::DelayedEvaluator.new{ 'test' } - @vo.set_or_return(:test, value, {:kind_of => Numeric}) + it "nillable_set_or_return supports nilling values" do + expect(@vo.nillable_set_or_return(:test, "meow", {})).to eq("meow") + expect(@vo.nillable_set_or_return(:test, TinyClass::NULL_ARG, {})).to eq("meow") + expect(@vo.nillable_set_or_return(:test, nil, {})).to be_nil + expect(@vo.nillable_set_or_return(:test, TinyClass::NULL_ARG, {})).to be_nil end - - it "should raise an error when delayed evaluated attribute is not valid" do - value = Chef::DelayedEvaluator.new{ 'test' } - @vo.set_or_return(:test, value, {:kind_of => Numeric}) - expect do - @vo.set_or_return(:test, nil, {:kind_of => Numeric}) - end.to raise_error(Chef::Exceptions::ValidationFailed) - end - - it "should create DelayedEvaluator instance when #lazy is used" do - @vo.set_or_return(:delayed, @vo.lazy{ 'test' }, {}) - expect(@vo.instance_variable_get(:@delayed)).to be_a(Chef::DelayedEvaluator) - end - - it "should execute block on each call when DelayedEvaluator" do - value = 'fubar' - @vo.set_or_return(:test, @vo.lazy{ value }, {}) - expect(@vo.set_or_return(:test, nil, {})).to eq('fubar') - value = 'foobar' - expect(@vo.set_or_return(:test, nil, {})).to eq('foobar') - value = 'fauxbar' - expect(@vo.set_or_return(:test, nil, {})).to eq('fauxbar') - end - - it "should not evaluate non DelayedEvaluator instances" do - value = lambda{ 'test' } - @vo.set_or_return(:test, value, {}) - expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id) - expect(@vo.set_or_return(:test, nil, {})).to be_a(Proc) - end - end diff --git a/spec/unit/mixin/powershell_type_coercions_spec.rb b/spec/unit/mixin/powershell_type_coercions_spec.rb new file mode 100644 index 0000000000..988c3926c1 --- /dev/null +++ b/spec/unit/mixin/powershell_type_coercions_spec.rb @@ -0,0 +1,72 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'chef/mixin/powershell_type_coercions' +require 'base64' + +class Chef::PSTypeTester + include Chef::Mixin::PowershellTypeCoercions +end + +describe Chef::Mixin::PowershellTypeCoercions do + let (:test_class) { Chef::PSTypeTester.new } + + describe '#translate_type' do + it 'should single quote a string' do + expect(test_class.translate_type('foo')).to eq("'foo'") + end + + ["'", '"', '#', '`'].each do |c| + it "should base64 encode a string that contains #{c}" do + expect(test_class.translate_type("#{c}")).to match(Base64.strict_encode64(c)) + end + end + + it 'should not quote an integer' do + expect(test_class.translate_type(123)).to eq('123') + end + + it 'should not quote a floating point number' do + expect(test_class.translate_type(123.4)).to eq('123.4') + end + + it 'should return $false when an instance of FalseClass is provided' do + expect(test_class.translate_type(false)).to eq('$false') + end + + it 'should return $true when an instance of TrueClass is provided' do + expect(test_class.translate_type(true)).to eq('$true') + end + + it 'should translate all members of a hash and wrap them in @{} separated by ;' do + expect(test_class.translate_type({"a" => 1, "b" => 1.2, "c" => false, "d" => true + })).to eq("@{a=1;b=1.2;c=$false;d=$true}") + end + + it 'should translat all members of an array and them by a ,' do + expect(test_class.translate_type([true, false])).to eq('@($true,$false)') + end + + it 'should fall back :to_psobject if we have not defined at explicit rule' do + ps_obj = double("PSObject") + expect(ps_obj).to receive(:to_psobject).and_return('$true') + expect(test_class.translate_type(ps_obj)).to eq('($true)') + end + end +end diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 7aafc287ea..1dbd07a021 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -53,3 +53,25 @@ describe 'Chef::Platform#supports_dsc?' do end end end + +describe 'Chef::Platform#supports_dsc_invoke_resource?' do + it 'returns false if powershell is not present' do + node = Chef::Node.new + expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey + end + + ['1.0', '2.0', '3.0', '4.0', '5.0.10017.9'].each do |version| + it "returns false for Powershell #{version}" do + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = version + expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey + end + end + + it "returns true for Powershell 5.0.10018.0" do + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = "5.0.10018.0" + expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_truthy + end +end + diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb index fb65ef0fea..e0115bc42a 100644 --- a/spec/unit/platform_spec.rb +++ b/spec/unit/platform_spec.rb @@ -20,8 +20,6 @@ require 'spec_helper' describe "Chef::Platform supports" do [ - :mac_os_x, - :mac_os_x_server, :freebsd, :ubuntu, :debian, @@ -34,9 +32,6 @@ describe "Chef::Platform supports" do :gentoo, :arch, :solaris, - :mswin, - :mingw32, - :windows, :gcel, :ibm_powerkvm ].each do |platform| diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb new file mode 100644 index 0000000000..0a6c22bdcf --- /dev/null +++ b/spec/unit/provider/dsc_resource_spec.rb @@ -0,0 +1,84 @@ +# +# Author:: Jay Mundrawala (<jdm@chef.io>) +# +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef' +require 'spec_helper' + +describe Chef::Provider::DscResource do + let (:events) { Chef::EventDispatch::Dispatcher.new } + let (:run_context) { Chef::RunContext.new(node, {}, events) } + let (:resource) { Chef::Resource::DscResource.new("dscresource", run_context) } + let (:provider) do + Chef::Provider::DscResource.new(resource, run_context) + end + + context 'when Powershell does not support Invoke-DscResource' do + let (:node) { + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = '4.0' + node + } + + it 'raises a NoProviderAvailable exception' do + expect(provider).not_to receive(:meta_configuration) + expect{provider.run_action(:run)}.to raise_error( + Chef::Exceptions::NoProviderAvailable, /5\.0\.10018\.0/) + end + end + + context 'when Powershell supports Invoke-DscResource' do + let (:node) { + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = '5.0.10018.0' + node + } + + context 'when RefreshMode is not set to Disabled' do + let (:meta_configuration) { {'RefreshMode' => 'AnythingElse'}} + + it 'raises an exception' do + expect(provider).to receive(:meta_configuration).and_return( + meta_configuration) + expect { provider.run_action(:run) }.to raise_error( + Chef::Exceptions::NoProviderAvailable, /Disabled/) + end + end + + context 'when RefreshMode is set to Disabled' do + let (:meta_configuration) { {'RefreshMode' => 'Disabled'}} + + it 'does not update the resource if it is up to date' do + expect(provider).to receive(:meta_configuration).and_return( + meta_configuration) + expect(provider).to receive(:test_resource).and_return(true) + provider.run_action(:run) + expect(resource).not_to be_updated + end + + it 'converges the resource if it is not up to date' do + expect(provider).to receive(:meta_configuration).and_return( + meta_configuration) + expect(provider).to receive(:test_resource).and_return(false) + expect(provider).to receive(:set_resource) + provider.run_action(:run) + expect(resource).to be_updated + end + end + end +end diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb index ee9c9e89fb..b0cdb9969d 100644 --- a/spec/unit/provider/package/openbsd_spec.rb +++ b/spec/unit/provider/package/openbsd_spec.rb @@ -21,28 +21,116 @@ require 'ostruct' describe Chef::Provider::Package::Openbsd do + let(:node) do + node = Chef::Node.new + node.default['kernel'] = {'name' => 'OpenBSD', 'release' => '5.5', 'machine' => 'amd64'} + node + end + + let (:provider) do + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + Chef::Provider::Package::Openbsd.new(new_resource, run_context) + end + + let(:new_resource) { Chef::Resource::Package.new(name)} + before(:each) do - @node = Chef::Node.new - @node.default['kernel'] = {'name' => 'OpenBSD', 'release' => '5.5', 'machine' => 'amd64'} - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, {}, @events) ENV['PKG_PATH'] = nil end describe "install a package" do - before do - @name = 'ihavetoes' - @new_resource = Chef::Resource::Package.new(@name) - @current_resource = Chef::Resource::Package.new(@name) - @provider = Chef::Provider::Package::Openbsd.new(@new_resource, @run_context) - @provider.current_resource = @current_resource - end - it "should run the installation command" do - expect(@provider).to receive(:shell_out!).with( - "pkg_add -r #{@name}", - {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} - ) {OpenStruct.new :status => true} - @provider.install_package(@name, nil) + let(:name) { 'ihavetoes' } + let(:version) {'0.0'} + + context 'when not already installed' do + before do + allow(provider).to receive(:shell_out!).with("pkg_info -e \"#{name}->0\"", anything()).and_return(instance_double('shellout', :stdout => '')) + end + + context 'when there is a single candidate' do + + context 'when installing from source' do + it 'should run the installation command' do + pending('Installing from source is not supported yet') + # This is a consequence of load_current_resource being called before define_resource_requirements + # It can be deleted once an implementation is provided + allow(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( + instance_double('shellout', :stdout => "#{name}-#{version}\n")) + new_resource.source('/some/path/on/disk.tgz') + provider.run_action(:install) + end + end + + context 'when source is not provided' do + it 'should run the installation command' do + expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( + instance_double('shellout', :stdout => "#{name}-#{version}\n")) + expect(provider).to receive(:shell_out!).with( + "pkg_add -r #{name}-#{version}", + {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} + ) {OpenStruct.new :status => true} + provider.run_action(:install) + end + end + end + + context 'when there are multiple candidates' do + let(:flavor_a) { 'flavora' } + let(:flavor_b) { 'flavorb' } + + context 'if no version is specified' do + it 'should raise an exception' do + expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( + instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor_a}\n#{name}-#{version}-#{flavor_b}\n")) + expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /multiple matching candidates/) + end + end + + context 'if a flavor is specified' do + + let(:flavor) { 'flavora' } + let(:package_name) {'ihavetoes' } + let(:name) { "#{package_name}--#{flavor}" } + + context 'if no version is specified' do + it 'should run the installation command' do + expect(provider).to receive(:shell_out!).with("pkg_info -e \"#{package_name}->0\"", anything()).and_return(instance_double('shellout', :stdout => '')) + expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( + instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor}\n")) + expect(provider).to receive(:shell_out!).with( + "pkg_add -r #{name}-#{version}-#{flavor}", + {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} + ) {OpenStruct.new :status => true} + provider.run_action(:install) + end + end + + context 'if a version is specified' do + it 'runs the installation command' do + pending('Specifying both a version and flavor is not supported') + new_resource.version(version) + allow(provider).to receive(:shell_out!).with(/pkg_info -e/, anything()).and_return(instance_double('shellout', :stdout => '')) + allow(provider).to receive(:candidate_version).and_return("#{package_name}-#{version}-#{flavor}") + provider.run_action(:install) + end + end + end + + context 'if a version is specified' do + it 'should use the flavor from the version' do + expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}-#{version}-#{flavor_b}\"", anything()).and_return( + instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor_a}\n")) + + new_resource.version("#{version}-#{flavor_b}") + expect(provider).to receive(:shell_out!).with( + "pkg_add -r #{name}-#{version}-#{flavor_b}", + {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}} + ) {OpenStruct.new :status => true} + provider.run_action(:install) + end + end + end end end diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb index fb751592df..9905a6e4ae 100644 --- a/spec/unit/provider/service/macosx_spec.rb +++ b/spec/unit/provider/service/macosx_spec.rb @@ -22,17 +22,17 @@ describe Chef::Provider::Service::Macosx do describe ".gather_plist_dirs" do context "when HOME directory is set" do before do - allow(ENV).to receive(:[]).with('HOME').and_return("/User/someuser") + allow(Chef::Util::PathHelper).to receive(:home).with('Library', 'LaunchAgents').and_yield('/Users/someuser/Library/LaunchAgents') end it "includes users's LaunchAgents folder" do - expect(described_class.gather_plist_dirs).to include("#{ENV['HOME']}/Library/LaunchAgents") + expect(described_class.gather_plist_dirs).to include("/Users/someuser/Library/LaunchAgents") end end context "when HOME directory is not set" do before do - allow(ENV).to receive(:[]).with('HOME').and_return(nil) + allow(Chef::Util::PathHelper).to receive(:home).with('Library', 'LaunchAgents').and_return(nil) end it "doesn't include user's LaunchAgents folder" do diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index a9fa08ebfd..63c381f08e 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -452,6 +452,137 @@ describe Chef::ProviderResolver do end + describe "for the package provider" do + let(:resource_name) { :package } + + before do + expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) + end + + %w{mac_os_x mac_os_x_server}.each do |platform| + describe "on #{platform}" do + let(:os) { "darwin" } + let(:platform) { platform } + let(:platform_family) { "mac_os_x" } + let(:platform_version) { "10.9.2" } + + + it "returns a Chef::Provider::Package::Homebrew provider" do + expect(resolved_provider).to eql(Chef::Provider::Package::Homebrew) + end + end + end + end + + provider_mapping = { + "mac_os_x" => { + :package => Chef::Provider::Package::Homebrew, + :user => Chef::Provider::User::Dscl, + :group => Chef::Provider::Group::Dscl, + }, + "mac_os_x_server" => { + :package => Chef::Provider::Package::Homebrew, + :user => Chef::Provider::User::Dscl, + :group => Chef::Provider::Group::Dscl, + }, + "mswin" => { + :env => Chef::Provider::Env::Windows, + :user => Chef::Provider::User::Windows, + :group => Chef::Provider::Group::Windows, + :mount => Chef::Provider::Mount::Windows, + :batch => Chef::Provider::Batch, + :powershell_script => Chef::Provider::PowershellScript, + }, + "mingw32" => { + :env => Chef::Provider::Env::Windows, + :user => Chef::Provider::User::Windows, + :group => Chef::Provider::Group::Windows, + :mount => Chef::Provider::Mount::Windows, + :batch => Chef::Provider::Batch, + :powershell_script => Chef::Provider::PowershellScript, + }, + "windows" => { + :env => Chef::Provider::Env::Windows, + :user => Chef::Provider::User::Windows, + :group => Chef::Provider::Group::Windows, + :mount => Chef::Provider::Mount::Windows, + :batch => Chef::Provider::Batch, + :powershell_script => Chef::Provider::PowershellScript, + }, + "aix" => { + :cron => Chef::Provider::Cron::Aix, + }, + "netbsd"=> { + :group => Chef::Provider::Group::Groupmod, + }, + "openbsd" => { + :group => Chef::Provider::Group::Usermod, + :package => Chef::Provider::Package::Openbsd, + }, + } + + def self.do_platform(platform_hash) + platform_hash.each do |resource, provider| + describe "for #{resource}" do + let(:resource_name) { resource } + + it "resolves to a #{provider}" do + expect(resolved_provider).to eql(provider) + end + end + end + end + + describe "individual platform mappings" do + let(:resource_name) { :user } + + before do + expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup) + end + + %w{mac_os_x mac_os_x_server}.each do |platform| + describe "on #{platform}" do + let(:os) { "darwin" } + let(:platform) { platform } + let(:platform_family) { "mac_os_x" } + let(:platform_version) { "10.9.2" } + + do_platform(provider_mapping[platform]) + end + end + + %w{mswin mingw32 windows}.each do |platform| + describe "on #{platform}" do + let(:os) { "windows" } + let(:platform) { platform } + let(:platform_family) { "windows" } + let(:platform_version) { "10.9.2" } + + do_platform(provider_mapping[platform]) + end + end + + describe "on AIX" do + let(:os) { "aix" } + let(:platform) { "aix" } + let(:platform_family) { "aix" } + let(:platform_version) { "6.2" } + + do_platform(provider_mapping['aix']) + end + + %w{netbsd openbsd}.each do |platform| + describe "on #{platform}" do + let(:os) { platform } + let(:platform) { platform } + let(:platform_family) { platform } + let(:platform_version) { "10.0-RELEASE" } + + do_platform(provider_mapping[platform]) + end + end + end + describe "resolving static providers" do def resource_class(resource) Chef::Resource.const_get(convert_to_class_name(resource.to_s)) @@ -481,6 +612,7 @@ describe Chef::ProviderResolver do link: Chef::Provider::Link, log: Chef::Provider::Log::ChefLog, macports_package: Chef::Provider::Package::Macports, + mdadm: Chef::Provider::Mdadm, pacman_package: Chef::Provider::Package::Pacman, paludis_package: Chef::Provider::Package::Paludis, perl: Chef::Provider::Script, diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index 8d0b1bcfd2..e1604483f3 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -593,5 +593,9 @@ describe Chef::Recipe do expect(recipe.singleton_class.included_modules).to include(Chef::DSL::Audit) expect(recipe.respond_to?(:control_group)).to be true end + + it "should respond to :ps_credential from Chef::DSL::Powershell" do + expect(recipe.respond_to?(:ps_credential)).to be true + end end end diff --git a/spec/unit/resource/deploy_spec.rb b/spec/unit/resource/deploy_spec.rb index 0403a7ba6b..07f5f973c0 100644 --- a/spec/unit/resource/deploy_spec.rb +++ b/spec/unit/resource/deploy_spec.rb @@ -30,12 +30,35 @@ describe Chef::Resource::Deploy do class << self + + def resource_has_a_hash_attribute(attr_name) + it "has a Hash attribute for #{attr_name.to_s}" do + @resource.send(attr_name, {foo: "bar"}) + expect(@resource.send(attr_name)).to eql({foo: "bar"}) + expect {@resource.send(attr_name, 8675309)}.to raise_error(ArgumentError) + end + + it "the Hash attribute for #{attr_name.to_s} is nillable" do + @resource.send(attr_name, {foo: "bar"}) + expect(@resource.send(attr_name)).to eql({foo: "bar"}) + @resource.send(attr_name, nil) + expect(@resource.send(attr_name)).to eql(nil) + end + end + def resource_has_a_string_attribute(attr_name) it "has a String attribute for #{attr_name.to_s}" do @resource.send(attr_name, "this is a string") expect(@resource.send(attr_name)).to eql("this is a string") expect {@resource.send(attr_name, 8675309)}.to raise_error(ArgumentError) end + + it "the String attribute for #{attr_name.to_s} is nillable" do + @resource.send(attr_name, "this is a string") + expect(@resource.send(attr_name)).to eql("this is a string") + @resource.send(attr_name, nil) + expect(@resource.send(attr_name)).to eql(nil) + end end def resource_has_a_boolean_attribute(attr_name, opts={:defaults_to=>false}) @@ -189,6 +212,10 @@ describe Chef::Resource::Deploy do expect(@resource.symlink_before_migrate).to eq({"wtf?" => "wtf is going on"}) end + resource_has_a_hash_attribute :symlink_before_migrate + resource_has_a_hash_attribute :symlinks + resource_has_a_hash_attribute :additional_remotes + resource_has_a_callback_attribute :before_migrate resource_has_a_callback_attribute :before_symlink resource_has_a_callback_attribute :before_restart diff --git a/spec/unit/resource/dsc_resource_spec.rb b/spec/unit/resource/dsc_resource_spec.rb new file mode 100644 index 0000000000..ae15f56eaf --- /dev/null +++ b/spec/unit/resource/dsc_resource_spec.rb @@ -0,0 +1,85 @@ +# +# Author:: Adam Edwards (<adamed@getchef.com>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe Chef::Resource::DscResource do + let(:dsc_test_resource_name) { 'DSCTest' } + let(:dsc_test_property_name) { :DSCTestProperty } + let(:dsc_test_property_value) { 'DSCTestValue' } + + context 'when Powershell supports Dsc' do + let(:dsc_test_run_context) { + node = Chef::Node.new + node.automatic[:languages][:powershell][:version] = '5.0.10018.0' + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + } + let(:dsc_test_resource) { + Chef::Resource::DscResource.new(dsc_test_resource_name, dsc_test_run_context) + } + + it "has a default action of `:run`" do + expect(dsc_test_resource.action).to eq(:run) + end + + it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do + expect(dsc_test_resource.allowed_actions.to_set).to eq([:run,:nothing].to_set) + end + + it "allows the resource attribute to be set" do + dsc_test_resource.resource(dsc_test_resource_name) + expect(dsc_test_resource.resource).to eq(dsc_test_resource_name) + end + + it "allows the module_name attribute to be set" do + dsc_test_resource.module_name(dsc_test_resource_name) + expect(dsc_test_resource.module_name).to eq(dsc_test_resource_name) + end + + context "when setting a dsc property" do + it "allows setting a dsc property with a property name of type Symbol" do + dsc_test_resource.property(dsc_test_property_name, dsc_test_property_value) + expect(dsc_test_resource.property(dsc_test_property_name)).to eq(dsc_test_property_value) + expect(dsc_test_resource.properties[dsc_test_property_name]).to eq(dsc_test_property_value) + end + + it "raises a TypeError if property_name is not a symbol" do + expect{ + dsc_test_resource.property('Foo', dsc_test_property_value) + }.to raise_error(TypeError) + end + + context "when using DelayedEvaluators" do + it "allows setting a dsc property with a property name of type Symbol" do + dsc_test_resource.property(dsc_test_property_name, Chef::DelayedEvaluator.new { + dsc_test_property_value + }) + expect(dsc_test_resource.property(dsc_test_property_name)).to eq(dsc_test_property_value) + expect(dsc_test_resource.properties[dsc_test_property_name]).to eq(dsc_test_property_value) + end + end + end + + context 'Powershell DSL methods' do + it "responds to :ps_credential" do + expect(dsc_test_resource.respond_to?(:ps_credential)).to be true + end + end + end +end diff --git a/spec/unit/shell_spec.rb b/spec/unit/shell_spec.rb index 0e028f4359..617abcfde2 100644 --- a/spec/unit/shell_spec.rb +++ b/spec/unit/shell_spec.rb @@ -56,7 +56,7 @@ describe Shell do describe "configuring IRB" do it "configures irb history" do Shell.configure_irb - expect(Shell.irb_conf[:HISTORY_FILE]).to eq("~/.chef/chef_shell_history") + expect(Shell.irb_conf[:HISTORY_FILE]).to eq("#{ENV['HOME']}/.chef/chef_shell_history") expect(Shell.irb_conf[:SAVE_HISTORY]).to eq(1000) end diff --git a/spec/unit/util/dsc/resource_store.rb b/spec/unit/util/dsc/resource_store.rb new file mode 100644 index 0000000000..a89e73fcaa --- /dev/null +++ b/spec/unit/util/dsc/resource_store.rb @@ -0,0 +1,76 @@ +# +# Author:: Jay Mundrawala <jdm@chef.io> +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef' +require 'chef/util/dsc/resource_store' + +describe Chef::Util::DSC::ResourceStore do + let(:resource_store) { Chef::Util::DSC::ResourceStore.new } + let(:resource_a) { { + 'ResourceType' => 'AFoo', + 'Name' => 'Foo', + 'Module' => {'Name' => 'ModuleA'} + } + } + + let(:resource_b) { { + 'ResourceType' => 'BFoo', + 'Name' => 'Foo', + 'Module' => {'Name' => 'ModuleB'} + } + } + + context 'when resources are not cached' do + context 'when calling #resources' do + it 'returns an empty array' do + expect(resource_store.resources).to eql([]) + end + end + + context 'when calling #find' do + it 'returns an empty list if it cannot find any matching resources' do + expect(resource_store).to receive(:query_resource).and_return([]) + expect(resource_store.find('foo')).to eql([]) + end + + it 'returns the resource if it is found (comparisons are case insensitive)' do + expect(resource_store).to receive(:query_resource).and_return([resource_a]) + expect(resource_store.find('foo')).to eql([resource_a]) + end + + it 'returns multiple resoures if they are found' do + expect(resource_store).to receive(:query_resource).and_return([resource_a, resource_b]) + expect(resource_store.find('foo')).to include(resource_a, resource_b) + end + + it 'deduplicates resources by ResourceName' do + expect(resource_store).to receive(:query_resource).and_return([resource_a, resource_a]) + resource_store.find('foo') + expect(resource_store.resources).to eq([resource_a]) + end + end + end + + context 'when resources are cached' do + it 'recalls resources from the cache if present' do + expect(resource_store).not_to receive(:query_resource) + expect(resource_store).to receive(:resources).and_return([resource_a]) + resource_store.find('foo') + end + end +end diff --git a/spec/unit/util/powershell/ps_credential_spec.rb b/spec/unit/util/powershell/ps_credential_spec.rb new file mode 100644 index 0000000000..bac58b02e5 --- /dev/null +++ b/spec/unit/util/powershell/ps_credential_spec.rb @@ -0,0 +1,37 @@ +# +# Author:: Jay Mundrawala <jdm@chef.io> +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef' +require 'chef/util/powershell/ps_credential' + +describe Chef::Util::Powershell::PSCredential do + let (:username) { 'foo' } + let (:password) { 'password' } + + context 'when username and password are provided' do + let(:ps_credential) { Chef::Util::Powershell::PSCredential.new(username, password)} + context 'when calling to_psobject' do + it 'should create the script to create a PSCredential when calling' do + allow(ps_credential).to receive(:encrypt).with(password).and_return('encrypted') + expect(ps_credential.to_psobject).to eq( + "New-Object System.Management.Automation.PSCredential("\ + "'#{username}',('encrypted' | ConvertTo-SecureString))") + end + end + end +end diff --git a/spec/unit/workstation_config_loader_spec.rb b/spec/unit/workstation_config_loader_spec.rb index a865103188..72631f3dfa 100644 --- a/spec/unit/workstation_config_loader_spec.rb +++ b/spec/unit/workstation_config_loader_spec.rb @@ -65,7 +65,7 @@ describe Chef::WorkstationConfigLoader do let(:home) { "/Users/example.user" } before do - env["HOME"] = home + allow(Chef::Util::PathHelper).to receive(:home).with('.chef').and_yield(File.join(home, '.chef')) allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/knife.rb").and_return(true) end |