cloud-mu 1.9.0.pre.beta → 2.0.0.pre.alpha

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/Berksfile +16 -54
  3. data/Berksfile.lock +14 -62
  4. data/bin/mu-aws-setup +131 -108
  5. data/bin/mu-configure +311 -74
  6. data/bin/mu-gcp-setup +84 -62
  7. data/bin/mu-load-config.rb +46 -2
  8. data/bin/mu-self-update +11 -9
  9. data/bin/mu-upload-chef-artifacts +4 -4
  10. data/{mu.gemspec → cloud-mu.gemspec} +2 -2
  11. data/cookbooks/awscli/Berksfile +8 -0
  12. data/cookbooks/mu-activedirectory/Berksfile +11 -0
  13. data/cookbooks/mu-firewall/Berksfile +9 -0
  14. data/cookbooks/mu-firewall/metadata.rb +1 -1
  15. data/cookbooks/mu-glusterfs/Berksfile +10 -0
  16. data/cookbooks/mu-jenkins/Berksfile +14 -0
  17. data/cookbooks/mu-master/Berksfile +23 -0
  18. data/cookbooks/mu-master/attributes/default.rb +1 -1
  19. data/cookbooks/mu-master/metadata.rb +2 -2
  20. data/cookbooks/mu-master/recipes/default.rb +1 -1
  21. data/cookbooks/mu-master/recipes/init.rb +7 -3
  22. data/cookbooks/mu-master/recipes/ssl-certs.rb +1 -0
  23. data/cookbooks/mu-mongo/Berksfile +10 -0
  24. data/cookbooks/mu-openvpn/Berksfile +11 -0
  25. data/cookbooks/mu-php54/Berksfile +13 -0
  26. data/cookbooks/mu-splunk/Berksfile +10 -0
  27. data/cookbooks/mu-tools/Berksfile +21 -0
  28. data/cookbooks/mu-tools/files/default/Mu_CA.pem +15 -15
  29. data/cookbooks/mu-utility/Berksfile +9 -0
  30. data/cookbooks/mu-utility/metadata.rb +2 -1
  31. data/cookbooks/nagios/Berksfile +7 -4
  32. data/cookbooks/s3fs/Berksfile +9 -0
  33. data/environments/dev.json +6 -6
  34. data/environments/prod.json +6 -6
  35. data/modules/mu.rb +20 -42
  36. data/modules/mu/cleanup.rb +102 -100
  37. data/modules/mu/cloud.rb +90 -28
  38. data/modules/mu/clouds/aws.rb +449 -218
  39. data/modules/mu/clouds/aws/alarm.rb +29 -17
  40. data/modules/mu/clouds/aws/cache_cluster.rb +78 -64
  41. data/modules/mu/clouds/aws/collection.rb +25 -18
  42. data/modules/mu/clouds/aws/container_cluster.rb +73 -66
  43. data/modules/mu/clouds/aws/database.rb +124 -116
  44. data/modules/mu/clouds/aws/dnszone.rb +27 -20
  45. data/modules/mu/clouds/aws/firewall_rule.rb +30 -22
  46. data/modules/mu/clouds/aws/folder.rb +18 -3
  47. data/modules/mu/clouds/aws/function.rb +77 -23
  48. data/modules/mu/clouds/aws/group.rb +19 -12
  49. data/modules/mu/clouds/aws/habitat.rb +153 -0
  50. data/modules/mu/clouds/aws/loadbalancer.rb +59 -52
  51. data/modules/mu/clouds/aws/log.rb +30 -23
  52. data/modules/mu/clouds/aws/msg_queue.rb +29 -20
  53. data/modules/mu/clouds/aws/notifier.rb +222 -0
  54. data/modules/mu/clouds/aws/role.rb +178 -90
  55. data/modules/mu/clouds/aws/search_domain.rb +40 -24
  56. data/modules/mu/clouds/aws/server.rb +169 -137
  57. data/modules/mu/clouds/aws/server_pool.rb +60 -83
  58. data/modules/mu/clouds/aws/storage_pool.rb +59 -31
  59. data/modules/mu/clouds/aws/user.rb +36 -27
  60. data/modules/mu/clouds/aws/userdata/linux.erb +101 -93
  61. data/modules/mu/clouds/aws/vpc.rb +250 -189
  62. data/modules/mu/clouds/azure.rb +132 -0
  63. data/modules/mu/clouds/cloudformation.rb +65 -1
  64. data/modules/mu/clouds/cloudformation/alarm.rb +8 -0
  65. data/modules/mu/clouds/cloudformation/cache_cluster.rb +7 -0
  66. data/modules/mu/clouds/cloudformation/collection.rb +7 -0
  67. data/modules/mu/clouds/cloudformation/database.rb +7 -0
  68. data/modules/mu/clouds/cloudformation/dnszone.rb +7 -0
  69. data/modules/mu/clouds/cloudformation/firewall_rule.rb +9 -2
  70. data/modules/mu/clouds/cloudformation/loadbalancer.rb +7 -0
  71. data/modules/mu/clouds/cloudformation/log.rb +7 -0
  72. data/modules/mu/clouds/cloudformation/server.rb +7 -0
  73. data/modules/mu/clouds/cloudformation/server_pool.rb +7 -0
  74. data/modules/mu/clouds/cloudformation/vpc.rb +7 -0
  75. data/modules/mu/clouds/google.rb +214 -110
  76. data/modules/mu/clouds/google/container_cluster.rb +42 -24
  77. data/modules/mu/clouds/google/database.rb +15 -6
  78. data/modules/mu/clouds/google/firewall_rule.rb +17 -25
  79. data/modules/mu/clouds/google/group.rb +13 -5
  80. data/modules/mu/clouds/google/habitat.rb +105 -0
  81. data/modules/mu/clouds/google/loadbalancer.rb +28 -20
  82. data/modules/mu/clouds/google/server.rb +93 -354
  83. data/modules/mu/clouds/google/server_pool.rb +18 -10
  84. data/modules/mu/clouds/google/user.rb +22 -14
  85. data/modules/mu/clouds/google/vpc.rb +97 -69
  86. data/modules/mu/config.rb +133 -38
  87. data/modules/mu/config/alarm.rb +25 -0
  88. data/modules/mu/config/cache_cluster.rb +5 -3
  89. data/modules/mu/config/cache_cluster.yml +23 -0
  90. data/modules/mu/config/database.rb +25 -16
  91. data/modules/mu/config/database.yml +3 -3
  92. data/modules/mu/config/function.rb +1 -2
  93. data/modules/mu/config/{project.rb → habitat.rb} +10 -10
  94. data/modules/mu/config/notifier.rb +85 -0
  95. data/modules/mu/config/notifier.yml +9 -0
  96. data/modules/mu/config/role.rb +1 -1
  97. data/modules/mu/config/search_domain.yml +2 -2
  98. data/modules/mu/config/server.rb +13 -1
  99. data/modules/mu/config/server.yml +3 -3
  100. data/modules/mu/config/server_pool.rb +3 -1
  101. data/modules/mu/config/storage_pool.rb +3 -1
  102. data/modules/mu/config/storage_pool.yml +19 -0
  103. data/modules/mu/config/vpc.rb +70 -8
  104. data/modules/mu/groomers/chef.rb +2 -3
  105. data/modules/mu/kittens.rb +500 -122
  106. data/modules/mu/master.rb +5 -5
  107. data/modules/mu/mommacat.rb +151 -91
  108. data/modules/tests/super_complex_bok.yml +12 -0
  109. data/modules/tests/super_simple_bok.yml +12 -0
  110. data/spec/mu/clouds/azure_spec.rb +82 -0
  111. data/spec/spec_helper.rb +105 -0
  112. metadata +26 -5
  113. data/modules/mu/clouds/aws/notification.rb +0 -139
  114. data/modules/mu/config/notification.rb +0 -44
@@ -39,8 +39,12 @@ module MU
39
39
  class MuCloudFlagNotImplemented < StandardError;
40
40
  end
41
41
 
42
- generic_class_methods = [:find, :cleanup, :validateConfig, :schema]
43
- generic_instance_methods = [:create, :notify, :mu_name, :cloud_id, :config, :cloud_desc]
42
+ # Methods which a cloud resource implementation, e.g. Server, must implement
43
+ generic_class_methods = [:find, :cleanup, :validateConfig, :schema, :isGlobal?]
44
+ generic_instance_methods = [:create, :notify, :mu_name, :cloud_id, :config]
45
+
46
+ # Class methods which the base of a cloud implementation must implement
47
+ generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl]
44
48
 
45
49
  # Initialize empty classes for each of these. We'll fill them with code
46
50
  # later; we're doing this here because otherwise the parser yells about
@@ -80,7 +84,7 @@ module MU
80
84
  class Alarm;
81
85
  end
82
86
  # Stub base class; real implementations generated at runtime
