diff options
Diffstat (limited to 'lib/chef/knife')
76 files changed, 879 insertions, 1237 deletions
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index f5dc29371f..15d0f1be18 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -67,6 +67,11 @@ class Chef :description => "The ssh gateway", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key } + option :ssh_gateway_identity, + :long => "--ssh-gateway-identity SSH_GATEWAY_IDENTITY", + :description => "The SSH identity file used for gateway authentication", + :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway_identity] = key } + option :forward_agent, :short => "-A", :long => "--forward-agent", @@ -101,21 +106,19 @@ class Chef :description => "The proxy server for the node being bootstrapped", :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p } + option :bootstrap_proxy_user, + :long => "--bootstrap-proxy-user PROXY_USER", + :description => "The proxy authentication username for the node being bootstrapped" + + option :bootstrap_proxy_pass, + :long => "--bootstrap-proxy-pass PROXY_PASS", + :description => "The proxy authentication password for the node being bootstrapped" + option :bootstrap_no_proxy, :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]", :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode", :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np } - # DEPR: Remove this option in Chef 13 - option :distro, - :short => "-d DISTRO", - :long => "--distro DISTRO", - :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.", - :proc => Proc.new { |v| - Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use -t / --bootstrap-template option instead.") - v - } - option :bootstrap_template, :short => "-t TEMPLATE", :long => "--bootstrap-template TEMPLATE", @@ -136,15 +139,6 @@ class Chef :description => "Execute the bootstrap via sudo with password", :boolean => false - # DEPR: Remove this option in Chef 13 - option :template_file, - :long => "--template-file TEMPLATE", - :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.", - :proc => Proc.new { |v| - Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use -t / --bootstrap-template option instead.") - v - } - option :run_list, :short => "-r RUN_LIST", :long => "--run-list RUN_LIST", @@ -206,6 +200,11 @@ class Chef :description => "Custom command to install chef-client", :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic } + option :bootstrap_preinstall_command, + :long => "--bootstrap-preinstall-command COMMANDS", + :description => "Custom commands to run before installing chef-client", + :proc => Proc.new { |preic| Chef::Config[:knife][:bootstrap_preinstall_command] = preic } + option :bootstrap_wget_options, :long => "--bootstrap-wget-options OPTIONS", :description => "Add options to wget when installing chef-client", @@ -224,6 +223,7 @@ class Chef unless valid_values.include?(v) raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}" end + v } option :node_verify_api_cert, @@ -293,10 +293,9 @@ class Chef end def bootstrap_template - # The order here is important. We want to check if we have the new Chef 12 option is set first. - # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at - # the end. - config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template + # Allow passing a bootstrap template or use the default + # @return [String] The CLI specific bootstrap template or the default + config[:bootstrap_template] || default_bootstrap_template end def find_template @@ -304,7 +303,7 @@ class Chef # Use the template directly if it's a path to an actual file if File.exists?(template) - Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}") + Chef::Log.trace("Using the specified bootstrap template: #{File.dirname(template)}") return template end @@ -317,7 +316,7 @@ class Chef bootstrap_files.flatten! template_file = Array(bootstrap_files).find do |bootstrap_template| - Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}") + Chef::Log.trace("Looking for bootstrap template in #{File.dirname(bootstrap_template)}") File.exists?(bootstrap_template) end @@ -326,7 +325,7 @@ class Chef raise Errno::ENOENT end - Chef::Log.debug("Found bootstrap template in #{File.dirname(template_file)}") + Chef::Log.trace("Found bootstrap template in #{File.dirname(template_file)}") template_file end @@ -429,11 +428,12 @@ class Chef ssh.config[:ssh_password] = config[:ssh_password] ssh.config[:ssh_port] = config[:ssh_port] ssh.config[:ssh_gateway] = config[:ssh_gateway] + ssh.config[:ssh_gateway_identity] = config[:ssh_gateway_identity] ssh.config[:forward_agent] = config[:forward_agent] ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || config[:identity_file] ssh.config[:manual] = true ssh.config[:host_key_verify] = config[:host_key_verify] - ssh.config[:on_error] = :raise + ssh.config[:on_error] = true ssh end diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb index 6007ff9859..c80a3aa4ff 100644 --- a/lib/chef/knife/bootstrap/templates/chef-full.erb +++ b/lib/chef/knife/bootstrap/templates/chef-full.erb @@ -162,6 +162,12 @@ do_download() { return 16 } +<%# Run any custom commands before installing chef-client -%> +<%# Ex. wait for cloud-init to complete -%> +<% if knife_config[:bootstrap_preinstall_command] %> + <%= knife_config[:bootstrap_preinstall_command] %> +<% end %> + <% if knife_config[:bootstrap_install_command] %> <%= knife_config[:bootstrap_install_command] %> <% else %> @@ -182,21 +188,21 @@ fi mkdir -p /etc/chef <% if client_pem -%> -cat > /etc/chef/client.pem <<EOP +cat > /etc/chef/client.pem <<'EOP' <%= ::File.read(::File.expand_path(client_pem)) %> EOP chmod 0600 /etc/chef/client.pem <% end -%> <% if validation_key -%> -cat > /etc/chef/validation.pem <<EOP +cat > /etc/chef/validation.pem <<'EOP' <%= validation_key %> EOP chmod 0600 /etc/chef/validation.pem <% end -%> <% if encrypted_data_bag_secret -%> -cat > /etc/chef/encrypted_data_bag_secret <<EOP +cat > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP chmod 0600 /etc/chef/encrypted_data_bag_secret @@ -212,17 +218,17 @@ mkdir -p /etc/chef/trusted_certs mkdir -p /etc/chef/ohai/hints <% @chef_config[:knife][:hints].each do |name, hash| -%> -cat > /etc/chef/ohai/hints/<%= name %>.json <<EOP +cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' <%= Chef::JSONCompat.to_json(hash) %> EOP <% end -%> <% end -%> -cat > /etc/chef/client.rb <<EOP +cat > /etc/chef/client.rb <<'EOP' <%= config_content %> EOP -cat > /etc/chef/first-boot.json <<EOP +cat > /etc/chef/first-boot.json <<'EOP' <%= Chef::JSONCompat.to_json(first_boot) %> EOP diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb index e1b2365361..82b521c7d1 100644 --- a/lib/chef/knife/client_delete.rb +++ b/lib/chef/knife/client_delete.rb @@ -32,29 +32,32 @@ class Chef :long => "--delete-validators", :description => "Force deletion of client if it's a validator" - banner "knife client delete CLIENT (options)" + banner "knife client delete [CLIENT [CLIENT]] (options)" def run - @client_name = @name_args[0] - - if @client_name.nil? + if @name_args.length == 0 show_usage - ui.fatal("You must specify a client name") + ui.fatal("You must specify at least one client name") exit 1 end - delete_object(Chef::ApiClientV1, @client_name, "client") { - object = Chef::ApiClientV1.load(@client_name) + @name_args.each do |client_name| + delete_client(client_name) + end + end + + def delete_client(client_name) + delete_object(Chef::ApiClientV1, client_name, "client") do + object = Chef::ApiClientV1.load(client_name) if object.validator unless config[:delete_validators] - ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}") + ui.fatal("You must specify --delete-validators to delete the validator client #{client_name}") exit 2 end end object.destroy - } + end end - end end end diff --git a/lib/chef/knife/client_key_create.rb b/lib/chef/knife/client_key_create.rb index 68ad4d16d2..1f209ec879 100644 --- a/lib/chef/knife/client_key_create.rb +++ b/lib/chef/knife/client_key_create.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_create" require "chef/knife/key_create_base" class Chef @@ -30,6 +31,8 @@ class Chef class ClientKeyCreate < Knife include Chef::Knife::KeyCreateBase + banner "knife client key create CLIENT (options)" + attr_reader :actor def initialize(argv = []) diff --git a/lib/chef/knife/client_key_delete.rb b/lib/chef/knife/client_key_delete.rb index 64eae2e27c..3463389a05 100644 --- a/lib/chef/knife/client_key_delete.rb +++ b/lib/chef/knife/client_key_delete.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_delete" class Chef class Knife diff --git a/lib/chef/knife/client_key_edit.rb b/lib/chef/knife/client_key_edit.rb index 1dbd3c487b..1a685b7a56 100644 --- a/lib/chef/knife/client_key_edit.rb +++ b/lib/chef/knife/client_key_edit.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_edit" require "chef/knife/key_edit_base" class Chef diff --git a/lib/chef/knife/client_key_list.rb b/lib/chef/knife/client_key_list.rb index 194ad42931..aa63c1196b 100644 --- a/lib/chef/knife/client_key_list.rb +++ b/lib/chef/knife/client_key_list.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_list" require "chef/knife/key_list_base" class Chef diff --git a/lib/chef/knife/client_key_show.rb b/lib/chef/knife/client_key_show.rb index 77f9e96c5a..49bc9d3596 100644 --- a/lib/chef/knife/client_key_show.rb +++ b/lib/chef/knife/client_key_show.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_show" class Chef class Knife diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb index 5d9b2c0962..cc2b218e87 100644 --- a/lib/chef/knife/client_reregister.rb +++ b/lib/chef/knife/client_reregister.rb @@ -44,7 +44,7 @@ class Chef end client = Chef::ApiClientV1.reregister(@client_name) - Chef::Log.debug("Updated client data: #{client.inspect}") + Chef::Log.trace("Updated client data: #{client.inspect}") key = client.private_key if config[:file] File.open(config[:file], "w") do |f| diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb index e726e32684..abde923788 100644 --- a/lib/chef/knife/configure.rb +++ b/lib/chef/knife/configure.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/util/path_helper" class Chef class Knife @@ -67,26 +68,22 @@ class Chef end def run - ask_user_for_config_path - FileUtils.mkdir_p(chef_config_path) + config_file = File.join(chef_config_path, "credentials") ask_user_for_config - ::File.open(config[:config_file], "w") do |f| + config_file = File.expand_path(config_file) + if File.exist?(config_file) + confirm("Overwrite #{config_file}") + end + ::File.open(config_file, "w") do |f| f.puts <<-EOH -log_level :info -log_location STDOUT -node_name '#{new_client_name}' -client_key '#{new_client_key}' -validation_client_name '#{validation_client_name}' -validation_key '#{validation_key}' -chef_server_url '#{chef_server}' -syntax_check_cache_path '#{File.join(chef_config_path, "syntax_check_cache")}' +[default] +client_name = '#{new_client_name}' +client_key = '#{new_client_key}' +chef_server_url = '#{chef_server}' EOH - unless chef_repo.empty? - f.puts "cookbook_path [ '#{chef_repo}/cookbooks' ]" - end end if config[:initial] @@ -111,29 +108,14 @@ EOH ui.msg("Before running commands with Knife") ui.msg("") ui.msg("*****") - ui.msg("") - ui.msg("You must place your validation key in:") - ui.msg(" #{validation_key}") - ui.msg("Before generating instance data with Knife") - ui.msg("") - ui.msg("*****") end ui.msg("Configuration file written to #{config[:config_file]}") end - def ask_user_for_config_path - config[:config_file] ||= ask_question("Where should I put the config file? ", :default => "#{Chef::Config[:user_home]}/.chef/knife.rb") - # have to use expand path to expand the tilde character to the user's home - config[:config_file] = File.expand_path(config[:config_file]) - if File.exists?(config[:config_file]) - confirm("Overwrite #{config[:config_file]}") - end - end - def ask_user_for_config server_name = guess_servername - @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}:443") + @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}/organizations/myorg") if config[:initial] @new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", :default => Etc.getlogin) @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => "admin") @@ -142,10 +124,6 @@ EOH else @new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", :default => Etc.getlogin) end - @validation_client_name = config[:validation_client_name] || ask_question("Please enter the validation clientname: ", :default => "chef-validator") - @validation_key = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => "/etc/chef-server/chef-validator.pem") - @validation_key = File.expand_path(@validation_key) - @chef_repo = config[:repository] || ask_question("Please enter the path to a chef repository (or leave blank): ") @new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem") @new_client_key = File.expand_path(@new_client_key) @@ -153,18 +131,12 @@ EOH def guess_servername o = Ohai::System.new - o.load_plugins - o.require_plugin "os" - o.require_plugin "hostname" + o.all_plugins(%w{ os hostname fqdn }) o[:fqdn] || o[:machinename] || o[:hostname] || "localhost" end - def config_file - config[:config_file] - end - def chef_config_path - File.dirname(config_file) + Chef::Util::PathHelper.home(".chef") end end end diff --git a/lib/chef/knife/configure_client.rb b/lib/chef/knife/configure_client.rb index 7d0b3d260d..c015687ac7 100644 --- a/lib/chef/knife/configure_client.rb +++ b/lib/chef/knife/configure_client.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,8 +34,6 @@ class Chef FileUtils.mkdir_p(@config_dir) ui.info("Writing client.rb") File.open(File.join(@config_dir, "client.rb"), "w") do |file| - file.puts("log_level :info") - file.puts("log_location STDOUT") file.puts("chef_server_url '#{Chef::Config[:chef_server_url]}'") file.puts("validation_client_name '#{Chef::Config[:validation_client_name]}'") end diff --git a/lib/chef/knife/cookbook_bulk_delete.rb b/lib/chef/knife/cookbook_bulk_delete.rb index bd1c8a22cc..cdd1584e36 100644 --- a/lib/chef/knife/cookbook_bulk_delete.rb +++ b/lib/chef/knife/cookbook_bulk_delete.rb @@ -42,7 +42,7 @@ class Chef all_cookbooks = Chef::CookbookVersion.list cookbooks_names = all_cookbooks.keys.grep(regex) - cookbooks_to_delete = cookbooks_names.inject({}) { |hash, name| hash[name] = all_cookbooks[name];hash } + cookbooks_to_delete = cookbooks_names.inject({}) { |hash, name| hash[name] = all_cookbooks[name]; hash } ui.msg "All versions of the following cookbooks will be deleted:" ui.msg "" ui.msg ui.list(cookbooks_to_delete.keys.sort, :columns_down) diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index 950de380f8..6122fd52d3 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -21,438 +21,9 @@ require "chef/knife" class Chef class Knife class CookbookCreate < Knife - - deps do - require "chef/json_compat" - require "uri" - require "fileutils" - end - - banner "knife cookbook create COOKBOOK (options)" - - option :cookbook_path, - :short => "-o PATH", - :long => "--cookbook-path PATH", - :description => "The directory where the cookbook will be created" - - option :readme_format, - :short => "-r FORMAT", - :long => "--readme-format FORMAT", - :description => "Format of the README file, supported formats are 'md' (markdown) and 'rdoc' (rdoc)" - - option :cookbook_license, - :short => "-I LICENSE", - :long => "--license LICENSE", - :description => "License for cookbook, apachev2, gplv2, gplv3, mit or none" - - option :cookbook_copyright, - :short => "-C COPYRIGHT", - :long => "--copyright COPYRIGHT", - :description => "Name of copyright holder" - - option :cookbook_email, - :short => "-m EMAIL", - :long => "--email EMAIL", - :description => "Email address of cookbook maintainer" - def run - self.config = Chef::Config.merge!(config) - if @name_args.length < 1 - show_usage - ui.fatal("You must specify a cookbook name") - exit 1 - end - - if default_cookbook_path_empty? && parameter_empty?(config[:cookbook_path]) - raise ArgumentError, "Default cookbook_path is not specified in the knife.rb config file, and a value to -o is not provided. Nowhere to write the new cookbook to." - end - - cookbook_path = File.expand_path(Array(config[:cookbook_path]).first) - cookbook_name = @name_args.first - copyright = config[:cookbook_copyright] || "YOUR_COMPANY_NAME" - email = config[:cookbook_email] || "YOUR_EMAIL" - license = ((config[:cookbook_license] != "false") && config[:cookbook_license]) || "none" - readme_format = ((config[:readme_format] != "false") && config[:readme_format]) || "md" - create_cookbook(cookbook_path, cookbook_name, copyright, license) - create_readme(cookbook_path, cookbook_name, readme_format) - create_changelog(cookbook_path, cookbook_name) - create_metadata(cookbook_path, cookbook_name, copyright, email, license, readme_format) - end - - def create_cookbook(dir, cookbook_name, copyright, license) - msg("** Creating cookbook #{cookbook_name} in #{dir}") - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "attributes")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "recipes")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "definitions")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "libraries")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "resources")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "providers")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "files", "default")}" - FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "templates", "default")}" - unless File.exists?(File.join(dir, cookbook_name, "recipes", "default.rb")) - open(File.join(dir, cookbook_name, "recipes", "default.rb"), "w") do |file| - file.puts <<-EOH -# -# Cookbook Name:: #{cookbook_name} -# Recipe:: default -# -# Copyright #{Time.now.year}, #{copyright} -# -EOH - case license - when "apachev2" - file.puts <<-EOH -# 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. -# -EOH - when "gplv2" - file.puts <<-EOH -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -EOH - when "gplv3" - file.puts <<-EOH -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -EOH - when "mit" - file.puts <<-EOH -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -EOH - when "none" - file.puts <<-EOH -# All rights reserved - Do Not Redistribute -# -EOH - end - end - end - end - - def create_changelog(dir, cookbook_name) - msg("** Creating CHANGELOG for cookbook: #{cookbook_name}") - unless File.exists?(File.join(dir, cookbook_name, "CHANGELOG.md")) - open(File.join(dir, cookbook_name, "CHANGELOG.md"), "w") do |file| - file.puts <<-EOH -# #{cookbook_name} CHANGELOG - -This file is used to list changes made in each version of the #{cookbook_name} cookbook. - -## 0.1.0 -- [your_name] - Initial release of #{cookbook_name} - -- - - -Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. - -The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. -EOH - end - end + Chef::Log.fatal("knife cookbook create has been removed. Please use `chef generate cookbook` from the ChefDK") end - - def create_readme(dir, cookbook_name, readme_format) - msg("** Creating README for cookbook: #{cookbook_name}") - unless File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}")) - open(File.join(dir, cookbook_name, "README.#{readme_format}"), "w") do |file| - case readme_format - when "rdoc" - file.puts <<-EOH -= #{cookbook_name} Cookbook -TODO: Enter the cookbook description here. - -e.g. -This cookbook makes your favorite breakfast sandwich. - -== Requirements -TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. - -e.g. -==== packages -- +toaster+ - #{cookbook_name} needs toaster to brown your bagel. - -== Attributes -TODO: List your cookbook attributes here. - -e.g. -==== #{cookbook_name}::default -<table> - <tr> - <th>Key</th> - <th>Type</th> - <th>Description</th> - <th>Default</th> - </tr> - <tr> - <td><tt>['#{cookbook_name}']['bacon']</tt></td> - <td>Boolean</td> - <td>whether to include bacon</td> - <td><tt>true</tt></td> - </tr> -</table> - -== Usage -==== #{cookbook_name}::default -TODO: Write usage instructions for each cookbook. - -e.g. -Just include +#{cookbook_name}+ in your node's +run_list+: - - { - "name":"my_node", - "run_list": [ - "recipe[#{cookbook_name}]" - ] - } - -== Contributing -TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. - -e.g. -1. Fork the repository on Github -2. Create a named feature branch (like `add_component_x`) -3. Write your change -4. Write tests for your change (if applicable) -5. Run the tests, ensuring they all pass -6. Submit a Pull Request using Github - -== License and Authors -Authors: TODO: List authors -EOH - when "md", "mkd", "txt" - file.puts <<-EOH -# #{cookbook_name} Cookbook - -TODO: Enter the cookbook description here. - -e.g. -This cookbook makes your favorite breakfast sandwich. - -## Requirements - -TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. - -e.g. -### Platforms - -- SandwichOS - -### Chef - -- Chef 12.0 or later - -### Cookbooks - -- `toaster` - #{cookbook_name} needs toaster to brown your bagel. - -## Attributes - -TODO: List your cookbook attributes here. - -e.g. -### #{cookbook_name}::default - -<table> - <tr> - <th>Key</th> - <th>Type</th> - <th>Description</th> - <th>Default</th> - </tr> - <tr> - <td><tt>['#{cookbook_name}']['bacon']</tt></td> - <td>Boolean</td> - <td>whether to include bacon</td> - <td><tt>true</tt></td> - </tr> -</table> - -## Usage - -### #{cookbook_name}::default - -TODO: Write usage instructions for each cookbook. - -e.g. -Just include `#{cookbook_name}` in your node's `run_list`: - -```json -{ - "name":"my_node", - "run_list": [ - "recipe[#{cookbook_name}]" - ] -} -``` - -## Contributing - -TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. - -e.g. -1. Fork the repository on Github -2. Create a named feature branch (like `add_component_x`) -3. Write your change -4. Write tests for your change (if applicable) -5. Run the tests, ensuring they all pass -6. Submit a Pull Request using Github - -## License and Authors - -Authors: TODO: List authors - -EOH - else - file.puts <<-EOH -#{cookbook_name} Cookbook -#{'=' * "#{cookbook_name} Cookbook".length} - TODO: Enter the cookbook description here. - - e.g. - This cookbook makes your favorite breakfast sandwich. - -Requirements - TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. - - e.g. - toaster #{cookbook_name} needs toaster to brown your bagel. - -Attributes - TODO: List your cookbook attributes here. - - #{cookbook_name} - Key Type Description Default - ['#{cookbook_name}']['bacon'] Boolean whether to include bacon true - -Usage - #{cookbook_name} - TODO: Write usage instructions for each cookbook. - - e.g. - Just include `#{cookbook_name}` in your node's `run_list`: - - [code] - { - "name":"my_node", - "run_list": [ - "recipe[#{cookbook_name}]" - ] - } - [/code] - -Contributing - TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. - - e.g. - 1. Fork the repository on Github - 2. Create a named feature branch (like `add_component_x`) - 3. Write your change - 4. Write tests for your change (if applicable) - 5. Run the tests, ensuring they all pass - 6. Submit a Pull Request using Github - -License and Authors - Authors: TODO: List authors -EOH - end - end - end - end - - def create_metadata(dir, cookbook_name, copyright, email, license, readme_format) - msg("** Creating metadata for cookbook: #{cookbook_name}") - - license_name = case license - when "apachev2" - "Apache 2.0" - when "gplv2" - "GNU Public License 2.0" - when "gplv3" - "GNU Public License 3.0" - when "mit" - "MIT" - when "none" - "All rights reserved" - end - - unless File.exist?(File.join(dir, cookbook_name, "metadata.rb")) - open(File.join(dir, cookbook_name, "metadata.rb"), "w") do |file| - if File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}")) - long_description = "long_description IO.read(File.join(File.dirname(__FILE__), 'README.#{readme_format}'))" - end - file.puts <<-EOH -name '#{cookbook_name}' -maintainer '#{copyright}' -maintainer_email '#{email}' -license '#{license_name}' -description 'Installs/Configures #{cookbook_name}' -#{long_description} -version '0.1.0' -EOH - end - end - end - - private - - def default_cookbook_path_empty? - Chef::Config[:cookbook_path].nil? || Chef::Config[:cookbook_path].empty? - end - - def parameter_empty?(parameter) - parameter.nil? || parameter.empty? - end - end end end diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb index 741f444093..77e7aa0d09 100644 --- a/lib/chef/knife/cookbook_download.rb +++ b/lib/chef/knife/cookbook_download.rb @@ -70,12 +70,12 @@ class Chef ui.info("Downloading #{@cookbook_name} cookbook version #{@version}") cookbook = Chef::CookbookVersion.load(@cookbook_name, @version) - manifest = cookbook.manifest + manifest = cookbook.cookbook_manifest basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}") if File.exists?(basedir) if config[:force] - Chef::Log.debug("Deleting #{basedir}") + Chef::Log.trace("Deleting #{basedir}") FileUtils.rm_rf(basedir) else ui.fatal("Directory #{basedir} exists, use --force to overwrite") @@ -83,12 +83,11 @@ class Chef end end - Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) + manifest.by_parent_directory.each do |segment, files| ui.info("Downloading #{segment}") - manifest[segment].each do |segment_file| + files.each do |segment_file| dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR)) - Chef::Log.debug("Downloading #{segment_file['path']} to #{dest}") + Chef::Log.trace("Downloading #{segment_file['path']} to #{dest}") FileUtils.mkdir_p(File.dirname(dest)) tempfile = rest.streaming_request(segment_file["url"]) FileUtils.mv(tempfile.path, dest) diff --git a/lib/chef/knife/cookbook_metadata.rb b/lib/chef/knife/cookbook_metadata.rb index 29eba6a36a..6f8f6db996 100644 --- a/lib/chef/knife/cookbook_metadata.rb +++ b/lib/chef/knife/cookbook_metadata.rb @@ -1,7 +1,7 @@ # # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +47,7 @@ class Chef if config[:all] cl = Chef::CookbookLoader.new(config[:cookbook_path]) cl.load_cookbooks - cl.each do |cname, cookbook| + cl.each_key do |cname| generate_metadata(cname.to_s) end else @@ -80,7 +80,7 @@ class Chef File.open(json_file, "w") do |f| f.write(Chef::JSONCompat.to_json_pretty(md)) end - Chef::Log.debug("Generated #{json_file}") + Chef::Log.trace("Generated #{json_file}") rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax." ui.stderr.puts "in #{file}:" diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb index 5fab7c303f..1d9983632d 100644 --- a/lib/chef/knife/cookbook_show.rb +++ b/lib/chef/knife/cookbook_show.rb @@ -51,6 +51,10 @@ class Chef :description => "Show corresponding URIs" def run + cookbook_name, cookbook_version, segment, filename = @name_args + + cookbook = Chef::CookbookVersion.load(cookbook_name, cookbook_version) unless cookbook_version.nil? + case @name_args.length when 4 # We are showing a specific file node = Hash.new @@ -59,15 +63,11 @@ class Chef node[:platform_version] = config[:platform_version] if config.has_key?(:platform_version) class << node - def attribute?(name) + def attribute?(name) # rubocop:disable Lint/NestedMethodDefinition has_key?(name) end end - cookbook_name, segment, filename = @name_args[0], @name_args[2], @name_args[3] - cookbook_version = @name_args[1] == "latest" ? "_latest" : @name_args[1] - - cookbook = rest.get("cookbooks/#{cookbook_name}/#{cookbook_version}") manifest_entry = cookbook.preferred_manifest_record(node, segment, filename) temp_file = rest.streaming_request(manifest_entry[:url]) @@ -76,14 +76,14 @@ class Chef pretty_print(temp_file.read) when 3 # We are showing a specific part of the cookbook - cookbook_version = @name_args[1] == "latest" ? "_latest" : @name_args[1] - result = rest.get("cookbooks/#{@name_args[0]}/#{cookbook_version}") - output(result.manifest[@name_args[2]]) - when 2 # We are showing the whole cookbook data - cookbook_version = @name_args[1] == "latest" ? "_latest" : @name_args[1] - output(rest.get("cookbooks/#{@name_args[0]}/#{cookbook_version}")) + if segment == "metadata" + output(cookbook.metadata) + else + output(cookbook.files_for(segment)) + end + when 2 # We are showing the whole cookbook + output(cookbook.display) when 1 # We are showing the cookbook versions (all of them) - cookbook_name = @name_args[0] env = config[:environment] api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}" output(format_cookbook_list_for_display(rest.get(api_endpoint))) diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb index 7d0e21791d..07e0037bd6 100644 --- a/lib/chef/knife/cookbook_site_download.rb +++ b/lib/chef/knife/cookbook_site_download.rb @@ -37,10 +37,22 @@ class Chef :long => "--force", :description => "Force download deprecated version" + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + def run if current_cookbook_deprecated? message = "DEPRECATION: This cookbook has been deprecated. " - message << "It has been replaced by #{replacement_cookbook}." + replacement = replacement_cookbook + if !replacement.to_s.strip.empty? + message << "It has been replaced by #{replacement}." + else + message << "No replacement has been defined." + end ui.warn message unless config[:force] @@ -59,7 +71,7 @@ class Chef private def cookbooks_api_url - "https://supermarket.chef.io/api/v1/cookbooks" + "#{config[:supermarket_site]}/api/v1/cookbooks" end def current_cookbook_data @@ -98,7 +110,7 @@ class Chef end def replacement_cookbook - File.basename(current_cookbook_data["replacement"]) + File.basename(current_cookbook_data["replacement"] || "") end def specific_cookbook_version_url diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb index 802fdd792b..f4d692e13c 100644 --- a/lib/chef/knife/cookbook_site_install.rb +++ b/lib/chef/knife/cookbook_site_install.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2010-2016, Chef Software Inc. +# Copyright:: Copyright 2010-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ require "chef/knife" require "chef/exceptions" require "shellwords" +require "mixlib/archive" class Chef class Knife @@ -59,6 +60,13 @@ class Chef :boolean => true, :default => false + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + attr_reader :cookbook_name attr_reader :vendor_path @@ -75,7 +83,7 @@ class Chef # Check to ensure we have a valid source of cookbooks before continuing # @install_path = File.expand_path(Array(config[:cookbook_path]).first) - ui.info "Installing #@cookbook_name to #{@install_path}" + ui.info "Installing #{@cookbook_name} to #{@install_path}" @repo = CookbookSCMRepo.new(@install_path, ui, config) #cookbook_path = File.join(vendor_path, name_args[0]) @@ -108,7 +116,7 @@ class Chef end unless config[:no_deps] - preferred_metadata.dependencies.each do |cookbook, version_list| + preferred_metadata.dependencies.each_key do |cookbook| # Doesn't do versions.. yet nv = self.class.new nv.config = config @@ -134,6 +142,7 @@ class Chef def download_cookbook_to(download_path) downloader = Chef::Knife::CookbookSiteDownload.new downloader.config[:file] = download_path + downloader.config[:supermarket_site] = config[:supermarket_site] downloader.name_args = name_args downloader.run downloader @@ -141,12 +150,7 @@ class Chef def extract_cookbook(upstream_file, version) ui.info("Uncompressing #{@cookbook_name} version #{version}.") - # FIXME: Detect if we have the bad tar from git on Windows: https://github.com/opscode/chef/issues/1753 - extract_command = "tar zxvf \"#{convert_path upstream_file}\"" - if Chef::Platform.windows? - extract_command << " --force-local" - end - shell_out!(extract_command, :cwd => @install_path) + Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false) end def clear_existing_files(cookbook_path) @@ -157,9 +161,9 @@ class Chef def convert_path(upstream_file) # converts a Windows path (C:\foo) to a mingw path (/c/foo) if ENV["MSYSTEM"] == "MINGW32" - return upstream_file.sub(/^([[:alpha:]]):/, '/\1') + upstream_file.sub(/^([[:alpha:]]):/, '/\1') else - return Shellwords.escape upstream_file + Shellwords.escape upstream_file end end diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb index abe48bf340..3bdef8abe5 100644 --- a/lib/chef/knife/cookbook_site_list.rb +++ b/lib/chef/knife/cookbook_site_list.rb @@ -30,6 +30,13 @@ class Chef :long => "--with-uri", :description => "Show corresponding URIs" + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + def run if config[:with_uri] cookbooks = Hash.new @@ -41,7 +48,7 @@ class Chef end def get_cookbook_list(items = 10, start = 0, cookbook_collection = {}) - cookbooks_url = "https://supermarket.chef.io/api/v1/cookbooks?items=#{items}&start=#{start}" + cookbooks_url = "#{config[:supermarket_site]}/api/v1/cookbooks?items=#{items}&start=#{start}" cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb index ba4b873efc..d401844217 100644 --- a/lib/chef/knife/cookbook_site_search.rb +++ b/lib/chef/knife/cookbook_site_search.rb @@ -24,12 +24,19 @@ class Chef banner "knife cookbook site search QUERY (options)" category "cookbook site" + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + def run output(search_cookbook(name_args[0])) end def search_cookbook(query, items = 10, start = 0, cookbook_collection = {}) - cookbooks_url = "https://supermarket.chef.io/api/v1/search?q=#{query}&items=#{items}&start=#{start}" + cookbooks_url = "#{config[:supermarket_site]}/api/v1/search?q=#{query}&items=#{items}&start=#{start}" cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 6f37568f5f..e4d55276da 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -50,6 +50,13 @@ class Chef :default => false, :description => "Don't take action, only print what files will be uploaded to Supermarket." + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + def run config[:cookbook_path] ||= Chef::Config[:cookbook_path] @@ -71,12 +78,12 @@ class Chef Chef::CookbookUploader.new(cookbook).validate_cookbooks tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook) begin - Chef::Log.debug("Temp cookbook directory is #{tmp_cookbook_dir.inspect}") + Chef::Log.trace("Temp cookbook directory is #{tmp_cookbook_dir.inspect}") ui.info("Making tarball #{cookbook_name}.tgz") shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", :cwd => tmp_cookbook_dir) rescue => e ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.") - Chef::Log.debug("\n#{e.backtrace.join("\n")}") + Chef::Log.trace("\n#{e.backtrace.join("\n")}") exit(1) end @@ -91,11 +98,11 @@ class Chef begin do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key]) ui.info("Upload complete") - Chef::Log.debug("Removing local staging directory at #{tmp_cookbook_dir}") + Chef::Log.trace("Removing local staging directory at #{tmp_cookbook_dir}") FileUtils.rm_rf tmp_cookbook_dir rescue => e ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") - Chef::Log.debug("\n#{e.backtrace.join("\n")}") + Chef::Log.trace("\n#{e.backtrace.join("\n")}") exit(1) end @@ -106,23 +113,17 @@ class Chef end def get_category(cookbook_name) - begin - data = noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") - if !data["category"] && data["error_code"] - ui.fatal("Received an error from Supermarket: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") - exit(1) - else - data["category"] - end - rescue => e - ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") - Chef::Log.debug("\n#{e.backtrace.join("\n")}") - exit(1) - end + data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}") + data["category"] + rescue => e + return "Other" if e.kind_of?(Net::HTTPServerException) && e.response.code == "404" + ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") + Chef::Log.trace("\n#{e.backtrace.join("\n")}") + exit(1) end def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename) - uri = "https://supermarket.chef.io/api/v1/cookbooks" + uri = "#{config[:supermarket_site]}/api/v1/cookbooks" category_string = Chef::JSONCompat.to_json({ "category" => cookbook_category }) diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb index c0280cb318..ce153ca5a1 100644 --- a/lib/chef/knife/cookbook_site_show.rb +++ b/lib/chef/knife/cookbook_site_show.rb @@ -24,21 +24,32 @@ class Chef banner "knife cookbook site show COOKBOOK [VERSION] (options)" category "cookbook site" + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + def run output(format_for_display(get_cookbook_data)) end + def supermarket_uri + "#{config[:supermarket_site]}/api/v1" + end + def get_cookbook_data case @name_args.length when 1 - noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") + noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}") when 2 - noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].tr('.', '_')}") + noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}/versions/#{name_args[1].tr('.', '_')}") end end def get_cookbook_list(items = 10, start = 0, cookbook_collection = {}) - cookbooks_url = "https://supermarket.chef.io/api/v1/cookbooks?items=#{items}&start=#{start}" + cookbooks_url = "#{supermarket_uri}/cookbooks?items=#{items}&start=#{start}" cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb index 310f6ac41d..bdabff0b94 100644 --- a/lib/chef/knife/cookbook_site_unshare.rb +++ b/lib/chef/knife/cookbook_site_unshare.rb @@ -30,6 +30,13 @@ class Chef banner "knife cookbook site unshare COOKBOOK" category "cookbook site" + option :supermarket_site, + :short => "-m SUPERMARKET_SITE", + :long => "--supermarket-site SUPERMARKET_SITE", + :description => "Supermarket Site", + :default => "https://supermarket.chef.io", + :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } + def run @cookbook_name = @name_args[0] if @cookbook_name.nil? @@ -41,7 +48,7 @@ class Chef confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}" begin - rest.delete "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}" + rest.delete "#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}" rescue Net::HTTPServerException => e raise e unless e.message =~ /Forbidden/ ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it." diff --git a/lib/chef/knife/cookbook_test.rb b/lib/chef/knife/cookbook_test.rb index 1a5c9df74e..f3a4981c95 100644 --- a/lib/chef/knife/cookbook_test.rb +++ b/lib/chef/knife/cookbook_test.rb @@ -2,7 +2,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: Matthew Kent (<mkent@magoazul.com>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2018, Chef Software Inc. # Copyright:: Copyright 2010-2016, Matthew Kent # License:: Apache License, Version 2.0 # @@ -43,14 +43,14 @@ class Chef :description => "Test all cookbooks, rather than just a single cookbook" def run - ui.warn("DEPRECATED: Please use ChefSpec or Rubocop to syntax-check cookbooks.") + ui.warn("DEPRECATED: Please use ChefSpec or Cookstyle to syntax-check cookbooks.") config[:cookbook_path] ||= Chef::Config[:cookbook_path] checked_a_cookbook = false if config[:all] cl = cookbook_loader cl.load_cookbooks - cl.each do |key, cookbook| + cl.each_key do |key| checked_a_cookbook = true test_cookbook(key) end diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb index 6938ac280d..f67a26dc9a 100644 --- a/lib/chef/knife/cookbook_upload.rb +++ b/lib/chef/knife/cookbook_upload.rb @@ -2,7 +2,7 @@ # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) # Author:: Nuo Yan (<yan.nuo@gmail.com>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -146,12 +146,12 @@ class Chef end if upload_failures == 0 - ui.info "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""}." + ui.info "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"}." elsif upload_failures > 0 && upload_ok > 0 - ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""} ok but #{upload_failures} " + - "cookbook#{upload_failures > 1 ? "s" : ""} upload failed." + ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"} ok but #{upload_failures} " + + "cookbook#{upload_failures == 1 ? "" : "s"} upload failed." elsif upload_failures > 0 && upload_ok == 0 - ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures > 1 ? "s" : ""}." + ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures == 1 ? "" : "s"}." exit 1 end end @@ -172,7 +172,7 @@ class Chef if ! upload_set.has_key?(cookbook_name) upload_set[cookbook_name] = cookbook_repo[cookbook_name] if config[:depends] - upload_set[cookbook_name].metadata.dependencies.each { |dep, ver| @name_args << dep } + upload_set[cookbook_name].metadata.dependencies.each_key { |dep| @name_args << dep } end end rescue Exceptions::CookbookNotFoundInRepo => e diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 48d2cb9e77..deb7f8a3b4 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -54,7 +54,7 @@ class Chef end def client_d - @cliend_d ||= client_d_content + @client_d ||= client_d_content end def encrypted_data_bag_secret @@ -67,12 +67,36 @@ class Chef @trusted_certs ||= trusted_certs_content end + def get_log_location + if !(@chef_config[:config_log_location].class == IO ) && (@chef_config[:config_log_location].nil? || @chef_config[:config_log_location].to_s.empty?) + "STDOUT" + elsif @chef_config[:config_log_location].equal?(:win_evt) + raise "The value :win_evt is not supported for config_log_location on Linux Platforms \n" + elsif @chef_config[:config_log_location].equal?(:syslog) + ":syslog" + elsif @chef_config[:config_log_location].equal?(STDOUT) + "STDOUT" + elsif @chef_config[:config_log_location].equal?(STDERR) + "STDERR" + elsif @chef_config[:config_log_location] + %Q{"#{@chef_config[:config_log_location]}"} + else + "STDOUT" + end + end + def config_content client_rb = <<-CONFIG -log_location STDOUT chef_server_url "#{@chef_config[:chef_server_url]}" validation_client_name "#{@chef_config[:validation_client_name]}" CONFIG + + if !(@chef_config[:config_log_level].nil? || @chef_config[:config_log_level].empty?) + client_rb << %Q{log_level :#{@chef_config[:config_log_level]}\n} + end + + client_rb << "log_location #{get_log_location}\n" + if @config[:chef_node_name] client_rb << %Q{node_name "#{@config[:chef_node_name]}"\n} else @@ -114,6 +138,16 @@ validation_client_name "#{@chef_config[:validation_client_name]}" client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n} end + if knife_config[:bootstrap_proxy_user] + client_rb << %Q{http_proxy_user "#{knife_config[:bootstrap_proxy_user]}"\n} + client_rb << %Q{https_proxy_user "#{knife_config[:bootstrap_proxy_user]}"\n} + end + + if knife_config[:bootstrap_proxy_pass] + client_rb << %Q{http_proxy_pass "#{knife_config[:bootstrap_proxy_pass]}"\n} + client_rb << %Q{https_proxy_pass "#{knife_config[:bootstrap_proxy_pass]}"\n} + end + if knife_config[:bootstrap_no_proxy] client_rb << %Q{no_proxy "#{knife_config[:bootstrap_no_proxy]}"\n} end @@ -127,13 +161,14 @@ validation_client_name "#{@chef_config[:validation_client_name]}" end if Chef::Config[:fips] - client_rb << <<-CONFIG -fips true -chef_version = ::Chef::VERSION.split(".") -unless chef_version[0].to_i > 12 || (chef_version[0].to_i == 12 && chef_version[1].to_i >= 8) - raise "FIPS Mode requested but not supported by this client" -end -CONFIG + client_rb << <<-CONFIG.gsub(/^ {14}/, "") + fips true + require "chef/version" + chef_version = ::Chef::VERSION.split(".") + unless chef_version[0].to_i > 12 || (chef_version[0].to_i == 12 && chef_version[1].to_i >= 8) + raise "FIPS Mode requested but not supported by this client" + end + CONFIG end client_rb @@ -143,7 +178,11 @@ CONFIG # If the user doesn't have a client path configure, let bash use the PATH for what it was designed for client_path = @chef_config[:chef_client_path] || "chef-client" s = "#{client_path} -j /etc/chef/first-boot.json" - s << " -l debug" if @config[:verbosity] && @config[:verbosity] >= 2 + if @config[:verbosity] && @config[:verbosity] >= 3 + s << " -l trace" + elsif @config[:verbosity] && @config[:verbosity] >= 2 + s << " -l debug" + end s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil? s << " --no-color" unless @config[:color] s @@ -188,6 +227,7 @@ CONFIG attributes[:run_list] = @run_list end + attributes.delete(:run_list) if attributes[:policy_name] && !attributes[:policy_name].empty? attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty? end end @@ -219,7 +259,7 @@ CONFIG content << "mkdir #{file_on_node}\n" else content << "cat > #{file_on_node} <<'EOP'\n" + - f.read + "\nEOP\n" + f.read.gsub("'", "'\\\\''") + "\nEOP\n" end end end diff --git a/lib/chef/knife/core/cookbook_scm_repo.rb b/lib/chef/knife/core/cookbook_scm_repo.rb index e909066b02..38f432e5bb 100644 --- a/lib/chef/knife/core/cookbook_scm_repo.rb +++ b/lib/chef/knife/core/cookbook_scm_repo.rb @@ -122,7 +122,7 @@ class Chef git("branch --no-color").stdout.lines.any? { |l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ } end - def get_current_branch() + def get_current_branch ref = git("symbolic-ref HEAD").stdout ref.chomp.split("/")[2] end @@ -131,9 +131,9 @@ class Chef def git_repo?(directory) if File.directory?(File.join(directory, ".git")) - return true + true elsif File.dirname(directory) == directory - return false + false else git_repo?(File.dirname(directory)) end diff --git a/lib/chef/knife/core/custom_manifest_loader.rb b/lib/chef/knife/core/custom_manifest_loader.rb deleted file mode 100644 index 9fe51599af..0000000000 --- a/lib/chef/knife/core/custom_manifest_loader.rb +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright:: Copyright 2015-2016, 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/version" -class Chef - class Knife - class SubcommandLoader - - # - # Load a subcommand from a user-supplied - # manifest file - # - class CustomManifestLoader < Chef::Knife::SubcommandLoader - attr_accessor :manifest - def initialize(chef_config_dir, plugin_manifest) - super(chef_config_dir) - @manifest = plugin_manifest - end - - # If the user has created a ~/.chef/plugin_manifest.json file, we'll use - # that instead of inspecting the on-system gems to find the plugins. The - # file format is expected to look like: - # - # { "plugins": { - # "knife-ec2": { - # "paths": [ - # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb", - # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb" - # ] - # } - # } - # } - # - # Extraneous content in this file is ignored. This is intentional so that we - # can adapt the file format for potential behavior changes to knife in - # the future. - def find_subcommands_via_manifest - # Format of subcommand_files is "relative_path" (something you can - # Kernel.require()) => full_path. The relative path isn't used - # currently, so we just map full_path => full_path. - subcommand_files = {} - manifest["plugins"].each do |plugin_name, plugin_manifest| - plugin_manifest["paths"].each do |cmd_path| - subcommand_files[cmd_path] = cmd_path - end - end - subcommand_files.merge(find_subcommands_via_dirglob) - end - - def subcommand_files - @subcommand_files ||= (find_subcommands_via_manifest.values + site_subcommands).flatten.uniq - end - end - end - end -end diff --git a/lib/chef/knife/core/gem_glob_loader.rb b/lib/chef/knife/core/gem_glob_loader.rb index f5eff26903..c4523d69ad 100644 --- a/lib/chef/knife/core/gem_glob_loader.rb +++ b/lib/chef/knife/core/gem_glob_loader.rb @@ -81,9 +81,9 @@ class Chef files = [] if check_load_path - files = $LOAD_PATH.map { |load_path| + files = $LOAD_PATH.map do |load_path| Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob_dir(load_path)}#{Gem.suffix_pattern}"] - }.flatten.select { |file| File.file? file.untaint } + end.flatten.select { |file| File.file? file.untaint } end gem_files = latest_gem_specs.map do |spec| @@ -98,7 +98,7 @@ class Chef files.concat gem_files files.uniq! if check_load_path - return files + files end def latest_gem_specs @@ -110,7 +110,7 @@ class Chef end def check_spec_for_glob(spec, glob) - dirs = if spec.require_paths.size > 1 then + dirs = if spec.require_paths.size > 1 "{#{spec.require_paths.join(',')}}" else spec.require_paths.first diff --git a/lib/chef/knife/core/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb index f273cb5bca..861bf1510d 100644 --- a/lib/chef/knife/core/generic_presenter.rb +++ b/lib/chef/knife/core/generic_presenter.rb @@ -28,12 +28,19 @@ class Chef # :nodoc: def self.included(includer) includer.class_eval do - @attrs_to_show = [] + option :field_separator, + :short => "-S SEPARATOR", + :long => "--field-separator SEPARATOR", + :description => "Character separator used to delineate nesting in --attribute filters (default \".\")" + option :attribute, :short => "-a ATTR1 [-a ATTR2]", :long => "--attribute ATTR1 [--attribute ATTR2] ", - :proc => lambda { |val| @attrs_to_show << val }, - :description => "Show one or more attributes" + :description => "Show one or more attributes", + :proc => Proc.new { |a| + Chef::Config[:knife][:attribute] ||= [] + Chef::Config[:knife][:attribute].push(a) + } end end end @@ -173,25 +180,28 @@ class Chef config[:attribute] || config[:run_list] end + # GenericPresenter is used in contexts where MultiAttributeReturnOption + # is not, so we need to set the default value here rather than as part + # of the CLI option. + def attribute_field_separator + config[:field_separator] || "." + end + def extract_nested_value(data, nested_value_spec) - nested_value_spec.split(".").each do |attr| - if data.nil? - nil # don't get no method error on nil - # Must check :[] before attr because spec can include - # `keys` - want the key named `keys`, not a list of - # available keys. - elsif data.respond_to?(:[]) && data.has_key?(attr) - data = data[attr] - elsif data.respond_to?(attr.to_sym) - data = data.send(attr.to_sym) - else - data = begin - data.send(attr.to_sym) - rescue NoMethodError - nil - end - end + nested_value_spec.split(attribute_field_separator).each do |attr| + data = + if data.is_a?(Array) + data[attr.to_i] + elsif data.respond_to?(:[], false) && data.key?(attr) + data[attr] + elsif data.respond_to?(attr.to_sym, false) + # handles -a chef_environment and other things that hang of the node and aren't really attributes + data.public_send(attr.to_sym) + else + nil + end end + # necessary (?) for coercing objects (the run_list object?) to hashes ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data end diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb index 68c1acf4f1..1e2d9b41b6 100644 --- a/lib/chef/knife/core/status_presenter.rb +++ b/lib/chef/knife/core/status_presenter.rb @@ -101,29 +101,38 @@ class Chef fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn] name = node["name"] || node.name - hours, minutes, = time_difference_in_hms(node["ohai_time"]) - hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" - minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" run_list = "#{node['run_list']}" if config[:run_list] - if hours > 24 - color = :red - text = hours_text - elsif hours >= 1 - color = :yellow - text = hours_text + line_parts = Array.new + + if node["ohai_time"] + hours, minutes, seconds = time_difference_in_hms(node["ohai_time"]) + hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" + minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" + seconds_text = "#{seconds} second#{seconds == 1 ? ' ' : 's'}" + if hours > 24 + color = :red + text = hours_text + elsif hours >= 1 + color = :yellow + text = hours_text + elsif minutes >= 1 + color = :green + text = minutes_text + else + color = :green + text = seconds_text + end + line_parts << @ui.color(text, color) + " ago" << name else - color = :green - text = minutes_text + line_parts << "Node #{name} has not yet converged" end - line_parts = Array.new - line_parts << @ui.color(text, color) + " ago" << name line_parts << fqdn if fqdn line_parts << ip if ip line_parts << run_list if run_list if node["platform"] - platform = node["platform"] + platform = node["platform"].dup if node["platform_version"] platform << " #{node['platform_version']}" end @@ -148,7 +157,7 @@ class Chef difference = difference % 3600 minutes = (difference / 60).to_i seconds = (difference % 60) - return [hours, minutes, seconds] + [hours, minutes, seconds] end end diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index e617b39ded..026967d6ec 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -1,6 +1,6 @@ # Author:: Christopher Brown (<cb@chef.io>) # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ require "chef/version" require "chef/util/path_helper" require "chef/knife/core/gem_glob_loader" require "chef/knife/core/hashed_command_loader" -require "chef/knife/core/custom_manifest_loader" class Chef class Knife @@ -38,7 +37,6 @@ class Chef # class SubcommandLoader attr_reader :chef_config_dir - attr_reader :env # A small factory method. Eventually, this is the only place # where SubcommandLoader should know about its subclasses, but @@ -48,11 +46,8 @@ class Chef # or directly instantiate the appropriate subclass def self.for_config(chef_config_dir) if autogenerated_manifest? - Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}") + Chef::Log.trace("Using autogenerated hashed command manifest #{plugin_manifest_path}") Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest) - elsif custom_manifest? - Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") - Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest) else Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir) end @@ -72,10 +67,6 @@ class Chef plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY) end - def self.custom_manifest? - plugin_manifest? && plugin_manifest.key?("plugins") - end - def self.plugin_manifest Chef::JSONCompat.from_json(File.read(plugin_manifest_path)) end @@ -84,14 +75,8 @@ class Chef Chef::Util::PathHelper.home(".chef", "plugin_manifest.json") end - def initialize(chef_config_dir, env = nil) + def initialize(chef_config_dir) @chef_config_dir = chef_config_dir - - # Deprecated and un-used instance variable. - @env = env - unless env.nil? - Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") - end end # Load all the sub-commands @@ -149,21 +134,6 @@ class Chef end # - # Subclassses should define this themselves. Eventually, this will raise a - # NotImplemented error, but for now, we mimic the behavior the user was likely - # to get in the past. - # - def subcommand_files - Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated. -Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)" - @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest? - Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files - else - Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files - end - end - - # # Utility function for finding an element in a hash given an array # of words and a separator. We find the the longest key in the # hash composed of the given words joined by the separator. diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb index 67e431f1a7..b2efbd8b8f 100644 --- a/lib/chef/knife/core/ui.rb +++ b/lib/chef/knife/core/ui.rb @@ -65,22 +65,18 @@ class Chef # Prints a message to stdout. Aliased as +info+ for compatibility with # the logger API. def msg(message) - begin - stdout.puts message - rescue Errno::EPIPE => e - raise e if @config[:verbosity] >= 2 - exit 0 - end + stdout.puts message + rescue Errno::EPIPE => e + raise e if @config[:verbosity] >= 2 + exit 0 end # Prints a msg to stderr. Used for info, warn, error, and fatal. def log(message) - begin - stderr.puts message - rescue Errno::EPIPE => e - raise e if @config[:verbosity] >= 2 - exit 0 - end + stderr.puts message + rescue Errno::EPIPE => e + raise e if @config[:verbosity] >= 2 + exit 0 end alias :info :log @@ -138,7 +134,7 @@ class Chef end def ask_question(question, opts = {}) - question = question + "[#{opts[:default]}] " if opts[:default] + question += "[#{opts[:default]}] " if opts[:default] if opts[:default] && config[:defaults] opts[:default] @@ -155,12 +151,10 @@ class Chef end def pretty_print(data) - begin - stdout.puts data - rescue Errno::EPIPE => e - raise e if @config[:verbosity] >= 2 - exit 0 - end + stdout.puts data + rescue Errno::EPIPE => e + raise e if @config[:verbosity] >= 2 + exit 0 end # Hash -> Hash @@ -178,7 +172,7 @@ class Chef tf.sync = true tf.puts output tf.close - raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}") + raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup.html for details." unless system("#{config[:editor]} #{tf.path}") output = IO.read(tf.path) end @@ -186,8 +180,7 @@ class Chef if parse_output if object_class.nil? - Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please pass in the class to inflate or use #edit_hash") - Chef::JSONCompat.from_json(output) + raise ArgumentError, "Please pass in the object class to hydrate or use #edit_hash" else object_class.from_hash(Chef::JSONCompat.parse(output)) end @@ -215,9 +208,9 @@ class Chef output_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(output)) if object_parsed_again != output_parsed_again output.save - self.msg("Saved #{output}") + msg("Saved #{output}") else - self.msg("Object unchanged, not saving") + msg("Object unchanged, not saving") end output(format_for_display(object)) if config[:print_after] end @@ -247,19 +240,19 @@ class Chef when "Y", "y" true when "N", "n" - self.msg("You said no, so I'm done here.") + msg("You said no, so I'm done here.") false when "" unless default_choice.nil? default_choice else - self.msg("I have no idea what to do with '#{answer}'") - self.msg("Just say Y or N, please.") + msg("I have no idea what to do with '#{answer}'") + msg("Just say Y or N, please.") confirm_without_exit(question, append_instructions, default_choice) end else - self.msg("I have no idea what to do with '#{answer}'") - self.msg("Just say Y or N, please.") + msg("I have no idea what to do with '#{answer}'") + msg("Just say Y or N, please.") confirm_without_exit(question, append_instructions, default_choice) end end diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index 196278bb80..563e931dca 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -49,13 +49,15 @@ class Chef exit(1) end - # create the data bag + # Verify if the data bag exists begin + rest.get("data/#{@data_bag_name}") + ui.info("Data bag #{@data_bag_name} already exists") + rescue Net::HTTPServerException => e + raise unless e.to_s =~ /^404/ + # if it doesn't exists, try to create it rest.post("data", { "name" => @data_bag_name }) ui.info("Created data_bag[#{@data_bag_name}]") - rescue Net::HTTPServerException => e - raise unless e.to_s =~ /^409/ - ui.info("Data bag #{@data_bag_name} already exists") end # if an item is specified, create it, as well diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb index b426cd442c..a612004e15 100644 --- a/lib/chef/knife/data_bag_secret_options.rb +++ b/lib/chef/knife/data_bag_secret_options.rb @@ -95,7 +95,7 @@ class Chef ## # Determine if the user has specified an appropriate secret for encrypting data bag items. - # @returns boolean + # @return boolean def base_encryption_secret_provided?(need_encrypt_flag = true) validate_secrets @@ -114,7 +114,7 @@ class Chef # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret return true end - return false + false end def has_cl_secret? diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index ea9c390f19..5df0561276 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -51,7 +51,7 @@ class Chef ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") format_for_display(raw_data) else - ui.warn("Unencrypted data bag detected, ignoring any provided secret options.") + ui.warn("Unencrypted data bag detected, ignoring any provided secret options.") if secret format_for_display(raw_data) end diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb index e773f65106..eec92cc9a3 100644 --- a/lib/chef/knife/deps.rb +++ b/lib/chef/knife/deps.rb @@ -72,49 +72,47 @@ class Chef end def get_dependencies(entry) - begin - if entry.parent && entry.parent.path == "/cookbooks" - return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" } + if entry.parent && entry.parent.path == "/cookbooks" + entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" } - elsif entry.parent && entry.parent.path == "/nodes" - node = Chef::JSONCompat.parse(entry.read) - result = [] - if node["chef_environment"] && node["chef_environment"] != "_default" - result << "/environments/#{node['chef_environment']}.json" - end - if node["run_list"] - result += dependencies_from_runlist(node["run_list"]) - end - result + elsif entry.parent && entry.parent.path == "/nodes" + node = Chef::JSONCompat.parse(entry.read) + result = [] + if node["chef_environment"] && node["chef_environment"] != "_default" + result << "/environments/#{node['chef_environment']}.json" + end + if node["run_list"] + result += dependencies_from_runlist(node["run_list"]) + end + result - elsif entry.parent && entry.parent.path == "/roles" - role = Chef::JSONCompat.parse(entry.read) - result = [] - if role["run_list"] - dependencies_from_runlist(role["run_list"]).each do |dependency| - result << dependency if !result.include?(dependency) - end + elsif entry.parent && entry.parent.path == "/roles" + role = Chef::JSONCompat.parse(entry.read) + result = [] + if role["run_list"] + dependencies_from_runlist(role["run_list"]).each do |dependency| + result << dependency if !result.include?(dependency) end - if role["env_run_lists"] - role["env_run_lists"].each_pair do |env, run_list| - dependencies_from_runlist(run_list).each do |dependency| - result << dependency if !result.include?(dependency) - end + end + if role["env_run_lists"] + role["env_run_lists"].each_pair do |env, run_list| + dependencies_from_runlist(run_list).each do |dependency| + result << dependency if !result.include?(dependency) end end - result + end + result - elsif !entry.exists? - raise Chef::ChefFS::FileSystem::NotFoundError.new(entry) + elsif !entry.exists? + raise Chef::ChefFS::FileSystem::NotFoundError.new(entry) - else - [] - end - rescue Chef::ChefFS::FileSystem::NotFoundError => e - ui.error "#{format_path(e.entry)}: No such file or directory" - self.exit_code = 2 + else [] end + rescue Chef::ChefFS::FileSystem::NotFoundError => e + ui.error "#{format_path(e.entry)}: No such file or directory" + self.exit_code = 2 + [] end def dependencies_from_runlist(run_list) diff --git a/lib/chef/knife/edit.rb b/lib/chef/knife/edit.rb index 8489e4e179..4d7338f9f6 100644 --- a/lib/chef/knife/edit.rb +++ b/lib/chef/knife/edit.rb @@ -9,7 +9,7 @@ class Chef deps do require "chef/chef_fs/file_system" - require "chef/chef_fs/file_system/not_found_error" + require "chef/chef_fs/file_system/exceptions" end option :local, @@ -58,7 +58,7 @@ class Chef # Let the user edit the temporary file if !system("#{config[:editor]} #{file.path}") - raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_using.html for details." + raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup.html for details." end result_text = IO.read(file.path) diff --git a/lib/chef/knife/environment_compare.rb b/lib/chef/knife/environment_compare.rb index 8a2ef853d3..9131f06068 100644 --- a/lib/chef/knife/environment_compare.rb +++ b/lib/chef/knife/environment_compare.rb @@ -81,7 +81,7 @@ class Chef def constraint_list(environments) constraints = {} - environments.each do |env, url| + environments.each do |env, url| # rubocop:disable Performance/HashEachMethods # Because you cannot modify the default environment I filter it out here. unless env == "_default" envdata = Chef::Environment.load(env) @@ -94,17 +94,17 @@ class Chef def cookbook_list(constraints) result = {} - constraints.each { |env, cb| result.merge!(cb) } + constraints.each_value { |cb| result.merge!(cb) } result end def matrix_output(cookbooks, constraints) rows = [ "" ] environments = [] - constraints.each { |e, v| environments << e.to_s } + constraints.each_key { |e| environments << e.to_s } columns = environments.count + 1 environments.each { |env| rows << ui.color(env, :bold) } - cookbooks.each do |c, v| + cookbooks.each_key do |c| total = [] environments.each { |n| total << constraints[n][c] } if total.uniq.count == 1 diff --git a/lib/chef/knife/exec.rb b/lib/chef/knife/exec.rb index 0aa8ea2ba8..7b27a51b85 100644 --- a/lib/chef/knife/exec.rb +++ b/lib/chef/knife/exec.rb @@ -68,15 +68,15 @@ class Chef::Knife::Exec < Chef::Knife # Failing that, try searching the script path. If we can't find # anything, fail gracefully. - Chef::Log.debug("Searching script_path: #{config[:script_path].inspect}") + Chef::Log.trace("Searching script_path: #{config[:script_path].inspect}") config[:script_path].each do |path| path = File.expand_path(path) test = File.join(path, x) - Chef::Log.debug("Testing: #{test}") + Chef::Log.trace("Testing: #{test}") if File.exists?(test) script = test - Chef::Log.debug("Found: #{test}") + Chef::Log.trace("Found: #{test}") return script end end diff --git a/lib/chef/knife/help.rb b/lib/chef/knife/help.rb deleted file mode 100644 index e45b54eec8..0000000000 --- a/lib/chef/knife/help.rb +++ /dev/null @@ -1,101 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -class Chef - class Knife - class Help < Chef::Knife - - banner "knife help [list|TOPIC]" - - def run - if name_args.empty? - ui.info "Usage: knife SUBCOMMAND (options)" - ui.msg "" - # This command is atypical, the user is likely not interested in usage of - # this command, but knife in general. So hack the banner. - opt_parser.banner = "General Knife Options:" - ui.msg opt_parser.to_s - ui.msg "" - ui.info "For further help:" - ui.info(<<-MOAR_HELP) - knife help list list help topics - knife help knife show general knife help - knife help TOPIC display the manual for TOPIC - knife SUBCOMMAND --help show the options for a command -MOAR_HELP - exit 1 - else - @query = name_args.join("-") - end - - case @query - when "topics", "list" - print_help_topics - exit 1 - when "intro", "knife" - @topic = "knife" - else - @topic = find_manpages_for_query(@query) - end - - manpage_path = find_manpage_path(@topic) - exec "man #{manpage_path}" - end - - def help_topics - # The list of help topics is generated by a rake task from the available man pages - # This constant is provided in help_topics.rb which is automatically required/loaded by the knife subcommand loader. - HELP_TOPICS - end - - def print_help_topics - ui.info "Available help topics are: " - help_topics.collect { |t| t.gsub(/knife-/, "") }.sort.each do |topic| - ui.msg " #{topic}" - end - end - - def find_manpages_for_query(query) - possibilities = help_topics.select do |manpage| - ::File.fnmatch("knife-#{query}*", manpage) || ::File.fnmatch("#{query}*", manpage) - end - if possibilities.empty? - ui.error "No help found for '#{query}'" - ui.msg "" - print_help_topics - exit 1 - elsif possibilities.size == 1 - possibilities.first - else - ui.info "Multiple help topics match your query. Pick one:" - ui.highline.choose(*possibilities) - end - end - - def find_manpage_path(topic) - if ::File.exists?(::File.expand_path("../distro/common/man/man1/#{topic}.1", CHEF_ROOT)) - # If we've provided the man page in the gem, give that - return ::File.expand_path("../distro/common/man/man1/#{topic}.1", CHEF_ROOT) - else - # Otherwise, we'll just be using MANPATH - topic - end - end - end - end -end diff --git a/lib/chef/knife/help_topics.rb b/lib/chef/knife/help_topics.rb deleted file mode 100644 index a2aad65f55..0000000000 --- a/lib/chef/knife/help_topics.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Do not edit this file by hand -# This file is autogenerated by the docs:list rake task from the available manpages - -HELP_TOPICS = ["chef-shell", "knife-bootstrap", "knife-client", "knife-configure", "knife-cookbook-site", "knife-cookbook", "knife-data-bag", "knife-delete", "knife-deps", "knife-diff", "knife-download", "knife-edit", "knife-environment", "knife-exec", "knife-index-rebuild", "knife-list", "knife-node", "knife-raw", "knife-recipe-list", "knife-role", "knife-search", "knife-show", "knife-ssh", "knife-status", "knife-tag", "knife-upload", "knife-user", "knife-xargs", "knife"] diff --git a/lib/chef/knife/index_rebuild.rb b/lib/chef/knife/index_rebuild.rb deleted file mode 100644 index 206b7b0fbf..0000000000 --- a/lib/chef/knife/index_rebuild.rb +++ /dev/null @@ -1,133 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@kallistec.com>) -# Copyright:: Copyright 2009-2016, Daniel DeLeo -# 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/knife" - -class Chef - class Knife - class IndexRebuild < Knife - - banner "knife index rebuild (options)" - option :yes, - :short => "-y", - :long => "--yes", - :boolean => true, - :description => "don't bother to ask if I'm sure" - - def run - api_info = grab_api_info - - if unsupported_version?(api_info) - unsupported_server_message(api_info) - exit 1 - else - deprecated_server_message - nag - output rest.post("/search/reindex", {}) - end - end - - def grab_api_info - # Since we don't yet have any endpoints that implement an - # OPTIONS handler, we need to get our version header - # information in a more roundabout way. We'll try to query - # for a node we know won't exist; the 404 response that comes - # back will give us what we want - dummy_node = "knife_index_rebuild_test_#{rand(1000000)}" - rest.get("/nodes/#{dummy_node}") - rescue Net::HTTPServerException => exception - r = exception.response - parse_api_info(r) - end - - # Only Chef 11+ servers will have version information in their - # headers, and only those servers will lack an API endpoint for - # index rebuilding. - def unsupported_version?(api_info) - !!api_info["version"] - end - - def unsupported_server_message(api_info) - ui.error("Rebuilding the index is not available via knife for #{server_type(api_info)}s version 11.0.0 and above.") - ui.info("Instead, run the '#{ctl_command(api_info)} reindex' command on the server itself.") - end - - def deprecated_server_message - ui.warn("'knife index rebuild' has been removed for Chef 11+ servers. It will continue to work for prior versions, however.") - end - - def nag - ui.info("This operation is destructive. Rebuilding the index may take some time.") - ui.confirm("Continue") - end - - # Chef 11 (and above) servers return various pieces of - # information about the server in an +x-ops-api-info+ header. - # This is a +;+ delimited string of key / value pairs, separated - # by +=+. - # - # Given a Net::HTTPResponse object, this method extracts this - # information (if present), and returns it as a hash. If no - # such header is found, an empty hash is returned. - def parse_api_info(response) - value = response["x-ops-api-info"] - if value - kv = value.split(";") - kv.inject({}) do |acc, pair| - k, v = pair.split("=") - acc[k] = v - acc - end - else - {} - end - end - - # Given an API info hash (see +#parse_api_info(response)+), - # return a string describing the kind of server we're - # interacting with (based on the +flavor+ field) - def server_type(api_info) - case api_info["flavor"] - when "osc" - "Open Source Chef Server" - when "opc" - "Private Chef Server" - else - # Generic fallback - "Chef Server" - end - end - - # Given an API info hash (see +#parse_api_info(response)+), - # return the name of the "server-ctl" command for the kind of - # server we're interacting with (based on the +flavor+ field) - def ctl_command(api_info) - case api_info["flavor"] - when "osc" - "chef-server-ctl" - when "opc" - "private-chef-ctl" - else - # Generic fallback - "chef-server-ctl" - end - end - - end - end -end diff --git a/lib/chef/knife/list.rb b/lib/chef/knife/list.rb index 3d1583b270..e1a50b9360 100644 --- a/lib/chef/knife/list.rb +++ b/lib/chef/knife/list.rb @@ -44,7 +44,6 @@ class Chef patterns = name_args.length == 0 ? [""] : name_args # Get the top-level matches - args = pattern_args_from(patterns) all_results = parallelize(pattern_args_from(patterns)) do |pattern| pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).to_a @@ -70,7 +69,7 @@ class Chef # Flatten out directory results if necessary if config[:flat] - dir_results.each do |result, children| + dir_results.each do |result, children| # rubocop:disable Performance/HashEachMethods results += children end dir_results = [] @@ -95,10 +94,10 @@ class Chef printed_something = true end output "#{format_path(result)}:" - print_results(children.map { |result| maybe_add_slash(result.name, result.dir?) }.sort, "") + print_results(children.map { |result| maybe_add_slash(result.display_name, result.dir?) }.sort, "") end - exit self.exit_code if self.exit_code + exit exit_code if exit_code end def add_dir_result(result) diff --git a/lib/chef/knife/node_delete.rb b/lib/chef/knife/node_delete.rb index 4dd7d764a1..52b8d122ca 100644 --- a/lib/chef/knife/node_delete.rb +++ b/lib/chef/knife/node_delete.rb @@ -27,18 +27,18 @@ class Chef require "chef/json_compat" end - banner "knife node delete NODE (options)" + banner "knife node delete [NODE [NODE]] (options)" def run - @node_name = @name_args[0] - - if @node_name.nil? + if @name_args.length == 0 show_usage - ui.fatal("You must specify a node name") + ui.fatal("You must specify at least one node name") exit 1 end - delete_object(Chef::Node, @node_name) + @name_args.each do |node_name| + delete_object(Chef::Node, node_name) + end end end diff --git a/lib/chef/knife/node_policy_set.rb b/lib/chef/knife/node_policy_set.rb new file mode 100644 index 0000000000..404446fe0e --- /dev/null +++ b/lib/chef/knife/node_policy_set.rb @@ -0,0 +1,79 @@ +# +# Author:: Piyush Awasthi (<piyush.awasthi@chef.io>) +# Copyright:: Copyright 2017-2018, 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/knife" + +class Chef + class Knife + class NodePolicySet < Knife + + deps do + require "chef/node" + require "chef/json_compat" + end + + banner "knife node policy set NODE POLICY_GROUP POLICY_NAME (options)" + + def run + validate_node! + validate_options! + node = Chef::Node.load(@name_args[0]) + set_policy(node) + if node.save + ui.info "Successfully set the policy on node #{node.name}" + else + ui.info "Error in updating node #{node.name}" + end + end + + private + + # Set policy name and group to node + def set_policy(node) + policy_group, policy_name = @name_args[1..-1] + node.policy_name = policy_name + node.policy_group = policy_group + end + + # Validate policy name and policy group + def validate_options! + if incomplete_policyfile_options? + ui.error("Policy group and name must be specified together") + exit 1 + end + true + end + + # Validate node pass in CLI + def validate_node! + if @name_args[0].nil? + ui.error("You must specify a node name") + show_usage + exit 1 + end + end + + # True if one of policy_name or policy_group was given, but not both + def incomplete_policyfile_options? + policy_group, policy_name = @name_args[1..-1] + (policy_group.nil? || policy_name.nil? || @name_args[1..-1].size > 2) + end + + end + end +end diff --git a/lib/chef/knife/node_run_list_add.rb b/lib/chef/knife/node_run_list_add.rb index f8d40c8321..fb4ce3bc12 100644 --- a/lib/chef/knife/node_run_list_add.rb +++ b/lib/chef/knife/node_run_list_add.rb @@ -27,7 +27,7 @@ class Chef require "chef/json_compat" end - banner "knife node run_list add [NODE] [ENTRY[,ENTRY]] (options)" + banner "knife node run_list add [NODE] [ENTRY [ENTRY]] (options)" option :after, :short => "-a ITEM", diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb index 3f9cdabfff..df50818c23 100644 --- a/lib/chef/knife/node_run_list_remove.rb +++ b/lib/chef/knife/node_run_list_remove.rb @@ -27,7 +27,7 @@ class Chef require "chef/json_compat" end - banner "knife node run_list remove [NODE] [ENTRY[,ENTRY]] (options)" + banner "knife node run_list remove [NODE] [ENTRY [ENTRY]] (options)" def run node = Chef::Node.load(@name_args[0]) diff --git a/lib/chef/knife/node_show.rb b/lib/chef/knife/node_show.rb index c616b8ab72..3092b3fc27 100644 --- a/lib/chef/knife/node_show.rb +++ b/lib/chef/knife/node_show.rb @@ -55,11 +55,6 @@ class Chef node = Chef::Node.load(@node_name) output(format_for_display(node)) - self.class.attrs_to_show = [] - end - - def self.attrs_to_show=(attrs) - @attrs_to_show = attrs end end end diff --git a/lib/chef/knife/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb index b513f31328..dee1cf41b1 100644 --- a/lib/chef/knife/osc_user_reregister.rb +++ b/lib/chef/knife/osc_user_reregister.rb @@ -49,7 +49,7 @@ class Chef end user = Chef::User.load(@user_name).reregister - Chef::Log.debug("Updated user data: #{user.inspect}") + Chef::Log.trace("Updated user data: #{user.inspect}") key = user.private_key if config[:file] File.open(config[:file], "w") do |f| diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/osc_user_show.rb index 22e9bf4dcd..5350837ad3 100644 --- a/lib/chef/knife/osc_user_show.rb +++ b/lib/chef/knife/osc_user_show.rb @@ -48,7 +48,6 @@ class Chef user = Chef::User.load(@user_name) output(format_for_display(user)) end - end end end diff --git a/lib/chef/knife/role_env_run_list_add.rb b/lib/chef/knife/role_env_run_list_add.rb index 61aec506a9..4a3bb70641 100644 --- a/lib/chef/knife/role_env_run_list_add.rb +++ b/lib/chef/knife/role_env_run_list_add.rb @@ -27,7 +27,7 @@ class Chef require "chef/json_compat" end - banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY[,ENTRY]] (options)" + banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY [ENTRY]] (options)" option :after, :short => "-a ITEM", diff --git a/lib/chef/knife/role_run_list_add.rb b/lib/chef/knife/role_run_list_add.rb index 6aa92d37ba..7eaaa136bb 100644 --- a/lib/chef/knife/role_run_list_add.rb +++ b/lib/chef/knife/role_run_list_add.rb @@ -27,7 +27,7 @@ class Chef require "chef/json_compat" end - banner "knife role run_list add [ROLE] [ENTRY[,ENTRY]] (options)" + banner "knife role run_list add [ROLE] [ENTRY [ENTRY]] (options)" option :after, :short => "-a ITEM", diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb index 38d1ab3f42..94c33aa594 100644 --- a/lib/chef/knife/search.rb +++ b/lib/chef/knife/search.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ require "chef/knife" require "chef/knife/core/node_presenter" +require "addressable/uri" class Chef class Knife @@ -36,12 +37,6 @@ class Chef banner "knife search INDEX QUERY (options)" - option :sort, - :short => "-o SORT", - :long => "--sort SORT", - :description => "The order to sort the results in", - :default => nil - option :start, :short => "-b ROW", :long => "--start ROW", @@ -78,33 +73,34 @@ class Chef def run read_cli_args - fuzzify_query if @type == "node" ui.use_presenter Knife::Core::NodePresenter end q = Chef::Search::Query.new - escaped_query = URI.escape(@query, - Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) result_items = [] result_count = 0 search_args = Hash.new - search_args[:sort] = config[:sort] if config[:sort] + search_args[:fuzz] = true search_args[:start] = config[:start] if config[:start] search_args[:rows] = config[:rows] if config[:rows] if config[:filter_result] search_args[:filter_result] = create_result_filter(config[:filter_result]) elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?) search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute]) + elsif config[:id_only] + search_args[:filter_result] = create_result_filter_from_attributes([]) end begin - q.search(@type, escaped_query, search_args) do |item| + q.search(@type, @query, search_args) do |item| formatted_item = Hash.new - if item.is_a?(Hash) + if config[:id_only] + formatted_item = format_for_display({ "id" => item["__display_name"] }) + elsif item.is_a?(Hash) # doing a little magic here to set the correct name formatted_item[item["__display_name"]] = item.reject { |k| k == "__display_name" } else @@ -116,7 +112,7 @@ class Chef rescue Net::HTTPServerException => e msg = Chef::JSONCompat.from_json(e.response.body)["error"].first ui.error("knife search failed: #{msg}") - exit 1 + exit 99 end if ui.interchange? @@ -131,6 +127,9 @@ class Chef end end end + + # return a "failure" code to the shell so that knife search can be used in pipes similar to grep + exit 1 if result_count == 0 end def read_cli_args @@ -158,12 +157,6 @@ class Chef end end - def fuzzify_query - if @query !~ /:/ - @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*" - end - end - # This method turns a set of key value pairs in a string into the appropriate data structure that the # chef-server search api is expecting. # expected input is in the form of: @@ -183,7 +176,7 @@ class Chef return_id, attr_path = f.split("=") final_filter[return_id.to_sym] = attr_path.split(".") end - return final_filter + final_filter end def create_result_filter_from_attributes(filter_array) @@ -193,7 +186,7 @@ class Chef end # adding magic filter so we can actually pull the name as before final_filter["__display_name"] = [ "name" ] - return final_filter + final_filter end end diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 2bbcbfc79e..c38dc43e74 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2009-2016, Chef Software Inc. +# Copyright:: Copyright 2009-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,6 @@ class Chef deps do require "net/ssh" require "net/ssh/multi" - require "chef/monkey_patches/net-ssh-multi" require "readline" require "chef/exceptions" require "chef/search/query" @@ -47,11 +46,10 @@ class Chef :default => nil, :proc => lambda { |o| o.to_i } - option :attribute, + option :ssh_attribute, :short => "-a ATTR", :long => "--attribute ATTR", - :description => "The attribute to use for opening the connection - default depends on the context", - :proc => Proc.new { |key| Chef::Config[:knife][:ssh_attribute] = key.strip } + :description => "The attribute to use for opening the connection - default depends on the context" option :manual, :short => "-m", @@ -60,6 +58,10 @@ class Chef :description => "QUERY is a space separated list of servers", :default => false + option :prefix_attribute, + :long => "--prefix-attribute ATTR", + :description => "The attribute to use for prefixing the ouput - default depends on the context" + option :ssh_user, :short => "-x USERNAME", :long => "--ssh-user USERNAME", @@ -93,16 +95,16 @@ class Chef :description => "The ssh gateway", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key.strip } + option :ssh_gateway_identity, + :long => "--ssh-gateway-identity SSH_GATEWAY_IDENTITY", + :description => "The SSH identity file used for gateway authentication" + option :forward_agent, :short => "-A", :long => "--forward-agent", :description => "Enable SSH agent forwarding", :boolean => true - option :identity_file, - :long => "--identity-file IDENTITY_FILE", - :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead." - option :ssh_identity_file, :short => "-i IDENTITY_FILE", :long => "--ssh-identity-file IDENTITY_FILE", @@ -119,7 +121,13 @@ class Chef :long => "--exit-on-error", :description => "Immediately exit if an error is encountered", :boolean => true, - :proc => Proc.new { :raise } + :default => false + + option :duplicated_fqdns, + :long => "--duplicated-fqdns", + :description => "Behavior if FQDNs are duplicated, ignored by default", + :proc => Proc.new { |key| Chef::Config[:knife][:duplicated_fqdns] = key.strip.to_sym }, + :default => :ignore option :tmux_split, :long => "--tmux-split", @@ -128,15 +136,13 @@ class Chef :default => false def session - config[:on_error] ||= :skip ssh_error_handler = Proc.new do |server| - case config[:on_error] - when :skip - ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}" - $!.backtrace.each { |l| Chef::Log.debug(l) } - when :raise + if config[:on_error] #Net::SSH::Multi magic to force exception to be re-raised. throw :go, :raise + else + ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}" + $!.backtrace.each { |l| Chef::Log.debug(l) } end end @@ -148,7 +154,7 @@ class Chef if config[:ssh_gateway] gw_host, gw_user = config[:ssh_gateway].split("@").reverse gw_host, gw_port = gw_host.split(":") - gw_opts = session_options(gw_host, gw_port, gw_user) + gw_opts = session_options(gw_host, gw_port, gw_user, gateway: true) user = gw_opts.delete(:user) begin @@ -164,63 +170,92 @@ class Chef end def configure_session - list = config[:manual] ? - @name_args[0].split(" ") : - search_nodes + list = config[:manual] ? @name_args[0].split(" ") : search_nodes if list.length == 0 - if @action_nodes.length == 0 + if @search_count == 0 ui.fatal("No nodes returned from search") else - ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes" : "node"} found, " + + ui.fatal("#{@search_count} #{@search_count > 1 ? "nodes" : "node"} found, " + "but does not have the required attribute to establish the connection. " + "Try setting another attribute to open the connection using --attribute.") end exit 10 end + if %i{warn fatal}.include?(config[:duplicated_fqdns]) + fqdns = list.map { |v| v[0] } + if fqdns.count != fqdns.uniq.count + duplicated_fqdns = fqdns.uniq + ui.send(config[:duplicated_fqdns], + "SSH #{duplicated_fqdns.count > 1 ? 'nodes are' : 'node is'} " + + "duplicated: #{duplicated_fqdns.join(',')}") + exit 10 if config[:duplicated_fqdns] == :fatal + end + end session_from_list(list) end - def get_ssh_attribute(node) + def get_prefix_attribute(item) + # Order of precedence for prefix + # 1) config value (cli or knife config) + # 2) nil + msg = "Using node attribute '%s' as the prefix: %s" + if item["prefix"] + Chef::Log.debug(sprintf(msg, config[:prefix_attribute], item["prefix"])) + item["prefix"] + else + nil + end + end + + def get_ssh_attribute(item) # Order of precedence for ssh target - # 1) command line attribute - # 2) configuration file - # 3) cloud attribute - # 4) fqdn - if config[:attribute] - Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target") - attribute = config[:attribute] - elsif Chef::Config[:knife][:ssh_attribute] - Chef::Log.debug("Using node attribute #{Chef::Config[:knife][:ssh_attribute]}") - attribute = Chef::Config[:knife][:ssh_attribute] - elsif node[:cloud] && - node[:cloud][:public_hostname] && - !node[:cloud][:public_hostname].empty? - Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target") - attribute = "cloud.public_hostname" + # 1) config value (cli or knife config) + # 2) cloud attribute + # 3) fqdn + msg = "Using node attribute '%s' as the ssh target: %s" + if item["target"] + Chef::Log.debug(sprintf(msg, config[:ssh_attribute], item["target"])) + item["target"] + elsif !item.dig("cloud", "public_hostname").to_s.empty? + Chef::Log.debug(sprintf(msg, "cloud.public_hostname", item["cloud"]["public_hostname"])) + item["cloud"]["public_hostname"] else - # falling back to default of fqdn - Chef::Log.debug("Using node attribute 'fqdn' as the ssh target") - attribute = "fqdn" + Chef::Log.debug(sprintf(msg, "fqdn", item["fqdn"])) + item["fqdn"] end - attribute end def search_nodes list = Array.new query = Chef::Search::Query.new - @action_nodes = query.search(:node, @name_args[0])[0] - @action_nodes.each do |item| + required_attributes = { fqdn: ["fqdn"], cloud: ["cloud"] } + + separator = ui.presenter.attribute_field_separator + + if config[:prefix_attribute] + required_attributes[:prefix] = config[:prefix_attribute].split(separator) + end + + if config[:ssh_attribute] + required_attributes[:target] = config[:ssh_attribute].split(separator) + end + + @search_count = 0 + query.search(:node, @name_args[0], filter_result: required_attributes, fuzz: true) do |item| + @search_count += 1 # we should skip the loop to next iteration if the item # returned by the search is nil next if item.nil? # next if we couldn't find the specified attribute in the # returned node object - host = extract_nested_value(item, get_ssh_attribute(item)) + host = get_ssh_attribute(item) next if host.nil? - ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port] - srv = [host, ssh_port] + prefix = get_prefix_attribute(item) + ssh_port = item.dig("cloud", "public_ssh_port") + srv = [host, ssh_port, prefix] list.push(srv) end + list end @@ -232,15 +267,19 @@ class Chef # @param host [String] Hostname for this session. # @param port [String] SSH port for this session. # @param user [String] Optional username for this session. + # @param gateway [Boolean] Flag: host or gateway key # @return [Hash<Symbol, Object>] - def session_options(host, port, user = nil) - ssh_config = Net::SSH.configuration_for(host) + def session_options(host, port, user = nil, gateway: false) + ssh_config = Net::SSH.configuration_for(host, true) {}.tap do |opts| # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user] opts[:user] = user || config[:ssh_user] || ssh_config[:user] - if config[:ssh_identity_file] + if !gateway && config[:ssh_identity_file] opts[:keys] = File.expand_path(config[:ssh_identity_file]) opts[:keys_only] = true + elsif gateway && config[:ssh_gateway_identity] + opts[:keys] = File.expand_path(config[:ssh_gateway_identity]) + opts[:keys_only] = true elsif config[:ssh_password] opts[:password] = config[:ssh_password] end @@ -249,38 +288,45 @@ class Chef opts[:forward_agent] = forward_agent unless forward_agent.nil? port ||= ssh_config[:port] opts[:port] = port unless port.nil? - opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug + opts[:logger] = Chef::Log.with_child(subsystem: "net/ssh") if Chef::Log.level == :trace if !config[:host_key_verify] - opts[:paranoid] = false + opts[:verify_host_key] = false opts[:user_known_hosts_file] = "/dev/null" end + if ssh_config[:keepalive] + opts[:keepalive] = true + opts[:keepalive_interval] = ssh_config[:keepalive_interval] + end end end def session_from_list(list) list.each do |item| - host, ssh_port = item + host, ssh_port, prefix = item + prefix = host unless prefix Chef::Log.debug("Adding #{host}") - session_opts = session_options(host, ssh_port) + session_opts = session_options(host, ssh_port, gateway: false) # Handle port overrides for the main connection. session_opts[:port] = Chef::Config[:knife][:ssh_port] if Chef::Config[:knife][:ssh_port] session_opts[:port] = config[:ssh_port] if config[:ssh_port] # Handle connection timeout session_opts[:timeout] = Chef::Config[:knife][:ssh_timeout] if Chef::Config[:knife][:ssh_timeout] session_opts[:timeout] = config[:ssh_timeout] if config[:ssh_timeout] + # Handle session prefix + session_opts[:properties] = { prefix: prefix } # Create the hostspec. hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host # Connect a new session on the multi. session.use(hostspec, session_opts) - @longest = host.length if host.length > @longest + @longest = prefix.length if prefix.length > @longest end session end def fixup_sudo(command) - command.sub(/^sudo/, 'sudo -p \'knife sudo password: \'') + command.sub(/^sudo/, "sudo -p 'knife sudo password: '") end def print_data(host, data) @@ -311,19 +357,23 @@ class Chef subsession ||= session command = fixup_sudo(command) command.force_encoding("binary") if command.respond_to?(:force_encoding) - subsession.open_channel do |ch| - ch.request_pty - ch.exec command do |ch, success| - raise ArgumentError, "Cannot execute #{command}" unless success - ch.on_data do |ichannel, data| - print_data(ichannel[:host], data) - if data =~ /^knife sudo password: / - print_data(ichannel[:host], "\n") - ichannel.send_data("#{get_password}\n") + subsession.open_channel do |chan| + if config[:on_error] && exit_status != 0 + chan.close() + else + chan.request_pty + chan.exec command do |ch, success| + raise ArgumentError, "Cannot execute #{command}" unless success + ch.on_data do |ichannel, data| + print_data(ichannel.connection[:prefix], data) + if data =~ /^knife sudo password: / + print_data(ichannel.connection[:prefix], "\n") + ichannel.send_data("#{get_password}\n") + end + end + ch.on_request "exit-status" do |ichannel, data| + exit_status = [exit_status, data.read_long].max end - end - ch.on_request "exit-status" do |ichannel, data| - exit_status = [exit_status, data.read_long].max end end end @@ -434,7 +484,7 @@ class Chef end.join(" \\; ") end - tmux_name = "'knife ssh #{@name_args[0].tr(':', '=')}'" + tmux_name = "'knife ssh #{@name_args[0].tr(':.', '=-')}'" begin server = session.servers_for.first cmd = ["tmux new-session -d -s #{tmux_name}", @@ -530,12 +580,11 @@ class Chef end def configure_ssh_identity_file - # config[:identity_file] is DEPRECATED in favor of :ssh_identity_file - config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file] || config[:identity_file] || Chef::Config[:knife][:ssh_identity_file]) + config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file] || Chef::Config[:knife][:ssh_identity_file]) end - def extract_nested_value(data_structure, path_spec) - ui.presenter.extract_nested_value(data_structure, path_spec) + def configure_ssh_gateway_identity + config[:ssh_gateway_identity] = get_stripped_unfrozen_value(config[:ssh_gateway_identity] || Chef::Config[:knife][:ssh_gateway_identity]) end def run @@ -543,29 +592,32 @@ class Chef configure_user configure_password - configure_ssh_identity_file + @password = config[:ssh_password] if config[:ssh_password] + + # If a password was not given, check for SSH identity file. + if !@password + configure_ssh_identity_file + configure_ssh_gateway_identity + end + configure_gateway configure_session exit_status = - case @name_args[1] - when "interactive" - interactive - when "screen" - screen - when "tmux" - tmux - when "macterm" - macterm - when "cssh" - cssh - when "csshx" - Chef::Log.warn("knife ssh csshx will be deprecated in a future release") - Chef::Log.warn("please use knife ssh cssh instead") - cssh - else - ssh_command(@name_args[1..-1].join(" ")) - end + case @name_args[1] + when "interactive" + interactive + when "screen" + screen + when "tmux" + tmux + when "macterm" + macterm + when "cssh" + cssh + else + ssh_command(@name_args[1..-1].join(" ")) + end session.close if exit_status != 0 diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb index 0c672f322e..c864ef52ec 100644 --- a/lib/chef/knife/ssl_check.rb +++ b/lib/chef/knife/ssl_check.rb @@ -44,7 +44,7 @@ class Chef def uri @uri ||= begin - Chef::Log.debug("Checking SSL cert on #{given_uri}") + Chef::Log.trace("Checking SSL cert on #{given_uri}") URI.parse(given_uri) end end @@ -131,7 +131,7 @@ class Chef true rescue OpenSSL::SSL::SSLError => e ui.error "The SSL certificate of #{host} could not be verified" - Chef::Log.debug e.message + Chef::Log.trace e.message debug_invalid_cert false end @@ -141,7 +141,7 @@ class Chef true rescue OpenSSL::SSL::SSLError => e ui.error "The SSL cert is signed by a trusted authority but is not valid for the given hostname" - Chef::Log.debug(e) + Chef::Log.trace(e) debug_invalid_host false end @@ -257,7 +257,8 @@ ADVICE def trusted_certificates if configuration.trusted_certs_dir && Dir.exist?(configuration.trusted_certs_dir) - Dir.glob(File.join(configuration.trusted_certs_dir, "*.{crt,pem}")) + glob_dir = ChefConfig::PathHelper.escape_glob_dir(configuration.trusted_certs_dir) + Dir.glob(File.join(glob_dir, "*.{crt,pem}")) else [] end @@ -275,7 +276,7 @@ ADVICE rescue OpenSSL::X509::StoreError => e return e.message end - return nil + nil end end end diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb index 5af1a905d5..98c98d06ae 100644 --- a/lib/chef/knife/ssl_fetch.rb +++ b/lib/chef/knife/ssl_fetch.rb @@ -41,7 +41,7 @@ class Chef def uri @uri ||= begin - Chef::Log.debug("Checking SSL cert on #{given_uri}") + Chef::Log.trace("Checking SSL cert on #{given_uri}") URI.parse(given_uri) end end @@ -89,8 +89,11 @@ class Chef def cn_of(certificate) subject = certificate.subject - cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" } - cn_field_tuple[1] + if cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" } + cn_field_tuple[1] + else + nil + end end # Convert the CN of a certificate into something that will work well as a @@ -117,9 +120,10 @@ class Chef def write_cert(cert) FileUtils.mkdir_p(trusted_certs_dir) cn = cn_of(cert) - filename = File.join(trusted_certs_dir, "#{normalize_cn(cn)}.crt") - ui.msg("Adding certificate for #{cn} in #{filename}") - File.open(filename, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f| + filename = cn.nil? ? "#{host}_#{Time.new.to_i}" : normalize_cn(cn) + full_path = File.join(trusted_certs_dir, "#{filename}.crt") + ui.msg("Adding certificate for #{filename} in #{full_path}") + File.open(full_path, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f| f.print(cert.to_s) end end diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb index 551d5addfc..0e3cd7e7d6 100644 --- a/lib/chef/knife/status.rb +++ b/lib/chef/knife/status.rb @@ -96,13 +96,13 @@ class Chef all_nodes << node end - output(all_nodes.sort { |n1, n2| + output(all_nodes.sort do |n1, n2| if config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse] (n2["ohai_time"] || 0) <=> (n1["ohai_time"] || 0) else (n1["ohai_time"] || 0) <=> (n2["ohai_time"] || 0) end - }) + end) end end diff --git a/lib/chef/knife/supermarket_download.rb b/lib/chef/knife/supermarket_download.rb new file mode 100644 index 0000000000..5657558591 --- /dev/null +++ b/lib/chef/knife/supermarket_download.rb @@ -0,0 +1,33 @@ +# +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/knife/cookbook_site_download" + +class Chef + class Knife + class SupermarketDownload < Knife::CookbookSiteDownload + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) + + banner "knife supermarket download COOKBOOK [VERSION] (options)" + category "supermarket" + end + end +end diff --git a/lib/chef/knife/cookbook_site_vendor.rb b/lib/chef/knife/supermarket_install.rb index 291715cc0b..7642e68181 100644 --- a/lib/chef/knife/cookbook_site_vendor.rb +++ b/lib/chef/knife/supermarket_install.rb @@ -1,6 +1,6 @@ # -# Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,28 +19,15 @@ require "chef/knife" require "chef/knife/cookbook_site_install" -class Chef::Knife::CookbookSiteVendor < Chef::Knife::CookbookSiteInstall +class Chef + class Knife + class SupermarketInstall < Knife::CookbookSiteInstall + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) - def self.load_deps - superclass.load_deps + banner "knife supermarket install COOKBOOK [VERSION] (options)" + category "supermarket" + end end - - def self.options=(new_opts) - superclass.options = new_opts - end - - def self.options - superclass.options - end - - banner(<<-B) -************************************************* -DEPRECATED: please use knife cookbook site install -************************************************* - -#{superclass.banner} -B - - category "deprecated" - end diff --git a/lib/chef/knife/supermarket_list.rb b/lib/chef/knife/supermarket_list.rb new file mode 100644 index 0000000000..f2bc98bd0e --- /dev/null +++ b/lib/chef/knife/supermarket_list.rb @@ -0,0 +1,33 @@ +# +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/knife/cookbook_site_list" + +class Chef + class Knife + class SupermarketList < Knife::CookbookSiteList + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) + + banner "knife supermarket list (options)" + category "supermarket" + end + end +end diff --git a/lib/chef/knife/supermarket_search.rb b/lib/chef/knife/supermarket_search.rb new file mode 100644 index 0000000000..3206b0cb80 --- /dev/null +++ b/lib/chef/knife/supermarket_search.rb @@ -0,0 +1,33 @@ +# +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/knife/cookbook_site_search" + +class Chef + class Knife + class SupermarketSearch < Knife::CookbookSiteSearch + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) + + banner "knife supermarket search QUERY (options)" + category "supermarket" + end + end +end diff --git a/lib/chef/knife/supermarket_share.rb b/lib/chef/knife/supermarket_share.rb new file mode 100644 index 0000000000..3109b9e794 --- /dev/null +++ b/lib/chef/knife/supermarket_share.rb @@ -0,0 +1,33 @@ +# +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/knife/cookbook_site_share" + +class Chef + class Knife + class SupermarketShare < Knife::CookbookSiteShare + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) + + banner "knife supermarket share COOKBOOK [CATEGORY] (options)" + category "supermarket" + end + end +end diff --git a/lib/chef/knife/supermarket_show.rb b/lib/chef/knife/supermarket_show.rb new file mode 100644 index 0000000000..2ad122143f --- /dev/null +++ b/lib/chef/knife/supermarket_show.rb @@ -0,0 +1,33 @@ +# +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/knife/cookbook_site_show" + +class Chef + class Knife + class SupermarketShow < Knife::CookbookSiteShow + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) + + banner "knife supermarket show COOKBOOK [VERSION] (options)" + category "supermarket" + end + end +end diff --git a/lib/chef/knife/supermarket_unshare.rb b/lib/chef/knife/supermarket_unshare.rb new file mode 100644 index 0000000000..fd48e172ce --- /dev/null +++ b/lib/chef/knife/supermarket_unshare.rb @@ -0,0 +1,33 @@ +# +# Author:: Christopher Webber (<cwebber@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/knife/cookbook_site_unshare" + +class Chef + class Knife + class SupermarketUnshare < Knife::CookbookSiteUnshare + # Handle the subclassing (knife doesn't do this :() + dependency_loaders.concat(superclass.dependency_loaders) + options.merge!(superclass.options) + + banner "knife supermarket unshare COOKBOOK (options)" + category "supermarket" + end + end +end diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb index d21afb1059..c4a89f3707 100644 --- a/lib/chef/knife/user_create.rb +++ b/lib/chef/knife/user_create.rb @@ -78,6 +78,8 @@ knife user create for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user create. If you are using an Open Source 11 Server, please use that command to avoid this warning. +NOTE: Backwards compatibility for Open Source 11 Server in these commands will be removed +in Chef 15 which will be released April 2019. EOF end diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb index ce4575ceab..abfb45253e 100644 --- a/lib/chef/knife/user_delete.rb +++ b/lib/chef/knife/user_delete.rb @@ -37,6 +37,8 @@ knife user delete for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user delete. If you are using an Open Source 11 Server, please use that command to avoid this warning. +NOTE: Backwards compatibility for Open Source 11 Server in these commands will be removed +in Chef 15 which will be released April 2019. EOF end @@ -60,7 +62,7 @@ EOF end output(format_for_display(object)) if config[:print_after] - self.msg("Deleted #{user_name}") + msg("Deleted #{user_name}") end def run diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb index bb80ede267..d184b85a6c 100644 --- a/lib/chef/knife/user_edit.rb +++ b/lib/chef/knife/user_edit.rb @@ -37,6 +37,8 @@ knife user edit for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife oc_user namespace. For backwards compatibility, we will forward this request to knife osc_user edit. If you are using an Open Source 11 Server, please use that command to avoid this warning. +NOTE: Backwards compatibility for Open Source 11 Server in these commands will be removed +in Chef 15 which will be released April 2019. EOF end diff --git a/lib/chef/knife/user_key_create.rb b/lib/chef/knife/user_key_create.rb index 95a98a2f4f..1cceb886c4 100644 --- a/lib/chef/knife/user_key_create.rb +++ b/lib/chef/knife/user_key_create.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_create" require "chef/knife/key_create_base" class Chef diff --git a/lib/chef/knife/user_key_delete.rb b/lib/chef/knife/user_key_delete.rb index 1c559f2ef0..2a0c958ed9 100644 --- a/lib/chef/knife/user_key_delete.rb +++ b/lib/chef/knife/user_key_delete.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_delete" class Chef class Knife diff --git a/lib/chef/knife/user_key_edit.rb b/lib/chef/knife/user_key_edit.rb index 561af8edd0..ca056e3650 100644 --- a/lib/chef/knife/user_key_edit.rb +++ b/lib/chef/knife/user_key_edit.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_edit" require "chef/knife/key_edit_base" class Chef diff --git a/lib/chef/knife/user_key_list.rb b/lib/chef/knife/user_key_list.rb index 799c069182..10583f4a3c 100644 --- a/lib/chef/knife/user_key_list.rb +++ b/lib/chef/knife/user_key_list.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_list" require "chef/knife/key_list_base" class Chef diff --git a/lib/chef/knife/user_key_show.rb b/lib/chef/knife/user_key_show.rb index e09d5e04ed..b3a6ee73dc 100644 --- a/lib/chef/knife/user_key_show.rb +++ b/lib/chef/knife/user_key_show.rb @@ -17,6 +17,7 @@ # require "chef/knife" +require "chef/knife/key_show" class Chef class Knife diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb index 8d2f2c1e73..fec6792134 100644 --- a/lib/chef/knife/user_reregister.rb +++ b/lib/chef/knife/user_reregister.rb @@ -37,6 +37,8 @@ knife user reregister for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user reregister. If you are using an Open Source 11 Server, please use that command to avoid this warning. +NOTE: Backwards compatibility for Open Source 11 Server in these commands will be removed +in Chef 15 which will be released April 2019. EOF end @@ -73,7 +75,7 @@ EOF run_osc_11_user_reregister else # EC / CS 12 case user.reregister - Chef::Log.debug("Updated user data: #{user.inspect}") + Chef::Log.trace("Updated user data: #{user.inspect}") key = user.private_key if config[:file] File.open(config[:file], "w") do |f| diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb index 04251c0863..6ba4ab5016 100644 --- a/lib/chef/knife/user_show.rb +++ b/lib/chef/knife/user_show.rb @@ -39,6 +39,8 @@ knife user show for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user show. If you are using an Open Source 11 Server, please use that command to avoid this warning. +NOTE: Backwards compatibility for Open Source 11 Server in these commands will be removed +in Chef 15 which will be released April 2019. EOF end diff --git a/lib/chef/knife/xargs.rb b/lib/chef/knife/xargs.rb index 6559ca2e74..a316fb8cf7 100644 --- a/lib/chef/knife/xargs.rb +++ b/lib/chef/knife/xargs.rb @@ -9,7 +9,7 @@ class Chef deps do require "chef/chef_fs/file_system" - require "chef/chef_fs/file_system/not_found_error" + require "chef/chef_fs/file_system/exceptions" end # TODO modify to remote-only / local-only pattern (more like delete) @@ -181,7 +181,7 @@ class Chef def destroy_tempfiles(tempfiles) # Unlink the files now that we're done with them - tempfiles.keys.each { |tempfile| tempfile.close! } + tempfiles.each_key { |tempfile| tempfile.close! } end def xargs_files(command, tempfiles) |