summaryrefslogtreecommitdiff
path: root/lib/chef/knife/bootstrap.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/knife/bootstrap.rb')
-rw-r--r--lib/chef/knife/bootstrap.rb234
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