83
- class Notification;
87
+ class Notifier;
84
88
  end
85
89
  # Stub base class; real implementations generated at runtime
86
90
  class Log;
@@ -98,7 +102,7 @@ module MU
98
102
  class MsgQueue;
99
103
  end
100
104
  # Stub base class; real implementations generated at runtime
101
- class Project;
105
+ class Habitat;
102
106
  end
103
107
  # Stub base class; real implementations generated at runtime
104
108
  class Folder;
@@ -227,12 +231,12 @@ module MU
227
231
  :class => generic_class_methods,
228
232
  :instance => generic_instance_methods + [:groom]
229
233
  },
230
- :Notification => {
234
+ :Notifier => {
231
235
  :has_multiples => false,
232
236
  :can_live_in_vpc => false,
233
- :cfg_name => "notification",
234
- :cfg_plural => "notifications",
235
- :interface => self.const_get("Notification"),
237
+ :cfg_name => "notifier",
238
+ :cfg_plural => "notifiers",
239
+ :interface => self.const_get("Notifier"),
236
240
  :deps_wait_on_my_creation => false,
237
241
  :waits_on_parent_completion => false,
238
242
  :class => generic_class_methods,
@@ -304,12 +308,12 @@ module MU
304
308
  :class => generic_class_methods,
305
309
  :instance => generic_instance_methods + [:groom]
306
310
  },
307
- :Project => {
311
+ :Habitat => {
308
312
  :has_multiples => false,
309
313
  :can_live_in_vpc => false,
310
- :cfg_name => "project",
311
- :cfg_plural => "projects",
312
- :interface => self.const_get("Project"),
314
+ :cfg_name => "habitat",
315
+ :cfg_plural => "habitats",
316
+ :interface => self.const_get("Habitat"),
313
317
  :deps_wait_on_my_creation => true,
314
318
  :waits_on_parent_completion => true,
315
319
  :class => generic_class_methods,
@@ -399,16 +403,30 @@ module MU
399
403
  }
400
404
  end
401
405
 
406
+ # List of known/supported Cloud providers. This may be modified at runtime
407
+ # if an implemention is defective or missing required methods.
408
+ @@supportedCloudList = ['AWS', 'CloudFormation', 'Google', 'Azure']
409
+
402
410
  # List of known/supported Cloud providers
403
411
  def self.supportedClouds
404
- ["AWS", "CloudFormation", "Google"]
412
+ @@supportedCloudList
405
413
  end
406
414
 
407
415
  # Load the container class for each cloud we know about, and inject autoload
408
416
  # code for each of its supported resource type classes.
417
+ failed = []
409
418
  MU::Cloud.supportedClouds.each { |cloud|
410
419
  require "mu/clouds/#{cloud.downcase}"
420
+ cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
421
+ generic_class_methods_toplevel.each { |method|
422
+ if !cloudclass.respond_to?(method)
423
+ MU.log "MU::Cloud::#{cloud} has not implemented required class method #{method}, disabling", MU::ERR
424
+ failed << cloud
425
+ end
426
+ }
411
427
  }
428
+ failed.uniq!
429
+ @@supportedCloudList = @@supportedCloudList - failed
412
430
 
413
431
  # @return [Mutex]
414
432
  def self.userdata_mutex
@@ -559,6 +577,7 @@ module MU
559
577
  attr_reader :deploy_id
560
578
  attr_reader :mu_name
561
579
  attr_reader :cloud_id
580
+ attr_reader :credentials
562
581
  attr_reader :url
563
582
  attr_reader :config
564
583
  attr_reader :deploydata
@@ -623,6 +642,7 @@ module MU
623
642
  def initialize(mommacat: nil,
624
643
  mu_name: nil,
625
644
  cloud_id: nil,
645
+ credentials: nil,
626
646
  kitten_cfg: nil,
627
647
  delayed_save: false)
628
648
  raise MuError, "Cannot invoke Cloud objects without a configuration" if kitten_cfg.nil?
@@ -631,7 +651,9 @@ module MU
631
651
  @config = kitten_cfg
632
652
  @delayed_save = delayed_save
633
653
  @cloud_id = cloud_id
634
-
654
+ @credentials = credentials
655
+ @credentials ||= kitten_cfg['credentials']
656
+
635
657
  if !@deploy.nil?
636
658
  @deploy_id = @deploy.deploy_id
637
659
  MU.log "Initializing an instance of #{self.class.name} in #{@deploy_id} #{mu_name}", MU::DEBUG, details: kitten_cfg
@@ -720,21 +742,23 @@ module MU
720
742
  end
721
743
  end
722
744
  end
723
-
724
- def cloud_desc
745
+
746
+ def cloud_desc()
725
747
  describe
726
748
  if !@cloudobj.nil?
727
- @cloud_desc = @cloudobj.cloud_desc
749
+ @cloud_desc_cache ||= @cloudobj.cloud_desc
728
750
  @url = @cloudobj.url if @cloudobj.respond_to?(:url)
729
- elsif !@config.nil? and !@cloud_id.nil?
751
+ end
752
+ if !@config.nil? and !@cloud_id.nil? and @cloud_desc_cache.nil?
730
753
  # The find() method should be returning a Hash with the cloud_id
731
754
  # as a key and a cloud platform descriptor as the value.
732
755
  begin
733
- matches = self.class.find(region: @config['region'], cloud_id: @cloud_id, flags: @config)
756
+
757
+ matches = self.class.find(region: @config['region'], cloud_id: @cloud_id, flags: @config, credentials: @credentials)
734
758
  if !matches.nil? and matches.is_a?(Hash) and matches.has_key?(@cloud_id)
735
- @cloud_desc = matches[@cloud_id]
759
+ @cloud_desc_cache = matches[@cloud_id]
736
760
  else
737
- MU.log "Failed to find a live #{self.class.shortname} with identifier #{@cloud_id} in #{@config['region']}, which has a record in deploy #{@deploy.deploy_id}", MU::WARN, details: caller
761
+ MU.log "Failed to find a live #{self.class.shortname} with identifier #{@cloud_id} in #{@credentials}/#{@config['region']}, which has a record in deploy #{@deploy.deploy_id}", MU::WARN, details: caller
738
762
  end
739
763
  rescue Exception => e
740
764
  MU.log "Got #{e.inspect} trying to find cloud handle for #{self.class.shortname} #{@mu_name} (#{@cloud_id})", MU::WARN
@@ -742,7 +766,7 @@ module MU
742
766
  end
743
767
  end
744
768
 
745
- return @cloud_desc
769
+ return @cloud_desc_cache
746
770
  end
747
771
 
748
772
  # Retrieve all of the known metadata for this resource.
@@ -755,6 +779,7 @@ module MU
755
779
  end
756
780
  res_type = self.class.cfg_plural
757
781
  res_name = @config['name'] if !@config.nil?
782
+ @credentials ||= @config['credentials'] if !@config.nil?
758
783
  deploydata = nil
759
784
  if !@deploy.nil? and @deploy.is_a?(MU::MommaCat) and
760
785
  !@deploy.deployment.nil? and
@@ -836,11 +861,42 @@ module MU
836
861
  # Special dependencies: my containing VPC
837
862
  if self.class.can_live_in_vpc and !@config['vpc'].nil?
838
863
  MU.log "Loading VPC for #{self}", MU::DEBUG, details: @config['vpc']
839
- if !@config['vpc']["vpc_name"].nil? and
864
+ if !@config['vpc']["vpc_name"].nil? and @deploy
865
+ sib_by_name = @deploy.findLitterMate(name: @config['vpc']['vpc_name'], type: "vpcs", return_all: true)
866
+ if sib_by_name.is_a?(Array)
867
+ if sib_by_name.size == 1
868
+ @vpc = matches.first
869
+ else
870
+ # XXX ok but this is the wrong place for this really the config parser needs to sort this out somehow
871
+ # we got multiple matches, try to pick one by preferred subnet
872
+ # behavior
873
+ sib_by_name.each { |sibling|
874
+ all_private = sibling.subnets.map { |s| s.private? }.all?(true)
875
+ all_public = sibling.subnets.map { |s| s.private? }.all?(false)
876
+ if all_private and ["private", "all_private"].include?(@config['vpc']['subnet_pref'])
877
+ @vpc = sibling
878
+ break
879
+ elsif all_public and ["public", "all_public"].include?(@config['vpc']['subnet_pref'])
880
+ @vpc = sibling
881
+ break
882
+ else
883
+ MU.log "Got multiple matching VPCs for #{@mu_name}, so I'm arbitrarily choosing #{sibling.mu_name}"
884
+ @vpc = sibling
885
+ break
886
+ end
887
+ }
888
+ end
889
+ else
890
+ MU.log "in dependencies() and findLitterMate gave me "+sib_by_name.to_s+" on behalf of "+self.to_s, MU::NOTICE, details: @config['vpc']
891
+ @vpc = sib_by_name
892
+ end
893
+ end
894
+
895
+ if !@vpc and !@config['vpc']["vpc_name"].nil? and
840
896
  @dependencies.has_key?("vpc") and
841
897
  @dependencies["vpc"].has_key?(@config['vpc']["vpc_name"])
842
898
  @vpc = @dependencies["vpc"][@config['vpc']["vpc_name"]]
843
- else
899
+ elsif !@vpc
844
900
  tag_key, tag_value = @config['vpc']['tag'].split(/=/, 2) if !@config['vpc']['tag'].nil?
845
901
  if !@config['vpc'].has_key?("vpc_id") and
846
902
  !@config['vpc'].has_key?("deploy_id") and !@deploy.nil?
@@ -883,12 +939,14 @@ module MU
883
939
  nat_cloud_id: @config['vpc']['nat_host_id'],
884
940
  nat_filter_key: "vpc-id",
885
941
  region: @config['vpc']["region"],
886
- nat_filter_value: @vpc.cloud_id
942
+ nat_filter_value: @vpc.cloud_id,
943
+ credentials: @config['credentials']
887
944
  )
888
945
  else
889
946
  @nat = @vpc.findNat(
890
947
  nat_cloud_id: @config['vpc']['nat_host_id'],
891
- region: @config['vpc']["region"]
948
+ region: @config['vpc']["region"],
949
+ credentials: @config['credentials']
892
950
  )
893
951
  end
894
952
  end
@@ -939,9 +997,13 @@ module MU
939
997
  # sense there
940
998
  cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
941
999
  if args[:region] and cloudbase.respond_to?(:listRegions)
942
- next if !cloudbase.listRegions.include?(args[:region])
1000
+ next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
1001
+ end
1002
+ begin
1003
+ cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
1004
+ rescue MU::MuError => e
1005
+ next
943
1006
  end
944
- cloudclass = MU::Cloud.loadCloudType(cloud, shortname)
945
1007
 
946
1008
  found = cloudclass.find(args)
947
1009
  if !found.nil?
@@ -26,81 +26,112 @@ module MU
26
26
  class AWS
27
27
  @@myRegion_var = nil
28
28
 
29
- @@creds_loaded = false
29
+ @@creds_loaded = {}
30
30
 
31
31
  # Load some credentials for using the AWS API
32
- def self.loadCredentials
33
- return if @@creds_loaded
34
- if $MU_CFG and $MU_CFG['aws']
35
- loaded = false
36
- if $MU_CFG['aws']['access_key'] and $MU_CFG['aws']['access_secret'] and
37
- # access key and secret just sitting in mu.yaml
38
- !$MU_CFG['aws']['access_key'].empty? and
39
- !$MU_CFG['aws']['access_secret'].empty?
40
- Aws.config = {
41
- access_key_id: $MU_CFG['aws']['access_key'],
42
- secret_access_key: $MU_CFG['aws']['access_secret'],
43
- region: $MU_CFG['aws']['region']
44
- }
45
- loaded = true
46
- elsif $MU_CFG['aws']['credentials_file'] and
47
- !$MU_CFG['aws']['credentials_file'].empty?
48
- # pull access key and secret from an awscli-style credentials file
49
- begin
50
- File.read($MU_CFG["aws"]["credentials_file"]) # make sure it's there
51
- credfile = IniFile.load($MU_CFG["aws"]["credentials_file"])
32
+ # @param name [String]: The name of the mu.yaml AWS credential set to use. If not specified, will use the default credentials, and set the global Aws.config credentials to those.
33
+ # @return [Aws::Credentials]
34
+ def self.loadCredentials(name = nil)
35
+ @@creds_loaded ||= {}
52
36
 
53
- if !credfile.sections or credfile.sections.size == 0
54
- raise ::IniFile::Error, "No AWS profiles found in #{$MU_CFG["aws"]["credentials_file"]}"
55
- end
56
- data = credfile.has_section?("default") ? credfile["default"] : credfile[credfile.sections.first]
57
- if data["aws_access_key_id"] and data["aws_secret_access_key"]
58
- Aws.config = {
59
- access_key_id: data['aws_access_key_id'],
60
- secret_access_key: data['aws_secret_access_key'],
61
- region: $MU_CFG['aws']['region']
62
- }
63
- loaded = true
64
- else
65
- MU.log "AWS credentials in #{$MU_CFG["aws"]["credentials_file"]} specified, but is missing aws_access_key_id or aws_secret_access_key elements", MU::WARN
37
+ if name.nil?
38
+ return @@creds_loaded["#default"] if @@creds_loaded["#default"]
39
+ else
40
+ return @@creds_loaded[name] if @@creds_loaded[name]
41
+ end
42
+
43
+ cred_cfg = credConfig(name)
44
+ if cred_cfg.nil?
45
+ return nil
46
+ end
47
+
48
+ loaded = false
49
+ cred_obj = nil
50
+ if cred_cfg['access_key'] and cred_cfg['access_secret'] and
51
+ # access key and secret just sitting in mu.yaml
52
+ !cred_cfg['access_key'].empty? and
53
+ !cred_cfg['access_secret'].empty?
54
+ cred_obj = Aws::Credentials.new(
55
+ cred_cfg['access_key'], cred_cfg['access_secret']
56
+ )
57
+ if name.nil?
58
+ # Aws.config = {
59
+ # access_key_id: cred_cfg['access_key'],
60
+ # secret_access_key: cred_cfg['access_secret'],
61
+ # region: cred_cfg['region']
62
+ # }
63
+ end
64
+ elsif cred_cfg['credentials_file'] and
65
+ !cred_cfg['credentials_file'].empty?
66
+
67
+ # pull access key and secret from an awscli-style credentials file
68
+ begin
69
+ File.read(cred_cfg["credentials_file"]) # make sure it's there
70
+ credfile = IniFile.load(cred_cfg["credentials_file"])
71
+
72
+ if !credfile.sections or credfile.sections.size == 0
73
+ raise ::IniFile::Error, "No AWS profiles found in #{cred_cfg["credentials_file"]}"
74
+ end
75
+ data = credfile.has_section?("default") ? credfile["default"] : credfile[credfile.sections.first]
76
+ if data["aws_access_key_id"] and data["aws_secret_access_key"]
77
+ cred_obj = Aws::Credentials.new(
78
+ data['aws_access_key_id'], data['aws_secret_access_key']
79
+ )
80
+ if name.nil?
81
+ # Aws.config = {
82
+ # access_key_id: data['aws_access_key_id'],
83
+ # secret_access_key: data['aws_secret_access_key'],
84
+ # region: cred_cfg['region']
85
+ # }
66
86
  end
67
- rescue IniFile::Error, Errno::ENOENT, Errno::EACCES => e
68
- MU.log "AWS credentials file #{$MU_CFG["aws"]["credentials_file"]} is missing or invalid", MU::WARN, details: e.message
87
+ else
88
+ MU.log "AWS credentials in #{cred_cfg["credentials_file"]} specified, but is missing aws_access_key_id or aws_secret_access_key elements", MU::WARN
69
89
  end
70
- elsif $MU_CFG['aws']['credentials'] and
71
- !$MU_CFG['aws']['credentials'].empty?
72
- # pull access key and secret from a vault
73
- begin
74
- vault, item = $MU_CFG["aws"]["credentials"].split(/:/)
75
- data = MU::Groomer::Chef.getSecret(vault: vault, item: item).to_h
76
- if data["access_key"] and data["access_secret"]
77
- Aws.config = {
78
- access_key_id: data['access_key'],
79
- secret_access_key: data['access_secret'],
80
- region: $MU_CFG['aws']['region']
81
- }
82
- loaded = true
83
- else
84
- MU.log "AWS credentials vault:item #{$MU_CFG["aws"]["credentials"]} specified, but is missing access_key or access_secret elements", MU::WARN
90
+ rescue IniFile::Error, Errno::ENOENT, Errno::EACCES => e
91
+ MU.log "AWS credentials file #{cred_cfg["credentials_file"]} is missing or invalid", MU::WARN, details: e.message
92
+ end
93
+ elsif cred_cfg['credentials'] and
94
+ !cred_cfg['credentials'].empty?
95
+ # pull access key and secret from a vault
96
+ begin
97
+ vault, item = cred_cfg["credentials"].split(/:/)
98
+ data = MU::Groomer::Chef.getSecret(vault: vault, item: item).to_h
99
+ if data["access_key"] and data["access_secret"]
100
+ cred_obj = Aws::Credentials.new(
101
+ cred_cfg['access_key'], cred_cfg['access_secret']
102
+ )
103
+ if name.nil?
104
+ # Aws.config = {
105
+ # access_key_id: data['access_key'],
106
+ # secret_access_key: data['access_secret'],
107
+ # region: cred_cfg['region']
108
+ # }
85
109
  end
86
- rescue MU::Groomer::Chef::MuNoSuchSecret
87
- MU.log "AWS credentials vault:item #{$MU_CFG["aws"]["credentials"]} specified, but does not exist", MU::WARN
110
+ else
111
+ MU.log "AWS credentials vault:item #{cred_cfg["credentials"]} specified, but is missing access_key or access_secret elements", MU::WARN
88
112
  end
113
+ rescue MU::Groomer::Chef::MuNoSuchSecret
114
+ MU.log "AWS credentials vault:item #{cred_cfg["credentials"]} specified, but does not exist", MU::WARN
89
115
  end
116
+ end
90
117
 
91
- if !loaded and hosted?
92
- # assume we've got an IAM profile and hope for the best
93
- ENV.delete('AWS_ACCESS_KEY_ID')
94
- ENV.delete('AWS_SECRET_ACCESS_KEY')
95
- Aws.config = {region: ENV['EC2_REGION']}
96
- loaded = true
97
- end
118
+ if !cred_obj and hosted?
119
+ # assume we've got an IAM profile and hope for the best
120
+ ENV.delete('AWS_ACCESS_KEY_ID')
121
+ ENV.delete('AWS_SECRET_ACCESS_KEY')
122
+ cred_obj = Aws::InstanceProfileCredentials.new
123
+ # if name.nil?
124
+ # Aws.config = {region: ENV['EC2_REGION']}
125
+ # end
126
+ end
98
127
 
99
- @@creds_loaded = loaded
100
- if !@@creds_loaded
101
- raise MuError, "AWS layer is enabled in mu.yaml, but I couldn't find working API credentials anywhere"
102
- end
128
+ if name.nil?
129
+ @@creds_loaded["#default"] = cred_obj
130
+ else
131
+ @@creds_loaded[name] = cred_obj
103
132
  end
133
+
134
+ cred_obj
104
135
  end
105
136
 
106
137
  # Any cloud-specific instance methods we require our resource
@@ -114,13 +145,17 @@ module MU
114
145
  # If we've configured AWS as a provider, or are simply hosted in AWS,
115
146
  # decide what our default region is.
116
147
  def self.myRegion
148
+ return @@myRegion_var if @@myRegion_var
149
+ return nil if credConfig.nil? and !hosted?
150
+
117
151
  if $MU_CFG and (!$MU_CFG['aws'] or !account_number) and !hosted?
118
152
  return nil
119
153
  end
154
+
120
155
  if $MU_CFG and $MU_CFG['aws'] and $MU_CFG['aws']['region']
121
- @@myRegion_var ||= MU::Cloud::AWS.ec2($MU_CFG['aws']['region']).describe_availability_zones.availability_zones.first.region_name
156
+ @@myRegion_var ||= MU::Cloud::AWS.ec2(region: $MU_CFG['aws']['region']).describe_availability_zones.availability_zones.first.region_name
122
157
  elsif ENV.has_key?("EC2_REGION") and !ENV['EC2_REGION'].empty?
123
- @@myRegion_var ||= MU::Cloud::AWS.ec2(ENV['EC2_REGION']).describe_availability_zones.availability_zones.first.region_name
158
+ @@myRegion_var ||= MU::Cloud::AWS.ec2(region: ENV['EC2_REGION']).describe_availability_zones.availability_zones.first.region_name
124
159
  else
125
160
  # hacky, but useful in a pinch
126
161
  az_str = MU::Cloud::AWS.getAWSMetaData("placement/availability-zone")
@@ -140,7 +175,7 @@ module MU
140
175
  # @param value [String]: The value of the tag to remove
141
176
  # @param region [String]: The cloud provider region
142
177
  def self.removeTag(key, value, resources = [], region: myRegion)
143
- MU::Cloud::AWS.ec2(region).delete_tags(
178
+ MU::Cloud::AWS.ec2(region: region).delete_tags(
144
179
  resources: resources,
145
180
  tags: [
146
181
  {
@@ -158,11 +193,11 @@ module MU
158
193
  # @param value [String]: The value of the tag
159
194
  # @param region [String]: The cloud provider region
160
195
  # @return [void,<Hash>]
161
- def self.createTag(key, value, resources = [], region: myRegion)
196
+ def self.createTag(key, value, resources = [], region: myRegion, credentials: nil)
162
197
 
163
198
  if !MU::Cloud::CloudFormation.emitCloudFormation
164
199
  begin
165
- MU::Cloud::AWS.ec2(region).create_tags(
200
+ MU::Cloud::AWS.ec2(region: region, credentials: credentials).create_tags(
166
201
  resources: resources,
167
202
  tags: [
168
203
  {
@@ -196,7 +231,7 @@ module MU
196
231
  # server resides.
197
232
  # @param region [String]: The region to search.
198
233
  # @return [Array<String>]: The Availability Zones in this region.
199
- def self.listAZs(region = MU.curRegion)
234
+ def self.listAZs(region: MU.curRegion, account: nil, credentials: nil)
200
235
  if $MU_CFG and (!$MU_CFG['aws'] or !account_number)
201
236
  return []
202
237
  end
@@ -204,7 +239,7 @@ module MU
204
239
  return @@azs[region]
205
240
  end
206
241
  if region
207
- azs = MU::Cloud::AWS.ec2(region).describe_availability_zones(
242
+ azs = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_availability_zones(
208
243
  filters: [name: "region-name", values: [region]]
209
244
  )
210
245
  end
@@ -218,21 +253,57 @@ module MU
218
253
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
219
254
  # @param deploy_id [String]: The deploy for which we're writing the secret
220
255
  # @param value [String]: The contents of the secret
221
- def self.writeDeploySecret(deploy_id, value, name = nil)
256
+ def self.writeDeploySecret(deploy_id, value, name = nil, credentials: nil)
222
257
  name ||= deploy_id+"-secret"
223
258
  begin
224
- MU.log "Writing #{name} to S3 bucket #{MU.adminBucketName}"
225
- MU::Cloud::AWS.s3(myRegion).put_object(
259
+ MU.log "Writing #{name} to S3 bucket #{adminBucketName(credentials)}"
260
+ MU::Cloud::AWS.s3(region: myRegion, credentials: credentials).put_object(
226
261
  acl: "private",
227
- bucket: MU.adminBucketName,
262
+ bucket: adminBucketName(credentials),
228
263
  key: name,
229
264
  body: value
230
265
  )
231
266
  rescue Aws::S3::Errors => e
232
- raise MU::MommaCat::DeployInitializeError, "Got #{e.inspect} trying to write #{name} to #{MU.adminBucketName}"
267
+ raise MU::MommaCat::DeployInitializeError, "Got #{e.inspect} trying to write #{name} to #{adminBucketName(credentials)}"
233
268
  end
234
269
  end
235
270
 
271
+ # Log bucket policy for enabling CloudTrail logging to our log bucket in S3.
272
+ def self.cloudtrailBucketPolicy(credentials = nil)
273
+ cfg = credConfig(credentials)
274
+ policy_json = '{
275
+ "Version": "2012-10-17",
276
+ "Statement": [
277
+ {
278
+ "Sid": "AWSCloudTrailAclCheck20131101",
279
+ "Effect": "Allow",
280
+ "Principal": {
281
+ "AWS": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':iam::<%= MU.account_number %>:root",
282
+ "Service": "cloudtrail.amazonaws.com"
283
+ },
284
+ "Action": "s3:GetBucketAcl",
285
+ "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'"
286
+ },
287
+ {
288
+ "Sid": "AWSCloudTrailWrite20131101",
289
+ "Effect": "Allow",
290
+ "Principal": {
291
+ "AWS": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':iam::'+credToAcct(credentials)+':root",
292
+ "Service": "cloudtrail.amazonaws.com"
293
+ },
294
+ "Action": "s3:PutObject",
295
+ "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'/AWSLogs/'+credToAcct(credentials)+'/*",
296
+ "Condition": {
297
+ "StringEquals": {
298
+ "s3:x-amz-acl": "bucket-owner-full-control"
299
+ }
300
+ }
301
+ }
302
+ ]
303
+ }'
304
+ ERB.new(policy_json).result
305
+ end
306
+
236
307
  @@is_in_aws = nil
237
308
 
238
309
  # Alias for #{MU::Cloud::AWS.hosted?}
@@ -293,32 +364,137 @@ module MU
293
364
  sample
294
365
  end
295
366
 
296
- @my_acct_num = nil
367
+ @@my_acct_num = nil
368
+ @@my_hosted_cfg = nil
369
+ @@acct_to_profile_map = {}
370
+
371
+ # Map the name of a credential set back to an AWS account number
372
+ # @param name [String]
373
+ def self.credToAcct(name = nil)
374
+ creds = credConfig(name)
375
+
376
+ return creds['account_number'] if creds['account_number']
377
+
378
+ user_list = MU::Cloud::AWS.iam(credentials: name).list_users.users
379
+ acct_num = MU::Cloud::AWS.iam(credentials: name).list_users.users.first.arn.split(/:/)[4]
380
+ acct_num.to_s
381
+ end
382
+
383
+ # Return the name strings of all known sets of credentials for this cloud
384
+ # @return [Array<String>]
385
+ def self.listCredentials
386
+ if !$MU_CFG['aws']
387
+ return hosted? ? ["#default"] : nil
388
+ end
389
+
390
+ $MU_CFG['aws'].keys
391
+ end
392
+
393
+ def self.adminBucketName(credentials = nil)
394
+ #XXX find a default if this particular account doesn't have a log_bucket_name configured
395
+ cfg = credConfig(credentials)
396
+ cfg['log_bucket_name']
397
+ end
398
+
399
+ def self.adminBucketUrl(credentials = nil)
400
+ "s3://"+adminBucketName+"/"
401
+ end
402
+
403
+ # Return the $MU_CFG data associated with a particular profile/name/set of
404
+ # credentials. If no account name is specified, will return one flagged as
405
+ # default. Returns nil if AWS is not configured. Throws an exception if
406
+ # an account name is specified which does not exist.
407
+ # @param name [String]: The name of the key under 'aws' in mu.yaml to return
408
+ # @return [Hash,nil]
409
+ def self.credConfig(name = nil, name_only: false)
410
+ # If there's nothing in mu.yaml (which is wrong), but we're running
411
+ # on a machine hosted in AWS, *and* that machine has an IAM profile,
412
+ # fake it with those credentials and hope for the best.
413
+ if !$MU_CFG['aws'] or !$MU_CFG['aws'].is_a?(Hash) or $MU_CFG['aws'].size == 0
414
+ return @@my_hosted_cfg if @@my_hosted_cfg
415
+
416
+ if hosted?
417
+ begin
418
+ iam_data = JSON.parse(getAWSMetaData("iam/info"))
419
+ if iam_data["InstanceProfileArn"] and !iam_data["InstanceProfileArn"].empty?
420
+ @@my_hosted_cfg = hosted_config
421
+ return name_only ? "#default" : @@my_hosted_cfg
422
+ end
423
+ rescue JSON::ParserError => e
424
+ end
425
+ end
426
+
427
+ return nil
428
+ end
429
+
430
+ if name.nil?
431
+ $MU_CFG['aws'].each_pair { |name, cfg|
432
+ if cfg['default']
433
+ return name_only ? name : cfg
434
+ end
435
+ }
436
+ else
437
+ if $MU_CFG['aws'][name]
438
+ return name_only ? name : $MU_CFG['aws'][name]
439
+ elsif @@acct_to_profile_map[name.to_s]
440
+ return name_only ? name : @@acct_to_profile_map[name.to_s]
441
+ elsif name.is_a?(Integer) or name.match(/^\d+$/)
442
+ # Try to map backwards from an account id, if that's what we go
443
+ $MU_CFG['aws'].each_pair { |acctname, cfg|
444
+ if cfg['account_number'] and name.to_s == cfg['account_number'].to_s
445
+ return name_only ? acctname : $MU_CFG['aws'][acctname]
446
+ end
447
+ }
448
+
449
+ # Check each credential sets' resident account, then
450
+ $MU_CFG['aws'].each_pair { |acctname, cfg|
451
+ begin
452
+ user_list = MU::Cloud::AWS.iam(credentials: acctname).list_users.users
453
+ # rescue ::Aws::IAM::Errors => e # XXX why does this NameError here?
454
+ rescue Exception => e
455
+ MU.log e.inspect, MU::WARN, details: cfg
456
+ next
457
+ end
458
+ acct_num = MU::Cloud::AWS.iam(credentials: acctname).list_users.users.first.arn.split(/:/)[4]
459
+ if acct_num.to_s == name.to_s
460
+ cfg['account_number'] = acct_num.to_s
461
+ @@acct_to_profile_map[name.to_s] = cfg
462
+ return name_only ? name.to_s : cfg
463
+ return cfg
464
+ end
465
+ }
466
+ end
467
+
468
+ raise MuError, "AWS credential set #{name} was requested, but I see no such working credentials in mu.yaml"
469
+ end
470
+ end
297
471
 
298
- # Fetch the AWS account number where this Mu master resides. If it's not in
299
- # AWS at all, or otherwise cannot be determined, return nil.
300
- # here.
472
+ # Fetch the AWS account number where this Mu master resides. If it's not
473
+ # in AWS at all, or otherwise cannot be determined, return nil. here.
301
474
  # XXX account for Google and non-cloud situations
302
- # XXX but what about multi-account uuuugh
475
+ # XXX this needs to be "myAccountNumber" or somesuch
476
+ # XXX and maybe do the IAM thing for arbitrary, non-resident accounts
303
477
  def self.account_number
304
- return nil if !$MU_CFG['aws']
305
- return @my_acct_num if @my_acct_num
306
-
307
- begin
308
- user_list = MU::Cloud::AWS.iam($MU_CFG['aws']['region']).list_users.users
309
- # rescue ::Aws::IAM::Errors => e # XXX why does this NameError here?
310
- rescue Exception => e
311
- MU.log "Got #{e.inspect} while trying to figure out our account number", MU::WARN, details: caller
312
- end
313
- if user_list.nil? or user_list.size == 0
478
+ return nil if credConfig.nil?
479
+ return @@my_acct_num if @@my_acct_num
480
+ loadCredentials
481
+ # XXX take optional credential set argument
482
+
483
+ # begin
484
+ # user_list = MU::Cloud::AWS.iam(region: credConfig['region']).list_users.users
485
+ ## rescue ::Aws::IAM::Errors => e # XXX why does this NameError here?
486
+ # rescue Exception => e
487
+ # MU.log "Got #{e.inspect} while trying to figure out our account number", MU::WARN, details: caller
488
+ # end
489
+ # if user_list.nil? or user_list.size == 0
314
490
  mac = MU::Cloud::AWS.getAWSMetaData("network/interfaces/macs/").split(/\n/)[0]
315
491
  acct_num = MU::Cloud::AWS.getAWSMetaData("network/interfaces/macs/#{mac}owner-id")
316
492
  acct_num.chomp!
317
- else
318
- acct_num = MU::Cloud::AWS.iam($MU_CFG['aws']['region']).list_users.users.first.arn.split(/:/)[4]
319
- end
493
+ # else
494
+ # acct_num = MU::Cloud::AWS.iam(region: credConfig['region']).list_users.users.first.arn.split(/:/)[4]
495
+ # end
320
496
  MU.setVar("acct_num", acct_num)
321
- @my_acct_num ||= acct_num
497
+ @@my_acct_num ||= acct_num
322
498
  acct_num
323
499
  end
324
500
 
@@ -327,17 +503,19 @@ module MU
327
503
  # region that is local to this Mu server will be listed first.
328
504
  # @param us_only [Boolean]: Restrict results to United States only
329
505
  # @return [Array<String>]
330
- def self.listRegions(us_only = false)
331
- if $MU_CFG and (!$MU_CFG['aws'] or !account_number)
332
- return []
333
- end
506
+ def self.listRegions(us_only = false, credentials: nil)
507
+
334
508
  if @@regions.size == 0
335
- result = MU::Cloud::AWS.ec2(myRegion).describe_regions.regions
509
+ return [] if credConfig.nil?
510
+ result = MU::Cloud::AWS.ec2(region: myRegion, credentials: credentials).describe_regions.regions
336
511
  regions = []
337
512
  result.each { |r|
338
- @@regions[r.region_name] = Proc.new { listAZs(r.region_name) }
513
+ @@regions[r.region_name] = Proc.new {
514
+ listAZs(region: r.region_name, credentials: credentials)
515
+ }
339
516
  }
340
517
  end
518
+
341
519
  regions = if us_only
342
520
  @@regions.keys.delete_if { |r| !r.match(/^us\-/) }.uniq
343
521
  else
@@ -368,14 +546,14 @@ module MU
368
546
  # @param keyname [String]: The name of the key to create.
369
547
  # @param public_key [String]: The public key
370
548
  # @return [Array<String>]: keypairname, ssh_private_key, ssh_public_key
371
- def self.createEc2SSHKey(keyname, public_key)
549
+ def self.createEc2SSHKey(keyname, public_key, credentials: nil)
372
550
  # We replicate this key in all regions
373
551
  if !MU::Cloud::CloudFormation.emitCloudFormation
374
552
  MU::Cloud::AWS.listRegions.each { |region|
375
553
  MU.log "Replicating #{keyname} to EC2 in #{region}", MU::DEBUG, details: @ssh_public_key
376
- MU::Cloud::AWS.ec2(region).import_key_pair(
377
- key_name: keyname,
378
- public_key_material: public_key
554
+ MU::Cloud::AWS.ec2(region: region, credentials: credentials).import_key_pair(
555
+ key_name: keyname,
556
+ public_key_material: public_key
379
557
  )
380
558
  }
381
559
  end
@@ -389,9 +567,7 @@ module MU
389
567
  # @return [Hash]
390
568
  def self.listInstanceTypes(region = myRegion)
391
569
  return @@instance_types if @@instance_types and @@instance_types[region]
392
- if $MU_CFG and (!$MU_CFG['aws'] or !account_number)
393
- return {}
394
- end
570
+ return {} if credConfig.nil?
395
571
 
396
572
  human_region = @@regionLookup[region]
397
573
 
@@ -402,7 +578,7 @@ module MU
402
578
  begin
403
579
  # Pricing API isn't widely available, so ask a region we know supports
404
580
  # it
405
- resp = MU::Cloud::AWS.pricing("us-east-1").get_products(
581
+ resp = MU::Cloud::AWS.pricing(region: "us-east-1").get_products(
406
582
  service_code: "AmazonEC2",
407
583
  filters: [
408
584
  {
@@ -456,7 +632,7 @@ module MU
456
632
 
457
633
  if !name.nil? and !name.empty?
458
634
  matches = []
459
- acmcerts = MU::Cloud::AWS.acm(region).list_certificates(
635
+ acmcerts = MU::Cloud::AWS.acm(region: region).list_certificates(
460
636
  certificate_statuses: ["ISSUED"]
461
637
  )
462
638
  acmcerts.certificate_summary_list.each { |cert|
@@ -475,14 +651,14 @@ module MU
475
651
  if matches.size == 1
476
652
  return matches.first
477
653
  elsif matches.size == 0
478
- raise MuError, "No IAM or ACM certificate named #{name} was found"
654
+ raise MuError, "No IAM or ACM certificate named #{name} was found in #{region}"
479
655
  elsif matches.size > 1
480
- raise MuError, "Multiple certificates named #{name} were found. Remove extras or use ssl_certificate_id to supply the exact ARN of the one you want to use."
656
+ raise MuError, "Multiple certificates named #{name} were found in #{region}. Remove extras or use ssl_certificate_id to supply the exact ARN of the one you want to use."
481
657
  end
482
658
  end
483
659
 
484
660
  if id.match(/^arn:aws(?:-us-gov)?:acm/)
485
- resp = MU::Cloud::AWS.acm(region).get_certificate(
661
+ resp = MU::Cloud::AWS.acm(region: region).get_certificate(
486
662
  certificate_arn: id
487
663
  )
488
664
  if resp.nil?
@@ -510,221 +686,255 @@ module MU
510
686
  end
511
687
 
512
688
  # Amazon Certificate Manager API
513
- def self.acm(region = MU.curRegion)
689
+ def self.acm(region: MU.curRegion, credentials: nil)
514
690
  region ||= myRegion
515
- @@acm_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "ACM", region: region)
516
- @@acm_api[region]
691
+ @@acm_api[credentials] ||= {}
692
+ @@acm_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "ACM", region: region, credentials: credentials)
693
+ @@acm_api[credentials][region]
517
694
  end
518
695
 
519
696
  # Amazon's IAM API
520
- def self.iam(region = MU.curRegion)
521
- region ||= myRegion
522
- @@iam_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "IAM", region: region)
523
- @@iam_api[region]
697
+ def self.iam(credentials: nil)
698
+ @@iam_api[credentials] ||= MU::Cloud::AWS::Endpoint.new(api: "IAM", credentials: credentials)
699
+ @@iam_api[credentials]
524
700
  end
525
701
 
526
702
  # Amazon's EC2 API
527
- def self.ec2(region = MU.curRegion)
703
+ def self.ec2(region: MU.curRegion, credentials: nil)
528
704
  region ||= myRegion
529
- @@ec2_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "EC2", region: region)
530
- @@ec2_api[region]
705
+ @@ec2_api[credentials] ||= {}
706
+ @@ec2_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "EC2", region: region, credentials: credentials)
707
+ @@ec2_api[credentials][region]
531
708
  end
532
709
 
533
710
  # Amazon's Autoscaling API
534
- def self.autoscale(region = MU.curRegion)
711
+ def self.autoscale(region: MU.curRegion, credentials: nil)
535
712
  region ||= myRegion
536
- @@autoscale_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "AutoScaling", region: region)
537
- @@autoscale_api[region]
713
+ @@autoscale_api[credentials] ||= {}
714
+ @@autoscale_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "AutoScaling", region: region, credentials: credentials)
715
+ @@autoscale_api[credentials][region]
538
716
  end
539
717
 
540
718
  # Amazon's ElasticLoadBalancing API
541
- def self.elb(region = MU.curRegion)
719
+ def self.elb(region: MU.curRegion, credentials: nil)
542
720
  region ||= myRegion
543
- @@elb_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElasticLoadBalancing", region: region)
544
- @@elb_api[region]
721
+ @@elb_api[credentials] ||= {}
722
+ @@elb_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElasticLoadBalancing", region: region, credentials: credentials)
723
+ @@elb_api[credentials][region]
545
724
  end
546
725
 
547
726
  # Amazon's ElasticLoadBalancingV2 (ALB) API
548
- def self.elb2(region = MU.curRegion)
727
+ def self.elb2(region: MU.curRegion, credentials: nil)
549
728
  region ||= myRegion
550
- @@elb2_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElasticLoadBalancingV2", region: region)
551
- @@elb2_api[region]
729
+ @@elb2_api[credentials] ||= {}
730
+ @@elb2_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElasticLoadBalancingV2", region: region, credentials: credentials)
731
+ @@elb2_api[credentials][region]
552
732
  end
553
733
 
554
734
  # Amazon's Route53 API
555
- def self.route53(region = MU.curRegion)
556
- region ||= myRegion
557
- @@route53_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "Route53", region: region)
558
- @@route53_api[region]
735
+ def self.route53(credentials: nil)
736
+ @@route53_api[credentials] ||= MU::Cloud::AWS::Endpoint.new(api: "Route53", credentials: credentials)
737
+ @@route53_api[credentials]
559
738
  end
560
739
 
561
740
  # Amazon's RDS API
562
- def self.rds(region = MU.curRegion)
741
+ def self.rds(region: MU.curRegion, credentials: nil)
563
742
  region ||= myRegion
564
- @@rds_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "RDS", region: region)
565
- @@rds_api[region]
743
+ @@rds_api[credentials] ||= {}
744
+ @@rds_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "RDS", region: region, credentials: credentials)
745
+ @@rds_api[credentials][region]
566
746
  end
567
747
 
568
748
  # Amazon's CloudFormation API
569
- def self.cloudformation(region = MU.curRegion)
749
+ def self.cloudformation(region: MU.curRegion, credentials: nil)
570
750
  region ||= myRegion
571
- @@cloudformation_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudFormation", region: region)
572
- @@cloudformation_api[region]
751
+ @@cloudformation_api[credentials] ||= {}
752
+ @@cloudformation_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudFormation", region: region, credentials: credentials)
753
+ @@cloudformation_api[credentials][region]
573
754
  end
574
755
 
575
756
  # Amazon's S3 API
576
- def self.s3(region = MU.curRegion)
757
+ def self.s3(region: MU.curRegion, credentials: nil)
577
758
  region ||= myRegion
578
- @@s3_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "S3", region: region)
579
- @@s3_api[region]
759
+ @@s3_api[credentials] ||= {}
760
+ @@s3_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "S3", region: region, credentials: credentials)
761
+ @@s3_api[credentials][region]
580
762
  end
581
763
 
582
764
  # Amazon's CloudTrail API
583
- def self.cloudtrail(region = MU.curRegion)
765
+ def self.cloudtrail(region: MU.curRegion, credentials: nil)
584
766
  region ||= myRegion
585
- @@cloudtrail_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudTrail", region: region)
586
- @@cloudtrail_api[region]
767
+ @@cloudtrail_api[credentials] ||= {}
768
+ @@cloudtrail_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudTrail", region: region, credentials: credentials)
769
+ @@cloudtrail_api[credentials][region]
587
770
  end
588
771
 
589
772
  # Amazon's CloudWatch API
590
- def self.cloudwatch(region = MU.curRegion)
773
+ def self.cloudwatch(region: MU.curRegion, credentials: nil)
591
774
  region ||= myRegion
592
- @@cloudwatch_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudWatch", region: region)
593
- @@cloudwatch_api[region]
775
+ @@cloudwatch_api[credentials] ||= {}
776
+ @@cloudwatch_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudWatch", region: region, credentials: credentials)
777
+ @@cloudwatch_api[credentials][region]
594
778
  end
595
779
 
596
780
  # Amazon's Web Application Firewall API (Global, for CloudFront et al)
597
- def self.wafglobal(region = MU.curRegion)
781
+ def self.wafglobal(region: MU.curRegion, credentials: nil)
598
782
  region ||= myRegion
599
- @@wafglobal[region] ||= MU::Cloud::AWS::Endpoint.new(api: "WAF", region: region)
600
- @@wafglobal[region]
783
+ @@wafglobal_api[credentials] ||= {}
784
+ @@wafglobal[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "WAF", region: region, credentials: credentials)
785
+ @@wafglobal[credentials][region]
601
786
  end
602
787
 
603
788
 
604
789
  # Amazon's Web Application Firewall API (Regional, for ALBs et al)
605
- def self.waf(region = MU.curRegion)
790
+ def self.waf(region: MU.curRegion, credentials: nil)
606
791
  region ||= myRegion
607
- @@waf[region] ||= MU::Cloud::AWS::Endpoint.new(api: "WAFRegional", region: region)
608
- @@waf[region]
792
+ @@waf[credentials] ||= {}
793
+ @@waf[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "WAFRegional", region: region, credentials: credentials)
794
+ @@waf[credentials][region]
609
795
  end
610
796
 
611
797
  # Amazon's CloudWatchLogs API
612
- def self.cloudwatchlogs(region = MU.curRegion)
798
+ def self.cloudwatchlogs(region: MU.curRegion, credentials: nil)
613
799
  region ||= myRegion
614
- @@cloudwatchlogs_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudWatchLogs", region: region)
615
- @@cloudwatchlogs_api[region]
800
+ @@cloudwatchlogs_api[credentials] ||= {}
801
+ @@cloudwatchlogs_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudWatchLogs", region: region, credentials: credentials)
802
+ @@cloudwatchlogs_api[credentials][region]
616
803
  end
617
804
 
618
805
  # Amazon's CloudFront API
619
- def self.cloudfront(region = MU.curRegion)
806
+ def self.cloudfront(region: MU.curRegion, credentials: nil)
620
807
  region ||= myRegion
621
- @@cloudfront_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudFront", region: region)
622
- @@cloudfront_api[region]
808
+ @@cloudfront_api[credentials] ||= {}
809
+ @@cloudfront_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudFront", region: region, credentials: credentials)
810
+ @@cloudfront_api[credentials][region]
623
811
  end
624
812
 
625
813
  # Amazon's ElastiCache API
626
- def self.elasticache(region = MU.curRegion)
814
+ def self.elasticache(region: MU.curRegion, credentials: nil)
627
815
  region ||= myRegion
628
- @@elasticache_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElastiCache", region: region)
629
- @@elasticache_api[region]
816
+ @@elasticache_api[credentials] ||= {}
817
+ @@elasticache_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElastiCache", region: region, credentials: credentials)
818
+ @@elasticache_api[credentials][region]
630
819
  end
631
820
 
632
821
  # Amazon's SNS API
633
- def self.sns(region = MU.curRegion)
822
+ def self.sns(region: MU.curRegion, credentials: nil)
634
823
  region ||= myRegion
635
- @@sns_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "SNS", region: region)
636
- @@sns_api[region]
824
+ @@sns_api[credentials] ||= {}
825
+ @@sns_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "SNS", region: region, credentials: credentials)
826
+ @@sns_api[credentials][region]
637
827
  end
638
828
 
639
829
  # Amazon's SQS API
640
- def self.sqs(region = MU.curRegion)
830
+ def self.sqs(region: MU.curRegion, credentials: nil)
641
831
  region ||= myRegion
642
- @@sqs_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "SQS", region: region)
643
- @@sqs_api[region]
832
+ @@sqs_api[credentials] ||= {}
833
+ @@sqs_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "SQS", region: region, credentials: credentials)
834
+ @@sqs_api[credentials][region]
644
835
  end
645
836
 
646
837
  # Amazon's EFS API
647
- def self.efs(region = MU.curRegion)
838
+ def self.efs(region: MU.curRegion, credentials: nil)
648
839
  region ||= myRegion
649
- @@efs_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "EFS", region: region)
650
- @@efs_api[region]
840
+ @@efs_api[credentials] ||= {}
841
+ @@efs_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "EFS", region: region, credentials: credentials)
842
+ @@efs_api[credentials][region]
651
843
  end
