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

Sign up to get free protection for your applications and to get access to all the features.
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