cloud-mu 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/ansible/roles/mu-nat/tasks/main.yml +3 -0
  3. data/bin/mu-aws-setup +41 -7
  4. data/bin/mu-azure-setup +34 -0
  5. data/bin/mu-configure +214 -119
  6. data/bin/mu-gcp-setup +37 -2
  7. data/bin/mu-node-manage +3 -0
  8. data/bin/mu-refresh-ssl +67 -0
  9. data/bin/mu-run-tests +14 -4
  10. data/bin/mu-self-update +30 -10
  11. data/bin/mu-upload-chef-artifacts +30 -26
  12. data/cloud-mu.gemspec +8 -6
  13. data/cookbooks/mu-master/attributes/default.rb +5 -1
  14. data/cookbooks/mu-master/metadata.rb +2 -2
  15. data/cookbooks/mu-master/recipes/default.rb +81 -26
  16. data/cookbooks/mu-master/recipes/init.rb +197 -62
  17. data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
  18. data/cookbooks/mu-master/recipes/vault.rb +78 -77
  19. data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
  20. data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
  21. data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
  22. data/cookbooks/mu-tools/attributes/default.rb +5 -0
  23. data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
  24. data/cookbooks/mu-tools/libraries/helper.rb +12 -2
  25. data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
  26. data/cookbooks/mu-tools/recipes/apply_security.rb +6 -0
  27. data/cookbooks/mu-tools/recipes/aws_api.rb +6 -4
  28. data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
  29. data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
  30. data/cookbooks/mu-tools/recipes/google_api.rb +5 -2
  31. data/cookbooks/mu-tools/resources/disk.rb +108 -58
  32. data/extras/Gemfile.lock.bootstrap +394 -0
  33. data/extras/bucketstubs/error.html +0 -0
  34. data/extras/bucketstubs/index.html +0 -0
  35. data/extras/clean-stock-amis +9 -9
  36. data/extras/git_rpm/build.sh +20 -0
  37. data/extras/git_rpm/mugit.spec +53 -0
  38. data/extras/image-generators/VMWare/centos8.yaml +15 -0
  39. data/extras/openssl_rpm/build.sh +19 -0
  40. data/extras/openssl_rpm/mussl.spec +46 -0
  41. data/extras/python_rpm/muthon.spec +14 -4
  42. data/extras/ruby_rpm/muby.spec +9 -5
  43. data/extras/sqlite_rpm/build.sh +19 -0
  44. data/extras/sqlite_rpm/muqlite.spec +47 -0
  45. data/install/installer +7 -5
  46. data/modules/mu.rb +12 -5
  47. data/modules/mu/cloud/machine_images.rb +1 -1
  48. data/modules/mu/cloud/providers.rb +6 -1
  49. data/modules/mu/cloud/resource_base.rb +1 -1
  50. data/modules/mu/cloud/ssh_sessions.rb +4 -0
  51. data/modules/mu/config.rb +28 -12
  52. data/modules/mu/config/database.rb +2 -2
  53. data/modules/mu/config/firewall_rule.rb +1 -1
  54. data/modules/mu/config/ref.rb +2 -2
  55. data/modules/mu/config/schema_helpers.rb +12 -3
  56. data/modules/mu/config/server.rb +10 -4
  57. data/modules/mu/config/server_pool.rb +2 -2
  58. data/modules/mu/config/vpc.rb +10 -10
  59. data/modules/mu/defaults/AWS.yaml +32 -32
  60. data/modules/mu/deploy.rb +23 -10
  61. data/modules/mu/groomers/chef.rb +2 -2
  62. data/modules/mu/master.rb +49 -3
  63. data/modules/mu/mommacat.rb +8 -5
  64. data/modules/mu/mommacat/naming.rb +2 -2
  65. data/modules/mu/mommacat/storage.rb +22 -27
  66. data/modules/mu/providers/aws.rb +142 -48
  67. data/modules/mu/providers/aws/alarm.rb +3 -3
  68. data/modules/mu/providers/aws/bucket.rb +19 -19
  69. data/modules/mu/providers/aws/cache_cluster.rb +22 -22
  70. data/modules/mu/providers/aws/cdn.rb +2 -2
  71. data/modules/mu/providers/aws/collection.rb +14 -14
  72. data/modules/mu/providers/aws/container_cluster.rb +27 -27
  73. data/modules/mu/providers/aws/database.rb +40 -39
  74. data/modules/mu/providers/aws/dnszone.rb +5 -5
  75. data/modules/mu/providers/aws/endpoint.rb +35 -35
  76. data/modules/mu/providers/aws/firewall_rule.rb +26 -23
  77. data/modules/mu/providers/aws/function.rb +28 -28
  78. data/modules/mu/providers/aws/group.rb +7 -7
  79. data/modules/mu/providers/aws/habitat.rb +2 -2
  80. data/modules/mu/providers/aws/job.rb +6 -6
  81. data/modules/mu/providers/aws/loadbalancer.rb +34 -34
  82. data/modules/mu/providers/aws/log.rb +14 -14
  83. data/modules/mu/providers/aws/msg_queue.rb +10 -10
  84. data/modules/mu/providers/aws/nosqldb.rb +8 -8
  85. data/modules/mu/providers/aws/notifier.rb +7 -7
  86. data/modules/mu/providers/aws/role.rb +17 -15
  87. data/modules/mu/providers/aws/search_domain.rb +10 -10
  88. data/modules/mu/providers/aws/server.rb +176 -95
  89. data/modules/mu/providers/aws/server_pool.rb +65 -105
  90. data/modules/mu/providers/aws/storage_pool.rb +17 -9
  91. data/modules/mu/providers/aws/user.rb +1 -1
  92. data/modules/mu/providers/aws/vpc.rb +103 -51
  93. data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
  94. data/modules/mu/providers/azure.rb +78 -12
  95. data/modules/mu/providers/azure/server.rb +18 -3
  96. data/modules/mu/providers/cloudformation/server.rb +1 -1
  97. data/modules/mu/providers/google.rb +19 -4
  98. data/modules/mu/providers/google/folder.rb +6 -2
  99. data/modules/mu/providers/google/function.rb +65 -30
  100. data/modules/mu/providers/google/role.rb +1 -1
  101. data/modules/mu/providers/google/vpc.rb +27 -2
  102. data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
  103. data/modules/tests/k8s.yaml +1 -1
  104. metadata +24 -8