652
844
 
653
845
  # Amazon's Lambda API
654
- def self.lambda(region = MU.curRegion)
846
+ def self.lambda(region: MU.curRegion, credentials: nil)
655
847
  region ||= myRegion
656
- @@lambda_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "Lambda", region: region)
657
- @@lambda_api[region]
848
+ @@lambda_api[credentials] ||= {}
849
+ @@lambda_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "Lambda", region: region, credentials: credentials)
850
+ @@lambda_api[credentials][region]
658
851
  end
659
852
 
660
853
  # Amazon's API Gateway API
661
- def self.apig(region = MU.curRegion)
854
+ def self.apig(region: MU.curRegion, credentials: nil)
662
855
  region ||= myRegion
663
- @@apig_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "APIGateway", region: region)
664
- @@apig_api[region]
856
+ @@apig_api[credentials] ||= {}
857
+ @@apig_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "APIGateway", region: region, credentials: credentials)
858
+ @@apig_api[credentials][region]
665
859
  end
666
860
 
667
861
  # Amazon's Cloudwatch Events API
668
862
  def self.cloudwatch_events(region = MU.cureRegion)
669
863
  region ||= myRegion
670
- @@cloudwatch_events_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudWatchEvents", region: region)
864
+ @@cloudwatch_events_api[credentials] ||= {}
865
+ @@cloudwatch_events_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CloudWatchEvents", region: region, credentials: credentials)
671
866
  @@cloudwatch_events_api
