Class: CapifyEc2

Inherits:
Object
  • Object
show all
Defined in:
lib/capify-ec2.rb

Constant Summary collapse

SLEEP_COUNT =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ec2_config = "config/ec2.yml") ⇒ CapifyEc2

Returns a new instance of CapifyEc2.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/capify-ec2.rb', line 16

def initialize(ec2_config = "config/ec2.yml")
  case ec2_config
  when Hash
    @ec2_config = ec2_config
  when String
    @ec2_config = YAML.load_file ec2_config
  else
    raise ArgumentError, "Invalid ec2_config: #{ec2_config.inspect}"
  end

  # Maintain backward compatibility with previous config format
  @ec2_config[:project_tags] ||= []
  # User can change the Project tag string
  @ec2_config[:aws_project_tag] ||= "Project"
  # User can change the Roles tag string
  @ec2_config[:aws_roles_tag] ||= "Roles"
  # User can change the Options tag string.
  @ec2_config[:aws_options_tag] ||= "Options"

  @ec2_config[:project_tags] << @ec2_config[:project_tag] if @ec2_config[:project_tag]
  
  regions = determine_regions()
  
  @instances = []

  regions.each do |region|
    begin
      servers = Fog::Compute.new( :provider => 'AWS',
                                  :aws_access_key_id => aws_access_key_id,
                                  :aws_secret_access_key => aws_secret_access_key,
                                  :region => region
                                ).servers
    rescue => e
      puts "[Capify-EC2] Unable to connect to AWS: #{e}.".red.bold
      exit 1
    end

    servers.each do |server|
      @instances << server if server.ready?
    end
  end
end

Instance Attribute Details

#ec2_configObject

Returns the value of attribute ec2_config.



10
11
12
# File 'lib/capify-ec2.rb', line 10

def ec2_config
  @ec2_config
end

#instancesObject

Returns the value of attribute instances.



10
11
12
# File 'lib/capify-ec2.rb', line 10

def instances
  @instances
end

#load_balancerObject

Returns the value of attribute load_balancer.



10
11
12
# File 'lib/capify-ec2.rb', line 10

def load_balancer
  @load_balancer
end

Instance Method Details

#aws_access_key_idObject



63
64
65
# File 'lib/capify-ec2.rb', line 63

def aws_access_key_id
  @ec2_config[:aws_access_key_id] || Fog.credentials[:aws_access_key_id] || ENV['AWS_ACCESS_KEY_ID'] || raise("Missing AWS Access Key ID")
end

#aws_secret_access_keyObject



67
68
69
# File 'lib/capify-ec2.rb', line 67

