lib/chef/knife/ec2_server_create.rb in knife-ec2-0.6.6 vs lib/chef/knife/ec2_server_create.rb in knife-ec2-0.8.0

- old
+ new

@@ -69,10 +69,14 @@ option :associate_eip, :long => "--associate-eip IP_ADDRESS", :description => "Associate existing elastic IP address with instance after launch" + option :dedicated_instance, + :long => "--dedicated_instance", + :description => "Launch as a Dedicated instance (VPC ONLY)" + option :placement_group, :long => "--placement-group PLACEMENT_GROUP", :description => "The placement group to place a cluster compute instance", :proc => Proc.new { |pg| Chef::Config[:knife][:placement_group] = pg } @@ -135,15 +139,15 @@ 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 } + :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 is 'chef-full'", @@ -210,11 +214,11 @@ option :bootstrap_protocol, :long => "--bootstrap-protocol protocol", :description => "protocol to bootstrap windows servers. options: winrm/ssh", :proc => Proc.new { |key| Chef::Config[:knife][:bootstrap_protocol] = key }, - :default => "winrm" + :default => nil option :fqdn, :long => "--fqdn FQDN", :description => "Pre-defined FQDN", :proc => Proc.new { |key| Chef::Config[:knife][:fqdn] = key }, @@ -243,106 +247,19 @@ :default => [] option :server_connect_attribute, :long => "--server-connect-attribute ATTRIBUTE", :short => "-a ATTRIBUTE", - :description => "The EC2 server attribute to use for SSH connection", + :description => "The EC2 server attribute to use for SSH connection. Use this attr for creating VPC instances along with --associate-eip", :default => nil - def tcp_test_winrm(ip_addr, port) - tcp_socket = TCPSocket.new(ip_addr, port) - yield - true - rescue SocketError - sleep 2 - false - rescue Errno::ETIMEDOUT - false - rescue Errno::EPERM - false - rescue Errno::ECONNREFUSED - sleep 2 - false - rescue Errno::EHOSTUNREACH - sleep 2 - false - rescue Errno::ENETUNREACH - sleep 2 - false - ensure - tcp_socket && tcp_socket.close - end + option :associate_public_ip, + :long => "--associate-public-ip", + :description => "Associate public ip to VPC instance.", + :boolean => true, + :default => false - def tcp_test_ssh(hostname, ssh_port) - tcp_socket = TCPSocket.new(hostname, ssh_port) - readable = IO.select([tcp_socket], nil, nil, 5) - if readable - Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}") - yield - true - else - false - end - rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError - sleep 2 - false - rescue Errno::EPERM, Errno::ETIMEDOUT - false - # This happens on some mobile phone networks - rescue Errno::ECONNRESET - sleep 2 - false - ensure - tcp_socket && tcp_socket.close - end - - def decrypt_admin_password(encoded_password, key) - require 'base64' - require 'openssl' - private_key = OpenSSL::PKey::RSA.new(key) - encrypted_password = Base64.decode64(encoded_password) - password = private_key.private_decrypt(encrypted_password) - password - end - - def check_windows_password_available(server_id) - response = connection.get_password_data(server_id) - if not response.body["passwordData"] - return false - end - response.body["passwordData"] - end - - def windows_password - if not locate_config_value(:winrm_password) - if locate_config_value(:identity_file) - print "\n#{ui.color("Waiting for Windows Admin password to be available", :magenta)}" - print(".") until check_windows_password_available(@server.id) { - sleep 1000 #typically is available after 30 mins - puts("done") - } - response = connection.get_password_data(@server.id) - data = File.read(locate_config_value(:identity_file)) - config[:winrm_password] = decrypt_admin_password(response.body["passwordData"], data) - else - ui.error("Cannot find SSH Identity file, required to fetch dynamically generated password") - exit 1 - end - else - locate_config_value(:winrm_password) - end - end - - def load_winrm_deps - require 'winrm' - require 'em-winrm' - require 'chef/knife/winrm' - require 'chef/knife/bootstrap_windows_winrm' - require 'chef/knife/bootstrap_windows_ssh' - require 'chef/knife/core/windows_bootstrap_context' - end - def run $stdout.sync = true validate! @@ -396,33 +313,37 @@ # occasionally 'ready?' isn't, so retry a couple times if needed. tries = 6 begin create_tags(hashed_tags) unless hashed_tags.empty? associate_eip(elastic_ip) if config[:associate_eip] - rescue Fog::Compute::AWS::NotFound, Fog::Errors::Error => e + rescue Fog::Compute::AWS::NotFound, Fog::Errors::Error raise if (tries -= 1) <= 0 ui.warn("server not ready, retrying tag application (retries left: #{tries})") sleep 5 retry end if vpc_mode? msg_pair("Subnet ID", @server.subnet_id) + msg_pair("Tenancy", @server.tenancy) + if config[:associate_public_ip] + msg_pair("Public DNS Name", @server.dns_name) + end if elastic_ip msg_pair("Public IP Address", @server.public_ip_address) end else msg_pair("Public DNS Name", @server.dns_name) msg_pair("Public IP Address", @server.public_ip_address) msg_pair("Private DNS Name", @server.private_dns_name) end msg_pair("Private IP Address", @server.private_ip_address) - #Check if Server is Windows or Linux if is_image_windows? protocol = locate_config_value(:bootstrap_protocol) + protocol ||= 'winrm' # Set distro to windows-chef-client-msi config[:distro] = "windows-chef-client-msi" if (config[:distro].nil? || config[:distro] == "chef-full") if protocol == 'winrm' load_winrm_deps print "\n#{ui.color("Waiting for winrm", :magenta)}" @@ -437,15 +358,16 @@ sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10) puts("done") } ssh_override_winrm end - bootstrap_for_windows_node(@server,ssh_connect_host).run + bootstrap_for_windows_node(@server, ssh_connect_host).run else - wait_for_sshd(ssh_connect_host) - ssh_override_winrm - bootstrap_for_linux_node(@server,ssh_connect_host).run + print "\n#{ui.color("Waiting for sshd", :magenta)}" + wait_for_sshd(ssh_connect_host) + ssh_override_winrm + bootstrap_for_linux_node(@server, ssh_connect_host).run end puts "\n" msg_pair("Instance ID", @server.id) msg_pair("Flavor", @server.flavor_id) @@ -478,10 +400,14 @@ if config[:ebs_optimized] msg_pair("EBS is Optimized", @server.ebs_optimized.to_s) end if vpc_mode? msg_pair("Subnet ID", @server.subnet_id) + msg_pair("Tenancy", @server.tenancy) + if config[:associate_public_ip] + msg_pair("Public DNS Name", @server.dns_name) + end else msg_pair("Public DNS Name", @server.dns_name) msg_pair("Public IP Address", @server.public_ip_address) msg_pair("Private DNS Name", @server.private_dns_name) end @@ -509,40 +435,39 @@ Chef::Config[:knife][:hints]["ec2"] ||= {} bootstrap end def fetch_server_fqdn(ip_addr) - require 'resolv' - Resolv.getname(ip_addr) + require 'resolv' + Resolv.getname(ip_addr) end def bootstrap_for_windows_node(server, fqdn) - if locate_config_value(:bootstrap_protocol) == 'winrm' - if locate_config_value(:kerberos_realm) - #Fetch AD/WINS based fqdn if any for Kerberos-based Auth - fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(server.private_ip_address) - end - bootstrap = Chef::Knife::BootstrapWindowsWinrm.new - bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) - bootstrap.config[:winrm_password] = windows_password - bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport) - bootstrap.config[:kerberos_keytab_file] = locate_config_value(:kerberos_keytab_file) - bootstrap.config[:kerberos_realm] = locate_config_value(:kerberos_realm) - bootstrap.config[:kerberos_service] = locate_config_value(:kerberos_service) - bootstrap.config[:ca_trust_file] = locate_config_value(:ca_trust_file) - bootstrap.config[:winrm_port] = locate_config_value(:winrm_port) - + if locate_config_value(:bootstrap_protocol) == 'winrm' || locate_config_value(:bootstrap_protocol) == nil + if locate_config_value(:kerberos_realm) + #Fetch AD/WINS based fqdn if any for Kerberos-based Auth + fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(server.private_ip_address) + end + bootstrap = Chef::Knife::BootstrapWindowsWinrm.new + bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) + bootstrap.config[:winrm_password] = windows_password + bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport) + bootstrap.config[:kerberos_keytab_file] = locate_config_value(:kerberos_keytab_file) + bootstrap.config[:kerberos_realm] = locate_config_value(:kerberos_realm) + bootstrap.config[:kerberos_service] = locate_config_value(:kerberos_service) + bootstrap.config[:ca_trust_file] = locate_config_value(:ca_trust_file) + bootstrap.config[:winrm_port] = locate_config_value(:winrm_port) elsif locate_config_value(:bootstrap_protocol) == 'ssh' - bootstrap = Chef::Knife::BootstrapWindowsSsh.new - bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) - bootstrap.config[:ssh_password] = locate_config_value(:ssh_password) - bootstrap.config[:ssh_port] = locate_config_value(:ssh_port) - bootstrap.config[:identity_file] = locate_config_value(:identity_file) - bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify) + bootstrap = Chef::Knife::BootstrapWindowsSsh.new + bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) + bootstrap.config[:ssh_password] = locate_config_value(:ssh_password) + bootstrap.config[:ssh_port] = locate_config_value(:ssh_port) + bootstrap.config[:identity_file] = locate_config_value(:identity_file) + bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify) else - ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh") - exit 1 + ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh") + exit 1 end bootstrap.name_args = [fqdn] bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.id bootstrap_common_params(bootstrap) end @@ -570,11 +495,10 @@ def ami @ami ||= connection.images.get(locate_config_value(:image)) end def validate! - super([:image, :aws_ssh_key_id, :aws_access_key_id, :aws_secret_access_key]) if ami.nil? ui.error("You have not provided a valid image (AMI) value. Please note the short option for this value recently changed from '-i' to '-I'.") exit 1 @@ -582,15 +506,26 @@ if vpc_mode? and !!config[:security_groups] ui.error("You are using a VPC, security groups specified with '-G' are not allowed, specify one or more security group ids with '-g' instead.") exit 1 end + if !vpc_mode? and !!config[:private_ip_address] ui.error("You can only specify a private IP address if you are using VPC.") exit 1 end + if config[:dedicated_instance] and !vpc_mode? + ui.error("You can only specify a Dedicated Instance if you are using VPC.") + exit 1 + end + + if !vpc_mode? and config[:associate_public_ip] + ui.error("--associate-public-ip option only applies to VPC instances, and you have not specified a subnet id.") + exit 1 + end + if config[:associate_eip] eips = connection.addresses.collect{|addr| addr if addr.domain == eip_scope}.compact unless eips.detect{|addr| addr.public_ip == config[:associate_eip] && addr.server_id == nil} ui.error("Elastic IP requested is not available.") @@ -627,10 +562,12 @@ } server_def[:subnet_id] = locate_config_value(:subnet_id) if vpc_mode? server_def[:private_ip_address] = locate_config_value(:private_ip_address) if vpc_mode? server_def[:placement_group] = locate_config_value(:placement_group) server_def[:iam_instance_profile_name] = locate_config_value(:iam_instance_profile) + server_def[:tenancy] = "dedicated" if vpc_mode? and locate_config_value(:dedicated_instance) + server_def[:associate_public_ip] = locate_config_value(:associate_public_ip) if vpc_mode? and config[:associate_public_ip] if Chef::Config[:knife][:aws_user_data] begin server_def.merge!(:user_data => File.read(Chef::Config[:knife][:aws_user_data])) rescue @@ -681,13 +618,18 @@ def wait_for_sshd(hostname) config[:ssh_gateway] ? wait_for_tunnelled_sshd(hostname) : wait_for_direct_sshd(hostname, config[:ssh_port]) end def wait_for_tunnelled_sshd(hostname) - print(".") - print(".") until tunnel_test_ssh(ssh_connect_host) { - sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10) + initial = true + print(".") until tunnel_test_ssh(hostname) { + if initial + initial = false + sleep (vpc_mode? ? 40 : 10) + else + sleep 10 + end puts("done") } end def tunnel_test_ssh(hostname, &block) @@ -705,22 +647,32 @@ rescue Errno::EPERM, Errno::ETIMEDOUT false end def wait_for_direct_sshd(hostname, ssh_port) - print(".") until tcp_test_ssh(ssh_connect_host, ssh_port) { - sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10) + initial = true + print(".") until tcp_test_ssh(hostname, ssh_port) { + if initial + initial = false + sleep (vpc_mode? ? 40 : 10) + else + sleep 10 + end puts("done") } end def ssh_connect_host @ssh_connect_host ||= if config[:server_connect_attribute] - server.send(config[:server_connect_attribute]) - else - vpc_mode? ? server.private_ip_address : server.dns_name - end + server.send(config[:server_connect_attribute]) + else + if vpc_mode? && !config[:associate_public_ip] + server.private_ip_address + else + server.dns_name + end + end end def create_tags(hashed_tags) hashed_tags.each_pair do |key,val| connection.tags.create :key => key, :value => val, :resource_id => @server.id @@ -751,9 +703,110 @@ # unset identity_file and set kerberos_keytab_file, override identity_file if locate_config_value(:identity_file).nil? && !locate_config_value(:kerberos_keytab_file).nil? config[:identity_file] = locate_config_value(:kerberos_keytab_file) end + end + + def tcp_test_winrm(ip_addr, port) + tcp_socket = TCPSocket.new(ip_addr, port) + yield + true + rescue SocketError + sleep 2 + false + rescue Errno::ETIMEDOUT + false + rescue Errno::EPERM + false + rescue Errno::ECONNREFUSED + sleep 2 + false + rescue Errno::EHOSTUNREACH + sleep 2 + false + rescue Errno::ENETUNREACH + sleep 2 + false + ensure + tcp_socket && tcp_socket.close + end + + def tcp_test_ssh(hostname, ssh_port) + tcp_socket = TCPSocket.new(hostname, ssh_port) + readable = IO.select([tcp_socket], nil, nil, 5) + if readable + ssh_banner = tcp_socket.gets + if ssh_banner.nil? or ssh_banner.empty? + false + else + Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{ssh_banner}") + yield + true + end + else + false + end + rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError + Chef::Log.debug("ssh failed to connect: #{hostname}") + sleep 2 + false + rescue Errno::EPERM, Errno::ETIMEDOUT + Chef::Log.debug("ssh timed out: #{hostname}") + false + # This happens on some mobile phone networks + rescue Errno::ECONNRESET + Chef::Log.debug("ssh reset its connection: #{hostname}") + sleep 2 + false + ensure + tcp_socket && tcp_socket.close + end + + def decrypt_admin_password(encoded_password, key) + require 'base64' + require 'openssl' + private_key = OpenSSL::PKey::RSA.new(key) + encrypted_password = Base64.decode64(encoded_password) + password = private_key.private_decrypt(encrypted_password) + password + end + + def check_windows_password_available(server_id) + response = connection.get_password_data(server_id) + if not response.body["passwordData"] + return false + end + response.body["passwordData"] + end + + def windows_password + if not locate_config_value(:winrm_password) + if locate_config_value(:identity_file) + print "\n#{ui.color("Waiting for Windows Admin password to be available", :magenta)}" + print(".") until check_windows_password_available(@server.id) { + sleep 1000 #typically is available after 30 mins + puts("done") + } + response = connection.get_password_data(@server.id) + data = File.read(locate_config_value(:identity_file)) + config[:winrm_password] = decrypt_admin_password(response.body["passwordData"], data) + else + ui.error("Cannot find SSH Identity file, required to fetch dynamically generated password") + exit 1 + end + else + locate_config_value(:winrm_password) + end + end + + def load_winrm_deps + require 'winrm' + require 'em-winrm' + require 'chef/knife/winrm' + require 'chef/knife/bootstrap_windows_winrm' + require 'chef/knife/bootstrap_windows_ssh' + require 'chef/knife/core/windows_bootstrap_context' end end end end