672
867
  end
673
868
 
674
869
  # Amazon's ECS API
675
- def self.ecs(region = MU.curRegion)
870
+ def self.ecs(region: MU.curRegion, credentials: nil)
676
871
  region ||= myRegion
677
- @@ecs_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "ECS", region: region)
678
- @@ecs_api[region]
872
+ @@ecs_api[credentials] ||= {}
873
+ @@ecs_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "ECS", region: region, credentials: credentials)
874
+ @@ecs_api[credentials][region]
679
875
  end
680
876
 
681
877
  # Amazon's EKS API
682
- def self.eks(region = MU.curRegion)
878
+ def self.eks(region: MU.curRegion, credentials: nil)
683
879
  region ||= myRegion
684
- @@eks_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "EKS", region: region)
685
- @@eks_api[region]
880
+ @@eks_api[credentials] ||= {}
881
+ @@eks_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "EKS", region: region, credentials: credentials)
882
+ @@eks_api[credentials][region]
686
883
  end
687
884
 
688
885
  # Amazon's Pricing API
689
- def self.pricing(region = MU.curRegion)
886
+ def self.pricing(region: MU.curRegion, credentials: nil)
690
887
  region ||= myRegion
691
- @@pricing_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "Pricing", region: region)
692
- @@pricing_api[region]
888
+ @@pricing_api[credentials] ||= {}
889
+ @@pricing_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "Pricing", region: region, credentials: credentials)
890
+ @@pricing_api[credentials][region]
693
891
  end