def aws_secret_access_key
  @ec2_config[:aws_secret_access_key] || Fog.credentials[:aws_secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY'] || raise("Missing AWS Secret Access Key")
end

#deregister_instance_from_elb(instance_name) ⇒ Object



166
167
168
169
170
171
172
173
174
# File 'lib/capify-ec2.rb', line 166

def deregister_instance_from_elb(instance_name)
  return unless @ec2_config[:load_balanced]
  instance = get_instance_by_name(instance_name)
  return if instance.nil?
  @@load_balancer = get_load_balancer_by_instance(instance.id)
  return if @@load_balancer.nil?

  elb.deregister_instances_from_load_balancer(instance.id, @@load_balancer.id)
end

#deregister_instance_from_elb_by_dns(server_dns) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/capify-ec2.rb', line 203

def deregister_instance_from_elb_by_dns(server_dns)
  instance = get_instance_by_dns(server_dns)
  load_balancer = get_load_balancer_by_instance(instance.id)

  if load_balancer
    puts "[Capify-EC2] Removing instance from ELB '#{load_balancer.id}'..."

    result = elb.deregister_instances_from_load_balancer(instance.id, load_balancer.id)
    raise "Unable to remove instance from ELB '#{load_balancer.id}'..." unless result.status == 200

    return load_balancer
  end
  false
end

#desired_instances(region = nil) ⇒ Object



120
121
122
# File 'lib/capify-ec2.rb', line 120

def desired_instances(region = nil)
  @ec2_config[:project_tags].empty? ? @instances : project_instances
end

#determine_regionsObject



59
60
61
# File 'lib/capify-ec2.rb', line 59

def determine_regions()
  @ec2_config[:aws_params][:regions] || [@ec2_config[:aws_params][:region]]
end

#display_instancesObject



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/capify-ec2.rb', line 71

def display_instances
  unless desired_instances and desired_instances.any?
    puts "[Capify-EC2] No instances were found using your 'ec2.yml' configuration.".red.bold
    return
  end
  
  # Set minimum widths for the variable length instance attributes.
  column_widths = { :name_min => 4, :type_min => 4, :dns_min => 5, :roles_min => @ec2_config[:aws_roles_tag].length, :options_min => @ec2_config[:aws_options_tag].length }

  # Find the longest attribute across all instances, to format the columns properly.
  column_widths[:name]    = desired_instances.map{|i| i.name.to_s.ljust( column_widths[:name_min] )                                   || ' ' * column_widths[:name_min]    }.max_by(&:length).length
  column_widths[:type]    = desired_instances.map{|i| i.flavor_id                                                                     || ' ' * column_widths[:type_min]    }.max_by(&:length).length
  column_widths[:dns]     = desired_instances.map{|i| i.contact_point.to_s.ljust( column_widths[:dns_min] )                           || ' ' * column_widths[:dns_min]     }.max_by(&:length).length
  column_widths[:roles]   = desired_instances.map{|i| i.tags[@ec2_config[:aws_roles_tag]].to_s.ljust( column_widths[:roles_min] )     || ' ' * column_widths[:roles_min]   }.max_by(&:length).length
  column_widths[:options] = desired_instances.map{|i| i.tags[@ec2_config[:aws_options_tag]].to_s.ljust( column_widths[:options_min] ) || ' ' * column_widths[:options_min] }.max_by(&:length).length

  # Title row.
  puts "#{@ec2_config[:aws_project_tag].bold}: #{@ec2_config[:project_tags].join(', ')}." if @ec2_config[:project_tags].any?
  puts sprintf "%-3s   %s   %s   %s   %s   %s   %s   %s", 
    'Num'                                                         .bold,
    'Name'                       .ljust( column_widths[:name]    ).bold,
    'ID'                         .ljust( 10                      ).bold,
    'Type'                       .ljust( column_widths[:type]    ).bold,
    'DNS'                        .ljust( column_widths[:dns]     ).bold,
    'Zone'                       .ljust( 10                      ).bold,
    @ec2_config[:aws_roles_tag]  .ljust( column_widths[:roles]   ).bold,
    @ec2_config[:aws_options_tag].ljust( column_widths[:options] ).bold

  desired_instances.each_with_index do |instance, i|
    puts sprintf "%02d:   %-10s   %s   %s   %s   %-10s   %s   %s",
      i, 
      (instance.name || '')                               .ljust( column_widths[:name]    ).green,
      instance.id                                         .ljust( 2                       ).red,
      instance.flavor_id                                  .ljust( column_widths[:type]    ).cyan,
      instance.contact_point                              .ljust( column_widths[:dns]     ).blue.bold,
      instance.availability_zone                          .ljust( 10                      ).magenta,
      (instance.tags[@ec2_config[:aws_roles_tag]] || '')  .ljust( column_widths[:roles]   ).yellow,
      (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow
  end
end

#elbObject



145
146
147
# File 'lib/capify-ec2.rb', line 145

def elb
  Fog::AWS::ELB.new(:aws_access_key_id => aws_access_key_id, :aws_secret_access_key => aws_secret_access_key, :region => @ec2_config[:aws_params][:region])
end

#get_instance_by_dns(dns) ⇒ Object



137
138
139
# File 'lib/capify-ec2.rb', line 137

def get_instance_by_dns(dns)
  desired_instances.select {|instance| instance.dns_name == dns}.first
end

#get_instance_by_name(name) ⇒ Object



133
134
135
# File 'lib/capify-ec2.rb', line 133

def get_instance_by_name(name)
  desired_instances.select {|instance| instance.name == name}.first
end

#get_instances_by_region(roles, region) ⇒ Object



128
129
130
131
# File 'lib/capify-ec2.rb', line 128

def get_instances_by_region(roles, region)
  return unless region
  desired_instances.select {|instance| instance.availability_zone.match(region) && instance.tags[@ec2_config[:aws_roles_tag]].split(%r{,\s*}).include?(roles.to_s) rescue false}
end

#get_instances_by_role(role) ⇒ Object



124
125
126
# File 'lib/capify-ec2.rb', line 124

def get_instances_by_role(role)
  desired_instances.select {|instance| instance.tags[@ec2_config[:aws_roles_tag]].split(%r{,\s*}).include?(role.to_s) rescue false}
end

#get_load_balancer_by_instance(instance_id) ⇒ Object



149
150
151
152
153
154
155
# File 'lib/capify-ec2.rb', line 149

def get_load_balancer_by_instance(instance_id)
  hash = elb.load_balancers.inject({}) do |collect, load_balancer|
    load_balancer.instances.each {|load_balancer_instance_id| collect[load_balancer_instance_id] = load_balancer}
    collect
  end
  hash[instance_id]
end

#get_load_balancer_by_name(load_balancer_name) ⇒ Object



157
158
159
160
161
162
163
164
# File 'lib/capify-ec2.rb', line 157

def get_load_balancer_by_name(load_balancer_name)
  lbs = {}
  elb.load_balancers.each do |load_balancer|
    lbs[load_balancer.id] = load_balancer
  end
  lbs[load_balancer_name]

end

#instance_health(load_balancer, instance) ⇒ Object



141
142
143
# File 'lib/capify-ec2.rb', line 141

def instance_health(load_balancer, instance)
  elb.describe_instance_health(load_balancer.id, instance.id).body['DescribeInstanceHealthResult']['InstanceStates'][0]['State']
end

#instance_health_by_url(dns, port, path, expected_response, options = {}) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/capify-ec2.rb', line 246

def instance_health_by_url(dns, port, path, expected_response, options = {})
  def response_matches_expected?(response, expected_response)
    if expected_response.kind_of?(Array)
      expected_response.any?{ |r| response_matches_expected?(response, r) }
    elsif expected_response.kind_of?(Regexp)
      (response =~ expected_response) != nil
    else
      response == expected_response
    end
  end

  protocol = options[:https] ? 'https://' : 'http://'
  uri = URI("#{protocol}#{dns}:#{port}#{path}")
  
  puts "[Capify-EC2] Checking '#{uri}' for the content '#{expected_response.inspect}'..."    

  http = Net::HTTP.new(uri.host, uri.port)

  if uri.scheme == 'https'
   http.use_ssl = true
   http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  result = nil

  begin
    Timeout::timeout(options[:timeout]) do
      begin
        result = http.get(uri.path)
        raise "Server responded with '#{result.code}: #{result.body}', expected '#{expected_response}'" unless response_matches_expected?(result.body, expected_response)
      rescue => e
        puts "[Capify-EC2] Unexpected response: #{e}..."
        sleep 1
        retry
      end
    end
  rescue Timeout::Error => e
  end
  result ? response_matches_expected?(result.body, expected_response) : false
end

#project_instancesObject



116
117
118
# File 'lib/capify-ec2.rb', line 116

def project_instances
  @instances.select {|instance| @ec2_config[:project_tags].include?(instance.tags[@ec2_config[:aws_project_tag]])}
end

#register_instance_in_elb(instance_name, load_balancer_name = '') ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/capify-ec2.rb', line 176

def register_instance_in_elb(instance_name, load_balancer_name = '')
  return if !@ec2_config[:load_balanced]
  instance = get_instance_by_name(instance_name)
  return if instance.nil?
  load_balancer =  get_load_balancer_by_name(load_balancer_name) || @@load_balancer
  return if load_balancer.nil?

  elb.register_instances_with_load_balancer(instance.id, load_balancer.id)

  fail_after = @ec2_config[:fail_after] || 30
  state = instance_health(load_balancer, instance)
  time_elapsed = 0
  
  while time_elapsed < fail_after
    break if state == "InService"
    sleep SLEEP_COUNT
    time_elapsed += SLEEP_COUNT
    STDERR.puts 'Verifying Instance Health'
    state = instance_health(load_balancer, instance)
  end
  if state == 'InService'
    STDERR.puts "#{instance.name}: Healthy"
  else
    STDERR.puts "#{instance.name}: tests timed out after #{time_elapsed} seconds."
  end
end

#reregister_instance_with_elb_by_dns(server_dns, load_balancer, timeout) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/capify-ec2.rb', line 218

def reregister_instance_with_elb_by_dns(server_dns, load_balancer, timeout)
  instance = get_instance_by_dns(server_dns)

  sleep 10

  puts "[Capify-EC2] Re-registering instance with ELB '#{load_balancer.id}'..."
  result = elb.register_instances_with_load_balancer(instance.id, load_balancer.id)

  raise "Unable to re-register instance with ELB '#{load_balancer.id}'..." unless result.status == 200

  state = nil

  begin
    Timeout::timeout(timeout) do
      begin
        state = instance_health(load_balancer, instance)
        raise "Instance not ready" unless state == 'InService'
      rescue => e
        puts "[Capify-EC2] Unexpected response: #{e}..."
        sleep 1
        retry
      end
    end
  rescue Timeout::Error => e
  end
  state ? state == 'InService' : false
end

#response_matches_expected?(response, expected_response) ⇒ Boolean

Returns:

  • (Boolean)


247
248
249
250
251
252
253
254
255
# File 'lib/capify-ec2.rb', line 247

def response_matches_expected?(response, expected_response)
  if expected_response.kind_of?(Array)
    expected_response.any?{ |r| response_matches_expected?(response, r) }
  elsif expected_response.kind_of?(Regexp)
    (response =~ expected_response) != nil
  else
    response == expected_response
  end
end

#server_namesObject



112
113
114
# File 'lib/capify-ec2.rb', line 112

def server_names
  desired_instances.map {|instance| instance.name}
end