class Chef::Provisioning::AWSDriver::Driver
Provisions machines using the AWS
SDK
Constants
- PORT_DEFAULTS
- PROTOCOL_DEFAULTS
Attributes
aws_config[R]
aws_config_2[R]
Public Class Methods
canonicalize_url(driver_url, config)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 165 def self.canonicalize_url(driver_url, config) [driver_url, config] end
from_url(driver_url, config)
click to toggle source
URL scheme: aws:profilename:region TODO: migration path from fog:AWS - parse that URL canonical URL calls realpath on <path>
# File lib/chef/provisioning/aws_driver/driver.rb, line 101 def self.from_url(driver_url, config) Driver.new(driver_url, config) end
new(driver_url, config)
click to toggle source
Calls superclass method
# File lib/chef/provisioning/aws_driver/driver.rb, line 105 def initialize(driver_url, config) super _, profile_name, region = driver_url.split(":") profile_name = nil if profile_name && profile_name.empty? region = nil if region && region.empty? credentials = profile_name ? aws_credentials[profile_name] : aws_credentials.default @aws_config = Aws.config.update( access_key_id: credentials[:aws_access_key_id], secret_access_key: credentials[:aws_secret_access_key], region: region || credentials[:region], http_proxy: credentials[:proxy_uri] || nil, session_token: credentials[:aws_session_token] || nil, logger: Chef::Log.logger ) # TODO: document how users could add something to the Aws.config themselves if they want to # Right now we are supporting both V1 and V2, so we create 2 config sets credentials2 = Credentials2.new(profile_name: profile_name) Chef::Config.chef_provisioning ||= {} @aws_config_2 = { credentials: credentials2.get_credentials, region: region || ENV["AWS_DEFAULT_REGION"] || credentials[:region], # TODO: when we get rid of V1 replace the credentials class with something that knows how # to read ~/.aws/config http_proxy: credentials[:proxy_uri] || nil, logger: Chef::Log.logger, retry_limit: Chef::Config.chef_provisioning[:aws_retry_limit] || 5 } driver = self Chef::Resource::Machine.send(:define_method, :aws_object) do resource = Chef::Resource::AwsInstance.new(name, nil) resource.driver driver resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store resource.aws_object end Chef::Resource::MachineImage.send(:define_method, :aws_object) do resource = Chef::Resource::AwsImage.new(name, nil) resource.driver driver resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store resource.aws_object end Chef::Resource::LoadBalancer.send(:define_method, :aws_object) do resource = Chef::Resource::AwsLoadBalancer.new(name, nil) resource.driver driver resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store resource.aws_object end end
Public Instance Methods
account_id()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 872 def account_id begin # We've got an AWS account root credential or an IAM admin with access rights current_user = iam.get_user arn = current_user[:user][:arn] rescue ::Aws::IAM::Errors::AccessDenied => e # If we don't have access, the error message still tells us our account ID and user ... # https://forums.aws.amazon.com/thread.jspa?messageID=394344 if e.to_s !~ /\b(arn:aws:iam::[0-9]{12}:\S*)/ raise "IAM error response for GetUser did not include user ARN. Can't retrieve account ID." end arn = Regexp.last_match(1) end parse_arn(arn)[:account_id] end
allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options)
click to toggle source
Image methods
# File lib/chef/provisioning/aws_driver/driver.rb, line 591 def allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options) actual_image = image_for(image_spec) image_options = deep_symbolize_keys(image_options) machine_options = deep_symbolize_keys(machine_options) aws_tags = image_options.delete(:aws_tags) || {} if actual_image.nil? || !actual_image.exists? || actual_image.state.to_sym == :failed action_handler.perform_action "Create image #{image_spec.name} from machine #{machine_spec.name} with options #{image_options.inspect}" do image_options[:name] ||= image_spec.name image_options[:instance_id] ||= machine_spec.reference["instance_id"] image_options[:description] ||= "Image #{image_spec.name} created from machine #{machine_spec.name}" Chef::Log.debug "AWS Image options: #{image_options.inspect}" image_type = ec2_client.create_image(image_options.to_hash) actual_image = ec2_resource.image(image_type.image_id) image_spec.reference = { "driver_version" => Chef::Provisioning::AWSDriver::VERSION, "image_id" => actual_image.image_id, "allocated_at" => Time.now.to_i, "from-instance" => image_options[:instance_id] } image_spec.driver_url = driver_url end end aws_tags["from-instance"] = image_options[:instance_id] if image_options[:instance_id] converge_ec2_tags(actual_image, aws_tags, action_handler) end
allocate_load_balancer(action_handler, lb_spec, lb_options, machine_specs)
click to toggle source
Load balancer methods
# File lib/chef/provisioning/aws_driver/driver.rb, line 192 def allocate_load_balancer(action_handler, lb_spec, lb_options, machine_specs) lb_options = deep_symbolize_keys(lb_options) lb_options = AWSResource.lookup_options(lb_options, managed_entry_store: lb_spec.managed_entry_store, driver: self) # renaming lb_options[:port] to lb_options[:load_balancer_port] if lb_options[:listeners] lb_options[:listeners].each do |listener| listener[:load_balancer_port] = listener.delete(:port) if listener[:port] end end # We delete the attributes, tags, health check, and sticky sessions here because they are not valid in the create call # and must be applied afterward lb_attributes = lb_options.delete(:attributes) lb_aws_tags = lb_options.delete(:aws_tags) health_check = lb_options.delete(:health_check) sticky_sessions = lb_options.delete(:sticky_sessions) old_elb = nil actual_elb = load_balancer_for(lb_spec) if actual_elb.nil? lb_options[:listeners] ||= get_listeners(:http) if !lb_options[:subnets] && !lb_options[:availability_zones] && machine_specs lb_options[:subnets] = machine_specs.map { |s| ec2_resource.instance(s.reference["instance_id"]).subnet.id }.uniq end perform_action = proc { |desc, &block| action_handler.perform_action(desc, &block) } Chef::Log.debug "AWS Load Balancer options: #{lb_options.inspect}" updates = ["create load balancer #{lb_spec.name} in #{region}"] updates << " enable availability zones #{lb_options[:availability_zones]}" if lb_options[:availability_zones] updates << " attach subnets #{lb_options[:subnets].join(', ')}" if lb_options[:subnets] updates << " with listeners #{lb_options[:listeners]}" if lb_options[:listeners] updates << " with security groups #{lb_options[:security_groups]}" if lb_options[:security_groups] updates << " with tags #{lb_options[:aws_tags]}" if lb_options[:aws_tags] action_handler.perform_action updates do # IAM says the server certificate exists, but ELB throws this error Chef::Provisioning::AWSDriver::AWSProvider.retry_with_backoff(::Aws::ElasticLoadBalancing::Errors::CertificateNotFound) do lb_options[:listeners].each do |listener| if listener.key?(:server_certificate) listener[:ssl_certificate_id] = listener.delete(:server_certificate) listener[:ssl_certificate_id] = listener[:ssl_certificate_id][:arn] end end lb_options[:load_balancer_name] = lb_spec.name actual_elb = elb.create_load_balancer(lb_options) end # load aws object for load balancer after create actual_elb = load_balancer_for(lb_spec) lb_spec.reference = { "driver_version" => Chef::Provisioning::AWSDriver::VERSION, "allocated_at" => Time.now.utc.to_s } lb_spec.driver_url = driver_url end else # Header gets printed the first time we make an update perform_action = proc do |desc, &block| perform_action = proc { |desc, &block| action_handler.perform_action(desc, &block) } action_handler.perform_action ["Update load balancer #{lb_spec.name} in #{region}", desc].flatten, &block end # TODO: refactor this whole giant method into many smaller method calls if lb_options[:scheme] && lb_options[:scheme].downcase != actual_elb.scheme # TODO: CloudFormation automatically recreates the load_balancer, we should too raise "Scheme is immutable - you need to :destroy and :create the load_balancer to recreated it with the new scheme" end # Update security groups if lb_options[:security_groups] current = actual_elb.security_groups desired = lb_options[:security_groups] if current != desired perform_action.call(" updating security groups to #{desired.to_a}") do elb_client.apply_security_groups_to_load_balancer( load_balancer_name: actual_elb.load_balancer_name, security_groups: desired.to_a ) end end end if lb_options[:availability_zones] || lb_options[:subnets] # A subnet always belongs to an availability zone. When specifying a ELB spec, you can either # specify subnets OR AZs but not both. You cannot specify multiple subnets in the same AZ. # You must specify at least 1 subnet or AZ. On an update you cannot remove all subnets # or AZs - it must belong to one. if lb_options[:availability_zones] && lb_options[:subnets] # We do this check here because there is no atomic call we can make to specify both # subnets and AZs at the same time raise "You cannot specify both `availability_zones` and `subnets`" end # Users can switch from availability zones to subnets or vice versa. To ensure we do not # unassign all (which causes an AWS error) we first add all available ones, then remove # an unecessary ones actual_zones_subnets = {} actual_elb.subnets.each do |subnet| actual_zones_subnets[subnet] = Chef::Resource::AwsSubnet.get_aws_object(subnet, driver: self).availability_zone end # Only 1 of subnet or AZ will be populated b/c of our check earlier desired_subnets_zones = {} if lb_options[:availability_zones] lb_options[:availability_zones].each do |zone| # If the user specifies availability zone, we find the default subnet for that # AZ because this duplicates the create logic zone = zone.downcase filters = [ { name: "availabilityZone", values: [zone] }, { name: "defaultForAz", values: ["true"] } ] default_subnet = ec2_client.describe_subnets(filters: filters)[:subnets] if default_subnet.size != 1 raise "Could not find default subnet in availability zone #{zone}" end default_subnet = default_subnet[0] desired_subnets_zones[default_subnet[:subnet_id]] = zone end end unless lb_options[:subnets].nil? || lb_options[:subnets].empty? subnet_query = ec2_client.describe_subnets(subnet_ids: lb_options[:subnets])[:subnets] # AWS raises an error on an unknown subnet, but not an unknown AZ subnet_query.each do |subnet| zone = subnet[:availability_zone].downcase desired_subnets_zones[subnet[:subnet_id]] = zone end end # We only bother attaching subnets, because doing this automatically attaches the AZ attach_subnets = desired_subnets_zones.keys - actual_zones_subnets.keys unless attach_subnets.empty? action = " attach subnets #{attach_subnets.join(', ')}" enable_zones = (desired_subnets_zones.map { |s, z| z if attach_subnets.include?(s) }).compact action += " (availability zones #{enable_zones.join(', ')})" perform_action.call(action) do begin elb.attach_load_balancer_to_subnets( load_balancer_name: actual_elb.load_balancer_name, subnets: attach_subnets ) rescue ::Aws::ElasticLoadBalancing::Errors::InvalidConfigurationRequest => e Chef::Log.error "You cannot currently move from 1 subnet to another in the same availability zone. " \ "Amazon does not have an atomic operation which allows this. You must create a new " \ "ELB with the correct subnets and move instances into it. Tried to attach subets " \ "#{attach_subnets.join(', ')} (availability zones #{enable_zones.join(', ')}) to " \ "existing ELB named #{actual_elb.load_balancer_name}" raise e end end end detach_subnets = actual_zones_subnets.keys - desired_subnets_zones.keys unless detach_subnets.empty? action = " detach subnets #{detach_subnets.join(', ')}" disable_zones = (actual_zones_subnets.map { |s, z| z if detach_subnets.include?(s) }).compact action += " (availability zones #{disable_zones.join(', ')})" perform_action.call(action) do elb.detach_load_balancer_from_subnets( load_balancer_name: actual_elb.load_balancer_name, subnets: detach_subnets ) end end end # Update listeners - THIS IS NOT ATOMIC if lb_options[:listeners] add_listeners = {} lb_options[:listeners].each { |l| add_listeners[l[:load_balancer_port]] = l } actual_elb.listener_descriptions.each do |listener_description| listener = listener_description.listener desired_listener = add_listeners.delete(listener.load_balancer_port) if desired_listener # listener.(port|protocol|instance_port|instance_protocol) are immutable for the life # of the listener - must create a new one and delete old one immutable_updates = [] if listener.protocol != desired_listener[:protocol].to_s.upcase immutable_updates << " update protocol from #{listener.protocol.inspect} to #{desired_listener[:protocol].inspect}" end if listener.instance_port != desired_listener[:instance_port] immutable_updates << " update instance port from #{listener.instance_port.inspect} to #{desired_listener[:instance_port].inspect}" end if listener.instance_protocol != desired_listener[:instance_protocol].to_s.upcase immutable_updates << " update instance protocol from #{listener.instance_protocol.inspect} to #{desired_listener[:instance_protocol].inspect}" end if !immutable_updates.empty? perform_action.call(immutable_updates) do elb.delete_load_balancer_listeners(load_balancer_name: actual_elb.load_balancer_name, load_balancer_ports: [listener.load_balancer_port]) elb.create_load_balancer_listeners(listeners: [desired_listener], load_balancer_name: actual_elb.load_balancer_name) # actual_elb.listeners.create(desired_listener) end elsif listener.ssl_certificate_id && !server_certificate_eql?(listener.ssl_certificate_id, server_cert_from_spec(desired_listener)) # Server certificate is mutable - if no immutable changes required a full recreate, update cert perform_action.call(" update server certificate from #{listener.ssl_certificate_id} to #{server_cert_from_spec(desired_listener)}") do elb.set_load_balancer_listener_ssl_certificate( load_balancer_name: actual_elb.load_balancer_name, load_balancer_port: listener.load_balancer_port, ssl_certificate_id: server_cert_from_spec(desired_listener) ) end end else perform_action.call(" remove listener #{listener.load_balancer_port}") do elb.delete_load_balancer_listeners(load_balancer_name: actual_elb.load_balancer_name, load_balancer_ports: [listener.load_balancer_port]) end end end add_listeners.values.each do |listener| updates = [" add listener #{listener[:load_balancer_port]}"] updates << " set protocol to #{listener[:protocol].inspect}" updates << " set instance port to #{listener[:instance_port].inspect}" updates << " set instance protocol to #{listener[:instance_protocol].inspect}" updates << " set server certificate to #{server_cert_from_spec(listener)}" if server_cert_from_spec(listener) perform_action.call(updates) do elb.create_load_balancer_listeners(listeners: [listener], load_balancer_name: actual_elb.load_balancer_name) end end end end converge_elb_tags(actual_elb, lb_aws_tags, action_handler) # Update load balancer attributes if lb_attributes current = elb.describe_load_balancer_attributes(load_balancer_name: actual_elb.load_balancer_name)[:load_balancer_attributes].to_hash # Need to do a deep copy w/ Marshal load/dump to avoid overwriting current desired = deep_merge!(lb_attributes, Marshal.load(Marshal.dump(current))) if current != desired perform_action.call(" updating attributes to #{desired.inspect}") do elb.modify_load_balancer_attributes( load_balancer_name: actual_elb.load_balancer_name, load_balancer_attributes: desired.to_hash ) end end end # Update the load balancer health check, as above if health_check current = elb.describe_load_balancers(load_balancer_names: [actual_elb.load_balancer_name])[:load_balancer_descriptions][0][:health_check].to_hash desired = deep_merge!(health_check, Marshal.load(Marshal.dump(current))) if current != desired perform_action.call(" updating health check to #{desired.inspect}") do elb.configure_health_check( load_balancer_name: actual_elb.load_balancer_name, health_check: desired.to_hash ) end end end # Update the load balancer sticky sessions if sticky_sessions policy_name = "#{actual_elb.load_balancer_name}-sticky-session-policy" policies = elb.describe_load_balancer_policies(load_balancer_name: actual_elb.load_balancer_name) existing_cookie_policy = policies[:policy_descriptions].detect { |pd| pd[:policy_type_name] == "AppCookieStickinessPolicyType" && pd[:policy_name] == policy_name } existing_cookie_name = existing_cookie_policy ? (existing_cookie_policy[:policy_attribute_descriptions].detect { |pad| pad[:attribute_name] == "CookieName" })[:attribute_value] : nil desired_cookie_name = sticky_sessions[:cookie_name] # Create or update the policy to have the desired cookie_name if existing_cookie_policy.nil? perform_action.call(" creating sticky sessions with cookie_name #{desired_cookie_name}") do elb.create_app_cookie_stickiness_policy( load_balancer_name: actual_elb.load_balancer_name, policy_name: policy_name, cookie_name: desired_cookie_name ) end elsif existing_cookie_name && existing_cookie_name != desired_cookie_name perform_action.call(" updating sticky sessions from cookie_name #{existing_cookie_name} to cookie_name #{desired_cookie_name}") do elb.delete_load_balancer_policy( load_balancer_name: actual_elb.load_balancer_name, policy_name: policy_name ) elb.create_app_cookie_stickiness_policy( load_balancer_name: actual_elb.load_balancer_name, policy_name: policy_name, cookie_name: desired_cookie_name ) end end # Ensure the policy is attached to the appropriate listener elb_description = elb.describe_load_balancers(load_balancer_names: [actual_elb.load_balancer_name])[:load_balancer_descriptions].first listeners = elb_description[:listener_descriptions] sticky_sessions[:ports].each do |ss_port| listener = listeners.detect { |ld| ld[:listener][:load_balancer_port] == ss_port } next if listener.nil? policy_names = listener[:policy_names] next if policy_names.include?(policy_name) policy_names << policy_name elb.set_load_balancer_policies_of_listener( load_balancer_name: actual_elb.load_balancer_name, load_balancer_port: ss_port, policy_names: policy_names ) end end # Update instance list, but only if there are machines specified if machine_specs instances_to_add = [] if actual_elb.instances assigned_instance_ids = actual_elb.instances.map(&:instance_id) instances_to_add = machine_specs.reject { |s| assigned_instance_ids.include?(s.reference["instance_id"]) } instance_ids_to_remove = assigned_instance_ids - machine_specs.map { |s| s.reference["instance_id"] } end unless instances_to_add.empty? perform_action.call(" add machines #{instances_to_add.map(&:name).join(', ')}") do instance_ids_to_add = instances_to_add.map { |s| s.reference["instance_id"] } Chef::Log.debug("Adding instances #{instance_ids_to_add.join(', ')} to load balancer #{actual_elb.load_balancer_name} in region #{region}") instances_to_add.each do |instance| elb.register_instances_with_load_balancer(instances: [{ instance_id: instance.reference["instance_id"] }], load_balancer_name: actual_elb.load_balancer_name) end end end unless instance_ids_to_remove.empty? perform_action.call(" remove instances #{instance_ids_to_remove}") do instances_to_remove = Hash[instance_ids_to_remove.map { |id| [:instance_id, id] }] elb.deregister_instances_from_load_balancer(instances: [instances_to_remove], load_balancer_name: actual_elb.load_balancer_name) end end end # We have successfully switched all our instances to the (possibly) new LB # so it is safe to delete the old one. old_elb.delete unless old_elb.nil? ensure # Something went wrong before we could moved instances from the old ELB to the new one # Don't delete the old ELB, but warn users there could now be 2 ELBs with the same name unless old_elb.nil? Chef::Log.warn("It is possible there are now 2 ELB instances - #{old_elb.load_balancer_name} and #{actual_elb.load_balancer_name}. " \ "Determine which is correct and manually clean up the other.") end end
allocate_machine(action_handler, machine_spec, machine_options)
click to toggle source
Machine methods
# File lib/chef/provisioning/aws_driver/driver.rb, line 706 def allocate_machine(action_handler, machine_spec, machine_options) machine_options = deep_symbolize_keys(machine_options) instance = instance_for(machine_spec) bootstrap_options = bootstrap_options_for(action_handler, machine_spec, machine_options) if instance.nil? || !instance.exists? || instance.state.name == "terminated" action_handler.perform_action "Create #{machine_spec.name} with AMI #{bootstrap_options[:image_id]} in #{region}" do Chef::Log.debug "Creating instance with bootstrap options #{bootstrap_options}" instance = create_instance_and_reference(bootstrap_options, action_handler, machine_spec, machine_options) end end converge_ec2_tags(instance, machine_options[:aws_tags], action_handler) end
allocate_machines(action_handler, specs_and_options, parallelizer) { |machine_spec| ... }
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 720 def allocate_machines(action_handler, specs_and_options, parallelizer) create_servers(action_handler, specs_and_options, parallelizer) do |machine_spec, _server| yield machine_spec end specs_and_options.keys end
auto_scaling()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 853 def auto_scaling @auto_scaling ||= ::Aws::AutoScaling.new(config: aws_config) end
aws_credentials()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1043 def aws_credentials # Grab the list of possible credentials @aws_credentials ||= if driver_options[:aws_credentials] driver_options[:aws_credentials] else credentials = Credentials.new if driver_options[:aws_config_file] credentials.load_ini(driver_options[:aws_config_file]) elsif driver_options[:aws_csv_file] credentials.load_csv(driver_options[:aws_csv_file]) else credentials.load_default end credentials end end
bootstrap_options_for(action_handler, machine_spec, machine_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 905 def bootstrap_options_for(action_handler, machine_spec, machine_options) bootstrap_options = deep_symbolize_keys(machine_options[:bootstrap_options]) bootstrap_options = Hash({}) if bootstrap_options.nil? # These are hardcoded for now - only 1 machine at a time bootstrap_options[:min_count] = bootstrap_options[:max_count] = 1 bootstrap_options[:instance_type] ||= default_instance_type image_id = machine_options[:from_image] || bootstrap_options[:image_id] || machine_options[:image_id] || default_ami_for_region(region) bootstrap_options[:image_id] = image_id bootstrap_options.delete(:key_path) unless bootstrap_options[:key_name] Chef::Log.debug("No key specified, generating a default one...") bootstrap_options[:key_name] = default_aws_keypair(action_handler, machine_spec) end if bootstrap_options[:user_data] bootstrap_options[:user_data] = Base64.encode64(bootstrap_options[:user_data]) end # V1 -> V2 backwards compatability support unless bootstrap_options.fetch(:monitoring_enabled, nil).nil? bootstrap_options[:monitoring] = { enabled: bootstrap_options.delete(:monitoring_enabled) } end placement = {} if bootstrap_options[:availability_zone] placement[:availability_zone] = bootstrap_options.delete(:availability_zone) end if bootstrap_options[:placement_group] placement[:group_name] = bootstrap_options.delete(:placement_group) end unless bootstrap_options.fetch(:dedicated_tenancy, nil).nil? placement[:tenancy] = bootstrap_options.delete(:dedicated_tenancy) ? "dedicated" : "default" end bootstrap_options[:placement] = placement unless placement.empty? if bootstrap_options[:subnet] bootstrap_options[:subnet_id] = bootstrap_options.delete(:subnet) end if bootstrap_options[:iam_instance_profile] && bootstrap_options[:iam_instance_profile].is_a?(String) bootstrap_options[:iam_instance_profile] = { name: bootstrap_options[:iam_instance_profile] } end if machine_options[:is_windows] Chef::Log.debug "Setting Default windows userdata based on WinRM transport" if bootstrap_options[:user_data].nil? data = case machine_options[:winrm_transport] when "https" https_user_data else user_data end bootstrap_options[:user_data] = Base64.encode64(data) end else Chef::Log.debug "Non-windows, not setting Default userdata" end bootstrap_options = AWSResource.lookup_options(bootstrap_options, managed_entry_store: machine_spec.managed_entry_store, driver: self) # In the migration from V1 to V2 we still support associate_public_ip_address at the top level # we do this after the lookup because we have to copy any present subnets, etc. into the # network interfaces block unless bootstrap_options.fetch(:associate_public_ip_address, nil).nil? if bootstrap_options[:network_interfaces] raise "If you specify network_interfaces you must specify associate_public_ip_address in that list" end network_interface = { device_index: 0, associate_public_ip_address: bootstrap_options.delete(:associate_public_ip_address), delete_on_termination: true } if bootstrap_options[:subnet_id] network_interface[:subnet_id] = bootstrap_options.delete(:subnet_id) end if bootstrap_options[:private_ip_address] network_interface[:private_ip_address] = bootstrap_options.delete(:private_ip_address) end if bootstrap_options[:security_group_ids] network_interface[:groups] = bootstrap_options.delete(:security_group_ids) end bootstrap_options[:network_interfaces] = [network_interface] end Chef::Log.debug "AWS Bootstrap options: #{bootstrap_options.inspect}" deep_symbolize_keys(bootstrap_options) end
build_arn(partition: "aws", service: nil, region: aws_config[:region], account_id: self.account_id, resource: nil)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 857 def build_arn(partition: "aws", service: nil, region: aws_config[:region], account_id: self.account_id, resource: nil) "arn:#{partition}:#{service}:#{region}:#{account_id}:#{resource}" end
cloudsearch()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 161 def cloudsearch @cloudsearch ||= Aws::CloudSearch::Client.new(aws_config) end
connect_to_machine(name, chef_server = nil)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 766 def connect_to_machine(name, chef_server = nil) machine_spec = if name.is_a?(MachineSpec) name else Chef::Provisioning::ChefMachineSpec.get(name, chef_server) end machine_for(machine_spec, machine_spec.reference) end
convergence_strategy_for(machine_spec, machine_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1309 def convergence_strategy_for(machine_spec, machine_options) # Tell Ohai that this is an EC2 instance so that it runs the EC2 plugin convergence_options = Cheffish::MergedConfig.new( machine_options[:convergence_options] || {}, ohai_hints: { "ec2" => "" } ) convergence_options = deep_symbolize_keys(convergence_options) # Defaults unless machine_spec.reference return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(convergence_options, config) end if machine_spec.reference["is_windows"] Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(convergence_options, config) elsif machine_options[:cached_installer] == true Chef::Provisioning::ConvergenceStrategy::InstallCached.new(convergence_options, config) else Chef::Provisioning::ConvergenceStrategy::InstallSh.new(convergence_options, config) end end
create_instance_and_reference(bootstrap_options, action_handler, machine_spec, machine_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1518 def create_instance_and_reference(bootstrap_options, action_handler, machine_spec, machine_options) instance = nil # IAM says the instance profile is ready, but EC2 doesn't think it is # Not using retry_with_backoff here because we need to match on a string Retryable.retryable( tries: 10, sleep: ->(n) { [2**n, 16].min }, on: ::Aws::EC2::Errors::InvalidParameterValue, matching: /Invalid IAM Instance Profile name/ ) do |_retries, exception| Chef::Log.debug("Instance creation InvalidParameterValue exception is #{exception.inspect}") instance = ec2_resource.create_instances(bootstrap_options.to_hash)[0] end # Make sure the instance is ready to be tagged instance.wait_until_exists # Sometimes tagging fails even though the instance 'exists' Chef::Provisioning::AWSDriver::AWSProvider.retry_with_backoff(::Aws::EC2::Errors::InvalidInstanceIDNotFound) do instance.create_tags(tags: [{ key: "Name", value: machine_spec.name }]) end if machine_options.key?(:source_dest_check) instance.modify_attribute( source_dest_check: { value: machine_options[:source_dest_check] } ) end machine_spec.reference = { "driver_version" => Chef::Provisioning::AWSDriver::VERSION, "allocated_at" => Time.now.utc.to_s, "host_node" => action_handler.host_node, "image_id" => bootstrap_options[:image_id], "instance_id" => instance.id } machine_spec.driver_url = driver_url machine_spec.reference["key_name"] = bootstrap_options[:key_name] if bootstrap_options[:key_name] # TODO: 2.0 We no longer support `use_private_ip_for_ssh`, only `transport_address_location` if machine_options[:use_private_ip_for_ssh] unless @transport_address_location_warned Chef::Log.warn("The machine_option ':use_private_ip_for_ssh' has been deprecated, use ':transport_address_location'") @transport_address_location_warned = true end machine_options[:transport_address_location] ||= :private_ip end %w{is_windows winrm_username winrm_port winrm_password ssh_username sudo transport_address_location ssh_gateway}.each do |key| machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym] end instance end
create_servers(action_handler, specs_and_options, parallelizer) { |machine_spec, instance| ... }
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1440 def create_servers(action_handler, specs_and_options, parallelizer) specs_and_servers = instances_for(specs_and_options.keys) by_bootstrap_options = {} specs_and_options.each do |machine_spec, machine_options| instance = specs_and_servers[machine_spec] if instance if instance.state.name == "terminated" Chef::Log.warn "Machine #{machine_spec.name} (#{instance.id}) is terminated. Recreating ..." else # Even though the instance has been created the tags could be incorrect if it # was created before tags were introduced converge_ec2_tags(instance, machine_options[:aws_tags], action_handler) yield machine_spec, instance if block_given? next end elsif machine_spec.reference Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.reference['instance_id']} on #{driver_url}) no longer exists. Recreating ..." end bootstrap_options = bootstrap_options_for(action_handler, machine_spec, machine_options) by_bootstrap_options[bootstrap_options] ||= [] by_bootstrap_options[bootstrap_options] << machine_spec end # Create the servers in parallel parallelizer.parallelize(by_bootstrap_options) do |bootstrap_options, machine_specs| machine_description = if machine_specs.size == 1 "machine #{machine_specs.first.name}" else "machines #{machine_specs.map(&:name).join(', ')}" end description = ["creating #{machine_description} on #{driver_url}"] bootstrap_options.each_pair { |key, value| description << " #{key}: #{value.inspect}" } action_handler.report_progress description if action_handler.should_perform_actions # Actually create the servers parallelizer.parallelize(1.upto(machine_specs.size)) do |_i| # Assign each one to a machine spec machine_spec = machine_specs.pop machine_options = specs_and_options[machine_spec] clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) instance = create_instance_and_reference(clean_bootstrap_options, action_handler, machine_spec, machine_options) converge_ec2_tags(instance, machine_options[:aws_tags], action_handler) action_handler.performed_action "machine #{machine_spec.name} created as #{instance.id} on #{driver_url}" yield machine_spec, instance if block_given? end.to_a unless machine_specs.empty? raise "Not all machines were created by create_servers" end end end.to_a end
create_ssh_transport(machine_spec, machine_options, instance)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1221 def create_ssh_transport(machine_spec, machine_options, instance) ssh_options = ssh_options_for(machine_spec, machine_options, instance) username = machine_spec.reference["ssh_username"] || machine_options[:ssh_username] || default_ssh_username if machine_options.key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.reference["ssh_username"] Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.reference['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.reference['ssh_username']}. Please edit the node and change the chef_provisioning.reference.ssh_username attribute if you want to change it.") end options = {} if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != "root") options[:prefix] = "sudo " end remote_host = determine_remote_host(machine_spec, instance) # Enable pty by default options[:ssh_pty_enable] = true if machine_spec.reference.key?("ssh_gateway") options[:ssh_gateway] = machine_spec.reference["ssh_gateway"] elsif machine_options[:ssh_gateway] options[:ssh_gateway] = machine_options[:ssh_gateway] end Chef::Provisioning::Transport::SSH.new(remote_host, username, ssh_options, options, config) end
create_winrm_transport(machine_spec, machine_options, instance)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1098 def create_winrm_transport(machine_spec, machine_options, instance) remote_host = determine_remote_host(machine_spec, instance) username = machine_spec.reference["winrm_username"] || machine_options[:winrm_username] || default_winrm_username # default to http for now, should upgrade to https when knife support self-signed transport_type = machine_spec.reference["winrm_transport"] || machine_options[:winrm_transport] || default_winrm_transport type = case transport_type when "http" :plaintext when "https" :ssl end port = machine_spec.reference["winrm_port"] || machine_options[:winrm_port] || case transport_type when "http" "5985" when "https" "5986" end endpoint = "#{transport_type}://#{remote_host}:#{port}/wsman" pem_bytes = get_private_key(instance.key_name) password = machine_spec.reference["winrm_password"] || machine_options[:winrm_password] || begin if machine_spec.reference["winrm_encrypted_password"] decoded = Base64.decode64(machine_spec.reference["winrm_encrypted_password"]) else encrypted_admin_password = instance.password_data.password_data if encrypted_admin_password.nil? || encrypted_admin_password.empty? raise "You did not specify winrm_password in the machine options and no encrytpted password could be fetched from the instance" end machine_spec.reference["winrm_encrypted_password"] ||= encrypted_admin_password # ^^ saves encrypted password to the machine_spec decoded = Base64.decode64(encrypted_admin_password) end # decrypt so we can utilize private_key = OpenSSL::PKey::RSA.new(get_private_key(instance.key_name)) private_key.private_decrypt decoded end disable_sspi = machine_spec.reference["winrm_disable_sspi"] || machine_options[:winrm_disable_sspi] || false # default to Negotiate basic_auth_only = machine_spec.reference["winrm_basic_auth_only"] || machine_options[:winrm_basic_auth_only] || false # disallow Basic auth by default no_ssl_peer_verification = machine_spec.reference["winrm_no_ssl_peer_verification"] || machine_options[:winrm_no_ssl_peer_verification] || false # disallow MITM potential by default winrm_options = { user: username, pass: password, disable_sspi: disable_sspi, basic_auth_only: basic_auth_only, no_ssl_peer_verification: no_ssl_peer_verification } if no_ssl_peer_verification || (type != :ssl) # => we won't verify certs at all Chef::Log.info "No SSL or no peer verification" elsif machine_spec.reference["winrm_ssl_thumbprint"] # we have stored the cert Chef::Log.info "Using stored fingerprint" else # we need to retrieve the cert and verify it by connecting just to # retrieve the ssl certificate and compare it to what we see in the # console logs instance.console_output.data.output # again this seem to need to be run twice, to ensure encoded_output = instance.console_output.data.output console_lines = Base64.decode64(encoded_output).lines fp_context = OpenSSL::SSL::SSLContext.new tcp_connection = TCPSocket.new(instance.private_ip_address, port) ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, fp_context) begin ssl_connection.connect rescue OpenSSL::SSL::SSLError => e raise e unless e.message =~ /bad signature/ ensure tcp_connection.close end winrm_cert = ssl_connection.peer_cert_chain.first rdp_thumbprint = console_lines.grep( /RDPCERTIFICATE-THUMBPRINT/ )[-1].split(": ").last.chomp rdp_subject = console_lines.grep( /RDPCERTIFICATE-SUBJECTNAME/ )[-1].split(": ").last.chomp winrm_subject = winrm_cert.subject.to_s.split("=").last.upcase winrm_thumbprint = OpenSSL::Digest::SHA1.new(winrm_cert.to_der).to_s.upcase if (rdp_subject != winrm_subject) || (rdp_thumbprint != winrm_thumbprint) Chef::Log.fatal "Winrm ssl port certificate differs from rdp console logs" end # now cache these for later use in the reference if machine_spec.reference["winrm_ssl_subject"] != winrm_subject machine_spec.reference["winrm_ssl_subject"] = winrm_subject end if machine_spec.reference["winrm_ssl_thumbprint"] != winrm_thumbprint machine_spec.reference["winrm_ssl_thumbprint"] = winrm_thumbprint end if machine_spec.reference["winrm_ssl_cert"] != winrm_cert.to_pem machine_spec.reference["winrm_ssl_cert"] = winrm_cert.to_pem end end if machine_spec.reference["winrm_ssl_thumbprint"] winrm_options[:ssl_peer_fingerprint] = machine_spec.reference["winrm_ssl_thumbprint"] end Chef::Provisioning::Transport::WinRM.new(endpoint.to_s, type, winrm_options, {}) end
deep_symbolize_keys(hash_like)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 169 def deep_symbolize_keys(hash_like) # Process arrays first... if hash_like.is_a?(Array) # Node attributes are an ImmutableArray so lets convert them to an array first hash_like = hash_like.to_a hash_like.length.times do |e| hash_like[e] = deep_symbolize_keys(hash_like[e]) if hash_like[e].respond_to?(:values) || hash_like[e].is_a?(Array) end return hash_like end # Otherwise return ourselves if not a hash return hash_like unless hash_like.respond_to?(:values) # Otherwise we are hash like, push on through... return {} if hash_like.nil? || hash_like.empty? r = {} hash_like.each do |key, value| value = deep_symbolize_keys(value) if value.respond_to?(:values) || value.is_a?(Array) r[key.to_sym] = value end r end
default_ami_arch()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1060 def default_ami_arch "amd64" end
default_ami_for_criteria(region, arch, release, root_store, virtualization_type)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1076 def default_ami_for_criteria(region, arch, release, root_store, virtualization_type) ami = Ubuntu.release(release).amis.find do |ami| ami.arch == arch && ami.root_store == root_store && ami.region == region && ami.virtualization_type == virtualization_type end ami.name || raise("Default AMI not found") end
default_ami_for_region(region, criteria = {})
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1087 def default_ami_for_region(region, criteria = {}) Chef::Log.debug("Choosing default AMI for region '#{region}'") arch = criteria["arch"] || default_ami_arch release = criteria["release"] || default_ami_release root_store = criteria["root_store"] || default_ami_root_store virtualization_type = criteria["virtualization_type"] || default_ami_virtualization_type default_ami_for_criteria(region, arch, release, root_store, virtualization_type) end
default_ami_release()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1064 def default_ami_release "vivid" end
default_ami_root_store()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1068 def default_ami_root_store "ebs" end
default_ami_virtualization_type()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1072 def default_ami_virtualization_type "hvm" end
default_aws_keypair(action_handler, machine_spec)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1419 def default_aws_keypair(action_handler, machine_spec) driver = self default_key_name = default_aws_keypair_name(machine_spec) updated = @@chef_default_lock.synchronize do Provisioning.inline_resource(action_handler) do aws_key_pair default_key_name do driver driver chef_server machine_spec.managed_entry_store.chef_server managed_entry_store machine_spec.managed_entry_store allow_overwrite true end end end # Only warn the first time default_warning = "Using default key, which is not shared between machines! It is recommended to create an AWS key pair with the aws_key_pair resource, and set :bootstrap_options => { :key_name => <key name> }" Chef::Log.warn(default_warning) if updated default_key_name end
default_aws_keypair_name(machine_spec)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1410 def default_aws_keypair_name(machine_spec) if machine_spec.reference && Gem::Version.new(machine_spec.reference["driver_version"]) < Gem::Version.new("0.10") "metal_default" else "chef_default" end end
default_instance_type()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1626 def default_instance_type "t2.micro" end
default_ssh_username()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 989 def default_ssh_username "ubuntu" end
default_winrm_transport()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 997 def default_winrm_transport "http" end
default_winrm_username()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 993 def default_winrm_username "Administrator" end
destroy_image(action_handler, image_spec, image_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 633 def destroy_image(action_handler, image_spec, image_options) image_options = deep_symbolize_keys(image_options) # TODO: the driver should automatically be set by `inline_resource` d = self Provisioning.inline_resource(action_handler) do aws_image image_spec.name do action :destroy driver d chef_server image_spec.managed_entry_store.chef_server managed_entry_store image_spec.managed_entry_store end end end
destroy_load_balancer(action_handler, lb_spec, lb_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 574 def destroy_load_balancer(action_handler, lb_spec, lb_options) lb_options = deep_symbolize_keys(lb_options) return if lb_spec.nil? actual_elb = load_balancer_for(lb_spec) if actual_elb # Remove ELB from AWS action_handler.perform_action "Deleting EC2 ELB #{lb_spec.id}" do elb.delete_load_balancer(load_balancer_name: actual_elb.load_balancer_name) end end # Remove LB spec from databag lb_spec.delete(action_handler) end
destroy_machine(action_handler, machine_spec, machine_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 790 def destroy_machine(action_handler, machine_spec, machine_options) machine_options = deep_symbolize_keys(machine_options) d = self Provisioning.inline_resource(action_handler) do aws_instance machine_spec.name do action :destroy driver d chef_server machine_spec.managed_entry_store.chef_server managed_entry_store machine_spec.managed_entry_store end end # TODO: move this into the aws_instance provider somehow strategy = convergence_strategy_for(machine_spec, machine_options) strategy.cleanup_convergence(action_handler, machine_spec) end
determine_remote_host(machine_spec, instance)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1246 def determine_remote_host(machine_spec, instance) transport_address_location = (machine_spec.reference["transport_address_location"] || :none).to_sym if machine_spec.reference["use_private_ip_for_ssh"] # The machine_spec has the old config key, lets update it - a successful chef converge will save the machine_spec # TODO in 2.0 get rid of this update machine_spec.reference.delete("use_private_ip_for_ssh") machine_spec.reference["transport_address_location"] = :private_ip instance.private_ip_address elsif transport_address_location == :private_ip instance.private_ip_address elsif transport_address_location == :dns instance.dns_name elsif !instance.public_ip_address && instance.private_ip_address Chef::Log.warn("Server #{machine_spec.name} has no public ip address. Using private ip '#{instance.private_ip_address}'. Set machine_options ':transport_address_location => :private_ip' if this will always be the case ...") instance.private_ip_address elsif instance.public_ip_address instance.public_ip_address else raise "Server #{instance.id} has no private or public IP address!" end end
ec2()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 807 def ec2 @ec2 ||= ::Aws::EC2::Client.new(aws_config) end
elasticache()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 829 def elasticache @elasticache ||= ::Aws::ElastiCache::Client.new(aws_config) end
elb()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 825 def elb @elb ||= ::Aws::ElasticLoadBalancing::Client.new(aws_config) end
get_listener(listener)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1590 def get_listener(listener) result = {} case listener when Hash result.merge!(listener) when Array result[:load_balancer_port] = listener[0] if listener.size >= 1 result[:protocol] = listener[1] if listener.size >= 2 when Symbol, String result[:protocol] = listener when Integer result[:load_balancer_port] = listener else raise "Invalid listener #{listener}" end # If either port or protocol are set, set the other if result[:load_balancer_port] && !result[:protocol] result[:protocol] = PROTOCOL_DEFAULTS[result[:load_balancer_port]] elsif result[:protocol] && !result[:load_balancer_port] result[:load_balancer_port] = PORT_DEFAULTS[result[:protocol]] end if result[:instance_port] && !result[:instance_protocol] result[:instance_protocol] = PROTOCOL_DEFAULTS[result[:instance_port]] elsif result[:instance_protocol] && !result[:instance_port] result[:instance_port] = PORT_DEFAULTS[result[:instance_protocol]] end # If instance_port is still unset, copy port/protocol over result[:instance_port] ||= result[:load_balancer_port] result[:instance_protocol] ||= result[:protocol] result end
get_listeners(listeners)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1569 def get_listeners(listeners) case listeners when Hash listeners.map do |from, to| from = get_listener(from) from.delete(:instance_port) from.delete(:instance_protocol) to = get_listener(to) to.delete(:load_balancer_port) to.delete(:protocol) to.merge(from) end when Array listeners.map { |listener| get_listener(listener) } when nil nil else [get_listener(listeners)] end end
https_user_data()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 667 def https_user_data <<EOD <powershell> winrm quickconfig -q winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' winrm set winrm/config '@{MaxTimeoutms="1800000"}' netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow $SourceStoreScope = 'LocalMachine' $SourceStorename = 'Remote Desktop' $SourceStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $SourceStorename, $SourceStoreScope $SourceStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) $cert = $SourceStore.Certificates | Where-Object -FilterScript { $_.subject -like '*' } $DestStoreScope = 'LocalMachine' $DestStoreName = 'My' $DestStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $DestStoreName, $DestStoreScope $DestStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $DestStore.Add($cert) $SourceStore.Close() $DestStore.Close() winrm create winrm/config/listener?Address=*+Transport=HTTPS `@`{Hostname=`"($certId)`"`;CertificateThumbprint=`"($cert.Thumbprint)`"`} net stop winrm sc config winrm start=auto net start winrm </powershell> EOD end
iam()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 833 def iam @iam ||= ::Aws::IAM::Client.new(aws_config) end
image_for(image_spec)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1031 def image_for(image_spec) Chef::Resource::AwsImage.get_aws_object(image_spec.name, driver: self, managed_entry_store: image_spec.managed_entry_store, required: false) end
instance_for(machine_spec)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1016 def instance_for(machine_spec) if machine_spec.reference if machine_spec.driver_url != driver_url raise "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not currently supported! Use machine :destroy and then re-create the machine on the new driver." end Chef::Resource::AwsInstance.get_aws_object(machine_spec.reference["instance_id"], driver: self, managed_entry_store: machine_spec.managed_entry_store, required: false) end end
instances_for(machine_specs)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1025 def instances_for(machine_specs) result = {} machine_specs.each { |machine_spec| result[machine_spec] = instance_for(machine_spec) } result end
keypair_for(bootstrap_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1001 def keypair_for(bootstrap_options) if bootstrap_options[:key_name] keypair_name = bootstrap_options[:key_name] actual_key_pair = ec2_resource.key_pair(keypair_name) unless actual_key_pair.exists? ec2_resource.key_pairs.create(keypair_name) end actual_key_pair end end
load_balancer_for(lb_spec)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1012 def load_balancer_for(lb_spec) Chef::Resource::AwsLoadBalancer.get_aws_object(lb_spec.name, driver: self, managed_entry_store: lb_spec.managed_entry_store, required: false) end
machine_for(machine_spec, machine_options, instance = nil)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 891 def machine_for(machine_spec, machine_options, instance = nil) instance ||= instance_for(machine_spec) unless instance raise "Instance for node #{machine_spec.name} has not been created!" end if machine_spec.reference["is_windows"] Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options)) else Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options)) end end
parse_arn(arn)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 861 def parse_arn(arn) parts = arn.split(":", 6) { partition: parts[1], service: parts[2], region: parts[3], account_id: parts[4], resource: parts[5] } end
private_key_for(machine_spec, machine_options, instance)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1268 def private_key_for(machine_spec, machine_options, instance) if instance.respond_to?(:private_key) && instance.private_key instance.private_key elsif instance.respond_to?(:key_name) && instance.key_name key = get_private_key(instance.key_name) unless key raise "Server has key name '#{instance.key_name}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}" end key elsif machine_spec.reference["key_name"] key = get_private_key(machine_spec.reference["key_name"]) unless key raise "Server was created with key name '#{machine_spec.reference['key_name']}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}" end key elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_path] IO.read(machine_options[:bootstrap_options][:key_path]) elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_name] get_private_key(machine_options[:bootstrap_options][:key_name]) else # TODO: make a way to suggest other keys to try ... raise "No key found to connect to #{machine_spec.name} (#{machine_spec.reference.inspect})!" end end
rds()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 837 def rds @rds ||= ::Aws::RDS::Client.new(aws_config) end
ready_image(action_handler, image_spec, image_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 617 def ready_image(action_handler, image_spec, image_options) actual_image = image_for(image_spec) if actual_image.nil? || !actual_image.exists? raise "Cannot ready an image that does not exist" else image_options = deep_symbolize_keys(image_options) aws_tags = image_options.delete(:aws_tags) || {} aws_tags["from-instance"] = image_spec.reference["from-instance"] if image_spec.reference["from-instance"] converge_ec2_tags(actual_image, aws_tags, action_handler) if actual_image.state.to_sym != :available action_handler.report_progress "Waiting for image to be ready ..." wait_until_ready_image(action_handler, image_spec, actual_image) end end end
ready_load_balancer(action_handler, lb_spec, lb_options, machine_spec)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 572 def ready_load_balancer(action_handler, lb_spec, lb_options, machine_spec); end
ready_machine(action_handler, machine_spec, machine_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 727 def ready_machine(action_handler, machine_spec, machine_options) machine_options = deep_symbolize_keys(machine_options) instance = instance_for(machine_spec) converge_ec2_tags(instance, machine_options[:aws_tags], action_handler) if instance.nil? raise "Machine #{machine_spec.name} does not have an instance associated with it, or instance does not exist." end if instance.state.name != "running" wait_until_machine(action_handler, machine_spec, "finish stopping", instance) { |instance| instance.state.name != "stopping" } if instance.state.name == "stopped" action_handler.perform_action "Start #{machine_spec.name} (#{machine_spec.reference['instance_id']}) in #{region} ..." do instance.start end end wait_until_instance_running(action_handler, machine_spec, instance) end # Windows machines potentially do a bunch of extra stuff - setting hostname, # sending out encrypted password, restarting instance, etc. if machine_spec.reference["is_windows"] wait_until_machine(action_handler, machine_spec, "receive 'Windows is ready' message from the AWS console", instance) do |instance| instance.console_output.output # seems to be a bug as we need to run this twice # to consistently ensure the output is fully pulled encoded_output = instance.console_output.output if encoded_output.nil? || encoded_output.empty? false else output = Base64.decode64(encoded_output) output =~ /Message: Windows is Ready to use/ end end end wait_for_transport(action_handler, machine_spec, machine_options, instance) machine_for(machine_spec, machine_options, instance) end
region()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 157 def region aws_config_2[:region] end
s3_client()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 841 def s3_client @s3 ||= ::Aws::S3::Client.new(aws_config) end
server_cert_from_spec(spec)
click to toggle source
Retreive the server certificate from a listener spec, prefering the server_certificate key.
# File lib/chef/provisioning/aws_driver/driver.rb, line 564 def server_cert_from_spec(spec) if spec[:server_certificate] spec[:server_certificate] elsif spec[:ssl_certificate_id] spec[:ssl_certificate_id] end end
server_cert_to_string(cert)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 554 def server_cert_to_string(cert) if cert.is_a?(Hash) && cert.key?(:arn) cert[:arn] else cert end end
server_certificate_eql?(cert1, cert2)
click to toggle source
Compare two server certificates by casting them both to strings.
The parameters should either be a String containing the certificate ARN, or a IAM::ServerCertificate object.
# File lib/chef/provisioning/aws_driver/driver.rb, line 550 def server_certificate_eql?(cert1, cert2) server_cert_to_string(cert1) == server_cert_to_string(cert2) end
sns()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 845 def sns @sns ||= ::Aws::SNS::Client.new(aws_config) end
sqs()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 849 def sqs @sqs ||= ::Aws::SQS::Client.new(aws_config) end
ssh_options_for(machine_spec, machine_options, instance)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1293 def ssh_options_for(machine_spec, machine_options, instance) result = { # TODO: create a user known hosts file # :user_known_hosts_file => vagrant_ssh_config['UserKnownHostsFile'], # :paranoid => true, auth_methods: ["publickey"], keys_only: true, host_key_alias: "#{instance.id}.AWS" }.merge(machine_options[:ssh_options] || {}) unless result.key?(:key_data) result[:keys_only] = true result[:key_data] = [private_key_for(machine_spec, machine_options, instance)] end result end
stop_machine(action_handler, machine_spec, machine_options)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 776 def stop_machine(action_handler, machine_spec, machine_options) machine_options = deep_symbolize_keys(machine_options) instance = instance_for(machine_spec) if instance && instance.exists? wait_until_machine(action_handler, machine_spec, "finish coming up so we can stop it", instance) { |instance| instance.state.name != "pending" } if instance.state.name == "running" action_handler.perform_action "Stop #{machine_spec.name} (#{instance.id}) in #{region} ..." do instance.stop end end wait_until_machine(action_handler, machine_spec, "stop", instance) { |instance| %w{stopped terminated}.include?(instance.state.name) } end end
transport_for(machine_spec, machine_options, instance)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1035 def transport_for(machine_spec, machine_options, instance) if machine_spec.reference["is_windows"] create_winrm_transport(machine_spec, machine_options, instance) else create_ssh_transport(machine_spec, machine_options, instance) end end
user_data()
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 647 def user_data # TODO: Make this use HTTPS at some point. <<EOD <powershell> winrm quickconfig -q winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}' winrm set winrm/config '@{MaxTimeoutms="1800000"}' winrm set winrm/config/service '@{AllowUnencrypted="true"}' winrm set winrm/config/service/auth '@{Basic="true"}' netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow net stop winrm sc config winrm start=auto net start winrm </powershell> EOD end
wait_for_transport(action_handler, machine_spec, machine_options, instance = nil)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1388 def wait_for_transport(action_handler, machine_spec, machine_options, instance = nil) instance ||= instance_for(machine_spec) sleep_time = 10 transport = transport_for(machine_spec, machine_options, instance) unless instance.state.name.eql?("running") && transport.available? if action_handler.should_perform_actions action_handler.report_progress "waiting for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be connectable (transport up and running) ..." max_wait_time = Chef::Config.chef_provisioning[:machine_max_wait_time] || 120 Retryable.retryable( tries: (max_wait_time / sleep_time).to_i, sleep: sleep_time, matching: /did not become connectable within/ ) do |retries, _exception| action_handler.report_progress "been waiting #{sleep_time * retries}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{instance.id} on #{driver_url}) to become connectable ..." unless transport.available? raise "Instance #{machine_spec.name} (#{instance.id} on #{driver_url}) did not become connectable within #{max_wait_time} seconds" end end end end end
wait_until_image(action_handler, image_spec, image = nil) { |image| ... }
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1336 def wait_until_image(action_handler, image_spec, image = nil) image ||= image_for(image_spec) sleep_time = 10 unless yield(image) if action_handler.should_perform_actions action_handler.report_progress "waiting for #{image_spec.name} (#{image.id} on #{driver_url}) to be ready ..." max_wait_time = Chef::Config.chef_provisioning[:image_max_wait_time] || 300 Retryable.retryable( tries: (max_wait_time / sleep_time).to_i, sleep: sleep_time, matching: /did not become ready within/ ) do |retries, _exception| action_handler.report_progress "been waiting #{retries * sleep_time}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{image_spec.name} (#{image.id} on #{driver_url}) to become ready ..." # We have to manually reload the instance each loop, otherwise data is stale image.reload unless yield(image) raise "Image #{image.id} did not become ready within #{max_wait_time} seconds" end end end end end
wait_until_instance_running(action_handler, machine_spec, instance = nil)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1359 def wait_until_instance_running(action_handler, machine_spec, instance = nil) wait_until_machine(action_handler, machine_spec, "become ready", instance) do |instance| instance.state.name == "running" end end
wait_until_machine(action_handler, machine_spec, output_msg, instance = nil) { |instance| ... }
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1365 def wait_until_machine(action_handler, machine_spec, output_msg, instance = nil) instance ||= instance_for(machine_spec) sleep_time = 10 unless yield(instance) if action_handler.should_perform_actions action_handler.report_progress "waiting for #{machine_spec.name} (#{instance.id} on #{driver_url}) to #{output_msg} ..." max_wait_time = Chef::Config.chef_provisioning[:machine_max_wait_time] || 120 Retryable.retryable( tries: (max_wait_time / sleep_time).to_i, sleep: sleep_time, matching: /did not #{output_msg} within/ ) do |retries, _exception| action_handler.report_progress "been waiting #{sleep_time * retries}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{instance.id} on #{driver_url}) to #{output_msg} ..." # We have to manually reload the instance each loop, otherwise data is stale instance.reload unless yield(instance) raise "Instance #{machine_spec.name} (#{instance.id} on #{driver_url}) did not #{output_msg} within #{max_wait_time} seconds" end end end end end
wait_until_ready_image(action_handler, image_spec, image = nil)
click to toggle source
# File lib/chef/provisioning/aws_driver/driver.rb, line 1331 def wait_until_ready_image(action_handler, image_spec, image = nil) wait_until_image(action_handler, image_spec, image) { |image| image.state.to_sym == :available } action_handler.report_progress "Image #{image_spec.name} is now ready" end