cloud-mu 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +10 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -3
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +135 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +165 -111
  45. data/modules/mu/adoption.rb +401 -68
  46. data/modules/mu/cleanup.rb +199 -306
  47. data/modules/mu/cloud.rb +100 -1632
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +46 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +920 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +165 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +4 -4
  61. data/modules/mu/config/cache_cluster.rb +1 -1
  62. data/modules/mu/config/collection.rb +4 -4
  63. data/modules/mu/config/container_cluster.rb +9 -4
  64. data/modules/mu/config/database.rb +83 -104
  65. data/modules/mu/config/database.yml +1 -2
  66. data/modules/mu/config/dnszone.rb +6 -6
  67. data/modules/mu/config/doc_helpers.rb +516 -0
  68. data/modules/mu/config/endpoint.rb +4 -4
  69. data/modules/mu/config/firewall_rule.rb +103 -4
  70. data/modules/mu/config/folder.rb +4 -4
  71. data/modules/mu/config/function.rb +3 -3
  72. data/modules/mu/config/group.rb +4 -4
  73. data/modules/mu/config/habitat.rb +4 -4
  74. data/modules/mu/config/loadbalancer.rb +60 -14
  75. data/modules/mu/config/log.rb +4 -4
  76. data/modules/mu/config/msg_queue.rb +4 -4
  77. data/modules/mu/config/nosqldb.rb +4 -4
  78. data/modules/mu/config/notifier.rb +3 -3
  79. data/modules/mu/config/ref.rb +365 -0
  80. data/modules/mu/config/role.rb +4 -4
  81. data/modules/mu/config/schema_helpers.rb +509 -0
  82. data/modules/mu/config/search_domain.rb +4 -4
  83. data/modules/mu/config/server.rb +97 -70
  84. data/modules/mu/config/server.yml +1 -0
  85. data/modules/mu/config/server_pool.rb +5 -9
  86. data/modules/mu/config/storage_pool.rb +1 -1
  87. data/modules/mu/config/tail.rb +200 -0
  88. data/modules/mu/config/user.rb +4 -4
  89. data/modules/mu/config/vpc.rb +70 -27
  90. data/modules/mu/config/vpc.yml +0 -1
  91. data/modules/mu/defaults/AWS.yaml +83 -60
  92. data/modules/mu/defaults/Azure.yaml +1 -0
  93. data/modules/mu/defaults/Google.yaml +3 -2
  94. data/modules/mu/deploy.rb +30 -26
  95. data/modules/mu/groomer.rb +17 -2
  96. data/modules/mu/groomers/ansible.rb +188 -41
  97. data/modules/mu/groomers/chef.rb +116 -55
  98. data/modules/mu/logger.rb +127 -148
  99. data/modules/mu/master.rb +389 -2
  100. data/modules/mu/master/chef.rb +3 -4
  101. data/modules/mu/master/ldap.rb +3 -3
  102. data/modules/mu/master/ssl.rb +12 -3
  103. data/modules/mu/mommacat.rb +217 -2612
  104. data/modules/mu/mommacat/daemon.rb +397 -0
  105. data/modules/mu/mommacat/naming.rb +473 -0
  106. data/modules/mu/mommacat/search.rb +495 -0
  107. data/modules/mu/mommacat/storage.rb +722 -0
  108. data/modules/mu/{clouds → providers}/README.md +1 -1
  109. data/modules/mu/{clouds → providers}/aws.rb +271 -112
  110. data/modules/mu/{clouds → providers}/aws/alarm.rb +5 -3
  111. data/modules/mu/{clouds → providers}/aws/bucket.rb +26 -22
  112. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +33 -67
  113. data/modules/mu/{clouds → providers}/aws/collection.rb +24 -23
  114. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +681 -721
  115. data/modules/mu/providers/aws/database.rb +1744 -0
  116. data/modules/mu/{clouds → providers}/aws/dnszone.rb +64 -63
  117. data/modules/mu/{clouds → providers}/aws/endpoint.rb +22 -27
  118. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +214 -244
  119. data/modules/mu/{clouds → providers}/aws/folder.rb +7 -7
  120. data/modules/mu/{clouds → providers}/aws/function.rb +17 -22
  121. data/modules/mu/{clouds → providers}/aws/group.rb +23 -23
  122. data/modules/mu/{clouds → providers}/aws/habitat.rb +17 -14
  123. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +57 -48
  124. data/modules/mu/{clouds → providers}/aws/log.rb +15 -12
  125. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +17 -16
  126. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +18 -11
  127. data/modules/mu/{clouds → providers}/aws/notifier.rb +11 -6
  128. data/modules/mu/{clouds → providers}/aws/role.rb +112 -86
  129. data/modules/mu/{clouds → providers}/aws/search_domain.rb +39 -33
  130. data/modules/mu/{clouds → providers}/aws/server.rb +835 -1133
  131. data/modules/mu/{clouds → providers}/aws/server_pool.rb +56 -60
  132. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +24 -42
  133. data/modules/mu/{clouds → providers}/aws/user.rb +21 -22
  134. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  135. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  136. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  137. data/modules/mu/{clouds → providers}/aws/vpc.rb +523 -929
  138. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  139. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  140. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  141. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  142. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  143. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  144. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  145. data/modules/mu/{clouds → providers}/azure/server.rb +95 -48
  146. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  147. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  148. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  149. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  150. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  151. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  152. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  153. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  156. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  160. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  161. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  162. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  163. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  164. data/modules/mu/{clouds → providers}/google.rb +67 -30
  165. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  166. data/modules/mu/{clouds → providers}/google/container_cluster.rb +84 -77
  167. data/modules/mu/{clouds → providers}/google/database.rb +10 -20
  168. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  169. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  170. data/modules/mu/{clouds → providers}/google/function.rb +139 -167
  171. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  172. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  173. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +18 -20
  174. data/modules/mu/{clouds → providers}/google/role.rb +92 -58
  175. data/modules/mu/{clouds → providers}/google/server.rb +242 -155
  176. data/modules/mu/{clouds → providers}/google/server_pool.rb +25 -44
  177. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  178. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  179. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  180. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  181. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  182. data/modules/tests/bucket.yml +4 -0
  183. data/modules/tests/centos6.yaml +11 -0
  184. data/modules/tests/centos7.yaml +11 -0
  185. data/modules/tests/centos8.yaml +12 -0
  186. data/modules/tests/ecs.yaml +23 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/rds.yaml +108 -0
  189. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  190. data/modules/tests/regrooms/bucket.yml +19 -0
  191. data/modules/tests/regrooms/rds.yaml +123 -0
  192. data/modules/tests/server-with-scrub-muisms.yaml +1 -0
  193. data/modules/tests/super_simple_bok.yml +1 -3
  194. data/modules/tests/win2k12.yaml +17 -5
  195. data/modules/tests/win2k16.yaml +25 -0
  196. data/modules/tests/win2k19.yaml +25 -0
  197. data/requirements.txt +1 -0
  198. data/spec/mu/clouds/azure_spec.rb +2 -2
  199. metadata +232 -154
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1985
@@ -344,8 +344,8 @@ module MU
344
344
  )
