diff options
author | danielsdeleo <dan@getchef.com> | 2015-05-20 15:54:15 -0700 |
---|---|---|
committer | danielsdeleo <dan@getchef.com> | 2015-05-20 15:54:15 -0700 |
commit | 16163d77b84016229a0b285dada12fbc59b80565 (patch) | |
tree | a8c7656b83afc30fddadb187592764d0d4373971 | |
parent | b6f9e5feff3b97576534d70dc2873c4fd62d28e4 (diff) | |
parent | 9984b0cd6b1a88aec19cd5242d1082fe7f9c45a5 (diff) | |
download | chef-16163d77b84016229a0b285dada12fbc59b80565.tar.gz |
Merge branch 'config-gem'
54 files changed, 2130 insertions, 1364 deletions
@@ -3,6 +3,8 @@ gemspec :name => "chef" gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" +gem 'chef-config', path: "chef-config" + group(:docgen) do gem "yard" end @@ -17,7 +17,7 @@ # limitations under the License. # -require File.dirname(__FILE__) + '/lib/chef/version' +VERSION = IO.read(File.expand_path("../VERSION", __FILE__)).strip require 'rubygems' require 'rubygems/package_task' @@ -27,22 +27,99 @@ require_relative 'tasks/external_tests' GEM_NAME = "chef" +desc "build Gems of Chef's components" +task :package_components do + Dir.chdir("chef-config") do + sh "rake package" + end +end + +task :package => :package_components + +desc "build and install chef's components" +task :install_components => :package_components do + Dir.chdir("chef-config") do + sh "rake install" + end +end + +task :install => :install_components + +desc "clean up builds of Chef's components" +task :clobber_component_packages do + Dir.chdir("chef-config") do + sh "rake clobber_package" + end +end + +task :clobber_package => :clobber_component_packages + +desc "Update the version number for Chef's components" +task :update_components_versions do + Dir.chdir("chef-config") do + sh "rake version" + end +end + +desc "Regenerate lib/chef/version.rb from VERSION file" +task :version => :update_components_versions do + contents = <<-VERSION_RB +# Copyright:: Copyright (c) 2010-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. + +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# NOTE: This file is generated by running `rake version` in the top level of +# this repo. Do not edit this manually. Edit the VERSION file and run the rake +# task instead. +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +class Chef + CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) + VERSION = '#{VERSION}' +end + +# +# NOTE: the Chef::Version class is defined in version_class.rb +# +# NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The +# Chef::Version class is for _cookbooks_ only, and cannot handle +# pre-release chef-client versions like "10.14.0.rc.2". Please +# use Rubygem's Gem::Version class instead. +# +VERSION_RB + version_rb_path = File.expand_path("../lib/chef/version.rb", __FILE__) + IO.write(version_rb_path, contents) +end + Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path| gemspec = eval(IO.read(gemspec_path)) Gem::PackageTask.new(gemspec).define end -task :install => :package do - sh %{gem install pkg/#{GEM_NAME}-#{Chef::VERSION}.gem --no-rdoc --no-ri} +desc "Build and install a chef gem" +task :install => [:package] do + sh %{gem install pkg/#{GEM_NAME}-#{VERSION}.gem --no-rdoc --no-ri} end task :uninstall do - sh %{gem uninstall #{GEM_NAME} -x -v #{Chef::VERSION} } + sh %{gem uninstall #{GEM_NAME} -x -v #{VERSION} } end desc "Build it, tag it and ship it" task :ship => [:clobber_package, :gem] do - sh("git tag #{Chef::VERSION}") + sh("git tag #{VERSION}") sh("git push opscode --tags") Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem| sh("gem push #{built_gem}") @@ -80,3 +157,4 @@ begin rescue LoadError puts "yard is not available. (sudo) gem install yard to generate yard documentation." end + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000000..1e37dfcda9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +12.4.0.dev.0 diff --git a/chef-config/.gitignore b/chef-config/.gitignore new file mode 100644 index 0000000000..0cb6eeb067 --- /dev/null +++ b/chef-config/.gitignore @@ -0,0 +1,9 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/chef-config/.rspec b/chef-config/.rspec new file mode 100644 index 0000000000..eb3ef03653 --- /dev/null +++ b/chef-config/.rspec @@ -0,0 +1,2 @@ +--color +-fd diff --git a/chef-config/.travis.yml b/chef-config/.travis.yml new file mode 100644 index 0000000000..927580f35f --- /dev/null +++ b/chef-config/.travis.yml @@ -0,0 +1,31 @@ +language: ruby + +sudo: false +# Early warning system to catch if Rubygems breaks something +before_install: + gem update --system + +branches: + only: + - master + +matrix: + include: + - rvm: 2.0 + - rvm: 2.1 + +notifications: + on_change: true + on_failure: true + on_success: change + on_pull_requests: false + irc: + channels: + - chat.freenode.net#chef-hacking + hipchat: + rooms: + # Build Statuses + - secure: G8MNo94L8bmWWwkH2/ViB2QaZnZHZscYM/mEjDbOGd15sqrruwckeARyBoUcRI7P1C6AFmS4IKCNVXa6KzX4Pbh51gQWM92zRpRTZpplwtXz53/1l8ajLFLLMLvEMTlBFAANUKEUFAQPY4dMa14V3Qc5oijfIncN61k4nZNTKpY= + - rvm: 2.2 + # Open Source + - secure: hmcex4PpG5dn8WvjndONO4xCUKOC5kPU/bUEGRrfVbe2YKJE7t0XXbNDC96W/xBgzgnJzvf1Er0zJKDrNf4qEDEWFoozdN00WLcqREgaLLS3Seto2FjR/BpBk5q+sCV0rwwEMms2P4Qk+VSnDCnm9EaeM55hOabqNuOrRzoZLBQ= diff --git a/chef-config/Gemfile b/chef-config/Gemfile new file mode 100644 index 0000000000..d39725ff87 --- /dev/null +++ b/chef-config/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in chef-config.gemspec +gemspec diff --git a/chef-config/LICENSE b/chef-config/LICENSE new file mode 100644 index 0000000000..11069edd79 --- /dev/null +++ b/chef-config/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. diff --git a/chef-config/README.md b/chef-config/README.md new file mode 100644 index 0000000000..c36527282e --- /dev/null +++ b/chef-config/README.md @@ -0,0 +1,4 @@ +# ChefConfig + +This repo is experimental. Use at your own risk. + diff --git a/chef-config/Rakefile b/chef-config/Rakefile new file mode 100644 index 0000000000..6eb195f672 --- /dev/null +++ b/chef-config/Rakefile @@ -0,0 +1,55 @@ +require 'rspec/core/rake_task' +require 'rubygems/package_task' + +VERSION = IO.read(File.expand_path("../../VERSION", __FILE__)).strip + +Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path| + gemspec = eval(IO.read(gemspec_path)) + Gem::PackageTask.new(gemspec).define +end + +desc "Build and install a chef-config gem" +task :install => [:package] do + sh %{gem install pkg/chef-config-#{ChefConfig::VERSION}.gem --no-rdoc --no-ri} +end + +task :default => :spec + +desc "Run standard specs" +RSpec::Core::RakeTask.new(:spec) do |t| + t.pattern = FileList['spec/**/*_spec.rb'] +end + +desc "Regenerate lib/chef/version.rb from VERSION file" +task :version do + contents = <<-VERSION_RB +# Copyright:: Copyright (c) 2010-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. + +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# NOTE: This file is generated by running `rake version` in the top level of +# this repo. Do not edit this manually. Edit the VERSION file and run the rake +# task instead. +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +module ChefConfig + VERSION = '#{VERSION}' +end + +VERSION_RB + version_rb_path = File.expand_path("../lib/chef-config/version.rb", __FILE__) + IO.write(version_rb_path, contents) +end + diff --git a/chef-config/chef-config.gemspec b/chef-config/chef-config.gemspec new file mode 100644 index 0000000000..f0a569f3c1 --- /dev/null +++ b/chef-config/chef-config.gemspec @@ -0,0 +1,32 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'chef-config/version' + +Gem::Specification.new do |spec| + spec.name = "chef-config" + spec.version = ChefConfig::VERSION + spec.authors = ["Adam Jacob"] + spec.email = ["adam@chef.io"] + + spec.summary = %q{Chef's default configuration and config loading} + spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.license = "Apache-2.0" + + spec.require_paths = ["lib"] + + spec.add_dependency "mixlib-shellout", "~> 2.0" + spec.add_dependency "mixlib-config", "~> 2.0" + + spec.add_development_dependency "rake", "~> 10.0" + + %w(rspec-core rspec-expectations rspec-mocks).each do |rspec| + spec.add_development_dependency(rspec, "~> 3.2") + end + + spec.files = %w(Rakefile LICENSE README.md) + + Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + + spec.bindir = "bin" + spec.executables = [] +end diff --git a/chef-config/lib/chef-config.rb b/chef-config/lib/chef-config.rb new file mode 100644 index 0000000000..1f593c868f --- /dev/null +++ b/chef-config/lib/chef-config.rb @@ -0,0 +1,20 @@ +# +# 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. +# + +module ChefConfig + +end diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb new file mode 100644 index 0000000000..800e8065de --- /dev/null +++ b/chef-config/lib/chef-config/config.rb @@ -0,0 +1,737 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: AJ Christensen (<aj@opscode.com>) +# Author:: Mark Mzyk (<mmzyk@opscode.com>) +# Author:: Kyle Goodwin (<kgoodwin@primerevenue.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 'mixlib/config' +require 'pathname' + +require 'chef-config/logger' +require 'chef-config/windows' +require 'chef-config/path_helper' +require 'mixlib/shellout' + +module ChefConfig + + class Config + + extend Mixlib::Config + + # Evaluates the given string as config. + # + # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file. + def self.from_string(string, filename) + self.instance_eval(string, filename, 1) + end + + def self.inspect + configuration.inspect + end + + def self.platform_specific_path(path) + path = PathHelper.cleanpath(path) + if ChefConfig.windows? + # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb + if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef' + path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2]) + end + end + path + end + + def self.add_formatter(name, file_path=nil) + formatters << [name, file_path] + end + + def self.add_event_logger(logger) + event_handlers << logger + end + + # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.) + configurable(:config_file) + + default(:config_dir) do + if config_file + PathHelper.dirname(config_file) + else + PathHelper.join(user_home, ".chef", "") + end + end + + default :formatters, [] + + # Override the config dispatch to set the value of multiple server options simultaneously + # + # === Parameters + # url<String>:: String to be set for all of the chef-server-api URL's + # + configurable(:chef_server_url).writes_value { |url| url.to_s.strip } + + # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel. + # So while this is basically identical to what method_missing would do, we pull + # it up here and get a real method written so that things get dispatched + # properly. + configurable(:daemonize).writes_value { |v| v } + + # The root where all local chef object data is stored. cookbooks, data bags, + # environments are all assumed to be in separate directories under this. + # chef-solo uses these directories for input data. knife commands + # that upload or download files (such as knife upload, knife role from file, + # etc.) work. + default :chef_repo_path do + if self.configuration[:cookbook_path] + if self.configuration[:cookbook_path].kind_of?(String) + File.expand_path('..', self.configuration[:cookbook_path]) + else + self.configuration[:cookbook_path].map do |path| + File.expand_path('..', path) + end + end + else + cache_path + end + end + + def self.find_chef_repo_path(cwd) + # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it. + # This allows us to run config-free. + path = cwd + until File.directory?(PathHelper.join(path, "cookbooks")) + new_path = File.expand_path('..', path) + if new_path == path + ChefConfig.logger.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.") + return Dir.pwd + end + path = new_path + end + ChefConfig.logger.info("Auto-discovered chef repository at #{path}") + path + end + + def self.derive_path_from_chef_repo_path(child_path) + if chef_repo_path.kind_of?(String) + PathHelper.join(chef_repo_path, child_path) + else + chef_repo_path.map { |path| PathHelper.join(path, child_path)} + end + end + + # Location of acls on disk. String or array of strings. + # Defaults to <chef_repo_path>/acls. + # Only applies to Enterprise Chef commands. + default(:acl_path) { derive_path_from_chef_repo_path('acls') } + + # Location of clients on disk. String or array of strings. + # Defaults to <chef_repo_path>/acls. + default(:client_path) { derive_path_from_chef_repo_path('clients') } + + # Location of cookbooks on disk. String or array of strings. + # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path + # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]). + default(:cookbook_path) do + if self.configuration[:chef_repo_path] + derive_path_from_chef_repo_path('cookbooks') + else + Array(derive_path_from_chef_repo_path('cookbooks')).flatten + + Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten + end + end + + # Location of containers on disk. String or array of strings. + # Defaults to <chef_repo_path>/containers. + # Only applies to Enterprise Chef commands. + default(:container_path) { derive_path_from_chef_repo_path('containers') } + + # Location of data bags on disk. String or array of strings. + # Defaults to <chef_repo_path>/data_bags. + default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') } + + # Location of environments on disk. String or array of strings. + # Defaults to <chef_repo_path>/environments. + default(:environment_path) { derive_path_from_chef_repo_path('environments') } + + # Location of groups on disk. String or array of strings. + # Defaults to <chef_repo_path>/groups. + # Only applies to Enterprise Chef commands. + default(:group_path) { derive_path_from_chef_repo_path('groups') } + + # Location of nodes on disk. String or array of strings. + # Defaults to <chef_repo_path>/nodes. + default(:node_path) { derive_path_from_chef_repo_path('nodes') } + + # Location of roles on disk. String or array of strings. + # Defaults to <chef_repo_path>/roles. + default(:role_path) { derive_path_from_chef_repo_path('roles') } + + # Location of users on disk. String or array of strings. + # Defaults to <chef_repo_path>/users. + # Does not apply to Enterprise Chef commands. + default(:user_path) { derive_path_from_chef_repo_path('users') } + + # Location of policies on disk. String or array of strings. + # Defaults to <chef_repo_path>/policies. + default(:policy_path) { derive_path_from_chef_repo_path('policies') } + + # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity + default :enforce_path_sanity, true + + # Formatted Chef Client output is a beta feature, disabled by default: + default :formatter, "null" + + # The number of times the client should retry when registering with the server + default :client_registration_retries, 5 + + # An array of paths to search for knife exec scripts if they aren't in the current directory + default :script_path, [] + + # The root of all caches (checksums, cache and backup). If local mode is on, + # this is under the user's home directory. + default(:cache_path) do + if local_mode + PathHelper.join(config_dir, 'local-mode-cache') + else + primary_cache_root = platform_specific_path("/var") + primary_cache_path = platform_specific_path("/var/chef") + # Use /var/chef as the cache path only if that folder exists and we can read and write + # into it, or /var exists and we can read and write into it (we'll create /var/chef later). + # Otherwise, we'll create .chef under the user's home directory and use that as + # the cache path. + unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root) + secondary_cache_path = PathHelper.join(user_home, '.chef') + ChefConfig.logger.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}") + secondary_cache_path + else + primary_cache_path + end + end + end + + # Returns true only if the path exists and is readable and writeable for the user. + def self.path_accessible?(path) + File.exists?(path) && File.readable?(path) && File.writable?(path) + end + + # Where cookbook files are stored on the server (by content checksum) + default(:checksum_path) { PathHelper.join(cache_path, "checksums") } + + # Where chef's cache files should be stored + default(:file_cache_path) { PathHelper.join(cache_path, "cache") } + + # Where backups of chef-managed files should go + default(:file_backup_path) { PathHelper.join(cache_path, "backup") } + + # The chef-client (or solo) lockfile. + # + # If your `file_cache_path` resides on a NFS (or non-flock()-supporting + # fs), it's recommended to set this to something like + # '/tmp/chef-client-running.pid' + default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") } + + ## Daemonization Settings ## + # What user should Chef run as? + default :user, nil + default :group, nil + default :umask, 0022 + + # Valid log_levels are: + # * :debug + # * :info + # * :warn + # * :fatal + # These work as you'd expect. There is also a special `:auto` setting. + # When set to :auto, Chef will auto adjust the log verbosity based on + # context. When a tty is available (usually because the user is running chef + # in a console), the log level is set to :warn, and output formatters are + # used as the primary mode of output. When a tty is not available, the + # logger is the primary mode of output, and the log level is set to :info + default :log_level, :auto + + # Logging location as either an IO stream or string representing log file path + default :log_location, STDOUT + + # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty + default :force_formatter, false + + # Using `force_logger` causes chef to default to logger output when STDOUT is a tty + default :force_logger, false + + default :http_retry_count, 5 + default :http_retry_delay, 5 + default :interval, nil + default :once, nil + default :json_attribs, nil + # toggle info level log items that can create a lot of output + default :verbose_logging, true + default :node_name, nil + default :diff_disabled, false + default :diff_filesize_threshold, 10000000 + default :diff_output_threshold, 1000000 + default :local_mode, false + + default :pid_file, nil + + # Whether Chef Zero local mode should bind to a port. All internal requests + # will go through the socketless code path regardless, so the socket is + # only needed if other processes will connect to the local mode server. + # + # For compatibility this is set to true but it will be changed to false in + # the future. + default :listen, true + + config_context :chef_zero do + config_strict_mode true + default(:enabled) { ChefConfig::Config.local_mode } + default :host, 'localhost' + default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works + end + default :chef_server_url, "https://localhost:443" + + default(:chef_server_root) do + # if the chef_server_url is a path to an organization, aka + # 'some_url.../organizations/*' then remove the '/organization/*' by default + if self.configuration[:chef_server_url] =~ /\/organizations\/\S*$/ + self.configuration[:chef_server_url].split('/')[0..-3].join('/') + elsif self.configuration[:chef_server_url] # default to whatever chef_server_url is + self.configuration[:chef_server_url] + else + "https://localhost:443" + end + end + + default :rest_timeout, 300 + default :yum_timeout, 900 + default :yum_lock_timeout, 30 + default :solo, false + default :splay, nil + default :why_run, false + default :color, false + default :client_fork, true + default :ez, false + default :enable_reporting, true + default :enable_reporting_url_fatals, false + # Possible values for :audit_mode + # :enabled, :disabled, :audit_only, + # + # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature + # and is disabled by default. When users choose to enable audit-mode, + # a warning is issued in application/client#reconfigure. + # This can be removed when audit-mode is enabled by default. + default :audit_mode, :disabled + + # Chef only needs ohai to run the hostname plugin for the most basic + # functionality. If the rest of the ohai plugins are not needed (like in + # most of our testing scenarios) + default :minimal_ohai, false + + # Policyfile is an experimental feature where a node gets its run list and + # cookbook version set from a single document on the server instead of + # expanding the run list and having the server compute the cookbook version + # set based on environment constraints. + # + # Because this feature is experimental, it is not recommended for + # production use. Developent/release of this feature may not adhere to + # semver guidelines. + default :use_policyfile, false + + # Set these to enable SSL authentication / mutual-authentication + # with the server + + # Client side SSL cert/key for mutual auth + default :ssl_client_cert, nil + default :ssl_client_key, nil + + # Whether or not to verify the SSL cert for all HTTPS requests. When set to + # :verify_peer (default), all HTTPS requests will be validated regardless of other + # SSL verification settings. When set to :verify_none no HTTPS requests will + # be validated. + default :ssl_verify_mode, :verify_peer + + # Whether or not to verify the SSL cert for HTTPS requests to the Chef + # server API. If set to `true`, the server's cert will be validated + # regardless of the :ssl_verify_mode setting. This is set to `true` when + # running in local-mode. + # NOTE: This is a workaround until verify_peer is enabled by default. + default(:verify_api_cert) { ChefConfig::Config.local_mode } + + # Path to the default CA bundle files. + default :ssl_ca_path, nil + default(:ssl_ca_file) do + if ChefConfig.windows? and embedded_path = embedded_dir + cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem") + cacert_path if File.exist?(cacert_path) + else + nil + end + end + + # A directory that contains additional SSL certificates to trust. Any + # certificates in this directory will be added to whatever CA bundle ruby + # is using. Use this to add self-signed certs for your Chef Server or local + # HTTP file servers. + default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") } + + # Where should chef-solo download recipes from? + default :recipe_url, nil + + # Sets the version of the signed header authentication protocol to use (see + # the 'mixlib-authorization' project for more detail). Currently, versions + # 1.0 and 1.1 are available; however, the chef-server must first be + # upgraded to support version 1.1 before clients can begin using it. + # + # Version 1.1 of the protocol is required when using a `node_name` greater + # than ~90 bytes (~90 ascii characters), so chef-client will automatically + # switch to using version 1.1 when `node_name` is too large for the 1.0 + # protocol. If you intend to use large node names, ensure that your server + # supports version 1.1. Automatic detection of large node names means that + # users will generally not need to manually configure this. + # + # In the future, this configuration option may be replaced with an + # automatic negotiation scheme. + default :authentication_protocol_version, "1.0" + + # This key will be used to sign requests to the Chef server. This location + # must be writable by Chef during initial setup when generating a client + # identity on the server. + # + # The chef-server will look up the public key for the client using the + # `node_name` of the client. + # + # If chef-zero is enabled, this defaults to nil (no authentication). + default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") } + + # When registering the client, should we allow the client key location to + # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem + # If the path of the key goes through a directory like /tmp this should + # never be set to true or its possibly an easily exploitable security hole. + default :follow_client_key_symlink, false + + # This secret is used to decrypt encrypted data bag items. + default(:encrypted_data_bag_secret) do + if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret")) + platform_specific_path("/etc/chef/encrypted_data_bag_secret") + else + nil + end + end + + # As of Chef 11.0, version "1" is the default encrypted data bag item + # format. Version "2" is available which adds encrypt-then-mac protection. + # To maintain compatibility, versions other than 1 must be opt-in. + # + # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure. + # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO) + default :data_bag_encrypt_version, 1 + + # When reading data bag items, any supported version is accepted. However, + # if all encrypted data bags have been generated with the version 2 format, + # it is recommended to disable support for earlier formats to improve + # security. For example, the version 2 format is identical to version 1 + # except for the addition of an HMAC, so an attacker with MITM capability + # could downgrade an encrypted data bag to version 1 as part of an attack. + default :data_bag_decrypt_minimum_version, 0 + + # If there is no file in the location given by `client_key`, chef-client + # will temporarily use the "validator" identity to generate one. If the + # `client_key` is not present and the `validation_key` is also not present, + # chef-client will not be able to authenticate to the server. + # + # The `validation_key` is never used if the `client_key` exists. + # + # If chef-zero is enabled, this defaults to nil (no authentication). + default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") } + default :validation_client_name, "chef-validator" + + # When creating a new client via the validation_client account, Chef 11 + # servers allow the client to generate a key pair locally and send the + # public key to the server. This is more secure and helps offload work from + # the server, enhancing scalability. If enabled and the remote server + # implements only the Chef 10 API, client registration will not work + # properly. + # + # The default value is `true`. Set to `false` to disable client-side key + # generation (server generates client keys). + default(:local_key_generation) { true } + + # Zypper package provider gpg checks. Set to true to enable package + # gpg signature checking. This will be default in the + # future. Setting to false disables the warnings. + # Leaving this set to nil or false is a security hazard! + default :zypper_check_gpg, nil + + # Report Handlers + default :report_handlers, [] + + # Event Handlers + default :event_handlers, [] + + default :disable_event_loggers, false + + # Exception Handlers + default :exception_handlers, [] + + # Start handlers + default :start_handlers, [] + + # Syntax Check Cache. Knife keeps track of files that is has already syntax + # checked by storing files in this directory. `syntax_check_cache_path` is + # the new (and preferred) configuration setting. If not set, knife will + # fall back to using cache_options[:path], which is deprecated but exists in + # many client configs generated by pre-Chef-11 bootstrappers. + default(:syntax_check_cache_path) { cache_options[:path] } + + # Deprecated: + # Move this to the default value of syntax_cache_path when this is removed. + default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } } + + # Whether errors should be raised for deprecation warnings. When set to + # `false` (the default setting), a warning is emitted but code using + # deprecated methods/features/etc. should work normally otherwise. When set + # to `true`, usage of deprecated methods/features will raise a + # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that + # deprecated functionality is not used internally by Chef. End users + # should generally leave this at the default setting (especially in + # production), but it may be useful when testing cookbooks or other code if + # the user wishes to aggressively address deprecations. + default(:treat_deprecation_warnings_as_errors) do + # Using an environment variable allows this setting to be inherited in + # tests that spawn new processes. + ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS") + end + + # knife configuration data + config_context :knife do + default :ssh_port, nil + default :ssh_user, nil + default :ssh_attribute, nil + default :ssh_gateway, nil + default :bootstrap_version, nil + default :bootstrap_proxy, nil + default :bootstrap_template, nil + default :secret, nil + default :secret_file, nil + default :identity_file, nil + default :host_key_verify, nil + default :forward_agent, nil + default :sort_status_reverse, nil + default :hints, {} + end + + def self.set_defaults_for_windows + # Those lists of regular expressions define what chef considers a + # valid user and group name + # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx + principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+' + default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] + default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] + + default :fatal_windows_admin_check, false + end + + def self.set_defaults_for_nix + # Those lists of regular expressions define what chef considers a + # valid user and group name + # + # user/group cannot start with '-', '+' or '~' + # user/group cannot contain ':', ',' or non-space-whitespace or null byte + # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not + # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup + default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ] + default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ] + end + + # Those lists of regular expressions define what chef considers a + # valid user and group name + if ChefConfig.windows? + set_defaults_for_windows + else + set_defaults_for_nix + end + + # This provides a hook which rspec can stub so that we can avoid twiddling + # global state in tests. + def self.env + ENV + end + + def self.windows_home_path + ChefConfig.logger.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.") + PathHelper.home + end + + # returns a platform specific path to the user home dir if set, otherwise default to current directory. + default( :user_home ) { PathHelper.home || Dir.pwd } + + # Enable file permission fixup for selinux. Fixup will be done + # only if selinux is enabled in the system. + default :enable_selinux_file_permission_fixup, true + + # Use atomic updates (i.e. move operation) while updating contents + # of the files resources. When set to false copy operation is + # used to update files. + default :file_atomic_update, true + + # There are 3 possible values for this configuration setting. + # true => file staging is done in the destination directory + # false => file staging is done via tempfiles under ENV['TMP'] + # :auto => file staging will try using destination directory if possible and + # will fall back to ENV['TMP'] if destination directory is not usable. + default :file_staging_uses_destdir, :auto + + # Exit if another run is in progress and the chef-client is unable to + # get the lock before time expires. If nil, no timeout is enforced. (Exits + # immediately if 0.) + default :run_lock_timeout, nil + + # Number of worker threads for syncing cookbooks in parallel. Increasing + # this number can result in gateway errors from the server (namely 503 and 504). + # If you are seeing this behavior while using the default setting, reducing + # the number of threads will help. + default :cookbook_sync_threads, 10 + + # At the beginning of the Chef Client run, the cookbook manifests are downloaded which + # contain URLs for every file in every relevant cookbook. Most of the files + # (recipes, resources, providers, libraries, etc) are immediately synchronized + # at the start of the run. The handling of "files" and "templates" directories, + # however, have two modes of operation. They can either all be downloaded immediately + # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as + # cookbook_file or template resources are converged which require them (no_lazy_load==false). + # + # The advantage of lazily loading these files is that unnecessary files are not + # synchronized. This may be useful to users with large files checked into cookbooks which + # are only selectively downloaded to a subset of clients which use the cookbook. However, + # better solutions are to either isolate large files into individual cookbooks and only + # include those cookbooks in the run lists of the servers that need them -- or move to + # using remote_file and a more appropriate backing store like S3 for large file + # distribution. + # + # The disadvantages of lazily loading files are that users some time find it + # confusing that their cookbooks are not fully synchronzied to the cache initially, + # and more importantly the time-sensitive URLs which are in the manifest may time + # out on long Chef runs before the resource that uses the file is converged + # (leading to many confusing 403 errors on template/cookbook_file resources). + # + default :no_lazy_load, true + + # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit + # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to + # true then the user will get backcompat behavior but with a single nag warning that cookbooks + # may break with this setting in the future. The false setting is the recommended setting and + # will become the default. + default :chef_gem_compile_time, nil + + # A whitelisted array of attributes you want sent over the wire when node + # data is saved. + # The default setting is nil, which collects all data. Setting to [] will not + # collect any data for save. + default :automatic_attribute_whitelist, nil + default :default_attribute_whitelist, nil + default :normal_attribute_whitelist, nil + default :override_attribute_whitelist, nil + + config_context :windows_service do + # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run + # to finish + default :watchdog_timeout, 2 * (60 * 60) # 2 hours + end + + # Chef requires an English-language UTF-8 locale to function properly. We attempt + # to use the 'locale -a' command and search through a list of preferences until we + # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be + # able to use that even if there is no English locale on the server, but Mac, Solaris, + # AIX, etc do not have that locale. We then try to find an English locale and fall + # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try + # to do the work to return a non-US UTF-8 locale then we fail inside of providers when + # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then + # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding + # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by + # default rather than drop English. + # + # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly + # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'. + def self.guess_internal_locale + # https://github.com/opscode/chef/issues/2181 + # Some systems have the `locale -a` command, but the result has + # invalid characters for the default encoding. + # + # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8", + # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding. + cmd = Mixlib::ShellOut.new("locale -a").run_command + cmd.error! + locales = cmd.stdout.split + case + when locales.include?('C.UTF-8') + 'C.UTF-8' + when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8') + 'en_US.UTF-8' + when locales.include?('en.UTF-8') + 'en.UTF-8' + else + # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8 + guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i } + unless guesses.empty? + guessed_locale = guesses.first + # Transform into the form en_ZZ.UTF-8 + guessed_locale.gsub(/UTF-?8$/i, "UTF-8") + else + ChefConfig.logger.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support." + 'C' + end + end + rescue + if ChefConfig.windows? + ChefConfig.logger.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else." + else + ChefConfig.logger.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed." + end + 'en_US.UTF-8' + end + + default :internal_locale, guess_internal_locale + + # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g. + # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's + # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been + # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be + # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with + # magic tags to make ruby correctly identify the encoding being used. Changing this default will + # break Chef community cookbooks and is very highly discouraged. + default :ruby_encoding, Encoding::UTF_8 + + # If installed via an omnibus installer, this gives the path to the + # "embedded" directory which contains all of the software packaged with + # omnibus. This is used to locate the cacert.pem file on windows. + def self.embedded_dir + Pathname.new(_this_file).ascend do |path| + if path.basename.to_s == "embedded" + return path.to_s + end + end + + nil + end + + # Path to this file in the current install. + def self._this_file + File.expand_path(__FILE__) + end + end +end + + + diff --git a/chef-config/lib/chef-config/exceptions.rb b/chef-config/lib/chef-config/exceptions.rb new file mode 100644 index 0000000000..f5d76d856b --- /dev/null +++ b/chef-config/lib/chef-config/exceptions.rb @@ -0,0 +1,26 @@ +# +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef-config/windows' +require 'chef-config/logger' + +module ChefConfig + + class InvalidPath < StandardError + end + +end diff --git a/chef-config/lib/chef-config/logger.rb b/chef-config/lib/chef-config/logger.rb new file mode 100644 index 0000000000..57f18809ee --- /dev/null +++ b/chef-config/lib/chef-config/logger.rb @@ -0,0 +1,62 @@ +# +# 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. +# + + +module ChefConfig + + # Implements enough of Logger's API that we can use it in place of a real + # logger for `ChefConfig.logger` + class NullLogger + + def <<(_msg) + end + + def add(_severity, _message = nil, _progname = nil) + end + + def debug(_progname = nil, &block) + end + + def info(_progname = nil, &block) + end + + def warn(_progname = nil, &block) + end + + def deprecation(_progname = nil, &block) + end + + def error(_progname = nil, &block) + end + + def fatal(_progname = nil, &block) + end + + end + + @logger = NullLogger.new + + def self.logger=(new_logger) + @logger = new_logger + end + + def self.logger + @logger + end +end + + diff --git a/chef-config/lib/chef-config/path_helper.rb b/chef-config/lib/chef-config/path_helper.rb new file mode 100644 index 0000000000..acc6b76377 --- /dev/null +++ b/chef-config/lib/chef-config/path_helper.rb @@ -0,0 +1,233 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# 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/windows' +require 'chef-config/logger' +require 'chef-config/exceptions' + +module ChefConfig + class PathHelper + # Maximum characters in a standard Windows path (260 including drive letter and NUL) + WIN_MAX_PATH = 259 + + def self.dirname(path) + if ChefConfig.windows? + # Find the first slash, not counting trailing slashes + end_slash = path.size + loop do + slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1) + if !slash + return end_slash == path.size ? '.' : path_separator + elsif slash == end_slash - 1 + end_slash = slash + else + return path[0..slash-1] + end + end + else + ::File.dirname(path) + end + end + + BACKSLASH = '\\'.freeze + + def self.path_separator + if ChefConfig.windows? + File::ALT_SEPARATOR || BACKSLASH + else + File::SEPARATOR + end + end + + def self.join(*args) + path_separator_regex = Regexp.escape(File::SEPARATOR) + unless path_separator == File::SEPARATOR + path_separator_regex << Regexp.escape(path_separator) + end + + trailing_slashes = /[#{path_separator_regex}]+$/ + leading_slashes = /^[#{path_separator_regex}]+/ + + args.flatten.inject() do |joined_path, component| + joined_path = joined_path.sub(trailing_slashes, '') + component = component.sub(leading_slashes, '') + joined_path += "#{path_separator}#{component}" + end + end + + def self.validate_path(path) + if ChefConfig.windows? + unless printable?(path) + msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings." + ChefConfig.logger.error(msg) + raise ChefConfig::InvalidPath, msg + end + + if windows_max_length_exceeded?(path) + ChefConfig.logger.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'") + path.insert(0, "\\\\?\\") + end + end + + path + end + + def self.windows_max_length_exceeded?(path) + # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + unless path =~ /^\\\\?\\/ + if path.length > WIN_MAX_PATH + return true + end + end + + false + end + + def self.printable?(string) + # returns true if string is free of non-printable characters (escape sequences) + # this returns false for whitespace escape sequences as well, e.g. \n\t + if string =~ /[^[:print:]]/ + false + else + true + end + end + + # Produces a comparable path. + def self.canonical_path(path, add_prefix=true) + # First remove extra separators and resolve any relative paths + abs_path = File.absolute_path(path) + + if ChefConfig.windows? + # Add the \\?\ API prefix on Windows unless add_prefix is false + # Downcase on Windows where paths are still case-insensitive + abs_path.gsub!(::File::SEPARATOR, path_separator) + if add_prefix && abs_path !~ /^\\\\?\\/ + abs_path.insert(0, "\\\\?\\") + end + + abs_path.downcase! + end + + abs_path + end + + def self.cleanpath(path) + path = Pathname.new(path).cleanpath.to_s + # ensure all forward slashes are backslashes + if ChefConfig.windows? + path = path.gsub(File::SEPARATOR, path_separator) + end + path + end + + def self.paths_eql?(path1, path2) + canonical_path(path1) == canonical_path(path2) + end + + # Paths which may contain glob-reserved characters need + # to be escaped before globbing can be done. + # http://stackoverflow.com/questions/14127343 + def self.escape_glob(*parts) + path = cleanpath(join(*parts)) + path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x } + end + + def self.relative_path_from(from, to) + Pathname.new(cleanpath(to)).relative_path_from(Pathname.new(cleanpath(from))) + end + + # Retrieves the "home directory" of the current user while trying to ascertain the existence + # of said directory. The path returned uses / for all separators (the ruby standard format). + # If the home directory doesn't exist or an error is otherwise encountered, nil is returned. + # + # If a set of path elements is provided, they are appended as-is to the home path if the + # homepath exists. + # + # If an optional block is provided, the joined path is passed to that block if the home path is + # valid and the result of the block is returned instead. + # + # Home-path discovery is performed once. If a path is discovered, that value is memoized so + # that subsequent calls to home_dir don't bounce around. + # + # See self.all_homes. + def self.home(*args) + @@home_dir ||= self.all_homes { |p| break p } + if @@home_dir + path = File.join(@@home_dir, *args) + block_given? ? (yield path) : path + end + end + + # See self.home. This method performs a similar operation except that it yields all the different + # possible values of 'HOME' that one could have on this platform. Hence, on windows, if + # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice. + # This method goes out and checks the existence of each location at the time of the call. + # + # The return is a list of all the returned values from each block invocation or a list of paths + # if no block is provided. + def self.all_homes(*args) + paths = [] + if ChefConfig.windows? + # By default, Ruby uses the the following environment variables to determine Dir.home: + # HOME + # HOMEDRIVE HOMEPATH + # USERPROFILE + # Ruby only checks to see if the variable is specified - not if the directory actually exists. + # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive) + # while USERPROFILE points to the location where the user application settings and profile are stored. HOME + # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is + # HOMESHARE instead of HOMEDRIVE. + # + # We instead walk down the following and only include paths that actually exist. + # HOME + # HOMEDRIVE HOMEPATH + # HOMESHARE HOMEPATH + # USERPROFILE + + paths << ENV['HOME'] + paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] + paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH'] + paths << ENV['USERPROFILE'] + end + paths << Dir.home if ENV['HOME'] + + # Depending on what environment variables we're using, the slashes can go in any which way. + # Just change them all to / to keep things consistent. + # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on + # the particular brand of kool-aid you consume. This code assumes that \ and / are both + # path separators on any system being used. + paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path } + + # Filter out duplicate paths and paths that don't exist. + valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) } + valid_paths = valid_paths.uniq + + # Join all optional path elements at the end. + # If a block is provided, invoke it - otherwise just return what we've got. + joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) } + if block_given? + joined_paths.each { |p| yield p } + else + joined_paths + end + end + end +end + diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb new file mode 100644 index 0000000000..2371f31d98 --- /dev/null +++ b/chef-config/lib/chef-config/version.rb @@ -0,0 +1,25 @@ +# Copyright:: Copyright (c) 2010-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. + +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# NOTE: This file is generated by running `rake version` in the top level of +# this repo. Do not edit this manually. Edit the VERSION file and run the rake +# task instead. +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +module ChefConfig + VERSION = '12.4.0.dev.0' +end + diff --git a/chef-config/lib/chef-config/windows.rb b/chef-config/lib/chef-config/windows.rb new file mode 100644 index 0000000000..a2e90067df --- /dev/null +++ b/chef-config/lib/chef-config/windows.rb @@ -0,0 +1,29 @@ +# +# 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. +# + +module ChefConfig + + def self.windows? + if RUBY_PLATFORM =~ /mswin|mingw|windows/ + true + else + false + end + end + +end + diff --git a/chef-config/spec/spec_helper.rb b/chef-config/spec/spec_helper.rb new file mode 100644 index 0000000000..df9461cde9 --- /dev/null +++ b/chef-config/spec/spec_helper.rb @@ -0,0 +1,75 @@ +require 'chef-config/windows' + +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + config.filter_run_excluding :windows_only => true unless ChefConfig.windows? + config.filter_run_excluding :unix_only => true if ChefConfig.windows? + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + # config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +end diff --git a/spec/unit/config_spec.rb b/chef-config/spec/unit/config_spec.rb index d5d05dcfc8..395fa2618e 100644 --- a/spec/unit/config_spec.rb +++ b/chef-config/spec/unit/config_spec.rb @@ -18,37 +18,46 @@ # require 'spec_helper' -require 'chef/exceptions' -require 'chef/util/path_helper' +require 'chef-config/config' + +RSpec.describe ChefConfig::Config do + before(:each) do + ChefConfig::Config.reset + + # By default, treat deprecation warnings as errors in tests. + ChefConfig::Config.treat_deprecation_warnings_as_errors(true) + + # Set environment variable so the setting persists in child processes + ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS'] = "1" + end -describe Chef::Config do describe "config attribute writer: chef_server_url" do before do - Chef::Config.chef_server_url = "https://junglist.gen.nz" + ChefConfig::Config.chef_server_url = "https://junglist.gen.nz" end it "sets the server url" do - expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz") + expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz") end context "when the url has a leading space" do before do - Chef::Config.chef_server_url = " https://junglist.gen.nz" + ChefConfig::Config.chef_server_url = " https://junglist.gen.nz" end it "strips the space from the url when setting" do - expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz") + expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz") end end context "when the url is a frozen string" do before do - Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze + ChefConfig::Config.chef_server_url = " https://junglist.gen.nz".freeze end it "strips the space from the url when setting without raising an error" do - expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz") + expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz") end end end @@ -78,41 +87,17 @@ describe Chef::Config do # end # it "has an empty list of formatters by default" do - expect(Chef::Config.formatters).to eq([]) + expect(ChefConfig::Config.formatters).to eq([]) end it "configures a formatter with a short name" do - Chef::Config.add_formatter(:doc) - expect(Chef::Config.formatters).to eq([[:doc, nil]]) + ChefConfig::Config.add_formatter(:doc) + expect(ChefConfig::Config.formatters).to eq([[:doc, nil]]) end it "configures a formatter with a file output" do - Chef::Config.add_formatter(:doc, "/var/log/formatter.log") - expect(Chef::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]]) - end - - end - - describe "class method: manage_secret_key" do - before do - allow(Chef::FileCache).to receive(:load).and_return(true) - allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(false) - end - - it "should generate and store a chef server cookie id" do - expect(Chef::FileCache).to receive(:store).with("chef_server_cookie_id", /\w{40}/).and_return(true) - Chef::Config.manage_secret_key - end - - describe "when the filecache has a chef server cookie id key" do - before do - allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(true) - end - - it "should not generate and store a chef server cookie id" do - expect(Chef::FileCache).not_to receive(:store).with("chef_server_cookie_id", /\w{40}/) - Chef::Config.manage_secret_key - end + ChefConfig::Config.add_formatter(:doc, "/var/log/formatter.log") + expect(ChefConfig::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]]) end end @@ -121,27 +106,27 @@ describe Chef::Config do context "On #{is_windows ? 'Windows' : 'Unix'}" do def to_platform(*args) - Chef::Config.platform_specific_path(*args) + ChefConfig::Config.platform_specific_path(*args) end before :each do - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) + allow(ChefConfig).to receive(:windows?).and_return(is_windows) end describe "class method: platform_specific_path" do if is_windows it "should return a windows path on windows systems" do path = "/etc/chef/cookbooks" - allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' }) + allow(ChefConfig::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' }) # match on a regex that looks for the base path with an optional # system drive at the beginning (c:) # system drive is not hardcoded b/c it can change and b/c it is not present on linux systems - expect(Chef::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks") + expect(ChefConfig::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks") end else it "should return given path on non-windows systems" do path = "/etc/chef/cookbooks" - expect(Chef::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks") + expect(ChefConfig::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks") end end end @@ -149,7 +134,7 @@ describe Chef::Config do describe "default values" do let :primary_cache_path do if is_windows - "#{Chef::Config.env['SYSTEMDRIVE']}\\chef" + "#{ChefConfig::Config.env['SYSTEMDRIVE']}\\chef" else "/var/chef" end @@ -157,88 +142,88 @@ describe Chef::Config do let :secondary_cache_path do if is_windows - "#{Chef::Config[:user_home]}\\.chef" + "#{ChefConfig::Config[:user_home]}\\.chef" else - "#{Chef::Config[:user_home]}/.chef" + "#{ChefConfig::Config[:user_home]}/.chef" end end before do if is_windows - allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' }) - Chef::Config[:user_home] = 'C:\Users\charlie' + allow(ChefConfig::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' }) + ChefConfig::Config[:user_home] = 'C:\Users\charlie' else - Chef::Config[:user_home] = '/Users/charlie' + ChefConfig::Config[:user_home] = '/Users/charlie' end - allow(Chef::Config).to receive(:path_accessible?).and_return(false) + allow(ChefConfig::Config).to receive(:path_accessible?).and_return(false) end - describe "Chef::Config[:chef_server_root]" do + describe "ChefConfig::Config[:chef_server_root]" do context "when chef_server_url isn't set manually" do it "returns the default of 'https://localhost:443'" do - expect(Chef::Config[:chef_server_root]).to eq("https://localhost:443") + expect(ChefConfig::Config[:chef_server_root]).to eq("https://localhost:443") end end context "when chef_server_url matches '../organizations/*' without a trailing slash" do before do - Chef::Config[:chef_server_url] = "https://example.com/organizations/myorg" + ChefConfig::Config[:chef_server_url] = "https://example.com/organizations/myorg" end it "returns the full URL without /organizations/*" do - expect(Chef::Config[:chef_server_root]).to eq("https://example.com") + expect(ChefConfig::Config[:chef_server_root]).to eq("https://example.com") end end context "when chef_server_url matches '../organizations/*' with a trailing slash" do before do - Chef::Config[:chef_server_url] = "https://example.com/organizations/myorg/" + ChefConfig::Config[:chef_server_url] = "https://example.com/organizations/myorg/" end it "returns the full URL without /organizations/*" do - expect(Chef::Config[:chef_server_root]).to eq("https://example.com") + expect(ChefConfig::Config[:chef_server_root]).to eq("https://example.com") end end context "when chef_server_url matches '..organizations..' but not '../organizations/*'" do before do - Chef::Config[:chef_server_url] = "https://organizations.com/organizations" + ChefConfig::Config[:chef_server_url] = "https://organizations.com/organizations" end it "returns the full URL without any modifications" do - expect(Chef::Config[:chef_server_root]).to eq(Chef::Config[:chef_server_url]) + expect(ChefConfig::Config[:chef_server_root]).to eq(ChefConfig::Config[:chef_server_url]) end end context "when chef_server_url is a standard URL without the string organization(s)" do before do - Chef::Config[:chef_server_url] = "https://example.com/some_other_string" + ChefConfig::Config[:chef_server_url] = "https://example.com/some_other_string" end it "returns the full URL without any modifications" do - expect(Chef::Config[:chef_server_root]).to eq(Chef::Config[:chef_server_url]) + expect(ChefConfig::Config[:chef_server_root]).to eq(ChefConfig::Config[:chef_server_url]) end end end - describe "Chef::Config[:cache_path]" do + describe "ChefConfig::Config[:cache_path]" do context "when /var/chef exists and is accessible" do it "defaults to /var/chef" do - allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true) - expect(Chef::Config[:cache_path]).to eq(primary_cache_path) + allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true) + expect(ChefConfig::Config[:cache_path]).to eq(primary_cache_path) end end context "when /var/chef does not exist and /var is accessible" do it "defaults to /var/chef" do allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false) - allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true) - expect(Chef::Config[:cache_path]).to eq(primary_cache_path) + allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true) + expect(ChefConfig::Config[:cache_path]).to eq(primary_cache_path) end end context "when /var/chef does not exist and /var is not accessible" do it "defaults to $HOME/.chef" do allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false) - allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false) - expect(Chef::Config[:cache_path]).to eq(secondary_cache_path) + allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false) + expect(ChefConfig::Config[:cache_path]).to eq(secondary_cache_path) end end @@ -248,68 +233,70 @@ describe Chef::Config do allow(File).to receive(:readable?).with(to_platform("/var/chef")).and_return(true) allow(File).to receive(:writable?).with(to_platform("/var/chef")).and_return(false) - expect(Chef::Config[:cache_path]).to eq(secondary_cache_path) + expect(ChefConfig::Config[:cache_path]).to eq(secondary_cache_path) end end context "when chef is running in local mode" do before do - Chef::Config.local_mode = true + ChefConfig::Config.local_mode = true end context "and config_dir is /a/b/c" do before do - Chef::Config.config_dir to_platform('/a/b/c') + ChefConfig::Config.config_dir to_platform('/a/b/c') end it "cache_path is /a/b/c/local-mode-cache" do - expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache')) + expect(ChefConfig::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache')) end end context "and config_dir is /a/b/c/" do before do - Chef::Config.config_dir to_platform('/a/b/c/') + ChefConfig::Config.config_dir to_platform('/a/b/c/') end it "cache_path is /a/b/c/local-mode-cache" do - expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache')) + expect(ChefConfig::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache')) end end end end - it "Chef::Config[:file_backup_path] defaults to /var/chef/backup" do - allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path) + it "ChefConfig::Config[:file_backup_path] defaults to /var/chef/backup" do + allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) backup_path = is_windows ? "#{primary_cache_path}\\backup" : "#{primary_cache_path}/backup" - expect(Chef::Config[:file_backup_path]).to eq(backup_path) + expect(ChefConfig::Config[:file_backup_path]).to eq(backup_path) end - it "Chef::Config[:ssl_verify_mode] defaults to :verify_peer" do - expect(Chef::Config[:ssl_verify_mode]).to eq(:verify_peer) + it "ChefConfig::Config[:ssl_verify_mode] defaults to :verify_peer" do + expect(ChefConfig::Config[:ssl_verify_mode]).to eq(:verify_peer) end - it "Chef::Config[:ssl_ca_path] defaults to nil" do - expect(Chef::Config[:ssl_ca_path]).to be_nil + it "ChefConfig::Config[:ssl_ca_path] defaults to nil" do + expect(ChefConfig::Config[:ssl_ca_path]).to be_nil end - # TODO can this be removed? + # On Windows, we'll detect an omnibus build and set this to the + # cacert.pem included in the package, but it's nil if you're on Windows + # w/o omnibus (e.g., doing development on Windows, custom build, etc.) if !is_windows - it "Chef::Config[:ssl_ca_file] defaults to nil" do - expect(Chef::Config[:ssl_ca_file]).to be_nil + it "ChefConfig::Config[:ssl_ca_file] defaults to nil" do + expect(ChefConfig::Config[:ssl_ca_file]).to be_nil end end - it "Chef::Config[:data_bag_path] defaults to /var/chef/data_bags" do - allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path) + it "ChefConfig::Config[:data_bag_path] defaults to /var/chef/data_bags" do + allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) data_bag_path = is_windows ? "#{primary_cache_path}\\data_bags" : "#{primary_cache_path}/data_bags" - expect(Chef::Config[:data_bag_path]).to eq(data_bag_path) + expect(ChefConfig::Config[:data_bag_path]).to eq(data_bag_path) end - it "Chef::Config[:environment_path] defaults to /var/chef/environments" do - allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path) + it "ChefConfig::Config[:environment_path] defaults to /var/chef/environments" do + allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) environment_path = is_windows ? "#{primary_cache_path}\\environments" : "#{primary_cache_path}/environments" - expect(Chef::Config[:environment_path]).to eq(environment_path) + expect(ChefConfig::Config[:environment_path]).to eq(environment_path) end describe "setting the config dir" do @@ -317,30 +304,30 @@ describe Chef::Config do context "when the config file is /etc/chef/client.rb" do before do - Chef::Config.config_file = to_platform("/etc/chef/client.rb") + ChefConfig::Config.config_file = to_platform("/etc/chef/client.rb") end it "config_dir is /etc/chef" do - expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef")) + expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef")) end context "and chef is running in local mode" do before do - Chef::Config.local_mode = true + ChefConfig::Config.local_mode = true end it "config_dir is /etc/chef" do - expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef")) + expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef")) end end context "when config_dir is set to /other/config/dir/" do before do - Chef::Config.config_dir = to_platform("/other/config/dir/") + ChefConfig::Config.config_dir = to_platform("/other/config/dir/") end it "yields the explicit value" do - expect(Chef::Config.config_dir).to eq(to_platform("/other/config/dir/")) + expect(ChefConfig::Config.config_dir).to eq(to_platform("/other/config/dir/")) end end @@ -348,20 +335,20 @@ describe Chef::Config do context "when the user's home dir is /home/charlie/" do before do - Chef::Config.user_home = to_platform("/home/charlie") + ChefConfig::Config.user_home = to_platform("/home/charlie") end it "config_dir is /home/charlie/.chef/" do - expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), '')) + expect(ChefConfig::Config.config_dir).to eq(ChefConfig::PathHelper.join(to_platform("/home/charlie/.chef"), '')) end context "and chef is running in local mode" do before do - Chef::Config.local_mode = true + ChefConfig::Config.local_mode = true end it "config_dir is /home/charlie/.chef/" do - expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), '')) + expect(ChefConfig::Config.config_dir).to eq(ChefConfig::PathHelper.join(to_platform("/home/charlie/.chef"), '')) end end end @@ -377,43 +364,43 @@ describe Chef::Config do let(:default_ca_file) { "c:/opscode/chef/embedded/ssl/certs/cacert.pem" } it "finds the embedded dir in the default location" do - allow(Chef::Config).to receive(:_this_file).and_return(default_config_location) - expect(Chef::Config.embedded_dir).to eq("c:/opscode/chef/embedded") + allow(ChefConfig::Config).to receive(:_this_file).and_return(default_config_location) + expect(ChefConfig::Config.embedded_dir).to eq("c:/opscode/chef/embedded") end it "finds the embedded dir in a custom install location" do - allow(Chef::Config).to receive(:_this_file).and_return(alternate_install_location) - expect(Chef::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded") + allow(ChefConfig::Config).to receive(:_this_file).and_return(alternate_install_location) + expect(ChefConfig::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded") end it "doesn't error when not in an omnibus install" do - allow(Chef::Config).to receive(:_this_file).and_return(non_omnibus_location) - expect(Chef::Config.embedded_dir).to be_nil + allow(ChefConfig::Config).to receive(:_this_file).and_return(non_omnibus_location) + expect(ChefConfig::Config.embedded_dir).to be_nil end it "sets the ssl_ca_cert path if the cert file is available" do - allow(Chef::Config).to receive(:_this_file).and_return(default_config_location) + allow(ChefConfig::Config).to receive(:_this_file).and_return(default_config_location) allow(File).to receive(:exist?).with(default_ca_file).and_return(true) - expect(Chef::Config.ssl_ca_file).to eq(default_ca_file) + expect(ChefConfig::Config.ssl_ca_file).to eq(default_ca_file) end end end end - describe "Chef::Config[:user_home]" do + describe "ChefConfig::Config[:user_home]" do it "should set when HOME is provided" do expected = to_platform("/home/kitten") - allow(Chef::Util::PathHelper).to receive(:home).and_return(expected) - expect(Chef::Config[:user_home]).to eq(expected) + allow(ChefConfig::PathHelper).to receive(:home).and_return(expected) + expect(ChefConfig::Config[:user_home]).to eq(expected) end it "falls back to the current working directory when HOME and USERPROFILE is not set" do - allow(Chef::Util::PathHelper).to receive(:home).and_return(nil) - expect(Chef::Config[:user_home]).to eq(Dir.pwd) + allow(ChefConfig::PathHelper).to receive(:home).and_return(nil) + expect(ChefConfig::Config[:user_home]).to eq(Dir.pwd) end end - describe "Chef::Config[:encrypted_data_bag_secret]" do + describe "ChefConfig::Config[:encrypted_data_bag_secret]" do let(:db_secret_default_path){ to_platform("/etc/chef/encrypted_data_bag_secret") } before do @@ -423,55 +410,57 @@ describe Chef::Config do context "/etc/chef/encrypted_data_bag_secret exists" do let(:secret_exists) { true } it "sets the value to /etc/chef/encrypted_data_bag_secret" do - expect(Chef::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path + expect(ChefConfig::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path end end context "/etc/chef/encrypted_data_bag_secret does not exist" do let(:secret_exists) { false } it "sets the value to nil" do - expect(Chef::Config[:encrypted_data_bag_secret]).to be_nil + expect(ChefConfig::Config[:encrypted_data_bag_secret]).to be_nil end end end - describe "Chef::Config[:event_handlers]" do + describe "ChefConfig::Config[:event_handlers]" do it "sets a event_handlers to an empty array by default" do - expect(Chef::Config[:event_handlers]).to eq([]) + expect(ChefConfig::Config[:event_handlers]).to eq([]) end it "should be able to add custom handlers" do o = Object.new - Chef::Config[:event_handlers] << o - expect(Chef::Config[:event_handlers]).to be_include(o) + ChefConfig::Config[:event_handlers] << o + expect(ChefConfig::Config[:event_handlers]).to be_include(o) end end - describe "Chef::Config[:user_valid_regex]" do + describe "ChefConfig::Config[:user_valid_regex]" do context "on a platform that is not Windows" do it "allows one letter usernames" do - any_match = Chef::Config[:user_valid_regex].any? { |regex| regex.match('a') } + any_match = ChefConfig::Config[:user_valid_regex].any? { |regex| regex.match('a') } expect(any_match).to be_truthy end end end - describe "Chef::Config[:internal_locale]" do + describe "ChefConfig::Config[:internal_locale]" do let(:shell_out) do - double("Chef::Mixin::ShellOut double", :exitstatus => 0, :stdout => locales) + cmd = instance_double("Mixlib::ShellOut", exitstatus: 0, stdout: locales, error!: nil) + allow(cmd).to receive(:run_command).and_return(cmd) + cmd end let(:locales) { locale_array.join("\n") } before do - allow(Chef::Config).to receive(:shell_out_with_systems_locale!).with("locale -a").and_return(shell_out) + allow(Mixlib::ShellOut).to receive(:new).with("locale -a").and_return(shell_out) end shared_examples_for "a suitable locale" do it "returns an English UTF-8 locale" do - expect(Chef::Log).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/) - expect(Chef::Log).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/) - expect(Chef::Log).to_not receive(:debug).with(/No usable locale -a command found/) - expect(Chef::Config.guess_internal_locale).to eq expected_locale + expect(ChefConfig.logger).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/) + expect(ChefConfig.logger).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/) + expect(ChefConfig.logger).to_not receive(:debug).with(/No usable locale -a command found/) + expect(ChefConfig::Config.guess_internal_locale).to eq expected_locale end end @@ -521,25 +510,29 @@ describe Chef::Config do let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] } it "should fall back to C locale" do - expect(Chef::Log).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.") - expect(Chef::Config.guess_internal_locale).to eq 'C' + expect(ChefConfig.logger).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.") + expect(ChefConfig::Config.guess_internal_locale).to eq 'C' end end context "on error" do let(:locale_array) { [] } + let(:shell_out_cmd) { instance_double("Mixlib::ShellOut") } + before do - allow(Chef::Config).to receive(:shell_out_with_systems_locale!).and_raise("THIS IS AN ERROR") + allow(Mixlib::ShellOut).to receive(:new).and_return(shell_out_cmd) + allow(shell_out_cmd).to receive(:run_command) + allow(shell_out_cmd).to receive(:error!).and_raise(Mixlib::ShellOut::ShellCommandFailed, "this is an error") end it "should default to 'en_US.UTF-8'" do if is_windows - expect(Chef::Log).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.") + expect(ChefConfig.logger).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.") else - expect(Chef::Log).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.") + expect(ChefConfig.logger).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.") end - expect(Chef::Config.guess_internal_locale).to eq "en_US.UTF-8" + expect(ChefConfig::Config.guess_internal_locale).to eq "en_US.UTF-8" end end end @@ -551,7 +544,7 @@ describe Chef::Config do context "when using our default RSpec configuration" do it "defaults to treating deprecation warnings as errors" do - expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true) + expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(true) end it "sets CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS environment variable" do @@ -564,8 +557,8 @@ describe Chef::Config do # we're just checking that the presence of the environment variable # causes treat_deprecation_warnings_as_errors to be set to true after a # config reset. - Chef::Config.reset - expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true) + ChefConfig::Config.reset + expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(true) end end @@ -574,14 +567,15 @@ describe Chef::Config do before do ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS') - Chef::Config.reset + ChefConfig::Config.reset end it "defaults to NOT treating deprecation warnings as errors" do - expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(false) + expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(false) end end end + end diff --git a/chef-config/spec/unit/path_helper_spec.rb b/chef-config/spec/unit/path_helper_spec.rb new file mode 100644 index 0000000000..3e6213597a --- /dev/null +++ b/chef-config/spec/unit/path_helper_spec.rb @@ -0,0 +1,291 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# 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/path_helper' +require 'spec_helper' + +RSpec.describe ChefConfig::PathHelper do + + let(:path_helper) { described_class } + + shared_examples_for "common_functionality" do + describe "join" do + + it "joins starting with '' resolve to absolute paths" do + expect(path_helper.join('', 'a', 'b')).to eq("#{path_helper.path_separator}a#{path_helper.path_separator}b") + end + + it "joins ending with '' add a / to the end" do + expect(path_helper.join('a', 'b', '')).to eq("a#{path_helper.path_separator}b#{path_helper.path_separator}") + end + + end + + describe "dirname" do + it "dirname('abc') is '.'" do + expect(path_helper.dirname('abc')).to eq('.') + end + it "dirname('/') is '/'" do + expect(path_helper.dirname(path_helper.path_separator)).to eq(path_helper.path_separator) + end + it "dirname('a/b/c') is 'a/b'" do + expect(path_helper.dirname(path_helper.join('a', 'b', 'c'))).to eq(path_helper.join('a', 'b')) + end + it "dirname('a/b/c/') is 'a/b'" do + expect(path_helper.dirname(path_helper.join('a', 'b', 'c', ''))).to eq(path_helper.join('a', 'b')) + end + it "dirname('/a/b/c') is '/a/b'" do + expect(path_helper.dirname(path_helper.join('', 'a', 'b', 'c'))).to eq(path_helper.join('', 'a', 'b')) + end + end + end + + context "on windows" do + + before(:each) do + allow(ChefConfig).to receive(:windows?).and_return(true) + end + + include_examples("common_functionality") + + it "path_separator is \\" do + expect(path_helper.path_separator).to eq('\\') + end + + describe "platform-specific #join behavior" do + + it "joins components on Windows when some end with unix separators" do + expect(path_helper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz') + end + + it "joins components when some end with separators" do + expected = path_helper.cleanpath("/foo/bar/baz") + expected = "C:#{expected}" + expect(path_helper.join('C:\\foo\\', "bar", "baz")).to eq(expected) + end + + it "joins components when some end and start with separators" do + expected = path_helper.cleanpath("/foo/bar/baz") + expected = "C:#{expected}" + expect(path_helper.join('C:\\foo\\', "bar/", "/baz")).to eq(expected) + end + + it "joins components that don't end in separators" do + expected = path_helper.cleanpath("/foo/bar/baz") + expected = "C:#{expected}" + expect(path_helper.join('C:\\foo', "bar", "baz")).to eq(expected) + end + + end + + + it "cleanpath changes slashes into backslashes and leaves backslashes alone" do + expect(path_helper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d') + end + + it "cleanpath does not remove leading double backslash" do + expect(path_helper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d') + end + + end + + context "on unix" do + + before(:each) do + allow(ChefConfig).to receive(:windows?).and_return(false) + end + + include_examples("common_functionality") + + it "path_separator is /" do + expect(path_helper.path_separator).to eq('/') + end + + it "cleanpath removes extra slashes alone" do + expect(path_helper.cleanpath('/a///b/c/d/')).to eq('/a/b/c/d') + end + + describe "platform-specific #join behavior" do + + it "joins components when some end with separators" do + expected = path_helper.cleanpath("/foo/bar/baz") + expect(path_helper.join("/foo/", "bar", "baz")).to eq(expected) + end + + it "joins components when some end and start with separators" do + expected = path_helper.cleanpath("/foo/bar/baz") + expect(path_helper.join("/foo/", "bar/", "/baz")).to eq(expected) + end + + it "joins components that don't end in separators" do + expected = path_helper.cleanpath("/foo/bar/baz") + expect(path_helper.join("/foo", "bar", "baz")).to eq(expected) + end + + end + + end + + describe "validate_path" do + context "on windows" do + before(:each) do + # pass by default + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(path_helper).to receive(:printable?).and_return(true) + allow(path_helper).to receive(:windows_max_length_exceeded?).and_return(false) + end + + it "returns the path if the path passes the tests" do + expect(path_helper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged") + end + + it "does not raise an error if everything looks great" do + expect { path_helper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error + end + + it "raises an error if the path has invalid characters" do + allow(path_helper).to receive(:printable?).and_return(false) + expect { path_helper.validate_path("Newline!\n") }.to raise_error(ChefConfig::InvalidPath) + end + + it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do + long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250 + prefixed_long_path = "\\\\?\\" + long_path + allow(path_helper).to receive(:windows_max_length_exceeded?).and_return(true) + expect(path_helper.validate_path(long_path)).to eql(prefixed_long_path) + end + end + end + + describe "windows_max_length_exceeded?" do + it "returns true if the path is too long (259 + NUL) for the API" do + expect(path_helper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy + end + + it "returns false if the path is not too long (259 + NUL) for the standard API" do + expect(path_helper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey + end + + it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do + expect(path_helper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey + end + end + + describe "printable?" do + it "returns true if the string contains no non-printable characters" do + expect(path_helper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy + end + + it "returns true when given 'abc' in unicode" do + expect(path_helper.printable?("\u0061\u0062\u0063")).to be_truthy + end + + it "returns true when given japanese unicode" do + expect(path_helper.printable?("\uff86\uff87\uff88")).to be_truthy + end + + it "returns false if the string contains a non-printable character" do + expect(path_helper.printable?("\my files\work\notes.txt")).to be_falsey + end + + # This isn't necessarily a requirement, but here to be explicit about functionality. + it "returns false if the string contains a newline or tab" do + expect(path_helper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey + end + end + + describe "canonical_path" do + context "on windows", :windows_only do + it "returns an absolute path with backslashes instead of slashes" do + expect(path_helper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") + end + + it "adds the \\\\?\\ prefix if it is missing" do + expect(path_helper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") + end + + it "returns a lowercase path" do + expect(path_helper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive") + end + end + + context "not on windows", :unix_only do + it "returns a canonical path" do + expect(path_helper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default") + end + end + end + + describe "paths_eql?" do + it "returns true if the paths are the same" do + allow(path_helper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit") + allow(path_helper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") + expect(path_helper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy + end + + it "returns false if the paths are different" do + allow(path_helper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit") + allow(path_helper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") + expect(path_helper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey + end + end + + describe "escape_glob" do + it "escapes characters reserved by glob" do + path = "C:\\this\\*path\\[needs]\\escaping?" + escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" + expect(path_helper.escape_glob(path)).to eq(escaped_path) + end + + context "when given more than one argument" do + it "joins, cleanpaths, and escapes characters reserved by glob" do + args = ["this/*path", "[needs]", "escaping?"] + escaped_path = if ChefConfig.windows? + "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" + else + "this/\\*path/\\[needs\\]/escaping\\?" + end + expect(path_helper).to receive(:join).with(*args).and_call_original + expect(path_helper).to receive(:cleanpath).and_call_original + expect(path_helper.escape_glob(*args)).to eq(escaped_path) + end + end + end + + describe "all_homes" do + before do + stub_const('ENV', env) + allow(ChefConfig).to receive(:windows?).and_return(is_windows) + end + + context "on windows" do + let (:is_windows) { true } + end + + context "on unix" do + let (:is_windows) { false } + + context "when HOME is not set" do + let (:env) { {} } + it "returns an empty array" do + expect(path_helper.all_homes).to eq([]) + end + end + end + end +end diff --git a/chef.gemspec b/chef.gemspec index a3b25a11d8..f4f8a31207 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -15,7 +15,8 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 2.0.0" - s.add_dependency "mixlib-config", "~> 2.0" + s.add_dependency "chef-config", "= #{Chef::VERSION}" + s.add_dependency "mixlib-cli", "~> 1.4" s.add_dependency "mixlib-log", "~> 1.3" s.add_dependency "mixlib-authentication", "~> 1.3" diff --git a/lib/chef/config.rb b/lib/chef/config.rb index d2d3c736c2..629553c8ab 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -20,739 +20,35 @@ # limitations under the License. require 'chef/log' -require 'chef/exceptions' -require 'mixlib/config' -require 'chef/util/selinux' -require 'chef/util/path_helper' -require 'pathname' -require 'chef/mixin/shell_out' +require 'chef-config/logger' -class Chef - class Config - - extend Mixlib::Config - extend Chef::Mixin::ShellOut - - PathHelper = Chef::Util::PathHelper - - # Evaluates the given string as config. - # - # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file. - def self.from_string(string, filename) - self.instance_eval(string, filename, 1) - end - - # Manages the chef secret session key - # === Returns - # <newkey>:: A new or retrieved session key - # - def self.manage_secret_key - newkey = nil - if Chef::FileCache.has_key?("chef_server_cookie_id") - newkey = Chef::FileCache.load("chef_server_cookie_id") - else - chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a - newkey = "" - 40.times { |i| newkey << chars[rand(chars.size-1)] } - Chef::FileCache.store("chef_server_cookie_id", newkey) - end - newkey - end - - def self.inspect - configuration.inspect - end - - def self.platform_specific_path(path) - path = PathHelper.cleanpath(path) - if Chef::Platform.windows? - # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb - if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef' - path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2]) - end - end - path - end - - def self.add_formatter(name, file_path=nil) - formatters << [name, file_path] - end - - def self.add_event_logger(logger) - event_handlers << logger - end - - # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.) - configurable(:config_file) - - default(:config_dir) do - if config_file - PathHelper.dirname(config_file) - else - PathHelper.join(user_home, ".chef", "") - end - end - - default :formatters, [] - - # Override the config dispatch to set the value of multiple server options simultaneously - # - # === Parameters - # url<String>:: String to be set for all of the chef-server-api URL's - # - configurable(:chef_server_url).writes_value { |url| url.to_s.strip } - - # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel. - # So while this is basically identical to what method_missing would do, we pull - # it up here and get a real method written so that things get dispatched - # properly. - configurable(:daemonize).writes_value { |v| v } - - # The root where all local chef object data is stored. cookbooks, data bags, - # environments are all assumed to be in separate directories under this. - # chef-solo uses these directories for input data. knife commands - # that upload or download files (such as knife upload, knife role from file, - # etc.) work. - default :chef_repo_path do - if self.configuration[:cookbook_path] - if self.configuration[:cookbook_path].kind_of?(String) - File.expand_path('..', self.configuration[:cookbook_path]) - else - self.configuration[:cookbook_path].map do |path| - File.expand_path('..', path) - end - end - else - cache_path - end - end - - def self.find_chef_repo_path(cwd) - # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it. - # This allows us to run config-free. - path = cwd - until File.directory?(PathHelper.join(path, "cookbooks")) - new_path = File.expand_path('..', path) - if new_path == path - Chef::Log.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.") - return Dir.pwd - end - path = new_path - end - Chef::Log.info("Auto-discovered chef repository at #{path}") - path - end - - def self.derive_path_from_chef_repo_path(child_path) - if chef_repo_path.kind_of?(String) - PathHelper.join(chef_repo_path, child_path) - else - chef_repo_path.map { |path| PathHelper.join(path, child_path)} - end - end - - # Location of acls on disk. String or array of strings. - # Defaults to <chef_repo_path>/acls. - # Only applies to Enterprise Chef commands. - default(:acl_path) { derive_path_from_chef_repo_path('acls') } - - # Location of clients on disk. String or array of strings. - # Defaults to <chef_repo_path>/acls. - default(:client_path) { derive_path_from_chef_repo_path('clients') } - - # Location of cookbooks on disk. String or array of strings. - # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path - # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]). - default(:cookbook_path) do - if self.configuration[:chef_repo_path] - derive_path_from_chef_repo_path('cookbooks') - else - Array(derive_path_from_chef_repo_path('cookbooks')).flatten + - Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten - end - end - - # Location of containers on disk. String or array of strings. - # Defaults to <chef_repo_path>/containers. - # Only applies to Enterprise Chef commands. - default(:container_path) { derive_path_from_chef_repo_path('containers') } - - # Location of data bags on disk. String or array of strings. - # Defaults to <chef_repo_path>/data_bags. - default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') } - - # Location of environments on disk. String or array of strings. - # Defaults to <chef_repo_path>/environments. - default(:environment_path) { derive_path_from_chef_repo_path('environments') } - - # Location of groups on disk. String or array of strings. - # Defaults to <chef_repo_path>/groups. - # Only applies to Enterprise Chef commands. - default(:group_path) { derive_path_from_chef_repo_path('groups') } - - # Location of nodes on disk. String or array of strings. - # Defaults to <chef_repo_path>/nodes. - default(:node_path) { derive_path_from_chef_repo_path('nodes') } - - # Location of roles on disk. String or array of strings. - # Defaults to <chef_repo_path>/roles. - default(:role_path) { derive_path_from_chef_repo_path('roles') } - - # Location of users on disk. String or array of strings. - # Defaults to <chef_repo_path>/users. - # Does not apply to Enterprise Chef commands. - default(:user_path) { derive_path_from_chef_repo_path('users') } - - # Location of policies on disk. String or array of strings. - # Defaults to <chef_repo_path>/policies. - default(:policy_path) { derive_path_from_chef_repo_path('policies') } - - # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity - default :enforce_path_sanity, true - - # Formatted Chef Client output is a beta feature, disabled by default: - default :formatter, "null" - - # The number of times the client should retry when registering with the server - default :client_registration_retries, 5 - - # An array of paths to search for knife exec scripts if they aren't in the current directory - default :script_path, [] - - # The root of all caches (checksums, cache and backup). If local mode is on, - # this is under the user's home directory. - default(:cache_path) do - if local_mode - PathHelper.join(config_dir, 'local-mode-cache') - else - primary_cache_root = platform_specific_path("/var") - primary_cache_path = platform_specific_path("/var/chef") - # Use /var/chef as the cache path only if that folder exists and we can read and write - # into it, or /var exists and we can read and write into it (we'll create /var/chef later). - # Otherwise, we'll create .chef under the user's home directory and use that as - # the cache path. - unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root) - secondary_cache_path = PathHelper.join(user_home, '.chef') - Chef::Log.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}") - secondary_cache_path - else - primary_cache_path - end - end - end - - # Returns true only if the path exists and is readable and writeable for the user. - def self.path_accessible?(path) - File.exists?(path) && File.readable?(path) && File.writable?(path) - end - - # Where cookbook files are stored on the server (by content checksum) - default(:checksum_path) { PathHelper.join(cache_path, "checksums") } - - # Where chef's cache files should be stored - default(:file_cache_path) { PathHelper.join(cache_path, "cache") } - - # Where backups of chef-managed files should go - default(:file_backup_path) { PathHelper.join(cache_path, "backup") } - - # The chef-client (or solo) lockfile. - # - # If your `file_cache_path` resides on a NFS (or non-flock()-supporting - # fs), it's recommended to set this to something like - # '/tmp/chef-client-running.pid' - default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") } - - ## Daemonization Settings ## - # What user should Chef run as? - default :user, nil - default :group, nil - default :umask, 0022 - - # Valid log_levels are: - # * :debug - # * :info - # * :warn - # * :fatal - # These work as you'd expect. There is also a special `:auto` setting. - # When set to :auto, Chef will auto adjust the log verbosity based on - # context. When a tty is available (usually because the user is running chef - # in a console), the log level is set to :warn, and output formatters are - # used as the primary mode of output. When a tty is not available, the - # logger is the primary mode of output, and the log level is set to :info - default :log_level, :auto - - # Logging location as either an IO stream or string representing log file path - default :log_location, STDOUT - - # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty - default :force_formatter, false - - # Using `force_logger` causes chef to default to logger output when STDOUT is a tty - default :force_logger, false - - default :http_retry_count, 5 - default :http_retry_delay, 5 - default :interval, nil - default :once, nil - default :json_attribs, nil - # toggle info level log items that can create a lot of output - default :verbose_logging, true - default :node_name, nil - default :diff_disabled, false - default :diff_filesize_threshold, 10000000 - default :diff_output_threshold, 1000000 - default :local_mode, false - - default :pid_file, nil - - # Whether Chef Zero local mode should bind to a port. All internal requests - # will go through the socketless code path regardless, so the socket is - # only needed if other processes will connect to the local mode server. - # - # For compatibility this is set to true but it will be changed to false in - # the future. - default :listen, true - - config_context :chef_zero do - config_strict_mode true - default(:enabled) { Chef::Config.local_mode } - default :host, 'localhost' - default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works - end - - default :chef_server_url, "https://localhost:443" - default(:chef_server_root) do - # if the chef_server_url is a path to an organization, aka - # 'some_url.../organizations/*' then remove the '/organization/*' by default - if self.configuration[:chef_server_url] =~ /\/organizations\/\S*$/ - self.configuration[:chef_server_url].split('/')[0..-3].join('/') - elsif self.configuration[:chef_server_url] # default to whatever chef_server_url is - self.configuration[:chef_server_url] - else - "https://localhost:443" - end - end - - default :rest_timeout, 300 - default :yum_timeout, 900 - default :yum_lock_timeout, 30 - default :solo, false - default :splay, nil - default :why_run, false - default :color, false - default :client_fork, true - default :ez, false - default :enable_reporting, true - default :enable_reporting_url_fatals, false - # Possible values for :audit_mode - # :enabled, :disabled, :audit_only, - # - # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature - # and is disabled by default. When users choose to enable audit-mode, - # a warning is issued in application/client#reconfigure. - # This can be removed when audit-mode is enabled by default. - default :audit_mode, :disabled - - # Chef only needs ohai to run the hostname plugin for the most basic - # functionality. If the rest of the ohai plugins are not needed (like in - # most of our testing scenarios) - default :minimal_ohai, false - - # Policyfile is an experimental feature where a node gets its run list and - # cookbook version set from a single document on the server instead of - # expanding the run list and having the server compute the cookbook version - # set based on environment constraints. - # - # Because this feature is experimental, it is not recommended for - # production use. Developent/release of this feature may not adhere to - # semver guidelines. - default :use_policyfile, false - - # Set these to enable SSL authentication / mutual-authentication - # with the server - - # Client side SSL cert/key for mutual auth - default :ssl_client_cert, nil - default :ssl_client_key, nil +# DI our logger into ChefConfig before we load the config. Some defaults are +# auto-detected, and this emits log messages on some systems, all of which will +# occur at require-time. So we need to set the logger first. +ChefConfig.logger = Chef::Log - # Whether or not to verify the SSL cert for all HTTPS requests. When set to - # :verify_peer (default), all HTTPS requests will be validated regardless of other - # SSL verification settings. When set to :verify_none no HTTPS requests will - # be validated. - default :ssl_verify_mode, :verify_peer +require 'chef-config/config' - # Whether or not to verify the SSL cert for HTTPS requests to the Chef - # server API. If set to `true`, the server's cert will be validated - # regardless of the :ssl_verify_mode setting. This is set to `true` when - # running in local-mode. - # NOTE: This is a workaround until verify_peer is enabled by default. - default(:verify_api_cert) { Chef::Config.local_mode } +require 'chef/platform/query_helpers' - # Path to the default CA bundle files. - default :ssl_ca_path, nil - default(:ssl_ca_file) do - if Chef::Platform.windows? and embedded_path = embedded_dir - cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem") - cacert_path if File.exist?(cacert_path) - else - nil - end - end - - # A directory that contains additional SSL certificates to trust. Any - # certificates in this directory will be added to whatever CA bundle ruby - # is using. Use this to add self-signed certs for your Chef Server or local - # HTTP file servers. - default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") } - - # Where should chef-solo download recipes from? - default :recipe_url, nil - - # Sets the version of the signed header authentication protocol to use (see - # the 'mixlib-authorization' project for more detail). Currently, versions - # 1.0 and 1.1 are available; however, the chef-server must first be - # upgraded to support version 1.1 before clients can begin using it. - # - # Version 1.1 of the protocol is required when using a `node_name` greater - # than ~90 bytes (~90 ascii characters), so chef-client will automatically - # switch to using version 1.1 when `node_name` is too large for the 1.0 - # protocol. If you intend to use large node names, ensure that your server - # supports version 1.1. Automatic detection of large node names means that - # users will generally not need to manually configure this. - # - # In the future, this configuration option may be replaced with an - # automatic negotiation scheme. - default :authentication_protocol_version, "1.0" - - # This key will be used to sign requests to the Chef server. This location - # must be writable by Chef during initial setup when generating a client - # identity on the server. - # - # The chef-server will look up the public key for the client using the - # `node_name` of the client. - # - # If chef-zero is enabled, this defaults to nil (no authentication). - default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") } - - # When registering the client, should we allow the client key location to - # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem - # If the path of the key goes through a directory like /tmp this should - # never be set to true or its possibly an easily exploitable security hole. - default :follow_client_key_symlink, false - - # This secret is used to decrypt encrypted data bag items. - default(:encrypted_data_bag_secret) do - if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret")) - platform_specific_path("/etc/chef/encrypted_data_bag_secret") - else - nil - end - end - - # As of Chef 11.0, version "1" is the default encrypted data bag item - # format. Version "2" is available which adds encrypt-then-mac protection. - # To maintain compatibility, versions other than 1 must be opt-in. - # - # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure. - # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO) - default :data_bag_encrypt_version, 1 - - # When reading data bag items, any supported version is accepted. However, - # if all encrypted data bags have been generated with the version 2 format, - # it is recommended to disable support for earlier formats to improve - # security. For example, the version 2 format is identical to version 1 - # except for the addition of an HMAC, so an attacker with MITM capability - # could downgrade an encrypted data bag to version 1 as part of an attack. - default :data_bag_decrypt_minimum_version, 0 - - # If there is no file in the location given by `client_key`, chef-client - # will temporarily use the "validator" identity to generate one. If the - # `client_key` is not present and the `validation_key` is also not present, - # chef-client will not be able to authenticate to the server. - # - # The `validation_key` is never used if the `client_key` exists. - # - # If chef-zero is enabled, this defaults to nil (no authentication). - default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") } - default :validation_client_name, "chef-validator" - - # When creating a new client via the validation_client account, Chef 11 - # servers allow the client to generate a key pair locally and send the - # public key to the server. This is more secure and helps offload work from - # the server, enhancing scalability. If enabled and the remote server - # implements only the Chef 10 API, client registration will not work - # properly. - # - # The default value is `true`. Set to `false` to disable client-side key - # generation (server generates client keys). - default(:local_key_generation) { true } - - # Zypper package provider gpg checks. Set to true to enable package - # gpg signature checking. This will be default in the - # future. Setting to false disables the warnings. - # Leaving this set to nil or false is a security hazard! - default :zypper_check_gpg, nil - - # Report Handlers - default :report_handlers, [] +class Chef + Config = ChefConfig::Config - # Event Handlers - default :event_handlers, [] + # We re-open ChefConfig::Config to add additional settings. Generally, + # everything should go in chef-config so it's shared with whoever uses that. + # We make execeptions to that rule when: + # * The functionality isn't likely to be useful outside of Chef + # * The functionality makes use of a dependency we don't want to add to chef-config + class Config - default :disable_event_loggers, false default :event_loggers do evt_loggers = [] - if Chef::Platform::windows? and not Chef::Platform::windows_server_2003? + if ChefConfig.windows? and not Chef::Platform.windows_server_2003? evt_loggers << :win_evt end evt_loggers end - # Exception Handlers - default :exception_handlers, [] - - # Start handlers - default :start_handlers, [] - - # Syntax Check Cache. Knife keeps track of files that is has already syntax - # checked by storing files in this directory. `syntax_check_cache_path` is - # the new (and preferred) configuration setting. If not set, knife will - # fall back to using cache_options[:path], which is deprecated but exists in - # many client configs generated by pre-Chef-11 bootstrappers. - default(:syntax_check_cache_path) { cache_options[:path] } - - # Deprecated: - # Move this to the default value of syntax_cache_path when this is removed. - default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } } - - # Whether errors should be raised for deprecation warnings. When set to - # `false` (the default setting), a warning is emitted but code using - # deprecated methods/features/etc. should work normally otherwise. When set - # to `true`, usage of deprecated methods/features will raise a - # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that - # deprecated functionality is not used internally by Chef. End users - # should generally leave this at the default setting (especially in - # production), but it may be useful when testing cookbooks or other code if - # the user wishes to aggressively address deprecations. - default(:treat_deprecation_warnings_as_errors) do - # Using an environment variable allows this setting to be inherited in - # tests that spawn new processes. - ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS") - end - - # knife configuration data - config_context :knife do - default :ssh_port, nil - default :ssh_user, nil - default :ssh_attribute, nil - default :ssh_gateway, nil - default :bootstrap_version, nil - default :bootstrap_proxy, nil - default :bootstrap_template, nil - default :secret, nil - default :secret_file, nil - default :identity_file, nil - default :host_key_verify, nil - default :forward_agent, nil - default :sort_status_reverse, nil - default :hints, {} - end - - def self.set_defaults_for_windows - # Those lists of regular expressions define what chef considers a - # valid user and group name - # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx - principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+' - default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] - default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] - - default :fatal_windows_admin_check, false - end - - def self.set_defaults_for_nix - # Those lists of regular expressions define what chef considers a - # valid user and group name - # - # user/group cannot start with '-', '+' or '~' - # user/group cannot contain ':', ',' or non-space-whitespace or null byte - # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not - # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup - default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ] - default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ] - end - - # Those lists of regular expressions define what chef considers a - # valid user and group name - if Chef::Platform.windows? - set_defaults_for_windows - else - set_defaults_for_nix - end - - # This provides a hook which rspec can stub so that we can avoid twiddling - # global state in tests. - def self.env - ENV - end - - def self.windows_home_path - Chef::Log.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.") - PathHelper.home - end - - # returns a platform specific path to the user home dir if set, otherwise default to current directory. - default( :user_home ) { PathHelper.home || Dir.pwd } - - # Enable file permission fixup for selinux. Fixup will be done - # only if selinux is enabled in the system. - default :enable_selinux_file_permission_fixup, true - - # Use atomic updates (i.e. move operation) while updating contents - # of the files resources. When set to false copy operation is - # used to update files. - default :file_atomic_update, true - - # There are 3 possible values for this configuration setting. - # true => file staging is done in the destination directory - # false => file staging is done via tempfiles under ENV['TMP'] - # :auto => file staging will try using destination directory if possible and - # will fall back to ENV['TMP'] if destination directory is not usable. - default :file_staging_uses_destdir, :auto - - # Exit if another run is in progress and the chef-client is unable to - # get the lock before time expires. If nil, no timeout is enforced. (Exits - # immediately if 0.) - default :run_lock_timeout, nil - - # Number of worker threads for syncing cookbooks in parallel. Increasing - # this number can result in gateway errors from the server (namely 503 and 504). - # If you are seeing this behavior while using the default setting, reducing - # the number of threads will help. - default :cookbook_sync_threads, 10 - - # At the beginning of the Chef Client run, the cookbook manifests are downloaded which - # contain URLs for every file in every relevant cookbook. Most of the files - # (recipes, resources, providers, libraries, etc) are immediately synchronized - # at the start of the run. The handling of "files" and "templates" directories, - # however, have two modes of operation. They can either all be downloaded immediately - # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as - # cookbook_file or template resources are converged which require them (no_lazy_load==false). - # - # The advantage of lazily loading these files is that unnecessary files are not - # synchronized. This may be useful to users with large files checked into cookbooks which - # are only selectively downloaded to a subset of clients which use the cookbook. However, - # better solutions are to either isolate large files into individual cookbooks and only - # include those cookbooks in the run lists of the servers that need them -- or move to - # using remote_file and a more appropriate backing store like S3 for large file - # distribution. - # - # The disadvantages of lazily loading files are that users some time find it - # confusing that their cookbooks are not fully synchronzied to the cache initially, - # and more importantly the time-sensitive URLs which are in the manifest may time - # out on long Chef runs before the resource that uses the file is converged - # (leading to many confusing 403 errors on template/cookbook_file resources). - # - default :no_lazy_load, true - - # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit - # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to - # true then the user will get backcompat behavior but with a single nag warning that cookbooks - # may break with this setting in the future. The false setting is the recommended setting and - # will become the default. - default :chef_gem_compile_time, nil - - # A whitelisted array of attributes you want sent over the wire when node - # data is saved. - # The default setting is nil, which collects all data. Setting to [] will not - # collect any data for save. - default :automatic_attribute_whitelist, nil - default :default_attribute_whitelist, nil - default :normal_attribute_whitelist, nil - default :override_attribute_whitelist, nil - - config_context :windows_service do - # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run - # to finish - default :watchdog_timeout, 2 * (60 * 60) # 2 hours - end - - # Chef requires an English-language UTF-8 locale to function properly. We attempt - # to use the 'locale -a' command and search through a list of preferences until we - # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be - # able to use that even if there is no English locale on the server, but Mac, Solaris, - # AIX, etc do not have that locale. We then try to find an English locale and fall - # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try - # to do the work to return a non-US UTF-8 locale then we fail inside of providers when - # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then - # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding - # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by - # default rather than drop English. - # - # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly - # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'. - def self.guess_internal_locale - # https://github.com/opscode/chef/issues/2181 - # Some systems have the `locale -a` command, but the result has - # invalid characters for the default encoding. - # - # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8", - # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding. - locales = shell_out_with_systems_locale!("locale -a").stdout.split - case - when locales.include?('C.UTF-8') - 'C.UTF-8' - when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8') - 'en_US.UTF-8' - when locales.include?('en.UTF-8') - 'en.UTF-8' - else - # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8 - guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i } - unless guesses.empty? - guessed_locale = guesses.first - # Transform into the form en_ZZ.UTF-8 - guessed_locale.gsub(/UTF-?8$/i, "UTF-8") - else - Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support." - 'C' - end - end - rescue - if Chef::Platform.windows? - Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else." - else - Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed." - end - 'en_US.UTF-8' - end - - default :internal_locale, guess_internal_locale - - # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g. - # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's - # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been - # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be - # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with - # magic tags to make ruby correctly identify the encoding being used. Changing this default will - # break Chef community cookbooks and is very highly discouraged. - default :ruby_encoding, Encoding::UTF_8 - - # If installed via an omnibus installer, this gives the path to the - # "embedded" directory which contains all of the software packaged with - # omnibus. This is used to locate the cacert.pem file on windows. - def self.embedded_dir - Pathname.new(_this_file).ascend do |path| - if path.basename.to_s == "embedded" - return path.to_s - end - end - - nil - end - - # Path to this file in the current install. - def self._this_file - File.expand_path(__FILE__) - end end end + diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index f7c85fbe23..03b1c9ca1e 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -21,11 +21,7 @@ class Chef class << self def windows? - if RUBY_PLATFORM =~ /mswin|mingw|windows/ - true - else - false - end + ChefConfig.windows? end def windows_server_2003? diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb index fda8ad2e5e..4ce2c040a4 100644 --- a/lib/chef/provider/file.rb +++ b/lib/chef/provider/file.rb @@ -28,6 +28,7 @@ require 'chef/mixin/checksum' require 'chef/mixin/file_class' require 'chef/util/backup' require 'chef/util/diff' +require 'chef/util/selinux' require 'chef/deprecation/provider/file' require 'chef/deprecation/warnings' require 'chef/file_content_management/deploy' diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb index 66c2e3f19f..10527f8906 100644 --- a/lib/chef/util/path_helper.rb +++ b/lib/chef/util/path_helper.rb @@ -16,212 +16,11 @@ # limitations under the License. # +require 'chef-config/path_helper' + class Chef class Util - class PathHelper - # Maximum characters in a standard Windows path (260 including drive letter and NUL) - WIN_MAX_PATH = 259 - - def self.dirname(path) - if Chef::Platform.windows? - # Find the first slash, not counting trailing slashes - end_slash = path.size - loop do - slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1) - if !slash - return end_slash == path.size ? '.' : path_separator - elsif slash == end_slash - 1 - end_slash = slash - else - return path[0..slash-1] - end - end - else - ::File.dirname(path) - end - end - - BACKSLASH = '\\'.freeze - - def self.path_separator - if Chef::Platform.windows? - File::ALT_SEPARATOR || BACKSLASH - else - File::SEPARATOR - end - end - - def self.join(*args) - args.flatten.inject do |joined_path, component| - # Joined path ends with / - joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '') - component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '') - joined_path += "#{path_separator}#{component}" - end - end - - def self.validate_path(path) - if Chef::Platform.windows? - unless printable?(path) - msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings." - Chef::Log.error(msg) - raise Chef::Exceptions::ValidationFailed, msg - end - - if windows_max_length_exceeded?(path) - Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'") - path.insert(0, "\\\\?\\") - end - end - - path - end - - def self.windows_max_length_exceeded?(path) - # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API - # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx - unless path =~ /^\\\\?\\/ - if path.length > WIN_MAX_PATH - return true - end - end - - false - end - - def self.printable?(string) - # returns true if string is free of non-printable characters (escape sequences) - # this returns false for whitespace escape sequences as well, e.g. \n\t - if string =~ /[^[:print:]]/ - false - else - true - end - end - - # Produces a comparable path. - def self.canonical_path(path, add_prefix=true) - # First remove extra separators and resolve any relative paths - abs_path = File.absolute_path(path) - - if Chef::Platform.windows? - # Add the \\?\ API prefix on Windows unless add_prefix is false - # Downcase on Windows where paths are still case-insensitive - abs_path.gsub!(::File::SEPARATOR, path_separator) - if add_prefix && abs_path !~ /^\\\\?\\/ - abs_path.insert(0, "\\\\?\\") - end - - abs_path.downcase! - end - - abs_path - end - - def self.cleanpath(path) - path = Pathname.new(path).cleanpath.to_s - # ensure all forward slashes are backslashes - if Chef::Platform.windows? - path = path.gsub(File::SEPARATOR, path_separator) - end - path - end - - def self.paths_eql?(path1, path2) - canonical_path(path1) == canonical_path(path2) - end - - # Paths which may contain glob-reserved characters need - # to be escaped before globbing can be done. - # http://stackoverflow.com/questions/14127343 - def self.escape_glob(*parts) - path = cleanpath(join(*parts)) - path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x } - end - - def self.relative_path_from(from, to) - pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from))) - end - - # Retrieves the "home directory" of the current user while trying to ascertain the existence - # of said directory. The path returned uses / for all separators (the ruby standard format). - # If the home directory doesn't exist or an error is otherwise encountered, nil is returned. - # - # If a set of path elements is provided, they are appended as-is to the home path if the - # homepath exists. - # - # If an optional block is provided, the joined path is passed to that block if the home path is - # valid and the result of the block is returned instead. - # - # Home-path discovery is performed once. If a path is discovered, that value is memoized so - # that subsequent calls to home_dir don't bounce around. - # - # See self.all_homes. - def self.home(*args) - @@home_dir ||= self.all_homes { |p| break p } - if @@home_dir - path = File.join(@@home_dir, *args) - block_given? ? (yield path) : path - end - end - - # See self.home. This method performs a similar operation except that it yields all the different - # possible values of 'HOME' that one could have on this platform. Hence, on windows, if - # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice. - # This method goes out and checks the existence of each location at the time of the call. - # - # The return is a list of all the returned values from each block invocation or a list of paths - # if no block is provided. - def self.all_homes(*args) - paths = [] - if Chef::Platform.windows? - # By default, Ruby uses the the following environment variables to determine Dir.home: - # HOME - # HOMEDRIVE HOMEPATH - # USERPROFILE - # Ruby only checks to see if the variable is specified - not if the directory actually exists. - # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive) - # while USERPROFILE points to the location where the user application settings and profile are stored. HOME - # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is - # HOMESHARE instead of HOMEDRIVE. - # - # We instead walk down the following and only include paths that actually exist. - # HOME - # HOMEDRIVE HOMEPATH - # HOMESHARE HOMEPATH - # USERPROFILE - - paths << ENV['HOME'] - paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] - paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH'] - paths << ENV['USERPROFILE'] - end - paths << Dir.home if ENV['HOME'] - - # Depending on what environment variables we're using, the slashes can go in any which way. - # Just change them all to / to keep things consistent. - # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on - # the particular brand of kool-aid you consume. This code assumes that \ and / are both - # path separators on any system being used. - paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path } - - # Filter out duplicate paths and paths that don't exist. - valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) } - valid_paths = valid_paths.uniq - - # Join all optional path elements at the end. - # If a block is provided, invoke it - otherwise just return what we've got. - joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) } - if block_given? - joined_paths.each { |p| yield p } - else - joined_paths - end - end - end + PathHelper = ChefConfig::PathHelper end end -# Break a require loop when require chef/util/path_helper -require 'chef/platform' -require 'chef/exceptions' diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 30ede54095..f7466084b6 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -1,6 +1,4 @@ - -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. +# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# NOTE: This file is generated by running `rake version` in the top level of +# this repo. Do not edit this manually. Edit the VERSION file and run the rake +# task instead. +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + class Chef CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__))) VERSION = '12.4.0.dev.0' diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb index 763021607b..485e98f247 100644 --- a/spec/functional/rebooter_spec.rb +++ b/spec/functional/rebooter_spec.rb @@ -70,7 +70,7 @@ describe Chef::Platform::Rebooter do shared_context 'test a reboot method' do def test_rebooter_method(method_sym, is_windows, expected_reboot_str) - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) + allow(ChefConfig).to receive(:windows?).and_return(is_windows) expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str) expect(rebooter).to receive(method_sym).once.and_call_original rebooter.send(method_sym, run_context.node) diff --git a/spec/functional/resource/aixinit_service_spec.rb b/spec/functional/resource/aixinit_service_spec.rb index 19b65ca2a0..3d9216158e 100755 --- a/spec/functional/resource/aixinit_service_spec.rb +++ b/spec/functional/resource/aixinit_service_spec.rb @@ -208,4 +208,4 @@ describe Chef::Resource::Service, :requires_root, :aix_only do end end end -end
\ No newline at end of file +end diff --git a/spec/support/mock/platform.rb b/spec/support/mock/platform.rb index ab2c19baff..7eae82fe7d 100644 --- a/spec/support/mock/platform.rb +++ b/spec/support/mock/platform.rb @@ -6,7 +6,7 @@ # testing code that mixes in platform specific modules like +Chef::Mixin::Securable+ # or +Chef::FileAccessControl+ def platform_mock(platform = :unix, &block) - allow(Chef::Platform).to receive(:windows?).and_return(platform == :windows ? true : false) + allow(ChefConfig).to receive(:windows?).and_return(platform == :windows ? true : false) ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil) if platform == :windows diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb index 018be0a015..7de9698451 100644 --- a/spec/support/shared/unit/provider/file.rb +++ b/spec/support/shared/unit/provider/file.rb @@ -255,7 +255,7 @@ shared_examples_for Chef::Provider::File do context "examining file security metadata on Unix with a file that exists" do before do # fake that we're on unix even if we're on windows - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) # mock up the filesystem to behave like unix setup_normal_file stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000) @@ -331,7 +331,7 @@ shared_examples_for Chef::Provider::File do context "examining file security metadata on Unix with a file that does not exist" do before do # fake that we're on unix even if we're on windows - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) setup_missing_file end @@ -380,7 +380,7 @@ shared_examples_for Chef::Provider::File do before do # fake that we're on unix even if we're on windows - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) # mock up the filesystem to behave like unix setup_normal_file stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000) diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index c753ca0ab8..f358aa1c77 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -60,7 +60,7 @@ describe Chef::Application::Client, "reconfigure" do context "when interval is given" do before do Chef::Config[:interval] = 600 - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "should terminate with message" do @@ -77,7 +77,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config context "when interval is given on windows" do before do Chef::Config[:interval] = 600 - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) end it "should not terminate" do diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index c1bde072f6..d8c4ede796 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -719,7 +719,7 @@ describe Chef::Client do describe "windows_admin_check" do context "platform is not windows" do before do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "shouldn't be called" do @@ -730,7 +730,7 @@ describe Chef::Client do context "platform is windows" do before do - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) end it "should be called" do diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb index 2c4ad11787..23ffc21f7f 100644 --- a/spec/unit/cookbook/cookbook_version_loader_spec.rb +++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe Chef::Cookbook::CookbookVersionLoader do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end describe "loading a cookbook" do diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb index 471fc01831..ee4e0bed02 100644 --- a/spec/unit/cookbook/syntax_check_spec.rb +++ b/spec/unit/cookbook/syntax_check_spec.rb @@ -21,7 +21,7 @@ require "chef/cookbook/syntax_check" describe Chef::Cookbook::SyntaxCheck do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') } diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index 45a985bafd..b1384bffe7 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe Chef::CookbookLoader do before do - allow(Chef::Platform).to receive(:windows?) {false} + allow(ChefConfig).to receive(:windows?) {false} end let(:repo_paths) do [ diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb index f6db1e222a..bd9a99a1de 100644 --- a/spec/unit/data_bag_spec.rb +++ b/spec/unit/data_bag_spec.rb @@ -22,7 +22,7 @@ require 'chef/data_bag' describe Chef::DataBag do before(:each) do @data_bag = Chef::DataBag.new - allow(Chef::Platform)::to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end describe "initialize" do diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index aaace60f6a..0195e6d406 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -23,7 +23,7 @@ require 'net/ssh' describe Chef::Knife::Bootstrap do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end let(:knife) do Chef::Log.logger = Logger.new(StringIO.new) diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb index 7f9308b28a..76ebf154db 100644 --- a/spec/unit/knife/core/subcommand_loader_spec.rb +++ b/spec/unit/knife/core/subcommand_loader_spec.rb @@ -24,7 +24,7 @@ describe Chef::Knife::SubcommandLoader do let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') } before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) end diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index ac42ad6dd6..4f48d4ff0d 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -420,7 +420,7 @@ EOM before(:each) do stdout = double('StringIO', :tty? => true) allow(@ui).to receive(:stdout).and_return(stdout) - allow(Chef::Platform).to receive(:windows?) { true } + allow(ChefConfig).to receive(:windows?) { true } Chef::Config.reset end diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index 3882bff349..8b6502145c 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -26,7 +26,7 @@ Chef::Knife::DataBagFromFile.load_deps describe Chef::Knife::DataBagFromFile do before :each do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } Chef::Config[:node_name] = "webmonkey.example.com" FileUtils.mkdir_p([db_folder, db_folder2]) db_file.write(Chef::JSONCompat.to_json(plain_data)) diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb index d150e5ee64..11ad23c919 100644 --- a/spec/unit/knife/environment_from_file_spec.rb +++ b/spec/unit/knife/environment_from_file_spec.rb @@ -23,7 +23,7 @@ Chef::Knife::EnvironmentFromFile.load_deps describe Chef::Knife::EnvironmentFromFile do before(:each) do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @knife = Chef::Knife::EnvironmentFromFile.new @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb index ec8e182e3d..3a924b9538 100644 --- a/spec/unit/mixin/path_sanity_spec.rb +++ b/spec/unit/mixin/path_sanity_spec.rb @@ -35,7 +35,7 @@ describe Chef::Mixin::PathSanity do @gem_bindir = '/some/gem/bin' allow(Gem).to receive(:bindir).and_return(@gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(@ruby_bindir) - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "adds all useful PATHs even if environment is an empty hash" do @@ -77,7 +77,7 @@ describe Chef::Mixin::PathSanity do gem_bindir = 'C:\gems\bin' allow(Gem).to receive(:bindir).and_return(gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(ruby_bindir) - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'} @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}") diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb index f02bd34b8f..6a867b5f9a 100644 --- a/spec/unit/mixin/template_spec.rb +++ b/spec/unit/mixin/template_spec.rb @@ -39,7 +39,7 @@ describe Chef::Mixin::Template, "render_template" do describe "when running on windows" do before do - allow(Chef::Platform).to receive(:windows?).and_return(true) + allow(ChefConfig).to receive(:windows?).and_return(true) end it "should render the templates with windows line endings" do @@ -54,7 +54,7 @@ describe Chef::Mixin::Template, "render_template" do describe "when running on unix" do before do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end it "should render the templates with unix line endings" do diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 1dbd07a021..33d4c2c3b7 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' describe "Chef::Platform#windows_server_2003?" do it "returns false early when not on windows" do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Platform).not_to receive(:require) expect(Chef::Platform.windows_server_2003?).to be_falsey end diff --git a/spec/unit/provider/deploy/revision_spec.rb b/spec/unit/provider/deploy/revision_spec.rb index 4ca64e3445..caa60878e1 100644 --- a/spec/unit/provider/deploy/revision_spec.rb +++ b/spec/unit/provider/deploy/revision_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' describe Chef::Provider::Deploy::Revision do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @temp_dir = Dir.mktmpdir Chef::Config[:file_cache_path] = @temp_dir @resource = Chef::Resource::Deploy.new("/my/deploy/dir") diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb index c95a9b3d57..a2997d8399 100644 --- a/spec/unit/provider/deploy_spec.rb +++ b/spec/unit/provider/deploy_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' describe Chef::Provider::Deploy do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @release_time = Time.utc( 2004, 8, 15, 16, 23, 42) allow(Time).to receive(:now).and_return(@release_time) @expected_release_dir = "/my/deploy/dir/releases/20040815162342" diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb index 13c57bfe56..6489f2ca50 100644 --- a/spec/unit/provider/directory_spec.rb +++ b/spec/unit/provider/directory_spec.rb @@ -48,7 +48,7 @@ describe Chef::Provider::Directory do describe "scanning file security metadata on unix" do before do - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) end let(:mock_stat) do cstats = double("stats") diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb index 51305b6225..1274203ce3 100644 --- a/spec/unit/provider/execute_spec.rb +++ b/spec/unit/provider/execute_spec.rb @@ -39,7 +39,7 @@ describe Chef::Provider::Execute do let(:new_resource) { Chef::Resource::Execute.new("foo_resource", run_context) } before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @original_log_level = Chef::Log.level Chef::Log.level = :info allow(STDOUT).to receive(:tty?).and_return(true) diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb index 5ea037d944..32d0812d8c 100644 --- a/spec/unit/provider/user/dscl_spec.rb +++ b/spec/unit/provider/user/dscl_spec.rb @@ -24,7 +24,7 @@ require 'mixlib/shellout' describe Chef::Provider::User::Dscl do before do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } end let(:node) { node = Chef::Node.new diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb index 5421b5a7b3..f120ca6da6 100644 --- a/spec/unit/role_spec.rb +++ b/spec/unit/role_spec.rb @@ -21,7 +21,7 @@ require 'chef/role' describe Chef::Role do before(:each) do - allow(Chef::Platform).to receive(:windows?) { false } + allow(ChefConfig).to receive(:windows?) { false } @role = Chef::Role.new @role.name("ops_master") end diff --git a/spec/unit/shell_spec.rb b/spec/unit/shell_spec.rb index 3a8f1a5346..379043a017 100644 --- a/spec/unit/shell_spec.rb +++ b/spec/unit/shell_spec.rb @@ -43,7 +43,7 @@ describe Shell do before do Shell.irb_conf = {} allow(Shell::ShellSession.instance).to receive(:reset!) - allow(Chef::Platform).to receive(:windows?).and_return(false) + allow(ChefConfig).to receive(:windows?).and_return(false) allow(Chef::Util::PathHelper).to receive(:home).and_return('/home/foo') end diff --git a/spec/unit/util/path_helper_spec.rb b/spec/unit/util/path_helper_spec.rb deleted file mode 100644 index 23db9587a6..0000000000 --- a/spec/unit/util/path_helper_spec.rb +++ /dev/null @@ -1,255 +0,0 @@ -# -# Author:: Bryan McLellan <btm@loftninjas.org> -# 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/util/path_helper' -require 'spec_helper' - -describe Chef::Util::PathHelper do - PathHelper = Chef::Util::PathHelper - - [ false, true ].each do |is_windows| - context "on #{is_windows ? "windows" : "unix"}" do - before(:each) do - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) - end - - describe "join" do - it "joins components when some end with separators" do - expected = PathHelper.cleanpath("/foo/bar/baz") - expected = "C:#{expected}" if is_windows - expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar", "baz")).to eq(expected) - end - - it "joins components when some end and start with separators" do - expected = PathHelper.cleanpath("/foo/bar/baz") - expected = "C:#{expected}" if is_windows - expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar/", "/baz")).to eq(expected) - end - - it "joins components that don't end in separators" do - expected = PathHelper.cleanpath("/foo/bar/baz") - expected = "C:#{expected}" if is_windows - expect(PathHelper.join(is_windows ? 'C:\\foo' : "/foo", "bar", "baz")).to eq(expected) - end - - it "joins starting with '' resolve to absolute paths" do - expect(PathHelper.join('', 'a', 'b')).to eq("#{PathHelper.path_separator}a#{PathHelper.path_separator}b") - end - - it "joins ending with '' add a / to the end" do - expect(PathHelper.join('a', 'b', '')).to eq("a#{PathHelper.path_separator}b#{PathHelper.path_separator}") - end - - if is_windows - it "joins components on Windows when some end with unix separators" do - expect(PathHelper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz') - end - end - end - - if is_windows - it "path_separator is \\" do - expect(PathHelper.path_separator).to eq('\\') - end - else - it "path_separator is /" do - expect(PathHelper.path_separator).to eq('/') - end - end - - if is_windows - it "cleanpath changes slashes into backslashes and leaves backslashes alone" do - expect(PathHelper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d') - end - it "cleanpath does not remove leading double backslash" do - expect(PathHelper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d') - end - else - it "cleanpath removes extra slashes alone" do - expect(PathHelper.cleanpath('/a///b/c/d/')).to eq('/a/b/c/d') - end - end - - describe "dirname" do - it "dirname('abc') is '.'" do - expect(PathHelper.dirname('abc')).to eq('.') - end - it "dirname('/') is '/'" do - expect(PathHelper.dirname(PathHelper.path_separator)).to eq(PathHelper.path_separator) - end - it "dirname('a/b/c') is 'a/b'" do - expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c'))).to eq(PathHelper.join('a', 'b')) - end - it "dirname('a/b/c/') is 'a/b'" do - expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c', ''))).to eq(PathHelper.join('a', 'b')) - end - it "dirname('/a/b/c') is '/a/b'" do - expect(PathHelper.dirname(PathHelper.join('', 'a', 'b', 'c'))).to eq(PathHelper.join('', 'a', 'b')) - end - end - end - end - - describe "validate_path" do - context "on windows" do - before(:each) do - # pass by default - allow(Chef::Platform).to receive(:windows?).and_return(true) - allow(PathHelper).to receive(:printable?).and_return(true) - allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(false) - end - - it "returns the path if the path passes the tests" do - expect(PathHelper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged") - end - - it "does not raise an error if everything looks great" do - expect { PathHelper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error - end - - it "raises an error if the path has invalid characters" do - allow(PathHelper).to receive(:printable?).and_return(false) - expect { PathHelper.validate_path("Newline!\n") }.to raise_error(Chef::Exceptions::ValidationFailed) - end - - it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do - long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250 - prefixed_long_path = "\\\\?\\" + long_path - allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(true) - expect(PathHelper.validate_path(long_path)).to eql(prefixed_long_path) - end - end - end - - describe "windows_max_length_exceeded?" do - it "returns true if the path is too long (259 + NUL) for the API" do - expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy - end - - it "returns false if the path is not too long (259 + NUL) for the standard API" do - expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey - end - - it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do - expect(PathHelper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey - end - end - - describe "printable?" do - it "returns true if the string contains no non-printable characters" do - expect(PathHelper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy - end - - it "returns true when given 'abc' in unicode" do - expect(PathHelper.printable?("\u0061\u0062\u0063")).to be_truthy - end - - it "returns true when given japanese unicode" do - expect(PathHelper.printable?("\uff86\uff87\uff88")).to be_truthy - end - - it "returns false if the string contains a non-printable character" do - expect(PathHelper.printable?("\my files\work\notes.txt")).to be_falsey - end - - # This isn't necessarily a requirement, but here to be explicit about functionality. - it "returns false if the string contains a newline or tab" do - expect(PathHelper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey - end - end - - describe "canonical_path" do - context "on windows", :windows_only do - it "returns an absolute path with backslashes instead of slashes" do - expect(PathHelper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") - end - - it "adds the \\\\?\\ prefix if it is missing" do - expect(PathHelper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") - end - - it "returns a lowercase path" do - expect(PathHelper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive") - end - end - - context "not on windows", :unix_only do - it "returns a canonical path" do - expect(PathHelper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default") - end - end - end - - describe "paths_eql?" do - it "returns true if the paths are the same" do - allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit") - allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") - expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy - end - - it "returns false if the paths are different" do - allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit") - allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") - expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey - end - end - - describe "escape_glob" do - it "escapes characters reserved by glob" do - path = "C:\\this\\*path\\[needs]\\escaping?" - escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" - expect(PathHelper.escape_glob(path)).to eq(escaped_path) - end - - context "when given more than one argument" do - it "joins, cleanpaths, and escapes characters reserved by glob" do - args = ["this/*path", "[needs]", "escaping?"] - escaped_path = if windows? - "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" - else - "this/\\*path/\\[needs\\]/escaping\\?" - end - expect(PathHelper).to receive(:join).with(*args).and_call_original - expect(PathHelper).to receive(:cleanpath).and_call_original - expect(PathHelper.escape_glob(*args)).to eq(escaped_path) - end - end - end - - describe "all_homes" do - before do - stub_const('ENV', env) - allow(Chef::Platform).to receive(:windows?).and_return(is_windows) - end - - context "on windows" do - let (:is_windows) { true } - end - - context "on unix" do - let (:is_windows) { false } - - context "when HOME is not set" do - let (:env) { {} } - it "returns an empty array" do - expect(PathHelper.all_homes).to eq([]) - end - end - end - end -end diff --git a/tasks/rspec.rb b/tasks/rspec.rb index d45e6bc2df..6e802d3df8 100644 --- a/tasks/rspec.rb +++ b/tasks/rspec.rb @@ -25,8 +25,21 @@ CHEF_ROOT = File.join(File.dirname(__FILE__), "..") begin require 'rspec/core/rake_task' + + desc "Run specs for Chef's Components" + task :component_specs do + Dir.chdir("chef-config") do + Bundler.with_clean_env do + sh("bundle install --local") + sh("bundle exec rake spec") + end + end + end + task :default => :spec + task :spec => :component_specs + desc "Run standard specs (minus long running specs)" RSpec::Core::RakeTask.new(:spec) do |t| # right now this just limits to functional + unit, but could also remove |