694
892
 
695
893
  # Amazon's Simple Systems Manager API
696
- def self.ssm(region = MU.curRegion)
894
+ def self.ssm(region: MU.curRegion, credentials: nil)
697
895
  region ||= myRegion
698
- @@ssm_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "SSM", region: region)
699
- @@ssm_api[region]
896
+ @@ssm_api[credentials] ||= {}
897
+ @@ssm_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "SSM", region: region, credentials: credentials)
898
+ @@ssm_api[credentials][region]
700
899
  end
701
900
 
702
901
  # Amazon's Elasticsearch API
703
- def self.elasticsearch(region = MU.curRegion)
902
+ def self.elasticsearch(region: MU.curRegion, credentials: nil)
704
903
  region ||= myRegion
705
- @@elasticsearch_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElasticsearchService", region: region)
706
- @@elasticsearch_api[region]
904
+ @@elasticsearch_api[credentials] ||= {}
905
+ @@elasticsearch_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "ElasticsearchService", region: region, credentials: credentials)
906
+ @@elasticsearch_api[credentials][region]
707
907
  end
708
908
 
709
909
  # Amazon's Cognito Identity API
710
- def self.cognito_ident(region = MU.curRegion)
910
+ def self.cognito_ident(region: MU.curRegion, credentials: nil)
711
911
  region ||= myRegion