345
345
  rescue Aws::Route53::Errors::LastVPCAssociation => e
346
346
  MU.log e.inspect, MU::WARN
347
- rescue Aws::Route53::Errors::VPCAssociationNotFound => e
348
- MU.log "VPC #{vpc_id} access to zone #{id} already revoked", MU::WARN
347
+ rescue Aws::Route53::Errors::VPCAssociationNotFound
348
+ MU.log "VPC #{vpc_id} access to zone #{id} already revoked", MU::NOTICE
349
349
  end
350
350
  end
351
351
  end
@@ -366,10 +366,10 @@ module MU
366
366
  # @param location [Hash<String>]: A parsed Hash of {MU::Config::BasketofKittens::dnszones::records::geo_location}.
367
367
  # @param set_identifier [String]: A unique string to differentiate otherwise-similar records. Normally auto-generated, should not need to specify.
368
368
  # @param alias_zone [String]: Zone ID of the target's hosted zone, when creating an alias (type R53ALIAS)
369
- def self.manageRecord(id, name, type, targets: nil, aliases: nil,
369
+ def self.manageRecord(id, name, type, targets: nil,
370
370
  ttl: 7200, delete: false, sync_wait: true, failover: nil,
371
371
  healthcheck: nil, region: nil, weight: nil, overwrite: true,
372
- location: nil, set_identifier: nil, alias_zone: nil)
372
+ location: nil, set_identifier: nil, alias_zone: nil, noop: false)
373
373
 
374
374
  MU.setVar("curRegion", region) if !region.nil?
375
375
  zone = MU::Cloud::DNSZone.find(cloud_id: id).values.first
@@ -380,6 +380,11 @@ module MU
380
380
  action = "UPSERT" if overwrite
381
381
  action = "DELETE" if delete
382
382
 
383
+ record_sets = MU::Cloud::AWS.route53.list_resource_record_sets(
384
+ hosted_zone_id: id,
385
+ start_record_name: name
386
+ ).resource_record_sets if delete
387
+
383
388
  if type == "R53ALIAS"
384
389
  target_zone = id
385
390
  target_name = targets[0].downcase
@@ -413,7 +418,15 @@ module MU
413
418
  }
414
419
  else
415
420
  rrsets = []
416
- if !targets.nil?
421
+ if delete
422
+ record_sets.each { |r|
423
+ if r.name == name and r.type == type
424
+ rrsets = MU.structToHash(r.resource_records)
425
+ end
426
+ }
427
+ end
428
+
429
+ if !targets.nil? and (!delete or rrsets.empty?)
417
430
  targets.each { |target|
418
431
  rrsets << {value: target}
419
432
  }
@@ -426,6 +439,7 @@ module MU
426
439
  resource_records: rrsets
427
440
  }
428
441
 
442
+
429
443
  if !healthcheck.nil?
430
444
  base_rrset[:health_check_id] = healthcheck
431
445
  end
@@ -445,12 +459,13 @@ module MU
445
459
 
446
460
  # Doing an UPSERT with a new set_identifier will fail with a record already exist error, so lets try and get it from an existing record.
447
461
  # This can be an issue with multiple secondary failover records
