diff options
Diffstat (limited to 'lib/chef/knife/bootstrap.rb')
-rw-r--r-- | lib/chef/knife/bootstrap.rb | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb new file mode 100644 index 0000000000..a8e9201c26 --- /dev/null +++ b/lib/chef/knife/bootstrap.rb @@ -0,0 +1,234 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2010 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 'erubis' + +class Chef + class Knife + class Bootstrap < Knife + + deps do + require 'chef/knife/core/bootstrap_context' + require 'chef/json_compat' + require 'tempfile' + require 'highline' + require 'net/ssh' + require 'net/ssh/multi' + require 'chef/knife/ssh' + Chef::Knife::Ssh.load_deps + end + + banner "knife bootstrap FQDN (options)" + + option :ssh_user, + :short => "-x USERNAME", + :long => "--ssh-user USERNAME", + :description => "The ssh username", + :default => "root" + + option :ssh_password, + :short => "-P PASSWORD", + :long => "--ssh-password PASSWORD", + :description => "The ssh password" + + option :ssh_port, + :short => "-p PORT", + :long => "--ssh-port PORT", + :description => "The ssh port", + :default => "22", + :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key } + + option :ssh_gateway, + :short => "-G GATEWAY", + :long => "--ssh-gateway GATEWAY", + :description => "The ssh gateway", + :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key } + + option :identity_file, + :short => "-i IDENTITY_FILE", + :long => "--identity-file IDENTITY_FILE", + :description => "The SSH identity file used for authentication" + + 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 => lambda { |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 :distro, + :short => "-d DISTRO", + :long => "--distro DISTRO", + :description => "Bootstrap a distro using a template", + :default => "chef-full" + + option :use_sudo, + :long => "--sudo", + :description => "Execute the bootstrap via sudo", + :boolean => true + + 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(/[\s,]+/) }, + :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 :host_key_verify, + :long => "--[no-]host-key-verify", + :description => "Verify host key, enabled by default.", + :boolean => true, + :default => true + + option :hint, + :long => "--hint HINT_NAME[=HINT_FILE]", + :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.", + :proc => Proc.new { |h| + Chef::Config[:knife][:hints] ||= Hash.new + name, path = h.split("=") + Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new } + + def find_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(Knife.chef_config_dir, "bootstrap", "#{config[:distro]}.erb") if Knife.chef_config_dir + bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{config[:distro]}.erb") if ENV['HOME'] + 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)}") + + template + end + + def render_template(template=nil) + context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config) + Erubis::Eruby.new(template).evaluate(context) + end + + def read_template + IO.read(@template_file).chomp + end + + def run + validate_name_args! + @template_file = find_template(config[:bootstrap_template]) + @node_name = Array(@name_args).first + # back compat--templates may use this setting: + config[:server_name] = @node_name + + $stdout.sync = true + + ui.info("Bootstrapping Chef on #{ui.color(@node_name, :bold)}") + + begin + knife_ssh.run + rescue Net::SSH::AuthenticationFailed + unless config[:ssh_password] + ui.info("Failed to authenticate #{config[:ssh_user]} - trying password auth") + knife_ssh_with_password_auth.run + end + end + end + + def validate_name_args! + if Array(@name_args).first.nil? + ui.error("Must pass an FQDN or ip to bootstrap") + exit 1 + end + end + + def server_name + Array(@name_args).first + end + + def knife_ssh + ssh = Chef::Knife::Ssh.new + ssh.ui = ui + ssh.name_args = [ server_name, ssh_command ] + ssh.config[:ssh_user] = config[:ssh_user] + ssh.config[:ssh_password] = config[:ssh_password] + ssh.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port] + ssh.config[:ssh_gateway] = Chef::Config[:knife][:ssh_gateway] || config[:ssh_gateway] + ssh.config[:identity_file] = config[:identity_file] + ssh.config[:manual] = true + ssh.config[:host_key_verify] = config[:host_key_verify] + ssh.config[:on_error] = :raise + ssh + end + + def knife_ssh_with_password_auth + ssh = knife_ssh + ssh.config[:identity_file] = nil + ssh.config[:ssh_password] = ssh.get_password + ssh + end + + def ssh_command + command = render_template(read_template) + + if config[:use_sudo] + command = "sudo #{command}" + end + + command + end + + end + end +end |