class Chef::Knife::Ec2ServerCreate
Attributes
Public Instance Methods
Set default port 5986 if winrm_ssl return true otherwise set it to 5985 Set default port 22 for ssh protocol @return [Integer]
# File lib/chef/knife/ec2_server_create.rb, line 1383 def assign_default_port if winrm? config[:winrm_ssl] ? 5986 : 5985 else 22 end end
# File lib/chef/knife/ec2_server_create.rb, line 1193 def associate_address(elastic_ip) if vpc_mode? ec2_connection.associate_address({ allocation_id: elastic_ip.allocation_id, instance_id: server.id, }) else ec2_connection.associate_address({ public_ip: elastic_ip.public_ip, instance_id: server.id, }) end end
# File lib/chef/knife/ec2_server_create.rb, line 1240 def attach_nics attachments = [] config[:network_interfaces].each_with_index do |nic_id, index| attachments << ec2_connection.attach_network_interface({ device_index: index + 1, instance_id: server.id, network_interface_id: nic_id, }) end wait_for_nic_attachment attachments end
# File lib/chef/knife/ec2_server_create.rb, line 537 def bootstrap_common_params config[:encrypted_data_bag_secret] = s3_secret || config[:secret] config[:encrypted_data_bag_secret_file] = config[:secret_file] # retrieving the secret from S3 is unique to knife-ec2, so we need to set "command line secret" to the value fetched from S3 # When linux vm is spawned, the chef's secret option proc function sets the value "command line secret" and this value is used by # chef's code to check if secret option is passed through command line or not config[:cl_secret] = s3_secret if config[:s3_secret] config[:secret] = s3_secret || config[:secret] # Modify global configuration state to ensure hint gets set by # knife-bootstrap config[:hints] ||= {} config[:hints]["ec2"] ||= {} end
# File lib/chef/knife/ec2_server_create.rb, line 1337 def check_windows_password_available(server_id) sleep 10 response = fetch_password_data(server_id) return false unless response.password_data true end
# File lib/chef/knife/ec2_server_create.rb, line 1079 def configure_ssh_gateway(ssh_gateway) gw_host, gw_user = ssh_gateway.split("@").reverse gw_host, gw_port = gw_host.split(":") gateway_options = { port: gw_port || 22 } # Load the SSH config for the SSH gateway host. # Set the gateway user if it was not part of the # SSH gateway string, and use any configured # SSH keys. ssh_gateway_config = Net::SSH::Config.for(gw_host) gw_user ||= ssh_gateway_config[:user] # Always use the gateway keys from the SSH Config gateway_keys = ssh_gateway_config[:keys] # Use the keys specificed on the command line if available (overrides SSH Config) if config[:ssh_gateway_identity] gateway_keys = Array(config[:ssh_gateway_identity]) end unless gateway_keys.nil? gateway_options[:keys] = gateway_keys end Net::SSH::Gateway.new(gw_host, gw_user, gateway_options) end
Identify connection attribute if: Option –server-connect-attribute is set. For VPC mode check if public IP or elastic IP has been requested. Otherwise assign public DNS or public IP. @return [String]
# File lib/chef/knife/ec2_server_create.rb, line 1141 def connect_attribute @connect_attribute ||= begin if !!config[:server_connect_attribute] config[:server_connect_attribute] elsif vpc_mode? && !(subnet_public_ip_on_launch? || config[:associate_public_ip] || config[:associate_eip]) "private_ip_address" else server.public_dns_name ? "public_dns_name" : "public_ip_address" end end end
Assign connection host based on attribute passed @return [String]
# File lib/chef/knife/ec2_server_create.rb, line 1129 def connection_host @connection_host ||= server.send(connect_attribute) puts "\nSSH Target Address: #{@connection_host}(#{connect_attribute})" @connection_host end
TODO: connection_protocol and connection_port
used to choose winrm/ssh or 5985/22 based on the image chosen
# File lib/chef/knife/ec2_server_create.rb, line 1373 def connection_port port = config[:connection_port] || config[knife_key_for_protocol(:port)] return port if port assign_default_port end
url values override CLI flags, if you provide both we'll use the one that you gave in the URL.
# File lib/chef/knife/ec2_server_create.rb, line 1393 def connection_protocol_ec2 return @connection_protocol_ec2 if @connection_protocol_ec2 default_protocol = is_image_windows? ? "winrm" : "ssh" from_url = host_descriptor =~ %r{^(.*)://} ? $1 : nil from_config = config[:connection_protocol] @connection_protocol_ec2 = from_url || from_config || default_protocol end
# File lib/chef/knife/ec2_server_create.rb, line 1402 def connection_user @connection_user ||= config[:connection_user] || config[knife_key_for_protocol(:user)] end
# File lib/chef/knife/ec2_server_create.rb, line 985 def create_ec2_instance(attributes) ec2_connection.run_instances(attributes) end
# File lib/chef/knife/ec2_server_create.rb, line 1175 def create_key_pair key_name = "#{config[:connection_user].delete(".\\")}-#{SecureRandom.hex(10)}" key_pair = ec2_connection.create_key_pair({ key_name: key_name, }) save_keypair_file(key_pair) if key_pair end
# File lib/chef/knife/ec2_server_create.rb, line 1329 def decrypt_admin_password(encoded_password, key) require "base64" unless defined?(Base64) require "openssl" unless defined?(OpenSSL) private_key = OpenSSL::PKey::RSA.new(key) encrypted_password = Base64.decode64(encoded_password) private_key.private_decrypt(encrypted_password) end
return the default bootstrap template based on platform @return [String]
# File lib/chef/knife/ec2_server_create.rb, line 493 def default_bootstrap_template is_image_windows? ? "windows-chef-client-msi" : "chef-full" end
disable_source_dest_check
option is used to set value of source_dest_check attribute in ec2. By default the source destination check is enabled in ec2. This value must be disable for a NAT instance to perform NAT.
# File lib/chef/knife/ec2_server_create.rb, line 1265 def disable_source_dest_check ec2_connection.modify_instance_attribute({ source_dest_check: { value: false, }, instance_id: server.id, }) end
# File lib/chef/knife/ec2_server_create.rb, line 512 def download_validation_key(tempfile) Chef::Log.debug "Downloading validation key " \ "<#{config[:validation_key_url]}> to file " \ "<#{tempfile}>" case URI(config[:validation_key_url]).scheme when "s3" File.open(tempfile, "w") { |f| f.write(s3_validation_key) } end end
# File lib/chef/knife/ec2_server_create.rb, line 747 def eip_scope if vpc_mode? "vpc" else "standard" end end
# File lib/chef/knife/ec2_server_create.rb, line 1254 def enable_classic_link(vpc_id, security_group_ids) ec2_connection.attach_classic_link_vpc({ instance_id: server.id, groups: security_group_ids, vpc_id: vpc_id, }) end
base64-encoded text
# File lib/chef/knife/ec2_server_create.rb, line 836 def encode_data(text) require "base64" unless defined?(Base64) Base64.encode64(text) end
Returns the name of node after evaluation of server id if %s is present. Eg: “Test-%s” will return “Test-i-12345” in case the instance id is i-12345
# File lib/chef/knife/ec2_server_create.rb, line 1368 def evaluate_node_name(node_name) node_name % server.id end
# File lib/chef/knife/ec2_server_create.rb, line 552 def fetch_server_fqdn(ip_addr) require "resolv" Resolv.getname(ip_addr) end
# File lib/chef/knife/ec2_server_create.rb, line 1021 def get_ssh_gateway_for(hostname) if config[:ssh_gateway] # The ssh_gateway specified in the knife config (if any) takes # precedence over anything in the SSH configuration Chef::Log.debug("Using ssh gateway #{config[:ssh_gateway]} from knife config") config[:ssh_gateway] else # Next, check if the SSH configuration has a ProxyCommand # directive for this host. If there is one, parse out the # host from the proxy command ssh_proxy = Net::SSH::Config.for(hostname)[:proxy] if ssh_proxy.respond_to?(:command_line_template) # ssh gateway_hostname nc %h %p proxy_pattern = /ssh\s+(\S+)\s+nc/ matchdata = proxy_pattern.match(ssh_proxy.command_line_template) if matchdata.nil? Chef::Log.debug("Unable to determine ssh gateway for '#{hostname}' from ssh config template: #{ssh_proxy.command_line_template}") nil else # Return hostname extracted from command line template Chef::Log.debug("Using ssh gateway #{matchdata[1]} from ssh config") matchdata[1] end else # Return nil if we cannot find an ssh_gateway Chef::Log.debug("No ssh gateway found, making a direct connection") nil end end end
# File lib/chef/knife/ec2_server_create.rb, line 998 def instances_wait_until_ready(instance_id) ec2_connection.wait_until(:instance_running, instance_ids: [instance_id]) do |w| w.max_attempts = max_attempts end rescue Aws::Waiters::Errors::WaiterFailed => error puts "failed waiting for instance running: #{error.message}" end
# File lib/chef/knife/ec2_server_create.rb, line 1006 def max_attempts delay = 15 # Default Delay for waiter attempts = 40 # Default max attempts for waiter if config[:aws_connection_timeout] attempts = (config[:aws_connection_timeout].to_f / delay).to_i end attempts end
# File lib/chef/knife/ec2_server_create.rb, line 271 def plugin_create_instance! requested_elastic_ip = config[:associate_eip] if config[:associate_eip] # For VPC EIP assignment we need the allocation ID so fetch full EIP details elastic_ip = ec2_connection.describe_addresses.addresses.detect { |addr| addr if addr.public_ip == requested_elastic_ip } if config[:spot_price] server_def = spot_instances_attributes spot_request = ec2_connection.request_spot_instances(server_def) msg_pair("Spot Request ID", spot_request.spot_instance_request_id) msg_pair("Spot Request Type", spot_request.type) msg_pair("Spot Price", spot_request.spot_price) case config[:spot_wait_mode] when "prompt", "", nil wait_msg = "Do you want to wait for Spot Instance Request fulfillment? (Y/N) \n" wait_msg += "Y - Wait for Spot Instance request fulfillment\n" wait_msg += "N - Do not wait for Spot Instance request fulfillment. " wait_msg += ui.color("[WARN :: Request would be alive on AWS ec2 side but execution of Chef Bootstrap on the target instance will get skipped.]\n", :red, :bold) wait_msg += ui.color("\n[WARN :: For any of the above mentioned choices, (Y) - if the instance does not get allocated before the command itself times-out or (N) - user decides to exit, then in both cases user needs to manually bootstrap the instance in the future after it gets allocated.]\n\n", :cyan, :bold) confirm(wait_msg) when "wait" # wait for the node and run Chef bootstrap when "exit" ui.color("The 'exit' option was specified for --spot-wait-mode, exiting.", :cyan) exit else raise "Invalid value for --spot-wait-mode: '#{config[:spot_wait_mode]}', " \ "valid values: wait, exit, prompt" end print ui.color("Waiting for Spot Request fulfillment: ", :cyan) spot_response = spot_instances_wait_until_ready(spot_request.spot_instance_request_id) @server = fetch_ec2_instance(spot_response.instance_id) else begin response_obj = create_ec2_instance(server_attributes) instance_id = response_obj.instances[0].instance_id print "\n#{ui.color("Waiting for EC2 to create the instance\n", :magenta)}" # wait for instance to come up before acting against it instances_wait_until_ready(instance_id) @server = fetch_ec2_instance(instance_id) rescue => error error.message.sub("download completed, but downloaded file not found", "Verify that you have public internet access.") ui.error error.message Chef::Log.debug("#{error.backtrace.join("\n")}") exit end end msg_pair("Instance ID", server.id) msg_pair("Flavor", server.instance_type) msg_pair("Image", server.image_id) msg_pair("Region", fetch_region) msg_pair("Availability Zone", server.availability_zone) msg_pair("Security Groups", printed_security_groups) unless vpc_mode? || (server.groups && server.security_groups_ids) msg_pair("Security Group Ids", printed_security_group_ids) if vpc_mode? || server.security_groups_ids msg_pair("IAM Profile", config[:iam_instance_profile]) msg_pair("AWS Tags", printed_aws_tags) msg_pair("Volume Tags", printed_volume_tags) msg_pair("SSH Key", server.key_name) msg_pair("T2/T3 Unlimited", printed_t2_t3_unlimited) puts("\n") # occasionally 'ready?' isn't, so retry a couple times if needed. tries = 6 begin disable_source_dest_check if vpc_mode? && config[:disable_source_dest_check] create_tags(hashed_tags) unless hashed_tags.empty? create_volume_tags(hashed_volume_tags) unless hashed_volume_tags.empty? associate_address(elastic_ip) if config[:associate_eip] enable_classic_link(config[:classic_link_vpc_id], config[:classic_link_vpc_security_group_ids]) if config[:classic_link_vpc_id] rescue Aws::EC2::Errors::ServiceError, Aws::EC2::Errors::Error raise if (tries -= 1) <= 0 ui.warn("server not ready, retrying tag application (retries left: #{tries})") sleep 5 retry end attach_nics if config[:network_interfaces] # Re-fetch the latest server data after assigning vpc or network interfaces @server = fetch_ec2_instance(instance_id) if reload_server_data_required? 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.public_dns_name) end if elastic_ip msg_pair("Public IP Address", server.public_ip_address) end else msg_pair("Public DNS Name", server.public_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) if config[:validation_key_url] download_validation_key(validation_key_path) Chef::Config[:validation_key] = validation_key_path end # Check if Server is Windows or Linux if is_image_windows? if winrm? print "\n#{ui.color("Waiting for winrm access to become available", :magenta)}" print(".") until tcp_test_winrm(connection_host, connection_port) do sleep 10 puts("done") end else print "\n#{ui.color("Waiting for sshd access to become available", :magenta)}" # If FreeSSHd, winsshd etc are available print(".") until tcp_test_ssh(connection_host, connection_port) do sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10) puts("done") end end else print "\n#{ui.color("Waiting for sshd access to become available", :magenta)}" wait_for_sshd(connection_host) end fqdn = connection_host if winrm? if config[:kerberos_realm] # Fetch AD/WINS based fqdn if any for Kerberos-based Auth fqdn = config[:fqdn] || fetch_server_fqdn(server.private_ip_address) end config[:connection_password] = windows_password end @name_args = [fqdn] if config[:chef_node_name] config[:chef_node_name] = evaluate_node_name(config[:chef_node_name]) else config[:chef_node_name] = server.id end bootstrap_common_params end
# File lib/chef/knife/ec2_server_create.rb, line 421 def plugin_finalize puts "\n" msg_pair("Instance ID", server.id) msg_pair("Flavor", server.instance_type) msg_pair("Placement Group", server.placement_group) unless server.placement_group.nil? msg_pair("Image", server.image_id) msg_pair("Region", fetch_region) msg_pair("Availability Zone", server.availability_zone) msg_pair("Security Groups", printed_security_groups) unless vpc_mode? || (server.groups.nil? && server.security_group_ids) msg_pair("Security Group Ids", printed_security_group_ids) if vpc_mode? || server.security_group_ids msg_pair("IAM Profile", config[:iam_instance_profile]) if config[:iam_instance_profile] msg_pair("Primary ENI", config[:primary_eni]) if config[:primary_eni] msg_pair("AWS Tags", printed_aws_tags) msg_pair("Chef Tags", config[:tags]) if config[:tags].any? msg_pair("SSH Key", server.key_name) msg_pair("Root Device Type", server.root_device_type) msg_pair("Root Volume Tags", printed_volume_tags) if server.root_device_type == "ebs" device_map = server.block_device_mappings.first msg_pair("Root Device Name", device_map.device_name) msg_pair("Root Volume ID", device_map.ebs.volume_id) msg_pair("Root Device Delete on Terminate", device_map.ebs.delete_on_termination) msg_pair("Standard or Provisioned IOPS", device_map.ebs.volume_type) if device_map.ebs.respond_to?("volume_type") msg_pair("IOPS rate", device_map.ebs.iops) if device_map.ebs.respond_to?("iops") print "\n#{ui.color("Block devices", :magenta)}\n" print "#{ui.color("===========================", :magenta)}\n" server.block_device_mappings.each do |device_map| msg_pair("Device Name", device_map.device_name) msg_pair("Volume ID", device_map.ebs.volume_id) msg_pair("Delete on Terminate", device_map.ebs.delete_on_termination.to_s) msg_pair("Standard or Provisioned IOPS", device_map.ebs.volume_type) if device_map.ebs.respond_to?("volume_type") msg_pair("IOPS rate", device_map.ebs.iops) if device_map.ebs.respond_to?("iops") print "\n" end print "#{ui.color("===========================", :magenta)}\n" if config[:ebs_size] volume_size = ami.block_device_mappings[0].ebs.volume_size if volume_size.to_i < config[:ebs_size].to_i volume_too_large_warning = "#{config[:ebs_size]}GB " + "EBS volume size is larger than size set in AMI of " + "#{volume_size}GB.\n" + "Use file system tools to make use of the increased volume size." msg_pair("Warning", volume_too_large_warning, :yellow) end end end 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.public_dns_name) end else msg_pair("Public DNS Name", server.public_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) msg_pair("Environment", config[:environment] || "_default") msg_pair("Run List", (config[:run_list] || []).join(", ")) if config[:first_boot_attributes] || config[:first_boot_attributes_from_file] msg_pair("JSON Attributes", config[:first_boot_attributes] || config[:first_boot_attributes_from_file]) end end
When options connection_protocol and connection_port
are not provided It will set as default
# File lib/chef/knife/ec2_server_create.rb, line 565 def plugin_setup! validate_aws_config!(%i{image aws_access_key_id aws_secret_access_key}) config[:connection_protocol] ||= connection_protocol_ec2 config[:connection_port] ||= connection_port end
# File lib/chef/knife/ec2_server_create.rb, line 575 def plugin_validate_options! if config.key?(:aws_ssh_key_id) config[:ssh_key_name] = config[:aws_ssh_key_id] unless config[:ssh_key_name] config.delete(:aws_ssh_key_id) ui.warn("Use of aws_ssh_key_id option in knife.rb/config.rb config is deprecated, use ssh_key_name option instead.") end create_key_pair unless config[:ssh_key_name] validate_nics! if config[:network_interfaces] if ami.nil? ui.error("The provided AMI value '#{config[:image]}' could not be found. Is this AMI availble in the provided region #{config[:region]}?") exit 1 end if vpc_mode? && !!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? && !!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] && !vpc_mode? ui.error("You can only specify a Dedicated Instance if you are using VPC.") exit 1 end if !vpc_mode? && 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 = ec2_connection.describe_addresses.addresses.collect { |addr| addr if addr.domain == eip_scope }.compact unless eips.detect { |addr| addr.public_ip == config[:associate_eip] && (addr.instance_id.nil? || addr.instance_id.empty?) } ui.error("Elastic IP requested is not available.") exit 1 end end if config[:ebs_provisioned_iops] && (config[:ebs_volume_type] != "io1") ui.error("--provisioned-iops option is only supported for volume type of 'io1'") exit 1 end if (config[:ebs_volume_type] == "io1") && config[:ebs_provisioned_iops].nil? ui.error("--provisioned-iops option is required when using volume type of 'io1'") exit 1 end if config[:ebs_volume_type] && ! %w{gp2 io1 standard st1 sc1}.include?(config[:ebs_volume_type]) ui.error("--ebs-volume-type must be 'standard' or 'io1' or 'gp2' or 'st1' or 'sc1'") msg opt_parser exit 1 end # validation for ebs_size if (%w{st1 sc1}.include?(config[:ebs_volume_type])) && ! config[:ebs_size].to_i.between?(500, 16384) ui.error("--ebs-size should be in between 500-16384 for 'st1' or 'sc1' ebs volume type.") exit 1 end if config[:security_groups] && config[:security_groups].class == String ui.error("Invalid value type for knife[:security_groups] in knife configuration file (i.e knife.rb/config.rb). Type should be array. e.g - knife[:security_groups] = ['sgroup1']") exit 1 end # Validation for security_group_ids passed through knife.rb/config.rb. It will raise error if values are not provided in Array. if config[:security_group_ids] && config[:security_group_ids].class == String ui.error("Invalid value type for knife[:security_group_ids] in knife configuration file (i.e knife.rb/config.rb). Type should be array. e.g - knife[:security_group_ids] = ['sgroup1']") exit 1 end if config[:classic_link_vpc_id].nil? ^ config[:classic_link_vpc_security_group_ids].nil? ui.error("--classic-link-vpc-id and --classic-link-vpc-security-group-ids must be used together") exit 1 end if vpc_mode? && config[:classic_link_vpc_id] ui.error("You can only use ClassicLink if you are not using a VPC") exit 1 end if config[:ebs_encrypted] error_message = "" errors = [] # validation for flavor and ebs_encrypted if !config[:flavor] ui.error("--ebs-encrypted option requires valid flavor to be specified.") exit 1 elsif config[:ebs_encrypted] && ! %w{m3.medium m3.large m3.xlarge m3.2xlarge m4.large m4.xlarge m4.2xlarge m4.4xlarge m4.10xlarge m4.16xlarge t2.nano t2.micro t2.small t2.medium t2.large t2.xlarge t2.2xlarge d2.xlarge d2.2xlarge d2.4xlarge d2.8xlarge c4.large c4.xlarge c4.2xlarge c4.4xlarge c4.8xlarge c3.large c3.xlarge c3.2xlarge c3.4xlarge c3.8xlarge cr1.8xlarge r3.large r3.xlarge r3.2xlarge r3.4xlarge r3.8xlarge r4.large r4.xlarge r4.2xlarge r4.4xlarge r4.8xlarge r4.16xlarge x1.16xlarge x1.32xlarge i2.xlarge i2.2xlarge i2.4xlarge i2.8xlarge i3.large i3.xlarge i3.2xlarge i3.4xlarge i3.8xlarge i3.16xlarge f1.2xlarge f1.16xlarge g2.2xlarge g2.8xlarge p2.xlarge p2.8xlarge p2.16xlarge}.include?(config[:flavor]) ui.error("--ebs-encrypted option is not supported for #{config[:flavor]} flavor.") exit 1 end # validation for ebs_size and ebs_volume_type and ebs_encrypted if !config[:ebs_size] errors << "--ebs-encrypted option requires valid --ebs-size to be specified." elsif (config[:ebs_volume_type] == "gp2") && ! config[:ebs_size].to_i.between?(1, 16384) errors << "--ebs-size should be in between 1-16384 for 'gp2' ebs volume type." elsif (config[:ebs_volume_type] == "io1") && ! config[:ebs_size].to_i.between?(4, 16384) errors << "--ebs-size should be in between 4-16384 for 'io1' ebs volume type." elsif (config[:ebs_volume_type] == "standard") && ! config[:ebs_size].to_i.between?(1, 1024) errors << "--ebs-size should be in between 1-1024 for 'standard' ebs volume type." end if errors.each { |e| error_message = "#{error_message} #{e}" }.any? ui.error(error_message) exit 1 end end if config[:spot_price] && config[:disable_api_termination] ui.error("spot-price and disable-api-termination options cannot be passed together as 'Termination Protection' cannot be enabled for spot instances.") exit 1 end if config[:spot_price].nil? && config[:spot_wait_mode] unless config[:spot_wait_mode].casecmp("prompt") == 0 ui.error("spot-wait-mode option requires that a spot-price option is set.") exit 1 end end volume_tags = config[:volume_tags] if !volume_tags.nil? && (volume_tags.length != volume_tags.to_s.count("=")) ui.error("Volume Tags should be entered in a key = value pair") exit 1 end if winrm? && !config[:connection_password].nil? reg = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,40}$/ unless config[:connection_password]&.match?(reg) ui.error("Complexity requirements are not met. Password length should be 8-40 characters and include: 1 uppercase, 1 lowercase, 1 digit, and 1 special character") exit 1 end end if config[:chef_tag] # If --chef-tag is provided then it will be set in chef as single value e.g. --chef-tag "myTag" # --tags has been removed from knife-ec2, now it's available in core config[:tags] += config[:chef_tag] ui.warn("[DEPRECATED] --chef-tag option is deprecated and will be removed in future release. Use --tags TAGS option instead.") end if config[:cpu_credits] && !config[:flavor] ui.error("Instance type should be specified and should be any of T2/T3 type.") exit 1 end end
# File lib/chef/knife/ec2_server_create.rb, line 1424 def printed_security_group_ids if server.security_group_ids server.security_group_ids.join(", ") else "default" end end
If we don't specify a security group or security group id, Fog will pick the appropriate default one. In case of a VPC we don't know the default security group id at this point unless we look it up, hence 'default' is printed if no id was specified.
# File lib/chef/knife/ec2_server_create.rb, line 1416 def printed_security_groups if server.groups server.groups.join(", ") else "default" end end
# File lib/chef/knife/ec2_server_create.rb, line 1464 def printed_t2_t3_unlimited if config[:cpu_credits] == "unlimited" "Enabled" else "Disabled" end end
# File lib/chef/knife/ec2_server_create.rb, line 816 def process_user_data(script_lines) unless ssl_config_data_already_exist? ps_start_tag = "<powershell>\n" ps_end_tag = "</powershell>\n" ps_start_tag_index = script_lines.index(ps_start_tag) || script_lines.index(ps_start_tag.strip) ps_end_tag_index = script_lines.index(ps_end_tag) || script_lines.index(ps_end_tag.strip) case when ( ps_start_tag_index && !ps_end_tag_index ) || ( !ps_start_tag_index && ps_end_tag_index ) ui.error("Provided user_data file is invalid.") exit 1 when ps_start_tag_index && ps_end_tag_index script_lines[ps_end_tag_index] = ssl_config_user_data + ps_end_tag when !ps_start_tag_index && !ps_end_tag_index script_lines.insert(-1, "\n\n" + ps_start_tag + ssl_config_user_data + ps_end_tag) end end script_lines end
# File lib/chef/knife/ec2_server_create.rb, line 1472 def reload_server_data_required? !!config[:associate_eip] || !!config[:classic_link_vpc_id] || !!config[:network_interfaces] end
# File lib/chef/knife/ec2_server_create.rb, line 529 def s3_secret @s3_secret ||= begin return false unless config[:s3_secret] Chef::Knife::S3Source.fetch(config[:s3_secret], config) end end
# File lib/chef/knife/ec2_server_create.rb, line 523 def s3_validation_key @s3_validation_key ||= begin Chef::Knife::S3Source.fetch(config[:validation_key_url], config) end end
# File lib/chef/knife/ec2_server_create.rb, line 1184 def save_keypair_file(key_pair) file_path = File.join(Config.config_dir, "#{key_pair.key_name}.pem") file = File.open(file_path, "w+") { |f| f << key_pair.key_material } config[:ssh_key_name] = key_pair.key_name config[:ssh_identity_file] = file.path puts "\nGenerated keypair file: #{file.path}" end
# File lib/chef/knife/ec2_server_create.rb, line 850 def server_attributes attributes = { image_id: config[:image], instance_type: config[:flavor], key_name: config[:ssh_key_name], max_count: 1, min_count: 1, placement: { availability_zone: config[:availability_zone], }, } network_attrs = {} if !!config[:primary_eni] network_attrs[:network_interface_id] = config[:primary_eni] elsif vpc_mode? network_attrs[:subnet_id] = config[:subnet_id] end if vpc_mode? network_attrs[:groups] = config[:security_group_ids] if !!config[:security_group_ids] network_attrs[:private_ip_address] = config[:private_ip_address] network_attrs[:associate_public_ip_address] = config[:associate_public_ip] else attributes[:security_groups] = config[:security_groups] end if network_attrs.length > 0 network_attrs[:device_index] = 0 attributes[:network_interfaces] = [network_attrs] else attributes[:security_group_ids] = config[:security_group_ids] end attributes[:placement][:group_name] = config[:placement_group] attributes[:placement][:tenancy] = "dedicated" if vpc_mode? && config[:dedicated_instance] attributes[:iam_instance_profile] = {} attributes[:iam_instance_profile][:name] = config[:iam_instance_profile] if config[:winrm_ssl] if config[:aws_user_data] begin user_data = File.readlines(config[:aws_user_data]) if config[:create_ssl_listener] user_data = process_user_data(user_data) end user_data = user_data.join attributes.merge!(user_data: encode_data(user_data)) rescue ui.warn("Cannot read #{config[:aws_user_data]}: #{$!.inspect}. Ignoring option.") end else if config[:create_ssl_listener] attributes[:user_data] = encode_data("<powershell>\n" + ssl_config_user_data + "</powershell>\n") end end else if config[:aws_user_data] begin user_data = File.read(config[:aws_user_data]) attributes.merge!(user_data: encode_data(user_data)) rescue ui.warn("Cannot read #{config[:aws_user_data]}: #{$!.inspect}. Ignoring option.") end end end attributes[:ebs_optimized] = !!config[:ebs_optimized] if ami.root_device_type == "ebs" if config[:ebs_encrypted] || %w{st1 sc1}.include?(config[:ebs_volume_type]) ami_map = ami.block_device_mappings[1] else ami_map = ami.block_device_mappings.first end ebs_size = begin if config[:ebs_size] Integer(config[:ebs_size]).to_s else ami_map.ebs.volume_size.to_s if ami_map.ebs.respond_to?(:volume_size) end rescue ArgumentError puts "--ebs-size must be an integer" msg opt_parser exit 1 end delete_term = config[:ebs_delete_on_term] iops_rate = begin if config[:ebs_provisioned_iops] Integer(config[:ebs_provisioned_iops]).to_s else ami_map.ebs.iops.to_s if ami_map.ebs.respond_to?(:iops) end rescue ArgumentError puts "--provisioned-iops must be an integer" msg opt_parser exit 1 end attributes[:block_device_mappings] = [{ device_name: ami_map.device_name, ebs: { delete_on_termination: delete_term, volume_size: ebs_size, volume_type: config[:ebs_volume_type], # accepts standard, io1, gp2, sc1, st1 }, }] attributes[:block_device_mappings][0][:ebs][:iops] = iops_rate unless iops_rate.nil? || iops_rate.empty? attributes[:block_device_mappings][0][:ebs][:encrypted] = true if config[:ebs_encrypted] end if config[:ephemeral] && config[:ephemeral].length > 0 ephemeral_blocks = [] config[:ephemeral].each_with_index do |device_name, i| ephemeral_blocks << { virtual_name: "ephemeral#{i}", device_name: device_name } end attributes[:block_device_mappings] += ephemeral_blocks end ## cannot pass disable_api_termination option to the API when using spot instances ## attributes[:disable_api_termination] = config[:disable_api_termination] if config[:spot_price].nil? attributes[:instance_initiated_shutdown_behavior] = config[:instance_initiated_shutdown_behavior] if config[:cpu_credits] attributes[:credit_specification] = { cpu_credits: config[:cpu_credits], } end attributes end
# File lib/chef/knife/ec2_server_create.rb, line 1406 def server_name server ? connection_host : nil end
# File lib/chef/knife/ec2_server_create.rb, line 841 def spot_instances_attributes attributes = { instance_count: 1, launch_specification: server_attributes, spot_price: config[:spot_price], type: config[:spot_request_type], } end
# File lib/chef/knife/ec2_server_create.rb, line 989 def spot_instances_wait_until_ready(spot_request_id) ec2_connection.wait_until( :spot_instance_request_fulfilled, spot_instance_request_ids: [spot_request_id] ).spot_instance_requests[0] rescue Aws::Waiters::Errors::WaiterFailed => error puts "failed waiting for spot request fulfill: #{error.message}" end
# File lib/chef/knife/ec2_server_create.rb, line 812 def ssl_config_data_already_exist? File.read(config[:aws_user_data]).gsub(/\\\\/, "\\").include? ssl_config_user_data.strip end
# File lib/chef/knife/ec2_server_create.rb, line 755 def ssl_config_user_data user_related_commands = "" user = connection_user.split("\\") if (user[0] == ".") || (user[0] == "") || (user.length == 1) user_related_commands = <<~EOH net user /add #{connection_user.delete('.\\')} #{windows_password} #{@allow_long_password}; net localgroup Administrators /add #{connection_user.delete('.\\')}; EOH end <<~EOH #{user_related_commands} If (-Not (Get-Service WinRM | Where-Object {$_.status -eq "Running"})) { winrm quickconfig -q } If (winrm e winrm/config/listener | Select-String -Pattern " Transport = HTTP\\b" -Quiet) { winrm delete winrm/config/listener?Address=*+Transport=HTTP } $vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/public-ipv4 If (-Not $vm_name) { $vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/local-ipv4 } $name = new-object -com "X509Enrollment.CX500DistinguishedName.1" $name.Encode("CN=$vm_name", 0) $key = new-object -com "X509Enrollment.CX509PrivateKey.1" $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider" $key.KeySpec = 1 $key.Length = 2048 $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)" $key.MachineContext = 1 $key.Create() $serverauthoid = new-object -com "X509Enrollment.CObjectId.1" $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1") $ekuoids = new-object -com "X509Enrollment.CObjectIds.1" $ekuoids.add($serverauthoid) $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1" $ekuext.InitializeEncode($ekuoids) $cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1" $cert.InitializeFromPrivateKey(2, $key, "") $cert.Subject = $name $cert.Issuer = $cert.Subject $cert.NotBefore = get-date $cert.NotAfter = $cert.NotBefore.AddYears(10) $cert.X509Extensions.Add($ekuext) $cert.Encode() $enrollment = new-object -com "X509Enrollment.CX509Enrollment.1" $enrollment.InitializeFromRequest($cert) $certdata = $enrollment.CreateRequest(0) $enrollment.InstallResponse(2, $certdata, 0, "") $thumbprint = (Get-ChildItem -Path cert:\\localmachine\\my | Where-Object {$_.Subject -match "$vm_name"}).Thumbprint; $create_listener_cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname=`"$vm_name`";CertificateThumbprint=`"$thumbprint`"}'" iex $create_listener_cmd netsh advfirewall firewall add rule name="WinRM HTTPS" protocol=TCP dir=in Localport=5986 remoteport=any action=allow localip=any remoteip=any profile=any enable=yes EOH end
@return [Boolean]
# File lib/chef/knife/ec2_server_create.rb, line 1120 def subnet_public_ip_on_launch? return false unless server.subnet_id subnet = fetch_subnet(server.subnet_id) subnet.map_public_ip_on_launch end
# File lib/chef/knife/ec2_server_create.rb, line 1298 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? || 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, Errno::ENOTCONN, 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
# File lib/chef/knife/ec2_server_create.rb, line 1274 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
# File lib/chef/knife/ec2_server_create.rb, line 1065 def tunnel_test_ssh(ssh_gateway, hostname, &block) status = false gateway = configure_ssh_gateway(ssh_gateway) gateway.open(hostname, connection_port) do |local_tunnel_port| status = tcp_test_ssh("localhost", local_tunnel_port, &block) end status rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError sleep 2 false rescue Errno::EPERM, Errno::ETIMEDOUT false end
# File lib/chef/knife/ec2_server_create.rb, line 571 def validate_name_args! # We don't know the name of our instance yet end
# File lib/chef/knife/ec2_server_create.rb, line 1207 def validate_nics! params = {} if vpc_mode? network_attrs = { name: "vpc_id", value: [vpc_id] } params["filters"] = [network_attrs] end interfaces = ec2_connection.describe_network_interfaces(params) valid_nic_ids = interfaces.network_interfaces.map(&:network_interface_id) invalid_nic_ids = config[:network_interfaces] - valid_nic_ids return true if invalid_nic_ids.empty? ui.error "The following network interfaces are invalid: " \ "#{invalid_nic_ids.join(", ")}" exit 1 end
# File lib/chef/knife/ec2_server_create.rb, line 497 def validation_key_path @validation_key_path ||= begin if URI(config[:validation_key_url]).scheme == "file" URI(config[:validation_key_url]).path else validation_key_tmpfile.path end end end
@return [Tempfile]
# File lib/chef/knife/ec2_server_create.rb, line 508 def validation_key_tmpfile @validation_key_tmpfile ||= Tempfile.new("validation_key") end
# File lib/chef/knife/ec2_server_create.rb, line 1225 def vpc_id @vpc_id ||= fetch_subnet(config[:subnet_id]).vpc_id end
# File lib/chef/knife/ec2_server_create.rb, line 557 def vpc_mode? # Amazon Virtual Private Cloud requires a subnet_id. If # present, do a few things differently !!config[:subnet_id] end
# File lib/chef/knife/ec2_server_create.rb, line 1106 def wait_for_direct_sshd(hostname, ssh_port) initial = true print(".") until tcp_test_ssh(hostname, ssh_port) do if initial initial = false sleep (vpc_mode? ? 40 : 10) else sleep 10 end puts("done") end end
# File lib/chef/knife/ec2_server_create.rb, line 1229 def wait_for_nic_attachment attached_nics_count = 0 until attached_nics_count == config[:network_interfaces].count attachment_nics = config[:network_interfaces].map do |nic_id| fetch_network_interfaces(nic_id).attachment.status end attached_nics_count = attachment_nics.grep("attached").count end end
# File lib/chef/knife/ec2_server_create.rb, line 1016 def wait_for_sshd(hostname) ssh_gateway = get_ssh_gateway_for(hostname) ssh_gateway ? wait_for_tunnelled_sshd(ssh_gateway, hostname) : wait_for_direct_sshd(hostname, connection_port) end
# File lib/chef/knife/ec2_server_create.rb, line 1052 def wait_for_tunnelled_sshd(ssh_gateway, hostname) initial = true print(".") until tunnel_test_ssh(ssh_gateway, hostname) do if initial initial = false sleep (vpc_mode? ? 40 : 10) else sleep 10 end puts("done") end end
# File lib/chef/knife/ec2_server_create.rb, line 1345 def windows_password if not config[:connection_password] if config[:ssh_identity_file] if server print "\n#{ui.color("Waiting for Windows Admin password to be available: ", :magenta)}" print(".") until check_windows_password_available(server.id) { puts("done") } response = fetch_password_data(server.id) data = File.read(config[:ssh_identity_file]) config[:connection_password] = decrypt_admin_password(response.password_data, data) else print "\n#{ui.color("Fetching instance details: \n", :magenta)}" end else ui.error("Cannot find SSH Identity file, required to fetch dynamically generated password") exit 1 end else config[:connection_password] end end