@@ -70,8 +70,8 @@ module MU
70
70
  # XXX kludge to get at knife-windows when it's installed from
71
71
  # a git repo and bundler sticks it somewhere in a corner
72
72
  $LOAD_PATH.each { |path|
73
- if path.match(/\/gems\/aws\-sdk\-core\-\d+\.\d+\.\d+\/lib$/)
74
- addpath = path.sub(/\/gems\/aws\-sdk\-core\-\d+\.\d+\.\d+\/lib$/, "")+"/bundler/gems"
73
+ if path.match(/\/gems\/chef\-\d+\.\d+\.\d+\/lib$/)
74
+ addpath = path.sub(/\/gems\/chef\-\d+\.\d+\.\d+\/lib$/, "")+"/bundler/gems"
75
75
  Dir.glob(addpath+"/knife-windows-*").each { |version|
76
76
  $LOAD_PATH << version+"/lib"
77
77
  }
data/modules/mu/master.rb CHANGED
@@ -195,9 +195,12 @@ module MU
195
195
  temp_dev = "/dev/#{ramdisk}"
196
196
 
197
197
  if !File.open("/etc/mtab").read.match(/ #{path} /)
198
- realdevice = device.dup
199
- if MU::Cloud::Google.hosted?
200
- realdevice = "/dev/disk/by-id/google-"+device.gsub(/.*?\/([^\/]+)$/, '\1')
198
+ realdevice = if MU::Cloud::Google.hosted?
199
+ "/dev/disk/by-id/google-"+device.gsub(/.*?\/([^\/]+)$/, '\1')
200
+ elsif MU::Cloud::AWS.hosted?
201
+ MU::Cloud::AWS.realDevicePath(device.dup)
202
+ else
203
+ device.dup
201
204
  end
202
205
  alias_device = cryptfile ? "/dev/mapper/"+path.gsub(/[^0-9a-z_\-]/i, "_") : realdevice
203
206
 
@@ -216,6 +219,9 @@ module MU
216
219
  tag_name: "Name",
217
220
  tag_value: "#{$MU_CFG['hostname']} #{path}"
218
221
  )
222
+ # the device might be on some arbitrary NVMe slot
223
+ realdevice = MU::Cloud::AWS.realDevicePath(realdevice)
224
+ alias_device = cryptfile ? "/dev/mapper/"+path.gsub(/[^0-9a-z_\-]/i, "_") : realdevice
219
225
  elsif MU::Cloud::Google.hosted?
220
226
  dummy_svr = MU::Cloud::Google::Server.new(
221
227
  mu_name: "MU-MASTER",
@@ -901,5 +907,45 @@ module MU
901
907
  }
902
908
  end
903
909
 
910
+ # Just list our block devices
911
+ # @return [Array<String>]
912
+ def self.listBlockDevices
913
+ if File.executable?("/bin/lsblk")
914
+ %x{/bin/lsblk -i -p -r -n | egrep ' disk( |$)'}.each_line.map { |l|
915
+ l.chomp.sub(/ .*/, '')
916
+ }
917
+ else
918
+ # XXX something dumber
919
+ nil
920
+ end
921
+ end
922
+
923
+
924
+ # Retrieve the UUID of a block device, if available
925
+ # @param dev [String]
926
+ def self.diskUUID(dev)
927
+ realdev = if MU::Cloud::Google.hosted?
928
+ "/dev/disk/by-id/google-"+dev.gsub(/.*?\/([^\/]+)$/, '\1')
929
+ elsif MU::Cloud::AWS.hosted?
930
+ MU::Cloud::AWS.realDevicePath(dev)
931
+ else
932
+ dev
933
+ end
934
+ %x{/sbin/blkid #{realdev} -o export | grep ^UUID=}.chomp
935
+ end
936
+
937
+ # Determine whether we're running in an NVMe-enabled environment
938
+ def self.nvme?
939
+ if File.executable?("/bin/lsblk")
940
+ %x{/bin/lsblk -i -p -r -n}.each_line { |l|
941
+ return true if l =~ /^\/dev\/nvme\d/
942
+ }
943
+ else
944
+ return true if File.exists?("/dev/nvme0n1")
945
+ end
946
+ false
947
+ end
948
+
949
+
904
950
  end
905
951
  end
@@ -173,6 +173,7 @@ module MU
173
173
  @public_key = nil
174
174
  @secrets = Hash.new
175
175
  @secrets['instance_secret'] = Hash.new
176
+ @secrets['windows_admin_password'] = Hash.new
176
177
  @ssh_key_name = ssh_key_name
177
178
  @ssh_private_key = ssh_private_key
178
179
  @ssh_public_key = ssh_public_key
@@ -512,6 +513,8 @@ module MU
512
513
  @ssh_private_key = File.read("#{ssh_dir}/#{@ssh_key_name}")
513
514
  @ssh_private_key.chomp!
514
515
 
516
+ # XXX the following mess belongs in cloud layers, probably in their initDeploy
517
+ # methods
515
518
  if numKittens(clouds: ["AWS"], types: ["Server", "ServerPool", "ContainerCluster"]) > 0
516
519
  creds_used = []
517
520
  ["servers", "server_pools", "container_clusters"].each { |type|
@@ -551,7 +554,7 @@ module MU
551
554
 
552
555
  begin
553
556
  if !no_write
554
- if !MU::MommaCat.lock("deployment-notification", deploy_id: @deploy_id, retries: 10)
557
+ if !MU::MommaCat.lock("deployment-notification", deploy_id: @deploy_id, retries: 300)
555
558
  raise MuError, "Failed to get deployment-notifcation lock for #{@deploy_id}"
556
559
  end
557
560
  end
@@ -884,13 +887,13 @@ MAIL_HEAD_END
884
887
  if resource and resource.config and resource.config['cloud']
885
888
  cloudclass = MU::Cloud.cloudClass(resource.config['cloud'])
886
889
 
887
- cloudclass.writeDeploySecret(@deploy_id, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
888
- cloudclass.writeDeploySecret(@deploy_id, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])
890
+ cloudclass.writeDeploySecret(self, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
891
+ cloudclass.writeDeploySecret(self, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])
889
892
  if pfx_cert
890
- cloudclass.writeDeploySecret(@deploy_id, pfx_cert.to_der, cert_cn+".pfx", credentials: resource.config['credentials'])
893
+ cloudclass.writeDeploySecret(self, pfx_cert.to_der, cert_cn+".pfx", credentials: resource.config['credentials'])
891
894
  end
892
895
  if winrm_cert
893
- cloudclass.writeDeploySecret(@deploy_id, winrm_cert.to_pem, cert_cn+"-winrm.crt", credentials: resource.config['credentials'])
896
+ cloudclass.writeDeploySecret(self, winrm_cert.to_pem, cert_cn+"-winrm.crt", credentials: resource.config['credentials'])
894
897
  end
895
898
  end
896
899
 
@@ -164,7 +164,7 @@ module MU
164
164
  # @param scrub_mu_isms [Boolean]: Don't bother with generating names specific to this deployment. Used to generate generic CloudFormation templates, amongst other purposes.
165
165
  # @param disallowed_chars [Regexp]: A pattern of characters that are illegal for this resource name, such as +/[^a-zA-Z0-9-]/+
166
166
  # @return [String]: A full name string for this resource
167
- def getResourceName(name, max_length: 255, need_unique_string: false, use_unique_string: nil, reuse_unique_string: false, scrub_mu_isms: @original_config['scrub_mu_isms'], disallowed_chars: nil)
167
+ def getResourceName(name, max_length: 255, need_unique_string: false, use_unique_string: nil, reuse_unique_string: false, scrub_mu_isms: @original_config['scrub_mu_isms'], disallowed_chars: nil, never_gen_unique: false)
168
168
  if name.nil?
169
169
  raise MuError, "Got no argument to MU::MommaCat.getResourceName"
170
170
  end
@@ -219,7 +219,7 @@ module MU
219
219
  else
220
220
  # If we have to strip anything, assume we've lost uniqueness and
221
221
  # will have to compensate with #genUniquenessString.
222
- need_unique_string = true
222
+ need_unique_string = true if !never_gen_unique
223
223
  reserved = 4
224
224
  basename.sub!(/-[^-]+-#{@seed.upcase}-#{Regexp.escape(name.upcase)}$/, "")
225
225
  basename = basename + "-" + @seed.upcase + "-" + name.upcase
@@ -208,25 +208,26 @@ module MU
208
208
  if lockid == id
209
209
  thread = Thread.list.select { |t| t.object_id == thread_id }.first
210
210
  if thread.object_id != Thread.current.object_id
211
- MU.log "#{thread_id} sitting on #{id}", MU::WARN, thread.backtrace
211
+ MU.log "#{thread_id} sitting on #{id} (#{thread.thread_variables.map { |v| "#{v.to_s}: #{thread.thread_variable_get(v).to_s}" }.join(", ")})", MU::WARN, thread.backtrace
212
212
  end
213
213
  end
214
214
  }
215
215
  }
216
216
  }
217
217
  }
