Class: AwsRegion::AwsInstance

Inherits:
AwsBase
  • Object
show all
Defined in:
lib/aws_region.rb

Overview

Class to handle EC2 Instances

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from AwsBase

#log

Constructor Details

#initialize(region, options = {}) ⇒ AwsInstance

Returns a new instance of AwsInstance.


478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/aws_region.rb', line 478

def initialize(region, options = {})
  @tags = {}
  @region = region
  if options.has_key?(:instance)
    @_instance = options[:instance]
    @id = @_instance[:instance_id]
    @public_ip = @_instance[:public_ip_address]
    @private_ip = @_instance[:private_ip_address]
  else
    resp = @region.ec2.run_instances(options[:template])
    raise "Error creating instance using options" if resp.nil? or resp[:instances].length <= 0
    @_instance = resp[:instances][0]
    @id = @_instance[:instance_id]
    @tags = options[:tags]
    self.add_tags(@tags)
    self.wait
    instance = @region.ec2.describe_instances(:instance_ids => [@id])[0][0].instances[0]
    @public_ip = instance[:public_ip_address]
    @private_ip = instance[:private_ip_address]
    raise "could not get ip address" if @public_ip.nil? && @private_ip.nil?
    self.inject_into_environment
  end
  @_instance.tags.each do |t|
    @tags[t[:key].to_sym] = t[:value]
  end
end

Instance Attribute Details

#_instanceObject

Returns the value of attribute _instance


476
477
478
# File 'lib/aws_region.rb', line 476

def _instance
  @_instance
end

#idObject

Returns the value of attribute id


476
477
478
# File 'lib/aws_region.rb', line 476

def id
  @id
end

#private_ipObject

Returns the value of attribute private_ip


476
477
478
# File 'lib/aws_region.rb', line 476

def private_ip
  @private_ip
end

#public_ipObject

Returns the value of attribute public_ip


476
477
478
# File 'lib/aws_region.rb', line 476

def public_ip
  @public_ip
end

#regionObject

Returns the value of attribute region


476
477
478
# File 'lib/aws_region.rb', line 476

def region
  @region
end

#tagsObject

Returns the value of attribute tags


476
477
478
# File 'lib/aws_region.rb', line 476

def tags
  @tags
end

Instance Method Details

#add_tags(h_tags) ⇒ Object

Add tags to an instance

Parameters:

  • h_tags (Hash)
    • Hash of tags to add to instance


554
555
556
557
558
559
560
561
# File 'lib/aws_region.rb', line 554

def add_tags(h_tags)
  tags = []
  h_tags.each do |k, v|
    tags << {:key => k.to_s, :value => v}
  end
  resp = @region.ec2.create_tags({:resources => [@id],
                                  :tags => tags})
end

#add_to_lb(lb_name) ⇒ Object

Add an instance to an elastic lb

Parameters:

  • lb_name (String)
    • Name of elastic load balancer


565
566
567
568
# File 'lib/aws_region.rb', line 565

def add_to_lb(lb_name)
  @region.elb.register_instances_with_load_balancer({:load_balancer_name => lb_name,
                                                     :instances => [{:instance_id => @id}]})
end

#authorize_sg_ingress(groups) ⇒ Object

authorize security group ingress for public ip of this instance on port

Parameters:

  • groups (Array)
    • each element is String: “security_group_id:port”. For example: [“sg-0xxxxx:80”, “sg-0xxxxx:8443”, “sg-0yyyyy:3000”]


702
703
704
705
706
707
708
709
710
711
712
713
# File 'lib/aws_region.rb', line 702

def authorize_sg_ingress(groups)
  raise "no public ip" unless @public_ip.to_s.match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
  groups.each do |gp|
    options = get_simple_sg_options(gp)
    if has_sg_rule?(gp)
      log "security group rule #{gp} for #{self.public_ip} already exists"
    else
      @region.ec2.authorize_security_group_ingress options
      log "added #{self.public_ip} to security group for :port #{gp}"
    end
  end
end

#connectObject

Connects using ssh to an ec2 instance


617
618
619
620
621
622
623
624
625
# File 'lib/aws_region.rb', line 617

def connect
  if self.state(use_cached_state = false) != "running"
    log "Cannot connect, instance: #{@region.region}://#{@id} due to its state: #{self.state}"
    return
  end
  ip = (self.public_ip && self.public_ip.strip() != "") ? self.public_ip : self.private_ip
  log "Connecting: ssh -i ~/.ssh/aws/#{@tags[:key]} #{@tags[:user]}@#{ip}"
  exec "ssh -i ~/.ssh/aws/#{@tags[:key]} #{@tags[:user]}@#{ip}"
end

#eject_from_environmentObject


626
627
628
629
630
631
632
633
634
# File 'lib/aws_region.rb', line 626

