lib/chef/knife/bootstrap_windows_base.rb in knife-windows-0.5.15 vs lib/chef/knife/bootstrap_windows_base.rb in knife-windows-0.6.0

- old
+ new

@@ -1,196 +1,206 @@ -# -# Author:: Seth Chisamore (<schisamo@opscode.com>) -# Copyright:: Copyright (c) 2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'chef/knife' -require 'chef/encrypted_data_bag_item' -require 'chef/knife/core/windows_bootstrap_context' - -class Chef - class Knife - module BootstrapWindowsBase - - # :nodoc: - # Would prefer to do this in a rational way, but can't be done b/c of - # Mixlib::CLI's design :( - def self.included(includer) - includer.class_eval do - - deps do - require 'readline' - require 'chef/json_compat' - end - - option :chef_node_name, - :short => "-N NAME", - :long => "--node-name NAME", - :description => "The Chef node name for your new node" - - option :prerelease, - :long => "--prerelease", - :description => "Install the pre-release chef gems" - - option :bootstrap_version, - :long => "--bootstrap-version VERSION", - :description => "The version of Chef to install", - :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v } - - option :bootstrap_proxy, - :long => "--bootstrap-proxy PROXY_URL", - :description => "The proxy server for the node being bootstrapped", - :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p } - - option :bootstrap_no_proxy, - :long => "--bootstrap-no-proxy ", - :description => "Avoid a proxy server for the given addresses", - :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np } - - option :distro, - :short => "-d DISTRO", - :long => "--distro DISTRO", - :description => "Bootstrap a distro using a template", - :default => "windows-chef-client-msi" - - option :template_file, - :long => "--template-file TEMPLATE", - :description => "Full path to location of template to use", - :default => false - - option :run_list, - :short => "-r RUN_LIST", - :long => "--run-list RUN_LIST", - :description => "Comma separated list of roles/recipes to apply", - :proc => lambda { |o| o.split(",") }, - :default => [] - - option :first_boot_attributes, - :short => "-j JSON_ATTRIBS", - :long => "--json-attributes", - :description => "A JSON string to be added to the first run of chef-client", - :proc => lambda { |o| JSON.parse(o) }, - :default => {} - - option :encrypted_data_bag_secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to decrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config.", - :default => false - - option :encrypted_data_bag_secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config." - - end - end - - # TODO: This should go away when CHEF-2193 is fixed - def load_template(template=nil) - # Are we bootstrapping using an already shipped template? - if config[:template_file] - bootstrap_files = config[:template_file] - else - bootstrap_files = [] - bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap', "#{config[:distro]}.erb") - bootstrap_files << File.join(Dir.pwd, ".chef", "bootstrap", "#{config[:distro]}.erb") - bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{config[:distro]}.erb") - bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{config[:distro]}.erb")) - bootstrap_files.flatten! - end - - template = Array(bootstrap_files).find do |bootstrap_template| - Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}") - ::File.exists?(bootstrap_template) - end - - unless template - ui.info("Can not find bootstrap definition for #{config[:distro]}") - raise Errno::ENOENT - end - - Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}") - - IO.read(template).chomp - end - - def render_template(template=nil) - if config[:encrypted_data_bag_secret_file] - config[:encrypted_data_bag_secret] = Chef::EncryptedDataBagItem.load_secret(config[:encrypted_data_bag_secret_file]) - end - context = Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config) - Erubis::Eruby.new(template).evaluate(context) - end - - def bootstrap(proto=nil) - - validate_name_args! - - @node_name = Array(@name_args).first - # back compat--templates may use this setting: - config[:server_name] = @node_name - - STDOUT.sync = STDERR.sync = true - - ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}") - # create a bootstrap.bat file on the node - # we have to run the remote commands in 2047 char chunks - create_bootstrap_bat_command do |command_chunk, chunk_num| - begin - render_command_result = run_command(%Q!cmd.exe /C echo "Rendering #{bootstrap_bat_file} chunk #{chunk_num}" && #{command_chunk}!) - ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 - render_command_result - rescue SystemExit => e - raise unless e.success? - end - end - - # execute the bootstrap.bat file - bootstrap_command_result = run_command(bootstrap_command) - ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0 - bootstrap_command_result - end - - def bootstrap_command - @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" - end - - def create_bootstrap_bat_command(&block) - bootstrap_bat = [] - chunk_num = 0 - render_template(load_template(config[:bootstrap_template])).each_line do |line| - # escape WIN BATCH special chars - line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"} - # windows commands are limited to 2047 characters - if((bootstrap_bat + [line]).join(" && ").size > 2047 ) - yield bootstrap_bat.join(" && "), chunk_num += 1 - bootstrap_bat = [] - end - bootstrap_bat << ">> #{bootstrap_bat_file} (echo.#{line.chomp.strip})" - end - yield bootstrap_bat.join(" && "), chunk_num += 1 - end - - def bootstrap_bat_file - @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" - end - - def locate_config_value(key) - key = key.to_sym - Chef::Config[:knife][key] || config[key] - end - end - end -end +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/knife' +require 'chef/encrypted_data_bag_item' +require 'chef/knife/core/windows_bootstrap_context' + +class Chef + class Knife + module BootstrapWindowsBase + + # :nodoc: + # Would prefer to do this in a rational way, but can't be done b/c of + # Mixlib::CLI's design :( + def self.included(includer) + includer.class_eval do + + deps do + require 'readline' + require 'chef/json_compat' + end + + option :chef_node_name, + :short => "-N NAME", + :long => "--node-name NAME", + :description => "The Chef node name for your new node" + + option :prerelease, + :long => "--prerelease", + :description => "Install the pre-release chef gems" + + option :bootstrap_version, + :long => "--bootstrap-version VERSION", + :description => "The version of Chef to install", + :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v } + + option :bootstrap_proxy, + :long => "--bootstrap-proxy PROXY_URL", + :description => "The proxy server for the node being bootstrapped", + :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p } + + option :bootstrap_no_proxy, + :long => "--bootstrap-no-proxy ", + :description => "Avoid a proxy server for the given addresses", + :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np } + + option :distro, + :short => "-d DISTRO", + :long => "--distro DISTRO", + :description => "Bootstrap a distro using a template", + :default => "windows-chef-client-msi" + + option :template_file, + :long => "--template-file TEMPLATE", + :description => "Full path to location of template to use", + :default => false + + option :run_list, + :short => "-r RUN_LIST", + :long => "--run-list RUN_LIST", + :description => "Comma separated list of roles/recipes to apply", + :proc => lambda { |o| o.split(",") }, + :default => [] + + option :first_boot_attributes, + :short => "-j JSON_ATTRIBS", + :long => "--json-attributes", + :description => "A JSON string to be added to the first run of chef-client", + :proc => lambda { |o| JSON.parse(o) }, + :default => {} + + option :encrypted_data_bag_secret, + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to decrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config.", + :default => false + + option :encrypted_data_bag_secret_file, + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config." + + option :auth_timeout, + :long => "--auth-timeout MINUTES", + :description => "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 25 minutes.", + :default => 25 + end + end + + # TODO: This should go away when CHEF-2193 is fixed + def load_template(template=nil) + # Are we bootstrapping using an already shipped template? + if config[:template_file] + bootstrap_files = config[:template_file] + else + bootstrap_files = [] + bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap', "#{config[:distro]}.erb") + bootstrap_files << File.join(Dir.pwd, ".chef", "bootstrap", "#{config[:distro]}.erb") + bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{config[:distro]}.erb") + bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{config[:distro]}.erb")) + bootstrap_files.flatten! + end + + template = Array(bootstrap_files).find do |bootstrap_template| + Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}") + ::File.exists?(bootstrap_template) + end + + unless template + ui.info("Can not find bootstrap definition for #{config[:distro]}") + raise Errno::ENOENT + end + + Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}") + + IO.read(template).chomp + end + + def render_template(template=nil) + if config[:encrypted_data_bag_secret_file] + config[:encrypted_data_bag_secret] = Chef::EncryptedDataBagItem.load_secret(config[:encrypted_data_bag_secret_file]) + end + context = Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config) + Erubis::Eruby.new(template).evaluate(context) + end + + def bootstrap(proto=nil) + validate_name_args! + + @node_name = Array(@name_args).first + # back compat--templates may use this setting: + config[:server_name] = @node_name + + STDOUT.sync = STDERR.sync = true + + wait_for_remote_response( config[:auth_timeout].to_i ) + ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}") + # create a bootstrap.bat file on the node + # we have to run the remote commands in 2047 char chunks + create_bootstrap_bat_command do |command_chunk, chunk_num| + begin + render_command_result = run_command(%Q!cmd.exe /C echo "Rendering #{bootstrap_bat_file} chunk #{chunk_num}" && #{command_chunk}!) + ui.error("Batch render command returned #{render_command_result}") if render_command_result != 0 + render_command_result + rescue SystemExit => e + raise unless e.success? + end + end + + # execute the bootstrap.bat file + bootstrap_command_result = run_command(bootstrap_command) + ui.error("Bootstrap command returned #{bootstrap_command_result}") if bootstrap_command_result != 0 + bootstrap_command_result + end + + protected + + # Default implementation -- override only if required by the transport + def wait_for_remote_response(wait_max_minutes) + end + + def bootstrap_command + @bootstrap_command ||= "cmd.exe /C #{bootstrap_bat_file}" + end + + def create_bootstrap_bat_command(&block) + bootstrap_bat = [] + chunk_num = 0 + render_template(load_template(config[:bootstrap_template])).each_line do |line| + # escape WIN BATCH special chars + line.gsub!(/[(<|>)^]/).each{|m| "^#{m}"} + # windows commands are limited to 2047 characters + if((bootstrap_bat + [line]).join(" && ").size > 2047 ) + yield bootstrap_bat.join(" && "), chunk_num += 1 + bootstrap_bat = [] + end + bootstrap_bat << ">> #{bootstrap_bat_file} (echo.#{line.chomp.strip})" + end + yield bootstrap_bat.join(" && "), chunk_num += 1 + end + + def bootstrap_bat_file + @bootstrap_bat_file ||= "\"%TEMP%\\bootstrap-#{Process.pid}-#{Time.now.to_i}.bat\"" + end + + def locate_config_value(key) + key = key.to_sym + Chef::Config[:knife][key] || config[key] + end + end + end +end