712
- @@cognito_ident_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CognitoIdentity", region: region)
713
- @@cognito_ident_api[region]
912
+ @@cognito_ident_api[credentials] ||= {}
913
+ @@cognito_ident_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CognitoIdentity", region: region, credentials: credentials)
914
+ @@cognito_ident_api[credentials][region]
714
915
  end
715
916
 
716
917
  # Amazon's Cognito Identity Provider API
717
- def self.cognito_user(region = MU.curRegion)
918
+ def self.cognito_user(region: MU.curRegion, credentials: nil)
718
919
  region ||= myRegion
719
- @@cognito_user_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "CognitoIdentityProvider", region: region)
720
- @@cognito_user_api[region]
920
+ @@cognito_user_api[credentials] ||= {}
921
+ @@cognito_user_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "CognitoIdentityProvider", region: region, credentials: credentials)
922
+ @@cognito_user_api[credentials][region]
721
923
  end
722
924
 
723
925
  # Amazon's KMS API
724
- def self.kms(region = MU.curRegion)
926
+ def self.kms(region: MU.curRegion, credentials: nil)
725
927
  region ||= myRegion
726
- @@kms_api[region] ||= MU::Cloud::AWS::Endpoint.new(api: "KMS", region: region)
727
- @@kms_api[region]
928
+ @@kms_api[credentials] ||= {}
929
+ @@kms_api[credentials][region] ||= MU::Cloud::AWS::Endpoint.new(api: "KMS", region: region, credentials: credentials)
930
+ @@kms_api[credentials][region]
931
+ end
932
+
933
+ # Amazon's Organizations API
934
+ def self.orgs(credentials: nil)
935
+ @@organizations_api ||= {}
936
+ @@organizations_api[credentials] ||= MU::Cloud::AWS::Endpoint.new(api: "Organizations", credentials: credentials)
937
+ @@organizations_api[credentials]
728
938
  end
