Class: Terrafying::Aws::Ops

Inherits:
Object
  • Object
show all
Defined in:
lib/terrafying/aws.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(region) ⇒ Ops

Returns a new instance of Ops.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/terrafying/aws.rb', line 20

def initialize(region)
  half_jitter = lambda { |c|
    sleep_time = 0.5 * (2**c.retries)
    Kernel.sleep(Kernel.rand((sleep_time / 2)..sleep_time))
  }

  ::Aws.config.update(
    region: region,
    retry_limit: 7,
    retry_backoff: half_jitter
  )

  @autoscaling_client = ::Aws::AutoScaling::Client.new
  @ec2_resource = ::Aws::EC2::Resource.new
  @ec2_client = ::Aws::EC2::Client.new
  @elb_client = ::Aws::ElasticLoadBalancingV2::Client.new
  @route53_client = ::Aws::Route53::Client.new
  @s3_client = ::Aws::S3::Client.new
  @sts_client = ::Aws::STS::Client.new
  @pricing_client = ::Aws::Pricing::Client.new(region: 'us-east-1') # no AWS Pricing endpoint in Europe
  @msk_client = ::Aws::Kafka::Client.new
  @region = region
end

Instance Attribute Details

#regionObject (readonly)

Returns the value of attribute region.



18
19
20
# File 'lib/terrafying/aws.rb', line 18

def region
  @region
end

Instance Method Details

#account_idObject



44
45
46
# File 'lib/terrafying/aws.rb', line 44

def 
  @account_id_cache ||= @sts_client.get_caller_identity.
end

#all_regionsObject



48
49
50
# File 'lib/terrafying/aws.rb', line 48

def all_regions
  @all_regions ||= @ec2_client.describe_regions.regions.map(&:region_name)
end

#all_security_groupsObject



52
53
54
# File 'lib/terrafying/aws.rb', line 52

def all_security_groups
  @all_security_groups ||= @ec2_resource.security_groups.to_a
end

#ami(name, owners = ['self']) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/terrafying/aws.rb', line 263

