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", stage = '') ⇒ 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
# File 'lib/capify-ec2.rb', line 16

def initialize(ec2_config = "config/ec2.yml", stage = '')
  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
  @ec2_config[:stage] = stage

  # 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"
  # User can change the Stages tag string
  @ec2_config[:aws_stages_tag] ||= "Stages"

  @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', :region => region}.merge!(security_credentials) ).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



71
72
73
# File 'lib/capify-ec2.rb', line 71

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

#aws_secret_access_keyObject



75
76
77
# File 'lib/capify-ec2.rb', line 75

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

#deregister_instance_from_elb(instance_name) ⇒ Object



192
193
194
195
196
197
198
199
200
# File 'lib/capify-ec2.rb', line 192

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



229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/capify-ec2.rb', line 229

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



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

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

#determine_regionsObject



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

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

#display_instancesObject



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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/capify-ec2.rb', line 79

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, :stages_min => @ec2_config[:aws_stages_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[:stages]  = desired_instances.map{|i| i.tags[@ec2_config[:aws_stages_tag]].to_s.ljust( column_widths[:stages_min] )   || ' ' * column_widths[:stages_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

  roles_present   = desired_instances.map{|i| i.tags[@ec2_config[:aws_roles_tag]].to_s}.max_by(&:length).length > 0
  options_present = desired_instances.map{|i| i.tags[@ec2_config[:aws_options_tag]].to_s}.max_by(&:length).length > 0
  stages_present  = desired_instances.map{|i| i.tags[@ec2_config[:aws_stages_tag]].to_s}.max_by(&:length).length > 0

  # Project and Stages info..
  info_label_width = [@ec2_config[:aws_project_tag], @ec2_config[:aws_stages_tag]].map(&:length).max
  puts "#{@ec2_config[:aws_project_tag].rjust( info_label_width ).bold}: #{@ec2_config[:project_tags].join(', ')}." if @ec2_config[:project_tags].any?
  puts "#{@ec2_config[:aws_stages_tag].rjust( info_label_width ).bold}: #{@ec2_config[:stage]}." unless @ec2_config[:stage].to_s.empty?
  
  # Title row.
  status_output = []
  status_output << 'Num'                                                         .bold
  status_output << 'Name'                       .ljust( column_widths[:name]    ).bold
  status_output << 'ID'                         .ljust( 10                      ).bold
  status_output << 'Type'                       .ljust( column_widths[:type]    ).bold
  status_output << 'DNS'                        .ljust( column_widths[:dns]     ).bold
  status_output << 'Zone'                       .ljust( 10                      ).bold
  status_output << @ec2_config[:aws_stages_tag] .ljust( column_widths[:stages]  ).bold if stages_present
  status_output << @ec2_config[:aws_roles_tag]  .ljust( column_widths[:roles]   ).bold if roles_present
  status_output << @ec2_config[:aws_options_tag].ljust( column_widths[:options] ).bold if options_present
  puts status_output.join("   ")
  
  desired_instances.each_with_index do |instance, i|
    status_output = []
    status_output << "%02d:" % i
    status_output << (instance.name || '')                               .ljust( column_widths[:name]    ).green
    status_output << instance.id                                         .ljust( 2                       ).red
    status_output << instance.flavor_id                                  .ljust( column_widths[:type]    ).cyan
    status_output << instance.contact_point                              .ljust( column_widths[:dns]     ).blue.bold
    status_output << instance.availability_zone                          .ljust( 10                      ).magenta
    status_output << (instance.tags[@ec2_config[:aws_stages_tag]]  || '').ljust( column_widths[:stages]  ).yellow if stages_present
    status_output << (instance.tags[@ec2_config[:aws_roles_tag]]   || '').ljust( column_widths[:roles]   ).yellow if roles_present
    status_output << (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow if options_present
    puts status_output.join("   ")
  end
end

#elbObject



171
172
173
# File 'lib/capify-ec2.rb', line 171

def elb
  Fog::AWS::ELB.new({:region => @ec2_config[:aws_params][:region]}.merge!(security_credentials))
end

#get_instance_by_dns(dns) ⇒ Object



163
164
165
# File 'lib/capify-ec2.rb', line 163

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

#get_instance_by_name(name) ⇒ Object



159
160
161
# File 'lib/capify-ec2.rb', line 159

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

#get_instances_by_region(roles, region) ⇒ Object



154
155
156
157
# File 'lib/capify-ec2.rb', line 154

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



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

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_instances_by_stage(instances = @instances) ⇒ Object



150
151
152
# File 'lib/capify-ec2.rb', line 150

def get_instances_by_stage(instances=@instances)
  instances.select {|instance| instance.tags[@ec2_config[:aws_stages_tag]].split(%r{,\s*}).include?(@ec2_config[:stage].to_s) rescue false}
end

#get_load_balancer_by_instance(instance_id) ⇒ Object



175
176
177
178
179
180
181
# File 'lib/capify-ec2.rb', line 175

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



183
184
185
186
187
188
189
190
# File 'lib/capify-ec2.rb', line 183

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



167
168
169
# File 'lib/capify-ec2.rb', line 167

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



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/capify-ec2.rb', line 272

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



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

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



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/capify-ec2.rb', line 202

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



244
245
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
# File 'lib/capify-ec2.rb', line 244

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)


273
274
275
276
277
278
279
280
281
# File 'lib/capify-ec2.rb', line 273

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

#security_credentialsObject



58
59
60
61
62
63
64
65
# File 'lib/capify-ec2.rb', line 58

def security_credentials
  if @ec2_config[:use_iam_profile]
    { :use_iam_profile       => true }
  else
    { :aws_access_key_id     => aws_access_key_id,
      :aws_secret_access_key => aws_secret_access_key }
  end
end

#server_namesObject



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

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