218
+
218
219
  begin
219
220
  if nonblock
220
221
  if !@locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
221
222
  if retries > 0
222
223
  success = false
223
- MU.retrier([], loop_if: Proc.new { !success }, loop_msg: "Waiting for lock on #{lockdir}/#{id}.lock...", max: retries) { |cur_retries, _wait|
224
+ MU.retrier([], loop_if: Proc.new { !success }, loop_msg: "Waiting for lock on #{lockdir}/#{id}.lock...", max: retries, wait: 1, logmsg_interval: 0) { |cur_retries, _wait|
224
225
  success = @locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
225
- if !success and cur_retries > 0 and (cur_retries % 3) == 0
226
- show_relevant.call(cur_retries)
226
+ if !success and cur_retries > 0 and (cur_retries % 45) == 0
227
+ show_relevant.call
227
228
  end
228
229
  }
229
- show_relevant.call(cur_retries) if !success
230
+ show_relevant.call if !success
230
231
  return success
231
232
  else
232
233
  return false
@@ -542,6 +543,22 @@ module MU
542
543
  return Dir.exist?(deploy_path)
543
544
  end
544
545
 
546
+ # Write our shared deploy secret out to wherever the cloud provider layers
547
+ # like to stash it.
548
+ def writeDeploySecret
549
+ return if !@deploy_secret
550
+ credsets = credsUsed
551
+ return if !credsets
552
+ if !@original_config['scrub_mu_isms'] and !@no_artifacts
553
+ cloudsUsed.each { |cloud|
554
+ credsets.each { |credentials|
555
+ next if MU::Cloud.cloudClass(cloud).credConfig(credentials).nil? # XXX this is a dumb way to check this, should be able to get credsUsed by cloud
556
+ MU::Cloud.cloudClass(cloud).writeDeploySecret(self, @deploy_secret, credentials: credentials)
557
+ }
558
+ }
559
+ end
560
+ end
561
+
545
562
  private
546
563
 
547
564
  def writeFile(filename, contents)
@@ -552,30 +569,8 @@ module MU
552
569
 
553
570
  # Helper for +initialize+
554
571
  def setDeploySecret
555
- credsets = {}
556
- MU::Cloud.resource_types.values.each { |attrs|
557
- if !@original_config[attrs[:cfg_plural]].nil? and @original_config[attrs[:cfg_plural]].size > 0
558
- @original_config[attrs[:cfg_plural]].each { |resource|
559
-
560
- credsets[resource['cloud']] ||= []
561
- credsets[resource['cloud']] << resource['credentials']
562
- @clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
563
- @clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
564
-
565
- }
566
- end
567
- }
568
-
569
572
  MU.log "Creating deploy secret for #{MU.deploy_id}"
570
573
  @deploy_secret = Password.random(256)
571
- if !@original_config['scrub_mu_isms'] and !@no_artifacts
572
- credsets.each_pair { |cloud, creds|
573
- creds.uniq!
574
- creds.each { |credentials|
575
- MU::Cloud.cloudClass(cloud).writeDeploySecret(@deploy_id, @deploy_secret, credentials: credentials)
576
- }
577
- }
578
- end
579
574
  end
580
575
 
581
576
  def loadObjects(delay_descriptor_load)
@@ -16,7 +16,6 @@ require "net/http"
16
16
  require 'open-uri'
17
17
  require 'timeout'
18
18
  require 'inifile'
19
- gem 'aws-sdk-core'
20
19
  autoload :Aws, "aws-sdk-core"
21
20
 
22
21
 
@@ -54,14 +53,19 @@ module MU
54
53
  def self.resourceInitHook(cloudobj, _deploy)
55
54
  class << self
56
55
  attr_reader :cloudformation_data
56
+ attr_reader :region
57
57
  end
58
+ return if !cloudobj
58
59
  cloudobj.instance_variable_set(:@cloudformation_data, {})
60
+
61
+ cloudobj.instance_variable_set(:@region, cloudobj.config['region'])
59
62
  end
60
63
 
61
64
  # Load some credentials for using the AWS API
62
65
  # @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.
63
66
  # @return [Aws::Credentials]
64
67
  def self.loadCredentials(name = nil)
68
+ gem 'aws-sdk-core'
65
69
  @@creds_loaded ||= {}
66
70
 
67
71
  if name.nil?
@@ -124,10 +128,14 @@ module MU
124
128
  # pull access key and secret from a vault
125
129
  begin
126
130
  vault, item = cred_cfg["credentials"].split(/:/)
127
- data = MU::Groomer::Chef.getSecret(vault: vault, item: item).to_h
128
- if data["access_key"] and data["access_secret"]
131
+ data = if !vault or !item
132
+ raise MuError.new "AWS #{name} credentials field value '#{cred_cfg["credentials"]}' malformed, should be vaultname:itemname", details: cred_cfg
133
+ else
134
+ MU::Groomer::Chef.getSecret(vault: vault, item: item).to_h
135
+ end
136
+ if data and data["access_key"] and data["access_secret"]
129
137
  cred_obj = Aws::Credentials.new(
130
- cred_cfg['access_key'], cred_cfg['access_secret']
138
+ data['access_key'], data['access_secret']
131
139
  )
132
140
  if name.nil?
133
141
  # Aws.config = {
@@ -137,10 +145,10 @@ module MU
137
145
  # }
138
146
  end
139
147
  else
140
- MU.log "AWS credentials vault:item #{cred_cfg["credentials"]} specified, but is missing access_key or access_secret elements", MU::WARN
148
+ raise MuError.new "AWS #{name} credentials vault:item #{cred_cfg["credentials"]} specified, but is missing access_key or access_secret elements", details: cred_cfg
141
149
  end
142
150
  rescue MU::Groomer::MuNoSuchSecret
143
- MU.log "AWS credentials vault:item #{cred_cfg["credentials"]} specified, but does not exist", MU::WARN
151
+ raise MuError.new "AWS #{name} credentials vault:item #{cred_cfg["credentials"]} specified, but does not exist", details: cred_cfg
144
152
  end
145
153
  end
146
154
 
@@ -186,6 +194,7 @@ end
186
194
  # @param r [String]
187
195
  # @return [String]
188
196
  def self.validate_region(r, credentials: nil)
197
+ require "aws-sdk-ec2"
189
198
  begin
190
199
  MU::Cloud::AWS.ec2(region: r, credentials: credentials).describe_availability_zones.availability_zones.first.region_name
191
200
  rescue ::Aws::EC2::Errors::UnauthorizedOperation => e
@@ -204,6 +213,7 @@ end
204
213
  # @param othertags [Array<Hash>]: Miscellaneous custom tags, in Basket of Kittens style
205
214
  # @return [void]
206
215
  def self.createStandardTags(resource = nil, region: MU.curRegion, credentials: nil, optional: true, nametag: nil, othertags: nil)
216
+ require "aws-sdk-ec2"
207
217
  tags = []
208
218
  MU::MommaCat.listStandardTags.each_pair { |name, value|
209
219
  tags << {key: name, value: value} if !value.nil?
@@ -261,20 +271,33 @@ end
261
271
  @@myVPCObj
262
272
  end
263
273
 
264
- # If we've configured AWS as a provider, or are simply hosted in AWS,
274
+ # If we've configured AWS as a provider, or are simply hosted in AWS,
265
275
  # decide what our default region is.
266
- def self.myRegion(credentials = nil)
267
- return @@myRegion_var if @@myRegion_var
276
+ def self.myRegion(credentials = nil, debug: false)
277
+ loglevel = debug ? MU::NOTICE : MU::DEBUG
278
+ if @@myRegion_var
279
+ MU.log "AWS.myRegion: returning #{@@myRegion_var} from cache", loglevel
280
+ return @@myRegion_var
281
+ end
268
282
 
283
+ MU.log "AWS.myRegion: credConfig", loglevel, details: credConfig
284
+ MU.log "AWS.myRegion: hosted?", loglevel, details: hosted?.to_s
285
+ MU.log "AWS.myRegion: ENV['EC2_REGION']", loglevel, details: ENV['EC2_REGION']
286
+ MU.log "AWS.myRegion: $MU_CFG['aws']", loglevel, details: $MU_CFG['aws']
269
287
  if credConfig.nil? and !hosted? and !ENV['EC2_REGION']
288
+ MU.log "AWS.myRegion: nothing of use set, returning", loglevel
270
289
  return nil
271
290
  end
272
291
 
273
292
  if $MU_CFG and $MU_CFG['aws']
274
293
  $MU_CFG['aws'].each_pair { |credset, cfg|
294
+ MU.log "AWS.myRegion: #{credset} != #{credentials} ?", loglevel, details: cfg
275
295
  next if credentials and credset != credentials
296
+ MU.log "AWS.myRegion: validating credset #{credset}", loglevel, details: cfg
276
297
  next if !cfg['region']
277
- if (cfg['default'] or !@@myRegion_var) and validate_region(cfg['region'], credentials: credset)
298
+ MU.log "AWS.myRegion: validation response", loglevel, details: validate_region(cfg['region'], credentials: credset)
299
+ if (cfg['default'] or !@@myRegion_var or $MU_CFG['aws'].size == 1) and validate_region(cfg['region'], credentials: credset)
300
+ MU.log "AWS.myRegion: liking this set", loglevel, details: cfg
278
301
  @@myRegion_var = cfg['region']
279
302
  break if cfg['default'] or credentials
280
303
  end
@@ -286,15 +309,21 @@ end
286
309
  (Aws.config['access_key'] and Aws.config['access_secret'])
287
310
  )
288
311
  # Make sure this string is valid by way of the API
312
+ MU.log "AWS.myRegion: using ENV", loglevel, details: ENV
289
313
  @@myRegion_var = ENV['EC2_REGION']
290
314
  end
291
315
 
292
316
  if hosted? and !@@myRegion_var
293
317
  # hacky, but useful in a pinch (and if we're hosted in AWS)
294
318
  az_str = MU::Cloud::AWS.getAWSMetaData("placement/availability-zone")
319
+ MU.log "AWS.myRegion: using hosted", loglevel, details: az_str
295
320
  @@myRegion_var = az_str.sub(/[a-z]$/i, "") if az_str
296
321
  end
297
322
 
323
+ if credConfig and credConfig["region"]
324
+ @@myRegion_var ||= credConfig["region"]
325
+ end
326
+
298
327
  @@myRegion_var
299
328
  end
300
329
 
@@ -382,8 +411,9 @@ end
382
411
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
383
412
  # @param deploy_id [String]: The deploy for which we're writing the secret
384
413
  # @param value [String]: The contents of the secret
385
- def self.writeDeploySecret(deploy_id, value, name = nil, credentials: nil)
386
- name ||= deploy_id+"-secret"
414
+ def self.writeDeploySecret(deploy, value, name = nil, credentials: nil)
415
+ require "aws-sdk-s3"
416
+ name ||= deploy.deploy_id+"-secret"
387
417
  begin
388
418
  MU.log "Writing #{name} to S3 bucket #{adminBucketName(credentials)}"
389
419
  MU::Cloud::AWS.s3(region: myRegion, credentials: credentials).put_object(
@@ -401,35 +431,35 @@ end
401
431
  def self.cloudtrailBucketPolicy(credentials = nil)
402
432
  cfg = credConfig(credentials)
403
433
  policy_json = '{
404
- "Version": "2012-10-17",
405
- "Statement": [
406
- {
407
- "Sid": "AWSCloudTrailAclCheck20131101",
408
- "Effect": "Allow",
434
+ "Version": "2012-10-17",
435
+ "Statement": [
436
+ {
437
+ "Sid": "AWSCloudTrailAclCheck20131101",
438
+ "Effect": "Allow",
409
439
  "Principal": {
410
440
  "AWS": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':iam::<%= MU.account_number %>:root",
411
441
  "Service": "cloudtrail.amazonaws.com"
412
442
  },
413
- "Action": "s3:GetBucketAcl",
414
- "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'"
415
- },
416
- {
417
- "Sid": "AWSCloudTrailWrite20131101",
418
- "Effect": "Allow",
443
+ "Action": "s3:GetBucketAcl",
444
+ "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'"
445
+ },
446
+ {
447
+ "Sid": "AWSCloudTrailWrite20131101",
448
+ "Effect": "Allow",
419
449
  "Principal": {
420
450
  "AWS": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':iam::'+credToAcct(credentials)+':root",
421
451
  "Service": "cloudtrail.amazonaws.com"
422
452
  },
423
- "Action": "s3:PutObject",
424
- "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'/AWSLogs/'+credToAcct(credentials)+'/*",
425
- "Condition": {
426
- "StringEquals": {
427
- "s3:x-amz-acl": "bucket-owner-full-control"
428
- }
429
- }
430
- }
431
- ]
432
- }'
453
+ "Action": "s3:PutObject",
454
+ "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(cfg['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'/AWSLogs/'+credToAcct(credentials)+'/*",
455
+ "Condition": {
456
+ "StringEquals": {
457
+ "s3:x-amz-acl": "bucket-owner-full-control"
458
+ }
459
+ }
460
+ }
461
+ ]
462
+ }'
433
463
  ERB.new(policy_json).result
434
464
  end
435
465
 
@@ -440,6 +470,53 @@ end
440
470
  MU::Cloud::AWS.hosted?
441
471
  end
442
472
 
473
+ # If we're in AWS and NVME-aware, return a mapping of AWS-side device
474
+ # names to actual NVME devices.
475
+ # @return [Hash]
476
+ def self.attachedNVMeDisks
477
+ if !hosted? or !File.executable?("/bin/lsblk") or !File.executable?("/sbin/nvme")
478
+ return {}
479
+ end
480
+ map = {}
481
+ devices = MU::Master.listBlockDevices
482
+ return {} if !devices
483
+ devices.each { |d|
484
+ if d =~ /^\/dev\/nvme/
485
+ %x{/sbin/nvme id-ctrl -v #{d}}.each_line { |desc|
486
+ if desc.match(/^0000: (?:[0-9a-f]{2} ){16}"(.+?)\./)
487
+ virt_dev = Regexp.last_match[1]
488
+ map[virt_dev] = d
489
+ break
490
+ end
491
+ }
492
+ end
493
+ }
494
+ map
495
+ end
496
+
497
+ # Map our own idea of what a block device is called back to whatever AWS
498
+ # and the operating system decided on amongst themselves. This currently
499
+ # exists to map generic "xvd[a-z]" style names back to real NVMe devices.
500
+ # @param dev [String]
501
+ def self.realDevicePath(dev)
502
+ return dev if !hosted?
503
+ value = nil
504
+ should_retry = Proc.new {
505
+ !value and MU::Master.nvme?
506
+ }
507
+ MU.retrier(loop_if: should_retry, wait: 5, max: 6) {
508
+ map = attachedNVMeDisks
509
+ value = if map[dev]
510
+ map[dev]
511
+ elsif map[dev.gsub(/.*?\//, '')]
512
+ map[dev.gsub(/.*?\//, '')]
513
+ else
514
+ dev # be nice to actually handle this too
515
+ end
516
+ }
517
+ value
518
+ end
519
+
443
520
  # Determine whether we (the Mu master, presumably) are hosted in this
444
521
  # cloud.
445
522
  # @return [Boolean]
@@ -457,7 +534,7 @@ end
457
534
 
458
535
  begin
459
536
  Timeout.timeout(4) do
460
- instance_id = open("http://169.254.169.254/latest/meta-data/instance-id").read
537
+ instance_id = URI.open("http://169.254.169.254/latest/meta-data/instance-id").read
461
538
  if !instance_id.nil? and instance_id.size > 0
462
539
  @@is_in_aws = true
463
540
  region = getAWSMetaData("placement/availability-zone").sub(/[a-z]$/i, "")
@@ -566,17 +643,18 @@ end
566
643
  end
567
644
 
568
645
  $MU_CFG['aws'].keys
569
- end
646
+ end
570
647
 
571
648
  # Resolve the administrative S3 bucket for a given credential set, or
572
649
  # return a default.
573
650
  # @param credentials [String]
574
651
  # @return [String]
575
652
  def self.adminBucketName(credentials = nil)
653
+ require "aws-sdk-s3"
576
654
  cfg = credConfig(credentials)
577
655
  return nil if !cfg
578
656
  if !cfg['log_bucket_name']
579
- cfg['log_bucket_name'] = $MU_CFG['hostname']
657
+ cfg['log_bucket_name'] = $MU_CFG['hostname']
580
658
  MU.log "No AWS log bucket defined for credentials #{credentials}, attempting to use default of #{cfg['log_bucket_name']}", MU::WARN
581
659
  end
582
660
  resp = MU::Cloud::AWS.s3(credentials: credentials).list_buckets
@@ -610,7 +688,7 @@ end
610
688
 
611
689
  # Return the $MU_CFG data associated with a particular profile/name/set of
612
690
  # credentials. If no account name is specified, will return one flagged as
613
- # default. Returns nil if AWS is not configured. Throws an exception if
691
+ # default. Returns nil if AWS is not configured. Throws an exception if
614
692
  # an account name is specified which does not exist.
615
693
  # @param name [String]: The name of the key under 'aws' in mu.yaml to return
616
694
  # @return [Hash,nil]
@@ -625,10 +703,13 @@ end
625
703
 
626
704
  if hosted?
627
705
  begin
628
- iam_data = JSON.parse(getAWSMetaData("iam/info"))
629
- if iam_data["InstanceProfileArn"] and !iam_data["InstanceProfileArn"].empty?
630
- @@my_hosted_cfg = hosted_config
631
- return name_only ? "#default" : @@my_hosted_cfg
706
+ iam_blob = getAWSMetaData("iam/info")
707
+ if iam_blob
708
+ iam_data = JSON.parse(iam_blob)
709
+ if iam_data["InstanceProfileArn"] and !iam_data["InstanceProfileArn"].empty?
710
+ @@my_hosted_cfg = hosted_config
711
+ return name_only ? "#default" : @@my_hosted_cfg
712
+ end
632
713
  end
633
714
  rescue JSON::ParserError => e
634
715
  end
@@ -692,6 +773,7 @@ end
692
773
  # XXX this needs to be "myAccountNumber" or somesuch
693
774
  # XXX and maybe do the IAM thing for arbitrary, non-resident accounts
694
775
  def self.account_number
776
+ require "aws-sdk-ec2"
695
777
  return nil if credConfig.nil?
696
778
  return @@my_acct_num if @@my_acct_num
697
779
  loadCredentials
@@ -749,7 +831,7 @@ end
749
831
  @@regions.keys.uniq
750
832
  end
751
833
 
752
- # XXX GovCloud doesn't show up if you query a commercial endpoint... that's
834
+ # XXX GovCloud doesn't show up if you query a commercial endpoint... that's
753
835
  # *probably* ok for most purposes? We can't call listAZs on it from out here
754
836
  # apparently, so getting around it is nontrivial
755
837
  # if !@@regions.has_key?("us-gov-west-1")
@@ -774,6 +856,7 @@ end
774
856
  # @param public_key [String]: The public key
775
857
  # @return [Array<String>]: keypairname, ssh_private_key, ssh_public_key
776
858
  def self.createEc2SSHKey(keyname, public_key, credentials: nil)
859
+ require "aws-sdk-ec2"
777
860
  # We replicate this key in all regions
778
861
  if !MU::Cloud::CloudFormation.emitCloudFormation
779
862
  MU::Cloud::AWS.listRegions.each { |region|
@@ -802,8 +885,16 @@ end
802
885
  def self.listInstanceTypes(region = myRegion)
803
886
  return @@instance_types if @@instance_types and @@instance_types[region]
804
887
  return {} if credConfig.nil?
888
+ if region.nil?
889
+ region = myRegion(debug: true)
890
+ end
891
+ return {} if region.nil?
805
892
 
806
893
  human_region = @@regionLookup[region]
894
+ if human_region.nil?
895
+ MU.log "Failed to map a Pricing API region name from #{region}", MU::ERR
896
+ return {}
897
+ end
807
898
 
808
899
  @@instance_types ||= {}
809
900
  @@instance_types[region] ||= {}
@@ -857,6 +948,7 @@ end
857
948
  # @param id [String]: The ARN of a known certificate. We just validate that it exists. This is ignored if a name parameter is supplied.
858
949
  # @return [String]: The ARN of a matching certificate that is known to exist. If it is an ACM certificate, we also know that it is not expired.
859
950
  def self.findSSLCertificate(name: nil, id: nil, region: myRegion, credentials: nil, raise_on_missing: true)
951
+ require "aws-sdk-iam"
860
952
  if (name.nil? or name.empty?) and (id.nil? or id.empty?)
861
953
  raise MuError, "Can't call findSSLCertificate without specifying either a name or an id"
862
954
  end
@@ -891,7 +983,7 @@ end
891
983
  return nil
892
984
  end
893
985
  elsif matches.size > 1
894
- 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."
986
+ 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."
895
987
  end
896
988
  end
897
989
 
@@ -953,7 +1045,7 @@ end
953
1045
  # Given a {MU::Config::Ref} block for an IAM or ACM SSL certificate,
954
1046
  # look up and validate the specified certificate. This is intended to be
955
1047
  # invoked from resource implementations' +validateConfig+ methods.
956
- # @param certblock [Hash,MU::Config::Ref]:
1048
+ # @param certblock [Hash,MU::Config::Ref]:
957
1049
  # @param region [String]: Default region to use when looking up the certificate, if its configuration block does not specify any
958
1050
  # @param credentials [String]: Default credentials to use when looking up the certificate, if its configuration block does not specify any
959
1051
  # @return [Boolean]
@@ -1121,7 +1213,7 @@ end
1121
1213
  @@elasticache_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "ElastiCache", region: region, credentials: credentials)
1122
1214
  @@elasticache_api[credentials][region]
1123
1215
  end
1124
-
1216
+
1125
1217
  # Amazon's SNS API
1126
1218
  def self.sns(region: MU.curRegion, credentials: nil)
1127
1219
  region ||= myRegion
@@ -1129,7 +1221,7 @@ end
1129
1221
  @@sns_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "SNS", region: region, credentials: credentials)
1130
1222
  @@sns_api[credentials][region]
1131
1223
  end
1132
-
1224
+
1133
1225
  # Amazon's SQS API
1134
1226
  def self.sqs(region: MU.curRegion, credentials: nil)
1135
1227
  region ||= myRegion
@@ -1161,7 +1253,7 @@ end
1161
1253
  @@apig_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "APIGateway", region: region, credentials: credentials)
1162
1254
  @@apig_api[credentials][region]
1163
1255
  end
1164
-
1256
+
1165
1257
  # Amazon's Cloudwatch Events API
1166
1258
  def self.cloudwatch_events(region = MU.cureRegion)
1167
1259
  region ||= myRegion
@@ -1274,7 +1366,7 @@ end
1274
1366
  begin
1275
1367
  response = nil
1276
1368
  Timeout.timeout(1) do
1277
- response = open("#{base_url}/#{param}").read
1369
+ response = URI.open("#{base_url}/#{param}").read
1278
1370
  end
1279
1371
 
1280
1372
  response
@@ -1299,6 +1391,7 @@ end
1299
1391
  tag_value=MU.deploy_id,
1300
1392
  region: MU.curRegion,
1301
1393
  credentials: nil)
1394
+ require "aws-sdk-ec2"
1302
1395
  attempts = 0
1303
1396
 
1304
1397
  return nil if resource.nil?
@@ -1339,6 +1432,7 @@ end
1339
1432
  # Mu Master, if we're in AWS.
1340
1433
  # @return [void]
1341
1434
  def self.openFirewallForClients
1435
+ require "aws-sdk-ec2"
1342
1436
  MU::Cloud.resourceClass("AWS", :FirewallRule)
1343
1437
  begin
1344
1438
  if File.exist?(Etc.getpwuid(Process.uid).dir+"/.chef/knife.rb")