def eject_from_environment
  if @tags.has_key?(:elastic_lb)
    log "Removing instance: #{@id} from '#{@tags[:elastic_lb]}' load balancer"
    self.remove_from_lb(tags[:elastic_lb])
  end
  if @tags.has_key?(:security_groups_foreign)
    self.revoke_sg_ingress(@tags[:security_groups_foreign].split(","))
  end
end

#get_simple_sg_options(group_id_port) ⇒ Hash

construct hash for authorize/revoke ingress and has_sg_rule?

Parameters:

  • group_id_port (String)
    • security_group:port like “sg_xxxxxx:8080”

Returns:

  • (Hash)
    • Hash for ec2 call for security group management


740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/aws_region.rb', line 740

def get_simple_sg_options(group_id_port)
  security_group_id, port = group_id_port.split(':')
  port = port.to_s.to_i
  raise "no security group id" unless security_group_id.to_s.length > 0
  raise "no, or invalid port" unless port.to_s.to_i > 0
  {:group_id => security_group_id,
   :ip_permissions => [ :ip_protocol => "tcp",
                        :from_port => port,
                        :to_port => port,
                        :ip_ranges => [:cidr_ip => "#{self.public_ip}/32"]
                      ]
  }
end

#has_sg_rule?(group_port) ⇒ Boolean

Does a security group allow ingress on a port for the public IP of this instance

Parameters:

  • group_port (String)
    • security_group:port like “sg_xxxxxx:8080”

Returns:

  • (Boolean)

    true if the security group allows ingress for the public IP of this instance on a certain port


682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/aws_region.rb', line 682

def has_sg_rule?(group_port)
  options = get_simple_sg_options(group_port)
  options_cidr_ip = options[:ip_permissions][0][:ip_ranges][0][:cidr_ip]
  group_id = options[:group_id]
  raise "missing security group_id" if group_id.nil?
  sg = @region.ec2.describe_security_groups(:group_ids => [group_id]).data.security_groups[0]
  sg.ip_permissions.each do |p|
    if p.ip_protocol == "tcp" &&
        p.from_port == options[:ip_permissions][0][:from_port] &&
        p.to_port == options[:ip_permissions][0][:to_port]
      p[:ip_ranges].each do |ipr|
        return true if ipr.cidr_ip == options_cidr_ip
      end
    end
  end
  false
end

#inject_into_environmentObject


636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
# File 'lib/aws_region.rb', line 636

def inject_into_environment
  if @tags.has_key?(:elastic_ip)
    @region.ec2.associate_address({:instance_id => @id, :public_ip => @tags[:elastic_ip]})
    log "Associated ip: #{@tags[:elastic_ip]} with instance: #{@id}"
  elsif @tags.has_key?(:elastic_ip_allocation_id)
    @region.ec2.associate_address({:instance_id => @id, :allocation_id => @tags[:elastic_ip_allocation_id]})
    log "Associated allocation id: #{@tags[:elastic_ip_allocation_id]} with instance: #{@id}"
  end
  if @tags.has_key?(:mount_points)
    mounts = @tags[:mount_points].split(";")
    mounts.each do |mnt|
      (volume_id,device) = mnt.split(",")
      log "Mounting volume: #{volume_id} on #{device}"
      self.mount(volume_id, device)
    end
  end
  if @tags.has_key?(:security_group_ids)
    self.set_security_groups(@tags[:security_group_ids].split(","))
  end
  if @tags.has_key?(:security_groups_foreign)
    self.authorize_sg_ingress(@tags[:security_groups_foreign].split(","))
  end

  # if any of the above fails, we probably do not want it in the lb
  if @tags.has_key?(:elastic_lb)
    self.add_to_lb(@tags[:elastic_lb])
    log "Adding instance: #{@id} to '#{@tags[:elastic_lb]}' load balancer"
  end
end

#mount(volume_id, device) ⇒ Object


731
732
733
734
735
# File 'lib/aws_region.rb', line 731

def mount(volume_id, device)
  @region.ec2.attach_volume({:instance_id => @id,
                             :volume_id => volume_id,
                             :device => device})
end

#remove_from_lb(lb_name) ⇒ Aws::PageableResponse

Remove instance from elastic lb

Parameters:

  • instance (AwsInstance)

    Instance to remove from lb

  • lb_name (String)

    Lb name from which the instance is to be removed

Returns:

  • (Aws::PageableResponse)

574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/aws_region.rb', line 574

def remove_from_lb(lb_name)
  lb = @region.elb.describe_load_balancers({:load_balancer_names => [lb_name]})
  if lb and lb[:load_balancer_descriptions].length > 0
    lb[:load_balancer_descriptions][0][:instances].each do |lb_i|
      if lb_i[:instance_id] == @id
        @elb.deregister_instances_from_load_balancer({:load_balancer_name => lb_name,
                                                      :instances => [{:instance_id => @id}]})
        sleep 30
      end
    end
  end
end

#revoke_sg_ingress(groups) ⇒ Object

revoke security group ingress for public ip of this instance on port