729
939
 
730
940
  # Fetch an Amazon instance metadata parameter (example: public-ipv4).
@@ -734,13 +944,13 @@ module MU
734
944
  base_url = "http://169.254.169.254/latest/meta-data/"
735
945
  begin
736
946
  response = nil
737
- Timeout.timeout(2) do
947
+ Timeout.timeout(1) do
738
948
  response = open("#{base_url}/#{param}").read
739
949
  end
740
950
 
741
951
  response
742
952
  rescue OpenURI::HTTPError, Timeout::Error, SocketError, Errno::ENETUNREACH, Net::HTTPServerException, Errno::EHOSTUNREACH => e
743
- # This is fairly normal, just handle it gracefully
953
+ # This is normal on machines checking to see if they're AWS-hosted
744
954
  logger = MU::Logger.new
745
955
  logger.log "Failed metadata request #{base_url}/#{param}: #{e.inspect}", MU::DEBUG
746
956
  return nil
@@ -844,7 +1054,7 @@ module MU
844
1054
  rule.from_port == port and rule.to_port == port
845
1055
  MU.log "Revoking old rules for port #{port.to_s} from #{sg_id}", MU::NOTICE
846
1056
  begin
847
- MU::Cloud::AWS.ec2(myRegion).revoke_security_group_ingress(
1057
+ MU::Cloud::AWS.ec2(region: myRegion).revoke_security_group_ingress(
848
1058
  group_id: sg_id,
849
1059
  ip_permissions: [
850
1060
  {
@@ -871,7 +1081,7 @@ module MU
871
1081
  }
872
1082
 
873
1083
  begin
874
- MU::Cloud::AWS.ec2(myRegion).authorize_security_group_ingress(
1084
+ MU::Cloud::AWS.ec2(region: myRegion).authorize_security_group_ingress(
875
1085
  group_id: sg_id,
876
1086
  ip_permissions: [
877
1087
  {
@@ -919,24 +1129,41 @@ module MU
919
1129
  class Endpoint
920
1130
  @api = nil
921
1131
  @region = nil
1132
+ @cred_obj = nil
1133
+ attr_reader :credentials
1134
+ attr_reader :account
922
1135
 
923
1136
  # Create an AWS API client
924
1137
  # @param region [String]: Amazon region so we know what endpoint to use
925
1138
  # @param api [String]: Which API are we wrapping?
926
- def initialize(region: MU.curRegion, api: "EC2")
927
- @region = region
1139
+ def initialize(region: MU.curRegion, api: "EC2", credentials: nil)
1140
+ @cred_obj = MU::Cloud::AWS.loadCredentials(credentials)
1141
+ @credentials = MU::Cloud::AWS.credConfig(credentials, name_only: true)
1142
+
1143
+ if !@cred_obj
1144
+ raise MuError, "Unable to locate valid AWS credentials for #{api} API. #{credentials ? "Credentials requested were '#{credentials}'": ""}"
1145
+ end
1146
+
1147
+ params = {}
1148
+
928
1149
  if region
929
- @api = Object.const_get("Aws::#{api}::Client").new(region: region)
930
- else
931
- @api = Object.const_get("Aws::#{api}::Client").new
1150
+ @region = region
1151
+ params[:region] = @region
932
1152
  end
1153
+
1154
+ params[:credentials] = @cred_obj
1155
+
1156
+ MU.log "Initializing #{api} object with credentials #{credentials}", MU::DEBUG, details: params
1157
+ @api = Object.const_get("Aws::#{api}::Client").new(params)
1158
+
1159
+ @api
933
1160
  end
934
1161
 
935
1162
  @instance_cache = {}
936
1163
  # Catch-all for AWS client methods. Essentially a pass-through with some
937
1164
  # rescues for known silly endpoint behavior.
938
1165
  def method_missing(method_sym, *arguments)
939
- MU::Cloud::AWS.loadCredentials
1166
+
940
1167
  retries = 0
941
1168
  begin
942
1169
  MU.log "Calling #{method_sym} in #{@region}", MU::DEBUG, details: arguments
@@ -949,7 +1176,7 @@ module MU
949
1176
  retval = @api.method(method_sym).call
950
1177
  end
951
1178
  return retval
952
- rescue Aws::EC2::Errors::InternalError, Aws::EC2::Errors::RequestLimitExceeded, Aws::EC2::Errors::Unavailable, Aws::Route53::Errors::Throttling, Aws::ElasticLoadBalancing::Errors::HttpFailureException, Aws::EC2::Errors::Http503Error, Aws::AutoScaling::Errors::Http503Error, Aws::AutoScaling::Errors::InternalFailure, Aws::AutoScaling::Errors::ServiceUnavailable, Aws::Route53::Errors::ServiceUnavailable, Aws::ElasticLoadBalancing::Errors::Throttling, Aws::RDS::Errors::ClientUnavailable, Aws::Waiters::Errors::UnexpectedError, Aws::ElasticLoadBalancing::Errors::ServiceUnavailable, Aws::ElasticLoadBalancingV2::Errors::Throttling, Seahorse::Client::NetworkingError, Aws::IAM::Errors::Throttling => e
1179
+ rescue Aws::EC2::Errors::InternalError, Aws::EC2::Errors::RequestLimitExceeded, Aws::EC2::Errors::Unavailable, Aws::Route53::Errors::Throttling, Aws::ElasticLoadBalancing::Errors::HttpFailureException, Aws::EC2::Errors::Http503Error, Aws::AutoScaling::Errors::Http503Error, Aws::AutoScaling::Errors::InternalFailure, Aws::AutoScaling::Errors::ServiceUnavailable, Aws::Route53::Errors::ServiceUnavailable, Aws::ElasticLoadBalancing::Errors::Throttling, Aws::RDS::Errors::ClientUnavailable, Aws::Waiters::Errors::UnexpectedError, Aws::ElasticLoadBalancing::Errors::ServiceUnavailable, Aws::ElasticLoadBalancingV2::Errors::Throttling, Seahorse::Client::NetworkingError, Aws::IAM::Errors::Throttling, Aws::EFS::Errors::ThrottlingException, Aws::Pricing::Errors::ThrottlingException => e
953
1180
  if e.class.name == "Seahorse::Client::NetworkingError" and e.message.match(/Name or service not known/)
954
1181
  MU.log e.inspect, MU::ERR
955
1182
  raise e
@@ -967,9 +1194,12 @@ module MU
967
1194
  # elsif retries > 100
968
1195
  # raise MuError, "Exhausted retries after #{retries} attempts while calling EC2's #{method_sym} in #{@region}. Args were: #{arguments}"
969
1196
  end
970
- MU.log "Got #{e.inspect} calling EC2's #{method_sym} in #{@region}, waiting #{interval.to_s}s and retrying. Args were: #{arguments}", debuglevel, details: caller
1197
+ MU.log "Got #{e.inspect} calling EC2's #{method_sym} in #{@region} with credentials #{@credentials}, waiting #{interval.to_s}s and retrying. Args were: #{arguments}", debuglevel, details: caller
971
1198
  sleep interval
972
1199
  retry
1200
+ rescue Exception => e
1201
+ MU.log "Got #{e.inspect} calling EC2's #{method_sym} in #{@region} with credentials #{@credentials}", MU::DEBUG, details: arguments
1202
+ raise e
973
1203
  end
974
1204
  end
975
1205
  end
@@ -1004,6 +1234,7 @@ module MU
1004
1234
  @@cognito_ident_api ={}
1005
1235
  @@cognito_user_api ={}
1006
1236
  @@kms_api ={}
1237
+ @@organizataion_api ={}
1007
1238
  end
1008
1239
  end
1009
1240
  end