448
- if (location || failover || region || weight) && set_identifier.nil?
449
- record_sets = MU::Cloud::AWS.route53.list_resource_record_sets(
462
+ if (location || failover || region || weight) and set_identifier.nil?
463
+ record_sets ||= MU::Cloud::AWS.route53.list_resource_record_sets(
450
464
  hosted_zone_id: id,
451
465
  start_record_name: name
452
466
  ).resource_record_sets
453
467
 
468
+
454
469
  record_sets.each { |r|
455
470
  if r.name == name
456
471
  if location && location == r.location
@@ -497,18 +512,23 @@ module MU
497
512
  MU.log "Adding DNS record #{name} => #{targets} (#{type}) to #{id}", details: params
498
513
  end
499
514
 
500
- begin
515
+ return if noop
516
+
517
+ on_retry = Proc.new { |e|
518
+ if (delete and e.message.match(/but it was not found/)) or
519
+ (!delete and e.message.match(/(it|name) already exists/))
520
+ MU.log e.message, MU::DEBUG, details: params
521
+ return
522
+ elsif e.class == Aws::Route53::Errors::InvalidChangeBatch
523
+ MU.log "Problem managing entry for #{name}", MU::ERR, details: params
524
+ raise MuError, e.inspect
525
+ end
526
+ }
527
+
528
+ change_id = nil
529
+ MU.retrier([Aws::Route53::Errors::PriorRequestNotComplete, Aws::Route53::Errors::InvalidChangeBatch], wait: 15, max: 10, on_retry: on_retry) {
501
530
  change_id = MU::Cloud::AWS.route53.change_resource_record_sets(params).change_info.id
502
- rescue Aws::Route53::Errors::PriorRequestNotComplete => e
503
- sleep 10
504
- retry
505
- rescue Aws::Route53::Errors::InvalidChangeBatch, Aws::Route53::Errors::InvalidInput, Exception => e
506
- return if e.message.match(/ but it already exists/) and !delete
507
- MU.log "Failed to change DNS records, #{e.inspect}", MU::ERR, details: params
508
- raise e if !delete
509
- MU.log "Record #{name} (#{type}) in #{id} can't be deleted. Already removed? #{e.inspect}", MU::WARN, details: params if delete
510
- return
511
- end
531
+ }
512
532
 
513
533
  if sync_wait
514
534
  attempts = 0
@@ -535,23 +555,27 @@ module MU
535
555
  # @param delete [Boolean]: Remove this entry instead of creating it.
536
556
  # @param cloudclass [Object]: The resource's Mu class.
537
557
  # @param sync_wait [Boolean]: Wait for DNS entry to propagate across zone.
538
- def self.genericMuDNSEntry(name: nil, target: nil, cloudclass: nil, noop: false, delete: false, sync_wait: true)
539
- return nil if name.nil? or target.nil? or cloudclass.nil?
540
- mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
558
+ def self.genericMuDNSEntry(name: nil, target: nil, cloudclass: nil, noop: false, delete: false, sync_wait: true, credentials: nil)
559
+ return nil if name.nil? or cloudclass.nil?
560
+ return nil if target.nil? and !delete
561
+ mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu", credentials: credentials).values.first
541
562
  raise MuError, "Couldn't isolate platform-mu DNS zone" if mu_zone.nil?
542
563
 
543
564
  if !mu_zone.nil? and !MU.myVPC.nil?
544
565
  subdomain = cloudclass.cfg_name
545
566
  dns_name = name.downcase+"."+subdomain
546
567
  dns_name += "."+MU.myInstanceId if MU.myInstanceId
568
+
547
569
  record_type = "CNAME"
548
570
  record_type = "A" if target.match(/^\d+\.\d+\.\d+\.\d+/)
549
571
  ip = nil
550
572
 
551
- lookup = MU::Cloud::AWS.route53.list_resource_record_sets(
552
- hosted_zone_id: mu_zone.id,
553
- start_record_name: "#{dns_name}.platform-mu",
554
- start_record_type: record_type
573
+ records = []
574
+ lookup = MU::Cloud::AWS.route53(credentials: credentials).list_resource_record_sets(
575
+ hosted_zone_id: mu_zone.id,
576
+ start_record_name: "#{dns_name}.platform-mu",
577
+ start_record_type: record_type,
578
+ max_items: 1
555
579
  ).resource_record_sets
556
580
 
557
581
  lookup.each { |record|
@@ -572,34 +596,14 @@ module MU
572
596
  # MU.log "'#{dns_name}.platform-mu' does not resolve.", MU::DEBUG, details: e.inspect
573
597
  # end
574
598
 
575
- if ip == target
576
- return "#{dns_name}.platform-mu" if !delete
577
- elsif noop
578
- return nil
599
+ if ip == target and !delete
600
+ return "#{dns_name}.platform-mu"
579
601
  end
580
602
 
581
603
  sync_wait = false if delete
582
604
 
583
605
  record_type = "R53ALIAS" if cloudclass == MU::Cloud::AWS::LoadBalancer
584
- attempts = 0
585
- begin
586
- MU::Cloud::AWS::DNSZone.manageRecord(mu_zone.id, dns_name, record_type, targets: [target], delete: delete, sync_wait: sync_wait)
587
- rescue Aws::Route53::Errors::PriorRequestNotComplete => e
588
- MU.log "Route53 was still processing a request, waiting", MU::WARN, details: e
589
- sleep 15
590
- retry
591
- rescue Aws::Route53::Errors::InvalidChangeBatch => e
592
- if e.inspect.match(/alias target name does not lie within the target zone/) and attempts < 5
593
- MU.log e.inspect, MU::WARN
594
- sleep 15
595
- attempts = attempts + 1
596
- retry
597
- elsif !e.inspect.match(/(it|name) already exists/)
598
- raise MuError, "Problem managing entry for #{dns_name} -> #{target}: #{e.inspect}"
599
- else
600
- MU.log "#{dns_name} already exists", MU::DEBUG, details: e.inspect
601
- end
602
- end
606
+ MU::Cloud::AWS::DNSZone.manageRecord(mu_zone.id, dns_name, record_type, targets: [target], delete: delete, sync_wait: sync_wait, noop: noop)
603
607
  return "#{dns_name}.platform-mu"
604
608
  else
605
609
  return nil
@@ -663,7 +667,8 @@ module MU
663
667
  # Called by {MU::Cleanup}. Locates resources that were created by the
664
668
  # currently-loaded deployment, and purges them.
665
669
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
666
- checks_to_clean = []
670
+ MU.log "AWS::DNSZone.cleanup: need to support flags['known']", MU::DEBUG, details: flags
671
+
667
672
  threads = []
668
673
  MU::Cloud::AWS.route53(credentials: credentials).list_health_checks.health_checks.each { |check|
669
674
  begin
@@ -692,19 +697,19 @@ module MU
692
697
  threads << Thread.new(check) { |mycheck|
693
698
  MU.dupGlobals(parent_thread_id)
694
699
  Thread.abort_on_exception = true
695
- MU.log "Removing health check #{check.id}"
700
+ MU.log "Removing health check #{mycheck.id}"
696
701
  retries = 5
697
702
  begin
698
- MU::Cloud::AWS.route53(credentials: credentials).delete_health_check(health_check_id: check.id) if !noop
703
+ MU::Cloud::AWS.route53(credentials: credentials).delete_health_check(health_mycheck_id: mycheck.id) if !noop
699
704
  rescue Aws::Route53::Errors::NoSuchHealthCheck => e
700
- MU.log "Health Check '#{check.id}' disappeared before I could remove it", MU::WARN, details: e.inspect
705
+ MU.log "Health Check '#{mycheck.id}' disappeared before I could remove it", MU::WARN, details: e.inspect
701
706
  rescue Aws::Route53::Errors::InvalidInput => e
702
707
  if e.message.match(/is still referenced from parent health check/) && retries <= 5
703
708
  sleep 5
704
709
  retries += 1
705
710
  retry
706
711
  else
707
- MU.log "Health Check #{check.id} still has a parent health check associated with it, skipping", MU::WARN, details: e.inspect
712
+ MU.log "Health Check #{mycheck.id} still has a parent health check associated with it, skipping", MU::WARN, details: e.inspect
708
713
  end
709
714
  end
710
715
  }
@@ -719,7 +724,7 @@ module MU
719
724
  }
720
725
 
721
726
  zones = MU::Cloud::DNSZone.find(deploy_id: MU.deploy_id, region: region)
722
- zones.each_pair { |id, zone|
727
+ zones.values.each { |zone|
723
728
  MU.log "Purging DNS Zone '#{zone.name}' (#{zone.id})"
724
729
  if !noop
725
730
  begin
@@ -727,7 +732,6 @@ module MU
727
732
  rrsets = MU::Cloud::AWS.route53(credentials: credentials).list_resource_record_sets(hosted_zone_id: zone.id)
728
733
  rrsets.resource_record_sets.each { |rrset|
729
734
  next if zone.name == rrset.name and (rrset.type == "NS" or rrset.type == "SOA")
730
- records = []
731
735
  MU::Cloud::AWS.route53(credentials: credentials).change_resource_record_sets(
732
736
  hosted_zone_id: zone.id,
733
737
  change_batch: {
@@ -791,9 +795,9 @@ module MU
791
795
  end
792
796
 
793
797
  # Cloud-specific configuration properties.
794
- # @param config [MU::Config]: The calling MU::Config object
798
+ # @param _config [MU::Config]: The calling MU::Config object
795
799
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
796
- def self.schema(config)
800
+ def self.schema(_config)
797
801
  toplevel_required = []
798
802
  schema = {}
799
803
  [toplevel_required, schema]
@@ -801,9 +805,9 @@ module MU
801
805
 
802
806
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::dnszones}, bare and unvalidated.
803
807
  # @param zone [Hash]: The resource to process and validate
804
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
808
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
805
809
  # @return [Boolean]: True if validation succeeded, False otherwise
806
- def self.validateConfig(zone, configurator)
810
+ def self.validateConfig(zone, _configurator)
807
811
  ok = true
808
812
 
809
813
  if !zone["records"].nil?
@@ -821,10 +825,7 @@ module MU
821
825
  end
822
826
 
823
827
  if !record['mu_type'].nil?
824
- zone["dependencies"] << {
825
- "type" => record['mu_type'],
826
- "name" => record['target']
827
- }
828
+ MU::Config.addDependency(zone, record['target'], record['mu_type'])
828
829
  end
829
830
 
830
831
  if record.has_key?('healthchecks') && !record['healthchecks'].empty?
@@ -116,15 +116,15 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
116
116
  end
117
117
 
118
118
  if m['integrate_with']
119
- role_arn = if m['iam_role']
120
- if m['iam_role'].match(/^arn:/)
121
- m['iam_role']
122
- else
123
- sib_role = @deploy.findLitterMate(name: m['iam_role'], type: "roles")
124
- sib_role.cloudobj.arn
119
+ # role_arn = if m['iam_role']
120
+ # if m['iam_role'].match(/^arn:/)
121
+ # m['iam_role']
122
+ # else
123
+ # sib_role = @deploy.findLitterMate(name: m['iam_role'], type: "roles")
124
+ # sib_role.cloudobj.arn
125
125
  # XXX make this more like get_role_arn in Function, or just use Role.find?
126
- end
127
- end
126
+ # end
127
+ # end
128
128
 
129
129
  function_obj = nil
130
130
 
@@ -198,13 +198,12 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
198
198
  generate_methods
199
199
 
200
200
  MU.log "Deploying API Gateway #{@config['name']} to #{@config['deploy_to']}"
201
- resp = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_deployment(
201
+ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).create_deployment(
202
202
  rest_api_id: @cloud_id,
203
203
  stage_name: @config['deploy_to']
204
204
  # cache_cluster_enabled: false,
205
205
  # cache_cluster_size: 0.5,
206
206
  )
207
- deployment_id = resp.id
208
207
  # this automatically creates a stage with the same name, so we don't
209
208
  # have to deal with that
210
209
 
@@ -220,11 +219,14 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
220
219
 
221
220
  end
222
221
 
222
+ @cloud_desc_cache = nil
223
223
  # @return [Struct]
224
- def cloud_desc
225
- MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_rest_api(
224
+ def cloud_desc(use_cache: true)
225
+ return @cloud_desc_cache if @cloud_desc_cache and use_cache
226
+ @cloud_desc_cache = MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials']).get_rest_api(
226
227
  rest_api_id: @cloud_id
227
228
  )
229
+ @cloud_desc_cache
228
230
  end
229
231
 
230
232
  # Return the metadata for this API
@@ -241,6 +243,9 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
241
243
  # @param region [String]: The cloud provider region
242
244
  # @return [void]
243
245
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
246
+ MU.log "AWS::Endpoint.cleanup: need to support flags['known']", MU::DEBUG, details: flags
247
+ MU.log "Placeholder: AWS Endpoint artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
248
+
244
249
  resp = MU::Cloud::AWS.apig(region: region, credentials: credentials).get_rest_apis
245
250
  if resp and resp.items
246
251
  resp.items.each { |api|
@@ -279,9 +284,9 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
279
284
  end
280
285
 
281
286
  # Cloud-specific configuration properties.
282
- # @param config [MU::Config]: The calling MU::Config object
287
+ # @param _config [MU::Config]: The calling MU::Config object
283
288
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
284
- def self.schema(config)
289
+ def self.schema(_config)
285
290
  toplevel_required = []
286
291
  schema = {
287
292
  "deploy_to" => {
@@ -467,11 +472,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
467
472
  endpoint['methods'].each { |m|
468
473
  if m['integrate_with'] and m['integrate_with']['name']
469
474
  if m['integrate_with']['type'] != "aws_generic"
470
- endpoint['dependencies'] ||= []
471
- endpoint['dependencies'] << {
472
- "type" => m['integrate_with']['type'],
473
- "name" => m['integrate_with']['name']
474
- }
475
+ MU::Config.addDependency(endpoint, m['integrate_with']['name'], m['integrate_with']['type'])
475
476
  end
476
477
 
477
478
  m['integrate_with']['backend_http_method'] ||= m['type']
@@ -520,13 +521,8 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
520
521
  end
521
522
  configurator.insertKitten(roledesc, "roles")
522
523
 
523
- endpoint['dependencies'] ||= []
524
524
  m['iam_role'] = endpoint['name']+"-"+m['integrate_with']['name']
525
-
526
- endpoint['dependencies'] << {
527
- "type" => "role",
528
- "name" => endpoint['name']+"-"+m['integrate_with']['name']
529
- }
525
+ MU::Config.addDependency(endpoint, m['iam_role'], "role")
530
526
  end
531
527
  end
532
528
  }
@@ -538,8 +534,6 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
538
534
  ok
539
535
  end
540
536
 
541
- private
542
-
543
537
  def self.cors_option_integrations(path)
544
538
  {
545
539
  "type" => "OPTIONS",
@@ -585,6 +579,7 @@ MU::Cloud::AWS.apig(region: @config['region'], credentials: @config['credentials
585
579
  }
586
580
  }
587
581
  end
582
+ private_class_method :cors_option_integrations
588
583
 
589
584
  end
590
585
  end
@@ -18,7 +18,7 @@ module MU
18
18
  class AWS
19
19
  # A firewall ruleset as configured in {MU::Config::BasketofKittens::firewall_rules}
20
20
  class FirewallRule < MU::Cloud::FirewallRule
21
- require "mu/clouds/aws/vpc"
21
+ require "mu/providers/aws/vpc"
22
22
 
23
23
  @admin_sgs = Hash.new
24
24
  @admin_sg_semaphore = Mutex.new
@@ -54,13 +54,12 @@ module MU
54
54
 
55
55
  secgroup = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_security_group(sg_struct)
56
56
  @cloud_id = secgroup.group_id
57
- rescue Aws::EC2::Errors::InvalidGroupDuplicate => e
57
+ rescue Aws::EC2::Errors::InvalidGroupDuplicate
58
58
  MU.log "EC2 Security Group #{groupname} already exists, using it", MU::NOTICE
59
59
  filters = [{name: "group-name", values: [groupname]}]
60
60
  filters << {name: "vpc-id", values: [vpc_id]} if !vpc_id.nil?
61
61
 
62
62
  secgroup = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_security_groups(filters: filters).security_groups.first
63
- deploy_id = @deploy.deploy_id if !@deploy_id.nil?
64
63
  if secgroup.nil?
65
64
  raise MuError, "Failed to locate security group named #{groupname}, even though EC2 says it already exists", caller
66
65
  end
@@ -69,24 +68,24 @@ module MU
69
68
 
70
69
  begin
71
70
  MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_security_groups(group_ids: [secgroup.group_id])
72
- rescue Aws::EC2::Errors::InvalidGroupNotFound => e
71
+ rescue Aws::EC2::Errors::InvalidGroupNotFound
73
72
  MU.log "#{secgroup.group_id} not yet ready, waiting...", MU::NOTICE
74
73
  sleep 10
75
74
  retry
76
75
  end
77
76
 
78
77
  MU::Cloud::AWS.createStandardTags(secgroup.group_id, region: @config['region'], credentials: @config['credentials'])
79
- MU::MommaCat.createTag(secgroup.group_id, "Name", groupname, region: @config['region'], credentials: @config['credentials'])
78
+ MU::Cloud::AWS.createTag(secgroup.group_id, "Name", groupname, region: @config['region'], credentials: @config['credentials'])
80
79
 
81
80
  if @config['optional_tags']
82
81
  MU::MommaCat.listOptionalTags.each { |key, value|
83
- MU::MommaCat.createTag(secgroup.group_id, key, value, region: @config['region'], credentials: @config['credentials'])
82
+ MU::Cloud::AWS.createTag(secgroup.group_id, key, value, region: @config['region'], credentials: @config['credentials'])
84
83
  }
85
84
  end
86
85
 
87
86
  if @config['tags']
88
87
  @config['tags'].each { |tag|
89
- MU::MommaCat.createTag(secgroup.group_id, tag['key'], tag['value'], region: @config['region'], credentials: @config['credentials'])
88
+ MU::Cloud::AWS.createTag(secgroup.group_id, tag['key'], tag['value'], region: @config['region'], credentials: @config['credentials'])
90
89
  }
91
90
  end
92
91
 
@@ -180,7 +179,7 @@ module MU
180
179
  ip_permissions: ec2_rule
181
180
  )
182
181
  end
183
- rescue Aws::EC2::Errors::InvalidPermissionDuplicate => e
182
+ rescue Aws::EC2::Errors::InvalidPermissionDuplicate
184
183
  MU.log "Attempt to add duplicate rule to #{@cloud_id}", MU::DEBUG, details: ec2_rule
185
184
  # Ensure that, at least, the description field gets updated on
186
185
  # existing rules
@@ -246,7 +245,7 @@ module MU
246
245
  # Reverse-map our cloud description into a runnable config hash.
247
246
  # We assume that any values we have in +@config+ are placeholders, and
248
247
  # calculate our own accordingly based on what's live in the cloud.
249
- def toKitten(rootparent: nil, billing: nil, habitats: nil)
248
+ def toKitten(**_args)
250
249
  bok = {
251
250
  "cloud" => "AWS",
252
251
  "credentials" => @config['credentials'],
@@ -383,9 +382,8 @@ module MU
383
382
  # @param region [String]: The cloud provider region
384
383
  # @return [void]
385
384
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
386
- filters = nil
387
- if flags and flags["vpc_id"]
388
- filters = [
385
+ filters = if flags and flags["vpc_id"]
386
+ [
389
387
  {name: "vpc-id", values: [flags["vpc_id"]]}
390
388
  ]
391
389
  else
@@ -395,11 +393,12 @@ module MU
395
393
  if !ignoremaster
396
394
  filters << {name: "tag:MU-MASTER-IP", values: [MU.mu_public_ip]}
397
395
  end
396
+ filters
398
397
  end
399
398
 
400
399
  # Some services create sneaky rogue ENIs which then block removal of
401
400
  # associated security groups. Find them and fry them.
402
- MU::Cloud::AWS::VPC.purge_interfaces(noop, filters, region: region, credentials: credentials)
401
+ MU::Cloud.resourceClass("AWS", "VPC").purge_interfaces(noop, filters, region: region, credentials: credentials)
403
402
 
404
403
  resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_security_groups(
405
404
  filters: filters
@@ -409,75 +408,8 @@ module MU
409
408
  MU.log "Revoking rules in EC2 Security Group #{sg.group_name} (#{sg.group_id})"
410
409
 
411
410
  if !noop
412
- ingress_to_revoke = Array.new
413
- egress_to_revoke = Array.new
414
- sg.ip_permissions.each { |hole|
415
- ingress_to_revoke << MU.structToHash(hole)
416
- ingress_to_revoke.each { |rule|
417
- if !rule[:user_id_group_pairs].nil? and rule[:user_id_group_pairs] .size == 0
418
- rule.delete(:user_id_group_pairs)
419
- elsif !rule[:user_id_group_pairs].nil?
420
- rule[:user_id_group_pairs].each { |group_ref|
421
- group_ref = MU.structToHash(group_ref)
422
- group_ref.delete(:group_name) if group_ref[:group_id]
423
- }
424
- end
425
-
426
- if !rule[:ip_ranges].nil? and rule[:ip_ranges].size == 0
427
- rule.delete(:ip_ranges)
428
- end
429
-
430
- if !rule[:prefix_list_ids].nil? and rule[:prefix_list_ids].size == 0
431
- rule.delete(:prefix_list_ids)
432
- end
433
-
434
- if !rule[:ipv_6_ranges].nil? and rule[:ipv_6_ranges].size == 0
435
- rule.delete(:ipv_6_ranges)
436
- end
437
- }
438
- }
439
- sg.ip_permissions_egress.each { |hole|
440
- egress_to_revoke << MU.structToHash(hole)
441
- egress_to_revoke.each { |rule|
442
- if !rule[:user_id_group_pairs].nil? and rule[:user_id_group_pairs].size == 0
443
- rule.delete(:user_id_group_pairs)
444
- elsif !rule[:user_id_group_pairs].nil?
445
- rule[:user_id_group_pairs].each { |group_ref|
446
- group_ref = MU.structToHash(group_ref)
447
- group_ref.delete(:group_name) if group_ref[:group_id]
448
- }
449
- end
450
-
451
- if !rule[:ip_ranges].nil? and rule[:ip_ranges].size == 0
452
- rule.delete(:ip_ranges)
453
- end
454
-
455
- if !rule[:prefix_list_ids].nil? and rule[:prefix_list_ids].size == 0
456
- rule.delete(:prefix_list_ids)
457
- end
458
-
459
- if !rule[:ipv_6_ranges].nil? and rule[:ipv_6_ranges].size == 0
460
- rule.delete(:ipv_6_ranges)
461
- end
462
- }
463
- }
464
- begin
465
-
466
- if ingress_to_revoke.size > 0
467
- MU::Cloud::AWS.ec2(credentials: credentials, region: region).revoke_security_group_ingress(
468
- group_id: sg.group_id,
469
- ip_permissions: ingress_to_revoke
470
- )
471
- end
472
- if egress_to_revoke.size > 0
473
- MU::Cloud::AWS.ec2(credentials: credentials, region: region).revoke_security_group_egress(
474
- group_id: sg.group_id,
475
- ip_permissions: egress_to_revoke
476
- )
477
- end
478
- rescue Aws::EC2::Errors::InvalidPermissionNotFound
479
- MU.log "Rule in #{sg.group_id} disappeared before I could remove it", MU::WARN
480
- end
411
+ revoke_rules(sg, region: region, credentials: credentials)
412
+ revoke_rules(sg, egress: true, region: region, credentials: credentials)
481
413
  end
482
414
  }
483
415
 
@@ -485,61 +417,132 @@ module MU
485
417
  next if sg.group_name == "default"
486
418
  MU.log "Removing EC2 Security Group #{sg.group_name}"
487
419
 
488
- retries = 0
489
- begin
490
- MU::Cloud::AWS.ec2(credentials: credentials, region: region).delete_security_group(group_id: sg.group_id) if !noop
491
- rescue Aws::EC2::Errors::CannotDelete => e
492
- MU.log e.message, MU::WARN
493
- rescue Aws::EC2::Errors::InvalidGroupNotFound
494
- MU.log "EC2 Security Group #{sg.group_name} disappeared before I could delete it!", MU::WARN
495
- rescue Aws::EC2::Errors::DependencyViolation, Aws::EC2::Errors::InvalidGroupInUse
496
- if retries < 10
497
- MU.log "EC2 Security Group #{sg.group_name} is still in use, waiting...", MU::NOTICE
498
- # try to get out from under loose network interfaces with which
499
- # we're associated
500
- if sg.vpc_id
501
- # get the default SG for this VPC
502
- default_resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_security_groups(
503
- filters: [
504
- { name: "group-name", values: ["default"] },
505
- { name: "vpc-id", values: [sg.vpc_id] }
506
- ]
507
- ).security_groups
508
- if default_resp and default_resp.size == 1
509
- default_sg = default_resp.first.group_id
510
- eni_resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_network_interfaces(
511
- filters: [ {name: "group-id", values: [sg.group_id]} ]
512
- )
513
- if eni_resp and eni_resp.data and
514
- eni_resp.data.network_interfaces
515
- eni_resp.data.network_interfaces.each { |iface|
516
- iface_groups = iface.groups.map { |sg| sg.group_id }
517
- iface_groups.delete(sg.group_id)
518
- iface_groups << default_sg if iface_groups.empty?
519
- MU.log "Attempting to remove #{sg.group_id} from ENI #{iface.network_interface_id}"
420
+ on_retry = Proc.new {
421
+ # try to get out from under loose network interfaces with which
422
+ # we're associated
423
+ if sg.vpc_id
424
+ default_sg = MU::Cloud.resourceClass("AWS", "VPC").getDefaultSg(sg.vpc_id, region: region, credentials: credentials)
425
+ if default_sg
426
+ eni_resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_network_interfaces(
427
+ filters: [ {name: "group-id", values: [sg.group_id]} ]
428
+ )
429
+ if eni_resp and eni_resp.data and
430
+ eni_resp.data.network_interfaces
431
+ eni_resp.data.network_interfaces.each { |iface|
432
+ iface_groups = iface.groups.map { |if_sg| if_sg.group_id }
433
+ iface_groups.delete(sg.group_id)
434
+ iface_groups << default_sg if iface_groups.empty?
435
+ MU.log "Attempting to remove #{sg.group_id} (#{sg.group_name}) from ENI #{iface.network_interface_id}"
436
+ begin
520
437
  MU::Cloud::AWS.ec2(credentials: credentials, region: region).modify_network_interface_attribute(
521
438
  network_interface_id: iface.network_interface_id,
522
439
  groups: iface_groups
523
440
  )
524
- }
525
- end
441
+ rescue ::Aws::EC2::Errors::InvalidNetworkInterfaceIDNotFound
442
+ # fine by me
443
+ rescue ::Aws::EC2::Errors::AuthFailure
444
+ MU.log "Permission denied attempting to trim Security Group list for #{iface.network_interface_id}", MU::WARN, details: iface.groups.map { |g| g.group_name }.join(",")+" => default"
445
+ end
446
+ }
526
447
  end
527
448
  end
449
+ end
450
+ }
528
451
 
529
- sleep 10
530
- retries = retries + 1
531
- retry
452
+ if !noop
453
+ MU.retrier([Aws::EC2::Errors::DependencyViolation, Aws::EC2::Errors::InvalidGroupInUse], ignoreme: [Aws::EC2::Errors::InvalidGroupNotFound], max: 10, wait: 10, on_retry: on_retry) {
454
+ begin
455
+ MU::Cloud::AWS.ec2(credentials: credentials, region: region).delete_security_group(group_id: sg.group_id)
456
+ rescue Aws::EC2::Errors::CannotDelete => e
457
+ MU.log e.message, MU::WARN
458
+ end
459
+ }
460
+ end
461
+
462
+ }
463
+ end
464
+
465
+ def self.revoke_rules(sg, egress: false, region: MU.myregion, credentials: nil)
466
+ holes = sg.send(egress ? :ip_permissions_egress : :ip_permissions)
467
+
468
+ to_revoke = []
469
+
470
+ holes.each { |hole|
471
+ to_revoke << MU.structToHash(hole)
472
+ to_revoke.each { |rule|
473
+ if !rule[:user_id_group_pairs].nil? and rule[:user_id_group_pairs].size == 0
474
+ rule.delete(:user_id_group_pairs)
475
+ elsif !rule[:user_id_group_pairs].nil?
476
+ rule[:user_id_group_pairs].each { |group_ref|
477
+ group_ref = MU.structToHash(group_ref)
478
+ group_ref.delete(:group_name) if group_ref[:group_id]
479
+ }
480
+ end
481
+
482
+ if !rule[:ip_ranges].nil? and rule[:ip_ranges].size == 0
483
+ rule.delete(:ip_ranges)
484
+ end
485
+
486
+ if !rule[:prefix_list_ids].nil? and rule[:prefix_list_ids].size == 0
487
+ rule.delete(:prefix_list_ids)
488
+ end
489
+
490
+ if !rule[:ipv_6_ranges].nil? and rule[:ipv_6_ranges].size == 0
491
+ rule.delete(:ipv_6_ranges)
492
+ end
493
+ }
494
+ }
495
+
496
+ if to_revoke.size > 0
497
+ begin
498
+ if egress
499
+ MU::Cloud::AWS.ec2(credentials: credentials, region: region).revoke_security_group_egress(
500
+ group_id: sg.group_id,
501
+ ip_permissions: to_revoke
502
+ )
532
503
  else
533
- MU.log "Failed to delete #{sg.group_name}", MU::ERR
504
+ MU::Cloud::AWS.ec2(credentials: credentials, region: region).revoke_security_group_ingress(
505
+ group_id: sg.group_id,
506
+ ip_permissions: to_revoke
507
+ )
534
508
  end
509
+ rescue Aws::EC2::Errors::InvalidPermissionNotFound
510
+ MU.log "Rule in #{sg.group_id} disappeared before I could remove it", MU::WARN
535
511
  end
512
+ end
513
+
514
+ end
515
+ private_class_method :revoke_rules
516
+
517
+ # Return an AWS-specific chunk of schema commonly used in the +ingress_rules+ parameter of other resource types.
518
+ # @return [Hash]
519
+ def self.ingressRuleAddtlSchema
520
+ {
521
+ "items" => {
522
+ "properties" => {
523
+ "sgs" => {
524
+ "type" => "array",
525
+ "items" => {
526
+ "description" => "Other AWS Security Groups; resources that are associated with this group will have this rule applied to their traffic",
527
+ "type" => "string"
528
+ }
529
+ },
530
+ "lbs" => {
531
+ "type" => "array",
532
+ "items" => {
533
+ "description" => "AWS Load Balancers which will have this rule applied to their traffic",
534
+ "type" => "string"
535
+ }
536
+ }
537
+ }
538
+ }
536
539
  }
537
540
  end
538
541
 
539
542
  # Cloud-specific configuration properties.
540
- # @param config [MU::Config]: The calling MU::Config object
543
+ # @param _config [MU::Config]: The calling MU::Config object
541
544
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
542
- def self.schema(config)
545
+ def self.schema(_config)
543
546
  toplevel_required = []
544
547
  schema = {
545
548
  "rules" => {
@@ -645,36 +648,16 @@ module MU
645
648
 
646
649
  if rule['firewall_rules']
647
650
  rule['firewall_rules'].each { |sg|
648
- if sg.is_a?(MU::Config::Ref) and sg.name
649
- acl["dependencies"] << {
650
- "type" => "firewall_rule",
651
- "name" => sg.name,
652
- "no_create_wait" => true
653
- }
654
- elsif sg['name'] and !sg['deploy_id']
655
- acl["dependencies"] << {
656
- "type" => "firewall_rule",
657
- "name" => sg['name'],
658
- "no_create_wait" => true
659
- }
651
+ if sg['name'] and !sg['deploy_id']
652
+ MU::Config.addDependency(acl, sg['name'], "firewall_rule", no_create_wait: true)
660
653
  end
661
654
  }
662
655
  end
663
656
 
664
657
  if rule['loadbalancers']
665
658
  rule['loadbalancers'].each { |lb|
666
- if lb.is_a?(MU::Config::Ref) and lb.name
667
- acl["dependencies"] << {
668
- "type" => "loadbalancer",
669
- "name" => lb.name,
670
- "phase" => "groom"
671
- }
672
- elsif lb['name'] and !lb['deploy_id']
673
- acl["dependencies"] << {
674
- "type" => "loadbalancer",
675
- "name" => lb['name'],
676
- "phase" => "groom"
677
- }
659
+ if lb['name'] and !lb['deploy_id']
660
+ MU::Config.addDependency(acl, lb['name'], "loadbalancer", phase: "groom")
678
661
  end
679
662
  }
680
663
  end
@@ -716,32 +699,7 @@ module MU
716
699
 
717
700
  private
718
701
 
719
- #########################################################################
720
- # Manufacture an EC2 security group. The second parameter, rules, is an
721
- # "ingress_rules" structure parsed and validated by MU::Config.
722
- #########################################################################
723
- def setRules(rules, add_to_self: false, ingress: true, egress: false)
724
- describe
725
- # XXX warn about attempt to set rules before we exist
726
- return if rules.nil? or rules.size == 0 or !@cloud_id
727
-
728
- # add_to_self means that this security is a "member" of its own rules
729
- # (which is to say, objects that have this SG are allowed in my these
730
- # rules)
731
- if add_to_self
732
- rules.each { |rule|
733
- if rule['sgs'].nil? or !rule['sgs'].include?(@cloud_id)
734
- new_rule = rule.clone
735
- new_rule.delete('hosts')
736
- rule['sgs'] = Array.new if rule['sgs'].nil?
737
- rule['sgs'] << @cloud_id
738
- end
739
- }
740
- end
741
-
742
- ec2_rules = convertToEc2(rules)
743
- ext_permissions = MU.structToHash(cloud_desc.ip_permissions)
744
-
702
+ def purge_extraneous_rules(ec2_rules, ext_permissions)
745
703
  # Purge any old rules that we're sure we created (check the comment)
746
704
  # but which are no longer configured.
747
705
  ext_permissions.each { |ext_rule|
@@ -778,97 +736,109 @@ module MU
778
736
  ip_permissions: [ext_rule]
779
737
  )
780
738
  end
781
-
782
739
  }
740
+ end
783
741
 
784
- # Creating an empty security group is ok, so don't freak out if we get
785
- # a null rule list.
786
- if !ec2_rules.nil?
787
- ec2_rules.uniq!
788
- retries = 0
789
- ec2_rules.each { |rule|
790
- haverule = nil
791
- different = false
792
- ext_permissions.each { |ext_rule|
793
- if rule[:from_port] == ext_rule[:from_port] and
794
- rule[:to_port] == ext_rule[:to_port] and
795
- rule[:ip_protocol] == ext_rule[:ip_protocol]
796
- haverule = ext_rule
797
- ext_rule.keys.each { |k|
798
- if ext_rule[k].nil? or ext_rule[k] == []
799
- haverule.delete(k)
800
- end
801
- different = true if rule[k] != ext_rule[k]
802
- }
803
- break
804
- end
805
- }
806
- if haverule and !different
807
- MU.log "Security Group rule already up-to-date in #{@mu_name}", MU::DEBUG, details: rule
808
- next
742
+ #########################################################################
743
+ # Manufacture an EC2 security group. The second parameter, rules, is an
744
+ # "ingress_rules" structure parsed and validated by MU::Config.
745
+ #########################################################################
746
+ def setRules(rules, add_to_self: false, ingress: true, egress: false)
747
+ # XXX warn about attempt to set rules before we exist
748
+ return if rules.nil? or rules.size == 0 or !@cloud_id
749
+
750
+ # add_to_self means that this security is a "member" of its own rules
751
+ # (which is to say, objects that have this SG are allowed in my these
752
+ # rules)
753
+ if add_to_self
754
+ rules.each { |rule|
755
+ if rule['sgs'].nil? or !rule['sgs'].include?(@cloud_id)
756
+ new_rule = rule.clone
757
+ new_rule.delete('hosts')
758
+ rule['sgs'] = Array.new if rule['sgs'].nil?
759
+ rule['sgs'] << @cloud_id
809
760
  end
761
+ }
762
+ end
810
763
 
811
- MU.log "Setting #{ingress ? "ingress" : "egress"} rule in Security Group #{@mu_name} (#{@cloud_id})", MU::NOTICE, details: rule
812
- begin
813
-
814
- if ingress
815
- if haverule
816
- begin
817
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).revoke_security_group_ingress(
818
- group_id: @cloud_id,
819
- ip_permissions: [haverule]
820
- )
821
- rescue Aws::EC2::Errors::InvalidPermissionNotFound => e
822
- end
764
+ ec2_rules = convertToEc2(rules)
765
+ return if ec2_rules.nil?
766
+
767
+ ext_permissions = MU.structToHash(cloud_desc(use_cache: false).ip_permissions)
768
+
769
+ purge_extraneous_rules(ec2_rules, ext_permissions)
770
+
771
+ ec2_rules.uniq!
772
+ ec2_rules.each { |rule|
773
+ haverule = nil
774
+ different = false
775
+ ext_permissions.each { |ext_rule|
776
+ if rule[:from_port] == ext_rule[:from_port] and
777
+ rule[:to_port] == ext_rule[:to_port] and
778
+ rule[:ip_protocol] == ext_rule[:ip_protocol]
779
+ haverule = ext_rule
780
+ ext_rule.keys.each { |k|
781
+ if ext_rule[k].nil? or ext_rule[k] == []
782
+ haverule.delete(k)
823
783
  end
784
+ different = true if rule[k] != ext_rule[k]
785
+ }
786
+ break
787
+ end
788
+ }
789
+ if haverule and !different
790
+ MU.log "Security Group rule already up-to-date in #{@mu_name}", MU::DEBUG, details: rule
791
+ next
792
+ end
793
+
794
+ MU.log "Setting #{ingress ? "ingress" : "egress"} rule in Security Group #{@mu_name} (#{@cloud_id})", MU::NOTICE, details: rule
795
+
796
+ MU.retrier([Aws::EC2::Errors::InvalidGroupNotFound], max: 10, wait: 10, ignoreme: [Aws::EC2::Errors::InvalidPermissionDuplicate]) {
797
+ if ingress
798
+ if haverule
824
799
  begin
825
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).authorize_security_group_ingress(
800
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).revoke_security_group_ingress(
826
801
  group_id: @cloud_id,
827
- ip_permissions: [rule]
802
+ ip_permissions: [haverule]
828
803
  )
829
- rescue Aws::EC2::Errors::InvalidParameterCombination => e
830
- MU.log "FirewallRule #{@mu_name} had a bogus rule: #{e.message}", MU::ERR, details: rule
831
- raise e
804
+ rescue Aws::EC2::Errors::InvalidPermissionNotFound
832
805
  end
833
806
  end
834
-
835
- if egress
836
- if haverule
837
- begin
838
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).revoke_security_group_egress(
839
- group_id: @cloud_id,
840
- ip_permissions: [haverule]
841
- )
842
- rescue Aws::EC2::Errors::InvalidPermissionNotFound => e
843
- end
844
- end
845
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).authorize_security_group_egress(
807
+ begin
808
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).authorize_security_group_ingress(
846
809
  group_id: @cloud_id,
847
810
  ip_permissions: [rule]
848
811
  )
812
+ rescue Aws::EC2::Errors::InvalidParameterCombination => e
813
+ MU.log "FirewallRule #{@mu_name} had a bogus rule: #{e.message}", MU::ERR, details: rule
814
+ raise e
849
815
  end
816
+ end
850
817
 
851
- rescue Aws::EC2::Errors::InvalidGroupNotFound => e
852
- MU.log "#{@mu_name} (#{@cloud_id}) does not yet exist", MU::WARN
853
- retries = retries + 1
854
- if retries < 10
855
- sleep 10
856
- retry
857
- else
858
- raise MuError, "#{@mu_name} does not exist", e.backtrace
818
+ if egress
819
+ if haverule
820
+ begin
821
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).revoke_security_group_egress(
822
+ group_id: @cloud_id,
823
+ ip_permissions: [haverule]
824
+ )
825
+ rescue Aws::EC2::Errors::InvalidPermissionNotFound
826
+ end
859
827
  end
860
- rescue Aws::EC2::Errors::InvalidPermissionDuplicate => e
861
- MU.log "Attempt to add duplicate rule to #{@mu_name}", MU::DEBUG, details: rule
828
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).authorize_security_group_egress(
829
+ group_id: @cloud_id,
830
+ ip_permissions: [rule]
831
+ )
862
832
  end
863
833
  }
864
- end
834
+ }
865
835
 
866
836
  end
867
837
 
868
- #########################################################################
869
- # Convert our config languages description of firewall rules into Amazon's.
870
- # This rule structure is as defined in MU::Config.
871
- #########################################################################
838
+ #######################################################################
839
+ # Convert our config languages description of firewall rules into
840
+ # Amazon's. Our rule structure is as defined in MU::Config.
841
+ #######################################################################
872
842
  def convertToEc2(rules)
873
843
  ec2_rules = []
874
844
  if rules != nil
@@ -992,8 +962,8 @@ module MU
992
962
  ec2_rules << ec2_rule
993
963
  }
994
964
  end
995
- ec2_rules.uniq!
996
- return ec2_rules
965
+
966
+ ec2_rules.uniq
997
967
  end
998
968
 
999
969
  end #class