def ami(name, owners = ['self'])
  @ami ||= {}
  @ami[name] ||=
    begin
      warn "looking for an image with prefix '#{name}'"
      resp = @ec2_client.describe_images(owners: owners)
      raise 'no images were found' if resp.images.count < 1

      m = resp.images.select { |a| /^#{name}/.match(a.name) }
      raise "no image with name '#{name}' was found" if m.count == 0

      m.sort { |x, y| y.creation_date <=> x.creation_date }.shift.image_id
    end
end

#asgs_by_tags(expectedTags = {}) ⇒ Object



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/terrafying/aws.rb', line 489

def asgs_by_tags(expectedTags = {})
  asgs = []
  next_token = nil

  loop do
    resp = @autoscaling_client.describe_auto_scaling_groups(next_token: next_token)

    asgs += resp.auto_scaling_groups.select do |asg|
      matches = asg.tags.select do |tag|
        expectedTags[tag.key.to_sym] == tag.value ||
          expectedTags[tag.key] == tag.value
      end

      matches.count == expectedTags.count
    end

    if resp.next_token
      next_token = resp.next_token
    else
      break
    end
  end

  asgs
end

#availability_zonesObject



278
279
280
281
282
283
284
285
# File 'lib/terrafying/aws.rb', line 278

def availability_zones
  @availability_zones ||=
    begin
      warn 'looking for AZs in the current region'
      resp = @ec2_client.describe_availability_zones({})
      resp.availability_zones.map(&:zone_name)
    end
end

#elastic_ip(alloc_id) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/terrafying/aws.rb', line 330

def elastic_ip(alloc_id)
  @ips ||= {}
  @ips[alloc_id] ||=
    begin
      warn "looking for an elastic ip with allocation_id '#{alloc_id}'"
      ips = @ec2_client.describe_addresses(
        filters: [
          {
            name: 'allocation-id',
            values: [alloc_id]
          }
        ]
      ).addresses
      if ips.count == 1
        ips.first
      elsif ips.count < 1
        raise "No elastic ip with allocation_id '#{alloc_id}' was found."
      elsif ips.count > 1
        raise "More than one elastic ip with allocation_id '#{alloc_id}' was found: " + ips.join(', ')
      end
    end
end

#endpoint_service_by_lb_arn(arn) ⇒ Object



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/terrafying/aws.rb', line 441

def endpoint_service_by_lb_arn(arn)
  @endpoint_services_by_lb_arn ||= {}
  @endpoint_services_by_lb_arn[arn] ||=
    begin
      resp = @ec2_client.describe_vpc_endpoint_service_configurations

      services = resp.service_configurations.select do |service|
        service.network_load_balancer_arns.include?(arn)
      end

      if services.count == 1
        services.first
      elsif services.count < 1
        raise "No endpoint service with lb arn '#{arn}' was found."
      elsif services.count > 1
        raise "More than one endpoint service with lb arn '#{arn}' was found: " + services.join(', ')
      end
    end
end

#endpoint_service_by_name(service_name) ⇒ Object



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/terrafying/aws.rb', line 417

def endpoint_service_by_name(service_name)
  @endpoint_service ||= {}
  @endpoint_service[service_name] ||=
    begin
      resp = @ec2_client.describe_vpc_endpoint_service_configurations(
        filters: [
          {
            name: 'service-name',
            values: [service_name]
          }
        ]
      )

      endpoint_services = resp.service_configurations
      if endpoint_services.count == 1
        endpoint_services.first
      elsif endpoint_services.count < 1
        raise "No endpoint service with name '#{service_name}' was found."
      elsif endpoint_services.count > 1
        raise "More than one endpoint service with name '#{service_name}' was found: " + endpoint_services.join(', ')
      end
    end
end

#hosted_zone(fqdn) ⇒ Object



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/terrafying/aws.rb', line 353

def hosted_zone(fqdn)
  @hosted_zones ||= {}
  @hosted_zones[fqdn] ||=
    begin
      warn "looking for a hosted zone with fqdn '#{fqdn}'"
      hosted_zones = @route53_client.list_hosted_zones_by_name(dns_name: fqdn).hosted_zones.select do |zone|
        zone.name == "#{fqdn}." && !zone.config.private_zone
      end
      if hosted_zones.count == 1
        hosted_zones.first
      elsif hosted_zones.count < 1
        raise "No hosted zone with fqdn '#{fqdn}' was found."
      elsif hosted_zones.count > 1
        raise "More than one hosted zone with name '#{fqdn}' was found: " + hosted_zones.join(', ')
      end
    end
end

#hosted_zone_by_tag(tag) ⇒ Object



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/terrafying/aws.rb', line 371

def hosted_zone_by_tag(tag)
  @hosted_zones ||= {}
  @hosted_zones[tag] ||=
    begin
      warn "looking for a hosted zone with tag '#{tag}'"
      @aws_hosted_zones ||= @route53_client.list_hosted_zones.hosted_zones.map do |zone|
        {
          zone: zone,
          tags: @route53_client.list_tags_for_resource(resource_type: 'hostedzone', resource_id: zone.id.split('/')[2]).resource_tag_set.tags
        }
      end

      hosted_zones = @aws_hosted_zones.select do |z|
        z[:tags].any? do |aws_tag|
          tag.any? { |k, v| aws_tag.key = String(k) && aws_tag.value == v }
        end
      end

      if hosted_zones.count == 1
        hosted_zones.first[:zone]
      elsif hosted_zones.count < 1
        raise "No hosted zone with tag '#{tag}' was found."
      elsif hosted_zones.count > 1
        raise "More than one hosted zone with tag '#{tag}' was found: " + hosted_zones.join(', ')
      end
    end
end

#instance_profile(name) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/terrafying/aws.rb', line 103

def instance_profile(name)
  @instance_profiles ||= {}
  @instance_profiles[name] ||=
    begin
      resource = ::Aws::IAM::Resource.new
      warn "Looking up id of instance profile '#{name}'"
      # unfortunately amazon don't let us filter for profiles using
      # a name filter, for now we have enumerate and filter manually
      coll = resource.instance_profiles
      profiles = []
      profiles = coll.select { |p| p.instance_profile_name =~ /#{name}/ }

      if profiles.count == 1
        profiles.first.instance_profile_id
      elsif profiles.count < 1
        raise "No instance profile with name '#{name}' was found."
      elsif profiles.count > 1
        raise "More than one instance profile with name '#{name}' found: " + profiles.join(', ')
      end
    end
end

#instance_type_vcpu_count(instance_type, location = 'EU (Ireland)') ⇒ Object



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/terrafying/aws.rb', line 529

def instance_type_vcpu_count(instance_type, location = 'EU (Ireland)')
  products_filter = {
    service_code: 'AmazonEC2',
    filters: [
      { field: 'operatingSystem', type: 'TERM_MATCH', value: 'Linux' },
      { field: 'tenancy', type: 'TERM_MATCH', value: 'Shared' },
      { field: 'instanceType', type: 'TERM_MATCH', value: instance_type },
      { field: 'location', type: 'TERM_MATCH', value: location },
      { field: 'preInstalledSw', type: 'TERM_MATCH', value: 'NA' }
    ],
    format_version: 'aws_v1'
  }

  products(products_filter).each do |product|
    vcpu = JSON.parse(product)['product']['attributes']['vcpu']
    return vcpu.to_i if vcpu
  end
end

#lb_by_name(name) ⇒ Object



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/terrafying/aws.rb', line 461

def lb_by_name(name)
  @lbs ||= {}
  @lbs[name] ||=
    begin
      load_balancers = @elb_client.describe_load_balancers(names: [name]).load_balancers

      if load_balancers.count == 1
        load_balancers.first
      elsif load_balancers.count < 1
        raise "No load balancer with name '#{name}' was found."
      elsif load_balancers.count > 1
        raise "More than one load balancer with name '#{name}' was found: " + load_balancers.join(', ')
      end
    end
end

#list_objects(bucket) ⇒ Object



408
409
410
411
412
413
414
415
# File 'lib/terrafying/aws.rb', line 408

def list_objects(bucket)
  @list_objects ||= {}
  @list_objects[bucket] ||=
    begin
      resp = @s3_client.list_objects_v2(bucket: bucket)
      resp.contents
    end
end

#msk_brokers(cluster_arn) ⇒ Object



548
549
550
551
552
553
554
555
556
557
558
# File 'lib/terrafying/aws.rb', line 548

def msk_brokers(cluster_arn)
  @brokers ||= {}
  @brokers[cluster_arn] ||= begin
    resp = @msk_client.get_bootstrap_brokers(cluster_arn: cluster_arn)
    brokers = resp.bootstrap_broker_string_tls.split(',')

    raise "No brokers found for cluster with arn: \"#{cluster_arn}\"'" if brokers.empty?

    brokers
  end
end

#nat_gateways_for_vpc(vpc_id) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/terrafying/aws.rb', line 170

def nat_gateways_for_vpc(vpc_id)
  @nat_gateways_for_vpc ||= {}
  @nat_gateways_for_vpc[vpc_id] ||=
    begin
      resp = @ec2_client.describe_nat_gateways(
        filter: [
          { name: 'vpc-id', values: [vpc_id] }
        ]
      )

      nat_gateways = resp.nat_gateways

      if nat_gateways.count >= 1
        nat_gateways
      elsif nat_gateways.count < 1
        raise "No nat-gateways for vpc #{vpc_id} were found"
      end
    end
end

#products(products_filter, _region = 'us-east-1') ⇒ Object



515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/terrafying/aws.rb', line 515

def products(products_filter, _region = 'us-east-1')
  next_token = nil
  Enumerator.new do |y|
    loop do
      resp = @pricing_client.get_products(products_filter.merge(next_token: next_token))
      resp.price_list.each do |product|
        y << product
      end
      next_token = resp.next_token
      break if next_token.nil?
    end
  end
end

#route_table(name) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/terrafying/aws.rb', line 307

def route_table(name)
  @route_tables ||= {}
  @route_tables[name] ||=
    begin
      warn "looking for a route table with name '#{name}'"
      route_tables = @ec2_client.describe_route_tables(
        filters: [
          {
            name: 'tag:Name',
            values: [name]
          }
        ]
      ).route_tables
      if route_tables.count == 1
        route_tables.first.route_table_id
      elsif route_tables.count < 1
        raise "No route table with name '#{name}' was found."
      elsif route_tables.count > 1
        raise "More than one route table with name '#{name}' was found: " + route_tables.join(', ')
      end
    end
end

#route_table_for_subnet(subnet_id) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/terrafying/aws.rb', line 125

def route_table_for_subnet(subnet_id)
  @route_table_for_subnet ||= {}
  @route_table_for_subnet[subnet_id] ||=
    begin
      resp = @ec2_client.describe_route_tables(
        filters: [
          { name: 'association.subnet-id', values: [subnet_id] }
        ]
      )

      route_tables = resp.route_tables

      if route_tables.count == 1
        route_tables.first
      elsif route_tables.count < 1
        raise "No route table for subnet '#{subnet_id}' was found."
      elsif profiles.count > 1
        raise "More than route table for subnet '#{subnet_id}' found: " + route_tables.join(', ')
      end
    end
end

#route_table_for_vpc(vpc_id) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/terrafying/aws.rb', line 147

def route_table_for_vpc(vpc_id)
  @route_table_for_vpc ||= {}
  @route_table_for_vpc[vpc_id] ||=
    begin
      resp = @ec2_client.describe_route_tables(
        filters: [
          { name: 'association.main', values: ['true'] },
          { name: 'vpc-id', values: [vpc_id] }
        ]
      )

      route_tables = resp.route_tables

      if route_tables.count == 1
        route_tables.first
      elsif route_tables.count < 1
        raise "No route table for vpc '#{vpc_id}' was found."
      elsif profiles.count > 1
        raise "More than route table for vpc '#{vpc_id}' found: " + route_tables.join(', ')
      end
    end
end

#s3_object(bucket, key) ⇒ Object



399
400
401
402
403
404
405
406
# File 'lib/terrafying/aws.rb', line 399

def s3_object(bucket, key)
  @s3_objects ||= {}
  @s3_objects["#{bucket}-#{key}"] ||=
    begin
      resp = @s3_client.get_object(bucket: bucket, key: key)
      resp.body.read
    end
end

#security_group(name) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/terrafying/aws.rb', line 56

def security_group(name)
  @security_groups ||= {}
  @security_groups[name] ||=
    begin
      warn "Looking up id of security group '#{name}'"
      groups = all_security_groups.select { |g| g.group_name == name }.take(2)
      if groups.count == 1
        groups.first.id
      elsif groups.count < 1
        raise "No security group with name '#{name}' was found."
      elsif groups.count > 1
        raise "More than one security group with name '#{name}' found: " + groups.join(', ')
      end
    end
end

#security_group_by_tags(tags) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/terrafying/aws.rb', line 88

def security_group_by_tags(tags)
  @security_groups_by_tags ||= {}
  @security_groups_by_tags[tags] ||=
    begin
      groups = all_security_groups.select { |g| g.tags.any? { |t| t.key == tags.keys && t.value == tags.values } }.take(2)
      if groups.count == 1
        groups.first.id
      elsif groups.count < 1
        raise "No security group with tags '#{tags}' was found."
      elsif groups.count > 1
        raise "More than one security group with tags '#{tags}' found: " + groups.join(', ')
      end
    end
end

#security_group_in_vpc(vpc_id, name) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/terrafying/aws.rb', line 72

def security_group_in_vpc(vpc_id, name)
  @security_groups_in_vpc ||= {}
  @security_groups_in_vpc[vpc_id + name] ||=
    begin
      warn "Looking up id of security group '#{name}'"
      groups = all_security_groups.select { |g| g.vpc_id == vpc_id && g.group_name == name }.take(2)
      if groups.count == 1
        groups.first.id
      elsif groups.count < 1
        raise "No security group with name '#{name}' was found."
      elsif groups.count > 1
        raise "More than one security group with name '#{name}' found: " + groups.join(', ')
      end
    end
end

#security_groups(*names) ⇒ Object



190
191
192
# File 'lib/terrafying/aws.rb', line 190

def security_groups(*names)
  names.map { |n| security_group(n) }
end

#security_groups_in_vpc(vpc_id, *names) ⇒ Object



194
195
196
# File 'lib/terrafying/aws.rb', line 194

def security_groups_in_vpc(vpc_id, *names)
  names.map { |n| security_group_in_vpc(vpc_id, n) }
end

#subnet(name) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/terrafying/aws.rb', line 198

def subnet(name)
  @subnets ||= {}
  @subnets[name] ||=
    begin
      warn "Looking up id of subnet '#{name}'"
      subnets = @ec2_resource.subnets(
        filters: [
          {
            name: 'tag:Name',
            values: [name]
          }
        ]
      ).limit(2)
      if subnets.count == 1
        subnets.first.id
      elsif subnets.count < 1
        raise "No subnet with name '#{name}' was found."
      elsif subnets.count > 1
        raise "More than one subnet with this name '#{name}' found : " + subnets.join(', ')
      end
    end
end

#subnet_by_id(id) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/terrafying/aws.rb', line 221

def subnet_by_id(id)
  @subnets_by_id ||= {}
  @subnets_by_id[id] ||=
    begin
      resp = @ec2_client.describe_subnets(
        subnet_ids: [id]
      )
      subnets = resp.subnets
      if subnets.count == 1
        subnets.first
      elsif subnets.count < 1
        raise "No subnet with id '#{id}' was found."
      elsif subnets.count > 1
        raise "More than one subnet with this id '#{id}' found : " + subnets.join(', ')
      end
    end
end

#subnets(*names) ⇒ Object



239
240
241
# File 'lib/terrafying/aws.rb', line 239

def subnets(*names)
  names.map { |n| subnet(n) }
end

#subnets_for_vpc(vpc_id) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/terrafying/aws.rb', line 243

def subnets_for_vpc(vpc_id)
  @subnets_for_vpc ||= {}
  @subnets_for_vpc[vpc_id] ||=
    begin
      resp = @ec2_client.describe_subnets(
        filters: [
          { name: 'vpc-id', values: [vpc_id] }
        ]
      )

      subnets = resp.subnets

      if subnets.count >= 1
        subnets
      elsif subnets.count < 1
        raise "No subnets found for '#{vpc_id}'."
      end
    end
end

#target_groups_by_lb(arn) ⇒ Object



477
478
479
480
481
482
483
484
485
486
487
# File 'lib/terrafying/aws.rb', line 477

def target_groups_by_lb(arn)
  @target_groups ||= {}
  @target_groups[arn] ||=
    begin
      resp = @elb_client.describe_target_groups(
        load_balancer_arn: arn
      )

      resp.target_groups
    end
end

#vpc(name) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/terrafying/aws.rb', line 287

def vpc(name)
  @vpcs ||= {}
  @vpcs[name] ||=
    begin
      warn "looking for a VPC with name '#{name}'"
      resp = @ec2_client.describe_vpcs({})
      matching_vpcs = resp.vpcs.select do |vpc|
        name_tag = vpc.tags.select { |tag| tag.key == 'Name' }.first
        name_tag && name_tag.value == name
      end
      if matching_vpcs.count == 1
        matching_vpcs.first
      elsif matching_vpcs.count < 1
        raise "No VPC with name '#{name}' was found."
      elsif matching_vpcs.count > 1
        raise "More than one VPC with name '#{name}' was found: " + matching_vpcs.join(', ')
      end
    end
end