diff options
author | Nate Walck <nwalck@fb.com> | 2015-07-20 14:44:27 -0700 |
---|---|---|
committer | Nate Walck <nwalck@fb.com> | 2015-07-20 14:44:27 -0700 |
commit | 6f6a21f17a3f42256a805afa90d45e54c754641d (patch) | |
tree | f6a4593921a7c87cf9ed2b889b37f6aaf4d61b20 | |
parent | 27dc8b7bc9cc5f7ba553c83072215132c840c3ef (diff) | |
parent | c91b3371036de811c9b2ba0e26f287e676dfd96f (diff) | |
download | chef-6f6a21f17a3f42256a805afa90d45e54c754641d.tar.gz |
Merge remote-tracking branch 'upstream/master'
183 files changed, 6038 insertions, 3268 deletions
diff --git a/.travis.yml b/.travis.yml index 2910725d7b..d7ad317e28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ branches: - master - 10-stable - 11-stable - - 12-stable # do not run expensive spec tests on PRs, only on branches script: " diff --git a/CHANGELOG.md b/CHANGELOG.md index df15462289..28886da594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,35 @@ -## 12.5.0 +## Unreleased * [**Ranjib Dey**](https://github.com/ranjib): [pr#3588](https://github.com/chef/chef/pull/3588) Count skipped resources among total resources in doc formatter +* [**John Kerry**](https://github.com/jkerry): + [pr#3539](https://github.com/chef/chef/pull/3539) Fix issue: registry_key resource is case sensitive in chef but not on windows +* [**David Eddy**](https://github.com/bahamas10): + [pr#3443](https://github.com/chef/chef/pull/3443) remove extraneous space + + +* [pr#3455](https://github.com/chef/chef/pull/3455) powershell_script: do not allow suppression of syntax errors +* [pr#3519](https://github.com/chef/chef/pull/3519) The wording seemed odd. +* [pr#3208](https://github.com/chef/chef/pull/3208) Missing require (require what you use). +* [pr#3449](https://github.com/chef/chef/pull/3449) correcting minor typo in user_edit knife action +* [pr#3572](https://github.com/chef/chef/pull/3572) Use windows paths without case-sensitivity. +* [pr#3666](https://github.com/chef/chef/pull/3666) Support SNI in `knife ssl check`. +* [pr#3667](https://github.com/chef/chef/pull/3667) Change chef service to start as 'Automatic delayed start'. +* [pr#3683](https://github.com/chef/chef/pull/3683) Correct Windows reboot command to delay in minutes, per the property. + +## 12.4.1 + +* [**Noah Kantrowitz**](https://github.com/coderanger): + [pr#3605](https://github.com/chef/chef/pull/3605) Rework `Resource#action` to match 12.3 API + +* [pr#3586](https://github.com/chef/chef/issues/3586) Fix bug preventing light weight resources from being used with heavy weight providers +* [Issue #3593](https://github.com/chef/chef/issues/3593) Fix bug where provider priority map did not take into consideration a provided block +* [pr#3630](https://github.com/chef/chef/pull/3630) Restore Chef::User and Chef::ApiClient namespace to API V0 functionality and move new functionality into Chef::UserV1 and Chef::ApiClientV1 until Chef 13. +* [pr#3611](https://github.com/chef/chef/pull/3611) Call `provides?` even if `provides` is not called +* [pr#3589](https://github.com/chef/chef/pull/3589) Fix errant bashisms +* [pr#3620](https://github.com/chef/chef/pull/3620) Fix issue where recipe names in run list mutate when version constaints are present +* [pr#3623](https://github.com/chef/chef/pull/3623) Allow LWRPs to access the real class when accessed through `Chef::Resource` and `Chef::Provider` +* [pr#3627](https://github.com/chef/chef/pull/3627) Separate priority map and DSL handler map so that `provides` has veto power over priority +* [pr#3638](https://github.com/chef/chef/pull/3638) Deprecate passing more than 1 argument to create a resource ## 12.4.0 diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 43009d1548..b1ae0713f1 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -29,6 +29,7 @@ another component. ### Maintainers * [Bryan McLellan](https://github.com/btm) +* [Noah Kantrowitz](https://github.com/coderanger) * [Daniel DeLeo](https://github.com/danielsdeleo) * [AJ Christensen](https://github.com/fujin) * [Phil Dibowitz](https://github.com/jaymzh) @@ -189,4 +190,5 @@ The specific components of Chef related to a given platform - including (but not ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) +* [Ryan Cragun](https://github.com/ryancragun) diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml index 8f36763e54..257058b4d2 100644 --- a/MAINTAINERS.toml +++ b/MAINTAINERS.toml @@ -34,6 +34,7 @@ another component. maintainers = [ "btm", + "coderanger", "danielsdeleo", "fujin", "jaymzh", @@ -199,7 +200,8 @@ The specific components of Chef related to a given platform - including (but not title = "ArchLinux" maintainers = [ - "lamont-granquist" + "lamont-granquist", + "ryancragun" ] [people] @@ -290,3 +292,11 @@ The specific components of Chef related to a given platform - including (but not [people.joemiller] Name = "Joe Miller" GitHub = "joemiller" + + [people.coderanger] + Name = "Noah Kantrowitz" + GitHub = "coderanger" + + [people.ryancragun] + Name = "Ryan Cragun" + GitHub = "ryancragun" diff --git a/appveyor.yml b/appveyor.yml index 5609648cb1..06448e2be2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,7 @@ skip_tags: true branches: only: - master - - 12-stable + - 12.4-stable cache: - C:\Ruby200\lib\ruby\gems\2.0.0 diff --git a/bin/chef-service-manager b/bin/chef-service-manager index 7cef10f506..bbcab5ca92 100755 --- a/bin/chef-service-manager +++ b/bin/chef-service-manager @@ -28,7 +28,8 @@ if Chef::Platform.windows? :service_name => "chef-client", :service_display_name => "Chef Client Service", :service_description => "Runs Chef Client on regular, configurable intervals.", - :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../../../bin/chef-windows-service')) + :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../../../bin/chef-windows-service')), + :delayed_start => true } Chef::Application::WindowsServiceManager.new(chef_client_service).run else diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index 63de8a451f..301a3ba0b6 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -739,6 +739,3 @@ module ChefConfig end end end - - - diff --git a/chef-config/lib/chef-config/exceptions.rb b/chef-config/lib/chef-config/exceptions.rb index f5d76d856b..1f80e505df 100644 --- a/chef-config/lib/chef-config/exceptions.rb +++ b/chef-config/lib/chef-config/exceptions.rb @@ -20,7 +20,7 @@ require 'chef-config/logger' module ChefConfig - class InvalidPath < StandardError - end + class ConfigurationError < ArgumentError; end + class InvalidPath < StandardError; end end diff --git a/chef-config/lib/chef-config/workstation_config_loader.rb b/chef-config/lib/chef-config/workstation_config_loader.rb new file mode 100644 index 0000000000..177cd776d4 --- /dev/null +++ b/chef-config/lib/chef-config/workstation_config_loader.rb @@ -0,0 +1,179 @@ +# +# Author:: Daniel DeLeo (<dan@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-config/config' +require 'chef-config/exceptions' +require 'chef-config/logger' +require 'chef-config/path_helper' +require 'chef-config/windows' + +module ChefConfig + class WorkstationConfigLoader + + # Path to a config file requested by user, (e.g., via command line option). Can be nil + attr_accessor :explicit_config_file + + # TODO: initialize this with a logger for Chef and Knife + def initialize(explicit_config_file, logger=nil) + @explicit_config_file = explicit_config_file + @chef_config_dir = nil + @config_location = nil + @logger = logger || NullLogger.new + end + + def no_config_found? + config_location.nil? + end + + def config_location + @config_location ||= (explicit_config_file || locate_local_config) + end + + def chef_config_dir + if @chef_config_dir.nil? + @chef_config_dir = false + full_path = working_directory.split(File::SEPARATOR) + (full_path.length - 1).downto(0) do |i| + candidate_directory = File.join(full_path[0..i] + [".chef"]) + if File.exist?(candidate_directory) && File.directory?(candidate_directory) + @chef_config_dir = candidate_directory + break + end + end + end + @chef_config_dir + end + + def load + # Ignore it if there's no explicit_config_file and can't find one at a + # default path. + return false if config_location.nil? + + if explicit_config_file && !path_exists?(config_location) + raise ChefConfig::ConfigurationError, "Specified config file #{config_location} does not exist" + end + + # Have to set Config.config_file b/c other config is derived from it. + Config.config_file = config_location + read_config(IO.read(config_location), config_location) + end + + # (Private API, public for test purposes) + def env + ENV + end + + # (Private API, public for test purposes) + def path_exists?(path) + Pathname.new(path).expand_path.exist? + end + + private + + def have_config?(path) + if path_exists?(path) + logger.info("Using config at #{path}") + true + else + logger.debug("Config not found at #{path}, trying next option") + false + end + end + + def locate_local_config + candidate_configs = [] + + # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine) + if env['KNIFE_HOME'] + candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb') + candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb') + end + # Look for $PWD/knife.rb + if Dir.pwd + candidate_configs << File.join(Dir.pwd, 'config.rb') + candidate_configs << File.join(Dir.pwd, 'knife.rb') + end + # Look for $UPWARD/.chef/knife.rb + if chef_config_dir + candidate_configs << File.join(chef_config_dir, 'config.rb') + candidate_configs << File.join(chef_config_dir, 'knife.rb') + end + # Look for $HOME/.chef/knife.rb + 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 | + have_config?(candidate_config) + end + end + + def working_directory + a = if ChefConfig.windows? + env['CD'] + else + env['PWD'] + end || Dir.pwd + + a + end + + def read_config(config_content, config_file_path) + Config.from_string(config_content, config_file_path) + rescue SignalException + raise + rescue SyntaxError => e + message = "" + message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n" + message << "#{e.class.name}: #{e.message}\n" + if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/] + line = file_line[/:([\d]+)$/, 1].to_i + message << highlight_config_error(config_file_path, line) + end + raise ChefConfig::ConfigurationError, message + rescue Exception => e + message = "You have an error in your config file #{config_file_path}\n\n" + message << "#{e.class.name}: #{e.message}\n" + filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/) + filtered_trace.each {|bt_line| message << " " << bt_line << "\n" } + if !filtered_trace.empty? + line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1] + message << highlight_config_error(config_file_path, line_nr.to_i) + end + raise ChefConfig::ConfigurationError, message + end + + + def highlight_config_error(file, line) + config_file_lines = [] + IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"} + if line == 1 + lines = config_file_lines[0..3] + else + lines = config_file_lines[Range.new(line - 2, line)] + end + "Relevant file content:\n" + lines.join("\n") + "\n" + end + + def logger + @logger + end + + end +end diff --git a/spec/unit/workstation_config_loader_spec.rb b/chef-config/spec/unit/workstation_config_loader_spec.rb index 72631f3dfa..9f24a4f11b 100644 --- a/spec/unit/workstation_config_loader_spec.rb +++ b/chef-config/spec/unit/workstation_config_loader_spec.rb @@ -18,9 +18,12 @@ require 'spec_helper' require 'tempfile' -require 'chef/workstation_config_loader' -describe Chef::WorkstationConfigLoader do +require 'chef-config/exceptions' +require 'chef-config/windows' +require 'chef-config/workstation_config_loader' + +RSpec.describe ChefConfig::WorkstationConfigLoader do let(:explicit_config_location) { nil } @@ -65,7 +68,7 @@ describe Chef::WorkstationConfigLoader do let(:home) { "/Users/example.user" } before do - allow(Chef::Util::PathHelper).to receive(:home).with('.chef').and_yield(File.join(home, '.chef')) + allow(ChefConfig::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 @@ -88,7 +91,7 @@ describe Chef::WorkstationConfigLoader do let(:env_pwd) { "/path/to/cwd" } before do - if Chef::Platform.windows? + if ChefConfig.windows? env["CD"] = env_pwd else env["PWD"] = env_pwd @@ -224,7 +227,7 @@ describe Chef::WorkstationConfigLoader do let(:explicit_config_location) { "/nope/nope/nope/frab/jab/nab" } it "raises a configuration error" do - expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError) + expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end @@ -233,15 +236,20 @@ describe Chef::WorkstationConfigLoader do let(:config_content) { "" } + # We need to keep a reference to the tempfile because while #close does + # not unlink the file, the object being GC'd will. + let(:tempfile) do + Tempfile.new("Chef-WorkstationConfigLoader-rspec-test").tap do |t| + t.print(config_content) + t.close + end + end + let(:explicit_config_location) do - # could use described_class, but remove all ':' from the path if so. - t = Tempfile.new("Chef-WorkstationConfigLoader-rspec-test") - t.print(config_content) - t.close - t.path + tempfile.path end - after { File.unlink(explicit_config_location) if File.exists?(explicit_config_location) } + after { File.unlink(explicit_config_location) if File.exist?(explicit_config_location) } context "and is valid" do @@ -249,12 +257,12 @@ describe Chef::WorkstationConfigLoader do it "loads the config" do expect(config_loader.load).to be(true) - expect(Chef::Config.config_file_evaluated).to be(true) + expect(ChefConfig::Config.config_file_evaluated).to be(true) end - it "sets Chef::Config.config_file" do + it "sets ChefConfig::Config.config_file" do config_loader.load - expect(Chef::Config.config_file).to eq(explicit_config_location) + expect(ChefConfig::Config.config_file).to eq(explicit_config_location) end end @@ -263,7 +271,7 @@ describe Chef::WorkstationConfigLoader do let(:config_content) { "{{{{{:{{" } it "raises a ConfigurationError" do - expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError) + expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end @@ -272,7 +280,7 @@ describe Chef::WorkstationConfigLoader do let(:config_content) { ":foo\n:bar\nraise 'oops'\n:baz\n" } it "raises a ConfigurationError" do - expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError) + expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end diff --git a/chef-windows.gemspec b/chef-windows.gemspec index 167358c7d7..428174889f 100644 --- a/chef-windows.gemspec +++ b/chef-windows.gemspec @@ -10,7 +10,7 @@ gemspec.add_dependency "win32-eventlog", "~> 0.6.2" gemspec.add_dependency "win32-mmap", "~> 0.4.1" gemspec.add_dependency "win32-mutex", "~> 0.4.2" gemspec.add_dependency "win32-process", "~> 0.7.5" -gemspec.add_dependency "win32-service", "0.8.6" +gemspec.add_dependency "win32-service", "~> 0.8.7" gemspec.add_dependency "windows-api", "~> 0.4.4" gemspec.add_dependency "windows-pr", "~> 1.2.4" gemspec.add_dependency "wmi-lite", "~> 1.0" diff --git a/lib/chef.rb b/lib/chef.rb index 6bce976439..1a0b802adb 100644 --- a/lib/chef.rb +++ b/lib/chef.rb @@ -31,5 +31,5 @@ require 'chef/daemon' require 'chef/run_status' require 'chef/handler' require 'chef/handler/json_file' - +require 'chef/event_dispatch/dsl' require 'chef/chef_class' diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb index ad31fb7d7b..b7b9f7dc43 100644 --- a/lib/chef/api_client.rb +++ b/lib/chef/api_client.rb @@ -1,7 +1,7 @@ # -# Author:: Adam Jacob (<adam@chef.io>) -# Author:: Nuo Yan (<nuo@chef.io>) -# Copyright:: Copyright (c) 2008, 2015 Chef Software, Inc. +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,18 +23,18 @@ require 'chef/mixin/from_file' require 'chef/mash' require 'chef/json_compat' require 'chef/search/query' -require 'chef/exceptions' -require 'chef/mixin/api_version_request_handling' require 'chef/server_api' +# DEPRECATION NOTE +# +# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1, +# which will be moved to this namespace. New development should occur in +# Chef::ApiClientV1 until the time before Chef 13. class Chef class ApiClient include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - include Chef::Mixin::ApiVersionRequestHandling - - SUPPORTED_API_VERSIONS = [0,1] # Create a new Chef::ApiClient object. def initialize @@ -43,25 +43,6 @@ class Chef @private_key = nil @admin = false @validator = false - @create_key = nil - end - - def chef_rest_v0 - @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}) - end - - def chef_rest_v1 - @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1"}) - end - - # will default to the current version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) - def http_api - @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url]) - end - - # will default to the current version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) - def self.http_api - Chef::REST.new(Chef::Config[:chef_server_url]) end # Gets or sets the client name. @@ -113,8 +94,7 @@ class Chef ) end - # Private key. The server will return it as a string. - # Set to true under API V0 to have the server regenerate the default key. + # Gets or sets the private key. # # @params [Optional String] The string representation of the private key. # @return [String] The current value. @@ -122,19 +102,7 @@ class Chef set_or_return( :private_key, arg, - :kind_of => [String, TrueClass, FalseClass] - ) - end - - # Used to ask server to generate key pair under api V1 - # - # @params [Optional True/False] Should be true or false - default is false. - # @return [True/False] The current value - def create_key(arg=nil) - set_or_return( - :create_key, - arg, - :kind_of => [ TrueClass, FalseClass ] + :kind_of => [String, FalseClass] ) end @@ -145,14 +113,13 @@ class Chef def to_hash result = { "name" => @name, + "public_key" => @public_key, "validator" => @validator, "admin" => @admin, 'json_class' => self.class.name, "chef_type" => "client" } - result["private_key"] = @private_key unless @private_key.nil? - result["public_key"] = @public_key unless @public_key.nil? - result["create_key"] = @create_key unless @create_key.nil? + result["private_key"] = @private_key if @private_key result end @@ -166,11 +133,10 @@ class Chef def self.from_hash(o) client = Chef::ApiClient.new client.name(o["name"] || o["clientname"]) + client.private_key(o["private_key"]) if o.key?("private_key") + client.public_key(o["public_key"]) client.admin(o["admin"]) client.validator(o["validator"]) - client.private_key(o["private_key"]) if o.key?("private_key") - client.public_key(o["public_key"]) if o.key?("public_key") - client.create_key(o["create_key"]) if o.key?("create_key") client end @@ -182,6 +148,10 @@ class Chef from_hash(Chef::JSONCompat.parse(j)) end + def self.http_api + Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}) + end + def self.reregister(name) api_client = load(name) api_client.reregister @@ -218,11 +188,11 @@ class Chef # Save this client via the REST API, returns a hash including the private key def save begin - update + http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator}) rescue Net::HTTPServerException => e # If that fails, go ahead and try and update it if e.response.code == "404" - create + http_api.post("clients", {:name => self.name, :admin => self.admin, :validator => self.validator }) else raise e end @@ -230,95 +200,18 @@ class Chef end def reregister - # Try API V0 and if it fails due to V0 not being supported, raise the proper error message. - # reregister only supported in API V0 or lesser. - reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true }) + reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true }) if reregistered_self.respond_to?(:[]) private_key(reregistered_self["private_key"]) else private_key(reregistered_self.private_key) end self - rescue Net::HTTPServerException => e - # if there was a 406 related to versioning, give error explaining that - # only API version 0 is supported for reregister command - if e.response.code == "406" && e.response["x-ops-server-api-version"] - version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"]) - min_version = version_header["min_version"] - max_version = version_header["max_version"] - error_msg = reregister_only_v0_supported_error_msg(max_version, min_version) - raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg) - else - raise e - end - end - - # Updates the client via the REST API - def update - # NOTE: API V1 dropped support for updating client keys via update (aka PUT), - # but this code never supported key updating in the first place. Since - # it was never implemented, we will simply ignore that functionality - # as it is being deprecated. - # Delete this comment after V0 support is dropped. - payload = { :name => name } - payload[:validator] = validator unless validator.nil? - - # DEPRECATION - # This field is ignored in API V1, but left for backwards-compat, - # can remove after API V0 is no longer supported. - payload[:admin] = admin unless admin.nil? - - begin - new_client = chef_rest_v1.put("clients/#{name}", payload) - rescue Net::HTTPServerException => e - # rescue API V0 if 406 and the server supports V0 - supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) - raise e unless supported_versions && supported_versions.include?(0) - new_client = chef_rest_v0.put("clients/#{name}", payload) - end - - new_client end # Create the client via the REST API def create - payload = { - :name => name, - :validator => validator, - # this field is ignored in API V1, but left for backwards-compat, - # can remove after OSC 11 support is finished? - :admin => admin - } - begin - # try API V1 - raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil? - - payload[:public_key] = public_key unless public_key.nil? - payload[:create_key] = create_key unless create_key.nil? - - new_client = chef_rest_v1.post("clients", payload) - - # get the private_key out of the chef_key hash if it exists - if new_client['chef_key'] - if new_client['chef_key']['private_key'] - new_client['private_key'] = new_client['chef_key']['private_key'] - end - new_client['public_key'] = new_client['chef_key']['public_key'] - new_client.delete('chef_key') - end - - rescue Net::HTTPServerException => e - # rescue API V0 if 406 and the server supports V0 - supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) - raise e unless supported_versions && supported_versions.include?(0) - - # under API V0, a key pair will always be created unless public_key is - # passed on initial POST - payload[:public_key] = public_key unless public_key.nil? - - new_client = chef_rest_v0.post("clients", payload) - end - Chef::ApiClient.from_hash(self.to_hash.merge(new_client)) + http_api.post("clients", self) end # As a string @@ -326,5 +219,14 @@ class Chef "client[#{@name}]" end + def inspect + "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " + + "public_key:'#{public_key}' private_key:'#{private_key}'" + end + + def http_api + @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}) + end + end end diff --git a/lib/chef/api_client_v1.rb b/lib/chef/api_client_v1.rb new file mode 100644 index 0000000000..80f0d2517c --- /dev/null +++ b/lib/chef/api_client_v1.rb @@ -0,0 +1,325 @@ +# +# Author:: Adam Jacob (<adam@chef.io>) +# Author:: Nuo Yan (<nuo@chef.io>) +# Copyright:: Copyright (c) 2008, 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/config' +require 'chef/mixin/params_validate' +require 'chef/mixin/from_file' +require 'chef/mash' +require 'chef/json_compat' +require 'chef/search/query' +require 'chef/exceptions' +require 'chef/mixin/api_version_request_handling' +require 'chef/server_api' +require 'chef/api_client' + +# COMPATIBILITY NOTE +# +# This ApiClientV1 code attempts to make API V1 requests and falls back to +# API V0 requests when it fails. New development should occur here instead +# of Chef::ApiClient as this will replace that namespace when Chef 13 is released. +# +# If you need to default to API V0 behavior (i.e. you need GET client to return +# a public key, etc), please use Chef::ApiClient and update your code to support +# API V1 before you pull in Chef 13. +class Chef + class ApiClientV1 + + include Chef::Mixin::FromFile + include Chef::Mixin::ParamsValidate + include Chef::Mixin::ApiVersionRequestHandling + + SUPPORTED_API_VERSIONS = [0,1] + + # Create a new Chef::ApiClientV1 object. + def initialize + @name = '' + @public_key = nil + @private_key = nil + @admin = false + @validator = false + @create_key = nil + end + + def chef_rest_v0 + @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0", :inflate_json_class => false}) + end + + def chef_rest_v1 + @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false}) + end + + def self.http_api + Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false}) + end + + # Gets or sets the client name. + # + # @params [Optional String] The name must be alpha-numeric plus - and _. + # @return [String] The current value of the name. + def name(arg=nil) + set_or_return( + :name, + arg, + :regex => /^[\-[:alnum:]_\.]+$/ + ) + end + + # Gets or sets whether this client is an admin. + # + # @params [Optional True/False] Should be true or false - default is false. + # @return [True/False] The current value + def admin(arg=nil) + set_or_return( + :admin, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + # Gets or sets the public key. + # + # @params [Optional String] The string representation of the public key. + # @return [String] The current value. + def public_key(arg=nil) + set_or_return( + :public_key, + arg, + :kind_of => String + ) + end + + # Gets or sets whether this client is a validator. + # + # @params [Boolean] whether or not the client is a validator. If + # `nil`, retrieves the already-set value. + # @return [Boolean] The current value + def validator(arg=nil) + set_or_return( + :validator, + arg, + :kind_of => [TrueClass, FalseClass] + ) + end + + # Private key. The server will return it as a string. + # Set to true under API V0 to have the server regenerate the default key. + # + # @params [Optional String] The string representation of the private key. + # @return [String] The current value. + def private_key(arg=nil) + set_or_return( + :private_key, + arg, + :kind_of => [String, TrueClass, FalseClass] + ) + end + + # Used to ask server to generate key pair under api V1 + # + # @params [Optional True/False] Should be true or false - default is false. + # @return [True/False] The current value + def create_key(arg=nil) + set_or_return( + :create_key, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + # The hash representation of the object. Includes the name and public_key. + # Private key is included if available. + # + # @return [Hash] + def to_hash + result = { + "name" => @name, + "validator" => @validator, + "admin" => @admin, + "chef_type" => "client" + } + result["private_key"] = @private_key unless @private_key.nil? + result["public_key"] = @public_key unless @public_key.nil? + result["create_key"] = @create_key unless @create_key.nil? + result + end + + # The JSON representation of the object. + # + # @return [String] the JSON string. + def to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) + end + + def self.from_hash(o) + client = Chef::ApiClientV1.new + client.name(o["name"] || o["clientname"]) + client.admin(o["admin"]) + client.validator(o["validator"]) + client.private_key(o["private_key"]) if o.key?("private_key") + client.public_key(o["public_key"]) if o.key?("public_key") + client.create_key(o["create_key"]) if o.key?("create_key") + client + end + + def self.from_json(j) + Chef::ApiClientV1.from_hash(Chef::JSONCompat.from_json(j)) + end + + def self.reregister(name) + api_client = Chef::ApiClientV1.load(name) + api_client.reregister + end + + def self.list(inflate=false) + if inflate + response = Hash.new + Chef::Search::Query.new.search(:client) do |n| + n = self.from_hash(n) if n.instance_of?(Hash) + response[n.name] = n + end + response + else + http_api.get("clients") + end + end + + # Load a client by name via the API + def self.load(name) + response = http_api.get("clients/#{name}") + Chef::ApiClientV1.from_hash(response) + end + + # Remove this client via the REST API + def destroy + chef_rest_v1.delete("clients/#{@name}") + end + + # Save this client via the REST API, returns a hash including the private key + def save + begin + update + rescue Net::HTTPServerException => e + # If that fails, go ahead and try and update it + if e.response.code == "404" + create + else + raise e + end + end + end + + def reregister + # Try API V0 and if it fails due to V0 not being supported, raise the proper error message. + # reregister only supported in API V0 or lesser. + reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true }) + if reregistered_self.respond_to?(:[]) + private_key(reregistered_self["private_key"]) + else + private_key(reregistered_self.private_key) + end + self + rescue Net::HTTPServerException => e + # if there was a 406 related to versioning, give error explaining that + # only API version 0 is supported for reregister command + if e.response.code == "406" && e.response["x-ops-server-api-version"] + version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"]) + min_version = version_header["min_version"] + max_version = version_header["max_version"] + error_msg = reregister_only_v0_supported_error_msg(max_version, min_version) + raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg) + else + raise e + end + end + + # Updates the client via the REST API + def update + # NOTE: API V1 dropped support for updating client keys via update (aka PUT), + # but this code never supported key updating in the first place. Since + # it was never implemented, we will simply ignore that functionality + # as it is being deprecated. + # Delete this comment after V0 support is dropped. + payload = { :name => name } + payload[:validator] = validator unless validator.nil? + + # DEPRECATION + # This field is ignored in API V1, but left for backwards-compat, + # can remove after API V0 is no longer supported. + payload[:admin] = admin unless admin.nil? + + begin + new_client = chef_rest_v1.put("clients/#{name}", payload) + rescue Net::HTTPServerException => e + # rescue API V0 if 406 and the server supports V0 + supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) + raise e unless supported_versions && supported_versions.include?(0) + new_client = chef_rest_v0.put("clients/#{name}", payload) + end + + Chef::ApiClientV1.from_hash(new_client) + end + + # Create the client via the REST API + def create + payload = { + :name => name, + :validator => validator, + # this field is ignored in API V1, but left for backwards-compat, + # can remove after OSC 11 support is finished? + :admin => admin + } + begin + # try API V1 + raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil? + + payload[:public_key] = public_key unless public_key.nil? + payload[:create_key] = create_key unless create_key.nil? + + new_client = chef_rest_v1.post("clients", payload) + + # get the private_key out of the chef_key hash if it exists + if new_client['chef_key'] + if new_client['chef_key']['private_key'] + new_client['private_key'] = new_client['chef_key']['private_key'] + end + new_client['public_key'] = new_client['chef_key']['public_key'] + new_client.delete('chef_key') + end + + rescue Net::HTTPServerException => e + # rescue API V0 if 406 and the server supports V0 + supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) + raise e unless supported_versions && supported_versions.include?(0) + + # under API V0, a key pair will always be created unless public_key is + # passed on initial POST + payload[:public_key] = public_key unless public_key.nil? + + new_client = chef_rest_v0.post("clients", payload) + end + Chef::ApiClientV1.from_hash(self.to_hash.merge(new_client)) + end + + # As a string + def to_s + "client[#{@name}]" + end + + end +end diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb index dd09d65b42..5bb2a1ceb0 100644 --- a/lib/chef/application/solo.rb +++ b/lib/chef/application/solo.rb @@ -214,7 +214,7 @@ class Chef::Application::Solo < Chef::Application FileUtils.mkdir_p(recipes_path) tarball_path = File.join(recipes_path, 'recipes.tgz') fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path) - Chef::Mixin::Command.run_command(:command => "tar zxvf #{tarball_path} -C #{recipes_path}") + Mixlib::ShellOut.new("tar zxvf #{tarball_path} -C #{recipes_path}").run_command end # json_attribs shuld be fetched after recipe_url tarball is unpacked. diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb index de8ed657c2..9bd6095260 100644 --- a/lib/chef/application/windows_service_manager.rb +++ b/lib/chef/application/windows_service_manager.rb @@ -78,7 +78,7 @@ class Chef raise ArgumentError, "Service definition is not provided" if service_options.nil? - required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path] + required_options = [:service_name, :service_display_name, :service_description, :service_file_path] required_options.each do |req_option| if !service_options.has_key?(req_option) @@ -92,6 +92,7 @@ class Chef @service_file_path = service_options[:service_file_path] @service_start_name = service_options[:run_as_user] @password = service_options[:run_as_password] + @delayed_start = service_options[:delayed_start] end def run(params = ARGV) @@ -113,17 +114,21 @@ class Chef cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR) ::Win32::Service.new( - :service_name => @service_name, - :display_name => @service_display_name, - :description => @service_description, - # Prior to 0.8.5, win32-service creates interactive services by default, - # and we don't want that, so we need to override the service type. - :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS, - :start_type => ::Win32::Service::SERVICE_AUTO_START, - :binary_path_name => cmd, - :service_start_name => @service_start_name, - :password => @password, - ) + :service_name => @service_name, + :display_name => @service_display_name, + :description => @service_description, + # Prior to 0.8.5, win32-service creates interactive services by default, + # and we don't want that, so we need to override the service type. + :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS, + :start_type => ::Win32::Service::SERVICE_AUTO_START, + :binary_path_name => cmd, + :service_start_name => @service_start_name, + :password => @password, + ) + ::Win32::Service.configure( + :service_name => @service_name, + :delayed_start => @delayed_start + ) unless @delayed_start.nil? puts "Service '#{@service_name}' has successfully been installed." end when 'status' diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index a0c74f7aec..458ac82467 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -28,6 +28,8 @@ require 'chef/platform/provider_priority_map' require 'chef/platform/resource_priority_map' +require 'chef/platform/provider_handler_map' +require 'chef/platform/resource_handler_map' class Chef class << self @@ -50,7 +52,14 @@ class Chef # attr_reader :run_context + # Register an event handler with user specified block # + # @return[Chef::EventDispatch::Base] handler object + def event_handler(&block) + dsl = Chef::EventDispatch::DSL.new('Chef client DSL') + dsl.instance_eval(&block) + end + # Get the array of providers associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol @@ -160,20 +169,26 @@ class Chef @node = nil @provider_priority_map = nil @resource_priority_map = nil + @provider_handler_map = nil + @resource_handler_map = nil end # @api private def provider_priority_map - @provider_priority_map ||= begin - # these slurp in the resource+provider world, so be exceedingly lazy about requiring them - Chef::Platform::ProviderPriorityMap.instance - end + # these slurp in the resource+provider world, so be exceedingly lazy about requiring them + @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance end # @api private def resource_priority_map - @resource_priority_map ||= begin - Chef::Platform::ResourcePriorityMap.instance - end + @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance + end + # @api private + def provider_handler_map + @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance + end + # @api private + def resource_handler_map + @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance end end diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb index 6666a3deee..40cbb36530 100644 --- a/lib/chef/chef_fs/config.rb +++ b/lib/chef/chef_fs/config.rb @@ -111,7 +111,7 @@ class Chef # def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil) @chef_config = chef_config - @cwd = cwd + @cwd = File.expand_path(cwd) @cookbook_version = options[:cookbook_version] if @chef_config[:repo_mode] == 'everything' && is_hosted? && !ui.nil? @@ -166,34 +166,37 @@ class Chef # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah' # server_path('/home/*/chef_repo/cookbooks/blah') == nil # - # If there are multiple paths (cookbooks, roles, data bags, etc. can all - # have separate paths), and cwd+the path reaches into one of them, we will - # return a path relative to that. Otherwise we will return a path to - # chef_repo. + # If there are multiple different, manually specified paths to object locations + # (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the + # path reaches into one of them, we will return a path relative to the first + # one to match it. Otherwise we expect the path provided to be to the chef + # repo path itself. Paths that are not available on the server are not supported. # # Globs are allowed as well, but globs outside server paths are NOT # (presently) supported. See above examples. TODO support that. # # If the path does not reach into ANY specified directory, nil is returned. def server_path(file_path) - pwd = File.expand_path(Dir.pwd) - absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd)) + target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd) # Check all object paths (cookbooks_dir, data_bags_dir, etc.) + # These are either manually specified by the user or autogenerated relative + # to chef_repo_path. object_paths.each_pair do |name, paths| paths.each do |path| - realest_path = Chef::ChefFS::PathUtils.realest_path(path) - if PathUtils.descendant_of?(absolute_pwd, realest_path) - relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path) - return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}" + object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd) + if relative_path = PathUtils.descendant_path(target_path, object_abs_path) + return Chef::ChefFS::PathUtils.join("/#{name}", relative_path) end end end # Check chef_repo_path Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path| - realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path) - if absolute_pwd == realest_chef_repo_path + # We're using realest_path here but we really don't need to - we can just expand the + # path and use realpath because a repo_path if provided *must* exist. + realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd) + if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path) return '/' end end @@ -201,15 +204,10 @@ class Chef nil end - # The current directory, relative to server root + # The current directory, relative to server root. This is a case-sensitive server path. + # It only exists if the current directory is a child of one of the recognized object_paths below. def base_path - @base_path ||= begin - if @chef_config[:chef_repo_path] - server_path(File.expand_path(@cwd)) - else - nil - end - end + @base_path ||= server_path(@cwd) end # Print the given server path, relative to the current directory @@ -217,10 +215,10 @@ class Chef server_path = entry.path if base_path && server_path[0,base_path.length] == base_path if server_path == base_path - return "." - elsif server_path[base_path.length,1] == "/" + return '.' + elsif server_path[base_path.length,1] == '/' return server_path[base_path.length + 1, server_path.length - base_path.length - 1] - elsif base_path == "/" && server_path[0,1] == "/" + elsif base_path == '/' && server_path[0,1] == '/' return server_path[1, server_path.length - 1] end end diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb index 134d22cbd5..b2351dac68 100644 --- a/lib/chef/chef_fs/file_pattern.rb +++ b/lib/chef/chef_fs/file_pattern.rb @@ -72,7 +72,7 @@ class Chef def could_match_children?(path) return false if path == '' # Empty string is not a path - argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/) + argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path) return false if is_absolute != argument_is_absolute path = path[1,path.length-1] if argument_is_absolute @@ -111,7 +111,7 @@ class Chef # # This method assumes +could_match_children?(path)+ is +true+. def exact_child_name_under(path) - path = path[1,path.length-1] if !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/) + path = path[1,path.length-1] if Chef::ChefFS::PathUtils::is_absolute?(path) dirs_in_path = Chef::ChefFS::PathUtils::split(path).length return nil if exact_parts.length <= dirs_in_path return exact_parts[dirs_in_path] @@ -149,7 +149,7 @@ class Chef # abc/*/def.match?('abc/foo/def') == true # abc/*/def.match?('abc/foo') == false def match?(path) - argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/) + argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path) return false if is_absolute != argument_is_absolute path = path[1,path.length-1] if argument_is_absolute !!regexp.match(path) @@ -160,17 +160,6 @@ class Chef pattern end - # Given a relative file pattern and a directory, makes a new file pattern - # starting with the directory. - # - # FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok') - # - # BUG: this does not support patterns starting with <tt>..</tt> - def self.relative_to(dir, pattern) - return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/ - FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern)) - end - private def regexp @@ -195,7 +184,7 @@ class Chef def calculate if !@regexp - @is_absolute = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/) + @is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern) full_regexp_parts = [] normalized_parts = [] diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb index 370308ee0a..824325f31b 100644 --- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb @@ -90,11 +90,11 @@ class Chef end def rest - Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true) + Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0") end def get_json(path) - Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path) + Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path) end def chef_rest diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb index 03652dc376..555f9aef0a 100644 --- a/lib/chef/chef_fs/file_system/cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/chef_fs/command_line' require 'chef/chef_fs/file_system/rest_list_dir' require 'chef/chef_fs/file_system/cookbook_subdir' require 'chef/chef_fs/file_system/cookbook_file' diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb index 86872dab71..9101e455f8 100644 --- a/lib/chef/chef_fs/knife.rb +++ b/lib/chef/chef_fs/knife.rb @@ -17,6 +17,7 @@ # require 'chef/knife' +require 'pathname' class Chef module ChefFS @@ -63,7 +64,7 @@ class Chef # --chef-repo-path forcibly overrides all other paths if config[:chef_repo_path] Chef::Config[:chef_repo_path] = config[:chef_repo_path] - %w(acl client cookbook container data_bag environment group node role user).each do |variable_name| + Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name| Chef::Config.delete("#{variable_name}_path".to_sym) end end @@ -98,14 +99,41 @@ class Chef end def pattern_arg_from(arg) - # TODO support absolute file paths and not just patterns? Too much? - # Could be super useful in a world with multiple repo paths - if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg) - # Check if chef repo path is specified to give a better error message - ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path") + inferred_path = nil + if Chef::ChefFS::PathUtils.is_absolute?(arg) + # We should be able to use this as-is - but the user might have incorrectly provided + # us with a path that is based off of the OS root path instead of the Chef-FS root. + # Do a quick and dirty sanity check. + if possible_server_path = @chef_fs_config.server_path(arg) + ui.warn("The absolute path provided is suspicious: #{arg}") + ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.") + ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'") + end + # Use the original path because we can't be sure. + inferred_path = arg + elsif arg[0,1] == '~' + # Let's be nice and fix it if possible - but warn the user. + ui.warn("A path relative to a user home directory has been provided: #{arg}") + ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.") + inferred_path = @chef_fs_config.server_path(arg) + ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.") + elsif Pathname.new(arg).absolute? + # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be + # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user. + ui.warn("An absolute file system path that isn't a server path was provided: #{arg}") + ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.") + inferred_path = @chef_fs_config.server_path(arg) + ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.") + elsif @chef_fs_config.base_path.nil? + # These are all relative paths. We can't resolve and root paths unless we are in the + # chef repo. + ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.") + ui.error("Current working directory is '#{@chef_fs_config.cwd}'.") exit(1) + else + inferred_path = Chef::ChefFS::PathUtils::join(@chef_fs_config.base_path, arg) end - Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg) + Chef::ChefFS::FilePattern.new(inferred_path) end def format_path(entry) diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb index 9ef75ce2e5..595f966378 100644 --- a/lib/chef/chef_fs/path_utils.rb +++ b/lib/chef/chef_fs/path_utils.rb @@ -23,31 +23,31 @@ class Chef module ChefFS class PathUtils - # If you are in 'source', this is what you would have to type to reach 'dest' - # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e' - # relative_to('/a/b', '/a/b') == '.' - def self.relative_to(dest, source) - # Skip past the common parts - source_parts = Chef::ChefFS::PathUtils.split(source) - dest_parts = Chef::ChefFS::PathUtils.split(dest) - i = 0 - until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i] - i+=1 - end - # dot-dot up from 'source' to the common ancestor, then - # descend to 'dest' from the common ancestor - result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i])) - result == '' ? '.' : result - end + # A Chef-FS path is a path in a chef-repository that can be used to address + # both files on a local file-system as well as objects on a chef server. + # These paths are stricter than file-system paths allowed on various OSes. + # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well). + # "/" is used as the path element separator (on windows, "\" is acceptable as well). + # No directory/path element may contain a literal "\" character. Any such characters + # encountered are either dealt with as separators (on windows) or as escape + # characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but + # may never use these to back-out of the root of a Chef-FS path. Any such extraneous + # ".."s are ignored. + # Chef-FS paths are case sensitive (since the paths on the server are). + # On OSes with case insensitive paths, you may be unable to locally deal with two + # objects whose server paths only differ by case. OTOH, the case of path segments + # that are outside the Chef-FS root (such as when looking at a file-system absolute + # path to discover the Chef-FS root path) are handled in accordance to the rules + # of the local file-system and OS. def self.join(*parts) return "" if parts.length == 0 # Determine if it started with a slash absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/ # Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away) - parts = parts.map { |part| part.gsub(/^\/|\/$/, "") } + parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') } # Don't join empty bits - result = parts.select { |part| part != "" }.join("/") + result = parts.select { |part| part != '' }.join('/') # Put the / back on absolute ? "/#{result}" : result end @@ -60,36 +60,67 @@ class Chef Chef::ChefFS::windows? ? '[\/\\\\]' : '/' end + # Given a server path, determines if it is absolute. + def self.is_absolute?(path) + !!(path =~ /^#{regexp_path_separator}/) + end # Given a path which may only be partly real (i.e. /x/y/z when only /x exists, # or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest - # part that actually exists. + # part that actually exists. The paths operated on here are not Chef-FS paths. + # These are OS paths that may contain symlinks but may not also fully exist. # # If /x is a symlink to /blarghle, and has no subdirectories, then: # PathUtils.realest_path('/x/y/z') == '/blarghle/y/z' # PathUtils.realest_path('/x/*/z') == '/blarghle/*/z' # PathUtils.realest_path('/*/y/z') == '/*/y/z' - def self.realest_path(path) - path = Pathname.new(path) - begin - path.realpath.to_s - rescue Errno::ENOENT - dirname = path.dirname - if dirname - PathUtils.join(realest_path(dirname), path.basename.to_s) - else - path.to_s + # + # TODO: Move this to wherever util/path_helper is these days. + def self.realest_path(path, cwd = Dir.pwd) + path = File.expand_path(path, cwd) + parent_path = File.dirname(path) + suffix = [] + + # File.dirname happens to return the path as its own dirname if you're + # at the root (such as at \\foo\bar, C:\ or /) + until parent_path == path do + # This can occur if a path such as "C:" is given. Ruby gives the parent as "C:." + # for reasons only it knows. + raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length + begin + path = File.realpath(path) + break + rescue Errno::ENOENT + suffix << File.basename(path) + path = parent_path + parent_path = File.dirname(path) end end + File.join(path, *suffix.reverse) end - def self.descendant_of?(path, ancestor) - path[0,ancestor.length] == ancestor && - (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/) + # Compares two path fragments according to the case-sentitivity of the host platform. + def self.os_path_eq?(left, right) + Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right end - def self.is_absolute?(path) - path =~ /^#{regexp_path_separator}/ + # Given two general OS-dependent file paths, determines the relative path of the + # child with respect to the ancestor. Both child and ancestor must exist and be + # fully resolved - this is strictly a lexical comparison. No trailing slashes + # and other shenanigans are allowed. + # + # TODO: Move this to util/path_helper. + def self.descendant_path(path, ancestor) + candidate_fragment = path[0, ancestor.length] + return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor) + if ancestor.length == path.length + '' + elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/ + path[ancestor.length+1..-1] + else + nil + end end + end end end diff --git a/lib/chef/constants.rb b/lib/chef/constants.rb new file mode 100644 index 0000000000..d39ce4c68d --- /dev/null +++ b/lib/chef/constants.rb @@ -0,0 +1,27 @@ +# +# Author:: John Keiser <jkeiser@chef.io> +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Chef + NOT_PASSED = Object.new + def NOT_PASSED.to_s + "NOT_PASSED" + end + def NOT_PASSED.inspect + to_s + end + NOT_PASSED.freeze +end diff --git a/lib/chef/delayed_evaluator.rb b/lib/chef/delayed_evaluator.rb new file mode 100644 index 0000000000..9f18a53445 --- /dev/null +++ b/lib/chef/delayed_evaluator.rb @@ -0,0 +1,21 @@ +# +# Author:: John Keiser <jkeiser@chef.io> +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Chef + class DelayedEvaluator < Proc + end +end diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb index 1ce12ed0a0..f15beaeab0 100644 --- a/lib/chef/dsl/resources.rb +++ b/lib/chef/dsl/resources.rb @@ -10,14 +10,16 @@ class Chef def self.add_resource_dsl(dsl_name) begin module_eval(<<-EOM, __FILE__, __LINE__+1) - def #{dsl_name}(name=nil, created_at=nil, &block) - declare_resource(#{dsl_name.inspect}, name, created_at || caller[0], &block) + def #{dsl_name}(*args, &block) + Chef::Log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1 + declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block) end EOM rescue SyntaxError # Handle the case where dsl_name has spaces, etc. - define_method(dsl_name.to_sym) do |name=nil, created_at=nil, &block| - declare_resource(dsl_name, name, created_at || caller[0], &block) + define_method(dsl_name.to_sym) do |*args, &block| + Chef::Log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1 + declare_resource(dsl_name, args[0], caller[0], &block) end end end diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb new file mode 100644 index 0000000000..c6f21c9b45 --- /dev/null +++ b/lib/chef/event_dispatch/dsl.rb @@ -0,0 +1,64 @@ +# +# Author:: Ranjib Dey (<ranjib@linux.com>) +# Copyright:: Copyright (c) 2015 Ranjib Dey +# 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/event_dispatch/base' +require 'chef/exceptions' +require 'chef/config' + +class Chef + module EventDispatch + class DSL + attr_reader :handler + + def initialize(name) + klass = Class.new(Chef::EventDispatch::Base) do + attr_reader :name + end + @handler = klass.new + @handler.instance_variable_set(:@name, name) + + # Use event.register API to add anonymous handler if Chef.run_context + # and associated event dispatcher is set, else fallback to + # Chef::Config[:hanlder] + if Chef.run_context && Chef.run_context.events + Chef::Log.debug("Registering handler '#{name}' using events api") + Chef.run_context.events.register(handler) + else + Chef::Log.debug("Registering handler '#{name}' using global config") + Chef::Config[:event_handlers] << handler + end + end + + # Adds a new event handler derived from base handler + # with user defined block against a chef event + # + # @return [Chef::EventDispatch::Base] a base handler object + def on(event_type, &block) + validate!(event_type) + handler.define_singleton_method(event_type) do |*args| + instance_exec(*args, &block) + end + end + + private + def validate!(event_type) + all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods) + raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type) + end + end + end +end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index dd0bac3cf9..e2e36e8162 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -17,12 +17,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'chef-config/exceptions' + class Chef # == Chef::Exceptions # Chef's custom exceptions are all contained within the Chef::Exceptions # namespace. class Exceptions + ConfigurationError = ChefConfig::ConfigurationError + # Backcompat with Chef::ShellOut code: require 'mixlib/shellout/exceptions' @@ -68,7 +72,6 @@ class Chef class DuplicateRole < RuntimeError; end class ValidationFailed < ArgumentError; end class InvalidPrivateKey < ArgumentError; end - class ConfigurationError < ArgumentError; end class MissingKeyAttribute < ArgumentError; end class KeyCommandInputError < ArgumentError; end class InvalidKeyArgument < ArgumentError; end @@ -97,7 +100,11 @@ class Chef class ConflictingMembersInGroup < ArgumentError; end class InvalidResourceReference < RuntimeError; end class ResourceNotFound < RuntimeError; end + class ProviderNotFound < RuntimeError; end + NoProviderAvailable = ProviderNotFound class VerificationNotFound < RuntimeError; end + class InvalidEventType < ArgumentError; end + class MultipleIdentityError < RuntimeError; end # Can't find a Resource of this type that is valid on this platform. class NoSuchResourceType < NameError @@ -218,8 +225,6 @@ class Chef class ChildConvergeError < RuntimeError; end - class NoProviderAvailable < RuntimeError; end - class DeprecatedFeatureError < RuntimeError; def initalize(message) super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)") diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb index d4b386a15a..8cff3bc032 100644 --- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb +++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb @@ -68,7 +68,10 @@ class Chef run_action = action || @resource.action begin - @resource.run_action(run_action) + # Coerce to an array to be safe. This could happen with a legacy + # resource or something overriding the default_action code in a + # subclass. + Array(run_action).each {|action_to_run| @resource.run_action(action_to_run) } resource_updated = @resource.updated rescue Mixlib::ShellOut::ShellCommandFailed resource_updated = nil diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb index 7582f4458f..1baf5724ae 100644 --- a/lib/chef/http/http_request.rb +++ b/lib/chef/http/http_request.rb @@ -40,7 +40,7 @@ class Chef engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby" - UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)" + UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +https://chef.io)" DEFAULT_UA = "Chef Client" << UA_COMMON USER_AGENT = "User-Agent".freeze diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb index 335b1f181c..575aec0f50 100644 --- a/lib/chef/knife/bootstrap/templates/chef-full.erb +++ b/lib/chef/knife/bootstrap/templates/chef-full.erb @@ -12,7 +12,7 @@ tmp_dir="$tmp/install.sh.$$" (umask 077 && mkdir $tmp_dir) || exit 1 exists() { - if command -v $1 &>/dev/null + if command -v $1 >/dev/null 2>&1 then return 0 else @@ -166,12 +166,12 @@ do_download() { <%= knife_config[:bootstrap_install_command] %> <% else %> install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>" - if ! exists /usr/bin/chef-client; then + if test -f /usr/bin/chef-client; then + echo "-----> Existing Chef installation detected" + else echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)" do_download ${install_sh} $tmp_dir/install.sh sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %> - else - echo "-----> Existing Chef installation detected" fi <% end %> @@ -226,6 +226,6 @@ cat > /etc/chef/first-boot.json <<EOP <%= Chef::JSONCompat.to_json(first_boot) %> EOP -echo "Starting first Chef Client run..." +echo "Starting the first Chef Client run..." <%= start_chef %>' diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb index f2be772759..b439e6f995 100644 --- a/lib/chef/knife/client_bulk_delete.rb +++ b/lib/chef/knife/client_bulk_delete.rb @@ -23,7 +23,7 @@ class Chef class ClientBulkDelete < Knife deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -39,7 +39,7 @@ class Chef ui.fatal("You must supply a regular expression to match the results against") exit 42 end - all_clients = Chef::ApiClient.list(true) + all_clients = Chef::ApiClientV1.list(true) matcher = /#{name_args[0]}/ clients_to_delete = {} diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb index 570c1ee950..fa9a1a7e32 100644 --- a/lib/chef/knife/client_create.rb +++ b/lib/chef/knife/client_create.rb @@ -23,7 +23,7 @@ class Chef class ClientCreate < Knife deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -57,12 +57,12 @@ class Chef banner "knife client create CLIENTNAME (options)" def client - @client_field ||= Chef::ApiClient.new + @client_field ||= Chef::ApiClientV1.new end def create_client(client) # should not be using save :( bad behavior - client.save + Chef::ApiClientV1.from_hash(client).save end def run @@ -93,7 +93,7 @@ class Chef output = edit_data(client) final_client = create_client(output) - ui.info("Created #{output}") + ui.info("Created #{final_client}") # output private_key if one if final_client.private_key diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb index d7d302ee1d..a49c0867a8 100644 --- a/lib/chef/knife/client_delete.rb +++ b/lib/chef/knife/client_delete.rb @@ -23,7 +23,7 @@ class Chef class ClientDelete < Knife deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -43,8 +43,8 @@ class Chef exit 1 end - delete_object(Chef::ApiClient, @client_name, 'client') { - object = Chef::ApiClient.load(@client_name) + delete_object(Chef::ApiClientV1, @client_name, 'client') { + object = Chef::ApiClientV1.load(@client_name) if object.validator unless config[:delete_validators] ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}") diff --git a/lib/chef/knife/client_edit.rb b/lib/chef/knife/client_edit.rb index c81bce902a..5dcd8f212b 100644 --- a/lib/chef/knife/client_edit.rb +++ b/lib/chef/knife/client_edit.rb @@ -23,7 +23,7 @@ class Chef class ClientEdit < Knife deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -38,7 +38,15 @@ class Chef exit 1 end - edit_object(Chef::ApiClient, @client_name) + original_data = Chef::ApiClientV1.load(@client_name).to_hash + edited_client = edit_data(original_data) + if original_data != edited_client + client = Chef::ApiClientV1.from_hash(edited_client) + client.save + ui.msg("Saved #{client}.") + else + ui.msg("Client unchanged, not saving.") + end end end end diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb index da0bf12dc3..d8a3698b6a 100644 --- a/lib/chef/knife/client_list.rb +++ b/lib/chef/knife/client_list.rb @@ -23,7 +23,7 @@ class Chef class ClientList < Knife deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -35,7 +35,7 @@ class Chef :description => "Show corresponding URIs" def run - output(format_list_for_display(Chef::ApiClient.list)) + output(format_list_for_display(Chef::ApiClientV1.list)) end end end diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb index 666fd09fd2..b94761e718 100644 --- a/lib/chef/knife/client_reregister.rb +++ b/lib/chef/knife/client_reregister.rb @@ -23,7 +23,7 @@ class Chef class ClientReregister < Knife deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -43,7 +43,7 @@ class Chef exit 1 end - client = Chef::ApiClient.reregister(@client_name) + client = Chef::ApiClientV1.reregister(@client_name) Chef::Log.debug("Updated client data: #{client.inspect}") key = client.private_key if config[:file] diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb index 822848fdc2..bdac3f9758 100644 --- a/lib/chef/knife/client_show.rb +++ b/lib/chef/knife/client_show.rb @@ -25,7 +25,7 @@ class Chef include Knife::Core::MultiAttributeReturnOption deps do - require 'chef/api_client' + require 'chef/api_client_v1' require 'chef/json_compat' end @@ -40,7 +40,7 @@ class Chef exit 1 end - client = Chef::ApiClient.load(@client_name) + client = Chef::ApiClientV1.load(@client_name) output(format_for_display(client)) end diff --git a/lib/chef/knife/osc_user_create.rb b/lib/chef/knife/osc_user_create.rb index c368296040..6c3415473f 100644 --- a/lib/chef/knife/osc_user_create.rb +++ b/lib/chef/knife/osc_user_create.rb @@ -27,7 +27,7 @@ class Chef class OscUserCreate < Knife deps do - require 'chef/osc_user' + require 'chef/user' require 'chef/json_compat' end @@ -69,7 +69,7 @@ class Chef exit 1 end - user = Chef::OscUser.new + user = Chef::User.new user.name(@user_name) user.admin(config[:admin]) user.password config[:user_password] @@ -79,7 +79,7 @@ class Chef end output = edit_data(user) - user = Chef::OscUser.from_hash(output).create + user = Chef::User.from_hash(output).create ui.info("Created #{user}") if user.private_key diff --git a/lib/chef/knife/osc_user_delete.rb b/lib/chef/knife/osc_user_delete.rb index d6fbd4a6a9..5cd4f10413 100644 --- a/lib/chef/knife/osc_user_delete.rb +++ b/lib/chef/knife/osc_user_delete.rb @@ -28,7 +28,7 @@ class Chef class OscUserDelete < Knife deps do - require 'chef/osc_user' + require 'chef/user' require 'chef/json_compat' end @@ -43,7 +43,7 @@ class Chef exit 1 end - delete_object(Chef::OscUser, @user_name) + delete_object(Chef::User, @user_name) end end diff --git a/lib/chef/knife/osc_user_edit.rb b/lib/chef/knife/osc_user_edit.rb index 4c38674d08..526475db05 100644 --- a/lib/chef/knife/osc_user_edit.rb +++ b/lib/chef/knife/osc_user_edit.rb @@ -28,7 +28,7 @@ class Chef class OscUserEdit < Knife deps do - require 'chef/osc_user' + require 'chef/user' require 'chef/json_compat' end @@ -43,10 +43,10 @@ class Chef exit 1 end - original_user = Chef::OscUser.load(@user_name).to_hash + original_user = Chef::User.load(@user_name).to_hash edited_user = edit_data(original_user) if original_user != edited_user - user = Chef::OscUser.from_hash(edited_user) + user = Chef::User.from_hash(edited_user) user.update ui.msg("Saved #{user}.") else diff --git a/lib/chef/knife/osc_user_list.rb b/lib/chef/knife/osc_user_list.rb index 92f049cd19..84fca31899 100644 --- a/lib/chef/knife/osc_user_list.rb +++ b/lib/chef/knife/osc_user_list.rb @@ -28,7 +28,7 @@ class Chef class OscUserList < Knife deps do - require 'chef/osc_user' + require 'chef/user' require 'chef/json_compat' end @@ -40,7 +40,7 @@ class Chef :description => "Show corresponding URIs" def run - output(format_list_for_display(Chef::OscUser.list)) + output(format_list_for_display(Chef::User.list)) end end end diff --git a/lib/chef/knife/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb index a71e0aa677..163b286fe0 100644 --- a/lib/chef/knife/osc_user_reregister.rb +++ b/lib/chef/knife/osc_user_reregister.rb @@ -28,7 +28,7 @@ class Chef class OscUserReregister < Knife deps do - require 'chef/osc_user' + require 'chef/user' require 'chef/json_compat' end @@ -48,7 +48,7 @@ class Chef exit 1 end - user = Chef::OscUser.load(@user_name).reregister + user = Chef::User.load(@user_name).reregister Chef::Log.debug("Updated user data: #{user.inspect}") key = user.private_key if config[:file] diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/osc_user_show.rb index 6a41ddae88..cb3a77585a 100644 --- a/lib/chef/knife/osc_user_show.rb +++ b/lib/chef/knife/osc_user_show.rb @@ -30,7 +30,7 @@ class Chef include Knife::Core::MultiAttributeReturnOption deps do - require 'chef/osc_user' + require 'chef/user' require 'chef/json_compat' end @@ -45,7 +45,7 @@ class Chef exit 1 end - user = Chef::OscUser.load(@user_name) + user = Chef::User.load(@user_name) output(format_for_display(user)) end diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb index c5fe4fc1aa..d71eacfc7e 100644 --- a/lib/chef/knife/ssl_check.rb +++ b/lib/chef/knife/ssl_check.rb @@ -73,11 +73,12 @@ class Chef exit 1 end - def verify_peer_socket @verify_peer_socket ||= begin tcp_connection = TCPSocket.new(host, port) - OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context) + ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context) + ssl_client.hostname = host + ssl_client end end diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb index e73f6be8b6..995573cd03 100644 --- a/lib/chef/knife/user_create.rb +++ b/lib/chef/knife/user_create.rb @@ -27,7 +27,7 @@ class Chef attr_accessor :user_field deps do - require 'chef/user' + require 'chef/user_v1' require 'chef/json_compat' end @@ -61,11 +61,11 @@ class Chef banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)" def user - @user_field ||= Chef::User.new + @user_field ||= Chef::UserV1.new end def create_user_from_hash(hash) - Chef::User.from_hash(hash).create + Chef::UserV1.from_hash(hash).create end def osc_11_warning diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb index 803be6b90c..828cd51588 100644 --- a/lib/chef/knife/user_delete.rb +++ b/lib/chef/knife/user_delete.rb @@ -23,7 +23,7 @@ class Chef class UserDelete < Knife deps do - require 'chef/user' + require 'chef/user_v1' require 'chef/json_compat' end @@ -55,7 +55,7 @@ EOF if Kernel.block_given? object = block.call else - object = Chef::User.load(user_name) + object = Chef::UserV1.load(user_name) object.destroy end @@ -77,10 +77,10 @@ EOF # Below is modification of Chef::Knife.delete_object to detect OSC 11 server. # When OSC 11 is deprecated, simply delete all this and go back to: # - # delete_object(Chef::User, @user_name) + # delete_object(Chef::UserV1, @user_name) # # Also delete our override of delete_object above - object = Chef::User.load(@user_name) + object = Chef::UserV1.load(@user_name) # OSC 11 case if object.username.nil? diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb index dd2fc02743..c3a4326ee8 100644 --- a/lib/chef/knife/user_edit.rb +++ b/lib/chef/knife/user_edit.rb @@ -23,7 +23,7 @@ class Chef class UserEdit < Knife deps do - require 'chef/user' + require 'chef/user_v1' require 'chef/json_compat' end @@ -56,8 +56,7 @@ EOF exit 1 end - original_user = Chef::User.load(@user_name).to_hash - + original_user = Chef::UserV1.load(@user_name).to_hash # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. # @@ -69,11 +68,11 @@ EOF else # EC / CS 12 user create edited_user = edit_data(original_user) if original_user != edited_user - user = Chef::User.from_hash(edited_user) + user = Chef::UserV1.from_hash(edited_user) user.update ui.msg("Saved #{user}.") else - ui.msg("User unchaged, not saving.") + ui.msg("User unchanged, not saving.") end end diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb index 7ae43dadc9..6a130392b9 100644 --- a/lib/chef/knife/user_list.rb +++ b/lib/chef/knife/user_list.rb @@ -25,7 +25,7 @@ class Chef class UserList < Knife deps do - require 'chef/user' + require 'chef/user_v1' require 'chef/json_compat' end @@ -37,7 +37,7 @@ class Chef :description => "Show corresponding URIs" def run - output(format_list_for_display(Chef::User.list)) + output(format_list_for_display(Chef::UserV1.list)) end end diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb index eab2245025..09fd1cd2d6 100644 --- a/lib/chef/knife/user_reregister.rb +++ b/lib/chef/knife/user_reregister.rb @@ -23,7 +23,7 @@ class Chef class UserReregister < Knife deps do - require 'chef/user' + require 'chef/user_v1' require 'chef/json_compat' end @@ -61,7 +61,7 @@ EOF exit 1 end - user = Chef::User.load(@user_name) + user = Chef::UserV1.load(@user_name) # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb index f5e81e9972..3a2443471a 100644 --- a/lib/chef/knife/user_show.rb +++ b/lib/chef/knife/user_show.rb @@ -25,7 +25,7 @@ class Chef include Knife::Core::MultiAttributeReturnOption deps do - require 'chef/user' + require 'chef/user_v1' require 'chef/json_compat' end @@ -58,7 +58,7 @@ EOF exit 1 end - user = Chef::User.load(@user_name) + user = Chef::UserV1.load(@user_name) # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index f7d52a19cf..322caea474 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -15,11 +15,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -class Chef - NOT_PASSED = Object.new +require 'chef/constants' +require 'chef/property' +require 'chef/delayed_evaluator' - class DelayedEvaluator < Proc - end +class Chef module Mixin module ParamsValidate @@ -34,20 +34,55 @@ class Chef # Would raise an exception if the value of :one above is not a kind_of? string. Valid # map options are: # - # :default:: Sets the default value for this parameter. - # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid. - # The key will be inserted into the error message if the Proc does not return true: - # "Option #{key}'s value #{value} #{message}!" - # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure - # that the value is one of those types. - # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of - # method names. - # :required:: Raise an exception if this parameter is missing. Valid values are true or false, - # by default, options are not required. - # :regex:: Match the value of the parameter against a regular expression. - # :equal_to:: Match the value of the parameter with ==. An array means it can be equal to any - # of the values. + # @param opts [Hash<Symbol,Object>] Validation opts. + # @option opts [Object,Array] :is An object, or list of + # objects, that must match the value using Ruby's `===` operator + # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.) + # @option opts [Object,Array] :equal_to An object, or list + # of objects, that must be equal to the value using Ruby's `==` + # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.) + # @option opts [Regexp,Array<Regexp>] :regex An object, or + # list of objects, that must match the value with `regex.match(value)`. + # (See #_pv_regex) + # @option opts [Class,Array<Class>] :kind_of A class, or + # list of classes, that the value must be an instance of. (See + # #_pv_kind_of.) + # @option opts [Hash<String,Proc>] :callbacks A hash of + # messages -> procs, all of which match the value. The proc must + # return a truthy or falsey value (true means it matches). (See + # #_pv_callbacks.) + # @option opts [Symbol,Array<Symbol>] :respond_to A method + # name, or list of method names, the value must respond to. (See + # #_pv_respond_to.) + # @option opts [Symbol,Array<Symbol>] :cannot_be A property, + # or a list of properties, that the value cannot have (such as `:nil` or + # `:empty`). The method with a questionmark at the end is called on the + # value (e.g. `value.empty?`). If the value does not have this method, + # it is considered valid (i.e. if you don't respond to `empty?` we + # assume you are not empty). (See #_pv_cannot_be.) + # @option opts [Proc] :coerce A proc which will be called to + # transform the user input to canonical form. The value is passed in, + # and the transformed value returned as output. Lazy values will *not* + # be passed to this method until after they are evaluated. Called in the + # context of the resource (meaning you can access other properties). + # (See #_pv_coerce.) (See #_pv_coerce.) + # @option opts [Boolean] :required `true` if this property + # must be present and not `nil`; `false` otherwise. This is checked + # after the resource is fully initialized. (See #_pv_required.) + # @option opts [Boolean] :name_property `true` if this + # property defaults to the same value as `name`. Equivalent to + # `default: lazy { name }`, except that #property_is_set? will + # return `true` if the property is set *or* if `name` is set. (See + # #_pv_name_property.) + # @option opts [Boolean] :name_attribute Same as `name_property`. + # @option opts [Object] :default The value this property + # will return if the user does not set one. If this is `lazy`, it will + # be run in the context of the instance (and able to access other + # properties). (See #_pv_default.) + # def validate(opts, map) + map = map.validation_options if map.is_a?(Property) + #-- # validate works by taking the keys in the validation map, assuming it's a hash, and # looking for _pv_:symbol as methods. Assuming it find them, it calls the right @@ -84,91 +119,8 @@ class Chef end def set_or_return(symbol, value, validation) - symbol = symbol.to_sym - iv_symbol = :"@#{symbol}" - - # Steal default, coerce, name_property and required from validation - # so that we can handle the order in which they are applied - validation = validation.dup - if validation.has_key?(:default) - default = validation.delete(:default) - elsif validation.has_key?('default') - default = validation.delete('default') - else - default = NOT_PASSED - end - coerce = validation.delete(:coerce) - coerce ||= validation.delete('coerce') - name_property = validation.delete(:name_property) - name_property ||= validation.delete('name_property') - name_property ||= validation.delete(:name_attribute) - name_property ||= validation.delete('name_attribute') - required = validation.delete(:required) - required ||= validation.delete('required') - - opts = {} - # If the user passed NOT_PASSED, or passed nil, then this is a get. - if value == NOT_PASSED || (value.nil? && !explicitly_allows_nil?(symbol, validation)) - - # Get the value if there is one - if self.instance_variable_defined?(iv_symbol) - opts[symbol] = self.instance_variable_get(iv_symbol) - - # Handle lazy values - if opts[symbol].is_a?(DelayedEvaluator) - if opts[symbol].arity >= 1 - opts[symbol] = opts[symbol].call(self) - else - opts[symbol] = opts[symbol].call - end - - # Coerce and validate the default value - _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required - _pv_coerce(opts, symbol, coerce) if coerce - validate(opts, { symbol => validation }) - end - - # Get the default value - else - _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required - _pv_default(opts, symbol, default) unless default == NOT_PASSED - _pv_name_property(opts, symbol, name_property) - - if opts.has_key?(symbol) - # Handle lazy defaults. - if opts[symbol].is_a?(DelayedEvaluator) - if opts[symbol].arity >= 1 - opts[symbol] = opts[symbol].call(self) - else - opts[symbol] = instance_eval(&opts[symbol]) - end - end - - # Coerce and validate the default value - _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required - _pv_coerce(opts, symbol, coerce) if coerce - # We presently do not validate defaults, for backwards compatibility. -# validate(opts, { symbol => validation }) - - # Defaults are presently "stickily" set on the instance - self.instance_variable_set(iv_symbol, opts[symbol]) - end - end - - # Set the value - else - opts[symbol] = value - unless opts[symbol].is_a?(DelayedEvaluator) - # Coerce and validate the value - _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required - _pv_coerce(opts, symbol, coerce) if coerce - validate(opts, { symbol => validation }) - end - - self.instance_variable_set(iv_symbol, opts[symbol]) - end - - opts[symbol] + property = SetOrReturnProperty.new(name: symbol, **validation) + property.call(self, value) end private @@ -193,8 +145,9 @@ class Chef if is_required return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?) return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?) - raise Exceptions::ValidationFailed, "Required argument #{key} is missing!" + raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!" end + true end # @@ -380,7 +333,7 @@ class Chef def _pv_name_property(opts, key, is_name_property=true) if is_name_property if opts[key].nil? - opts[key] = self.instance_variable_get("@name") + opts[key] = self.instance_variable_get(:"@name") end end end @@ -425,9 +378,9 @@ class Chef # x 1 #=> invalid # ``` # - # @example PropertyType + # @example Property # ```ruby - # type = PropertyType.new(is: String) + # type = Property.new(is: String) # property :x, type # x 'foo' #=> valid # x 1 #=> invalid @@ -448,8 +401,12 @@ class Chef value = _pv_opts_lookup(opts, key) to_be = [ to_be ].flatten(1) to_be.each do |tb| - if tb.is_a?(Proc) + case tb + when Proc return true if instance_exec(value, &tb) + when Property + validate(opts, { key => tb.validation_options }) + return true else return true if tb === value end @@ -484,6 +441,29 @@ class Chef opts[key.to_sym] = instance_exec(opts[key], &coercer) end end + + # Used by #set_or_return to avoid emitting a deprecation warning for + # "value nil" and to keep default stickiness working exactly the same + # @api private + class SetOrReturnProperty < Chef::Property + def get(resource) + value = super + # All values are sticky, frozen or not + if !is_set?(resource) + set_value(resource, value) + end + value + end + + def call(resource, value=NOT_PASSED) + # setting to nil does a get + if value.nil? && !explicitly_accepts_nil?(resource) + get(resource) + else + super + end + end + end end end end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index d5eed7c215..d905c8779e 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -20,13 +20,6 @@ class Chef class NodeMap # - # Create a new NodeMap - # - def initialize - @map = {} - end - - # # Set a key/value pair on the map with a filter. The filter must be true # when applied to the node in order to retrieve the value. # @@ -55,18 +48,17 @@ class Chef # The map is sorted in order of preference already; we just need to find # our place in it (just before the first value with the same preference level). insert_at = nil - @map[key] ||= [] - @map[key].each_with_index do |matcher,index| + map[key] ||= [] + map[key].each_with_index do |matcher,index| cmp = compare_matchers(key, new_matcher, matcher) insert_at ||= index if cmp && cmp <= 0 end if insert_at - @map[key].insert(insert_at, new_matcher) + map[key].insert(insert_at, new_matcher) else - @map[key] << new_matcher + map[key] << new_matcher end - insert_at ||= @map[key].size - 1 - @map + map end # @@ -100,8 +92,8 @@ class Chef # def list(node, key, canonical: nil) raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil? - return [] unless @map.has_key?(key) - @map[key].select do |matcher| + return [] unless map.has_key?(key) + map[key].select do |matcher| node_matches?(node, matcher) && canonical_matches?(canonical, matcher) end.map { |matcher| matcher[:value] } end @@ -110,11 +102,11 @@ class Chef # @return remaining # @api private def delete_canonical(key, value) - remaining = @map[key] + remaining = map[key] if remaining remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) } if remaining.empty? - @map.delete(key) + map.delete(key) remaining = nil end end @@ -181,7 +173,7 @@ class Chef end def compare_matchers(key, new_matcher, matcher) - cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:block] } + cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] } return cmp if cmp != 0 cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] } return cmp if cmp != 0 @@ -222,5 +214,9 @@ class Chef end cmp end + + def map + @map ||= {} + end end end diff --git a/lib/chef/osc_user.rb b/lib/chef/osc_user.rb deleted file mode 100644 index 52bfd11108..0000000000 --- a/lib/chef/osc_user.rb +++ /dev/null @@ -1,194 +0,0 @@ -# -# Author:: Steven Danna (steve@opscode.com) -# Copyright:: Copyright 2012 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -require 'chef/config' -require 'chef/mixin/params_validate' -require 'chef/mixin/from_file' -require 'chef/mash' -require 'chef/json_compat' -require 'chef/search/query' - -# TODO -# DEPRECATION NOTE -# This class was previously Chef::User. It is the code to support the User object -# corrosponding to the Open Source Chef Server 11 and only still exists to support -# users still on OSC 11. -# -# Chef::User now supports Chef Server 12. -# -# New development should occur in Chef::User. -# This file and corrosponding osc_user knife files -# should be removed once client support for Open Source Chef Server 11 expires. -class Chef - class OscUser - - include Chef::Mixin::FromFile - include Chef::Mixin::ParamsValidate - - def initialize - @name = '' - @public_key = nil - @private_key = nil - @password = nil - @admin = false - end - - def name(arg=nil) - set_or_return(:name, arg, - :regex => /^[a-z0-9\-_]+$/) - end - - def admin(arg=nil) - set_or_return(:admin, - arg, :kind_of => [TrueClass, FalseClass]) - end - - def public_key(arg=nil) - set_or_return(:public_key, - arg, :kind_of => String) - end - - def private_key(arg=nil) - set_or_return(:private_key, - arg, :kind_of => String) - end - - def password(arg=nil) - set_or_return(:password, - arg, :kind_of => String) - end - - def to_hash - result = { - "name" => @name, - "public_key" => @public_key, - "admin" => @admin - } - result["private_key"] = @private_key if @private_key - result["password"] = @password if @password - result - end - - def to_json(*a) - Chef::JSONCompat.to_json(to_hash, *a) - end - - def destroy - Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}") - end - - def create - payload = {:name => self.name, :admin => self.admin, :password => self.password } - payload[:public_key] = public_key if public_key - new_user =Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("users", payload) - Chef::OscUser.from_hash(self.to_hash.merge(new_user)) - end - - def update(new_key=false) - payload = {:name => name, :admin => admin} - payload[:private_key] = new_key if new_key - payload[:password] = password if password - updated_user = Chef::REST.new(Chef::Config[:chef_server_url]).put_rest("users/#{name}", payload) - Chef::OscUser.from_hash(self.to_hash.merge(updated_user)) - end - - def save(new_key=false) - begin - create - rescue Net::HTTPServerException => e - if e.response.code == "409" - update(new_key) - else - raise e - end - end - end - - def reregister - r = Chef::REST.new(Chef::Config[:chef_server_url]) - reregistered_self = r.put_rest("users/#{name}", { :name => name, :admin => admin, :private_key => true }) - private_key(reregistered_self["private_key"]) - self - end - - def to_s - "user[#{@name}]" - end - - def inspect - "Chef::OscUser name:'#{name}' admin:'#{admin.inspect}'" + - "public_key:'#{public_key}' private_key:#{private_key}" - end - - # Class Methods - - def self.from_hash(user_hash) - user = Chef::OscUser.new - user.name user_hash['name'] - user.private_key user_hash['private_key'] if user_hash.key?('private_key') - user.password user_hash['password'] if user_hash.key?('password') - user.public_key user_hash['public_key'] - user.admin user_hash['admin'] - user - end - - def self.from_json(json) - Chef::OscUser.from_hash(Chef::JSONCompat.from_json(json)) - end - - class << self - alias_method :json_create, :from_json - end - - def self.list(inflate=false) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users') - users = if response.is_a?(Array) - transform_ohc_list_response(response) # OHC/OPC - else - response # OSC - end - if inflate - users.inject({}) do |user_map, (name, _url)| - user_map[name] = Chef::OscUser.load(name) - user_map - end - else - users - end - end - - def self.load(name) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}") - Chef::OscUser.from_hash(response) - end - - # Gross. Transforms an API response in the form of: - # [ { "user" => { "username" => USERNAME }}, ...] - # into the form - # { "USERNAME" => "URI" } - def self.transform_ohc_list_response(response) - new_response = Hash.new - response.each do |u| - name = u['user']['username'] - new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}" - end - new_response - end - - private_class_method :transform_ohc_list_response - end -end diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb new file mode 100644 index 0000000000..001eb3dc8f --- /dev/null +++ b/lib/chef/platform/handler_map.rb @@ -0,0 +1,45 @@ +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/node_map' + +class Chef + class Platform + class HandlerMap < Chef::NodeMap + # + # "provides" lines with identical filters sort by class name (ascending). + # + def compare_matchers(key, new_matcher, matcher) + cmp = super + if cmp == 0 + # Sort by class name (ascending) as well, if all other properties + # are exactly equal + if new_matcher[:value].is_a?(Class) && !new_matcher[:override] + cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name } + if cmp < 0 + Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended." + elsif cmp > 0 + Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}." + end + end + end + cmp + end + end + end +end diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb index d559eece78..0b050deb59 100644 --- a/lib/chef/platform/priority_map.rb +++ b/lib/chef/platform/priority_map.rb @@ -1,3 +1,21 @@ +# +# Author:: John Keiser (<jkeiser@chef.io>) +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + require 'chef/node_map' class Chef @@ -6,7 +24,7 @@ class Chef def priority(resource_name, priority_array, *filter) set_priority_array(resource_name.to_sym, priority_array, *filter) end - + # @api private def get_priority_array(node, key) get(node, key) @@ -18,37 +36,6 @@ class Chef set(key, priority_array, *filter, &block) priority_array end - - # @api private - def list_handlers(node, key, **filters) - list(node, key, **filters).flatten(1).uniq - end - - # - # Priority maps have one extra precedence: priority arrays override "provides," - # and "provides" lines with identical filters sort by class name (ascending). - # - def compare_matchers(key, new_matcher, matcher) - # Priority arrays come before "provides" - if new_matcher[:value].is_a?(Array) != matcher[:value].is_a?(Array) - return new_matcher[:value].is_a?(Array) ? -1 : 1 - end - - cmp = super - if cmp == 0 - # Sort by class name (ascending) as well, if all other properties - # are exactly equal - if new_matcher[:value].is_a?(Class) && !new_matcher[:override] - cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name } - if cmp < 0 - Chef::Log.warn "You are overriding #{key} on #{new_matcher[:filters].inspect} with #{new_matcher[:value].inspect}: used to be #{matcher[:value].inspect}. Use override: true if this is what you intended." - elsif cmp > 0 - Chef::Log.warn "You declared a new resource #{new_matcher[:value].inspect} for resource #{key}, but it comes alphabetically after #{matcher[:value].inspect} and has the same filters (#{new_matcher[:filters].inspect}), so it will not be used. Use override: true if you want to use it for #{key}." - end - end - end - cmp - end end end end diff --git a/lib/chef/platform/provider_handler_map.rb b/lib/chef/platform/provider_handler_map.rb new file mode 100644 index 0000000000..4549d7994e --- /dev/null +++ b/lib/chef/platform/provider_handler_map.rb @@ -0,0 +1,29 @@ +# +# Author:: John Keiser (<jkeiser@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 'singleton' +require 'chef/platform/handler_map' + +class Chef + class Platform + # @api private + class ProviderHandlerMap < Chef::Platform::HandlerMap + include Singleton + end + end +end diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 4278b8d24f..38dd0e38af 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -176,7 +176,7 @@ class Chef platform_provider(platform, version, resource_type) || resource_matching_provider(platform, version, resource_type) - raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil? + raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil? provider_klass end @@ -197,7 +197,7 @@ class Chef def resource_matching_provider(platform, version, resource_type) if resource_type.kind_of?(Chef::Resource) - class_name = resource_type.class.name ? resource_type.class.name.split('::').last : + class_name = resource_type.class.name ? resource_type.class.name.split('::').last : convert_to_class_name(resource_type.resource_name.to_s) begin diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb index b46f0e394c..b78ac38f0c 100644 --- a/lib/chef/platform/rebooter.rb +++ b/lib/chef/platform/rebooter.rb @@ -32,7 +32,7 @@ class Chef cmd = if Chef::Platform.windows? # should this do /f as well? do we then need a minimum delay to let apps quit? - "shutdown /r /t #{reboot_info[:delay_mins]} /c \"#{reboot_info[:reason]}\"" + "shutdown /r /t #{reboot_info[:delay_mins]*60} /c \"#{reboot_info[:reason]}\"" else # probably Linux-only. "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\"" diff --git a/lib/chef/platform/resource_handler_map.rb b/lib/chef/platform/resource_handler_map.rb new file mode 100644 index 0000000000..27a7bb1342 --- /dev/null +++ b/lib/chef/platform/resource_handler_map.rb @@ -0,0 +1,29 @@ +# +# Author:: John Keiser (<jkeiser@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 'singleton' +require 'chef/platform/handler_map' + +class Chef + class Platform + # @api private + class ResourceHandlerMap < Chef::Platform::HandlerMap + include Singleton + end + end +end diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb index aa57e3ddf0..5cc86fd2e7 100644 --- a/lib/chef/platform/resource_priority_map.rb +++ b/lib/chef/platform/resource_priority_map.rb @@ -6,12 +6,6 @@ class Chef # @api private class ResourcePriorityMap < Chef::Platform::PriorityMap include Singleton - - # @api private - def get_priority_array(node, resource_name, canonical: nil) - super(node, resource_name.to_sym, canonical: canonical) - end - end end end diff --git a/lib/chef/property.rb b/lib/chef/property.rb new file mode 100644 index 0000000000..408090d37b --- /dev/null +++ b/lib/chef/property.rb @@ -0,0 +1,539 @@ +# +# Author:: John Keiser <jkeiser@chef.io> +# Copyright:: Copyright (c) 2015 John Keiser. +# 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/exceptions' +require 'chef/delayed_evaluator' + +class Chef + # + # Type and validation information for a property on a resource. + # + # A property named "x" manipulates the "@x" instance variable on a + # resource. The *presence* of the variable (`instance_variable_defined?(@x)`) + # tells whether the variable is defined; it may have any actual value, + # constrained only by validation. + # + # Properties may have validation, defaults, and coercion, and have full + # support for lazy values. + # + # @see Chef::Resource.property + # @see Chef::DelayedEvaluator + # + class Property + # + # Create a reusable property type that can be used in multiple properties + # in different resources. + # + # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for + # the list of options. + # + # @example + # Property.derive(default: 'hi') + # + def self.derive(**options) + new(**options) + end + + # + # Create a new property. + # + # @param options [Hash<Symbol,Object>] Property options, including + # control options here, as well as validation options (see + # Chef::Mixin::ParamsValidate#validate for a description of validation + # options). + # @option options [Symbol] :name The name of this property. + # @option options [Class] :declared_in The class this property comes from. + # @option options [Symbol] :instance_variable_name The instance variable + # tied to this property. Must include a leading `@`. Defaults to `@<name>`. + # `nil` means the property is opaque and not tied to a specific instance + # variable. + # @option options [Boolean] :desired_state `true` if this property is part of desired + # state. Defaults to `true`. + # @option options [Boolean] :identity `true` if this property is part of object + # identity. Defaults to `false`. + # @option options [Boolean] :name_property `true` if this + # property defaults to the same value as `name`. Equivalent to + # `default: lazy { name }`, except that #property_is_set? will + # return `true` if the property is set *or* if `name` is set. + # @option options [Object] :default The value this property + # will return if the user does not set one. If this is `lazy`, it will + # be run in the context of the instance (and able to access other + # properties) and cached. If not, the value will be frozen with Object#freeze + # to prevent users from modifying it in an instance. + # @option options [Proc] :coerce A proc which will be called to + # transform the user input to canonical form. The value is passed in, + # and the transformed value returned as output. Lazy values will *not* + # be passed to this method until after they are evaluated. Called in the + # context of the resource (meaning you can access other properties). + # @option options [Boolean] :required `true` if this property + # must be present; `false` otherwise. This is checked after the resource + # is fully initialized. + # + def initialize(**options) + options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + options[:name_property] = options.delete(:name_attribute) unless options.has_key?(:name_property) + @options = options + + if options.has_key?(:default) + options[:default] = options[:default].freeze + end + options[:name] = options[:name].to_sym if options[:name] + options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name] + end + + # + # The name of this property. + # + # @return [String] + # + def name + options[:name] + end + + # + # The class this property was defined in. + # + # @return [Class] + # + def declared_in + options[:declared_in] + end + + # + # The instance variable associated with this property. + # + # Defaults to `@<name>` + # + # @return [Symbol] + # + def instance_variable_name + if options.has_key?(:instance_variable_name) + options[:instance_variable_name] + elsif name + :"@#{name}" + end + end + + # + # The raw default value for this resource. + # + # Does not coerce or validate the default. Does not evaluate lazy values. + # + # Defaults to `lazy { name }` if name_property is true; otherwise defaults to + # `nil` + # + def default + return options[:default] if options.has_key?(:default) + return Chef::DelayedEvaluator.new { name } if name_property? + nil + end + + # + # Whether this is part of the resource's natural identity or not. + # + # @return [Boolean] + # + def identity? + options[:identity] + end + + # + # Whether this is part of desired state or not. + # + # Defaults to true. + # + # @return [Boolean] + # + def desired_state? + return true if !options.has_key?(:desired_state) + options[:desired_state] + end + + # + # Whether this is name_property or not. + # + # @return [Boolean] + # + def name_property? + options[:name_property] + end + + # + # Whether this property has a default value. + # + # @return [Boolean] + # + def has_default? + options.has_key?(:default) || name_property? + end + + # + # Whether this property is required or not. + # + # @return [Boolean] + # + def required? + options[:required] + end + + # + # Validation options. (See Chef::Mixin::ParamsValidate#validate.) + # + # @return [Hash<Symbol,Object>] + # + def validation_options + @validation_options ||= options.reject { |k,v| + [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k) + } + end + + # + # Handle the property being called. + # + # The base implementation does the property get-or-set: + # + # ```ruby + # resource.myprop # get + # resource.myprop value # set + # ``` + # + # Subclasses may implement this with any arguments they want, as long as + # the corresponding DSL calls it correctly. + # + # @param resource [Chef::Resource] The resource to get the property from. + # @param value The value to set (or NOT_PASSED if it is a get). + # + # @return The current value of the property. If it is a `set`, lazy values + # will be returned without running, validating or coercing. If it is a + # `get`, the non-lazy, coerced, validated value will always be returned. + # + def call(resource, value=NOT_PASSED) + if value == NOT_PASSED + return get(resource) + end + + # myprop nil is sometimes a get (backcompat) + if value.nil? && !explicitly_accepts_nil?(resource) + # If you say "my_property nil" and the property explicitly accepts + # nil values, we consider this a get. + Chef::Log.deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.") + return get(resource) + end + + # Anything else (myprop value) is a set + set(resource, value) + end + + # + # Get the property value from the resource, handling lazy values, + # defaults, and validation. + # + # - If the property's value is lazy, it is evaluated, coerced and validated. + # - If the property has no value, and is required, raises ValidationFailed. + # - If the property has no value, but has a lazy default, it is evaluated, + # coerced and validated. If the evaluated value is frozen, the resulting + # - If the property has no value, but has a default, the default value + # will be returned and frozen. If the default value is lazy, it will be + # evaluated, coerced and validated, and the result stored in the property. + # - If the property has no value, but is name_property, `resource.name` + # is retrieved, coerced, validated and stored in the property. + # - Otherwise, `nil` is returned. + # + # @param resource [Chef::Resource] The resource to get the property from. + # + # @return The value of the property. + # + # @raise Chef::Exceptions::ValidationFailed If the value is invalid for + # this property, or if the value is required and not set. + # + def get(resource) + if is_set?(resource) + value = get_value(resource) + if value.is_a?(DelayedEvaluator) + value = exec_in_resource(resource, value) + value = coerce(resource, value) + validate(resource, value) + end + value + + else + if has_default? + value = default + if value.is_a?(DelayedEvaluator) + value = exec_in_resource(resource, value) + end + + value = coerce(resource, value) + + # We don't validate defaults + + # If the value is mutable (non-frozen), we set it on the instance + # so that people can mutate it. (All constant default values are + # frozen.) + if !value.frozen? + set_value(resource, value) + end + + value + + elsif required? + raise Chef::Exceptions::ValidationFailed, "#{name} is required" + end + end + end + + # + # Set the value of this property in the given resource. + # + # Non-lazy values are coerced and validated before being set. Coercion + # and validation of lazy values is delayed until they are first retrieved. + # + # @param resource [Chef::Resource] The resource to set this property in. + # @param value The value to set. + # + # @return The value that was set, after coercion (if lazy, still returns + # the lazy value) + # + # @raise Chef::Exceptions::ValidationFailed If the value is invalid for + # this property. + # + def set(resource, value) + unless value.is_a?(DelayedEvaluator) + value = coerce(resource, value) + validate(resource, value) + end + set_value(resource, value) + end + + # + # Find out whether this property has been set. + # + # This will be true if: + # - The user explicitly set the value + # - The property has a default, and the value was retrieved. + # + # From this point of view, it is worth looking at this as "what does the + # user think this value should be." In order words, if the user grabbed + # the value, even if it was a default, they probably based calculations on + # it. If they based calculations on it and the value changes, the rest of + # the world gets inconsistent. + # + # @param resource [Chef::Resource] The resource to get the property from. + # + # @return [Boolean] + # + def is_set?(resource) + value_is_set?(resource) + end + + # + # Reset the value of this property so that is_set? will return false and the + # default will be returned in the future. + # + # @param resource [Chef::Resource] The resource to get the property from. + # + def reset(resource) + reset_value(resource) + end + + # + # Coerce an input value into canonical form for the property. + # + # After coercion, the value is suitable for storage in the resource. + # You must validate values after coercion, however. + # + # Does no special handling for lazy values. + # + # @param resource [Chef::Resource] The resource we're coercing against + # (to provide context for the coerce). + # @param value The value to coerce. + # + # @return The coerced value. + # + # @raise Chef::Exceptions::ValidationFailed If the value is invalid for + # this property. + # + def coerce(resource, value) + if options.has_key?(:coerce) + value = exec_in_resource(resource, options[:coerce], value) + end + value + end + + # + # Validate a value. + # + # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as + # options. + # + # @param resource [Chef::Resource] The resource we're validating against + # (to provide context for the validate). + # @param value The value to validate. + # + # @raise Chef::Exceptions::ValidationFailed If the value is invalid for + # this property. + # + def validate(resource, value) + resource.validate({ name => value }, { name => validation_options }) + end + + # + # Derive a new Property that is just like this one, except with some added or + # changed options. + # + # @param options [Hash<Symbol,Object>] List of options that would be passed + # to #initialize. + # + # @return [Property] The new property type. + # + def derive(**modified_options) + Property.new(**options.merge(**modified_options)) + end + + # + # Emit the DSL for this property into the resource class (`declared_in`). + # + # Creates a getter and setter for the property. + # + def emit_dsl + # We don't create the getter/setter if it's a custom property; we will + # be using the existing getter/setter to manipulate it instead. + return if !instance_variable_name + + # We prefer this form because the property name won't show up in the + # stack trace if you use `define_method`. + declared_in.class_eval <<-EOM, __FILE__, __LINE__+1 + def #{name}(value=NOT_PASSED) + self.class.properties[#{name.inspect}].call(self, value) + end + def #{name}=(value) + self.class.properties[#{name.inspect}].set(self, value) + end + EOM + rescue SyntaxError + # If the name is not a valid ruby name, we use define_method. + resource_class.define_method(name) do |value=NOT_PASSED| + self.class.properties[name].call(self, value) + end + resource_class.define_method("#{name}=") do |value| + self.class.properties[name].set(self, value) + end + end + + protected + + # + # The options this Property will use for get/set behavior and validation. + # + # @see #initialize for a list of valid options. + # + attr_reader :options + + # + # Find out whether this type accepts nil explicitly. + # + # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply + # an empty type. + # + # These examples accept nil explicitly: + # ```ruby + # property :a, [ String, nil ] + # property :a, [ String, NilClass ] + # property :a, [ String, proc { |v| v.nil? } ] + # ``` + # + # This does not (because the "is" doesn't exist or doesn't have nil): + # + # ```ruby + # property :x, String + # ``` + # + # These do not, even though nil would validate fine (because they do not + # have "is"): + # + # ```ruby + # property :a + # property :a, equal_to: [ 1, 2, 3, nil ] + # property :a, kind_of: [ String, NilClass ] + # property :a, respond_to: [ ] + # property :a, callbacks: { "a" => proc { |v| v.nil? } } + # ``` + # + # @param resource [Chef::Resource] The resource we're coercing against + # (to provide context for the coerce). + # + # @return [Boolean] Whether this value explicitly accepts nil. + # + # @api private + def explicitly_accepts_nil?(resource) + options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false) + end + + def get_value(resource) + if instance_variable_name + resource.instance_variable_get(instance_variable_name) + else + resource.send(name) + end + end + + def set_value(resource, value) + if instance_variable_name + resource.instance_variable_set(instance_variable_name, value) + else + resource.send(name, value) + end + end + + def value_is_set?(resource) + if instance_variable_name + resource.instance_variable_defined?(instance_variable_name) + else + true + end + end + + def reset_value(resource) + if instance_variable_name + if value_is_set?(resource) + resource.remove_instance_variable(instance_variable_name) + end + else + raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset" + end + end + + def exec_in_resource(resource, proc, *args) + if resource + if proc.arity > args.size + value = proc.call(resource, *args) + else + value = resource.instance_exec(*args, &proc) + end + else + value = proc.call + end + + if value.is_a?(DelayedEvaluator) + value = coerce(resource, value) + validate(resource, value) + end + value + end + end +end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 280277d947..dcfc92645b 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -176,7 +176,7 @@ class Chef end def self.provides(short_name, opts={}, &block) - Chef.provider_priority_map.set(short_name, self, opts, &block) + Chef.provider_handler_map.set(short_name, self, opts, &block) end def self.provides?(node, resource) diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb index 5fa84a21e9..379369ba6e 100644 --- a/lib/chef/provider/dsc_resource.rb +++ b/lib/chef/provider/dsc_resource.rb @@ -53,7 +53,7 @@ class Chef 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, + a.failure_message Chef::Exceptions::ProviderNotFound, err a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."] a.block_action! @@ -63,7 +63,7 @@ class Chef meta_configuration['RefreshMode'] == 'Disabled' } err = ["The LCM must have its RefreshMode set to Disabled. "] - a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ') + a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ') a.whyrun err + ["Assuming a previous resource sets the RefreshMode."] a.block_action! end diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index a75e68a475..b2432132b7 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -70,7 +70,7 @@ class Chef "Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.", ] a.assertion { supports_dsc? } - a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ') + a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ') a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."] a.block_action! end diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb index 4ad7b24c15..510dfde46d 100644 --- a/lib/chef/provider/mount/aix.rb +++ b/lib/chef/provider/mount/aix.rb @@ -32,7 +32,7 @@ class Chef @new_resource.options.clear end if @new_resource.fstype == "auto" - @new_resource.fstype = nil + @new_resource.send(:clear_fstype) end end diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index 9d534ec414..880104bff7 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -142,7 +142,7 @@ class Chef def action_remove if removing_package? description = @new_resource.version ? "version #{@new_resource.version} of " : "" - converge_by("remove #{description} package #{@current_resource.package_name}") do + converge_by("remove #{description}package #{@current_resource.package_name}") do remove_package(@current_resource.package_name, @new_resource.version) Chef::Log.info("#{@new_resource} removed") end @@ -491,37 +491,6 @@ class Chef end end - # Set provider priority - require 'chef/chef_class' - require 'chef/provider/package/dpkg' - require 'chef/provider/package/homebrew' - require 'chef/provider/package/macports' - require 'chef/provider/package/apt' - require 'chef/provider/package/yum' - require 'chef/provider/package/zypper' - require 'chef/provider/package/portage' - require 'chef/provider/package/pacman' - require 'chef/provider/package/ips' - require 'chef/provider/package/solaris' - require 'chef/provider/package/smartos' - require 'chef/provider/package/aix' - require 'chef/provider/package/paludis' - - Chef.set_provider_priority_array :package, [ Homebrew, Macports ], os: "darwin" - - Chef.set_provider_priority_array :package, Apt, platform_family: "debian" - Chef.set_provider_priority_array :package, Yum, platform_family: %w(rhel fedora) - Chef.set_provider_priority_array :package, Zypper, platform_family: "suse" - Chef.set_provider_priority_array :package, Portage, platform: "gentoo" - Chef.set_provider_priority_array :package, Pacman, platform: "arch" - Chef.set_provider_priority_array :package, Ips, platform: %w(openindiana opensolaris omnios solaris2) - Chef.set_provider_priority_array :package, Solaris, platform: "nexentacore" - Chef.set_provider_priority_array :package, Solaris, platform: "solaris2", platform_version: '< 5.11' - - Chef.set_provider_priority_array :package, SmartOS, platform: "smartos" - Chef.set_provider_priority_array :package, Aix, platform: "aix" - Chef.set_provider_priority_array :package, Paludis, platform: "exherbo" - private def shell_out_with_timeout(*command_args) diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb index b97db9d061..5165f4b4ea 100644 --- a/lib/chef/provider/package/aix.rb +++ b/lib/chef/provider/package/aix.rb @@ -26,6 +26,7 @@ class Chef class Package class Aix < Chef::Provider::Package + provides :package, os: "aix" provides :bff_package, os: "aix" include Chef::Mixin::GetSourceFromPackage diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb index bd6ed283bf..e109c9966a 100644 --- a/lib/chef/provider/package/apt.rb +++ b/lib/chef/provider/package/apt.rb @@ -25,6 +25,7 @@ class Chef class Package class Apt < Chef::Provider::Package + provides :package, platform_family: "debian" provides :apt_package, os: "linux" # return [Hash] mapping of package name to Boolean value diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb index beede1c916..e5c45f0a62 100644 --- a/lib/chef/provider/package/homebrew.rb +++ b/lib/chef/provider/package/homebrew.rb @@ -26,6 +26,7 @@ class Chef class Package class Homebrew < Chef::Provider::Package + provides :package, os: "darwin", override: true provides :homebrew_package include Chef::Mixin::HomebrewUser diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb index 4d7f4a3583..96c2e711d4 100644 --- a/lib/chef/provider/package/ips.rb +++ b/lib/chef/provider/package/ips.rb @@ -27,6 +27,7 @@ class Chef class Package class Ips < Chef::Provider::Package + provides :package, platform: %w(openindiana opensolaris omnios solaris2) provides :ips_package, os: "solaris2" attr_accessor :virtual diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb index e945211540..c7ea71ac8c 100644 --- a/lib/chef/provider/package/macports.rb +++ b/lib/chef/provider/package/macports.rb @@ -3,6 +3,7 @@ class Chef class Package class Macports < Chef::Provider::Package + provides :package, os: "darwin" provides :macports_package def load_current_resource diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb index f231101390..83fc09c8ae 100644 --- a/lib/chef/provider/package/openbsd.rb +++ b/lib/chef/provider/package/openbsd.rb @@ -31,6 +31,7 @@ class Chef class Openbsd < Chef::Provider::Package provides :package, os: "openbsd" + provides :openbsd_package include Chef::Mixin::ShellOut include Chef::Mixin::GetSourceFromPackage diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb index bf03e54656..01e3a9cc01 100644 --- a/lib/chef/provider/package/pacman.rb +++ b/lib/chef/provider/package/pacman.rb @@ -25,6 +25,7 @@ class Chef class Package class Pacman < Chef::Provider::Package + provides :package, platform: "arch" provides :pacman_package, os: "linux" def load_current_resource diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb index 407e0d0110..2d6302515b 100644 --- a/lib/chef/provider/package/paludis.rb +++ b/lib/chef/provider/package/paludis.rb @@ -24,6 +24,7 @@ class Chef class Package class Paludis < Chef::Provider::Package + provides :package, platform: "exherbo" provides :paludis_package, os: "linux" def load_current_resource diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb index 4ba0160bb0..95782a6774 100644 --- a/lib/chef/provider/package/portage.rb +++ b/lib/chef/provider/package/portage.rb @@ -25,6 +25,8 @@ class Chef class Provider class Package class Portage < Chef::Provider::Package + + provides :package, platform: "gentoo" provides :portage_package PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)} diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb index 0d5b801c96..71b8a9b9e1 100644 --- a/lib/chef/provider/package/smartos.rb +++ b/lib/chef/provider/package/smartos.rb @@ -29,6 +29,7 @@ class Chef class SmartOS < Chef::Provider::Package attr_accessor :is_virtual_package + provides :package, platform: "smartos" provides :smartos_package, os: "solaris2", platform_family: "smartos" def load_current_resource diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb index 9b10403344..e62f37d27b 100644 --- a/lib/chef/provider/package/solaris.rb +++ b/lib/chef/provider/package/solaris.rb @@ -27,6 +27,8 @@ class Chef include Chef::Mixin::GetSourceFromPackage + provides :package, platform: "nexentacore" + provides :package, platform: "solaris2", platform_version: '< 5.11' provides :solaris_package, os: "solaris2" # def initialize(*args) diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb index 85c2ba683c..e8c0483741 100644 --- a/lib/chef/provider/package/yum.rb +++ b/lib/chef/provider/package/yum.rb @@ -28,6 +28,7 @@ class Chef class Package class Yum < Chef::Provider::Package + provides :package, platform_family: %w(rhel fedora) provides :yum_package, os: "linux" class RPMUtils diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb index c2a3ac4ba8..ac42304ffb 100644 --- a/lib/chef/provider/package/zypper.rb +++ b/lib/chef/provider/package/zypper.rb @@ -29,6 +29,7 @@ class Chef class Package class Zypper < Chef::Provider::Package + provides :package, platform_family: "suse" provides :zypper_package, os: "linux" def load_current_resource diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index ed44dee6ae..3c52db1f33 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -30,8 +30,8 @@ class Chef end def action_run - valid_syntax = validate_script_syntax! - super if valid_syntax + validate_script_syntax! + super end def flags @@ -62,30 +62,43 @@ class Chef def validate_script_syntax! interpreter_arguments = default_interpreter_flags.join(' ') Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file | - user_script_file.puts("{#{@new_resource.code}}") - user_script_file.close + # Wrap the user's code in a PowerShell script block so that + # it isn't executed. However, syntactically invalid script + # in that block will still trigger a syntax error which is + # exactly what we want here -- verify the syntax without + # actually running the script. + user_code_wrapped_in_powershell_script_block = <<-EOH +{ + #{@new_resource.code} +} +EOH + user_script_file.puts user_code_wrapped_in_powershell_script_block + # A .close or explicit .flush required to ensure the file is + # written to the file system at this point, which is required since + # the intent is to execute the code just written to it. + user_script_file.close validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}" - # For consistency with other script resources, allow even syntax errors - # to be suppressed if the returns attribute would have suppressed it - # at converge. - valid_returns = [0] - specified_returns = @new_resource.returns.is_a?(Integer) ? - [@new_resource.returns] : - @new_resource.returns - valid_returns.concat([1]) if specified_returns.include?(1) - - result = shell_out!(validation_command, {returns: valid_returns}) - result.exitstatus == 0 + # Note that other script providers like bash allow syntax errors + # to be suppressed by setting 'returns' to a value that the + # interpreter would return as a status code in the syntax + # error case. We explicitly don't do this here -- syntax + # errors will not be suppressed, since doing so could make + # it harder for users to detect / debug invalid scripts. + + # Therefore, the only return value for a syntactically valid + # script is 0. If an exception is raised by shellout, this + # means a non-zero return and thus a syntactically invalid script. + shell_out!(validation_command, {returns: [0]}) end end def default_interpreter_flags - # 'Bypass' is preferable since it doesn't require user input confirmation - # for files such as PowerShell modules downloaded from the - # Internet. However, 'Bypass' is not supported prior to - # PowerShell 3.0, so the fallback is 'Unrestricted' + # Execution policy 'Bypass' is preferable since it doesn't require + # user input confirmation for files such as PowerShell modules + # downloaded from the Internet. However, 'Bypass' is not supported + # prior to PowerShell 3.0, so the fallback is 'Unrestricted' execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted' [ diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb index cd62f7c56f..948fa6c63f 100644 --- a/lib/chef/provider/registry_key.rb +++ b/lib/chef/provider/registry_key.rb @@ -64,7 +64,7 @@ class Chef def values_to_hash(values) if values - @name_hash = Hash[values.map { |val| [val[:name], val] }] + @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }] else @name_hash = {} end @@ -100,8 +100,8 @@ class Chef end end @new_resource.unscrubbed_values.each do |value| - if @name_hash.has_key?(value[:name]) - current_value = @name_hash[value[:name]] + if @name_hash.has_key?(value[:name].downcase) + current_value = @name_hash[value[:name].downcase] unless current_value[:type] == value[:type] && current_value[:data] == value[:data] converge_by("set value #{value}") do registry.set_value(@new_resource.key, value) @@ -122,7 +122,7 @@ class Chef end end @new_resource.unscrubbed_values.each do |value| - unless @name_hash.has_key?(value[:name]) + unless @name_hash.has_key?(value[:name].downcase) converge_by("create value #{value}") do registry.set_value(@new_resource.key, value) end @@ -133,7 +133,7 @@ class Chef def action_delete if registry.key_exists?(@new_resource.key) @new_resource.unscrubbed_values.each do |value| - if @name_hash.has_key?(value[:name]) + if @name_hash.has_key?(value[:name].downcase) converge_by("delete value #{value}") do registry.delete_value(@new_resource.key, value) end diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index 9c523b5e66..15dd72cb04 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -188,29 +188,11 @@ class Chef require 'chef/provider/service/upstart' require 'chef/provider/service/debian' require 'chef/provider/service/invokercd' - require 'chef/provider/service/freebsd' - require 'chef/provider/service/openbsd' - require 'chef/provider/service/solaris' - require 'chef/provider/service/macosx' - - def self.os(os, *providers) - Chef.set_provider_priority_array(:service, providers, os: os) - end - def self.platform_family(platform_family, *providers) - Chef.set_provider_priority_array(:service, providers, platform_family: platform_family) - end - - os %w(freebsd netbsd), Freebsd - os %w(openbsd), Openbsd - os %w(solaris2), Solaris - os %w(darwin), Macosx - os %w(linux), Systemd, Insserv, Redhat - - platform_family %w(arch), Systemd, Arch - platform_family %w(gentoo), Systemd, Gentoo - platform_family %w(debian), Systemd, Upstart, Insserv, Debian, Invokercd - platform_family %w(rhel fedora suse), Systemd, Insserv, Redhat + Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: 'arch' + Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: 'gentoo' + Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: 'debian' + Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w(rhel fedora suse) end end end diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index c67f3f05da..46c23fdd34 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -22,6 +22,8 @@ class Chef class Provider class Service class Debian < Chef::Provider::Service::Init + provides :service, platform_family: 'debian' + UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb index 4534e33f32..2fd2eac38e 100644 --- a/lib/chef/provider/service/insserv.rb +++ b/lib/chef/provider/service/insserv.rb @@ -24,6 +24,8 @@ class Chef class Service class Insserv < Chef::Provider::Service::Init + provides :service, platform_family: %w(debian rhel fedora suse) + def self.provides?(node, resource) super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) end diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb index fdf4cbc256..39022546b0 100644 --- a/lib/chef/provider/service/invokercd.rb +++ b/lib/chef/provider/service/invokercd.rb @@ -23,6 +23,8 @@ class Chef class Service class Invokercd < Chef::Provider::Service::Init + provides :service, platform_family: 'debian', override: true + def self.provides?(node, resource) super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd) end diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb index d509ee10ff..173191e01c 100644 --- a/lib/chef/provider/service/openbsd.rb +++ b/lib/chef/provider/service/openbsd.rb @@ -26,7 +26,7 @@ class Chef class Service class Openbsd < Chef::Provider::Service::Init - provides :service, os: [ "openbsd" ] + provides :service, os: "openbsd" include Chef::Mixin::ShellOut diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb index a8deb13aec..2330d88eb7 100644 --- a/lib/chef/provider/service/redhat.rb +++ b/lib/chef/provider/service/redhat.rb @@ -23,6 +23,8 @@ class Chef class Service class Redhat < Chef::Provider::Service::Init + provides :service, platform_family: %w(rhel fedora suse) + CHKCONFIG_ON = /\d:on/ CHKCONFIG_MISSING = /No such/ diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb index d8ce6af649..8809d1c708 100644 --- a/lib/chef/provider/service/upstart.rb +++ b/lib/chef/provider/service/upstart.rb @@ -25,6 +25,9 @@ class Chef class Provider class Service class Upstart < Chef::Provider::Service::Simple + + provides :service, platform_family: 'debian', override: true + UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/ def self.provides?(node, resource) diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index 5bfee343d1..8459bc1328 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -17,7 +17,7 @@ # require 'chef/exceptions' -require 'chef/platform/provider_priority_map' +require 'chef/platform/priority_map' class Chef # @@ -62,12 +62,47 @@ class Chef maybe_chef_platform_lookup(resource) end + # Does NOT call provides? on the resource (it is assumed this is being + # called *from* provides?). def provided_by?(provider_class) - prioritized_handlers.include?(provider_class) + potential_handlers.include?(provider_class) + end + + def enabled_handlers + @enabled_handlers ||= potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) } + end + + # TODO deprecate this and allow actions to be passed as a filter to + # `provides` so we don't have to have two separate things. + # @api private + def supported_handlers + enabled_handlers.select { |handler| handler.supports?(resource, action) } end private + def potential_handlers + handler_map.list(node, resource.resource_name).uniq + end + + # The list of handlers, with any in the priority_map moved to the front + def prioritized_handlers + @prioritized_handlers ||= begin + supported_handlers = self.supported_handlers + if supported_handlers.empty? + # if none of the providers specifically support the resource, we still need to pick one of the providers that are + # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then. + Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway." + supported_handlers = enabled_handlers + end + + prioritized = priority_map.list(node, resource.resource_name).flatten(1) + prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers + prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set) + prioritized + end + end + # if resource.provider is set, just return one of those objects def maybe_explicit_provider(resource) return nil unless resource.provider @@ -78,27 +113,7 @@ class Chef def maybe_dynamic_provider_resolution(resource, action) Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}" - # Get all the handlers in the priority bucket - handlers = prioritized_handlers - - # Narrow it down to handlers that return `true` to `provides?` - # TODO deprecate this and don't bother calling--the fact that they said - # `provides` should be enough. But we need to do it right now because - # some classes implement additional handling. - enabled_handlers = prioritized_handlers.select { |handler| handler.provides?(node, resource) } - - # Narrow it down to handlers that return `true` to `supports?` - # TODO deprecate this and allow actions to be passed as a filter to - # `provides` so we don't have to have two separate things. - supported_handlers = enabled_handlers.select { |handler| handler.supports?(resource, action) } - if supported_handlers.empty? - # if none of the providers specifically support the resource, we still need to pick one of the providers that are - # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then. - Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway." - handler = enabled_handlers.first - else - handler = supported_handlers.first - end + handler = prioritized_handlers.first if handler Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}" @@ -114,13 +129,16 @@ class Chef Chef::Platform.find_provider_for_node(node, resource) end - def provider_priority_map - Chef::Platform::ProviderPriorityMap.instance + def priority_map + Chef.provider_priority_map end - def prioritized_handlers - @prioritized_handlers ||= - provider_priority_map.list_handlers(node, resource.resource_name).flatten(1).uniq + def handler_map + Chef.provider_handler_map + end + + def overrode_provides?(handler) + handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner end module Deprecated @@ -129,33 +147,21 @@ class Chef @providers ||= Chef::Provider.descendants end - # this cut looks at if the provider can handle the resource type on the node def enabled_handlers - @enabled_handlers ||= - providers.select do |klass| - # NB: this is different from resource_resolver which must pass a resource_name - # FIXME: deprecate this and normalize on passing resource_name here - klass.provides?(node, resource) - end.sort {|a,b| a.to_s <=> b.to_s } - end - - # this cut looks at if the provider can handle the specific resource and action - def supported_handlers - @supported_handlers ||= - enabled_handlers.select do |klass| - klass.supports?(resource, action) - end - end - - # If there are no providers for a DSL, we search through the - def prioritized_handlers - @prioritized_handlers ||= super || begin - result = providers.select { |handler| handler.provides?(node, resource) }.sort_by(:name) - if !result.empty? - Chef::Log.deprecation("#{resource.resource_name.to_sym} is marked as providing DSL #{method_symbol}, but provides #{resource.resource_name.to_sym.inspect} was never called!") - Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + @enabled_handlers ||= begin + handlers = super + if handlers.empty? + # Look through all providers, and find ones that return true to provides. + # Don't bother with ones that don't override provides?, since they + # would have been in enabled_handlers already if that were so. (It's a + # perf concern otherwise.) + handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) } + handlers.each do |handler| + Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!") + Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + end end - result + handlers end end end diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 8d2532dac4..dca0033409 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -101,7 +101,7 @@ class Chef # @param run_context The context of the Chef run. Corresponds to #run_context. # def initialize(name, run_context=nil) - name(name) + name(name) unless name.nil? @run_context = run_context @noop = nil @before = nil @@ -130,37 +130,27 @@ class Chef end # - # The name of this particular resource. + # The list of properties defined on this resource. # - # This special resource attribute is set automatically from the declaration - # of the resource, e.g. - # - # execute 'Vitruvius' do - # command 'ls' - # end + # Everything defined with `property` is in this list. # - # Will set the name to "Vitruvius". + # @param include_superclass [Boolean] `true` to include properties defined + # on superclasses; `false` or `nil` to return the list of properties + # directly on this class. # - # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`. + # @return [Hash<Symbol,Property>] The list of property names and types. # - # This is also used for resource notifications and subscribes in the same manner. - # - # This will coerce any object into a string via #to_s. Arrays are a special case - # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more - # awkward `package[["foo", "bar"]]` that #to_s would produce. - # - # @param name [Object] The name to set, typically a String or Array - # @return [String] The name of this Resource. - # - def name(name=nil) - if !name.nil? - if name.is_a?(Array) - @name = name.join(', ') + def self.properties(include_superclass=true) + @properties ||= {} + if include_superclass + if superclass.respond_to?(:properties) + superclass.properties.merge(@properties) else - @name = name.to_s + @properties.dup end + else + @properties end - @name end # @@ -169,26 +159,24 @@ class Chef # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`) # @return [Array[Symbol]] the list of actions. # - attr_accessor :action def action(arg=nil) if arg - if arg.is_a?(Array) - arg = arg.map { |a| a.to_sym } - else - arg = arg.to_sym - end - Array(arg).each do |action| + arg = Array(arg).map(&:to_sym) + arg.each do |action| validate( { action: action }, { action: { kind_of: Symbol, equal_to: allowed_actions } } ) end - self.action = arg + @action = arg else @action end end + # Alias for normal assigment syntax. + alias_method :action=, :action + # # Sets up a notification that will run a particular action on another resource # if and when *this* resource is updated by an action. @@ -477,13 +465,21 @@ class Chef # # Get the value of the state attributes in this resource as a hash. # + # Does not include properties that are not set (unless they are identity + # properties). + # # @return [Hash{Symbol => Object}] A Hash of attribute => value for the # Resource class's `state_attrs`. + # def state_for_resource_reporter - self.class.state_attrs.inject({}) do |state_attrs, attr_name| - state_attrs[attr_name] = send(attr_name) - state_attrs + state = {} + state_properties = self.class.state_properties + state_properties.each do |property| + if property.identity? || property.is_set?(self) + state[property.name] = send(property.name) + end end + state end # @@ -496,17 +492,22 @@ class Chef alias_method :state, :state_for_resource_reporter # - # The value of the identity attribute, if declared. Falls back to #name if - # no identity attribute is declared. + # The value of the identity of this resource. + # + # - If there are no identity properties on the resource, `name` is returned. + # - If there is exactly one identity property on the resource, it is returned. + # - If there are more than one, they are returned in a hash. # - # @return The value of the identity attribute. + # @return [Object,Hash<Symbol,Object>] The identity of this resource. # def identity - if identity_attr = self.class.identity_attr - send(identity_attr) - else - name + result = {} + identity_properties = self.class.identity_properties + identity_properties.each do |property| + result[property.name] = send(property.name) end + return result.values.first if identity_properties.size == 1 + result end # @@ -758,6 +759,10 @@ class Chef # will return if the user does not set one. If this is `lazy`, it will # be run in the context of the instance (and able to access other # properties). + # @option options [Boolean] :desired_state `true` if this property is + # part of desired state. Defaults to `true`. + # @option options [Boolean] :identity `true` if this property + # is part of object identity. Defaults to `false`. # # @example Bare property # property :x @@ -774,29 +779,101 @@ class Chef def self.property(name, type=NOT_PASSED, **options) name = name.to_sym - if type != NOT_PASSED + options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name) + options.merge!(name: name, declared_in: self) + + if type == NOT_PASSED + # If a type is not passed, the property derives from the + # superclass property (if any) + if properties.has_key?(name) + property = properties[name].derive(**options) + else + property = property_type(**options) + end + + # If a Property is specified, derive a new one from that. + elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property) + property = type.derive(**options) + + # If a primitive type was passed, combine it with "is" + else if options[:is] options[:is] = ([ type ] + [ options[:is] ]).flatten(1) else options[:is] = type end + property = property_type(**options) end - define_method(name) do |value=NOT_PASSED| - set_or_return(name, value, options) - end - define_method("#{name}=") do |value| - set_or_return(name, value, options) - end + local_properties = properties(false) + local_properties[name] = property + + property.emit_dsl + end + + # + # Create a reusable property type that can be used in multiple properties + # in different resources. + # + # @param options [Hash<Symbol,Object>] Validation options. see #property for + # the list of options. + # + # @example + # property_type(default: 'hi') + # + def self.property_type(**options) + Property.derive(**options) end # + # The name of this particular resource. + # + # This special resource attribute is set automatically from the declaration + # of the resource, e.g. + # + # execute 'Vitruvius' do + # command 'ls' + # end + # + # Will set the name to "Vitruvius". + # + # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`. + # + # This is also used for resource notifications and subscribes in the same manner. + # + # This will coerce any object into a string via #to_s. Arrays are a special case + # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more + # awkward `package[["foo", "bar"]]` that #to_s would produce. + # + # @param name [Object] The name to set, typically a String or Array + # @return [String] The name of this Resource. + # + property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false + + # # Whether this property has been set (or whether it has a default that has # been retrieved). # + # @param name [Symbol] The name of the property. + # @return [Boolean] `true` if the property has been set. + # def property_is_set?(name) - name = name.to_sym - instance_variable_defined?("@#{name}") + property = self.class.properties[name.to_sym] + raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property + property.is_set?(self) + end + + # + # Clear this property as if it had never been set. It will thereafter return + # the default. + # been retrieved). + # + # @param name [Symbol] The name of the property. + # + def reset_property(name) + property = self.class.properties[name.to_sym] + raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property + property.reset(self) end # @@ -810,46 +887,186 @@ class Chef DelayedEvaluator.new(&block) end - # Set or return the list of "state attributes" implemented by the Resource - # subclass. State attributes are attributes that describe the desired state - # of the system, such as file permissions or ownership. In general, state - # attributes are attributes that could be populated by examining the state - # of the system (e.g., File.stat can tell you the permissions on an - # existing file). Contrarily, attributes that are not "state attributes" - # usually modify the way Chef itself behaves, for example by providing - # additional options for a package manager to use when installing a - # package. + # + # Get or set the list of desired state properties for this resource. + # + # State properties are properties that describe the desired state + # of the system, such as file permissions or ownership. + # In general, state properties are properties that could be populated by + # examining the state of the system (e.g., File.stat can tell you the + # permissions on an existing file). Contrarily, properties that are not + # "state properties" usually modify the way Chef itself behaves, for example + # by providing additional options for a package manager to use when + # installing a package. # # This list is used by the Chef client auditing system to extract # information from resources to describe changes made to the system. - def self.state_attrs(*attr_names) - @state_attrs ||= [] - @state_attrs = attr_names unless attr_names.empty? + # + # This method is unnecessary when declaring properties with `property`; + # properties are added to state_properties by default, and can be turned off + # with `desired_state: false`. + # + # ```ruby + # property :x # part of desired state + # property :y, desired_state: false # not part of desired state + # ``` + # + # @param names [Array<Symbol>] A list of property names to set as desired + # state. + # + # @return [Array<Property>] All properties in desired state. + # + def self.state_properties(*names) + if !names.empty? + names = names.map { |name| name.to_sym }.uniq - # Return *all* state_attrs that this class has, including inherited ones - if superclass.respond_to?(:state_attrs) - superclass.state_attrs + @state_attrs - else - @state_attrs + local_properties = properties(false) + # Add new properties to the list. + names.each do |name| + property = properties[name] + if !property + self.property name, instance_variable_name: false, desired_state: true + elsif !property.desired_state? + self.property name, desired_state: true + end + end + + # If state_attrs *excludes* something which is currently desired state, + # mark it as desired_state: false. + local_properties.each do |name,property| + if property.desired_state? && !names.include?(name) + self.property name, desired_state: false + end + end end + + properties.values.select { |property| property.desired_state? } + end + + # + # Set or return the list of "state properties" implemented by the Resource + # subclass. + # + # Equivalent to calling #state_properties and getting `state_properties.keys`. + # + # @deprecated Use state_properties.keys instead. Note that when you declare + # properties with `property`: properties are added to state_properties by + # default, and can be turned off with `desired_state: false` + # + # ```ruby + # property :x # part of desired state + # property :y, desired_state: false # not part of desired state + # ``` + # + # @param names [Array<Symbol>] A list of property names to set as desired + # state. + # + # @return [Array<Symbol>] All property names with desired state. + # + def self.state_attrs(*names) + state_properties(*names).map { |property| property.name } end - # Set or return the "identity attribute" for this resource class. This is - # generally going to be the "name attribute" for this resource. In other - # words, the resource type plus this attribute uniquely identify a given - # bit of state that chef manages. For a File resource, this would be the - # path, for a package resource, it will be the package name. This will show - # up in chef-client's audit records as a searchable field. - def self.identity_attr(attr_name=nil) - @identity_attr ||= nil - @identity_attr = attr_name if attr_name + # + # Set the identity of this resource to a particular set of properties. + # + # This drives #identity, which returns data that uniquely refers to a given + # resource on the given node (in such a way that it can be correlated + # across Chef runs). + # + # This method is unnecessary when declaring properties with `property`; + # properties can be added to identity during declaration with + # `identity: true`. + # + # ```ruby + # property :x, identity: true # part of identity + # property :y # not part of identity + # ``` + # + # If no properties are marked as identity, "name" is considered the identity. + # + # @param names [Array<Symbol>] A list of property names to set as the identity. + # + # @return [Array<Property>] All identity properties. + # + def self.identity_properties(*names) + if !names.empty? + names = names.map { |name| name.to_sym } - # If this class doesn't have an identity attr, we'll defer to the superclass: - if @identity_attr || !superclass.respond_to?(:identity_attr) - @identity_attr - else - superclass.identity_attr + # Add or change properties that are not part of the identity. + names.each do |name| + property = properties[name] + if !property + self.property name, instance_variable_name: false, identity: true + elsif !property.identity? + self.property name, identity: true + end + end + + # If identity_properties *excludes* something which is currently part of + # the identity, mark it as identity: false. + properties.each do |name,property| + if property.identity? && !names.include?(name) + self.property name, identity: false + end + end end + + result = properties.values.select { |property| property.identity? } + result = [ properties[:name] ] if result.empty? + result + end + + # + # Set the identity of this resource to a particular property. + # + # This drives #identity, which returns data that uniquely refers to a given + # resource on the given node (in such a way that it can be correlated + # across Chef runs). + # + # This method is unnecessary when declaring properties with `property`; + # properties can be added to identity during declaration with + # `identity: true`. + # + # ```ruby + # property :x, identity: true # part of identity + # property :y # not part of identity + # ``` + # + # @param name [Symbol] A list of property names to set as the identity. + # + # @return [Symbol] The identity property if there is only one; or `nil` if + # there are more than one. + # + # @raise [ArgumentError] If no arguments are passed and the resource has + # more than one identity property. + # + def self.identity_property(name=nil) + result = identity_properties(*Array(name)) + if result.size > 1 + raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})." + end + result.first + end + + # + # Set a property as the "identity attribute" for this resource. + # + # Identical to calling #identity_property.first.key. + # + # @param name [Symbol] The name of the property to set. + # + # @return [Symbol] + # + # @deprecated `identity_property` should be used instead. + # + # @raise [ArgumentError] If no arguments are passed and the resource has + # more than one identity property. + # + def self.identity_attr(name=nil) + property = identity_property(name) + return nil if !property + property.name end # @@ -1019,7 +1236,7 @@ class Chef if name name = name.to_sym # If our class is not already providing this name, provide it. - if !Chef::ResourceResolver.list(name).include?(self) + if !Chef::ResourceResolver.includes_handler?(name, self) provides name, canonical: true end @resource_name = name @@ -1095,22 +1312,17 @@ class Chef # Setting default_action will automatially add the action to # allowed_actions, if it isn't already there. # - # Defaults to :nothing. + # Defaults to [:nothing]. # # @param action_name [Symbol,Array<Symbol>] The default action (or series # of actions) to use. # - # @return [Symbol,Array<Symbol>] The default actions for the resource. + # @return [Array<Symbol>] The default actions for the resource. # def self.default_action(action_name=NOT_PASSED) unless action_name.equal?(NOT_PASSED) - if action_name.is_a?(Array) - @default_action = action_name.map { |arg| arg.to_sym } - else - @default_action = action_name.to_sym - end - - self.allowed_actions |= Array(@default_action) + @default_action = Array(action_name).map(&:to_sym) + self.allowed_actions |= @default_action end if @default_action @@ -1118,7 +1330,7 @@ class Chef elsif superclass.respond_to?(:default_action) superclass.default_action else - :nothing + [:nothing] end end def self.default_action=(action_name) @@ -1159,7 +1371,7 @@ class Chef action = action.to_sym new_action_provider_class.action(action, &recipe_block) self.allowed_actions += [ action ] - default_action action if default_action == :nothing + default_action action if Array(default_action) == [:nothing] end # @@ -1206,9 +1418,6 @@ class Chef define_method(:load_current_resource) {} end end - def self.default_action=(action_name) - default_action(action_name) - end # # Internal Resource Interface (for Chef) @@ -1304,7 +1513,7 @@ class Chef end def self.inherited(child) super - @sorted_descendants = nil + @@sorted_descendants = nil # set resource_name automatically if it's not set if child.name && !child.resource_name if child.name =~ /^Chef::Resource::(\w+)$/ @@ -1342,13 +1551,13 @@ class Chef remove_canonical_dsl end - result = Chef.resource_priority_map.set(name, self, options, &block) + result = Chef.resource_handler_map.set(name, self, options, &block) Chef::DSL::Resources.add_resource_dsl(name) result end - def self.provides?(node, resource) - Chef::ResourceResolver.resolve(resource, node: node).provided_by?(self) + def self.provides?(node, resource_name) + Chef::ResourceResolver.new(node, resource_name).provided_by?(self) end # Helper for #notifies @@ -1506,56 +1715,11 @@ class Chef Chef::Resource.send(:remove_const, class_name) end - # In order to generate deprecation warnings when you use Chef::Resource::MyLwrp, - # we make a special subclass (identical in nearly all respects) of the - # actual LWRP. When you say any of these, a deprecation warning will be - # generated: - # - # - Chef::Resource::MyLwrp.new(...) - # - resource.is_a?(Chef::Resource::MyLwrp) - # - resource.kind_of?(Chef::Resource::MyLwrp) - # - case resource - # when Chef::Resource::MyLwrp - # end - # - resource_subclass = Class.new(resource_class) do - resource_name nil # we do not actually provide anything - def initialize(*args, &block) - Chef::Log.deprecation("Using an LWRP by its name (#{self.class.name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.") - super - end - def self.resource_name(*args) - if args.empty? - @resource_name ||= superclass.resource_name - else - super - end - end - self - end - eval("Chef::Resource::#{class_name} = resource_subclass") - # Make case, is_a and kind_of work with the new subclass, for backcompat. - # Any subclass of Chef::Resource::ResourceClass is already a subclass of resource_class - # Any subclass of resource_class is considered a subclass of Chef::Resource::ResourceClass - resource_class.class_eval do - define_method(:is_a?) do |other| - other.is_a?(Module) && other === self - end - define_method(:kind_of?) do |other| - other.is_a?(Module) && other === self - end + if !Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Resource.const_set(class_name, resource_class) + deprecated_constants[class_name.to_sym] = resource_class end - resource_subclass.class_eval do - define_singleton_method(:===) do |other| - Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.") - # resource_subclass is a superclass of all resource_class descendants. - if self == resource_subclass && other.class <= resource_class - return true - end - super(other) - end - end - deprecated_constants[class_name.to_sym] = resource_subclass + end def self.deprecated_constants @@ -1579,7 +1743,7 @@ class Chef def self.remove_canonical_dsl if @resource_name - remaining = Chef.resource_priority_map.delete_canonical(@resource_name, self) + remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self) if !remaining Chef::DSL::Resources.remove_resource_dsl(@resource_name) end diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb index 2fcf183375..2877f61eb4 100644 --- a/lib/chef/resource/dsc_script.rb +++ b/lib/chef/resource/dsc_script.rb @@ -22,7 +22,7 @@ class Chef class Resource class DscScript < Chef::Resource - provides :dsc_script, platform: "windows" + provides :dsc_script, os: "windows" default_action :run diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb index 8d720dd411..2bf8e1dba8 100644 --- a/lib/chef/resource/ips_package.rb +++ b/lib/chef/resource/ips_package.rb @@ -23,6 +23,7 @@ class Chef class Resource class IpsPackage < ::Chef::Resource::Package + provides :package, os: "solaris2" provides :ips_package, os: "solaris2" allowed_actions :install, :remove, :upgrade diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb index 79986d127f..a5da0ba329 100644 --- a/lib/chef/resource/mount.rb +++ b/lib/chef/resource/mount.rb @@ -174,6 +174,14 @@ class Chef ) end + private + + # Used by the AIX provider to set fstype to nil. + # TODO use property to make nil a valid value for fstype + def clear_fstype + @fstype = nil + end + end end end diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb index f91fdb37e0..9ae8813d69 100644 --- a/lib/chef/resource/openbsd_package.rb +++ b/lib/chef/resource/openbsd_package.rb @@ -29,17 +29,6 @@ class Chef include Chef::Mixin::ShellOut provides :package, os: "openbsd" - - def after_created - assign_provider - end - - private - - def assign_provider - @provider = Chef::Provider::Package::Openbsd - end - end end end diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb index 2dc72d5c47..a98fb8b4fa 100644 --- a/lib/chef/resource/solaris_package.rb +++ b/lib/chef/resource/solaris_package.rb @@ -24,10 +24,7 @@ class Chef class Resource class SolarisPackage < Chef::Resource::Package provides :package, os: "solaris2", platform_family: "nexentacore" - provides :package, os: "solaris2", platform_family: "solaris2" do |node| - # on >= Solaris 11 we default to IPS packages instead - node[:platform_version].to_f <= 5.10 - end + provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10" end end end diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index 9df627beb2..47b3df18af 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -105,52 +105,80 @@ class Chef # # Whether this DSL is provided by the given resource_class. # + # Does NOT call provides? on the resource (it is assumed this is being + # called *from* provides?). + # # @api private def provided_by?(resource_class) - !prioritized_handlers.include?(resource_class) + potential_handlers.include?(resource_class) + end + + # + # Whether the given handler attempts to provide the resource class at all. + # + # @api private + def self.includes_handler?(resource_name, resource_class) + handler_map.list(nil, resource_name).include?(resource_class) end protected + def self.priority_map + Chef.resource_priority_map + end + + def self.handler_map + Chef.resource_handler_map + end + def priority_map - Chef::Platform::ResourcePriorityMap.instance + Chef.resource_priority_map + end + + def handler_map + Chef.resource_handler_map + end + + # @api private + def potential_handlers + handler_map.list(node, resource_name, canonical: canonical).uniq + end + + def enabled_handlers + potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) } end def prioritized_handlers - @prioritized_handlers ||= - priority_map.list_handlers(node, resource_name, canonical: canonical) + @prioritized_handlers ||= begin + enabled_handlers = self.enabled_handlers + + prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1) + prioritized &= enabled_handlers # Filter the priority map by the actual enabled handlers + prioritized |= enabled_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set) + prioritized + end + end + + def overrode_provides?(handler) + handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner end module Deprecated # return a deterministically sorted list of Chef::Resource subclasses - # @deprecated Now prioritized_handlers does its own work (more efficiently) def resources Chef::Resource.sorted_descendants end - # A list of all handlers - # @deprecated Now prioritized_handlers does its own work def enabled_handlers - Chef::Log.deprecation("enabled_handlers is deprecated. If you are implementing a ResourceResolver, use provided_handlers. If you are not, use Chef::ResourceResolver.list(#{resource_name.inspect}, node: <node>)") - resources.select { |klass| klass.provides?(node, resource_name) } - end - - protected - - # A list of all handlers for the given DSL. If there are no handlers in - # the map, we still check all descendants of Chef::Resource for backwards - # compatibility purposes. - def prioritized_handlers - @prioritized_handlers ||= super || - resources.select do |klass| - # Don't bother calling provides? unless it's overridden. We already - # know prioritized_handlers - if klass.method(:provides?).owner != Chef::Resource && klass.provides?(node, resource_name) - Chef::Log.deprecation("Resources #{provided.join(", ")} are marked as providing DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") - Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") - true - end + handlers = super + if handlers.empty? + handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) } + handlers.each do |handler| + Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") + Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end + end + handlers end end prepend Deprecated diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index f55d1740b1..b1113f594e 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -532,17 +532,24 @@ ERROR_MESSAGE ### # These need to be settable so deploy can run a resource_collection # independent of any cookbooks via +recipe_eval+ + def resource_collection=(value) + Chef::Log.deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + @resource_collection = value + end def audits=(value) Chef::Log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + @audits = value end def immediate_notification_collection=(value) Chef::Log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + @immediate_notification_collection = value end def delayed_notification_collection=(value) Chef::Log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + @delayed_notification_collection = value end end prepend Deprecated diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb index 7cce6fa48c..2824f08f31 100644 --- a/lib/chef/run_list/versioned_recipe_list.rb +++ b/lib/chef/run_list/versioned_recipe_list.rb @@ -70,15 +70,16 @@ class Chef # @return [Array] Array of strings with fully-qualified recipe names def with_fully_qualified_names_and_version_constraints self.map do |recipe_name| - ret = if recipe_name.include?('::') + qualified_recipe = if recipe_name.include?('::') recipe_name else "#{recipe_name}::default" end - if @versions[recipe_name] - ret << "@#{@versions[recipe_name]}" - end - ret + + version = @versions[recipe_name] + qualified_recipe = "#{qualified_recipe}@#{version}" if version + + qualified_recipe end end end diff --git a/lib/chef/user.rb b/lib/chef/user.rb index 717deb63c3..31ebeda86f 100644 --- a/lib/chef/user.rb +++ b/lib/chef/user.rb @@ -21,85 +21,45 @@ require 'chef/mixin/from_file' require 'chef/mash' require 'chef/json_compat' require 'chef/search/query' -require 'chef/mixin/api_version_request_handling' -require 'chef/exceptions' require 'chef/server_api' -# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends) +# TODO +# DEPRECATION NOTE +# This class will be replaced by Chef::UserV1 in Chef 13. It is the code to support the User object +# corrosponding to the Open Source Chef Server 11 and only still exists to support +# users still on OSC 11. # -# In general, Chef::User is no longer expected to support Open Source Chef 11 Server requests. -# The object that handles those requests has been moved to the Chef::OscUser namespace. +# Chef::UserV1 now supports Chef Server 12 and will be moved to this namespace in Chef 13. # -# Exception: self.list is backwards compatible with OSC 11 +# New development should occur in Chef::UserV1. +# This file and corrosponding osc_user knife files +# should be removed once client support for Open Source Chef Server 11 expires. class Chef class User include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - include Chef::Mixin::ApiVersionRequestHandling - - SUPPORTED_API_VERSIONS = [0,1] def initialize - @username = nil - @display_name = nil - @first_name = nil - @middle_name = nil - @last_name = nil - @email = nil - @password = nil + @name = '' @public_key = nil @private_key = nil - @create_key = nil @password = nil + @admin = false end - def chef_root_rest_v0 - @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"}) - end - - def chef_root_rest_v1 - @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"}) + def chef_rest_v0 + @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}) end - def username(arg=nil) - set_or_return(:username, arg, + def name(arg=nil) + set_or_return(:name, arg, :regex => /^[a-z0-9\-_]+$/) end - def display_name(arg=nil) - set_or_return(:display_name, - arg, :kind_of => String) - end - - def first_name(arg=nil) - set_or_return(:first_name, - arg, :kind_of => String) - end - - def middle_name(arg=nil) - set_or_return(:middle_name, - arg, :kind_of => String) - end - - def last_name(arg=nil) - set_or_return(:last_name, - arg, :kind_of => String) - end - - def email(arg=nil) - set_or_return(:email, - arg, :kind_of => String) - end - - def password(arg=nil) - set_or_return(:password, - arg, :kind_of => String) - end - - def create_key(arg=nil) - set_or_return(:create_key, arg, - :kind_of => [TrueClass, FalseClass]) + def admin(arg=nil) + set_or_return(:admin, + arg, :kind_of => [TrueClass, FalseClass]) end def public_key(arg=nil) @@ -119,17 +79,12 @@ class Chef def to_hash result = { - "username" => @username + "name" => @name, + "public_key" => @public_key, + "admin" => @admin } - result["display_name"] = @display_name unless @display_name.nil? - result["first_name"] = @first_name unless @first_name.nil? - result["middle_name"] = @middle_name unless @middle_name.nil? - result["last_name"] = @last_name unless @last_name.nil? - result["email"] = @email unless @email.nil? - result["password"] = @password unless @password.nil? - result["public_key"] = @public_key unless @public_key.nil? - result["private_key"] = @private_key unless @private_key.nil? - result["create_key"] = @create_key unless @create_key.nil? + result["private_key"] = @private_key if @private_key + result["password"] = @password if @password result end @@ -138,86 +93,21 @@ class Chef end def destroy - # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) - Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}") + chef_rest_v0.delete("users/#{@name}") end def create - # try v1, fail back to v0 if v1 not supported - begin - payload = { - :username => @username, - :display_name => @display_name, - :first_name => @first_name, - :last_name => @last_name, - :email => @email, - :password => @password - } - payload[:public_key] = @public_key unless @public_key.nil? - payload[:create_key] = @create_key unless @create_key.nil? - payload[:middle_name] = @middle_name unless @middle_name.nil? - raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil? - new_user = chef_root_rest_v1.post("users", payload) - - # get the private_key out of the chef_key hash if it exists - if new_user['chef_key'] - if new_user['chef_key']['private_key'] - new_user['private_key'] = new_user['chef_key']['private_key'] - end - new_user['public_key'] = new_user['chef_key']['public_key'] - new_user.delete('chef_key') - end - rescue Net::HTTPServerException => e - # rescue API V0 if 406 and the server supports V0 - supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) - raise e unless supported_versions && supported_versions.include?(0) - payload = { - :username => @username, - :display_name => @display_name, - :first_name => @first_name, - :last_name => @last_name, - :email => @email, - :password => @password - } - payload[:middle_name] = @middle_name unless @middle_name.nil? - payload[:public_key] = @public_key unless @public_key.nil? - # under API V0, the server will create a key pair if public_key isn't passed - new_user = chef_root_rest_v0.post("users", payload) - end - + payload = {:name => self.name, :admin => self.admin, :password => self.password } + payload[:public_key] = public_key if public_key + new_user = chef_rest_v0.post("users", payload) Chef::User.from_hash(self.to_hash.merge(new_user)) end def update(new_key=false) - begin - payload = {:username => username} - payload[:display_name] = display_name unless display_name.nil? - payload[:first_name] = first_name unless first_name.nil? - payload[:middle_name] = middle_name unless middle_name.nil? - payload[:last_name] = last_name unless last_name.nil? - payload[:email] = email unless email.nil? - payload[:password] = password unless password.nil? - - # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned - payload[:public_key] = public_key unless public_key.nil? - payload[:private_key] = new_key if new_key - - updated_user = chef_root_rest_v1.put("users/#{username}", payload) - rescue Net::HTTPServerException => e - if e.response.code == "400" - # if a 400 is returned but the error message matches the error related to private / public key fields, try V0 - # else, raise the 400 - error = Chef::JSONCompat.from_json(e.response.body)["error"].first - error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error) - if error_match.nil? - raise e - end - else # for other types of errors, test for API versioning errors right away - supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) - raise e unless supported_versions && supported_versions.include?(0) - end - updated_user = chef_root_rest_v0.put("users/#{username}", payload) - end + payload = {:name => name, :admin => admin} + payload[:private_key] = new_key if new_key + payload[:password] = password if password + updated_user = chef_rest_v0.put("users/#{name}", payload) Chef::User.from_hash(self.to_hash.merge(updated_user)) end @@ -233,47 +123,30 @@ class Chef end end - # Note: remove after API v0 no longer supported by client (and knife command). def reregister - begin - payload = self.to_hash.merge({"private_key" => true}) - reregistered_self = chef_root_rest_v0.put("users/#{username}", payload) - private_key(reregistered_self["private_key"]) - # only V0 supported for reregister - rescue Net::HTTPServerException => e - # if there was a 406 related to versioning, give error explaining that - # only API version 0 is supported for reregister command - if e.response.code == "406" && e.response["x-ops-server-api-version"] - version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"]) - min_version = version_header["min_version"] - max_version = version_header["max_version"] - error_msg = reregister_only_v0_supported_error_msg(max_version, min_version) - raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg) - else - raise e - end - end + reregistered_self = chef_rest_v0.put("users/#{name}", { :name => name, :admin => admin, :private_key => true }) + private_key(reregistered_self["private_key"]) self end def to_s - "user[#{@username}]" + "user[#{@name}]" + end + + def inspect + "Chef::User name:'#{name}' admin:'#{admin.inspect}'" + + "public_key:'#{public_key}' private_key:#{private_key}" end # Class Methods def self.from_hash(user_hash) user = Chef::User.new - user.username user_hash['username'] - user.display_name user_hash['display_name'] if user_hash.key?('display_name') - user.first_name user_hash['first_name'] if user_hash.key?('first_name') - user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name') - user.last_name user_hash['last_name'] if user_hash.key?('last_name') - user.email user_hash['email'] if user_hash.key?('email') - user.password user_hash['password'] if user_hash.key?('password') - user.public_key user_hash['public_key'] if user_hash.key?('public_key') + user.name user_hash['name'] user.private_key user_hash['private_key'] if user_hash.key?('private_key') - user.create_key user_hash['create_key'] if user_hash.key?('create_key') + user.password user_hash['password'] if user_hash.key?('password') + user.public_key user_hash['public_key'] + user.admin user_hash['admin'] user end @@ -286,19 +159,12 @@ class Chef end def self.list(inflate=false) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users') + response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get('users') users = if response.is_a?(Array) - # EC 11 / CS 12 V0, V1 - # GET /organizations/<org>/users - transform_list_response(response) - else - # OSC 11 - # GET /users - # EC 11 / CS 12 V0, V1 - # GET /users - response # OSC - end - + transform_ohc_list_response(response) # OHC/OPC + else + response # OSC + end if inflate users.inject({}) do |user_map, (name, _url)| user_map[name] = Chef::User.load(name) @@ -309,9 +175,8 @@ class Chef end end - def self.load(username) - # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) - response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}") + def self.load(name) + response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get("users/#{name}") Chef::User.from_hash(response) end @@ -319,7 +184,7 @@ class Chef # [ { "user" => { "username" => USERNAME }}, ...] # into the form # { "USERNAME" => "URI" } - def self.transform_list_response(response) + def self.transform_ohc_list_response(response) new_response = Hash.new response.each do |u| name = u['user']['username'] @@ -328,7 +193,6 @@ class Chef new_response end - private_class_method :transform_list_response - + private_class_method :transform_ohc_list_response end end diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb new file mode 100644 index 0000000000..31cb0576a2 --- /dev/null +++ b/lib/chef/user_v1.rb @@ -0,0 +1,335 @@ +# +# Author:: Steven Danna (steve@opscode.com) +# Copyright:: Copyright 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +require 'chef/config' +require 'chef/mixin/params_validate' +require 'chef/mixin/from_file' +require 'chef/mash' +require 'chef/json_compat' +require 'chef/search/query' +require 'chef/mixin/api_version_request_handling' +require 'chef/exceptions' +require 'chef/server_api' + +# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends) +# +# In general, Chef::UserV1 is no longer expected to support Open Source Chef 11 Server requests. +# The object that handles those requests remain in the Chef::User namespace. +# This code will be moved to the Chef::User namespace as of Chef 13. +# +# Exception: self.list is backwards compatible with OSC 11 +class Chef + class UserV1 + + include Chef::Mixin::FromFile + include Chef::Mixin::ParamsValidate + include Chef::Mixin::ApiVersionRequestHandling + + SUPPORTED_API_VERSIONS = [0,1] + + def initialize + @username = nil + @display_name = nil + @first_name = nil + @middle_name = nil + @last_name = nil + @email = nil + @password = nil + @public_key = nil + @private_key = nil + @create_key = nil + @password = nil + end + + def chef_root_rest_v0 + @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"}) + end + + def chef_root_rest_v1 + @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"}) + end + + def username(arg=nil) + set_or_return(:username, arg, + :regex => /^[a-z0-9\-_]+$/) + end + + def display_name(arg=nil) + set_or_return(:display_name, + arg, :kind_of => String) + end + + def first_name(arg=nil) + set_or_return(:first_name, + arg, :kind_of => String) + end + + def middle_name(arg=nil) + set_or_return(:middle_name, + arg, :kind_of => String) + end + + def last_name(arg=nil) + set_or_return(:last_name, + arg, :kind_of => String) + end + + def email(arg=nil) + set_or_return(:email, + arg, :kind_of => String) + end + + def password(arg=nil) + set_or_return(:password, + arg, :kind_of => String) + end + + def create_key(arg=nil) + set_or_return(:create_key, arg, + :kind_of => [TrueClass, FalseClass]) + end + + def public_key(arg=nil) + set_or_return(:public_key, + arg, :kind_of => String) + end + + def private_key(arg=nil) + set_or_return(:private_key, + arg, :kind_of => String) + end + + def password(arg=nil) + set_or_return(:password, + arg, :kind_of => String) + end + + def to_hash + result = { + "username" => @username + } + result["display_name"] = @display_name unless @display_name.nil? + result["first_name"] = @first_name unless @first_name.nil? + result["middle_name"] = @middle_name unless @middle_name.nil? + result["last_name"] = @last_name unless @last_name.nil? + result["email"] = @email unless @email.nil? + result["password"] = @password unless @password.nil? + result["public_key"] = @public_key unless @public_key.nil? + result["private_key"] = @private_key unless @private_key.nil? + result["create_key"] = @create_key unless @create_key.nil? + result + end + + def to_json(*a) + Chef::JSONCompat.to_json(to_hash, *a) + end + + def destroy + # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) + Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}") + end + + def create + # try v1, fail back to v0 if v1 not supported + begin + payload = { + :username => @username, + :display_name => @display_name, + :first_name => @first_name, + :last_name => @last_name, + :email => @email, + :password => @password + } + payload[:public_key] = @public_key unless @public_key.nil? + payload[:create_key] = @create_key unless @create_key.nil? + payload[:middle_name] = @middle_name unless @middle_name.nil? + raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil? + new_user = chef_root_rest_v1.post("users", payload) + + # get the private_key out of the chef_key hash if it exists + if new_user['chef_key'] + if new_user['chef_key']['private_key'] + new_user['private_key'] = new_user['chef_key']['private_key'] + end + new_user['public_key'] = new_user['chef_key']['public_key'] + new_user.delete('chef_key') + end + rescue Net::HTTPServerException => e + # rescue API V0 if 406 and the server supports V0 + supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) + raise e unless supported_versions && supported_versions.include?(0) + payload = { + :username => @username, + :display_name => @display_name, + :first_name => @first_name, + :last_name => @last_name, + :email => @email, + :password => @password + } + payload[:middle_name] = @middle_name unless @middle_name.nil? + payload[:public_key] = @public_key unless @public_key.nil? + # under API V0, the server will create a key pair if public_key isn't passed + new_user = chef_root_rest_v0.post("users", payload) + end + + Chef::UserV1.from_hash(self.to_hash.merge(new_user)) + end + + def update(new_key=false) + begin + payload = {:username => username} + payload[:display_name] = display_name unless display_name.nil? + payload[:first_name] = first_name unless first_name.nil? + payload[:middle_name] = middle_name unless middle_name.nil? + payload[:last_name] = last_name unless last_name.nil? + payload[:email] = email unless email.nil? + payload[:password] = password unless password.nil? + + # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned + payload[:public_key] = public_key unless public_key.nil? + payload[:private_key] = new_key if new_key + + updated_user = chef_root_rest_v1.put("users/#{username}", payload) + rescue Net::HTTPServerException => e + if e.response.code == "400" + # if a 400 is returned but the error message matches the error related to private / public key fields, try V0 + # else, raise the 400 + error = Chef::JSONCompat.from_json(e.response.body)["error"].first + error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error) + if error_match.nil? + raise e + end + else # for other types of errors, test for API versioning errors right away + supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) + raise e unless supported_versions && supported_versions.include?(0) + end + updated_user = chef_root_rest_v0.put("users/#{username}", payload) + end + Chef::UserV1.from_hash(self.to_hash.merge(updated_user)) + end + + def save(new_key=false) + begin + create + rescue Net::HTTPServerException => e + if e.response.code == "409" + update(new_key) + else + raise e + end + end + end + + # Note: remove after API v0 no longer supported by client (and knife command). + def reregister + begin + payload = self.to_hash.merge({"private_key" => true}) + reregistered_self = chef_root_rest_v0.put("users/#{username}", payload) + private_key(reregistered_self["private_key"]) + # only V0 supported for reregister + rescue Net::HTTPServerException => e + # if there was a 406 related to versioning, give error explaining that + # only API version 0 is supported for reregister command + if e.response.code == "406" && e.response["x-ops-server-api-version"] + version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"]) + min_version = version_header["min_version"] + max_version = version_header["max_version"] + error_msg = reregister_only_v0_supported_error_msg(max_version, min_version) + raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg) + else + raise e + end + end + self + end + + def to_s + "user[#{@username}]" + end + + # Class Methods + + def self.from_hash(user_hash) + user = Chef::UserV1.new + user.username user_hash['username'] + user.display_name user_hash['display_name'] if user_hash.key?('display_name') + user.first_name user_hash['first_name'] if user_hash.key?('first_name') + user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name') + user.last_name user_hash['last_name'] if user_hash.key?('last_name') + user.email user_hash['email'] if user_hash.key?('email') + user.password user_hash['password'] if user_hash.key?('password') + user.public_key user_hash['public_key'] if user_hash.key?('public_key') + user.private_key user_hash['private_key'] if user_hash.key?('private_key') + user.create_key user_hash['create_key'] if user_hash.key?('create_key') + user + end + + def self.from_json(json) + Chef::UserV1.from_hash(Chef::JSONCompat.from_json(json)) + end + + class << self + alias_method :json_create, :from_json + end + + def self.list(inflate=false) + response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users') + users = if response.is_a?(Array) + # EC 11 / CS 12 V0, V1 + # GET /organizations/<org>/users + transform_list_response(response) + else + # OSC 11 + # GET /users + # EC 11 / CS 12 V0, V1 + # GET /users + response # OSC + end + + if inflate + users.inject({}) do |user_map, (name, _url)| + user_map[name] = Chef::UserV1.load(name) + user_map + end + else + users + end + end + + def self.load(username) + # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) + response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}") + Chef::UserV1.from_hash(response) + end + + # Gross. Transforms an API response in the form of: + # [ { "user" => { "username" => USERNAME }}, ...] + # into the form + # { "USERNAME" => "URI" } + def self.transform_list_response(response) + new_response = Hash.new + response.each do |u| + name = u['user']['username'] + new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}" + end + new_response + end + + private_class_method :transform_list_response + + end +end diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index 18f12d26b8..1a1aa12fad 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -203,7 +203,7 @@ class Chef key_exists!(key_path) hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| - return true if reg.any? {|val| val == value[:name] } + return true if reg.any? {|val| safely_downcase(val) == safely_downcase(value[:name]) } end return false end @@ -213,7 +213,7 @@ class Chef hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| reg.each do |val_name, val_type, val_data| - if val_name == value[:name] && + if safely_downcase(val_name) == safely_downcase(value[:name]) && val_type == get_type_from_name(value[:type]) && val_data == value[:data] return true @@ -289,6 +289,14 @@ class Chef private + + def safely_downcase(val) + if val.is_a? String + return val.downcase + end + return val + end + def node run_context && run_context.node end diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb index 2454c9cccf..8398c5d616 100644 --- a/lib/chef/workstation_config_loader.rb +++ b/lib/chef/workstation_config_loader.rb @@ -1,5 +1,5 @@ # -# Author:: Daniel DeLeo (<dan@getchef.com>) +# Author:: Claire McQuin (<claire@chef.io>) # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # @@ -16,163 +16,8 @@ # limitations under the License. # -require 'chef/config_fetcher' -require 'chef/config' -require 'chef/null_logger' -require 'chef/util/path_helper' +require 'chef-config/workstation_config_loader' class Chef - - class WorkstationConfigLoader - - # Path to a config file requested by user, (e.g., via command line option). Can be nil - attr_accessor :explicit_config_file - - # TODO: initialize this with a logger for Chef and Knife - def initialize(explicit_config_file, logger=nil) - @explicit_config_file = explicit_config_file - @config_location = nil - @logger = logger || NullLogger.new - end - - def no_config_found? - config_location.nil? - end - - def config_location - @config_location ||= (explicit_config_file || locate_local_config) - end - - def chef_config_dir - if @chef_config_dir.nil? - @chef_config_dir = false - full_path = working_directory.split(File::SEPARATOR) - (full_path.length - 1).downto(0) do |i| - candidate_directory = File.join(full_path[0..i] + [".chef" ]) - if File.exist?(candidate_directory) && File.directory?(candidate_directory) - @chef_config_dir = candidate_directory - break - end - end - end - @chef_config_dir - end - - def load - # Ignore it if there's no explicit_config_file and can't find one at a - # default path. - return false if config_location.nil? - - if explicit_config_file && !path_exists?(config_location) - raise Exceptions::ConfigurationError, "Specified config file #{config_location} does not exist" - end - - # Have to set Chef::Config.config_file b/c other config is derived from it. - Chef::Config.config_file = config_location - read_config(IO.read(config_location), config_location) - end - - # (Private API, public for test purposes) - def env - ENV - end - - # (Private API, public for test purposes) - def path_exists?(path) - Pathname.new(path).expand_path.exist? - end - - private - - def have_config?(path) - if path_exists?(path) - logger.info("Using config at #{path}") - true - else - logger.debug("Config not found at #{path}, trying next option") - false - end - end - - def locate_local_config - candidate_configs = [] - - # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine) - if env['KNIFE_HOME'] - candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb') - candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb') - end - # Look for $PWD/knife.rb - if Dir.pwd - candidate_configs << File.join(Dir.pwd, 'config.rb') - candidate_configs << File.join(Dir.pwd, 'knife.rb') - end - # Look for $UPWARD/.chef/knife.rb - if chef_config_dir - candidate_configs << File.join(chef_config_dir, 'config.rb') - candidate_configs << File.join(chef_config_dir, 'knife.rb') - end - # Look for $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 | - have_config?(candidate_config) - end - end - - def working_directory - a = if Chef::Platform.windows? - env['CD'] - else - env['PWD'] - end || Dir.pwd - - a - end - - def read_config(config_content, config_file_path) - Chef::Config.from_string(config_content, config_file_path) - rescue SignalException - raise - rescue SyntaxError => e - message = "" - message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n" - message << "#{e.class.name}: #{e.message}\n" - if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/] - line = file_line[/:([\d]+)$/, 1].to_i - message << highlight_config_error(config_file_path, line) - end - raise Exceptions::ConfigurationError, message - rescue Exception => e - message = "You have an error in your config file #{config_file_path}\n\n" - message << "#{e.class.name}: #{e.message}\n" - filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/) - filtered_trace.each {|bt_line| message << " " << bt_line << "\n" } - if !filtered_trace.empty? - line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1] - message << highlight_config_error(config_file_path, line_nr.to_i) - end - raise Exceptions::ConfigurationError, message - end - - - def highlight_config_error(file, line) - config_file_lines = [] - IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"} - if line == 1 - lines = config_file_lines[0..3] - else - lines = config_file_lines[Range.new(line - 2, line)] - end - "Relevant file content:\n" + lines.join("\n") + "\n" - end - - def logger - @logger - end - - end + WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader end diff --git a/spec/data/trusted_certs/opscode.pem b/spec/data/trusted_certs/opscode.pem index 37a3dd1ef2..e421a4e6e9 100644 --- a/spec/data/trusted_certs/opscode.pem +++ b/spec/data/trusted_certs/opscode.pem @@ -1,60 +1,57 @@ -----BEGIN CERTIFICATE----- -MIIFrDCCBJSgAwIBAgIQB1O/fCb6cEytJ4BP3HTbCTANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSIwIAYDVQQDExlE -aWdpQ2VydCBTZWN1cmUgU2VydmVyIENBMB4XDTE0MDYxMDAwMDAwMFoXDTE1MDcw -MTEyMDAwMFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO -BgNVBAcTB1NlYXR0bGUxGzAZBgNVBAoTEkNoZWYgU29mdHdhcmUsIEluYzEWMBQG -A1UEAwwNKi5vcHNjb2RlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAMm+rf2RcPGBlZoM+hI4BxlaHbdRg1GZJ/T46UWFOBnZFVP++TX/pyjDsvns -xymcQywtoN/26+UIys6oWX1um9ikEokvf67LdsUeemQGFHFky8X1Ka2hVtKnxBhi -XZfvyHDR4IyFWU9AwmhnqySzxqCtynUu8Gktx7JVfqbRFMZ186pDcSw8LoaqjTVG -SzO7eNH2sM3doMueAHj7ITc2wUzmfa0Pdh+K8UoCn/HopU5LzycziJVPYvUkLT2m -YCV7VWRc+kObZseHhZAbyaDk3RgPQ/eRMhytAgbruBHWDqNesNw+ZA70w856Oj2Y -geO7JF+5V6WvkywrF8vydaoM2l8CAwEAAaOCAm8wggJrMB8GA1UdIwQYMBaAFJBx -2zfrc8jv3NUeErY0uitaoKaSMB0GA1UdDgQWBBQK5zjZwbcmcMNLnI2h1ioAldEV -ujCBygYDVR0RBIHCMIG/gg0qLm9wc2NvZGUuY29tghBjb3JwLm9wc2NvZGUuY29t -ghIqLmNvcnAub3BzY29kZS5jb22CDyoubGVhcm5jaGVmLmNvbYISKi5jb3JwLmdl -dGNoZWYuY29tgg0qLmdldGNoZWYuY29tggwqLm9wc2NvZGUudXOCC2dldGNoZWYu -Y29tggtvcHNjb2RlLmNvbYIRYXBpLmJlcmtzaGVsZi5jb22CDWxlYXJuY2hlZi5j -b22CCm9wc2NvZGUudXMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF -BwMBBggrBgEFBQcDAjBhBgNVHR8EWjBYMCqgKKAmhiRodHRwOi8vY3JsMy5kaWdp -Y2VydC5jb20vc3NjYS1nNi5jcmwwKqAooCaGJGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0 -LmNvbS9zc2NhLWc2LmNybDBCBgNVHSAEOzA5MDcGCWCGSAGG/WwBATAqMCgGCCsG -AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMHgGCCsGAQUFBwEB -BGwwajAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEIGCCsG -AQUFBzAChjZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTZWN1 -cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQUFAAOCAQEA -kgBpJ2t+St7SmWfeNU9EWAhy0NuUnRIi1jnqXdapfPmS6V/M0i2wP/p+crMty78e -+3ieuF5s0GJBLs85Hikcl3SlrrbIBJxozov1TY6zeOi6+TCsdXer6t6iQKz36zno -+k+T6lnMCyo9+pk1PhcAWyfo1Fz4xVOBVec/71VovFkkGD2//KB+sbDs+yh21N9M -ReO7duj16rQSctfO9R2h65djBNlgz6hXY2nlw8/x3uFfZobXOxDrTcH6Z8HIslkE -MiTXGix6zdqJaFRCWi+prnAztWs+jEy+v95VSEHPj3xpwZ9WjsxQN0kFA2EX61v/ -kGunmyhehGjblQRt7bpyiA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh +MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEgxCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFNlY3Vy -ZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V+Qh -qdWbYDd+jqFhf4HiGsJ1ZNmRUAvkNkQkbjDSm3on+sJqrmpwCTi5IArIZRBKiKwx -8tyS8mOhXYBjWYCSIxzm73ZKUDXJ2HE4ue3w5kKu0zgmeTD5IpTG26Y/QXiQ2N5c -fml9+JAVOtChoL76srIZodgr0c6/a91Jq6OS/rWryME+7gEA2KlEuEJziMNh9atK -gygK0tRJ+mqxzd9XLJTl4sqDX7e6YlwvaKXwwLn9K9HpH9gaYhW9/z2m98vv5ttl -LyU47PvmIGZYljQZ0hXOIdMkzNkUb9j+Vcfnb7YPGoxJvinyulqagSY3JG/XSBJs -Lln1nBi72fZo4t9FAgMBAAGjggFaMIIBVjASBgNVHRMBAf8ECDAGAQH/AgEAMA4G -A1UdDwEB/wQEAwIBhjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6 -Ly9vY3NwLmRpZ2ljZXJ0LmNvbTB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3Js -My5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMDegNaAzhjFo -dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3Js -MD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k -aWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSQcds363PI79zVHhK2NLorWqCmkjAf -BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTANBgkqhkiG9w0BAQUFAAOC -AQEAMM7RlVEArgYLoQ4CwBestn+PIPZAdXQczHixpE/q9NDEnaLegQcmH0CIUfAf -z7dMQJnQ9DxxmHOIlywZ126Ej6QfnFog41FcsMWemWpPyGn3EP9OrRnZyVizM64M -2ZYpnnGycGOjtpkWQh1l8/egHn3F1GUUsmKE1GxcCAzYbJMrtHZZitF//wPYwl24 -LyLWOPD2nGt9RuuZdPfrSg6ppgTre87wXGuYMVqYQOtpxAX0IKjKCDplbDgV9Vws -slXkLGtB8L5cRspKKaBIXiDSRf8F3jSvcEuBOeLKB1d8tjHcISnivpcOd5AUUUDh -v+PMGxmcJcqnBrJT3yOyzxIZow== +QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg +U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83 +nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd +KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f +/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX +kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0 +/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C +AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6 +Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1 +oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD +QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v +d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh +xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB +CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl +5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA +8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC +2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit +c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0 +j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E +aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN +MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j +MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e +2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA +tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB +ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL +TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM +ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP +gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d +pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF +oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g +K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At +oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG +A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 +LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl +cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw +DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ +zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q +5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl +CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD +eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt +fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP +ng== -----END CERTIFICATE----- diff --git a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb index b3c2333e9a..2b582feb05 100644 --- a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb +++ b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb @@ -43,7 +43,7 @@ describe Chef::Resource::WhyrunSafeRubyBlock do end it "updates the evil laugh, even in why-run mode" do - new_resource.run_action(new_resource.action) + Array(new_resource.action).each {|action| new_resource.run_action(action) } expect($evil_global_evil_laugh).to eq(:mwahahaha) expect(new_resource).to be_updated end diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb index 485e98f247..a0e2665de5 100644 --- a/spec/functional/rebooter_spec.rb +++ b/spec/functional/rebooter_spec.rb @@ -43,7 +43,7 @@ describe Chef::Platform::Rebooter do let(:expected) do { - :windows => 'shutdown /r /t 5 /c "rebooter spec test"', + :windows => 'shutdown /r /t 300 /c "rebooter spec test"', :linux => 'shutdown -r +5 "rebooter spec test"' } end diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/package_spec.rb index 5c17ca0107..8d37b072e8 100644 --- a/spec/functional/resource/package_spec.rb +++ b/spec/functional/resource/package_spec.rb @@ -386,5 +386,3 @@ describe Chef::Resource::Package, metadata do end end - - diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_script_spec.rb index 17ae8cbd2a..a8e51f4d9d 100644 --- a/spec/functional/resource/powershell_spec.rb +++ b/spec/functional/resource/powershell_script_spec.rb @@ -125,16 +125,16 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do expect { resource.run_action(:run) }.not_to raise_error end - it "raises an error if the script is not syntactically correct and returns is not set to 1" do + it "raises a Mixlib::ShellOut::ShellCommandFailed error if the script is not syntactically correct" do resource.code('if({)') resource.returns(0) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end - it "returns 1 if the script provided to the code attribute is not syntactically correct" do + it "raises an error if the script is not syntactically correct even if returns is set to 1 which is what powershell.exe returns for syntactically invalid scripts" do resource.code('if({)') resource.returns(1) - expect { resource.run_action(:run) }.not_to raise_error + expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end # This somewhat ambiguous case, two failures of different types, diff --git a/spec/functional/win32/registry_helper_spec.rb b/spec/functional/win32/registry_helper_spec.rb index 7b070e6fe1..9ef6fd006f 100644 --- a/spec/functional/win32/registry_helper_spec.rb +++ b/spec/functional/win32/registry_helper_spec.rb @@ -130,6 +130,9 @@ describe 'Chef::Win32::Registry', :windows_only do it "returns true if the value exists" do expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true) end + it "returns true if the value exists with a case mismatch on the value name" do + expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true) + end it "returns false if the value does not exist" do expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})).to eq(false) end @@ -145,6 +148,9 @@ describe 'Chef::Win32::Registry', :windows_only do it "returns true if the value exists" do expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true) end + it "returns true if the value exists with a case mismatch on the value name" do + expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true) + end it "throws an exception if the value does not exist" do expect {@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})}.to raise_error(Chef::Exceptions::Win32RegValueMissing) end @@ -160,6 +166,9 @@ describe 'Chef::Win32::Registry', :windows_only do it "returns true if all the data matches" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) end + it "returns true if all the data matches with a case mismatch on the data name" do + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) + end it "returns false if the name does not exist" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(false) end @@ -181,6 +190,9 @@ describe 'Chef::Win32::Registry', :windows_only do it "returns true if all the data matches" do expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) end + it "returns true if all the data matches with a case mismatch on the data name" do + expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) + end it "throws an exception if the name does not exist" do expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) end diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb index d2474deace..a1ce36146f 100644 --- a/spec/functional/win32/service_manager_spec.rb +++ b/spec/functional/win32/service_manager_spec.rb @@ -33,7 +33,7 @@ end # directories. # -describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only do +describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only, :appveyor_only do include_context "using Win32::Service" @@ -43,7 +43,7 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind end it "throws an error with required missing options" do - test_service.each do |key,value| + [:service_name, :service_display_name, :service_description, :service_file_path].each do |key| service_def = test_service.dup service_def.delete(key) diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb index 874b33901f..908657e5f7 100644 --- a/spec/integration/knife/chef_repo_path_spec.rb +++ b/spec/integration/knife/chef_repo_path_spec.rb @@ -24,6 +24,8 @@ describe 'chef_repo_path tests', :workstation do include IntegrationSupport include KnifeSupport + let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ } + # TODO alternate repo_path / *_path context 'alternate *_path' do when_the_repository 'has clients and clients2, cookbooks and cookbooks2, etc.' do @@ -109,14 +111,14 @@ EOM context 'when cwd is at the top level' do before { cwd '.' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end context 'when cwd is inside the data_bags directory' do before { cwd 'data_bags' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end @@ -192,14 +194,14 @@ EOM context 'when cwd is inside the data_bags directory' do before { cwd 'data_bags' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end context 'when cwd is inside chef_repo2' do before { cwd 'chef_repo2' } it 'knife list -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end @@ -225,14 +227,14 @@ EOM context 'when cwd is at the top level' do before { cwd '.' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end context 'when cwd is inside the data_bags directory' do before { cwd 'data_bags' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end @@ -445,7 +447,7 @@ EOM context 'when cwd is at the top level' do before { cwd '.' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end @@ -621,14 +623,14 @@ EOM context 'when cwd is at the top level' do before { cwd '.' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end context 'when cwd is inside the data_bags directory' do before { cwd 'data_bags' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end @@ -782,7 +784,7 @@ EOM context 'when cwd is at the top level' do before { cwd '.' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end @@ -823,7 +825,7 @@ EOM context 'when cwd is inside chef_repo2/data_bags' do before { cwd 'chef_repo2/data_bags' } it 'knife list --local -Rfp fails' do - knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n") + knife('list --local -Rfp').should_fail(error_rel_path_outside_repo) end end end diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb index 5426dce080..a4de6ae542 100644 --- a/spec/integration/recipes/recipe_dsl_spec.rb +++ b/spec/integration/recipes/recipe_dsl_spec.rb @@ -11,7 +11,7 @@ describe "Recipe DSL methods" do before(:all) { Namer.current_index = 1 } before { Namer.current_index += 1 } - context "With resource 'base_thingy' declared as BaseThingy" do + context "with resource 'base_thingy' declared as BaseThingy" do before(:context) { class BaseThingy < Chef::Resource @@ -19,6 +19,7 @@ describe "Recipe DSL methods" do default_action :create class<<self + attr_accessor :created_name attr_accessor :created_resource attr_accessor :created_provider end @@ -30,6 +31,7 @@ describe "Recipe DSL methods" do def load_current_resource end def action_create + BaseThingy.created_name = new_resource.name BaseThingy.created_resource = new_resource.class BaseThingy.created_provider = self.class end @@ -47,12 +49,38 @@ describe "Recipe DSL methods" do BaseThingy.created_provider = nil end + it "creates base_thingy when you call base_thingy in a recipe" do + recipe = converge { + base_thingy 'blah' do; end + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_name).to eq 'blah' + expect(BaseThingy.created_resource).to eq BaseThingy + end + + it "errors out when you call base_thingy do ... end in a recipe" do + expect_converge { + base_thingy do; end + }.to raise_error(ArgumentError, 'You must supply a name when declaring a base_thingy resource') + end + + it "emits a warning when you call base_thingy 'foo', 'bar' do ... end in a recipe" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + recipe = converge { + base_thingy 'foo', 'bar' do + end + } + expect(recipe.logged_warnings).to match(/Cannot create resource base_thingy with more than one argument. All arguments except the name \("foo"\) will be ignored. This will cause an error in Chef 13. Arguments: \["foo", "bar"\]/) + expect(BaseThingy.created_name).to eq 'foo' + expect(BaseThingy.created_resource).to eq BaseThingy + end + context "Deprecated automatic resource DSL" do before do Chef::Config[:treat_deprecation_warnings_as_errors] = false end - context "With a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do + context "with a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do before(:context) { class Chef::Resource::BackcompatThingy < Chef::Resource @@ -97,7 +125,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named RecipeDSLSpecNamespace::Bar::BarThingy" do + context "with a resource named RecipeDSLSpecNamespace::Bar::BarThingy" do before(:context) { class RecipeDSLSpecNamespace::Bar::BarThingy < BaseThingy @@ -112,7 +140,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named Chef::Resource::NoNameThingy with resource_name nil" do + context "with a resource named Chef::Resource::NoNameThingy with resource_name nil" do before(:context) { class Chef::Resource::NoNameThingy < BaseThingy @@ -128,7 +156,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named AnotherNoNameThingy with resource_name :another_thingy_name" do + context "with a resource named AnotherNoNameThingy with resource_name :another_thingy_name" do before(:context) { class AnotherNoNameThingy < BaseThingy @@ -152,7 +180,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named AnotherNoNameThingy2 with resource_name :another_thingy_name2; resource_name :another_thingy_name3" do + context "with a resource named AnotherNoNameThingy2 with resource_name :another_thingy_name2; resource_name :another_thingy_name3" do before(:context) { class AnotherNoNameThingy2 < BaseThingy @@ -184,7 +212,7 @@ describe "Recipe DSL methods" do end context "provides overriding resource_name" do - context "With a resource named AnotherNoNameThingy3 with provides :another_no_name_thingy3, os: 'blarghle'" do + context "with a resource named AnotherNoNameThingy3 with provides :another_no_name_thingy3, os: 'blarghle'" do before(:context) { class AnotherNoNameThingy3 < BaseThingy @@ -213,7 +241,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named AnotherNoNameThingy4 with two provides" do + context "with a resource named AnotherNoNameThingy4 with two provides" do before(:context) { class AnotherNoNameThingy4 < BaseThingy @@ -253,7 +281,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named AnotherNoNameThingy5, a different resource_name, and a provides with the original resource_name" do + context "with a resource named AnotherNoNameThingy5, a different resource_name, and a provides with the original resource_name" do before(:context) { class AnotherNoNameThingy5 < BaseThingy @@ -290,7 +318,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named AnotherNoNameThingy6, a provides with the original resource name, and a different resource_name" do + context "with a resource named AnotherNoNameThingy6, a provides with the original resource name, and a different resource_name" do before(:context) { class AnotherNoNameThingy6 < BaseThingy @@ -327,7 +355,7 @@ describe "Recipe DSL methods" do end end - context "With a resource named AnotherNoNameThingy7, a new resource_name, and provides with that new resource name" do + context "with a resource named AnotherNoNameThingy7, a new resource_name, and provides with that new resource name" do before(:context) { class AnotherNoNameThingy7 < BaseThingy @@ -365,7 +393,7 @@ describe "Recipe DSL methods" do end # opposite order from the previous test (provides, then resource_name) - context "With a resource named AnotherNoNameThingy8, a provides with a new resource name, and resource_name with that new resource name" do + context "with a resource named AnotherNoNameThingy8, a provides with a new resource name, and resource_name with that new resource name" do before(:context) { class AnotherNoNameThingy8 < BaseThingy @@ -401,119 +429,6 @@ describe "Recipe DSL methods" do }.to raise_error(NoMethodError) end end - - context "With a resource TwoClassesOneDsl" do - let(:class_name) { "TwoClassesOneDsl#{Namer.current_index}" } - let(:dsl_method) { :"two_classes_one_dsl#{Namer.current_index}" } - - before { - eval <<-EOM, nil, __FILE__, __LINE__+1 - class #{class_name} < BaseThingy - resource_name #{dsl_method.inspect} - end - EOM - } - context "and resource BlahModule::TwoClassesOneDsl" do - before { - eval <<-EOM, nil, __FILE__, __LINE__+1 - module BlahModule - class #{class_name} < BaseThingy - resource_name #{dsl_method.inspect} - end - end - EOM - } - it "two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl (alphabetical)" do - dsl_method = self.dsl_method - recipe = converge { - instance_eval("#{dsl_method} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq eval("BlahModule::#{class_name}") - end - it "resource_matching_short_name returns BlahModule::TwoClassesOneDsl" do - expect(Chef::Resource.resource_matching_short_name(dsl_method)).to eq eval("BlahModule::#{class_name}") - end - end - context "and resource BlahModule::TwoClassesOneDsl with resource_name nil" do - before { - eval <<-EOM, nil, __FILE__, __LINE__+1 - module BlahModule - class BlahModule::#{class_name} < BaseThingy - resource_name nil - end - end - EOM - } - it "two_classes_one_dsl resolves to ::TwoClassesOneDsl" do - dsl_method = self.dsl_method - recipe = converge { - instance_eval("#{dsl_method} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq eval("::#{class_name}") - end - it "resource_matching_short_name returns ::TwoClassesOneDsl" do - expect(Chef::Resource.resource_matching_short_name(dsl_method)).to eq eval("::#{class_name}") - end - end - context "and resource BlahModule::TwoClassesOneDsl with resource_name :argh" do - before { - eval <<-EOM, nil, __FILE__, __LINE__+1 - module BlahModule - class BlahModule::#{class_name} < BaseThingy - resource_name :argh - end - end - EOM - } - it "two_classes_one_dsl resolves to ::TwoClassesOneDsl" do - dsl_method = self.dsl_method - recipe = converge { - instance_eval("#{dsl_method} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq eval("::#{class_name}") - end - it "resource_matching_short_name returns ::TwoClassesOneDsl" do - expect(Chef::Resource.resource_matching_short_name(dsl_method)).to eq eval("::#{class_name}") - end - end - context "and resource BlahModule::TwoClassesOneDsl with provides :two_classes_one_dsl, os: 'blarghle'" do - before { - eval <<-EOM, nil, __FILE__, __LINE__+1 - module BlahModule - class BlahModule::#{class_name} < BaseThingy - resource_name #{dsl_method.inspect} - provides #{dsl_method.inspect}, os: 'blarghle' - end - end - EOM - } - - it "and os = blarghle, two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl" do - dsl_method = self.dsl_method - recipe = converge { - # this is an ugly way to test, make Cheffish expose node attrs - run_context.node.automatic[:os] = 'blarghle' - instance_eval("#{dsl_method} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq eval("BlahModule::#{class_name}") - end - - it "and os = linux, two_classes_one_dsl resolves to ::TwoClassesOneDsl" do - dsl_method = self.dsl_method - recipe = converge { - # this is an ugly way to test, make Cheffish expose node attrs - run_context.node.automatic[:os] = 'linux' - instance_eval("#{dsl_method} 'blah' do; end") - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq eval("::#{class_name}") - end - end - end end end @@ -833,7 +748,7 @@ describe "Recipe DSL methods" do end end - context "With platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do + context "with platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do before(:context) { class MySuperThingyFoo < BaseThingy resource_name :my_super_thingy_foo @@ -924,6 +839,601 @@ describe "Recipe DSL methods" do end end end + + context "with a resource named 'B' with resource name :two_classes_one_dsl" do + let(:two_classes_one_dsl) { :"two_classes_one_dsl#{Namer.current_index}" } + let(:resource_class) { + result = Class.new(BaseThingy) do + def self.name + "B" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result + } + before { resource_class } # pull on it so it gets defined before the recipe runs + + context "and another resource named 'A' with resource_name :two_classes_one_dsl" do + let(:resource_class_a) { + result = Class.new(BaseThingy) do + def self.name + "A" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result + } + before { resource_class_a } # pull on it so it gets defined before the recipe runs + + it "two_classes_one_dsl resolves to A (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_a + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class_a + end + end + + context "and another resource named 'Z' with resource_name :two_classes_one_dsl" do + let(:resource_class_z) { + result = Class.new(BaseThingy) do + def self.name + "Z" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result + } + before { resource_class_z } # pull on it so it gets defined before the recipe runs + + it "two_classes_one_dsl resolves to B (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + + context "and a priority array [ Z, B ]" do + before do + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z, resource_class ]) + end + + it "two_classes_one_dsl resolves to Z (respects the priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_z + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + + context "when Z provides(:two_classes_one_dsl) { false }" do + before do + resource_class_z.provides(two_classes_one_dsl) { false } + end + + it "two_classes_one_dsl resolves to B (picks the next thing in the priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + end + end + + context "and priority arrays [ B ] and [ Z ]" do + before do + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class ]) + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ]) + end + + it "two_classes_one_dsl resolves to Z (respects the most recent priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_z + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + + context "when Z provides(:two_classes_one_dsl) { false }" do + before do + resource_class_z.provides(two_classes_one_dsl) { false } + end + + it "two_classes_one_dsl resolves to B (picks the first match from the other priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + end + end + + context "and a priority array [ Z ]" do + before do + Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ]) + end + + context "when Z provides(:two_classes_one_dsl) { false }" do + before do + resource_class_z.provides(two_classes_one_dsl) { false } + end + + it "two_classes_one_dsl resolves to B (picks the first match outside the priority array)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + + it "resource_matching_short_name returns B" do + expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class + end + end + end + + end + + context "and a provider named 'B' which provides :two_classes_one_dsl" do + before do + resource_class.send(:define_method, :provider) { nil } + end + + let(:provider_class) { + result = Class.new(BaseThingy::Provider) do + def self.name + "B" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.provides two_classes_one_dsl + result + } + before { provider_class } # pull on it so it gets defined before the recipe runs + + context "and another provider named 'A'" do + let(:provider_class_a) { + result = Class.new(BaseThingy::Provider) do + def self.name + "A" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result + } + context "which provides :two_classes_one_dsl" do + before { provider_class_a.provides two_classes_one_dsl } + + it "two_classes_one_dsl resolves to A (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class_a + end + end + context "which provides(:two_classes_one_dsl) { false }" do + before { provider_class_a.provides(two_classes_one_dsl) { false } } + + it "two_classes_one_dsl resolves to B (since A declined)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + end + + context "and another provider named 'Z'" do + let(:provider_class_z) { + result = Class.new(BaseThingy::Provider) do + def self.name + "Z" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result + } + before { provider_class_z } # pull on it so it gets defined before the recipe runs + + context "which provides :two_classes_one_dsl" do + before { provider_class_z.provides two_classes_one_dsl } + + it "two_classes_one_dsl resolves to B (alphabetically earliest)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + + context "with a priority array [ Z, B ]" do + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] } + + it "two_classes_one_dsl resolves to Z (respects the priority map)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class_z + end + end + end + + context "which provides(:two_classes_one_dsl) { false }" do + before { provider_class_z.provides(two_classes_one_dsl) { false } } + + context "with a priority array [ Z, B ]" do + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] } + + it "two_classes_one_dsl resolves to B (the next one in the priority map)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + + context "with priority arrays [ B ] and [ Z ]" do + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z ] } + before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class ] } + + it "two_classes_one_dsl resolves to B (the one in the next priority map)" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + instance_eval("#{two_classes_one_dsl} 'blah'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + end + end + end + + context "and another resource Blarghle with provides :two_classes_one_dsl, os: 'blarghle'" do + let(:resource_class_blarghle) { + result = Class.new(BaseThingy) do + def self.name + "Blarghle" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + end + result.resource_name two_classes_one_dsl + result.provides two_classes_one_dsl, os: 'blarghle' + result + } + before { resource_class_blarghle } # pull on it so it gets defined before the recipe runs + + it "on os = blarghle, two_classes_one_dsl resolves to Blarghle" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + # this is an ugly way to test, make Cheffish expose node attrs + run_context.node.automatic[:os] = 'blarghle' + instance_eval("#{two_classes_one_dsl} 'blah' do; end") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class_blarghle + end + + it "on os = linux, two_classes_one_dsl resolves to B" do + two_classes_one_dsl = self.two_classes_one_dsl + recipe = converge { + # this is an ugly way to test, make Cheffish expose node attrs + run_context.node.automatic[:os] = 'linux' + instance_eval("#{two_classes_one_dsl} 'blah' do; end") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + end + end + end + + context "with a resource MyResource" do + let(:resource_class) { Class.new(BaseThingy) do + def self.called_provides + @called_provides + end + def to_s + "MyResource" + end + end } + let(:my_resource) { :"my_resource#{Namer.current_index}" } + let(:blarghle_blarghle_little_star) { :"blarghle_blarghle_little_star#{Namer.current_index}" } + + context "with resource_name :my_resource" do + before { + resource_class.resource_name my_resource + } + + context "with provides? returning true to my_resource" do + before { + my_resource = self.my_resource + resource_class.define_singleton_method(:provides?) do |node, resource_name| + @called_provides = true + resource_name == my_resource + end + } + + it "my_resource returns the resource and calls provides?, but does not emit a warning" do + dsl_name = self.my_resource + recipe = converge { + instance_eval("#{dsl_name} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_resource).to eq resource_class + expect(resource_class.called_provides).to be_truthy + end + end + + context "with provides? returning true to blarghle_blarghle_little_star and not resource_name" do + before do + blarghle_blarghle_little_star = self.blarghle_blarghle_little_star + resource_class.define_singleton_method(:provides?) do |node, resource_name| + @called_provides = true + resource_name == blarghle_blarghle_little_star + end + end + + it "my_resource does not return the resource" do + dsl_name = self.my_resource + expect_converge { + instance_eval("#{dsl_name} 'foo'") + }.to raise_error(Chef::Exceptions::NoSuchResourceType) + expect(resource_class.called_provides).to be_truthy + end + + it "blarghle_blarghle_little_star 'foo' returns the resource and emits a warning" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + dsl_name = self.blarghle_blarghle_little_star + recipe = converge { + instance_eval("#{dsl_name} 'foo'") + } + expect(recipe.logged_warnings).to include "WARN: #{resource_class}.provides? returned true when asked if it provides DSL #{dsl_name}, but provides :#{dsl_name} was never called!" + expect(BaseThingy.created_resource).to eq resource_class + expect(resource_class.called_provides).to be_truthy + end + end + + context "and a provider" do + let(:provider_class) do + Class.new(BaseThingy::Provider) do + def self.name + "MyProvider" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + def self.called_provides + @called_provides + end + end + end + + before do + resource_class.send(:define_method, :provider) { nil } + end + + context "that provides :my_resource" do + before do + provider_class.provides my_resource + end + + context "with supports? returning true" do + before do + provider_class.define_singleton_method(:supports?) { |resource,action| true } + end + + it "my_resource runs the provider and does not emit a warning" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + + context "and another provider supporting :my_resource with supports? false" do + let(:provider_class2) do + Class.new(BaseThingy::Provider) do + def self.name + "MyProvider2" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + def self.called_provides + @called_provides + end + provides my_resource + def self.supports?(resource, action) + false + end + end + end + + it "my_resource runs the first provider" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + end + end + + context "with supports? returning false" do + before do + provider_class.define_singleton_method(:supports?) { |resource,action| false } + end + + # TODO no warning? ick + it "my_resource runs the provider anyway" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + end + + context "and another provider supporting :my_resource with supports? true" do + let(:provider_class2) do + my_resource = self.my_resource + Class.new(BaseThingy::Provider) do + def self.name + "MyProvider2" + end + def self.to_s; name; end + def self.inspect; name.inspect; end + def self.called_provides + @called_provides + end + provides my_resource + def self.supports?(resource, action) + true + end + end + end + before { provider_class2 } # make sure the provider class shows up + + it "my_resource runs the other provider" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class2 + end + end + end + end + + context "with provides? returning true" do + before { + my_resource = self.my_resource + provider_class.define_singleton_method(:provides?) do |node, resource| + @called_provides = true + resource.declared_type == my_resource + end + } + + context "that provides :my_resource" do + before { + provider_class.provides my_resource + } + + it "my_resource calls the provider (and calls provides?), but does not emit a warning" do + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to eq '' + expect(BaseThingy.created_provider).to eq provider_class + expect(provider_class.called_provides).to be_truthy + end + end + + context "that does not call provides :my_resource" do + it "my_resource calls the provider (and calls provides?), and emits a warning" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + my_resource = self.my_resource + recipe = converge { + instance_eval("#{my_resource} 'foo'") + } + expect(recipe.logged_warnings).to include("WARN: #{provider_class}.provides? returned true when asked if it provides DSL #{my_resource}, but provides :#{my_resource} was never called!") + expect(BaseThingy.created_provider).to eq provider_class + expect(provider_class.called_provides).to be_truthy + end + end + end + + context "with provides? returning false to my_resource" do + before { + my_resource = self.my_resource + provider_class.define_singleton_method(:provides?) do |node, resource| + @called_provides = true + false + end + } + + context "that provides :my_resource" do + before { + provider_class.provides my_resource + } + + it "my_resource fails to find a provider (and calls provides)" do + my_resource = self.my_resource + expect_converge { + instance_eval("#{my_resource} 'foo'") + }.to raise_error(Chef::Exceptions::ProviderNotFound) + expect(provider_class.called_provides).to be_truthy + end + end + + context "that does not provide :my_resource" do + it "my_resource fails to find a provider (and calls provides)" do + my_resource = self.my_resource + expect_converge { + instance_eval("#{my_resource} 'foo'") + }.to raise_error(Chef::Exceptions::ProviderNotFound) + expect(provider_class.called_provides).to be_truthy + end + end + end + end + end + end end before(:all) { Namer.current_index = 0 } diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb index 4786294803..cee79133a9 100644 --- a/spec/integration/recipes/resource_action_spec.rb +++ b/spec/integration/recipes/resource_action_spec.rb @@ -337,7 +337,7 @@ describe "Resource.action" do NoActionJackson.action_was = action end } - expect(NoActionJackson.action_was).to eq :nothing + expect(NoActionJackson.action_was).to eq [:nothing] end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dcf244c3cc..3b55ba9de0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -87,12 +87,14 @@ Dir["spec/support/**/*.rb"]. OHAI_SYSTEM = Ohai::System.new OHAI_SYSTEM.all_plugins("platform") -TEST_PLATFORM = - (OHAI_SYSTEM['platform'] || - 'unknown_test_platform').dup.freeze -TEST_PLATFORM_VERSION = - (OHAI_SYSTEM['platform_version'] || - 'unknown_platform_version').dup.freeze +test_node = Chef::Node.new +test_node.automatic['os'] = (OHAI_SYSTEM['os'] || 'unknown_os').dup.freeze +test_node.automatic['platform_family'] = (OHAI_SYSTEM['platform_family'] || 'unknown_platform_family').dup.freeze +test_node.automatic['platform'] = (OHAI_SYSTEM['platform'] || 'unknown_platform').dup.freeze +test_node.automatic['platform_version'] = (OHAI_SYSTEM['platform_version'] || 'unknown_platform_version').dup.freeze +TEST_NODE = test_node.freeze +TEST_PLATFORM = TEST_NODE['platform'] +TEST_PLATFORM_VERSION = TEST_NODE['platform_version'] RSpec.configure do |config| config.include(Matchers) @@ -116,6 +118,7 @@ RSpec.configure do |config| config.filter_run_excluding :volatile_from_verify => false config.filter_run_excluding :skip_appveyor => true if ENV["APPVEYOR"] + config.filter_run_excluding :appveyor_only => true unless ENV["APPVEYOR"] config.filter_run_excluding :windows_only => true unless windows? config.filter_run_excluding :not_supported_on_mac_osx_106 => true if mac_osx_106? @@ -162,13 +165,17 @@ RSpec.configure do |config| config.filter_run_excluding :provider => lambda {|criteria| type, target_provider = criteria.first - platform = TEST_PLATFORM.dup - platform_version = TEST_PLATFORM_VERSION.dup - - begin - provider_for_running_platform = Chef::Platform.find_provider(platform, platform_version, type) - provider_for_running_platform != target_provider - rescue ArgumentError # no provider for platform + node = TEST_NODE.dup + resource_class = Chef::ResourceResolver.resolve(type, node: node) + if resource_class + resource = resource_class.new('test', Chef::RunContext.new(node, nil, nil)) + begin + provider = resource.provider_for_action(Array(resource_class.default_action).first) + provider.class != target_provider + rescue Chef::Exceptions::ProviderNotFound # no provider for platform + true + end + else true end } diff --git a/spec/support/shared/functional/win32_service.rb b/spec/support/shared/functional/win32_service.rb index 7dd1920418..2ee1a8ad88 100644 --- a/spec/support/shared/functional/win32_service.rb +++ b/spec/support/shared/functional/win32_service.rb @@ -46,7 +46,8 @@ shared_context "using Win32::Service" do :service_name => "spec-service", :service_display_name => "Spec Test Service", :service_description => "Service for testing Chef::Application::WindowsServiceManager.", - :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../platforms/win32/spec_service.rb')) + :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../platforms/win32/spec_service.rb')), + :delayed_start => true } } diff --git a/spec/support/shared/unit/api_versioning.rb b/spec/support/shared/unit/api_versioning.rb index a4f353de60..05a4117f8e 100644 --- a/spec/support/shared/unit/api_versioning.rb +++ b/spec/support/shared/unit/api_versioning.rb @@ -26,7 +26,7 @@ shared_examples_for "version handling" do allow(rest_v1).to receive(http_verb).and_raise(exception_406) end - context "when the server does not support the min or max server API version that Chef::User supports" do + context "when the server does not support the min or max server API version that Chef::UserV1 supports" do before do allow(object).to receive(:server_client_api_version_intersection).and_return([]) end @@ -34,7 +34,7 @@ shared_examples_for "version handling" do it "raises the original exception" do expect{ object.send(method) }.to raise_error(exception_406) end - end # when the server does not support the min or max server API version that Chef::User supports + end # when the server does not support the min or max server API version that Chef::UserV1 supports end # version handling shared_examples_for "user and client reregister" do diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb index bc4b38848b..a0e399b470 100644 --- a/spec/unit/api_client_spec.rb +++ b/spec/unit/api_client_spec.rb @@ -21,6 +21,11 @@ require 'spec_helper' require 'chef/api_client' require 'tempfile' +# DEPRECATION NOTE +# +# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1, +# which will be moved to this namespace. New development should occur in +# Chef::ApiClientV1 until the time before Chef 13. describe Chef::ApiClient do before(:each) do @client = Chef::ApiClient.new @@ -53,20 +58,6 @@ describe Chef::ApiClient do expect { @client.admin(Hash.new) }.to raise_error(ArgumentError) end - it "has an create_key flag attribute" do - @client.create_key(true) - expect(@client.create_key).to be_truthy - end - - it "create_key defaults to false" do - expect(@client.create_key).to be_falsey - end - - it "allows only boolean values for the create_key flag" do - expect { @client.create_key(false) }.not_to raise_error - expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError) - end - it "has a 'validator' flag attribute" do @client.validator(true) expect(@client.validator).to be_truthy @@ -129,12 +120,6 @@ describe Chef::ApiClient do expect(@json).to include(%q{"validator":false}) end - it "includes the 'create_key' flag when present" do - @client.create_key(true) - @json = @client.to_json - expect(@json).to include(%q{"create_key":true}) - end - it "includes the private key when present" do @client.private_key("monkeypants") expect(@client.to_json).to include(%q{"private_key":"monkeypants"}) @@ -143,15 +128,11 @@ describe Chef::ApiClient do it "does not include the private key if not present" do expect(@json).not_to include("private_key") end - - include_examples "to_json equivalent to Chef::JSONCompat.to_json" do - let(:jsonable) { @client } - end end describe "when deserializing from JSON (string) using ApiClient#from_json" do let(:client_string) do - "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}" + "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true}" end let(:client) do @@ -178,10 +159,6 @@ describe Chef::ApiClient do expect(client.admin).to be_truthy end - it "preserves the create_key status" do - expect(client.create_key).to be_truthy - end - it "preserves the 'validator' status" do expect(client.validator).to be_truthy end @@ -199,7 +176,6 @@ describe Chef::ApiClient do "private_key" => "monkeypants", "admin" => true, "validator" => true, - "create_key" => true, "json_class" => "Chef::ApiClient" } end @@ -224,10 +200,6 @@ describe Chef::ApiClient do expect(client.admin).to be_truthy end - it "preserves the create_key status" do - expect(client.create_key).to be_truthy - end - it "preserves the 'validator' status" do expect(client.validator).to be_truthy end @@ -243,18 +215,16 @@ describe Chef::ApiClient do before(:each) do client = { - "name" => "black", - "clientname" => "black", - "public_key" => "crowes", - "private_key" => "monkeypants", - "admin" => true, - "create_key" => true, - "validator" => true, - "json_class" => "Chef::ApiClient" + "name" => "black", + "clientname" => "black", + "public_key" => "crowes", + "private_key" => "monkeypants", + "admin" => true, + "validator" => true, + "json_class" => "Chef::ApiClient" } - - @http_client = double("Chef::REST mock") - allow(Chef::REST).to receive(:new).and_return(@http_client) + @http_client = double("Chef::ServerAPI mock") + allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) expect(@http_client).to receive(:get).with("clients/black").and_return(client) @client = Chef::ApiClient.load(client['name']) end @@ -275,10 +245,6 @@ describe Chef::ApiClient do expect(@client.admin).to be_a_kind_of(TrueClass) end - it "preserves the create_key status" do - expect(@client.create_key).to be_a_kind_of(TrueClass) - end - it "preserves the 'validator' status" do expect(@client.validator).to be_a_kind_of(TrueClass) end @@ -304,18 +270,13 @@ describe Chef::ApiClient do File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp } end - it "has an HTTP client configured with default credentials" do - expect(@client.http_api).to be_a_kind_of(Chef::REST) - expect(@client.http_api.client_name).to eq("silent-bob") - expect(@client.http_api.signing_key.to_s).to eq(private_key_data) - end end describe "when requesting a new key" do before do @http_client = double("Chef::REST mock") - allow(Chef::REST).to receive(:new).and_return(@http_client) + allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) end context "and the client does not exist on the server" do @@ -332,34 +293,24 @@ describe Chef::ApiClient do end context "and the client exists" do - let(:chef_rest_v0_mock) { double('chef rest root v0 object') } - let(:payload) { - {:name => "lost-my-key", :admin => false, :validator => false, :private_key => true} - } - before do @api_client_without_key = Chef::ApiClient.new @api_client_without_key.name("lost-my-key") - allow(@api_client_without_key).to receive(:chef_rest_v0).and_return(chef_rest_v0_mock) - #allow(@api_client_with_key).to receive(:http_api).and_return(_api_mock) - - allow(chef_rest_v0_mock).to receive(:put).with("clients/lost-my-key", payload).and_return(@api_client_with_key) - allow(chef_rest_v0_mock).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) - allow(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) + expect(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) end + context "and the client exists on a Chef 11-like server" do before do @api_client_with_key = Chef::ApiClient.new @api_client_with_key.name("lost-my-key") @api_client_with_key.private_key("the new private key") - allow(@api_client_with_key).to receive(:chef_rest_v0).and_return(chef_rest_v0_mock) + expect(@http_client).to receive(:put). + with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true). + and_return(@api_client_with_key) end it "returns an ApiClient with a private key" do - expect(chef_rest_v0_mock).to receive(:put).with("clients/lost-my-key", payload). - and_return(@api_client_with_key) - response = Chef::ApiClient.reregister("lost-my-key") # no sane == method for ApiClient :'( expect(response).to eq(@api_client_without_key) @@ -372,7 +323,7 @@ describe Chef::ApiClient do context "and the client exists on a Chef 10-like server" do before do @api_client_with_key = {"name" => "lost-my-key", "private_key" => "the new private key"} - expect(chef_rest_v0_mock).to receive(:put). + expect(@http_client).to receive(:put). with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true). and_return(@api_client_with_key) end @@ -390,134 +341,4 @@ describe Chef::ApiClient do end end - - describe "Versioned API Interactions" do - let(:response_406) { OpenStruct.new(:code => '406') } - let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } - let(:payload) { - { - :name => "some_name", - :validator => true, - :admin => true - } - } - - before do - @client = Chef::ApiClient.new - allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object')) - allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object')) - @client.name "some_name" - @client.validator true - @client.admin true - end - - describe "create" do - - # from spec/support/shared/unit/user_and_client_shared.rb - it_should_behave_like "user or client create" do - let(:object) { @client } - let(:error) { Chef::Exceptions::InvalidClientAttribute } - let(:rest_v0) { @client.chef_rest_v0 } - let(:rest_v1) { @client.chef_rest_v1 } - let(:url) { "clients" } - end - - context "when API V1 is not supported by the server" do - # from spec/support/shared/unit/api_versioning.rb - it_should_behave_like "version handling" do - let(:object) { @client } - let(:method) { :create } - let(:http_verb) { :post } - let(:rest_v1) { @client.chef_rest_v1 } - end - end - - end # create - - describe "update" do - context "when a valid client is defined" do - - shared_examples_for "client updating" do - it "updates the client" do - expect(rest). to receive(:put).with("clients/some_name", payload) - @client.update - end - - context "when only the name field exists" do - - before do - # needed since there is no way to set to nil via code - @client.instance_variable_set(:@validator, nil) - @client.instance_variable_set(:@admin, nil) - end - - after do - @client.validator true - @client.admin true - end - - it "updates the client with only the name" do - expect(rest). to receive(:put).with("clients/some_name", {:name => "some_name"}) - @client.update - end - end - - end - - context "when API V1 is supported by the server" do - - it_should_behave_like "client updating" do - let(:rest) { @client.chef_rest_v1 } - end - - end # when API V1 is supported by the server - - context "when API V1 is not supported by the server" do - context "when no version is supported" do - # from spec/support/shared/unit/api_versioning.rb - it_should_behave_like "version handling" do - let(:object) { @client } - let(:method) { :create } - let(:http_verb) { :post } - let(:rest_v1) { @client.chef_rest_v1 } - end - end # when no version is supported - - context "when API V0 is supported" do - - before do - allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406) - allow(@client).to receive(:server_client_api_version_intersection).and_return([0]) - end - - it_should_behave_like "client updating" do - let(:rest) { @client.chef_rest_v0 } - end - - end - - end # when API V1 is not supported by the server - end # when a valid client is defined - end # update - - # DEPRECATION - # This can be removed after API V0 support is gone - describe "reregister" do - context "when server API V0 is valid on the Chef Server receiving the request" do - it "creates a new object via the API" do - expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({:private_key => true})).and_return({}) - @client.reregister - end - end # when server API V0 is valid on the Chef Server receiving the request - - context "when server API V0 is not supported by the Chef Server" do - # from spec/support/shared/unit/api_versioning.rb - it_should_behave_like "user and client reregister" do - let(:object) { @client } - let(:rest_v0) { @client.chef_rest_v0 } - end - end # when server API V0 is not supported by the Chef Server - end # reregister - - end end diff --git a/spec/unit/api_client_v1_spec.rb b/spec/unit/api_client_v1_spec.rb new file mode 100644 index 0000000000..17aba8c3af --- /dev/null +++ b/spec/unit/api_client_v1_spec.rb @@ -0,0 +1,457 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +require 'chef/api_client_v1' +require 'tempfile' + +describe Chef::ApiClientV1 do + before(:each) do + @client = Chef::ApiClientV1.new + end + + it "has a name attribute" do + @client.name("ops_master") + expect(@client.name).to eq("ops_master") + end + + it "does not allow spaces in the name" do + expect { @client.name "ops master" }.to raise_error(ArgumentError) + end + + it "only allows string values for the name" do + expect { @client.name Hash.new }.to raise_error(ArgumentError) + end + + it "has an admin flag attribute" do + @client.admin(true) + expect(@client.admin).to be_truthy + end + + it "defaults to non-admin" do + expect(@client.admin).to be_falsey + end + + it "allows only boolean values for the admin flag" do + expect { @client.admin(false) }.not_to raise_error + expect { @client.admin(Hash.new) }.to raise_error(ArgumentError) + end + + it "has an create_key flag attribute" do + @client.create_key(true) + expect(@client.create_key).to be_truthy + end + + it "create_key defaults to false" do + expect(@client.create_key).to be_falsey + end + + it "allows only boolean values for the create_key flag" do + expect { @client.create_key(false) }.not_to raise_error + expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError) + end + + it "has a 'validator' flag attribute" do + @client.validator(true) + expect(@client.validator).to be_truthy + end + + it "defaults to non-validator" do + expect(@client.validator).to be_falsey + end + + it "allows only boolean values for the 'validator' flag" do + expect { @client.validator(false) }.not_to raise_error + expect { @client.validator(Hash.new) }.to raise_error(ArgumentError) + end + + it "has a public key attribute" do + @client.public_key("super public") + expect(@client.public_key).to eq("super public") + end + + it "accepts only String values for the public key" do + expect { @client.public_key "" }.not_to raise_error + expect { @client.public_key Hash.new }.to raise_error(ArgumentError) + end + + + it "has a private key attribute" do + @client.private_key("super private") + expect(@client.private_key).to eq("super private") + end + + it "accepts only String values for the private key" do + expect { @client.private_key "" }.not_to raise_error + expect { @client.private_key Hash.new }.to raise_error(ArgumentError) + end + + describe "when serializing to JSON" do + before(:each) do + @client.name("black") + @client.public_key("crowes") + @json = @client.to_json + end + + it "serializes as a JSON object" do + expect(@json).to match(/^\{.+\}$/) + end + + it "includes the name value" do + expect(@json).to include(%q{"name":"black"}) + end + + it "includes the public key value" do + expect(@json).to include(%{"public_key":"crowes"}) + end + + it "includes the 'admin' flag" do + expect(@json).to include(%q{"admin":false}) + end + + it "includes the 'validator' flag" do + expect(@json).to include(%q{"validator":false}) + end + + it "includes the 'create_key' flag when present" do + @client.create_key(true) + @json = @client.to_json + expect(@json).to include(%q{"create_key":true}) + end + + it "includes the private key when present" do + @client.private_key("monkeypants") + expect(@client.to_json).to include(%q{"private_key":"monkeypants"}) + end + + it "does not include the private key if not present" do + expect(@json).not_to include("private_key") + end + + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do + let(:jsonable) { @client } + end + end + + describe "when deserializing from JSON (string) using ApiClient#from_json" do + let(:client_string) do + "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}" + end + + let(:client) do + Chef::ApiClientV1.from_json(client_string) + end + + it "does not require a 'json_class' string" do + expect(Chef::JSONCompat.parse(client_string)["json_class"]).to eq(nil) + end + + it "should deserialize to a Chef::ApiClientV1 object" do + expect(client).to be_a_kind_of(Chef::ApiClientV1) + end + + it "preserves the name" do + expect(client.name).to eq("black") + end + + it "preserves the public key" do + expect(client.public_key).to eq("crowes") + end + + it "preserves the admin status" do + expect(client.admin).to be_truthy + end + + it "preserves the create_key status" do + expect(client.create_key).to be_truthy + end + + it "preserves the 'validator' status" do + expect(client.validator).to be_truthy + end + + it "includes the private key if present" do + expect(client.private_key).to eq("monkeypants") + end + end + + describe "when deserializing from JSON (hash) using ApiClientV1#from_json" do + let(:client_hash) do + { + "name" => "black", + "public_key" => "crowes", + "private_key" => "monkeypants", + "admin" => true, + "validator" => true, + "create_key" => true + } + end + + let(:client) do + Chef::ApiClientV1.from_json(Chef::JSONCompat.to_json(client_hash)) + end + + it "should deserialize to a Chef::ApiClientV1 object" do + expect(client).to be_a_kind_of(Chef::ApiClientV1) + end + + it "preserves the name" do + expect(client.name).to eq("black") + end + + it "preserves the public key" do + expect(client.public_key).to eq("crowes") + end + + it "preserves the admin status" do + expect(client.admin).to be_truthy + end + + it "preserves the create_key status" do + expect(client.create_key).to be_truthy + end + + it "preserves the 'validator' status" do + expect(client.validator).to be_truthy + end + + it "includes the private key if present" do + expect(client.private_key).to eq("monkeypants") + end + end + + describe "when loading from JSON" do + before do + end + + before(:each) do + client = { + "name" => "black", + "clientname" => "black", + "public_key" => "crowes", + "private_key" => "monkeypants", + "admin" => true, + "create_key" => true, + "validator" => true + } + + @http_client = double("Chef::ServerAPI mock") + allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) + expect(@http_client).to receive(:get).with("clients/black").and_return(client) + @client = Chef::ApiClientV1.load(client['name']) + end + + it "should deserialize to a Chef::ApiClientV1 object" do + expect(@client).to be_a_kind_of(Chef::ApiClientV1) + end + + it "preserves the name" do + expect(@client.name).to eq("black") + end + + it "preserves the public key" do + expect(@client.public_key).to eq("crowes") + end + + it "preserves the admin status" do + expect(@client.admin).to be_a_kind_of(TrueClass) + end + + it "preserves the create_key status" do + expect(@client.create_key).to be_a_kind_of(TrueClass) + end + + it "preserves the 'validator' status" do + expect(@client.validator).to be_a_kind_of(TrueClass) + end + + it "includes the private key if present" do + expect(@client.private_key).to eq("monkeypants") + end + + end + + describe "with correctly configured API credentials" do + before do + Chef::Config[:node_name] = "silent-bob" + Chef::Config[:client_key] = File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA) + end + + after do + Chef::Config[:node_name] = nil + Chef::Config[:client_key] = nil + end + + let :private_key_data do + File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp } + end + + end + + + describe "when requesting a new key" do + before do + @http_client = double("Chef::ServerAPI mock") + allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) + end + + context "and the client does not exist on the server" do + before do + @a_404_response = Net::HTTPNotFound.new("404 not found and such", nil, nil) + @a_404_exception = Net::HTTPServerException.new("404 not found exception", @a_404_response) + + expect(@http_client).to receive(:get).with("clients/lost-my-key").and_raise(@a_404_exception) + end + + it "raises a 404 error" do + expect { Chef::ApiClientV1.reregister("lost-my-key") }.to raise_error(Net::HTTPServerException) + end + end + end + + describe "Versioned API Interactions" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + let(:payload) { + { + :name => "some_name", + :validator => true, + :admin => true + } + } + + before do + @client = Chef::ApiClientV1.new + allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object')) + allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object')) + @client.name "some_name" + @client.validator true + @client.admin true + end + + describe "create" do + + # from spec/support/shared/unit/user_and_client_shared.rb + it_should_behave_like "user or client create" do + let(:object) { @client } + let(:error) { Chef::Exceptions::InvalidClientAttribute } + let(:rest_v0) { @client.chef_rest_v0 } + let(:rest_v1) { @client.chef_rest_v1 } + let(:url) { "clients" } + end + + context "when API V1 is not supported by the server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @client } + let(:method) { :create } + let(:http_verb) { :post } + let(:rest_v1) { @client.chef_rest_v1 } + end + end + + end # create + + describe "update" do + context "when a valid client is defined" do + + shared_examples_for "client updating" do + it "updates the client" do + expect(rest). to receive(:put).with("clients/some_name", payload).and_return(payload) + @client.update + end + + context "when only the name field exists" do + + before do + # needed since there is no way to set to nil via code + @client.instance_variable_set(:@validator, nil) + @client.instance_variable_set(:@admin, nil) + end + + after do + @client.validator true + @client.admin true + end + + it "updates the client with only the name" do + expect(rest). to receive(:put).with("clients/some_name", {:name => "some_name"}).and_return({:name => "some_name"}) + @client.update + end + end + + end + + context "when API V1 is supported by the server" do + + it_should_behave_like "client updating" do + let(:rest) { @client.chef_rest_v1 } + end + + end # when API V1 is supported by the server + + context "when API V1 is not supported by the server" do + context "when no version is supported" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @client } + let(:method) { :create } + let(:http_verb) { :post } + let(:rest_v1) { @client.chef_rest_v1 } + end + end # when no version is supported + + context "when API V0 is supported" do + + before do + allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406) + allow(@client).to receive(:server_client_api_version_intersection).and_return([0]) + end + + it_should_behave_like "client updating" do + let(:rest) { @client.chef_rest_v0 } + end + + end + + end # when API V1 is not supported by the server + end # when a valid client is defined + end # update + + # DEPRECATION + # This can be removed after API V0 support is gone + describe "reregister" do + context "when server API V0 is valid on the Chef Server receiving the request" do + it "creates a new object via the API" do + expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({:private_key => true})).and_return({}) + @client.reregister + end + end # when server API V0 is valid on the Chef Server receiving the request + + context "when server API V0 is not supported by the Chef Server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "user and client reregister" do + let(:object) { @client } + let(:rest_v0) { @client.chef_rest_v0 } + end + end # when server API V0 is not supported by the Chef Server + end # reregister + + end +end diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb index 1785ecfc86..7013bfa0bc 100644 --- a/spec/unit/application/solo_spec.rb +++ b/spec/unit/application/solo_spec.rb @@ -106,7 +106,8 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config describe "when the recipe_url configuration option is specified" do let(:tarfile) { StringIO.new("remote_tarball_content") } let(:target_file) { StringIO.new } - + let(:shellout) { double(run_command: nil, error!: nil, stdout: '') } + before do Chef::Config[:cookbook_path] = "#{Dir.tmpdir}/chef-solo/cookbooks" Chef::Config[:recipe_url] = "http://junglist.gen.nz/recipes.tgz" @@ -117,7 +118,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config allow(app).to receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(tarfile) allow(File).to receive(:open).with("#{Dir.tmpdir}/chef-solo/recipes.tgz", "wb").and_yield(target_file) - allow(Chef::Mixin::Command).to receive(:run_command).and_return(true) + allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) end it "should create the recipes path based on the parent of the cookbook path" do @@ -136,7 +137,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config end it "should untar the target file to the parent of the cookbook path" do - expect(Chef::Mixin::Command).to receive(:run_command).with({:command => "tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo"}).and_return(true) + expect(Mixlib::ShellOut).to receive(:new).with("tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo") app.reconfigure end end diff --git a/spec/unit/chef_class_spec.rb b/spec/unit/chef_class_spec.rb index 2528246be6..d6bf74572e 100644 --- a/spec/unit/chef_class_spec.rb +++ b/spec/unit/chef_class_spec.rb @@ -88,4 +88,27 @@ describe "Chef class" do expect(Chef.node).to eql(node) end end + + context '#event_handler' do + it 'adds a new handler' do + x = 1 + Chef.event_handler do + on :converge_start do + x = 2 + end + end + expect(Chef::Config[:event_handlers]).to_not be_empty + Chef::Config[:event_handlers].first.send(:converge_start) + expect(x).to eq(2) + end + + it 'raise error if unknown event type is passed' do + expect do + Chef.event_handler do + on :yolo do + end + end + end.to raise_error(Chef::Exceptions::InvalidEventType) + end + end end diff --git a/spec/unit/chef_fs/path_util_spec.rb b/spec/unit/chef_fs/path_util_spec.rb new file mode 100644 index 0000000000..42eb126dfb --- /dev/null +++ b/spec/unit/chef_fs/path_util_spec.rb @@ -0,0 +1,108 @@ +# +# Author:: Kartik Null Cating-Subramanian (<ksubramanian@chef.io>) +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'chef/chef_fs/path_utils' + +describe Chef::ChefFS::PathUtils do + context 'invoking join' do + it 'joins well-behaved distinct path elements' do + expect(Chef::ChefFS::PathUtils.join('a', 'b', 'c')).to eq('a/b/c') + end + + it 'strips extraneous slashes in the middle of paths' do + expect(Chef::ChefFS::PathUtils.join('a/', '/b', '/c/')).to eq('a/b/c') + expect(Chef::ChefFS::PathUtils.join('a/', '/b', '///c/')).to eq('a/b/c') + end + + it 'preserves the whether the first element was absolute or not' do + expect(Chef::ChefFS::PathUtils.join('/a/', '/b', 'c/')).to eq('/a/b/c') + expect(Chef::ChefFS::PathUtils.join('///a/', '/b', 'c/')).to eq('/a/b/c') + end + end + + context 'invoking is_absolute?' do + it 'confirms that paths starting with / are absolute' do + expect(Chef::ChefFS::PathUtils.is_absolute?('/foo/bar/baz')).to be true + expect(Chef::ChefFS::PathUtils.is_absolute?('/foo')).to be true + end + + it 'confirms that paths starting with // are absolute even though that looks like some windows network path' do + expect(Chef::ChefFS::PathUtils.is_absolute?('//foo/bar/baz')).to be true + end + + it 'confirms that root is indeed absolute' do + expect(Chef::ChefFS::PathUtils.is_absolute?('/')).to be true + end + + it 'confirms that paths starting without / are relative' do + expect(Chef::ChefFS::PathUtils.is_absolute?('foo/bar/baz')).to be false + expect(Chef::ChefFS::PathUtils.is_absolute?('a')).to be false + end + + it 'returns false for an empty path.' do + expect(Chef::ChefFS::PathUtils.is_absolute?('')).to be false + end + end + + context 'invoking realest_path' do + let(:good_path) { File.dirname(__FILE__) } + let(:parent_path) { File.dirname(good_path) } + + it 'handles paths with no wildcards or globs' do + expect(Chef::ChefFS::PathUtils.realest_path(good_path)).to eq(File.expand_path(good_path)) + end + + it 'handles paths with .. and .' do + expect(Chef::ChefFS::PathUtils.realest_path(good_path+'/../.')).to eq(File.expand_path(parent_path)) + end + + it 'handles paths with *' do + expect(Chef::ChefFS::PathUtils.realest_path(good_path + '/*/foo')).to eq(File.expand_path(good_path + '/*/foo')) + end + + it 'handles directories that do not exist' do + expect(Chef::ChefFS::PathUtils.realest_path(good_path + '/something/or/other')).to eq(File.expand_path(good_path + '/something/or/other')) + end + + it 'handles root correctly' do + if Chef::Platform.windows? + expect(Chef::ChefFS::PathUtils.realest_path('C:/')).to eq('C:/') + else + expect(Chef::ChefFS::PathUtils.realest_path('/')).to eq('/') + end + end + end + + context 'invoking descendant_path' do + it 'handles paths with various casing on windows' do + allow(Chef::ChefFS).to receive(:windows?) { true } + expect(Chef::ChefFS::PathUtils.descendant_path('C:/ab/b/c', 'C:/AB/B')).to eq('c') + expect(Chef::ChefFS::PathUtils.descendant_path('C:/ab/b/c', 'c:/ab/B')).to eq('c') + end + + it 'returns nil if the path does not have the given ancestor' do + expect(Chef::ChefFS::PathUtils.descendant_path('/D/E/F', '/A/B/C')).to be_nil + expect(Chef::ChefFS::PathUtils.descendant_path('/A/B/D', '/A/B/C')).to be_nil + end + + it 'returns blank if the ancestor equals the path' do + expect(Chef::ChefFS::PathUtils.descendant_path('/A/B/D', '/A/B/D')).to eq('') + end + end +end diff --git a/spec/unit/event_dispatch/dsl_spec.rb b/spec/unit/event_dispatch/dsl_spec.rb new file mode 100644 index 0000000000..f467ea81ea --- /dev/null +++ b/spec/unit/event_dispatch/dsl_spec.rb @@ -0,0 +1,87 @@ +# +# Author:: Ranjib Dey (<ranjib@linux.com>) +# +# Copyright:: Copyright (c) 2015 Ranjib Dey +# 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/event_dispatch/dsl' + +describe Chef::EventDispatch::DSL do + let(:events) do + Chef::EventDispatch::Dispatcher.new + end + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, nil, events) + end + + before do + Chef.set_run_context(run_context) + end + + after do + Chef.reset! + end + + subject{ described_class.new('test') } + + it 'set handler name' do + subject.on(:run_started) {} + expect(events.subscribers.first.name).to eq('test') + end + + it 'raise error when invalid event type is supplied' do + expect do + subject.on(:foo_bar) {} + end.to raise_error(Chef::Exceptions::InvalidEventType) + end + + it 'register user hooks against valid event type' do + subject.on(:run_failed) {'testhook'} + expect(events.subscribers.first.run_failed).to eq('testhook') + end + + it 'preserve state across event hooks' do + calls = [] + Chef.event_handler do + on :resource_updated do + calls << :updated + end + on :resource_action_start do + calls << :started + end + end + resource = Chef::Resource::RubyBlock.new('foo', run_context) + resource.block { } + resource.run_action(:run) + expect(calls).to eq([:started, :updated]) + end + + it 'preserve instance variables across handler callbacks' do + Chef.event_handler do + on :resource_action_start do + @ivar = [1] + end + on :resource_updated do + @ivar << 2 + end + end + resource = Chef::Resource::RubyBlock.new('foo', run_context) + resource.block { } + resource.run_action(:run) + expect(events.subscribers.first.instance_variable_get(:@ivar)).to eq([1, 2]) + end +end diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb index aaf0c48806..fd6469c146 100644 --- a/spec/unit/json_compat_spec.rb +++ b/spec/unit/json_compat_spec.rb @@ -72,8 +72,6 @@ describe Chef::JSONCompat do end end - # On FreeBSD 10.1 i386 rspec fails with a SystemStackError loading the expect line with more that 252 entries - # https://github.com/chef/chef/issues/3101 describe "with the file with 252 or less nested entries" do let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'nested.json')) } let(:hash) { Chef::JSONCompat.from_json(json) } @@ -84,7 +82,10 @@ describe Chef::JSONCompat do end it "should has 'test' as a 252 nested value" do - expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']).to eq('test') + v = 252.times.inject(hash) do |memo, _| + memo['key'] + end + expect(v).to eq('test') end end end diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb index 45bb4dd16c..1a6317ac00 100644 --- a/spec/unit/knife/client_bulk_delete_spec.rb +++ b/spec/unit/knife/client_bulk_delete_spec.rb @@ -45,7 +45,7 @@ describe Chef::Knife::ClientBulkDelete do clients = Hash.new nonvalidator_client_names.each do |client_name| - client = Chef::ApiClient.new() + client = Chef::ApiClientV1.new() client.name(client_name) allow(client).to receive(:destroy).and_return(true) clients[client_name] = client @@ -59,7 +59,7 @@ describe Chef::Knife::ClientBulkDelete do clients = Hash.new validator_client_names.each do |validator_client_name| - validator_client = Chef::ApiClient.new() + validator_client = Chef::ApiClientV1.new() validator_client.name(validator_client_name) allow(validator_client).to receive(:validator).and_return(true) allow(validator_client).to receive(:destroy).and_return(true) @@ -75,7 +75,7 @@ describe Chef::Knife::ClientBulkDelete do } before(:each) do - allow(Chef::ApiClient).to receive(:list).and_return(clients) + allow(Chef::ApiClientV1).to receive(:list).and_return(clients) end describe "run" do @@ -89,7 +89,7 @@ describe Chef::Knife::ClientBulkDelete do describe "with any clients" do it "should get the list of the clients" do - expect(Chef::ApiClient).to receive(:list) + expect(Chef::ApiClientV1).to receive(:list) knife.run end diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb index 8fecfc885f..a1dcc564e2 100644 --- a/spec/unit/knife/client_create_spec.rb +++ b/spec/unit/knife/client_create_spec.rb @@ -34,7 +34,7 @@ describe Chef::Knife::ClientCreate do end let(:client) do - Chef::ApiClient.new + Chef::ApiClientV1.new end let(:knife) do diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb index 0fb5e0bab7..619009979b 100644 --- a/spec/unit/knife/client_delete_spec.rb +++ b/spec/unit/knife/client_delete_spec.rb @@ -30,7 +30,7 @@ describe Chef::Knife::ClientDelete do describe 'run' do it 'should delete the client' do - expect(@knife).to receive(:delete_object).with(Chef::ApiClient, 'adam', 'client') + expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, 'adam', 'client') @knife.run end @@ -46,8 +46,8 @@ describe Chef::Knife::ClientDelete do before(:each) do allow(Chef::Knife::UI).to receive(:confirm).and_return(true) allow(@knife).to receive(:confirm).and_return(true) - @client = Chef::ApiClient.new - expect(Chef::ApiClient).to receive(:load).and_return(@client) + @client = Chef::ApiClientV1.new + expect(Chef::ApiClientV1).to receive(:load).and_return(@client) end it 'should delete non-validator client if --delete-validators is not set' do diff --git a/spec/unit/knife/client_edit_spec.rb b/spec/unit/knife/client_edit_spec.rb index c040c5e2f2..ad56d9212d 100644 --- a/spec/unit/knife/client_edit_spec.rb +++ b/spec/unit/knife/client_edit_spec.rb @@ -17,16 +17,29 @@ # require 'spec_helper' +require 'chef/api_client_v1' describe Chef::Knife::ClientEdit do before(:each) do @knife = Chef::Knife::ClientEdit.new @knife.name_args = [ 'adam' ] + @knife.config[:disable_editing] = true end describe 'run' do + let(:data) { + { + "name" => "adam", + "validator" => false, + "admin" => false, + "chef_type" => "client", + "create_key" => true + } + } + it 'should edit the client' do - expect(@knife).to receive(:edit_object).with(Chef::ApiClient, 'adam') + allow(Chef::ApiClientV1).to receive(:load).with('adam').and_return(data) + expect(@knife).to receive(:edit_data).with(data).and_return(data) @knife.run end diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/knife/client_list_spec.rb index eff01da4e9..ce0fa4f5e8 100644 --- a/spec/unit/knife/client_list_spec.rb +++ b/spec/unit/knife/client_list_spec.rb @@ -26,7 +26,7 @@ describe Chef::Knife::ClientList do describe 'run' do it 'should list the clients' do - expect(Chef::ApiClient).to receive(:list) + expect(Chef::ApiClientV1).to receive(:list) expect(@knife).to receive(:format_list_for_display) @knife.run end diff --git a/spec/unit/knife/client_reregister_spec.rb b/spec/unit/knife/client_reregister_spec.rb index f1be4ed570..7e763242e4 100644 --- a/spec/unit/knife/client_reregister_spec.rb +++ b/spec/unit/knife/client_reregister_spec.rb @@ -41,7 +41,7 @@ describe Chef::Knife::ClientReregister do context 'when not configured for file output' do it 'reregisters the client and prints the key' do - expect(Chef::ApiClient).to receive(:reregister).with('adam').and_return(@client_mock) + expect(Chef::ApiClientV1).to receive(:reregister).with('adam').and_return(@client_mock) @knife.run expect(@stdout.string).to match( /foo_key/ ) end @@ -49,7 +49,7 @@ describe Chef::Knife::ClientReregister do context 'when configured for file output' do it 'should write the private key to a file' do - expect(Chef::ApiClient).to receive(:reregister).with('adam').and_return(@client_mock) + expect(Chef::ApiClientV1).to receive(:reregister).with('adam').and_return(@client_mock) @knife.config[:file] = '/tmp/monkeypants' filehandle = StringIO.new diff --git a/spec/unit/knife/client_show_spec.rb b/spec/unit/knife/client_show_spec.rb index 8404e8d019..73a876cee0 100644 --- a/spec/unit/knife/client_show_spec.rb +++ b/spec/unit/knife/client_show_spec.rb @@ -27,7 +27,7 @@ describe Chef::Knife::ClientShow do describe 'run' do it 'should list the client' do - expect(Chef::ApiClient).to receive(:load).with('adam').and_return(@client_mock) + expect(Chef::ApiClientV1).to receive(:load).with('adam').and_return(@client_mock) expect(@knife).to receive(:format_for_display).with(@client_mock) @knife.run end @@ -37,7 +37,7 @@ describe Chef::Knife::ClientShow do @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) fake_client_contents = {"foo"=>"bar", "baz"=>"qux"} - expect(Chef::ApiClient).to receive(:load).with('adam').and_return(fake_client_contents) + expect(Chef::ApiClientV1).to receive(:load).with('adam').and_return(fake_client_contents) @knife.run expect(@stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n") end diff --git a/spec/unit/knife/osc_user_create_spec.rb b/spec/unit/knife/osc_user_create_spec.rb index 1b17d0d22f..e4ed78fe2b 100644 --- a/spec/unit/knife/osc_user_create_spec.rb +++ b/spec/unit/knife/osc_user_create_spec.rb @@ -36,19 +36,19 @@ describe Chef::Knife::OscUserCreate do @knife.name_args = [ 'a_user' ] @knife.config[:user_password] = "foobar" - @user = Chef::OscUser.new + @user = Chef::User.new @user.name "a_user" - @user_with_private_key = Chef::OscUser.new + @user_with_private_key = Chef::User.new @user_with_private_key.name "a_user" @user_with_private_key.private_key 'private_key' allow(@user).to receive(:create).and_return(@user_with_private_key) - allow(Chef::OscUser).to receive(:new).and_return(@user) - allow(Chef::OscUser).to receive(:from_hash).and_return(@user) + allow(Chef::User).to receive(:new).and_return(@user) + allow(Chef::User).to receive(:from_hash).and_return(@user) allow(@knife).to receive(:edit_data).and_return(@user.to_hash) end it "creates a new user" do - expect(Chef::OscUser).to receive(:new).and_return(@user) + expect(Chef::User).to receive(:new).and_return(@user) expect(@user).to receive(:create) @knife.run expect(@stderr.string).to match /created user.+a_user/i diff --git a/spec/unit/knife/osc_user_delete_spec.rb b/spec/unit/knife/osc_user_delete_spec.rb index 0e16393ffe..4a3ec4228f 100644 --- a/spec/unit/knife/osc_user_delete_spec.rb +++ b/spec/unit/knife/osc_user_delete_spec.rb @@ -31,7 +31,7 @@ describe Chef::Knife::OscUserDelete do end it 'deletes the user' do - expect(@knife).to receive(:delete_object).with(Chef::OscUser, 'my_user') + expect(@knife).to receive(:delete_object).with(Chef::User, 'my_user') @knife.run end diff --git a/spec/unit/knife/osc_user_edit_spec.rb b/spec/unit/knife/osc_user_edit_spec.rb index 71a9192389..279f2e30ef 100644 --- a/spec/unit/knife/osc_user_edit_spec.rb +++ b/spec/unit/knife/osc_user_edit_spec.rb @@ -38,7 +38,7 @@ describe Chef::Knife::OscUserEdit do it 'loads and edits the user' do data = { :name => "my_user" } - allow(Chef::OscUser).to receive(:load).with("my_user").and_return(data) + allow(Chef::User).to receive(:load).with("my_user").and_return(data) expect(@knife).to receive(:edit_data).with(data).and_return(data) @knife.run end diff --git a/spec/unit/knife/osc_user_list_spec.rb b/spec/unit/knife/osc_user_list_spec.rb index 59a15be058..f496a414b8 100644 --- a/spec/unit/knife/osc_user_list_spec.rb +++ b/spec/unit/knife/osc_user_list_spec.rb @@ -30,7 +30,7 @@ describe Chef::Knife::OscUserList do end it 'lists the users' do - expect(Chef::OscUser).to receive(:list) + expect(Chef::User).to receive(:list) expect(@knife).to receive(:format_list_for_display) @knife.run end diff --git a/spec/unit/knife/osc_user_reregister_spec.rb b/spec/unit/knife/osc_user_reregister_spec.rb index 406bbf1f3e..989eb180f1 100644 --- a/spec/unit/knife/osc_user_reregister_spec.rb +++ b/spec/unit/knife/osc_user_reregister_spec.rb @@ -29,7 +29,7 @@ describe Chef::Knife::OscUserReregister do @knife = Chef::Knife::OscUserReregister.new @knife.name_args = [ 'a_user' ] @user_mock = double('user_mock', :private_key => "private_key") - allow(Chef::OscUser).to receive(:load).and_return(@user_mock) + allow(Chef::User).to receive(:load).and_return(@user_mock) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end diff --git a/spec/unit/knife/osc_user_show_spec.rb b/spec/unit/knife/osc_user_show_spec.rb index 67b9b45809..18d2086099 100644 --- a/spec/unit/knife/osc_user_show_spec.rb +++ b/spec/unit/knife/osc_user_show_spec.rb @@ -32,7 +32,7 @@ describe Chef::Knife::OscUserShow do end it 'loads and displays the user' do - expect(Chef::OscUser).to receive(:load).with('my_user').and_return(@user_mock) + expect(Chef::User).to receive(:load).with('my_user').and_return(@user_mock) expect(@knife).to receive(:format_for_display).with(@user_mock) @knife.run end diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb index 8eda555108..fd46c47d99 100644 --- a/spec/unit/knife/ssl_check_spec.rb +++ b/spec/unit/knife/ssl_check_spec.rb @@ -163,6 +163,7 @@ E expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn) expect(ssl_socket).to receive(:connect) # no error expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error + expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error end it "prints a success message" do @@ -197,6 +198,7 @@ E expect(ssl_socket).to receive(:post_connection_check). with("foo.example.com"). and_raise(OpenSSL::SSL::SSLError) + expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error expect(ssl_socket_for_debug).to receive(:connect) expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt) end @@ -215,6 +217,8 @@ E expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs expect(ssl_socket).to receive(:connect). and_raise(OpenSSL::SSL::SSLError) + expect(ssl_socket).to receive(:hostname=). + with("foo.example.com") # no error expect(ssl_socket_for_debug).to receive(:connect) expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt) end diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb index 49d62cc2d7..fa5c8324b4 100644 --- a/spec/unit/knife/user_create_spec.rb +++ b/spec/unit/knife/user_create_spec.rb @@ -186,7 +186,7 @@ describe Chef::Knife::UserCreate do context "when a private_key is returned" do before do - allow(knife).to receive(:create_user_from_hash).and_return(Chef::User.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"}))) + allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"}))) end context "when --file is passed" do diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb index e49c781358..a24160624a 100644 --- a/spec/unit/knife/user_delete_spec.rb +++ b/spec/unit/knife/user_delete_spec.rb @@ -26,7 +26,7 @@ describe Chef::Knife::UserDelete do before(:each) do Chef::Knife::UserDelete.load_deps knife.name_args = [ 'my_user' ] - allow(Chef::User).to receive(:load).and_return(user) + allow(Chef::UserV1).to receive(:load).and_return(user) allow(user).to receive(:username).and_return('my_user') allow(knife.ui).to receive(:stderr).and_return(stdout) allow(knife.ui).to receive(:stdout).and_return(stdout) @@ -51,7 +51,7 @@ describe Chef::Knife::UserDelete do end it 'deletes the user' do - #expect(knife).to receive(:delete_object).with(Chef::User, 'my_user') + #expect(knife).to receive(:delete_object).with(Chef::UserV1, 'my_user') expect(knife).to receive(:delete_object).with('my_user') knife.run end diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb index 15a7726b20..a21d982d29 100644 --- a/spec/unit/knife/user_edit_spec.rb +++ b/spec/unit/knife/user_edit_spec.rb @@ -36,7 +36,7 @@ describe Chef::Knife::UserEdit do context "when the username field is not supported by the server" do before do allow(knife).to receive(:run_osc_11_user_edit).and_raise(SystemExit) - allow(Chef::User).to receive(:load).and_return({"username" => nil}) + allow(Chef::UserV1).to receive(:load).and_return({"username" => nil}) end it "displays the osc warning" do @@ -52,7 +52,7 @@ describe Chef::Knife::UserEdit do it 'loads and edits the user' do data = { "username" => "my_user" } - allow(Chef::User).to receive(:load).with("my_user").and_return(data) + allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data) expect(knife).to receive(:edit_data).with(data).and_return(data) knife.run end diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb index 9990cc802d..fa2bac426e 100644 --- a/spec/unit/knife/user_list_spec.rb +++ b/spec/unit/knife/user_list_spec.rb @@ -29,7 +29,7 @@ describe Chef::Knife::UserList do end it 'lists the users' do - expect(Chef::User).to receive(:list) + expect(Chef::UserV1).to receive(:list) expect(knife).to receive(:format_list_for_display) knife.run end diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb index 412a6ec374..89aa6726cd 100644 --- a/spec/unit/knife/user_reregister_spec.rb +++ b/spec/unit/knife/user_reregister_spec.rb @@ -26,7 +26,7 @@ describe Chef::Knife::UserReregister do before do Chef::Knife::UserReregister.load_deps knife.name_args = [ 'a_user' ] - allow(Chef::User).to receive(:load).and_return(user_mock) + allow(Chef::UserV1).to receive(:load).and_return(user_mock) allow(knife.ui).to receive(:stdout).and_return(stdout) allow(knife.ui).to receive(:stderr).and_return(stdout) allow(user_mock).to receive(:username).and_return('a_user') diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb index 43392a3a5c..7c39e428c0 100644 --- a/spec/unit/knife/user_show_spec.rb +++ b/spec/unit/knife/user_show_spec.rb @@ -35,7 +35,7 @@ describe Chef::Knife::UserShow do context "when the username field is not supported by the server" do before do allow(knife).to receive(:run_osc_11_user_show).and_raise(SystemExit) - allow(Chef::User).to receive(:load).with('my_user').and_return(user_mock) + allow(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock) allow(user_mock).to receive(:username).and_return(nil) end @@ -51,7 +51,7 @@ describe Chef::Knife::UserShow do end it 'loads and displays the user' do - expect(Chef::User).to receive(:load).with('my_user').and_return(user_mock) + expect(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock) expect(knife).to receive(:format_for_display).with(user_mock) knife.run end diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index 784ff966cd..bcb64cb21e 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -163,7 +163,7 @@ describe "LWRP" do it "Should load the old content, and not the new" do resource = Chef::ResourceResolver.resolve(:lwrp_foo) expect(resource).to eq @original_resource - expect(resource.default_action).to eq(:pass_buck) + expect(resource.default_action).to eq([:pass_buck]) expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey end end @@ -177,10 +177,6 @@ describe "LWRP" do end end - it "should load the resource into a properly-named class and emit a warning when it is initialized" do - expect { Chef::Resource::LwrpFoo.new('hi') }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) - end - it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo)) end @@ -202,7 +198,7 @@ describe "LWRP" do end it "should set the specified action as the default action" do - expect(get_lwrp(:lwrp_foo).new("blah").action).to eq(:pass_buck) + expect(get_lwrp(:lwrp_foo).new("blah").action).to eq([:pass_buck]) end it "should create a method for each attribute" do @@ -228,127 +224,6 @@ describe "LWRP" do expect(cls.node[:penguin_name]).to eql("jackass") end - context "resource class created" do - before do - @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] - Chef::Config[:treat_deprecation_warnings_as_errors] = false - end - after do - Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors - end - - it "should load the resource into a properly-named class" do - expect(Chef::Resource::LwrpFoo).to be_kind_of(Class) - expect(Chef::Resource::LwrpFoo <= Chef::Resource::LWRPBase).to be_truthy - end - - it "get_lwrp(:lwrp_foo).new is a Chef::Resource::LwrpFoo" do - lwrp = get_lwrp(:lwrp_foo).new('hi') - expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy - expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy - expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy - expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy - end - - it "Chef::Resource::LwrpFoo.new is a get_lwrp(:lwrp_foo)" do - lwrp = Chef::Resource::LwrpFoo.new('hi') - expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy - expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy - expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy - expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy - end - - it "works even if LwrpFoo exists in the top level" do - module ::LwrpFoo - end - expect(Chef::Resource::LwrpFoo).not_to eq(::LwrpFoo) - end - - context "with a subclass of get_lwrp(:lwrp_foo)" do - let(:subclass) do - Class.new(get_lwrp(:lwrp_foo)) - end - - it "subclass.new is a subclass" do - lwrp = subclass.new('hi') - expect(lwrp.kind_of?(subclass)).to be_truthy - expect(lwrp.is_a?(subclass)).to be_truthy - expect(subclass === lwrp).to be_truthy - expect(lwrp.class === subclass) - end - it "subclass.new is a Chef::Resource::LwrpFoo" do - lwrp = subclass.new('hi') - expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy - expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy - expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy - expect(lwrp.class === Chef::Resource::LwrpFoo) - end - it "subclass.new is a get_lwrp(:lwrp_foo)" do - lwrp = subclass.new('hi') - expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy - expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy - expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy - expect(lwrp.class === get_lwrp(:lwrp_foo)) - end - it "Chef::Resource::LwrpFoo.new is *not* a subclass" do - lwrp = Chef::Resource::LwrpFoo.new('hi') - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === Chef::Resource::LwrpFoo).to be_falsey - end - it "get_lwrp(:lwrp_foo).new is *not* a subclass" do - lwrp = get_lwrp(:lwrp_foo).new('hi') - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === get_lwrp(:lwrp_foo)).to be_falsey - end - end - - context "with a subclass of Chef::Resource::LwrpFoo" do - let(:subclass) do - Class.new(Chef::Resource::LwrpFoo) - end - - it "subclass.new is a subclass" do - lwrp = subclass.new('hi') - expect(lwrp.kind_of?(subclass)).to be_truthy - expect(lwrp.is_a?(subclass)).to be_truthy - expect(subclass === lwrp).to be_truthy - expect(lwrp.class === subclass) - end - it "subclass.new is a Chef::Resource::LwrpFoo" do - lwrp = subclass.new('hi') - expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy - expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy - expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy - expect(lwrp.class === Chef::Resource::LwrpFoo) - end - it "subclass.new is a get_lwrp(:lwrp_foo)" do - lwrp = subclass.new('hi') - expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy - expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy - expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy - expect(lwrp.class === get_lwrp(:lwrp_foo)) - end - it "Chef::Resource::LwrpFoo.new is *not* a subclass" do - lwrp = Chef::Resource::LwrpFoo.new('hi') - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === Chef::Resource::LwrpFoo).to be_falsey - end - it "get_lwrp(:lwrp_foo).new is *not* a subclass" do - lwrp = get_lwrp(:lwrp_foo).new('hi') - expect(lwrp.kind_of?(subclass)).to be_falsey - expect(lwrp.is_a?(subclass)).to be_falsey - expect(subclass === lwrp.class).to be_falsey - expect(subclass === get_lwrp(:lwrp_foo)).to be_falsey - end - end - end - context "resource_name" do let(:klass) { Class.new(Chef::Resource::LWRPBase) } @@ -419,7 +294,7 @@ describe "LWRP" do end it "delegates #default_action to the parent" do - expect(child.default_action).to eq(:eat) + expect(child.default_action).to eq([:eat]) end end @@ -436,7 +311,7 @@ describe "LWRP" do end it "does not delegate #default_action to the parent" do - expect(child.default_action).to eq(:dont_eat) + expect(child.default_action).to eq([:dont_eat]) end end @@ -564,7 +439,7 @@ describe "LWRP" do end it "sets itself as a provider for a resource of the same name" do - found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :lwrp_buck_passer) + found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :lwrp_buck_passer) # we bypass the per-file loading to get the file to load each time, # which creates the LWRP class repeatedly. New things get prepended to # the list of providers. @@ -576,7 +451,7 @@ describe "LWRP" do let(:lwrp_cookbok_name) { "l_w_r_p" } it "sets itself as a provider for a resource of the same name" do - found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer) + found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer) expect(found_providers.size).to eq(1) expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) end @@ -587,10 +462,10 @@ describe "LWRP" do let(:lwrp_cookbok_name) { "l-w-r-p" } it "sets itself as a provider for a resource of the same name" do - incorrect_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :'l-w-r-p_buck_passer') + incorrect_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :'l-w-r-p_buck_passer') expect(incorrect_providers).to eq([]) - found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer) + found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer) expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) end end @@ -704,7 +579,144 @@ describe "LWRP" do end end - end + context "resource class created" do + before(:context) do + @tmpdir = Dir.mktmpdir("lwrp_test") + resource_path = File.join(@tmpdir, "once.rb") + IO.write(resource_path, "default_action :create") + + @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] + Chef::Config[:treat_deprecation_warnings_as_errors] = false + Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil) + end + + after(:context) do + FileUtils.remove_entry @tmpdir + Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors + end + + it "should load the resource into a properly-named class" do + expect(Chef::Resource::LwrpOnce).to be_kind_of(Class) + expect(Chef::Resource::LwrpOnce <= Chef::Resource::LWRPBase).to be_truthy + end + + it "get_lwrp(:lwrp_once).new is a Chef::Resource::LwrpOnce" do + lwrp = get_lwrp(:lwrp_once).new('hi') + expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy + expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy + expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy + expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy + end + + it "Chef::Resource::LwrpOnce.new is a get_lwrp(:lwrp_once)" do + lwrp = Chef::Resource::LwrpOnce.new('hi') + expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy + expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy + expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy + expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy + end + + it "works even if LwrpOnce exists in the top level" do + module ::LwrpOnce + end + expect(Chef::Resource::LwrpOnce).not_to eq(::LwrpOnce) + end + + it "allows monkey patching of the lwrp through Chef::Resource" do + monkey = Module.new do + def issue_3607 + end + end + Chef::Resource::LwrpOnce.send(:include, monkey) + expect { get_lwrp(:lwrp_once).new("blah").issue_3607 }.not_to raise_error + end + + context "with a subclass of get_lwrp(:lwrp_once)" do + let(:subclass) do + Class.new(get_lwrp(:lwrp_once)) + end + + it "subclass.new is a subclass" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(subclass)).to be_truthy + expect(lwrp.is_a?(subclass)).to be_truthy + expect(subclass === lwrp).to be_truthy + expect(lwrp.class === subclass) + end + it "subclass.new is a Chef::Resource::LwrpOnce" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy + expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy + expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy + expect(lwrp.class === Chef::Resource::LwrpOnce) + end + it "subclass.new is a get_lwrp(:lwrp_once)" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy + expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy + expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy + expect(lwrp.class === get_lwrp(:lwrp_once)) + end + it "Chef::Resource::LwrpOnce.new is *not* a subclass" do + lwrp = Chef::Resource::LwrpOnce.new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === Chef::Resource::LwrpOnce).to be_falsey + end + it "get_lwrp(:lwrp_once).new is *not* a subclass" do + lwrp = get_lwrp(:lwrp_once).new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === get_lwrp(:lwrp_once)).to be_falsey + end + end + + context "with a subclass of Chef::Resource::LwrpOnce" do + let(:subclass) do + Class.new(Chef::Resource::LwrpOnce) + end + + it "subclass.new is a subclass" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(subclass)).to be_truthy + expect(lwrp.is_a?(subclass)).to be_truthy + expect(subclass === lwrp).to be_truthy + expect(lwrp.class === subclass) + end + it "subclass.new is a Chef::Resource::LwrpOnce" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy + expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy + expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy + expect(lwrp.class === Chef::Resource::LwrpOnce) + end + it "subclass.new is a get_lwrp(:lwrp_once)" do + lwrp = subclass.new('hi') + expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy + expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy + expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy + expect(lwrp.class === get_lwrp(:lwrp_once)) + end + it "Chef::Resource::LwrpOnce.new is *not* a subclass" do + lwrp = Chef::Resource::LwrpOnce.new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === Chef::Resource::LwrpOnce).to be_falsey + end + it "get_lwrp(:lwrp_once).new is *not* a subclass" do + lwrp = get_lwrp(:lwrp_once).new('hi') + expect(lwrp.kind_of?(subclass)).to be_falsey + expect(lwrp.is_a?(subclass)).to be_falsey + expect(subclass === lwrp.class).to be_falsey + expect(subclass === get_lwrp(:lwrp_once)).to be_falsey + end + end + end end + + diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb index 85e1c1abab..3724bbf583 100644 --- a/spec/unit/mixin/params_validate_spec.rb +++ b/spec/unit/mixin/params_validate_spec.rb @@ -21,6 +21,8 @@ require 'spec_helper' class TinyClass include Chef::Mixin::ParamsValidate + attr_reader :name + def music(is_good=true) is_good end @@ -331,11 +333,11 @@ describe Chef::Mixin::ParamsValidate do it "asserts that a value returns false from a predicate method" do expect do @vo.validate({:not_blank => "should pass"}, - {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}}) + {:not_blank => {:cannot_be => [ :nil, :empty ]}}) end.not_to raise_error expect do @vo.validate({:not_blank => ""}, - {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}}) + {:not_blank => {:cannot_be => [ :nil, :empty ]}}) end.to raise_error(Chef::Exceptions::ValidationFailed) end diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb index 9b5ff5e8c6..7b37ea59f4 100644 --- a/spec/unit/node_map_spec.rb +++ b/spec/unit/node_map_spec.rb @@ -131,6 +131,18 @@ describe Chef::NodeMap do allow(node).to receive(:[]).with(:platform_version).and_return("6.0") expect(node_map.get(node, :thing)).to eql(nil) end + + context "when there is a less specific definition" do + before do + node_map.set(:thing, :bar, platform_family: "rhel") + end + + it "returns the value when the node matches" do + allow(node).to receive(:[]).with(:platform_family).and_return("rhel") + allow(node).to receive(:[]).with(:platform_version).and_return("7.0") + expect(node_map.get(node, :thing)).to eql(:foo) + end + end end describe "resource back-compat testing" do diff --git a/spec/unit/osc_user_spec.rb b/spec/unit/osc_user_spec.rb deleted file mode 100644 index 16731b47bd..0000000000 --- a/spec/unit/osc_user_spec.rb +++ /dev/null @@ -1,276 +0,0 @@ -# -# Author:: Steven Danna (steve@opscode.com) -# Copyright:: Copyright (c) 2012 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# DEPRECATION NOTE -# This code only remains to support users still operating with -# Open Source Chef Server 11 and should be removed once support -# for OSC 11 ends. New development should occur in user_spec.rb. - -require 'spec_helper' - -require 'chef/osc_user' -require 'tempfile' - -describe Chef::OscUser do - before(:each) do - @user = Chef::OscUser.new - end - - describe "initialize" do - it "should be a Chef::OscUser" do - expect(@user).to be_a_kind_of(Chef::OscUser) - end - end - - describe "name" do - it "should let you set the name to a string" do - expect(@user.name("ops_master")).to eq("ops_master") - end - - it "should return the current name" do - @user.name "ops_master" - expect(@user.name).to eq("ops_master") - end - - # It is not feasible to check all invalid characters. Here are a few - # that we probably care about. - it "should not accept invalid characters" do - # capital letters - expect { @user.name "Bar" }.to raise_error(ArgumentError) - # slashes - expect { @user.name "foo/bar" }.to raise_error(ArgumentError) - # ? - expect { @user.name "foo?" }.to raise_error(ArgumentError) - # & - expect { @user.name "foo&" }.to raise_error(ArgumentError) - end - - - it "should not accept spaces" do - expect { @user.name "ops master" }.to raise_error(ArgumentError) - end - - it "should throw an ArgumentError if you feed it anything but a string" do - expect { @user.name Hash.new }.to raise_error(ArgumentError) - end - end - - describe "admin" do - it "should let you set the admin bit" do - expect(@user.admin(true)).to eq(true) - end - - it "should return the current admin value" do - @user.admin true - expect(@user.admin).to eq(true) - end - - it "should default to false" do - expect(@user.admin).to eq(false) - end - - it "should throw an ArgumentError if you feed it anything but true or false" do - expect { @user.name Hash.new }.to raise_error(ArgumentError) - end - end - - describe "public_key" do - it "should let you set the public key" do - expect(@user.public_key("super public")).to eq("super public") - end - - it "should return the current public key" do - @user.public_key("super public") - expect(@user.public_key).to eq("super public") - end - - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.public_key Hash.new }.to raise_error(ArgumentError) - end - end - - describe "private_key" do - it "should let you set the private key" do - expect(@user.private_key("super private")).to eq("super private") - end - - it "should return the private key" do - @user.private_key("super private") - expect(@user.private_key).to eq("super private") - end - - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.private_key Hash.new }.to raise_error(ArgumentError) - end - end - - describe "when serializing to JSON" do - before(:each) do - @user.name("black") - @user.public_key("crowes") - @json = @user.to_json - end - - it "serializes as a JSON object" do - expect(@json).to match(/^\{.+\}$/) - end - - it "includes the name value" do - expect(@json).to include(%q{"name":"black"}) - end - - it "includes the public key value" do - expect(@json).to include(%{"public_key":"crowes"}) - end - - it "includes the 'admin' flag" do - expect(@json).to include(%q{"admin":false}) - end - - it "includes the private key when present" do - @user.private_key("monkeypants") - expect(@user.to_json).to include(%q{"private_key":"monkeypants"}) - end - - it "does not include the private key if not present" do - expect(@json).not_to include("private_key") - end - - it "includes the password if present" do - @user.password "password" - expect(@user.to_json).to include(%q{"password":"password"}) - end - - it "does not include the password if not present" do - expect(@json).not_to include("password") - end - - include_examples "to_json equivalent to Chef::JSONCompat.to_json" do - let(:jsonable) { @user } - end - end - - describe "when deserializing from JSON" do - before(:each) do - user = { "name" => "mr_spinks", - "public_key" => "turtles", - "private_key" => "pandas", - "password" => "password", - "admin" => true } - @user = Chef::OscUser.from_json(Chef::JSONCompat.to_json(user)) - end - - it "should deserialize to a Chef::OscUser object" do - expect(@user).to be_a_kind_of(Chef::OscUser) - end - - it "preserves the name" do - expect(@user.name).to eq("mr_spinks") - end - - it "preserves the public key" do - expect(@user.public_key).to eq("turtles") - end - - it "preserves the admin status" do - expect(@user.admin).to be_truthy - end - - it "includes the private key if present" do - expect(@user.private_key).to eq("pandas") - end - - it "includes the password if present" do - expect(@user.password).to eq("password") - end - - end - - describe "API Interactions" do - before (:each) do - @user = Chef::OscUser.new - @user.name "foobar" - @http_client = double("Chef::REST mock") - allow(Chef::REST).to receive(:new).and_return(@http_client) - end - - describe "list" do - before(:each) do - Chef::Config[:chef_server_url] = "http://www.example.com" - @osc_response = { "admin" => "http://www.example.com/users/admin"} - @ohc_response = [ { "user" => { "username" => "admin" }} ] - allow(Chef::OscUser).to receive(:load).with("admin").and_return(@user) - @osc_inflated_response = { "admin" => @user } - end - - it "lists all clients on an OSC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) - expect(Chef::OscUser.list).to eq(@osc_response) - end - - it "inflate all clients on an OSC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response) - expect(Chef::OscUser.list(true)).to eq(@osc_inflated_response) - end - - it "lists all clients on an OHC/OPC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) - # We expect that Chef::OscUser.list will give a consistent response - # so OHC API responses should be transformed to OSC-style output. - expect(Chef::OscUser.list).to eq(@osc_response) - end - - it "inflate all clients on an OHC/OPC server" do - allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response) - expect(Chef::OscUser.list(true)).to eq(@osc_inflated_response) - end - end - - describe "create" do - it "creates a new user via the API" do - @user.password "password" - expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({}) - @user.create - end - end - - describe "read" do - it "loads a named user from the API" do - expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"}) - user = Chef::OscUser.load("foobar") - expect(user.name).to eq("foobar") - expect(user.admin).to eq(true) - expect(user.public_key).to eq("pubkey") - end - end - - describe "update" do - it "updates an existing user on via the API" do - expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({}) - @user.update - end - end - - describe "destroy" do - it "deletes the specified user via the API" do - expect(@http_client).to receive(:delete_rest).with("users/foobar") - @user.destroy - end - end - end -end diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb index 36325d5411..34b46f657f 100644 --- a/spec/unit/platform_spec.rb +++ b/spec/unit/platform_spec.rb @@ -103,7 +103,7 @@ describe Chef::Platform do end it "should raise an exception if a provider cannot be found for a resource type" do - expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(ArgumentError) + expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(Chef::Exceptions::ProviderNotFound) end it "should look up a provider for a resource with a Chef::Resource object" do diff --git a/spec/unit/property/state_spec.rb b/spec/unit/property/state_spec.rb index 80ebe01a41..e7fee0387f 100644 --- a/spec/unit/property/state_spec.rb +++ b/spec/unit/property/state_spec.rb @@ -50,20 +50,20 @@ describe "Chef::Resource#identity and #state" do end # identity - context "Chef::Resource#identity_attr" do + context "Chef::Resource#identity_properties" do with_property ":x" do - # it "name is the default identity" do - # expect(resource_class.identity_attr).to eq :name - # expect(resource_class.properties[:name].identity?).to be_falsey - # expect(resource.name).to eq 'blah' - # expect(resource.identity).to eq 'blah' - # end + it "name is the default identity" do + expect(resource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ] + expect(Chef::Resource.properties[:name].identity?).to be_falsey + expect(resource.name).to eq 'blah' + expect(resource.identity).to eq 'blah' + end - it "identity_attr :x changes the identity" do - expect(resource_class.identity_attr :x).to eq :x - expect(resource_class.identity_attr).to eq :x - # expect(resource_class.properties[:name].identity?).to be_falsey - # expect(resource_class.properties[:x].identity?).to be_truthy + it "identity_properties :x changes the identity" do + expect(resource_class.identity_properties :x).to eq [ resource_class.properties[:x] ] + expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] + expect(Chef::Resource.properties[:name].identity?).to be_falsey + expect(resource_class.properties[:x].identity?).to be_truthy expect(resource.x 'woo').to eq 'woo' expect(resource.x).to eq 'woo' @@ -72,28 +72,31 @@ describe "Chef::Resource#identity and #state" do expect(resource.identity).to eq 'woo' end - # with_property ":y, identity: true" do - # context "and identity_attr :x" do - # before do - # resource_class.class_eval do - # identity_attr :x - # end - # end - # - # it "only returns :x as identity" do - # resource.x 'foo' - # resource.y 'bar' - # expect(resource_class.identity_attr).to eq :x - # expect(resource.identity).to eq 'foo' - # end - # it "does not flip y.desired_state off" do - # resource.x 'foo' - # resource.y 'bar' - # expect(resource_class.state_attrs).to eq [ :x, :y ] - # expect(resource.state).to eq({ x: 'foo', y: 'bar' }) - # end - # end - # end + with_property ":y, identity: true" do + context "and identity_properties :x" do + before do + resource_class.class_eval do + identity_properties :x + end + end + + it "only returns :x as identity" do + resource.x 'foo' + resource.y 'bar' + expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] + expect(resource.identity).to eq 'foo' + end + it "does not flip y.desired_state off" do + resource.x 'foo' + resource.y 'bar' + expect(resource_class.state_properties).to eq [ + resource_class.properties[:x], + resource_class.properties[:y] + ] + expect(resource.state_for_resource_reporter).to eq(x: 'foo', y: 'bar') + end + end + end context "With a subclass" do let(:subresource_class) do @@ -107,57 +110,63 @@ describe "Chef::Resource#identity and #state" do end it "name is the default identity on the subclass" do - # expect(subresource_class.identity_attr).to eq :name - # expect(subresource_class.properties[:name].identity?).to be_falsey + expect(subresource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ] + expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(subresource.name).to eq 'sub' expect(subresource.identity).to eq 'sub' end - context "With identity_attr :x on the superclass" do + context "With identity_properties :x on the superclass" do before do resource_class.class_eval do - identity_attr :x + identity_properties :x end end it "The subclass inherits :x as identity" do - expect(subresource_class.identity_attr).to eq :x - # expect(subresource_class.properties[:name].identity?).to be_falsey - # expect(subresource_class.properties[:x].identity?).to be_truthy + expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x] ] + expect(Chef::Resource.properties[:name].identity?).to be_falsey + expect(subresource_class.properties[:x].identity?).to be_truthy subresource.x 'foo' expect(subresource.identity).to eq 'foo' end - # context "With property :y, identity: true on the subclass" do - # before do - # subresource_class.class_eval do - # property :y, identity: true - # end - # end - # it "The subclass's identity includes both x and y" do - # expect(subresource_class.identity_attr).to eq :x - # subresource.x 'foo' - # subresource.y 'bar' - # expect(subresource.identity).to eq({ x: 'foo', y: 'bar' }) - # end - # end + context "With property :y, identity: true on the subclass" do + before do + subresource_class.class_eval do + property :y, identity: true + end + end + it "The subclass's identity includes both x and y" do + expect(subresource_class.identity_properties).to eq [ + subresource_class.properties[:x], + subresource_class.properties[:y] + ] + subresource.x 'foo' + subresource.y 'bar' + expect(subresource.identity).to eq(x: 'foo', y: 'bar') + end + end with_property ":y, String" do - context "With identity_attr :y on the subclass" do + context "With identity_properties :y on the subclass" do before do subresource_class.class_eval do - identity_attr :y + identity_properties :y end end - # it "y is part of state" do - # subresource.x 'foo' - # subresource.y 'bar' - # expect(subresource.state).to eq({ x: 'foo', y: 'bar' }) - # expect(subresource_class.state_attrs).to eq [ :x, :y ] - # end + it "y is part of state" do + subresource.x 'foo' + subresource.y 'bar' + expect(subresource.state_for_resource_reporter).to eq(x: 'foo', y: 'bar') + expect(subresource_class.state_properties).to eq [ + subresource_class.properties[:x], + subresource_class.properties[:y] + ] + end it "y is the identity" do - expect(subresource_class.identity_attr).to eq :y + expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:y] ] subresource.x 'foo' subresource.y 'bar' expect(subresource.identity).to eq 'bar' @@ -171,24 +180,24 @@ describe "Chef::Resource#identity and #state" do end end - # with_property ":string_only, String, identity: true", ":string_only2, String" do - # it "identity_attr does not change validation" do - # resource_class.identity_attr :string_only - # expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed - # expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed - # end - # end - # - # with_property ":x, desired_state: false" do - # it "identity_attr does not flip on desired_state" do - # resource_class.identity_attr :x - # resource.x 'hi' - # expect(resource.identity).to eq 'hi' - # # expect(resource_class.properties[:x].desired_state?).to be_falsey - # expect(resource_class.state_attrs).to eq [] - # expect(resource.state).to eq({}) - # end - # end + with_property ":string_only, String, identity: true", ":string_only2, String" do + it "identity_properties does not change validation" do + resource_class.identity_properties :string_only + expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed + expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed + end + end + + with_property ":x, desired_state: false" do + it "identity_properties does not change desired_state" do + resource_class.identity_properties :x + resource.x 'hi' + expect(resource.identity).to eq 'hi' + expect(resource_class.properties[:x].desired_state?).to be_falsey + expect(resource_class.state_properties).to eq [] + expect(resource.state_for_resource_reporter).to eq({}) + end + end context "With custom property custom_property defined only as methods, using different variables for storage" do before do @@ -202,24 +211,26 @@ describe "Chef::Resource#identity and #state" do end end - context "And identity_attr :custom_property" do + context "And identity_properties :custom_property" do before do resource_class.class_eval do - identity_attr :custom_property + identity_properties :custom_property end end - it "identity_attr comes back as :custom_property" do - # expect(resource_class.properties[:custom_property].identity?).to be_truthy - expect(resource_class.identity_attr).to eq :custom_property + it "identity_properties comes back as :custom_property" do + expect(resource_class.properties[:custom_property].identity?).to be_truthy + expect(resource_class.identity_properties).to eq [ resource_class.properties[:custom_property] ] end - # it "custom_property becomes part of desired_state" do - # resource.custom_property = 1 - # expect(resource.state).to eq({ custom_property: 6 }) - # expect(resource_class.properties[:custom_property].desired_state?).to be_truthy - # expect(resource_class.state_attrs).to eq [ :custom_property ] - # end - it "identity_attr does not change custom_property's getter or setter" do + it "custom_property becomes part of desired_state" do + resource.custom_property = 1 + expect(resource.state_for_resource_reporter).to eq(custom_property: 6) + expect(resource_class.properties[:custom_property].desired_state?).to be_truthy + expect(resource_class.state_properties).to eq [ + resource_class.properties[:custom_property] + ] + end + it "identity_properties does not change custom_property's getter or setter" do resource.custom_property = 1 expect(resource.custom_property).to eq 6 end @@ -232,111 +243,111 @@ describe "Chef::Resource#identity and #state" do end end - # context "PropertyType#identity" do - # with_property ":x, identity: true" do - # it "name is only part of the identity if an identity attribute is defined" do - # expect(resource_class.identity_attr).to eq :x - # resource.x 'woo' - # expect(resource.identity).to eq 'woo' - # end - # end - # - # with_property ":x, identity: true, default: 'xxx'", - # ":y, identity: true, default: 'yyy'", - # ":z, identity: true, default: 'zzz'" do - # it "identity_attr returns the first identity attribute if multiple are defined" do - # expect(resource_class.identity_attr).to eq :x - # end - # it "identity returns all identity values in a hash if multiple are defined" do - # resource.x 'foo' - # resource.y 'bar' - # resource.z 'baz' - # expect(resource.identity).to eq({ x: 'foo', y: 'bar', z: 'baz' }) - # end - # it "identity returns only identity values that are set, and does not include defaults" do - # resource.x 'foo' - # resource.z 'baz' - # expect(resource.identity).to eq({ x: 'foo', z: 'baz' }) - # end - # it "identity returns only set identity values in a hash, if there is only one set identity value" do - # resource.x 'foo' - # expect(resource.identity).to eq({ x: 'foo' }) - # end - # it "identity returns an empty hash if no identity values are set" do - # expect(resource.identity).to eq({}) - # end - # it "identity_attr wipes out any other identity attributes if multiple are defined" do - # resource_class.identity_attr :y - # resource.x 'foo' - # resource.y 'bar' - # resource.z 'baz' - # expect(resource.identity).to eq 'bar' - # end - # end - # - # with_property ":x, identity: true, name_property: true" do - # it "identity when x is not defined returns the value of x" do - # expect(resource.identity).to eq 'blah' - # end - # it "state when x is not defined returns the value of x" do - # expect(resource.state).to eq({ x: 'blah' }) - # end - # end - # end - - # state_attrs - context "Chef::Resource#state_attrs" do - it "name is not part of state_attrs" do - expect(Chef::Resource.state_attrs).to eq [] - expect(resource_class.state_attrs).to eq [] - expect(resource.state).to eq({}) + context "Property#identity" do + with_property ":x, identity: true" do + it "name is only part of the identity if an identity attribute is defined" do + expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] + resource.x 'woo' + expect(resource.identity).to eq 'woo' + end + end + + with_property ":x, identity: true, default: 'xxx'", + ":y, identity: true, default: 'yyy'", + ":z, identity: true, default: 'zzz'" do + it "identity_property raises an error if multiple identity values are defined" do + expect { resource_class.identity_property }.to raise_error Chef::Exceptions::MultipleIdentityError + end + it "identity_attr raises an error if multiple identity values are defined" do + expect { resource_class.identity_attr }.to raise_error Chef::Exceptions::MultipleIdentityError + end + it "identity returns all identity values in a hash if multiple are defined" do + resource.x 'foo' + resource.y 'bar' + resource.z 'baz' + expect(resource.identity).to eq(x: 'foo', y: 'bar', z: 'baz') + end + it "identity returns all values whether any value is set or not" do + expect(resource.identity).to eq(x: 'xxx', y: 'yyy', z: 'zzz') + end + it "identity_properties wipes out any other identity attributes if multiple are defined" do + resource_class.identity_properties :y + resource.x 'foo' + resource.y 'bar' + resource.z 'baz' + expect(resource.identity).to eq 'bar' + end + end + + with_property ":x, identity: true, name_property: true" do + it "identity when x is not defined returns the value of x" do + expect(resource.identity).to eq 'blah' + end + it "state when x is not defined returns the value of x" do + expect(resource.state_for_resource_reporter).to eq(x: 'blah') + end + end + end + + # state_properties + context "Chef::Resource#state_properties" do + it "state_properties is empty by default" do + expect(Chef::Resource.state_properties).to eq [] + expect(resource.state_for_resource_reporter).to eq({}) end - # with_property ":x", ":y", ":z" do - # it "x, y and z are state attributes" do - # resource.x 1 - # resource.y 2 - # resource.z 3 - # expect(resource_class.state_attrs).to eq [ :x, :y, :z ] - # expect(resource.state).to eq(x: 1, y: 2, z: 3) - # end - # it "values that are not set are not included in state" do - # resource.x 1 - # expect(resource.state).to eq(x: 1) - # end - # it "when no values are set, nothing is included in state" do - # end - # end - # - # with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do - # it "x and z are state attributes, and y is not" do - # resource.x 1 - # resource.y 2 - # resource.z 3 - # expect(resource_class.state_attrs).to eq [ :x, :z ] - # expect(resource.state).to eq(x: 1, z: 3) - # end - # end - - # with_property ":x, name_property: true" do - # it "Unset values with name_property are included in state" do - # expect(resource.state).to eq(x: 'blah') - # end - # it "Set values with name_property are included in state" do - # resource.x 1 - # expect(resource.state).to eq(x: 1) - # end - # end - - # with_property ":x, default: 1" do - # it "Unset values with defaults are not included in state" do - # expect(resource.state).to eq({}) - # end - # it "Set values with defaults are included in state" do - # resource.x 1 - # expect(resource.state).to eq(x: 1) - # end - # end + with_property ":x", ":y", ":z" do + it "x, y and z are state attributes" do + resource.x 1 + resource.y 2 + resource.z 3 + expect(resource_class.state_properties).to eq [ + resource_class.properties[:x], + resource_class.properties[:y], + resource_class.properties[:z] + ] + expect(resource.state_for_resource_reporter).to eq(x: 1, y: 2, z: 3) + end + it "values that are not set are not included in state" do + resource.x 1 + expect(resource.state_for_resource_reporter).to eq(x: 1) + end + it "when no values are set, nothing is included in state" do + end + end + + with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do + it "x and z are state attributes, and y is not" do + resource.x 1 + resource.y 2 + resource.z 3 + expect(resource_class.state_properties).to eq [ + resource_class.properties[:x], + resource_class.properties[:z] + ] + expect(resource.state_for_resource_reporter).to eq(x: 1, z: 3) + end + end + + with_property ":x, name_property: true" do + # it "Unset values with name_property are included in state" do + # expect(resource.state_for_resource_reporter).to eq({ x: 'blah' }) + # end + it "Set values with name_property are included in state" do + resource.x 1 + expect(resource.state_for_resource_reporter).to eq(x: 1) + end + end + + with_property ":x, default: 1" do + it "Unset values with defaults are not included in state" do + expect(resource.state_for_resource_reporter).to eq({}) + end + it "Set values with defaults are included in state" do + resource.x 1 + expect(resource.state_for_resource_reporter).to eq(x: 1) + end + end context "With a class with a normal getter and setter" do before do @@ -349,143 +360,147 @@ describe "Chef::Resource#identity and #state" do end end end - it "state_attrs(:x) causes the value to be included in properties" do - resource_class.state_attrs(:x) + it "state_properties(:x) causes the value to be included in properties" do + resource_class.state_properties(:x) resource.x = 1 expect(resource.x).to eq 6 - expect(resource.state).to eq(x: 6) + expect(resource.state_for_resource_reporter).to eq(x: 6) + end + end + + context "When state_properties happens before properties are declared" do + before do + resource_class.class_eval do + state_properties :x + property :x + end + end + it "the property works and is in state_properties" do + expect(resource_class.state_properties).to include(resource_class.properties[:x]) + resource.x = 1 + expect(resource.x).to eq 1 + expect(resource.state_for_resource_reporter).to eq(x: 1) + end + end + + with_property ":x, Integer, identity: true" do + it "state_properties(:x) leaves the property in desired_state" do + resource_class.state_properties(:x) + resource.x 10 + + expect(resource_class.properties[:x].desired_state?).to be_truthy + expect(resource_class.state_properties).to eq [ + resource_class.properties[:x] + ] + expect(resource.state_for_resource_reporter).to eq(x: 10) + end + it "state_properties(:x) does not turn off validation" do + resource_class.state_properties(:x) + expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed + end + it "state_properties(:x) does not turn off identity" do + resource_class.state_properties(:x) + resource.x 10 + + expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] + expect(resource_class.properties[:x].identity?).to be_truthy + expect(resource.identity).to eq 10 end end - # with_property ":x, Integer, identity: true" do - # it "state_attrs(:x) leaves the property in desired_state" do - # resource_class.state_attrs(:x) - # resource.x 10 - # - # # expect(resource_class.properties[:x].desired_state?).to be_truthy - # expect(resource_class.state_attrs).to eq [ :x ] - # expect(resource.state).to eq(x: 10) - # end - # it "state_attrs(:x) does not turn off validation" do - # resource_class.state_attrs(:x) - # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed - # end - # it "state_attrs(:x) does not turn off identity" do - # resource_class.state_attrs(:x) - # resource.x 10 - # - # expect(resource_class.identity_attr).to eq :x - # # expect(resource_class.properties[:x].identity?).to be_truthy - # expect(resource.identity).to eq 10 - # end - # end - - # with_property ":x, Integer, identity: true, desired_state: false" do - # before do - # resource_class.class_eval do - # def y - # 20 - # end - # end - # end - # it "state_attrs(:x) sets the property in desired_state" do - # resource_class.state_attrs(:x) - # resource.x 10 - # - # # expect(resource_class.properties[:x].desired_state?).to be_truthy - # expect(resource_class.state_attrs).to eq [ :x ] - # expect(resource.state).to eq(x: 10) - # end - # it "state_attrs(:x) does not turn off validation" do - # resource_class.state_attrs(:x) - # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed - # end - # it "state_attrs(:x) does not turn off identity" do - # resource_class.state_attrs(:x) - # resource.x 10 - # - # expect(resource_class.identity_attr).to eq :x - # # expect(resource_class.properties[:x].identity?).to be_truthy - # expect(resource.identity).to eq 10 - # end - # it "state_attrs(:y) adds y and removes x from desired state" do - # resource_class.state_attrs(:y) - # resource.x 10 - # - # # expect(resource_class.properties[:x].desired_state?).to be_falsey - # # expect(resource_class.properties[:y].desired_state?).to be_truthy - # expect(resource_class.state_attrs).to eq [ :y ] - # expect(resource.state).to eq(y: 20) - # end - # it "state_attrs(:y) does not turn off validation" do - # resource_class.state_attrs(:y) - # - # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed - # end - # it "state_attrs(:y) does not turn off identity" do - # resource_class.state_attrs(:y) - # resource.x 10 - # - # expect(resource_class.identity_attr).to eq :x - # # expect(resource_class.properties[:x].identity?).to be_truthy - # expect(resource.identity).to eq 10 - # end - # - # context "With a subclassed resource" do - # let(:resource_subclass) do - # new_resource_name = self.class.new_resource_name - # Class.new(resource_class) do - # resource_name new_resource_name - # end - # end - # let(:subresource) do - # resource_subclass.new('blah') - # end - # it "state_attrs(:x) sets the property in desired_state" do - # resource_subclass.state_attrs(:x) - # subresource.x 10 - # - # # expect(resource_subclass.properties[:x].desired_state?).to be_truthy - # expect(resource_subclass.state_attrs).to eq [ :x ] - # expect(subresource.state).to eq(x: 10) - # end - # it "state_attrs(:x) does not turn off validation" do - # resource_subclass.state_attrs(:x) - # expect { subresource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed - # end - # it "state_attrs(:x) does not turn off identity" do - # resource_subclass.state_attrs(:x) - # subresource.x 10 - # - # expect(resource_subclass.identity_attr).to eq :x - # # expect(resource_subclass.properties[:x].identity?).to be_truthy - # expect(subresource.identity).to eq 10 - # end - # it "state_attrs(:y) adds y and removes x from desired state" do - # resource_subclass.state_attrs(:y) - # subresource.x 10 - # - # # expect(resource_subclass.properties[:x].desired_state?).to be_falsey - # # expect(resource_subclass.properties[:y].desired_state?).to be_truthy - # expect(resource_subclass.state_attrs).to eq [ :y ] - # expect(subresource.state).to eq(y: 20) - # end - # it "state_attrs(:y) does not turn off validation" do - # resource_subclass.state_attrs(:y) - # - # expect { subresource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed - # end - # it "state_attrs(:y) does not turn off identity" do - # resource_subclass.state_attrs(:y) - # subresource.x 10 - # - # expect(resource_subclass.identity_attr).to eq :x - # # expect(resource_subclass.properties[:x].identity?).to be_truthy - # expect(subresource.identity).to eq 10 - # end - # end - # end + with_property ":x, Integer, identity: true, desired_state: false" do + before do + resource_class.class_eval do + def y + 20 + end + end + end + + it "state_properties(:x) leaves x identical" do + old_value = resource_class.properties[:y] + resource_class.state_properties(:x) + resource.x 10 + + expect(resource_class.properties[:y].object_id).to eq old_value.object_id + + expect(resource_class.properties[:x].desired_state?).to be_truthy + expect(resource_class.properties[:x].identity?).to be_truthy + expect(resource_class.identity_properties).to eq [ + resource_class.properties[:x] + ] + expect(resource.identity).to eq(10) + expect(resource_class.state_properties).to eq [ + resource_class.properties[:x] + ] + expect(resource.state_for_resource_reporter).to eq(x: 10) + end + + it "state_properties(:y) adds y to desired state" do + old_value = resource_class.properties[:x] + resource_class.state_properties(:y) + resource.x 10 + + expect(resource_class.properties[:x].object_id).to eq old_value.object_id + expect(resource_class.properties[:x].desired_state?).to be_falsey + expect(resource_class.properties[:y].desired_state?).to be_truthy + expect(resource_class.state_properties).to eq [ + resource_class.properties[:y] + ] + expect(resource.state_for_resource_reporter).to eq(y: 20) + end + + context "With a subclassed resource" do + let(:subresource_class) do + new_resource_name = self.class.new_resource_name + Class.new(resource_class) do + resource_name new_resource_name + end + end + let(:subresource) do + subresource_class.new('blah') + end + + it "state_properties(:x) adds x to desired state" do + old_value = resource_class.properties[:y] + subresource_class.state_properties(:x) + subresource.x 10 + + expect(subresource_class.properties[:y].object_id).to eq old_value.object_id + + expect(subresource_class.properties[:x].desired_state?).to be_truthy + expect(subresource_class.properties[:x].identity?).to be_truthy + expect(subresource_class.identity_properties).to eq [ + subresource_class.properties[:x] + ] + expect(subresource.identity).to eq(10) + expect(subresource_class.state_properties).to eq [ + subresource_class.properties[:x] + ] + expect(subresource.state_for_resource_reporter).to eq(x: 10) + end + + it "state_properties(:y) adds y to desired state" do + old_value = resource_class.properties[:x] + subresource_class.state_properties(:y) + subresource.x 10 + + expect(subresource_class.properties[:x].object_id).to eq old_value.object_id + expect(subresource_class.properties[:y].desired_state?).to be_truthy + expect(subresource_class.state_properties).to eq [ + subresource_class.properties[:y] + ] + expect(subresource.state_for_resource_reporter).to eq(y: 20) + + expect(subresource_class.properties[:x].identity?).to be_truthy + expect(subresource_class.identity_properties).to eq [ + subresource_class.properties[:x] + ] + expect(subresource.identity).to eq(10) + end + end + end end end diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb index e32147bd38..a3649d228c 100644 --- a/spec/unit/property/validation_spec.rb +++ b/spec/unit/property/validation_spec.rb @@ -111,10 +111,6 @@ describe "Chef::Resource.property validation" do it "get succeeds" do expect(resource.x).to eq 'default' end - it "set(nil) = get" do - expect(resource.x nil).to eq 'default' - expect(resource.x).to eq 'default' - end it "set to valid value succeeds" do expect(resource.x 'str').to eq 'str' expect(resource.x).to eq 'str' @@ -122,15 +118,18 @@ describe "Chef::Resource.property validation" do it "set to invalid value raises ValidationFailed" do expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed end + it "set to nil emits a deprecation warning and does a get" do + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource.x 'str' + expect(resource.x nil).to eq 'str' + expect(resource.x).to eq 'str' + end end context "when the variable does not have an initial value" do it "get succeeds" do expect(resource.x).to be_nil end - it "set(nil) = get" do - expect(resource.x nil).to be_nil - expect(resource.x).to be_nil - end it "set to valid value succeeds" do expect(resource.x 'str').to eq 'str' expect(resource.x).to eq 'str' @@ -138,6 +137,13 @@ describe "Chef::Resource.property validation" do it "set to invalid value raises ValidationFailed" do expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed end + it "set to nil emits a deprecation warning and does a get" do + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource.x 'str' + expect(resource.x nil).to eq 'str' + expect(resource.x).to eq 'str' + end end end with_property ":x, [ String, nil ]" do @@ -255,10 +261,10 @@ describe "Chef::Resource.property validation" do [ '', 'abac' ], [ nil ] - # PropertyType - # validation_test 'is: PropertyType.new(is: :a)', - # [ :a ], - # [ :b, nil ] + # Property + validation_test 'is: Chef::Property.new(is: :a)', + [ :a ], + [ :b, nil ] # RSpec Matcher class Globalses @@ -523,10 +529,11 @@ describe "Chef::Resource.property validation" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end - it "value nil does a get" do + it "value nil emits a deprecation warning and does a get" do + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 - resource.x nil + expect(resource.x nil).to eq 1 expect(resource.x).to eq 1 end end @@ -549,31 +556,35 @@ describe "Chef::Resource.property validation" do end with_property ':x, name_property: true, required: true' do - it "if x is not specified, retrieval fails" do - expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed + it "if x is not specified, the name property is returned" do + expect(resource.x).to eq 'blah' end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end - it "value nil does a get" do + it "value nil emits a deprecation warning and does a get" do + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError + Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 - resource.x nil + expect(resource.x nil).to eq 1 expect(resource.x).to eq 1 end end with_property ':x, default: 10, required: true' do - it "if x is not specified, retrieval fails" do - expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed + it "if x is not specified, the default is returned" do + expect(resource.x).to eq 10 end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end - it "value nil does a get" do + it "value nil is invalid" do + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError + Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 - resource.x nil + expect(resource.x nil).to eq 1 expect(resource.x).to eq 1 end end @@ -596,11 +607,6 @@ describe "Chef::Resource.property validation" do end end - # it "getting the value causes a deprecation warning" do - # Chef::Config[:treat_deprecation_warnings_as_errors] = true - # expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError - # end - it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index ce0552c564..09f7e52329 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -58,11 +58,15 @@ describe "Chef::Resource.property" do else tags = [] end - properties = properties.map { |property| "property #{property}" } - context "With properties #{english_join(properties)}", *tags do + if properties.size == 1 + description = "With property #{properties.first}" + else + description = "With properties #{english_join(properties.map { |property| "#{property.inspect}" })}" + end + context description, *tags do before do properties.each do |property_str| - resource_class.class_eval(property_str, __FILE__, __LINE__) + resource_class.class_eval("property #{property_str}", __FILE__, __LINE__) end end instance_eval(&block) @@ -75,11 +79,10 @@ describe "Chef::Resource.property" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property).to eq 10 end - # it "emits a deprecation warning and does a get, if set to nil" do it "emits a deprecation warning and does a get, if set to nil" do expect(resource.bare_property 10).to eq 10 - # expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError - # Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError + Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.bare_property nil).to eq 10 expect(resource.bare_property).to eq 10 end @@ -92,11 +95,11 @@ describe "Chef::Resource.property" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property).to eq 10 end - # it "can be set to nil with =" do - # expect(resource.bare_property 10).to eq 10 - # expect(resource.bare_property = nil).to be_nil - # expect(resource.bare_property).to be_nil - # end + it "can be set to nil with =" do + expect(resource.bare_property 10).to eq 10 + expect(resource.bare_property = nil).to be_nil + expect(resource.bare_property).to be_nil + end it "can be updated with =" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property = 20).to eq 20 @@ -121,7 +124,7 @@ describe "Chef::Resource.property" do expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 - # expect(subresource_class.properties[:x]).not_to be_nil + expect(subresource_class.properties[:x]).not_to be_nil end it "x's validation is inherited" do @@ -140,18 +143,18 @@ describe "Chef::Resource.property" do expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 - # expect(subresource_class.properties[:x]).not_to be_nil + expect(subresource_class.properties[:x]).not_to be_nil end it "y is there" do expect(subresource.y 10).to eq 10 expect(subresource.y).to eq 10 expect(subresource.y = 20).to eq 20 expect(subresource.y).to eq 20 - # expect(subresource_class.properties[:y]).not_to be_nil + expect(subresource_class.properties[:y]).not_to be_nil end it "y is not on the superclass" do expect { resource_class.y 10 }.to raise_error - # expect(resource_class.properties[:y]).to be_nil + expect(resource_class.properties[:y]).to be_nil end end @@ -167,17 +170,37 @@ describe "Chef::Resource.property" do expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 - # expect(subresource_class.properties[:x]).not_to be_nil - # expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] + expect(subresource_class.properties[:x]).not_to be_nil + expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end - it "x's validation is overwritten" do - expect(subresource.x 'ohno').to eq 'ohno' - expect(subresource.x).to eq 'ohno' + it "x's validation is inherited" do + expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed end + end - it "the superclass's validation for x is still there" do - expect { resource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed + context "with property :x, default: 80 on the subclass" do + before do + subresource_class.class_eval do + property :x, default: 80 + end + end + + it "x is still there" do + expect(subresource.x 10).to eq 10 + expect(subresource.x).to eq 10 + expect(subresource.x = 20).to eq 20 + expect(subresource.x).to eq 20 + expect(subresource_class.properties[:x]).not_to be_nil + expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] + end + + it "x defaults to 80" do + expect(subresource.x).to eq 80 + end + + it "x's validation is inherited" do + expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed end end @@ -193,8 +216,8 @@ describe "Chef::Resource.property" do expect(subresource.x).to eq "10" expect(subresource.x = "20").to eq "20" expect(subresource.x).to eq "20" - # expect(subresource_class.properties[:x]).not_to be_nil - # expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] + expect(subresource_class.properties[:x]).not_to be_nil + expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x's validation is overwritten" do @@ -212,14 +235,89 @@ describe "Chef::Resource.property" do end end - context "Chef::Resource::PropertyType#property_is_set?" do + context "Chef::Resource::Property#reset_property" do + it "when a resource is newly created, reset_property(:name) sets property to nil" do + expect(resource.property_is_set?(:name)).to be_truthy + resource.reset_property(:name) + expect(resource.property_is_set?(:name)).to be_falsey + expect(resource.name).to be_nil + end + + it "when referencing an undefined property, reset_property(:x) raises an error" do + expect { resource.reset_property(:x) }.to raise_error(ArgumentError) + end + + with_property ':x' do + it "when the resource is newly created, reset_property(:x) does nothing" do + expect(resource.property_is_set?(:x)).to be_falsey + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to be_nil + end + it "when x is set, reset_property resets it" do + resource.x 10 + expect(resource.property_is_set?(:x)).to be_truthy + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to be_nil + end + end + + with_property ':x, Integer' do + it "when the resource is newly created, reset_property(:x) does nothing" do + expect(resource.property_is_set?(:x)).to be_falsey + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to be_nil + end + it "when x is set, reset_property resets it even though `nil` is technically invalid" do + resource.x 10 + expect(resource.property_is_set?(:x)).to be_truthy + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to be_nil + end + end + + with_property ':x, default: 10' do + it "when the resource is newly created, reset_property(:x) does nothing" do + expect(resource.property_is_set?(:x)).to be_falsey + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to eq 10 + end + it "when x is set, reset_property resets it and it returns the default" do + resource.x 20 + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to eq 10 + end + end + + with_property ':x, default: lazy { 10 }' do + it "when the resource is newly created, reset_property(:x) does nothing" do + expect(resource.property_is_set?(:x)).to be_falsey + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to eq 10 + end + it "when x is set, reset_property resets it and it returns the default" do + resource.x 20 + resource.reset_property(:x) + expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.x).to eq 10 + end + end + end + + context "Chef::Resource::Property#property_is_set?" do it "when a resource is newly created, property_is_set?(:name) is true" do expect(resource.property_is_set?(:name)).to be_truthy end - # it "when referencing an undefined property, property_is_set?(:x) raises an error" do - # expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError) - # end + it "when referencing an undefined property, property_is_set?(:x) raises an error" do + expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError) + end with_property ':x' do it "when the resource is newly created, property_is_set?(:x) is false" do @@ -259,9 +357,9 @@ describe "Chef::Resource.property" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end - it "when x is retrieved, property_is_set?(:x) is true" do + it "when x is retrieved, property_is_set?(:x) is false" do resource.x - expect(resource.property_is_set?(:x)).to be_truthy + expect(resource.property_is_set?(:x)).to be_falsey end end @@ -281,9 +379,9 @@ describe "Chef::Resource.property" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end - it "when x is retrieved, property_is_set?(:x) is true" do + it "when x is retrieved, property_is_set?(:x) is false" do resource.x - expect(resource.property_is_set?(:x)).to be_truthy + expect(resource.property_is_set?(:x)).to be_falsey end end @@ -299,14 +397,14 @@ describe "Chef::Resource.property" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end - it "when x is retrieved, property_is_set?(:x) is true" do + it "when x is retrieved, property_is_set?(:x) is false" do resource.x - expect(resource.property_is_set?(:x)).to be_truthy + expect(resource.property_is_set?(:x)).to be_falsey end end end - context "Chef::Resource::PropertyType#default" do + context "Chef::Resource::Property#default" do with_property ':x, default: 10' do it "when x is set, it returns its value" do expect(resource.x 20).to eq 20 @@ -317,7 +415,7 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 10 end it "when x is not set, it is not included in state" do - expect(resource.state).to eq({}) + expect(resource.state_for_resource_reporter).to eq({}) end it "when x is set to nil, it returns nil" do resource.instance_eval { @x = nil } @@ -339,8 +437,15 @@ describe "Chef::Resource.property" do end with_property ':x, default: 10, identity: true' do - it "when x is not set, it is not included in identity" do - expect(resource.state).to eq({}) + it "when x is not set, it is included in identity" do + expect(resource.identity).to eq(10) + end + end + + with_property ':x, default: 1, identity: true', ':y, default: 2, identity: true' do + it "when x is not set, it is still included in identity" do + resource.y 20 + expect(resource.identity).to eq(x: 1, y: 20) end end @@ -462,21 +567,35 @@ describe "Chef::Resource.property" do # end end - with_property ":x, default: lazy { Namer.next_index }, is: proc { |v| Namer.next_index; true }" do + with_property ":x, default: lazy { Namer.next_index.to_s }, is: proc { |v| Namer.next_index; true }" do it "validation is not run at all on the default value" do - expect(resource.x).to eq 1 + expect(resource.x).to eq '1' + expect(Namer.current_index).to eq 1 + end + # it "validation is run each time" do + # expect(resource.x).to eq '1' + # expect(Namer.current_index).to eq 2 + # expect(resource.x).to eq '1' + # expect(Namer.current_index).to eq 2 + # end + end + + with_property ":x, default: lazy { Namer.next_index.to_s.freeze }, is: proc { |v| Namer.next_index; true }" do + it "validation is not run at all on the default value" do + expect(resource.x).to eq '1' expect(Namer.current_index).to eq 1 end # it "validation is only run the first time" do - # expect(resource.x).to eq 1 + # expect(resource.x).to eq '1' # expect(Namer.current_index).to eq 2 - # expect(resource.x).to eq 1 + # expect(resource.x).to eq '1' # expect(Namer.current_index).to eq 2 # end end end context "coercion of defaults" do + # Frozen default, non-frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when the resource is created, the proc is not yet run" do resource @@ -487,13 +606,32 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 'hi1' expect(Namer.current_index).to eq 1 end - it "when x is retrieved, coercion is run, no more than once" do + it "when x is retrieved, coercion is run exactly once" do expect(resource.x).to eq '101' expect(resource.x).to eq '101' expect(Namer.current_index).to eq 1 end end + # Frozen default, frozen coerce + with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: 10' do + it "when the resource is created, the proc is not yet run" do + resource + expect(Namer.current_index).to eq 0 + end + it "when x is set, coercion is run" do + expect(resource.x 'hi').to eq 'hi1' + expect(resource.x).to eq 'hi1' + expect(Namer.current_index).to eq 1 + end + it "when x is retrieved, coercion is run each time" do + expect(resource.x).to eq '101' + expect(resource.x).to eq '102' + expect(Namer.current_index).to eq 2 + end + end + + # Frozen lazy default, non-frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when the resource is created, the proc is not yet run" do resource @@ -504,6 +642,29 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 'hi1' expect(Namer.current_index).to eq 1 end + it "when x is retrieved, coercion is run exactly once" do + expect(resource.x).to eq '101' + expect(resource.x).to eq '101' + expect(Namer.current_index).to eq 1 + end + end + + # Non-frozen lazy default, frozen coerce + with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: lazy { "10" }' do + it "when the resource is created, the proc is not yet run" do + resource + expect(Namer.current_index).to eq 0 + end + it "when x is set, coercion is run" do + expect(resource.x 'hi').to eq 'hi1' + expect(resource.x).to eq 'hi1' + expect(Namer.current_index).to eq 1 + end + it "when x is retrieved, coercion is run each time" do + expect(resource.x).to eq '101' + expect(resource.x).to eq '102' + expect(Namer.current_index).to eq 2 + end end with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do @@ -706,7 +867,7 @@ describe "Chef::Resource.property" do end end - context "Chef::Resource::PropertyType#coerce" do + context "Chef::Resource::Property#coerce" do with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "coercion runs on set" do expect(resource.x 10).to eq "101" @@ -740,7 +901,7 @@ describe "Chef::Resource.property" do end end - context "Chef::Resource::PropertyType validation" do + context "Chef::Resource::Property validation" do with_property ':x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }' do it "validation runs on set" do expect(resource.x 10).to eq 10 @@ -768,7 +929,7 @@ describe "Chef::Resource.property" do end [ 'name_attribute', 'name_property' ].each do |name| - context "Chef::Resource::PropertyType##{name}" do + context "Chef::Resource::Property##{name}" do with_property ":x, #{name}: true" do it "defaults x to resource.name" do expect(resource.x).to eq 'blah' @@ -797,6 +958,11 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 10 end end + with_property ":x, #{name}: true, required: true" do + it "defaults x to resource.name" do + expect(resource.x).to eq 'blah' + end + end end end end diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb index 63658ac601..f6bb78823f 100644 --- a/spec/unit/provider/deploy_spec.rb +++ b/spec/unit/provider/deploy_spec.rb @@ -622,7 +622,7 @@ describe Chef::Provider::Deploy do gems = @provider.send(:gem_packages) - expect(gems.map { |g| g.action }).to eq([:install]) + expect(gems.map { |g| g.action }).to eq([%i{install}]) expect(gems.map { |g| g.name }).to eq(%w{eventmachine}) expect(gems.map { |g| g.version }).to eq(%w{0.12.9}) end diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb index 0a6c22bdcf..65c1c019f0 100644 --- a/spec/unit/provider/dsc_resource_spec.rb +++ b/spec/unit/provider/dsc_resource_spec.rb @@ -35,10 +35,10 @@ describe Chef::Provider::DscResource do node } - it 'raises a NoProviderAvailable exception' do + it 'raises a ProviderNotFound exception' do expect(provider).not_to receive(:meta_configuration) expect{provider.run_action(:run)}.to raise_error( - Chef::Exceptions::NoProviderAvailable, /5\.0\.10018\.0/) + Chef::Exceptions::ProviderNotFound, /5\.0\.10018\.0/) end end @@ -56,7 +56,7 @@ describe Chef::Provider::DscResource do expect(provider).to receive(:meta_configuration).and_return( meta_configuration) expect { provider.run_action(:run) }.to raise_error( - Chef::Exceptions::NoProviderAvailable, /Disabled/) + Chef::Exceptions::ProviderNotFound, /Disabled/) end end diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb index d4b2eb3b22..76589e71c1 100644 --- a/spec/unit/provider/dsc_script_spec.rb +++ b/spec/unit/provider/dsc_script_spec.rb @@ -158,14 +158,14 @@ describe Chef::Provider::DscScript do expect { provider.run_action(:run) - }.to raise_error(Chef::Exceptions::NoProviderAvailable) + }.to raise_error(Chef::Exceptions::ProviderNotFound) end end it 'raises an exception if Powershell is not present' do expect { provider.run_action(:run) - }.to raise_error(Chef::Exceptions::NoProviderAvailable) + }.to raise_error(Chef::Exceptions::ProviderNotFound) end end diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/provider/powershell_script_spec.rb index 855c18af9b..855c18af9b 100644 --- a/spec/unit/provider/powershell_spec.rb +++ b/spec/unit/provider/powershell_script_spec.rb diff --git a/spec/unit/provider/registry_key_spec.rb b/spec/unit/provider/registry_key_spec.rb index 79811fdab8..47543ffe39 100644 --- a/spec/unit/provider/registry_key_spec.rb +++ b/spec/unit/provider/registry_key_spec.rb @@ -77,6 +77,18 @@ shared_examples_for "a registry key" do end describe "action_create" do + context "when a case insensitive match for the key exists" do + before(:each) do + expect(@double_registry).to receive(:key_exists?).twice.with(keyname.downcase).and_return(true) + end + it "should do nothing if the if a case insensitive key and the value both exist" do + @provider.new_resource.key(keyname.downcase) + expect(@double_registry).to receive(:get_values).with(keyname.downcase).and_return( testval1 ) + expect(@double_registry).not_to receive(:set_value) + @provider.load_current_resource + @provider.action_create + end + end context "when the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index cd3d7713a7..88df4a20cc 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -28,30 +28,37 @@ include Chef::Mixin::ConvertToClassName describe Chef::ProviderResolver do + let(:resource_name) { :service } + let(:provider) { nil } + let(:action) { :start } + let(:node) do node = Chef::Node.new - allow(node).to receive(:[]).with(:os).and_return(os) - allow(node).to receive(:[]).with(:platform_family).and_return(platform_family) - allow(node).to receive(:[]).with(:platform).and_return(platform) - allow(node).to receive(:[]).with(:platform_version).and_return(platform_version) - allow(node).to receive(:is_a?).and_return(Chef::Node) + node.automatic[:os] = os + node.automatic[:platform_family] = platform_family + node.automatic[:platform] = platform + node.automatic[:platform_version] = platform_version + node.automatic[:kernel] = { machine: 'i386' } node end + let(:run_context) { Chef::RunContext.new(node, nil, nil) } let(:provider_resolver) { Chef::ProviderResolver.new(node, resource, action) } + let(:resolved_provider) do + begin + resource ? resource.provider_for_action(action).class : nil + rescue Chef::Exceptions::ProviderNotFound + nil + end + end - let(:action) { :start } - - let(:resolved_provider) { provider_resolver.resolve } - - let(:provider) { nil } - - let(:resource_name) { :service } - - let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) } - - before do - allow(resource).to receive(:is_a?).with(Chef::Resource).and_return(true) + let(:resource) do + resource_class = Chef::ResourceResolver.resolve(resource_name, node: node) + if resource_class + resource = resource_class.new('test', run_context) + resource.provider = provider if provider + end + resource end def self.on_platform(platform, *tags, @@ -83,16 +90,41 @@ describe Chef::ProviderResolver do end def self.expect_providers(**providers) - providers.each do |name, provider| + providers.each do |name, expected| describe name.to_s do let(:resource_name) { name } - if provider - it "resolves to a #{provider}" do - expect(resolved_provider).to eql(provider) + + tags = [] + expected_provider = nil + expected_resource = nil + Array(expected).each do |p| + if p.is_a?(Class) && p <= Chef::Provider + expected_provider = p + elsif p.is_a?(Class) && p <= Chef::Resource + expected_resource = p + else + tags << p + end + end + + if expected_resource && expected_provider + it "'#{name}' resolves to resource #{expected_resource} and provider #{expected_provider}", *tags do + expect(resource.class).to eql(expected_resource) + provider = double(expected_provider, class: expected_provider) + expect(provider).to receive(:action=).with(action) + expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider) + expect(resolved_provider).to eql(expected_provider) + end + elsif expected_provider + it "'#{name}' resolves to provider #{expected_provider}", *tags do + provider = double(expected_provider) + expect(provider).to receive(:action=).with(action) + expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider) + expect(resolved_provider).to eql(expected_provider) end else - it "Fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})" do - expect { resolved_provider }.to raise_error /Cannot find a provider/ + it "'#{name}' fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})", *tags do + expect(resolved_provider).to be_nil end end end @@ -454,48 +486,48 @@ describe Chef::ProviderResolver do PROVIDERS = { - bash: Chef::Provider::Script, - breakpoint: Chef::Provider::Breakpoint, - chef_gem: Chef::Provider::Package::Rubygems, - cookbook_file: Chef::Provider::CookbookFile, - csh: Chef::Provider::Script, - deploy: Chef::Provider::Deploy::Timestamped, - deploy_revision: Chef::Provider::Deploy::Revision, - directory: Chef::Provider::Directory, - easy_install_package: Chef::Provider::Package::EasyInstall, - erl_call: Chef::Provider::ErlCall, - execute: Chef::Provider::Execute, - file: Chef::Provider::File, - gem_package: Chef::Provider::Package::Rubygems, - git: Chef::Provider::Git, - group: Chef::Provider::Group::Gpasswd, - homebrew_package: Chef::Provider::Package::Homebrew, - http_request: Chef::Provider::HttpRequest, - ifconfig: Chef::Provider::Ifconfig, - link: Chef::Provider::Link, - log: Chef::Provider::Log::ChefLog, - macports_package: Chef::Provider::Package::Macports, - mdadm: Chef::Provider::Mdadm, - mount: Chef::Provider::Mount::Mount, - perl: Chef::Provider::Script, - portage_package: Chef::Provider::Package::Portage, - python: Chef::Provider::Script, - remote_directory: Chef::Provider::RemoteDirectory, - route: Chef::Provider::Route, - ruby: Chef::Provider::Script, - ruby_block: Chef::Provider::RubyBlock, - script: Chef::Provider::Script, - subversion: Chef::Provider::Subversion, - template: Chef::Provider::Template, - timestamped_deploy: Chef::Provider::Deploy::Timestamped, - user: Chef::Provider::User::Useradd, - whyrun_safe_ruby_block: Chef::Provider::WhyrunSafeRubyBlock, + bash: [ Chef::Resource::Bash, Chef::Provider::Script ], + breakpoint: [ Chef::Resource::Breakpoint, Chef::Provider::Breakpoint ], + chef_gem: [ Chef::Resource::ChefGem, Chef::Provider::Package::Rubygems ], + cookbook_file: [ Chef::Resource::CookbookFile, Chef::Provider::CookbookFile ], + csh: [ Chef::Resource::Csh, Chef::Provider::Script ], + deploy: [ Chef::Resource::Deploy, Chef::Provider::Deploy::Timestamped ], + deploy_revision: [ Chef::Resource::DeployRevision, Chef::Provider::Deploy::Revision ], + directory: [ Chef::Resource::Directory, Chef::Provider::Directory ], + easy_install_package: [ Chef::Resource::EasyInstallPackage, Chef::Provider::Package::EasyInstall ], + erl_call: [ Chef::Resource::ErlCall, Chef::Provider::ErlCall ], + execute: [ Chef::Resource::Execute, Chef::Provider::Execute ], + file: [ Chef::Resource::File, Chef::Provider::File ], + gem_package: [ Chef::Resource::GemPackage, Chef::Provider::Package::Rubygems ], + git: [ Chef::Resource::Git, Chef::Provider::Git ], + group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ], + homebrew_package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ], + http_request: [ Chef::Resource::HttpRequest, Chef::Provider::HttpRequest ], + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], + link: [ Chef::Resource::Link, Chef::Provider::Link ], + log: [ Chef::Resource::Log, Chef::Provider::Log::ChefLog ], + macports_package: [ Chef::Resource::MacportsPackage, Chef::Provider::Package::Macports ], + mdadm: [ Chef::Resource::Mdadm, Chef::Provider::Mdadm ], + mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Mount ], + perl: [ Chef::Resource::Perl, Chef::Provider::Script ], + portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ], + python: [ Chef::Resource::Python, Chef::Provider::Script ], + remote_directory: [ Chef::Resource::RemoteDirectory, Chef::Provider::RemoteDirectory ], + route: [ Chef::Resource::Route, Chef::Provider::Route ], + ruby: [ Chef::Resource::Ruby, Chef::Provider::Script ], + ruby_block: [ Chef::Resource::RubyBlock, Chef::Provider::RubyBlock ], + script: [ Chef::Resource::Script, Chef::Provider::Script ], + subversion: [ Chef::Resource::Subversion, Chef::Provider::Subversion ], + template: [ Chef::Resource::Template, Chef::Provider::Template ], + timestamped_deploy: [ Chef::Resource::TimestampedDeploy, Chef::Provider::Deploy::Timestamped ], + user: [ Chef::Resource::User, Chef::Provider::User::Useradd ], + whyrun_safe_ruby_block: [ Chef::Resource::WhyrunSafeRubyBlock, Chef::Provider::WhyrunSafeRubyBlock ], # We want to check that these are unsupported: apt_package: nil, bff_package: nil, - dsc_script: nil, dpkg_package: nil, + dsc_script: nil, ips_package: nil, pacman_package: nil, paludis_package: nil, @@ -507,61 +539,62 @@ describe Chef::ProviderResolver do windows_service: nil, "linux" => { - apt_package: Chef::Provider::Package::Apt, - dpkg_package: Chef::Provider::Package::Dpkg, - pacman_package: Chef::Provider::Package::Pacman, - paludis_package: Chef::Provider::Package::Paludis, - rpm_package: Chef::Provider::Package::Rpm, - yum_package: Chef::Provider::Package::Yum, + apt_package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ], + dpkg_package: [ Chef::Resource::DpkgPackage, Chef::Provider::Package::Dpkg ], + pacman_package: [ Chef::Resource::PacmanPackage, Chef::Provider::Package::Pacman ], + paludis_package: [ Chef::Resource::PaludisPackage, Chef::Provider::Package::Paludis ], + rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ], + yum_package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ], "debian" => { - ifconfig: Chef::Provider::Ifconfig::Debian, - package: Chef::Provider::Package::Apt, -# service: Chef::Provider::Service::Debian, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Debian ], + package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ], +# service: [ Chef::Resource::DebianService, Chef::Provider::Service::Debian ], "debian" => { "7.0" => { }, "6.0" => { - ifconfig: Chef::Provider::Ifconfig, -# service: Chef::Provider::Service::Insserv, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], +# service: [ Chef::Resource::InsservService, Chef::Provider::Service::Insserv ], }, "5.0" => { - ifconfig: Chef::Provider::Ifconfig, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "gcel" => { "3.1.4" => { - ifconfig: Chef::Provider::Ifconfig, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "linaro" => { "3.1.4" => { - ifconfig: Chef::Provider::Ifconfig, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "linuxmint" => { "3.1.4" => { - ifconfig: Chef::Provider::Ifconfig, -# service: Chef::Provider::Service::Upstart, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], +# service: [ Chef::Resource::UpstartService, Chef::Provider::Service::Upstart ], }, }, "raspbian" => { "3.1.4" => { - ifconfig: Chef::Provider::Ifconfig, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "ubuntu" => { "11.10" => { }, "10.04" => { - ifconfig: Chef::Provider::Ifconfig, + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, }, "arch" => { - package: Chef::Provider::Package::Pacman, + # TODO should be Chef::Resource::PacmanPackage + package: [ Chef::Resource::Package, Chef::Provider::Package::Pacman ], "arch" => { "3.1.4" => { @@ -570,8 +603,8 @@ describe Chef::ProviderResolver do }, "freebsd" => { - group: Chef::Provider::Group::Pw, - user: Chef::Provider::User::Pw, + group: [ Chef::Resource::Group, Chef::Provider::Group::Pw ], + user: [ Chef::Resource::User, Chef::Provider::User::Pw ], "freebsd" => { "3.1.4" => { @@ -579,30 +612,31 @@ describe Chef::ProviderResolver do }, }, "suse" => { - group: Chef::Provider::Group::Gpasswd, + group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ], "suse" => { "12.0" => { }, %w(11.1 11.2 11.3) => { - group: Chef::Provider::Group::Suse, + group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ], }, }, "opensuse" => { -# service: Chef::Provider::Service::Redhat, - package: Chef::Provider::Package::Zypper, - group: Chef::Provider::Group::Usermod, +# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], + package: [ Chef::Resource::ZypperPackage, Chef::Provider::Package::Zypper ], + group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], "12.3" => { }, "12.2" => { - group: Chef::Provider::Group::Suse, + group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ], }, }, }, "gentoo" => { - package: Chef::Provider::Package::Portage, - portage_package: Chef::Provider::Package::Portage, -# service: Chef::Provider::Service::Gentoo, + # TODO should be Chef::Resource::PortagePackage + package: [ Chef::Resource::Package, Chef::Provider::Package::Portage ], + portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ], +# service: [ Chef::Resource::GentooService, Chef::Provider::Service::Gentoo ], "gentoo" => { "3.1.4" => { @@ -611,27 +645,27 @@ describe Chef::ProviderResolver do }, "rhel" => { -# service: Chef::Provider::Service::Systemd, - package: Chef::Provider::Package::Yum, - ifconfig: Chef::Provider::Ifconfig::Redhat, +# service: [ Chef::Resource::SystemdService, Chef::Provider::Service::Systemd ], + package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ], + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Redhat ], %w(amazon xcp xenserver ibm_powerkvm cloudlinux parallels) => { "3.1.4" => { -# service: Chef::Provider::Service::Redhat, +# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], }, }, %w(redhat centos scientific oracle) => { "7.0" => { }, "6.0" => { -# service: Chef::Provider::Service::Redhat, +# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], }, }, "fedora" => { "15.0" => { }, "14.0" => { -# service: Chef::Provider::Service::Redhat, +# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], }, }, }, @@ -640,9 +674,9 @@ describe Chef::ProviderResolver do "darwin" => { %w(mac_os_x mac_os_x_server) => { - group: Chef::Provider::Group::Dscl, - package: Chef::Provider::Package::Homebrew, - user: Chef::Provider::User::Dscl, + group: [ Chef::Resource::Group, Chef::Provider::Group::Dscl ], + package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ], + user: [ Chef::Resource::User, Chef::Provider::User::Dscl ], "mac_os_x" => { "10.9.2" => { @@ -652,17 +686,17 @@ describe Chef::ProviderResolver do }, "windows" => { - batch: Chef::Provider::Batch, - dsc_script: Chef::Provider::DscScript, - env: Chef::Provider::Env::Windows, - group: Chef::Provider::Group::Windows, - mount: Chef::Provider::Mount::Windows, - package: Chef::Provider::Package::Windows, - powershell_script: Chef::Provider::PowershellScript, - service: Chef::Provider::Service::Windows, - user: Chef::Provider::User::Windows, - windows_package: Chef::Provider::Package::Windows, - windows_service: Chef::Provider::Service::Windows, + batch: [ Chef::Resource::Batch, Chef::Provider::Batch ], + dsc_script: [ Chef::Resource::DscScript, Chef::Provider::DscScript ], + env: [ Chef::Resource::Env, Chef::Provider::Env::Windows ], + group: [ Chef::Resource::Group, Chef::Provider::Group::Windows ], + mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ], + package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ], + powershell_script: [ Chef::Resource::PowershellScript, Chef::Provider::PowershellScript ], + service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ], + user: [ Chef::Resource::User, Chef::Provider::User::Windows ], + windows_package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ], + windows_service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ], "windows" => { %w(mswin mingw32 windows) => { @@ -673,15 +707,16 @@ describe Chef::ProviderResolver do }, "aix" => { - bff_package: Chef::Provider::Package::Aix, - cron: Chef::Provider::Cron::Aix, - group: Chef::Provider::Group::Aix, - ifconfig: Chef::Provider::Ifconfig::Aix, - mount: Chef::Provider::Mount::Aix, - package: Chef::Provider::Package::Aix, - rpm_package: Chef::Provider::Package::Rpm, - user: Chef::Provider::User::Aix, -# service: Chef::Provider::Service::Aix, + bff_package: [ Chef::Resource::BffPackage, Chef::Provider::Package::Aix ], + cron: [ Chef::Resource::Cron, Chef::Provider::Cron::Aix ], + group: [ Chef::Resource::Group, Chef::Provider::Group::Aix ], + ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Aix ], + mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Aix ], + # TODO should be Chef::Resource::BffPackage + package: [ Chef::Resource::Package, Chef::Provider::Package::Aix ], + rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ], + user: [ Chef::Resource::User, Chef::Provider::User::Aix ], +# service: [ Chef::Resource::AixService, Chef::Provider::Service::Aix ], "aix" => { "aix" => { @@ -695,7 +730,7 @@ describe Chef::ProviderResolver do "hpux" => { "hpux" => { "3.1.4" => { - group: Chef::Provider::Group::Usermod + group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ] } } } @@ -705,15 +740,15 @@ describe Chef::ProviderResolver do "netbsd" => { "netbsd" => { "3.1.4" => { - group: Chef::Provider::Group::Groupmod, + group: [ Chef::Resource::Group, Chef::Provider::Group::Groupmod ], }, }, }, }, "openbsd" => { - group: Chef::Provider::Group::Usermod, - package: Chef::Provider::Package::Openbsd, + group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], + package: [ Chef::Resource::OpenbsdPackage, Chef::Provider::Package::Openbsd ], "openbsd" => { "openbsd" => { @@ -724,15 +759,15 @@ describe Chef::ProviderResolver do }, "solaris2" => { - group: Chef::Provider::Group::Usermod, - ips_package: Chef::Provider::Package::Ips, - package: Chef::Provider::Package::Ips, - mount: Chef::Provider::Mount::Solaris, - solaris_package: Chef::Provider::Package::Solaris, + group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], + ips_package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ], + package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ], + mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Solaris ], + solaris_package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ], "smartos" => { - smartos_package: Chef::Provider::Package::SmartOS, - package: Chef::Provider::Package::SmartOS, + smartos_package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ], + package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ], "smartos" => { "3.1.4" => { @@ -743,12 +778,11 @@ describe Chef::ProviderResolver do "solaris2" => { "nexentacore" => { "3.1.4" => { - package: Chef::Provider::Package::Solaris, }, }, "omnios" => { "3.1.4" => { - user: Chef::Provider::User::Solaris, + user: [ Chef::Resource::User, Chef::Provider::User::Solaris ], } }, "openindiana" => { @@ -760,11 +794,11 @@ describe Chef::ProviderResolver do }, }, "solaris2" => { - user: Chef::Provider::User::Solaris, + user: [ Chef::Resource::User, Chef::Provider::User::Solaris ], "5.11" => { + package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ], }, "5.9" => { - package: Chef::Provider::Package::Solaris, }, }, }, @@ -784,7 +818,8 @@ describe Chef::ProviderResolver do "exherbo" => { "exherbo" => { "3.1.4" => { - package: Chef::Provider::Package::Paludis + # TODO should be Chef::Resource::PaludisPackage + package: [ Chef::Resource::Package, Chef::Provider::Package::Paludis ] } } } diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index 17ea498fe3..511e7e9397 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -409,8 +409,8 @@ describe Chef::Recipe do end it "does not copy the action from the first resource" do - expect(original_resource.action).to eq(:score) - expect(duplicated_resource.action).to eq(:nothing) + expect(original_resource.action).to eq([:score]) + expect(duplicated_resource.action).to eq([:nothing]) end it "does not copy the source location of the first resource" do @@ -505,7 +505,7 @@ describe Chef::Recipe do recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb")) res = recipe.resources(:file => "/etc/nsswitch.conf") expect(res.name).to eql("/etc/nsswitch.conf") - expect(res.action).to eql(:create) + expect(res.action).to eql([:create]) expect(res.owner).to eql("root") expect(res.group).to eql("root") expect(res.mode).to eql(0644) diff --git a/spec/unit/registry_helper_spec.rb b/spec/unit/registry_helper_spec.rb index 036a0834db..b2d0b7b125 100644 --- a/spec/unit/registry_helper_spec.rb +++ b/spec/unit/registry_helper_spec.rb @@ -21,6 +21,7 @@ require 'spec_helper' describe Chef::Provider::RegistryKey do let(:value1) { { :name => "one", :type => :string, :data => "1" } } + let(:value1_upcase_name) { {:name => "ONE", :type => :string, :data => "1"} } let(:key_path) { 'HKCU\Software\OpscodeNumbers' } let(:key) { 'Software\OpscodeNumbers' } let(:key_parent) { 'Software' } @@ -71,7 +72,20 @@ describe Chef::Provider::RegistryKey do expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) @registry.set_value(key_path, value1) end - + it "does nothing if case insensitive key and hive and value exist" do + expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true) + expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true) + @registry.set_value(key_path.downcase, value1) + end + it "does nothing if key and hive and value with a case insensitive name exist" do + expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) + expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) + @registry.set_value(key_path.downcase, value1_upcase_name) + end it "updates value if key and hive and value exist, but data is different" do expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb index 9c867ebcc7..88ab34d568 100644 --- a/spec/unit/resource/breakpoint_spec.rb +++ b/spec/unit/resource/breakpoint_spec.rb @@ -37,7 +37,7 @@ describe Chef::Resource::Breakpoint do end it "defaults to the break action" do - expect(@breakpoint.action).to eq(:break) + expect(@breakpoint.action).to eq([:break]) end it "names itself after the line number of the file where it's created" do diff --git a/spec/unit/resource/cron_spec.rb b/spec/unit/resource/cron_spec.rb index 743552c1de..0978be6930 100644 --- a/spec/unit/resource/cron_spec.rb +++ b/spec/unit/resource/cron_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::Cron do end it "should have a default action of 'create'" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end it "should accept create or delete for action" do diff --git a/spec/unit/resource/directory_spec.rb b/spec/unit/resource/directory_spec.rb index c452b2a914..e9e80806db 100644 --- a/spec/unit/resource/directory_spec.rb +++ b/spec/unit/resource/directory_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::Directory do end it "should have a default action of 'create'" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end it "should accept create or delete for action" do diff --git a/spec/unit/resource/dsc_resource_spec.rb b/spec/unit/resource/dsc_resource_spec.rb index ae15f56eaf..06769d86ce 100644 --- a/spec/unit/resource/dsc_resource_spec.rb +++ b/spec/unit/resource/dsc_resource_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::DscResource do } it "has a default action of `:run`" do - expect(dsc_test_resource.action).to eq(:run) + expect(dsc_test_resource.action).to eq([:run]) end it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb index 71103ea590..4361b35b91 100644 --- a/spec/unit/resource/dsc_script_spec.rb +++ b/spec/unit/resource/dsc_script_spec.rb @@ -29,7 +29,7 @@ describe Chef::Resource::DscScript do Chef::RunContext.new(node, {}, empty_events) } let(:dsc_test_resource) { - Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) + Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) } let(:configuration_code) {'echo "This is supposed to create a configuration document."'} let(:configuration_path) {'c:/myconfigs/formatc.ps1'} @@ -38,7 +38,7 @@ describe Chef::Resource::DscScript do let(:configuration_data_script) { 'c:/myconfigs/data/safedata.psd1' } it "has a default action of `:run`" do - expect(dsc_test_resource.action).to eq(:run) + expect(dsc_test_resource.action).to eq([:run]) end it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do diff --git a/spec/unit/resource/env_spec.rb b/spec/unit/resource/env_spec.rb index 566827a27e..9bee07c593 100644 --- a/spec/unit/resource/env_spec.rb +++ b/spec/unit/resource/env_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::Env do end it "should have a default action of 'create'" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end { :create => false, :delete => false, :modify => false, :flibber => true }.each do |action,bad_value| diff --git a/spec/unit/resource/erl_call_spec.rb b/spec/unit/resource/erl_call_spec.rb index 008d27372a..9abf2e7812 100644 --- a/spec/unit/resource/erl_call_spec.rb +++ b/spec/unit/resource/erl_call_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::ErlCall do end it "should have a default action of run" do - expect(@resource.action).to eql(:run) + expect(@resource.action).to eql([:run]) end it "should accept run as an action" do diff --git a/spec/unit/resource/file_spec.rb b/spec/unit/resource/file_spec.rb index dd20f5f03a..76beaf15e1 100644 --- a/spec/unit/resource/file_spec.rb +++ b/spec/unit/resource/file_spec.rb @@ -29,7 +29,7 @@ describe Chef::Resource::File do end it "should have a default action of 'create'" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end it "should have a default content of nil" do diff --git a/spec/unit/resource/group_spec.rb b/spec/unit/resource/group_spec.rb index bcf9205f7e..a4029fc911 100644 --- a/spec/unit/resource/group_spec.rb +++ b/spec/unit/resource/group_spec.rb @@ -50,7 +50,7 @@ describe Chef::Resource::Group, "initialize" do end it "should set action to :create" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end %w{create remove modify manage}.each do |action| diff --git a/spec/unit/resource/link_spec.rb b/spec/unit/resource/link_spec.rb index 51221e0472..0246fcd13b 100644 --- a/spec/unit/resource/link_spec.rb +++ b/spec/unit/resource/link_spec.rb @@ -36,7 +36,7 @@ describe Chef::Resource::Link do end it "should have a default action of 'create'" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end { :create => false, :delete => false, :blues => true }.each do |action,bad_value| diff --git a/spec/unit/resource/mdadm_spec.rb b/spec/unit/resource/mdadm_spec.rb index 866309ec5b..6ca99c58e5 100644 --- a/spec/unit/resource/mdadm_spec.rb +++ b/spec/unit/resource/mdadm_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::Mdadm do end it "should have a default action of create" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end it "should accept create, assemble, stop as actions" do diff --git a/spec/unit/resource/mount_spec.rb b/spec/unit/resource/mount_spec.rb index ad95c06e04..acce26dcab 100644 --- a/spec/unit/resource/mount_spec.rb +++ b/spec/unit/resource/mount_spec.rb @@ -38,7 +38,7 @@ describe Chef::Resource::Mount do end it "should have a default action of mount" do - expect(@resource.action).to eql(:mount) + expect(@resource.action).to eql([:mount]) end it "should accept mount, umount and remount as actions" do diff --git a/spec/unit/resource/ohai_spec.rb b/spec/unit/resource/ohai_spec.rb index fe29755abf..3bc21a41d2 100644 --- a/spec/unit/resource/ohai_spec.rb +++ b/spec/unit/resource/ohai_spec.rb @@ -34,7 +34,7 @@ describe Chef::Resource::Ohai do end it "should have a default action of create" do - expect(@resource.action).to eql(:reload) + expect(@resource.action).to eql([:reload]) end it "should allow you to set the plugin attribute" do diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_script_spec.rb index 2505c4a3d7..2505c4a3d7 100644 --- a/spec/unit/resource/powershell_spec.rb +++ b/spec/unit/resource/powershell_script_spec.rb diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb index e2a864d73a..2e2811d026 100644 --- a/spec/unit/resource/registry_key_spec.rb +++ b/spec/unit/resource/registry_key_spec.rb @@ -45,7 +45,7 @@ describe Chef::Resource::RegistryKey, "initialize" do end it "should set action to :create" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end %w{create create_if_missing delete delete_key}.each do |action| diff --git a/spec/unit/resource/route_spec.rb b/spec/unit/resource/route_spec.rb index ffb9304511..ec1d369932 100644 --- a/spec/unit/resource/route_spec.rb +++ b/spec/unit/resource/route_spec.rb @@ -35,7 +35,7 @@ describe Chef::Resource::Route do end it "should have a default action of 'add'" do - expect(@resource.action).to eql(:add) + expect(@resource.action).to eql([:add]) end it "should accept add or delete for action" do diff --git a/spec/unit/resource/ruby_block_spec.rb b/spec/unit/resource/ruby_block_spec.rb index 5d83f7e367..8664564ac5 100644 --- a/spec/unit/resource/ruby_block_spec.rb +++ b/spec/unit/resource/ruby_block_spec.rb @@ -30,8 +30,8 @@ describe Chef::Resource::RubyBlock do expect(@resource).to be_a_kind_of(Chef::Resource::RubyBlock) end - it "should have a default action of 'create'" do - expect(@resource.action).to eql(:run) + it "should have a default action of 'run'" do + expect(@resource.action).to eql([:run]) end it "should have a resource name of :ruby_block" do @@ -46,7 +46,7 @@ describe Chef::Resource::RubyBlock do it "allows the action to be 'create'" do @resource.action :create - expect(@resource.action).to eq(:create) + expect(@resource.action).to eq([:create]) end describe "when it has been initialized with block code" do diff --git a/spec/unit/resource/user_spec.rb b/spec/unit/resource/user_spec.rb index f05de94fe0..3bf7e6187b 100644 --- a/spec/unit/resource/user_spec.rb +++ b/spec/unit/resource/user_spec.rb @@ -43,7 +43,7 @@ describe Chef::Resource::User, "initialize" do end it "should set action to :create" do - expect(@resource.action).to eql(:create) + expect(@resource.action).to eql([:create]) end it "should set supports[:manage_home] to false" do diff --git a/spec/unit/resource/windows_service_spec.rb b/spec/unit/resource/windows_service_spec.rb index 8866cad1bf..45a295c24e 100644 --- a/spec/unit/resource/windows_service_spec.rb +++ b/spec/unit/resource/windows_service_spec.rb @@ -44,6 +44,6 @@ describe Chef::Resource::WindowsService, "initialize" do it "allows the action to be 'configure_startup'" do resource.action :configure_startup - expect(resource.action).to eq(:configure_startup) + expect(resource.action).to eq([:configure_startup]) end end diff --git a/spec/unit/resource_resolver_spec.rb b/spec/unit/resource_resolver_spec.rb index 09ff026575..b3bda9d945 100644 --- a/spec/unit/resource_resolver_spec.rb +++ b/spec/unit/resource_resolver_spec.rb @@ -31,19 +31,23 @@ describe Chef::ResourceResolver do context 'instance methods' do let(:resolver) do - described_class.new(Chef::Node.new, 'execute[echo]') + described_class.new(Chef::Node.new, 'execute') end it '#resolve' do - expect(resolver.resolve).to be_nil + expect(resolver.resolve).to eq Chef::Resource::Execute end it '#list' do - expect(resolver.list).to be_empty + expect(resolver.list).to eq [ Chef::Resource::Execute ] end - it '#provided_by?' do + it '#provided_by? returns true when resource class is in the list' do expect(resolver.provided_by?(Chef::Resource::Execute)).to be_truthy end + + it '#provided_by? returns false when resource class is not in the list' do + expect(resolver.provided_by?(Chef::Resource::File)).to be_falsey + end end end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 8b0baff921..b9ba80068b 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -59,8 +59,8 @@ describe Chef::Resource do end describe "when declaring the identity attribute" do - it "has no identity attribute by default" do - expect(Chef::Resource.identity_attr).to be_nil + it "has :name as identity attribute by default" do + expect(Chef::Resource.identity_attr).to eq(:name) end it "sets an identity attribute" do @@ -810,21 +810,21 @@ describe Chef::Resource do end it 'adds mappings for a single platform' do - expect(Chef.resource_priority_map).to receive(:set).with( + expect(Chef.resource_handler_map).to receive(:set).with( :dinobot, Chef::Resource::Klz, { platform: ['autobots'] } ) klz.provides :dinobot, platform: ['autobots'] end it 'adds mappings for multiple platforms' do - expect(Chef.resource_priority_map).to receive(:set).with( + expect(Chef.resource_handler_map).to receive(:set).with( :energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']} ) klz.provides :energy, platform: ['autobots', 'decepticons'] end it 'adds mappings for all platforms' do - expect(Chef.resource_priority_map).to receive(:set).with( + expect(Chef.resource_handler_map).to receive(:set).with( :tape_deck, Chef::Resource::Klz, {} ) klz.provides :tape_deck @@ -977,4 +977,90 @@ describe Chef::Resource do end end + + describe "#action" do + let(:resource_class) do + Class.new(described_class) do + allowed_actions(%i{one two}) + end + end + let(:resource) { resource_class.new('test', nil) } + subject { resource.action } + + context "with a no action" do + it { is_expected.to eq [:nothing] } + end + + context "with a default action" do + let(:resource_class) do + Class.new(described_class) do + default_action(:one) + end + end + it { is_expected.to eq [:one] } + end + + context "with a symbol action" do + before { resource.action(:one) } + it { is_expected.to eq [:one] } + end + + context "with a string action" do + before { resource.action('two') } + it { is_expected.to eq [:two] } + end + + context "with an array action" do + before { resource.action([:two, :one]) } + it { is_expected.to eq [:two, :one] } + end + + context "with an assignment" do + before { resource.action = :one } + it { is_expected.to eq [:one] } + end + + context "with an array assignment" do + before { resource.action = [:two, :one] } + it { is_expected.to eq [:two, :one] } + end + + context "with an invalid action" do + it { expect { resource.action(:three) }.to raise_error Chef::Exceptions::ValidationFailed } + end + + context "with an invalid assignment action" do + it { expect { resource.action = :three }.to raise_error Chef::Exceptions::ValidationFailed } + end + end + + describe ".default_action" do + let(:default_action) { } + let(:resource_class) do + actions = default_action + Class.new(described_class) do + default_action(actions) if actions + end + end + subject { resource_class.default_action } + + context "with no default actions" do + it { is_expected.to eq [:nothing] } + end + + context "with a symbol default action" do + let(:default_action) { :one } + it { is_expected.to eq [:one] } + end + + context "with a string default action" do + let(:default_action) { 'one' } + it { is_expected.to eq [:one] } + end + + context "with an array default action" do + let(:default_action) { [:two, :one] } + it { is_expected.to eq [:two, :one] } + end + end end diff --git a/spec/unit/run_list/versioned_recipe_list_spec.rb b/spec/unit/run_list/versioned_recipe_list_spec.rb index 209ac37fc1..9c3ecaa0dd 100644 --- a/spec/unit/run_list/versioned_recipe_list_spec.rb +++ b/spec/unit/run_list/versioned_recipe_list_spec.rb @@ -26,98 +26,165 @@ describe Chef::RunList::VersionedRecipeList do end end + let(:list) { described_class.new } + + let(:versioned_recipes) { [] } + + let(:recipes) { [] } + + before do + recipes.each { |r| list << r } + versioned_recipes.each {|r| list.add_recipe r[:name], r[:version]} + end + describe "add_recipe" do - before(:each) do - @list = Chef::RunList::VersionedRecipeList.new - @list << "apt" - @list << "god" - @list << "apache2" - end + + let(:recipes) { %w[ apt god apache2 ] } it "should append the recipe to the end of the list" do - @list.add_recipe "rails" - expect(@list).to eq(["apt", "god", "apache2", "rails"]) + list.add_recipe "rails" + expect(list).to eq(["apt", "god", "apache2", "rails"]) end it "should not duplicate entries" do - @list.add_recipe "apt" - expect(@list).to eq(["apt", "god", "apache2"]) + list.add_recipe "apt" + expect(list).to eq(["apt", "god", "apache2"]) end it "should allow you to specify a version" do - @list.add_recipe "rails", "1.0.0" - expect(@list).to eq(["apt", "god", "apache2", "rails"]) - expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"}) + list.add_recipe "rails", "1.0.0" + expect(list).to eq(["apt", "god", "apache2", "rails"]) + expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"}) end it "should allow you to specify a version for a recipe that already exists" do - @list.add_recipe "apt", "1.2.3" - expect(@list).to eq(["apt", "god", "apache2"]) - expect(@list.with_versions).to include({:name => "apt", :version => "1.2.3"}) + list.add_recipe "apt", "1.2.3" + expect(list).to eq(["apt", "god", "apache2"]) + expect(list.with_versions).to include({:name => "apt", :version => "1.2.3"}) end it "should allow you to specify the same version of a recipe twice" do - @list.add_recipe "rails", "1.0.0" - @list.add_recipe "rails", "1.0.0" - expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"}) + list.add_recipe "rails", "1.0.0" + list.add_recipe "rails", "1.0.0" + expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"}) end it "should allow you to spcify no version, even when a version already exists" do - @list.add_recipe "rails", "1.0.0" - @list.add_recipe "rails" - expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"}) + list.add_recipe "rails", "1.0.0" + list.add_recipe "rails" + expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"}) end it "should not allow multiple versions of the same recipe" do - @list.add_recipe "rails", "1.0.0" - expect {@list.add_recipe "rails", "0.1.0"}.to raise_error Chef::Exceptions::CookbookVersionConflict + list.add_recipe "rails", "1.0.0" + expect {list.add_recipe "rails", "0.1.0"}.to raise_error Chef::Exceptions::CookbookVersionConflict end end describe "with_versions" do - before(:each) do - @recipes = [ + + let(:versioned_recipes) do + [ {:name => "apt", :version => "1.0.0"}, {:name => "god", :version => nil}, {:name => "apache2", :version => "0.0.1"} ] - @list = Chef::RunList::VersionedRecipeList.new - @recipes.each {|i| @list.add_recipe i[:name], i[:version]} end - it "should return an array of hashes with :name and :version" do - expect(@list.with_versions).to eq(@recipes) + expect(list.with_versions).to eq(versioned_recipes) end it "should retain the same order as the version-less list" do - with_versions = @list.with_versions - @list.each_with_index do |item, index| + with_versions = list.with_versions + list.each_with_index do |item, index| expect(with_versions[index][:name]).to eq(item) end end end describe "with_version_constraints" do - before(:each) do - @recipes = [ - {:name => "apt", :version => "~> 1.2.0"}, - {:name => "god", :version => nil}, - {:name => "apache2", :version => "0.0.1"} - ] - @list = Chef::RunList::VersionedRecipeList.new - @recipes.each {|i| @list.add_recipe i[:name], i[:version]} - @constraints = @recipes.map do |x| - { :name => x[:name], - :version_constraint => Chef::VersionConstraint.new(x[:version]) - } - end + + let(:versioned_recipes) do + [ + {:name => "apt", :version => "~> 1.2.0"}, + {:name => "god", :version => nil}, + {:name => "apache2", :version => "0.0.1"} + ] end + it "should return an array of hashes with :name and :version_constraint" do - @list.with_version_constraints.each do |x| - expect(x).to have_key :name - expect(x[:version_constraint]).not_to be nil + list.with_version_constraints.each_with_index do |recipe_spec, i| + + expected_recipe = versioned_recipes[i] + + expect(recipe_spec[:name]).to eq(expected_recipe[:name]) + expect(recipe_spec[:version_constraint]).to eq(Chef::VersionConstraint.new(expected_recipe[:version])) end end end + + describe "with_fully_qualified_names_and_version_constraints" do + + let(:fq_names) { list.with_fully_qualified_names_and_version_constraints } + + context "with bare cookbook names" do + + let(:recipes) { %w[ apache2 ] } + + it "gives $cookbook_name::default" do + expect(fq_names).to eq( %w[ apache2::default ] ) + end + + end + + context "with qualified recipe names but no versions" do + + let(:recipes) { %w[ mysql::server ] } + + it "returns the qualified recipe names" do + expect(fq_names).to eq( %w[ mysql::server ] ) + end + + end + + context "with unqualified names that have version constraints" do + + let(:versioned_recipes) do + [ + {:name => "apt", :version => "~> 1.2.0"}, + ] + end + + it "gives qualified names with their versions" do + expect(fq_names).to eq([ "apt::default@~> 1.2.0" ]) + end + + it "does not mutate the recipe name" do + expect(fq_names).to eq([ "apt::default@~> 1.2.0" ]) + expect(list).to eq( [ "apt" ] ) + end + + end + + context "with fully qualified names that have version constraints" do + + let(:versioned_recipes) do + [ + {:name => "apt::cacher", :version => "~> 1.2.0"}, + ] + end + + it "gives qualified names with their versions" do + expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ]) + end + + it "does not mutate the recipe name" do + expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ]) + expect(list).to eq( [ "apt::cacher" ] ) + end + + end + end + end diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb index 5222c951b3..97cc32eb3e 100644 --- a/spec/unit/user_spec.rb +++ b/spec/unit/user_spec.rb @@ -16,6 +16,11 @@ # limitations under the License. # +# DEPRECATION NOTE +# This code only remains to support users still operating with +# Open Source Chef Server 11 and should be removed once support +# for OSC 11 ends. New development should occur in user_spec.rb. + require 'spec_helper' require 'chef/user' @@ -26,141 +31,98 @@ describe Chef::User do @user = Chef::User.new end - shared_examples_for "string fields with no contraints" do - it "should let you set the public key" do - expect(@user.send(method, "some_string")).to eq("some_string") - end - - it "should return the current public key" do - @user.send(method, "some_string") - expect(@user.send(method)).to eq("some_string") - end - - it "should throw an ArgumentError if you feed it something lame" do - expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) - end - end - - shared_examples_for "boolean fields with no constraints" do - it "should let you set the field" do - expect(@user.send(method, true)).to eq(true) - end - - it "should return the current field value" do - @user.send(method, true) - expect(@user.send(method)).to eq(true) - end - - it "should return the false value when false" do - @user.send(method, false) - expect(@user.send(method)).to eq(false) - end - - it "should throw an ArgumentError if you feed it anything but true or false" do - expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) - end - end - describe "initialize" do it "should be a Chef::User" do expect(@user).to be_a_kind_of(Chef::User) end end - describe "username" do - it "should let you set the username to a string" do - expect(@user.username("ops_master")).to eq("ops_master") + describe "name" do + it "should let you set the name to a string" do + expect(@user.name("ops_master")).to eq("ops_master") end - it "should return the current username" do - @user.username "ops_master" - expect(@user.username).to eq("ops_master") + it "should return the current name" do + @user.name "ops_master" + expect(@user.name).to eq("ops_master") end # It is not feasible to check all invalid characters. Here are a few # that we probably care about. it "should not accept invalid characters" do # capital letters - expect { @user.username "Bar" }.to raise_error(ArgumentError) + expect { @user.name "Bar" }.to raise_error(ArgumentError) # slashes - expect { @user.username "foo/bar" }.to raise_error(ArgumentError) + expect { @user.name "foo/bar" }.to raise_error(ArgumentError) # ? - expect { @user.username "foo?" }.to raise_error(ArgumentError) + expect { @user.name "foo?" }.to raise_error(ArgumentError) # & - expect { @user.username "foo&" }.to raise_error(ArgumentError) + expect { @user.name "foo&" }.to raise_error(ArgumentError) end it "should not accept spaces" do - expect { @user.username "ops master" }.to raise_error(ArgumentError) + expect { @user.name "ops master" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do - expect { @user.username Hash.new }.to raise_error(ArgumentError) + expect { @user.name Hash.new }.to raise_error(ArgumentError) end end - describe "boolean fields" do - describe "create_key" do - it_should_behave_like "boolean fields with no constraints" do - let(:method) { :create_key } - end + describe "admin" do + it "should let you set the admin bit" do + expect(@user.admin(true)).to eq(true) end - end - describe "string fields" do - describe "public_key" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :public_key } - end + it "should return the current admin value" do + @user.admin true + expect(@user.admin).to eq(true) end - describe "private_key" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :private_key } - end + it "should default to false" do + expect(@user.admin).to eq(false) end - describe "display_name" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :display_name } - end + it "should throw an ArgumentError if you feed it anything but true or false" do + expect { @user.name Hash.new }.to raise_error(ArgumentError) end + end - describe "first_name" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :first_name } - end + describe "public_key" do + it "should let you set the public key" do + expect(@user.public_key("super public")).to eq("super public") end - describe "middle_name" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :middle_name } - end + it "should return the current public key" do + @user.public_key("super public") + expect(@user.public_key).to eq("super public") end - describe "last_name" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :last_name } - end + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.public_key Hash.new }.to raise_error(ArgumentError) end + end - describe "email" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :email } - end + describe "private_key" do + it "should let you set the private key" do + expect(@user.private_key("super private")).to eq("super private") end - describe "password" do - it_should_behave_like "string fields with no contraints" do - let(:method) { :password } - end + it "should return the private key" do + @user.private_key("super private") + expect(@user.private_key).to eq("super private") + end + + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.private_key Hash.new }.to raise_error(ArgumentError) end end describe "when serializing to JSON" do before(:each) do - @user.username("black") + @user.name("black") + @user.public_key("crowes") @json = @user.to_json end @@ -168,62 +130,16 @@ describe Chef::User do expect(@json).to match(/^\{.+\}$/) end - it "includes the username value" do - expect(@json).to include(%q{"username":"black"}) - end - - it "includes the display name when present" do - @user.display_name("get_displayed") - expect(@user.to_json).to include(%{"display_name":"get_displayed"}) - end - - it "does not include the display name if not present" do - expect(@json).not_to include("display_name") - end - - it "includes the first name when present" do - @user.first_name("char") - expect(@user.to_json).to include(%{"first_name":"char"}) - end - - it "does not include the first name if not present" do - expect(@json).not_to include("first_name") - end - - it "includes the middle name when present" do - @user.middle_name("man") - expect(@user.to_json).to include(%{"middle_name":"man"}) - end - - it "does not include the middle name if not present" do - expect(@json).not_to include("middle_name") - end - - it "includes the last name when present" do - @user.last_name("der") - expect(@user.to_json).to include(%{"last_name":"der"}) - end - - it "does not include the last name if not present" do - expect(@json).not_to include("last_name") - end - - it "includes the email when present" do - @user.email("charmander@pokemon.poke") - expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"}) - end - - it "does not include the email if not present" do - expect(@json).not_to include("email") + it "includes the name value" do + expect(@json).to include(%q{"name":"black"}) end - it "includes the public key when present" do - @user.public_key("crowes") - expect(@user.to_json).to include(%{"public_key":"crowes"}) + it "includes the public key value" do + expect(@json).to include(%{"public_key":"crowes"}) end - it "does not include the public key if not present" do - expect(@json).not_to include("public_key") + it "includes the 'admin' flag" do + expect(@json).to include(%q{"admin":false}) end it "includes the private key when present" do @@ -251,18 +167,11 @@ describe Chef::User do describe "when deserializing from JSON" do before(:each) do - user = { - "username" => "mr_spinks", - "display_name" => "displayed", - "first_name" => "char", - "middle_name" => "man", - "last_name" => "der", - "email" => "charmander@pokemon.poke", - "password" => "password", + user = { "name" => "mr_spinks", "public_key" => "turtles", "private_key" => "pandas", - "create_key" => false - } + "password" => "password", + "admin" => true } @user = Chef::User.from_json(Chef::JSONCompat.to_json(user)) end @@ -270,277 +179,34 @@ describe Chef::User do expect(@user).to be_a_kind_of(Chef::User) end - it "preserves the username" do - expect(@user.username).to eq("mr_spinks") - end - - it "preserves the display name if present" do - expect(@user.display_name).to eq("displayed") + it "preserves the name" do + expect(@user.name).to eq("mr_spinks") end - it "preserves the first name if present" do - expect(@user.first_name).to eq("char") - end - - it "preserves the middle name if present" do - expect(@user.middle_name).to eq("man") + it "preserves the public key" do + expect(@user.public_key).to eq("turtles") end - it "preserves the last name if present" do - expect(@user.last_name).to eq("der") + it "preserves the admin status" do + expect(@user.admin).to be_truthy end - it "preserves the email if present" do - expect(@user.email).to eq("charmander@pokemon.poke") + it "includes the private key if present" do + expect(@user.private_key).to eq("pandas") end it "includes the password if present" do expect(@user.password).to eq("password") end - it "preserves the public key if present" do - expect(@user.public_key).to eq("turtles") - end - - it "includes the private key if present" do - expect(@user.private_key).to eq("pandas") - end - - it "includes the create key status if not nil" do - expect(@user.create_key).to be_falsey - end end - describe "Versioned API Interactions" do - let(:response_406) { OpenStruct.new(:code => '406') } - let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } - - before (:each) do - @user = Chef::User.new - allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object')) - allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object')) - end - - describe "update" do - before do - # populate all fields that are valid between V0 and V1 - @user.username "some_username" - @user.display_name "some_display_name" - @user.first_name "some_first_name" - @user.middle_name "some_middle_name" - @user.last_name "some_last_name" - @user.email "some_email" - @user.password "some_password" - end - - let(:payload) { - { - :username => "some_username", - :display_name => "some_display_name", - :first_name => "some_first_name", - :middle_name => "some_middle_name", - :last_name => "some_last_name", - :email => "some_email", - :password => "some_password" - } - } - - context "when server API V1 is valid on the Chef Server receiving the request" do - context "when the user submits valid data" do - it "properly updates the user" do - expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({}) - @user.update - end - end - end - - context "when server API V1 is not valid on the Chef Server receiving the request" do - let(:payload) { - { - :username => "some_username", - :display_name => "some_display_name", - :first_name => "some_first_name", - :middle_name => "some_middle_name", - :last_name => "some_last_name", - :email => "some_email", - :password => "some_password", - :public_key => "some_public_key" - } - } - - before do - @user.public_key "some_public_key" - allow(@user.chef_root_rest_v1).to receive(:put) - end - - context "when the server returns a 400" do - let(:response_400) { OpenStruct.new(:code => '400') } - let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) } - - context "when the 400 was due to public / private key fields no longer being supported" do - let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' } - - before do - allow(response_400).to receive(:body).and_return(response_body_400) - allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) - end - - it "proceeds with the V0 PUT since it can handle public / private key fields" do - expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) - @user.update - end - - it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do - expect(@user).to_not receive(:server_client_api_version_intersection) - allow(@user.chef_root_rest_v0).to receive(:put).and_return({}) - @user.update - end - end # when the 400 was due to public / private key fields - - context "when the 400 was NOT due to public / private key fields no longer being supported" do - let(:response_body_400) { '{"error":["Some other error. "]}' } - - before do - allow(response_400).to receive(:body).and_return(response_body_400) - allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) - end - - it "will not proceed with the V0 PUT since the original bad request was not key related" do - expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload) - expect { @user.update }.to raise_error(exception_400) - end - - it "raises the original error" do - expect { @user.update }.to raise_error(exception_400) - end - - end - end # when the server returns a 400 - - context "when the server returns a 406" do - # from spec/support/shared/unit/api_versioning.rb - it_should_behave_like "version handling" do - let(:object) { @user } - let(:method) { :update } - let(:http_verb) { :put } - let(:rest_v1) { @user.chef_root_rest_v1 } - end - - context "when the server supports API V0" do - before do - allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) - allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406) - end - - it "properly updates the user" do - expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) - @user.update - end - end # when the server supports API V0 - end # when the server returns a 406 - - end # when server API V1 is not valid on the Chef Server receiving the request - end # update - - describe "create" do - let(:payload) { - { - :username => "some_username", - :display_name => "some_display_name", - :first_name => "some_first_name", - :last_name => "some_last_name", - :email => "some_email", - :password => "some_password" - } - } - before do - @user.username "some_username" - @user.display_name "some_display_name" - @user.first_name "some_first_name" - @user.last_name "some_last_name" - @user.email "some_email" - @user.password "some_password" - end - - # from spec/support/shared/unit/user_and_client_shared.rb - it_should_behave_like "user or client create" do - let(:object) { @user } - let(:error) { Chef::Exceptions::InvalidUserAttribute } - let(:rest_v0) { @user.chef_root_rest_v0 } - let(:rest_v1) { @user.chef_root_rest_v1 } - let(:url) { "users" } - end - - context "when handling API V1" do - it "creates a new user via the API with a middle_name when it exists" do - @user.middle_name "some_middle_name" - expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) - @user.create - end - end # when server API V1 is valid on the Chef Server receiving the request - - context "when API V1 is not supported by the server" do - # from spec/support/shared/unit/api_versioning.rb - it_should_behave_like "version handling" do - let(:object) { @user } - let(:method) { :create } - let(:http_verb) { :post } - let(:rest_v1) { @user.chef_root_rest_v1 } - end - end - - context "when handling API V0" do - before do - allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) - allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406) - end - - it "creates a new user via the API with a middle_name when it exists" do - @user.middle_name "some_middle_name" - expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) - @user.create - end - end # when server API V1 is not valid on the Chef Server receiving the request - - end # create - - # DEPRECATION - # This can be removed after API V0 support is gone - describe "reregister" do - let(:payload) { - { - "username" => "some_username", - } - } - - before do - @user.username "some_username" - end - - context "when server API V0 is valid on the Chef Server receiving the request" do - it "creates a new object via the API" do - expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({"private_key" => true})).and_return({}) - @user.reregister - end - end # when server API V0 is valid on the Chef Server receiving the request - - context "when server API V0 is not supported by the Chef Server" do - # from spec/support/shared/unit/api_versioning.rb - it_should_behave_like "user and client reregister" do - let(:object) { @user } - let(:rest_v0) { @user.chef_root_rest_v0 } - end - end # when server API V0 is not supported by the Chef Server - end # reregister - - end # Versioned API Interactions - describe "API Interactions" do before (:each) do @user = Chef::User.new - @user.username "foobar" - @http_client = double("Chef::REST mock") - allow(Chef::REST).to receive(:new).and_return(@http_client) + @user.name "foobar" + @http_client = double("Chef::ServerAPI mock") + allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) end describe "list" do @@ -552,6 +218,16 @@ describe Chef::User do @osc_inflated_response = { "admin" => @user } end + it "lists all clients on an OSC server" do + allow(@http_client).to receive(:get).with("users").and_return(@osc_response) + expect(Chef::User.list).to eq(@osc_response) + end + + it "inflate all clients on an OSC server" do + allow(@http_client).to receive(:get).with("users").and_return(@osc_response) + expect(Chef::User.list(true)).to eq(@osc_inflated_response) + end + it "lists all clients on an OHC/OPC server" do allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) # We expect that Chef::User.list will give a consistent response @@ -565,15 +241,31 @@ describe Chef::User do end end + describe "create" do + it "creates a new user via the API" do + @user.password "password" + expect(@http_client).to receive(:post).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({}) + @user.create + end + end + describe "read" do it "loads a named user from the API" do - expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"}) + expect(@http_client).to receive(:get).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"}) user = Chef::User.load("foobar") - expect(user.username).to eq("foobar") + expect(user.name).to eq("foobar") + expect(user.admin).to eq(true) expect(user.public_key).to eq("pubkey") end end + describe "update" do + it "updates an existing user on via the API" do + expect(@http_client).to receive(:put).with("users/foobar", {:name => "foobar", :admin => false}).and_return({}) + @user.update + end + end + describe "destroy" do it "deletes the specified user via the API" do expect(@http_client).to receive(:delete).with("users/foobar") diff --git a/spec/unit/user_v1_spec.rb b/spec/unit/user_v1_spec.rb new file mode 100644 index 0000000000..8fd370a010 --- /dev/null +++ b/spec/unit/user_v1_spec.rb @@ -0,0 +1,584 @@ +# +# Author:: Steven Danna (steve@opscode.com) +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +require 'chef/user_v1' +require 'tempfile' + +describe Chef::UserV1 do + before(:each) do + @user = Chef::UserV1.new + end + + shared_examples_for "string fields with no contraints" do + it "should let you set the public key" do + expect(@user.send(method, "some_string")).to eq("some_string") + end + + it "should return the current public key" do + @user.send(method, "some_string") + expect(@user.send(method)).to eq("some_string") + end + + it "should throw an ArgumentError if you feed it something lame" do + expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) + end + end + + shared_examples_for "boolean fields with no constraints" do + it "should let you set the field" do + expect(@user.send(method, true)).to eq(true) + end + + it "should return the current field value" do + @user.send(method, true) + expect(@user.send(method)).to eq(true) + end + + it "should return the false value when false" do + @user.send(method, false) + expect(@user.send(method)).to eq(false) + end + + it "should throw an ArgumentError if you feed it anything but true or false" do + expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) + end + end + + describe "initialize" do + it "should be a Chef::UserV1" do + expect(@user).to be_a_kind_of(Chef::UserV1) + end + end + + describe "username" do + it "should let you set the username to a string" do + expect(@user.username("ops_master")).to eq("ops_master") + end + + it "should return the current username" do + @user.username "ops_master" + expect(@user.username).to eq("ops_master") + end + + # It is not feasible to check all invalid characters. Here are a few + # that we probably care about. + it "should not accept invalid characters" do + # capital letters + expect { @user.username "Bar" }.to raise_error(ArgumentError) + # slashes + expect { @user.username "foo/bar" }.to raise_error(ArgumentError) + # ? + expect { @user.username "foo?" }.to raise_error(ArgumentError) + # & + expect { @user.username "foo&" }.to raise_error(ArgumentError) + end + + + it "should not accept spaces" do + expect { @user.username "ops master" }.to raise_error(ArgumentError) + end + + it "should throw an ArgumentError if you feed it anything but a string" do + expect { @user.username Hash.new }.to raise_error(ArgumentError) + end + end + + describe "boolean fields" do + describe "create_key" do + it_should_behave_like "boolean fields with no constraints" do + let(:method) { :create_key } + end + end + end + + describe "string fields" do + describe "public_key" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :public_key } + end + end + + describe "private_key" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :private_key } + end + end + + describe "display_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :display_name } + end + end + + describe "first_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :first_name } + end + end + + describe "middle_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :middle_name } + end + end + + describe "last_name" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :last_name } + end + end + + describe "email" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :email } + end + end + + describe "password" do + it_should_behave_like "string fields with no contraints" do + let(:method) { :password } + end + end + end + + describe "when serializing to JSON" do + before(:each) do + @user.username("black") + @json = @user.to_json + end + + it "serializes as a JSON object" do + expect(@json).to match(/^\{.+\}$/) + end + + it "includes the username value" do + expect(@json).to include(%q{"username":"black"}) + end + + it "includes the display name when present" do + @user.display_name("get_displayed") + expect(@user.to_json).to include(%{"display_name":"get_displayed"}) + end + + it "does not include the display name if not present" do + expect(@json).not_to include("display_name") + end + + it "includes the first name when present" do + @user.first_name("char") + expect(@user.to_json).to include(%{"first_name":"char"}) + end + + it "does not include the first name if not present" do + expect(@json).not_to include("first_name") + end + + it "includes the middle name when present" do + @user.middle_name("man") + expect(@user.to_json).to include(%{"middle_name":"man"}) + end + + it "does not include the middle name if not present" do + expect(@json).not_to include("middle_name") + end + + it "includes the last name when present" do + @user.last_name("der") + expect(@user.to_json).to include(%{"last_name":"der"}) + end + + it "does not include the last name if not present" do + expect(@json).not_to include("last_name") + end + + it "includes the email when present" do + @user.email("charmander@pokemon.poke") + expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"}) + end + + it "does not include the email if not present" do + expect(@json).not_to include("email") + end + + it "includes the public key when present" do + @user.public_key("crowes") + expect(@user.to_json).to include(%{"public_key":"crowes"}) + end + + it "does not include the public key if not present" do + expect(@json).not_to include("public_key") + end + + it "includes the private key when present" do + @user.private_key("monkeypants") + expect(@user.to_json).to include(%q{"private_key":"monkeypants"}) + end + + it "does not include the private key if not present" do + expect(@json).not_to include("private_key") + end + + it "includes the password if present" do + @user.password "password" + expect(@user.to_json).to include(%q{"password":"password"}) + end + + it "does not include the password if not present" do + expect(@json).not_to include("password") + end + + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do + let(:jsonable) { @user } + end + end + + describe "when deserializing from JSON" do + before(:each) do + user = { + "username" => "mr_spinks", + "display_name" => "displayed", + "first_name" => "char", + "middle_name" => "man", + "last_name" => "der", + "email" => "charmander@pokemon.poke", + "password" => "password", + "public_key" => "turtles", + "private_key" => "pandas", + "create_key" => false + } + @user = Chef::UserV1.from_json(Chef::JSONCompat.to_json(user)) + end + + it "should deserialize to a Chef::UserV1 object" do + expect(@user).to be_a_kind_of(Chef::UserV1) + end + + it "preserves the username" do + expect(@user.username).to eq("mr_spinks") + end + + it "preserves the display name if present" do + expect(@user.display_name).to eq("displayed") + end + + it "preserves the first name if present" do + expect(@user.first_name).to eq("char") + end + + it "preserves the middle name if present" do + expect(@user.middle_name).to eq("man") + end + + it "preserves the last name if present" do + expect(@user.last_name).to eq("der") + end + + it "preserves the email if present" do + expect(@user.email).to eq("charmander@pokemon.poke") + end + + it "includes the password if present" do + expect(@user.password).to eq("password") + end + + it "preserves the public key if present" do + expect(@user.public_key).to eq("turtles") + end + + it "includes the private key if present" do + expect(@user.private_key).to eq("pandas") + end + + it "includes the create key status if not nil" do + expect(@user.create_key).to be_falsey + end + end + + describe "Versioned API Interactions" do + let(:response_406) { OpenStruct.new(:code => '406') } + let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } + + before (:each) do + @user = Chef::UserV1.new + allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object')) + allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object')) + end + + describe "update" do + before do + # populate all fields that are valid between V0 and V1 + @user.username "some_username" + @user.display_name "some_display_name" + @user.first_name "some_first_name" + @user.middle_name "some_middle_name" + @user.last_name "some_last_name" + @user.email "some_email" + @user.password "some_password" + end + + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :middle_name => "some_middle_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password" + } + } + + context "when server API V1 is valid on the Chef Server receiving the request" do + context "when the user submits valid data" do + it "properly updates the user" do + expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({}) + @user.update + end + end + end + + context "when server API V1 is not valid on the Chef Server receiving the request" do + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :middle_name => "some_middle_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password", + :public_key => "some_public_key" + } + } + + before do + @user.public_key "some_public_key" + allow(@user.chef_root_rest_v1).to receive(:put) + end + + context "when the server returns a 400" do + let(:response_400) { OpenStruct.new(:code => '400') } + let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) } + + context "when the 400 was due to public / private key fields no longer being supported" do + let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' } + + before do + allow(response_400).to receive(:body).and_return(response_body_400) + allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) + end + + it "proceeds with the V0 PUT since it can handle public / private key fields" do + expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) + @user.update + end + + it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do + expect(@user).to_not receive(:server_client_api_version_intersection) + allow(@user.chef_root_rest_v0).to receive(:put).and_return({}) + @user.update + end + end # when the 400 was due to public / private key fields + + context "when the 400 was NOT due to public / private key fields no longer being supported" do + let(:response_body_400) { '{"error":["Some other error. "]}' } + + before do + allow(response_400).to receive(:body).and_return(response_body_400) + allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) + end + + it "will not proceed with the V0 PUT since the original bad request was not key related" do + expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload) + expect { @user.update }.to raise_error(exception_400) + end + + it "raises the original error" do + expect { @user.update }.to raise_error(exception_400) + end + + end + end # when the server returns a 400 + + context "when the server returns a 406" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @user } + let(:method) { :update } + let(:http_verb) { :put } + let(:rest_v1) { @user.chef_root_rest_v1 } + end + + context "when the server supports API V0" do + before do + allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) + allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406) + end + + it "properly updates the user" do + expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) + @user.update + end + end # when the server supports API V0 + end # when the server returns a 406 + + end # when server API V1 is not valid on the Chef Server receiving the request + end # update + + describe "create" do + let(:payload) { + { + :username => "some_username", + :display_name => "some_display_name", + :first_name => "some_first_name", + :last_name => "some_last_name", + :email => "some_email", + :password => "some_password" + } + } + before do + @user.username "some_username" + @user.display_name "some_display_name" + @user.first_name "some_first_name" + @user.last_name "some_last_name" + @user.email "some_email" + @user.password "some_password" + end + + # from spec/support/shared/unit/user_and_client_shared.rb + it_should_behave_like "user or client create" do + let(:object) { @user } + let(:error) { Chef::Exceptions::InvalidUserAttribute } + let(:rest_v0) { @user.chef_root_rest_v0 } + let(:rest_v1) { @user.chef_root_rest_v1 } + let(:url) { "users" } + end + + context "when handling API V1" do + it "creates a new user via the API with a middle_name when it exists" do + @user.middle_name "some_middle_name" + expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) + @user.create + end + end # when server API V1 is valid on the Chef Server receiving the request + + context "when API V1 is not supported by the server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "version handling" do + let(:object) { @user } + let(:method) { :create } + let(:http_verb) { :post } + let(:rest_v1) { @user.chef_root_rest_v1 } + end + end + + context "when handling API V0" do + before do + allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) + allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406) + end + + it "creates a new user via the API with a middle_name when it exists" do + @user.middle_name "some_middle_name" + expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({}) + @user.create + end + end # when server API V1 is not valid on the Chef Server receiving the request + + end # create + + # DEPRECATION + # This can be removed after API V0 support is gone + describe "reregister" do + let(:payload) { + { + "username" => "some_username", + } + } + + before do + @user.username "some_username" + end + + context "when server API V0 is valid on the Chef Server receiving the request" do + it "creates a new object via the API" do + expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({"private_key" => true})).and_return({}) + @user.reregister + end + end # when server API V0 is valid on the Chef Server receiving the request + + context "when server API V0 is not supported by the Chef Server" do + # from spec/support/shared/unit/api_versioning.rb + it_should_behave_like "user and client reregister" do + let(:object) { @user } + let(:rest_v0) { @user.chef_root_rest_v0 } + end + end # when server API V0 is not supported by the Chef Server + end # reregister + + end # Versioned API Interactions + + describe "API Interactions" do + before (:each) do + @user = Chef::UserV1.new + @user.username "foobar" + @http_client = double("Chef::REST mock") + allow(Chef::REST).to receive(:new).and_return(@http_client) + end + + describe "list" do + before(:each) do + Chef::Config[:chef_server_url] = "http://www.example.com" + @osc_response = { "admin" => "http://www.example.com/users/admin"} + @ohc_response = [ { "user" => { "username" => "admin" }} ] + allow(Chef::UserV1).to receive(:load).with("admin").and_return(@user) + @osc_inflated_response = { "admin" => @user } + end + + it "lists all clients on an OHC/OPC server" do + allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) + # We expect that Chef::UserV1.list will give a consistent response + # so OHC API responses should be transformed to OSC-style output. + expect(Chef::UserV1.list).to eq(@osc_response) + end + + it "inflate all clients on an OHC/OPC server" do + allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) + expect(Chef::UserV1.list(true)).to eq(@osc_inflated_response) + end + end + + describe "read" do + it "loads a named user from the API" do + expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"}) + user = Chef::UserV1.load("foobar") + expect(user.username).to eq("foobar") + expect(user.public_key).to eq("pubkey") + end + end + + describe "destroy" do + it "deletes the specified user via the API" do + expect(@http_client).to receive(:delete).with("users/foobar") + @user.destroy + end + end + end +end |