Parameters:

  • groups (Array)
    • each element is String: “security_group_id:port”. For example: [“sg-0xxxxx:80”, “sg-0xxxxx:8443”, “sg-0yyyyy:3000”]


717
718
719
720
721
722
723
724
725
726
727
728
729
730
# File 'lib/aws_region.rb', line 717

def revoke_sg_ingress(groups)
  # revoke the public ip of this instance for ingress on port for security group
  # groups is array of strings: security_group_id:port
  raise "no public ip" unless @public_ip.to_s.match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
  groups.each do |gp|
    options = get_simple_sg_options(gp)
    if has_sg_rule?(gp)
      @region.ec2.revoke_security_group_ingress options
      log "removed #{self.public_ip} from security group for :port #{gp}"
    else
      log "not removing #{self.public_ip} rule #{gp} because it does not exist"
    end
  end
end

#set_security_groups(groups) ⇒ Object


674
675
676
677
# File 'lib/aws_region.rb', line 674

def set_security_groups(groups)
  # only works on instances in a vpc
  @region.ec2.modify_instance_attribute({:instance_id => @id, :groups => groups})
end

#start(wait = false) ⇒ Object

Start an EC2 instance

Parameters:

  • wait (Boolean) (defaults to: false)
    • When true, will wait for instance to move into “running” state before returning


526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/aws_region.rb', line 526

def start(wait=false)
  if self.state(use_cached_state = false) != "stopped"
    log "Instance cannot be started - #{@region.region}://#{@id} is in the state: #{self.state}"
    return
  end
  log "Starting instance: #{@region.region}://#{@id}"
  @region.ec2.start_instances({:instance_ids => [@id]})
  if wait
    begin
      sleep 10
      log "Starting instance: #{@region.region}://#{@id} - state: #{self.state}"
    end while self.state(use_cached_state = false) != "running"
  end
  if @tags.has_key?("elastic_ip")
    @region.ec2.associate_address({:instance_id => @id, :public_ip => @tags['elastic_ip']})
    log "Associated ip: #{@tags['elastic_ip']} with instance: #{@id}"
  elsif @tags.has_key?("elastic_ip_allocation_id")
    @region.ec2.associate_address({:instance_id => @id, :allocation_id => @tags['elastic_ip_allocation_id']})
    log "Associated allocation id: #{@tags['elastic_ip_allocation_id']} with instance: #{@id}"
  end
  if @tags.has_key?("elastic_lb")
    self.add_to_lb(@tags["elastic_lb"])
    log "Adding instance: #{@id} to '#{@tags['elastic_lb']}' load balancer"
  end
end

#state(use_cached_state = true) ⇒ Object

Determine the state of an ec2 instance

Parameters:

  • use_cached_state (Boolean) (defaults to: true)
    • When true will use a cached version of the state rather than querying EC2 directly


508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/aws_region.rb', line 508

def state(use_cached_state=true)
  if !use_cached_state
    response = @region.ec2.describe_instances({instance_ids: [@id]})
    response[:reservations].each do |res|
      res[:instances].each do |inst|
        if inst[:instance_id] == @id
          return inst[:state][:name].strip()
        end
      end
    end
    return ""
  else
    @_instance.state[:name].strip()
  end
end

#stop(wait = false) ⇒ Object

Stops an ec2 instance

Parameters:

  • wait (Boolean) (defaults to: false)
    • When true, will wait for the instance to be completely stopped before returning


595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'lib/aws_region.rb', line 595

def stop(wait=false)
  if self.state(use_cached_state = false) != "running"
    log "Instance cannot be stopped - #{@region.region}://#{@id} is in the state: #{self.state}"
    return
  end
  self.eject_from_environment
  if @tags.has_key?("elastic_lb")
    log "Removing instance: #{@id} from '#{@tags['elastic_lb']}' load balancer"
    remove_from_lb(tags["elastic_lb"])
  end
  log "Stopping instance: #{@region.region}://#{@id}"
  @region.ec2.stop_instances({:instance_ids => [@id]})
  while self.state(use_cached_state = false) != "stopped"
    sleep 10
    log "Stopping instance: #{@region.region}://#{@id} - state: #{self.state}"
  end if wait
  if self.state(use_cached_state = false) == "stopped"
    log "Instance stopped: #{@region.region}://#{@id}"
  end
end

#terminateObject

Terminates ec2 instance


588
589
590
591
# File 'lib/aws_region.rb', line 588

def terminate()
  eject_from_environment
  @region.ec2.terminate_instances({:instance_ids => [@id]})
end

#wait(options = {:timeout => 300, :desired_status => "running"}) ⇒ Object


665
666
667
668
669
670
671
672
# File 'lib/aws_region.rb', line 665

def wait(options = {:timeout => 300, :desired_status => "running"})
  t0 = Time.now.to_i
  begin
    sleep 10
    log "Waiting on instance: #{@region.region}://#{@id} - current state: #{self.state}"
    return if Time.now.to_i - t0 > options[:timeout]
  end while self.state(use_cached_state = false) != options[:desired_status]
end