cloud-mu 3.3.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/ansible/roles/mu-nat/tasks/main.yml +3 -0
  3. data/bin/mu-aws-setup +41 -7
  4. data/bin/mu-azure-setup +36 -2
  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 +9 -7
  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 +12 -0
  23. data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
  24. data/cookbooks/mu-tools/libraries/helper.rb +98 -4
  25. data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
  26. data/cookbooks/mu-tools/recipes/apply_security.rb +31 -9
  27. data/cookbooks/mu-tools/recipes/aws_api.rb +8 -2
  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 +7 -0
  31. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  32. data/cookbooks/mu-tools/resources/disk.rb +113 -42
  33. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  34. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  35. data/extras/Gemfile.lock.bootstrap +394 -0
  36. data/extras/bucketstubs/error.html +0 -0
  37. data/extras/bucketstubs/index.html +0 -0
  38. data/extras/clean-stock-amis +11 -3
  39. data/extras/generate-stock-images +6 -3
  40. data/extras/git_rpm/build.sh +20 -0
  41. data/extras/git_rpm/mugit.spec +53 -0
  42. data/extras/image-generators/AWS/centos7.yaml +19 -16
  43. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  44. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  45. data/extras/image-generators/VMWare/centos8.yaml +15 -0
  46. data/extras/openssl_rpm/build.sh +19 -0
  47. data/extras/openssl_rpm/mussl.spec +46 -0
  48. data/extras/python_rpm/muthon.spec +14 -4
  49. data/extras/ruby_rpm/muby.spec +9 -5
  50. data/extras/sqlite_rpm/build.sh +19 -0
  51. data/extras/sqlite_rpm/muqlite.spec +47 -0
  52. data/install/installer +7 -5
  53. data/modules/mommacat.ru +2 -2
  54. data/modules/mu.rb +12 -5
  55. data/modules/mu/cloud/machine_images.rb +1 -1
  56. data/modules/mu/cloud/providers.rb +6 -1
  57. data/modules/mu/cloud/resource_base.rb +7 -4
  58. data/modules/mu/cloud/ssh_sessions.rb +5 -1
  59. data/modules/mu/cloud/wrappers.rb +16 -7
  60. data/modules/mu/config.rb +28 -12
  61. data/modules/mu/config/database.rb +2 -2
  62. data/modules/mu/config/firewall_rule.rb +1 -1
  63. data/modules/mu/config/ref.rb +3 -3
  64. data/modules/mu/config/schema_helpers.rb +12 -3
  65. data/modules/mu/config/server.rb +10 -4
  66. data/modules/mu/config/server_pool.rb +2 -2
  67. data/modules/mu/config/vpc.rb +10 -10
  68. data/modules/mu/defaults/AWS.yaml +96 -96
  69. data/modules/mu/deploy.rb +27 -14
  70. data/modules/mu/groomers/chef.rb +2 -2
  71. data/modules/mu/master.rb +49 -3
  72. data/modules/mu/mommacat.rb +27 -9
  73. data/modules/mu/mommacat/naming.rb +2 -2
  74. data/modules/mu/mommacat/search.rb +16 -5
  75. data/modules/mu/mommacat/storage.rb +67 -32
  76. data/modules/mu/providers/aws.rb +185 -71
  77. data/modules/mu/providers/aws/alarm.rb +3 -3
  78. data/modules/mu/providers/aws/bucket.rb +19 -19
  79. data/modules/mu/providers/aws/cache_cluster.rb +22 -22
  80. data/modules/mu/providers/aws/cdn.rb +2 -2
  81. data/modules/mu/providers/aws/collection.rb +14 -14
  82. data/modules/mu/providers/aws/container_cluster.rb +27 -27
  83. data/modules/mu/providers/aws/database.rb +49 -45
  84. data/modules/mu/providers/aws/dnszone.rb +5 -5
  85. data/modules/mu/providers/aws/endpoint.rb +35 -35
  86. data/modules/mu/providers/aws/firewall_rule.rb +26 -23
  87. data/modules/mu/providers/aws/function.rb +35 -32
  88. data/modules/mu/providers/aws/group.rb +7 -7
  89. data/modules/mu/providers/aws/habitat.rb +2 -2
  90. data/modules/mu/providers/aws/job.rb +35 -32
  91. data/modules/mu/providers/aws/loadbalancer.rb +58 -37
  92. data/modules/mu/providers/aws/log.rb +14 -14
  93. data/modules/mu/providers/aws/msg_queue.rb +10 -10
  94. data/modules/mu/providers/aws/nosqldb.rb +8 -8
  95. data/modules/mu/providers/aws/notifier.rb +7 -7
  96. data/modules/mu/providers/aws/role.rb +69 -47
  97. data/modules/mu/providers/aws/search_domain.rb +10 -10
  98. data/modules/mu/providers/aws/server.rb +198 -110
  99. data/modules/mu/providers/aws/server_pool.rb +71 -119
  100. data/modules/mu/providers/aws/storage_pool.rb +17 -9
  101. data/modules/mu/providers/aws/user.rb +1 -1
  102. data/modules/mu/providers/aws/vpc.rb +106 -51
  103. data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
  104. data/modules/mu/providers/azure.rb +82 -16
  105. data/modules/mu/providers/azure/server.rb +18 -3
  106. data/modules/mu/providers/cloudformation/server.rb +1 -1
  107. data/modules/mu/providers/google.rb +20 -5
  108. data/modules/mu/providers/google/folder.rb +6 -2
  109. data/modules/mu/providers/google/function.rb +65 -30
  110. data/modules/mu/providers/google/role.rb +2 -1
  111. data/modules/mu/providers/google/vpc.rb +27 -2
  112. data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
  113. data/modules/tests/k8s.yaml +1 -1
  114. metadata +32 -15
@@ -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, "")
@@ -550,7 +627,9 @@ end
550
627
  def self.credToAcct(name = nil)
551
628
  creds = credConfig(name)
552
629
 
553
- return creds['account_number'] if creds['account_number']
630
+ if creds['account_number'] and !creds['account_number'].empty?
631
+ return creds['account_number']
632
+ end
554
633
 
555
634
  acct_num = MU::Cloud::AWS.iam(credentials: name).list_users.users.first.arn.split(/:/)[4]
556
635
  acct_num.to_s
@@ -564,17 +643,18 @@ end
564
643
  end
565
644
 
566
645
  $MU_CFG['aws'].keys
567
- end
646
+ end
568
647
 
569
648
  # Resolve the administrative S3 bucket for a given credential set, or
570
649
  # return a default.
571
650
  # @param credentials [String]
572
651
  # @return [String]
573
652
  def self.adminBucketName(credentials = nil)
653
+ require "aws-sdk-s3"
574
654
  cfg = credConfig(credentials)
575
655
  return nil if !cfg
576
656
  if !cfg['log_bucket_name']
577
- cfg['log_bucket_name'] = $MU_CFG['hostname']
657
+ cfg['log_bucket_name'] = $MU_CFG['hostname']
578
658
  MU.log "No AWS log bucket defined for credentials #{credentials}, attempting to use default of #{cfg['log_bucket_name']}", MU::WARN
579
659
  end
580
660
  resp = MU::Cloud::AWS.s3(credentials: credentials).list_buckets
@@ -608,7 +688,7 @@ end
608
688
 
609
689
  # Return the $MU_CFG data associated with a particular profile/name/set of
610
690
  # credentials. If no account name is specified, will return one flagged as
611
- # 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
612
692
  # an account name is specified which does not exist.
613
693
  # @param name [String]: The name of the key under 'aws' in mu.yaml to return
614
694
  # @return [Hash,nil]
@@ -623,10 +703,13 @@ end
623
703
 
624
704
  if hosted?
625
705
  begin
626
- iam_data = JSON.parse(getAWSMetaData("iam/info"))
627
- if iam_data["InstanceProfileArn"] and !iam_data["InstanceProfileArn"].empty?
628
- @@my_hosted_cfg = hosted_config
629
- 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
630
713
  end
631
714
  rescue JSON::ParserError => e
632
715
  end
@@ -672,8 +755,8 @@ end
672
755
  next
673
756
  end
674
757
  acct_num = MU::Cloud::AWS.iam(credentials: acctname).list_users.users.first.arn.split(/:/)[4]
675
- if acct_num.to_s == name.to_s
676
- cfg['account_number'] = acct_num.to_s
758
+ cfg['account_number'] ||= acct_num.to_s
759
+ if acct_num.to_s == name.to_s
677
760
  @@acct_to_profile_map[name.to_s] = cfg
678
761
  return name_only ? name.to_s : cfg
679
762
  end
@@ -690,6 +773,7 @@ end
690
773
  # XXX this needs to be "myAccountNumber" or somesuch
691
774
  # XXX and maybe do the IAM thing for arbitrary, non-resident accounts
692
775
  def self.account_number
776
+ require "aws-sdk-ec2"
693
777
  return nil if credConfig.nil?
694
778
  return @@my_acct_num if @@my_acct_num
695
779
  loadCredentials
@@ -747,7 +831,7 @@ end
747
831
  @@regions.keys.uniq
748
832
  end
749
833
 
750
- # 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
751
835
  # *probably* ok for most purposes? We can't call listAZs on it from out here
752
836
  # apparently, so getting around it is nontrivial
753
837
  # if !@@regions.has_key?("us-gov-west-1")
@@ -772,6 +856,7 @@ end
772
856
  # @param public_key [String]: The public key
773
857
  # @return [Array<String>]: keypairname, ssh_private_key, ssh_public_key
774
858
  def self.createEc2SSHKey(keyname, public_key, credentials: nil)
859
+ require "aws-sdk-ec2"
775
860
  # We replicate this key in all regions
776
861
  if !MU::Cloud::CloudFormation.emitCloudFormation
777
862
  MU::Cloud::AWS.listRegions.each { |region|
@@ -800,8 +885,16 @@ end
800
885
  def self.listInstanceTypes(region = myRegion)
801
886
  return @@instance_types if @@instance_types and @@instance_types[region]
802
887
  return {} if credConfig.nil?
888
+ if region.nil?
889
+ region = myRegion(debug: true)
890
+ end
891
+ return {} if region.nil?
803
892
 
804
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
805
898
 
806
899
  @@instance_types ||= {}
807
900
  @@instance_types[region] ||= {}
@@ -855,6 +948,7 @@ end
855
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.
856
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.
857
950
  def self.findSSLCertificate(name: nil, id: nil, region: myRegion, credentials: nil, raise_on_missing: true)
951
+ require "aws-sdk-iam"
858
952
  if (name.nil? or name.empty?) and (id.nil? or id.empty?)
859
953
  raise MuError, "Can't call findSSLCertificate without specifying either a name or an id"
860
954
  end
@@ -889,7 +983,7 @@ end
889
983
  return nil
890
984
  end
891
985
  elsif matches.size > 1
892
- 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."
893
987
  end
894
988
  end
895
989
 
@@ -951,7 +1045,7 @@ end
951
1045
  # Given a {MU::Config::Ref} block for an IAM or ACM SSL certificate,
952
1046
  # look up and validate the specified certificate. This is intended to be
953
1047
  # invoked from resource implementations' +validateConfig+ methods.
954
- # @param certblock [Hash,MU::Config::Ref]:
1048
+ # @param certblock [Hash,MU::Config::Ref]:
955
1049
  # @param region [String]: Default region to use when looking up the certificate, if its configuration block does not specify any
956
1050
  # @param credentials [String]: Default credentials to use when looking up the certificate, if its configuration block does not specify any
957
1051
  # @return [Boolean]
@@ -1119,7 +1213,7 @@ end
1119
1213
  @@elasticache_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "ElastiCache", region: region, credentials: credentials)
1120
1214
  @@elasticache_api[credentials][region]
1121
1215
  end
1122
-
1216
+
1123
1217
  # Amazon's SNS API
1124
1218
  def self.sns(region: MU.curRegion, credentials: nil)
1125
1219
  region ||= myRegion
@@ -1127,7 +1221,7 @@ end
1127
1221
  @@sns_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "SNS", region: region, credentials: credentials)
1128
1222
  @@sns_api[credentials][region]
1129
1223
  end
1130
-
1224
+
1131
1225
  # Amazon's SQS API
1132
1226
  def self.sqs(region: MU.curRegion, credentials: nil)
1133
1227
  region ||= myRegion
@@ -1159,7 +1253,7 @@ end
1159
1253
  @@apig_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "APIGateway", region: region, credentials: credentials)
1160
1254
  @@apig_api[credentials][region]
1161
1255
  end
1162
-
1256
+
1163
1257
  # Amazon's Cloudwatch Events API
1164
1258
  def self.cloudwatch_events(region = MU.cureRegion)
1165
1259
  region ||= myRegion
@@ -1272,7 +1366,7 @@ end
1272
1366
  begin
1273
1367
  response = nil
1274
1368
  Timeout.timeout(1) do
1275
- response = open("#{base_url}/#{param}").read
1369
+ response = URI.open("#{base_url}/#{param}").read
1276
1370
  end
1277
1371
 
1278
1372
  response
@@ -1297,6 +1391,7 @@ end
1297
1391
  tag_value=MU.deploy_id,
1298
1392
  region: MU.curRegion,
1299
1393
  credentials: nil)
1394
+ require "aws-sdk-ec2"
1300
1395
  attempts = 0
1301
1396
 
1302
1397
  return nil if resource.nil?
@@ -1337,6 +1432,7 @@ end
1337
1432
  # Mu Master, if we're in AWS.
1338
1433
  # @return [void]
1339
1434
  def self.openFirewallForClients
1435
+ require "aws-sdk-ec2"
1340
1436
  MU::Cloud.resourceClass("AWS", :FirewallRule)
1341
1437
  begin
1342
1438
  if File.exist?(Etc.getpwuid(Process.uid).dir+"/.chef/knife.rb")
@@ -1516,6 +1612,7 @@ end
1516
1612
  def initialize(region: nil, api: "EC2", credentials: nil)
1517
1613
  @cred_obj = MU::Cloud::AWS.loadCredentials(credentials)
1518
1614
  @credentials = MU::Cloud::AWS.credConfig(credentials, name_only: true)
1615
+ @api_name = api
1519
1616
 
1520
1617
  if !@cred_obj
1521
1618
  raise MuError, "Unable to locate valid AWS credentials for #{api} API. #{credentials ? "Credentials requested were '#{credentials}'": ""}"
@@ -1533,6 +1630,8 @@ end
1533
1630
  params[:credentials] = @cred_obj
1534
1631
 
1535
1632
  MU.log "Initializing #{api} object with credentials #{credentials}", MU::DEBUG, details: params
1633
+ require "aws-sdk-#{api.downcase}"
1634
+
1536
1635
  @api = Object.const_get("Aws::#{api}::Client").new(params)
1537
1636
  end
1538
1637
 
@@ -1541,27 +1640,31 @@ end
1541
1640
  # rescues for known silly endpoint behavior.
1542
1641
  def method_missing(method_sym, *arguments)
1543
1642
  # make sure error symbols are loaded for our exception handling later
1544
- require "aws-sdk-core"
1545
- require "aws-sdk-core/rds"
1546
- require "aws-sdk-core/ec2"
1547
- require "aws-sdk-core/route53"
1548
- require "aws-sdk-core/iam"
1549
- require "aws-sdk-core/efs"
1550
- require "aws-sdk-core/pricing"
1551
- require "aws-sdk-core/apigateway"
1552
- require "aws-sdk-core/ecs"
1553
- require "aws-sdk-core/eks"
1554
- require "aws-sdk-core/cloudwatchlogs"
1555
- require "aws-sdk-core/cloudwatchevents"
1556
- require "aws-sdk-core/elasticloadbalancing"
1557
- require "aws-sdk-core/elasticloadbalancingv2"
1558
- require "aws-sdk-core/autoscaling"
1559
- require "aws-sdk-core/client_waiters"
1560
- require "aws-sdk-core/waiters/errors"
1643
+ require "aws-sdk-lambda"
1644
+ require "aws-sdk-rds"
1645
+ require "aws-sdk-ec2"
1646
+ require "aws-sdk-route53"
1647
+ require "aws-sdk-iam"
1648
+ require "aws-sdk-efs"
1649
+ require "aws-sdk-pricing"
1650
+ require "aws-sdk-apigateway"
1651
+ require "aws-sdk-ecs"
1652
+ require "aws-sdk-eks"
1653
+ require "aws-sdk-cloudwatchlogs"
1654
+ require "aws-sdk-cloudwatchevents"
1655
+ require "aws-sdk-elasticloadbalancing"
1656
+ require "aws-sdk-elasticloadbalancingv2"
1657
+ require "aws-sdk-autoscaling"
1658
+
1659
+ known_concats = {
1660
+ "Pricing" => {
1661
+ :get_products => :price_list
1662
+ }
1663
+ }
1561
1664
 
1562
1665
  retries = 0
1563
1666
  begin
1564
- MU.log "Calling #{method_sym} in #{@region}", MU::DEBUG, details: arguments
1667
+ MU.log "Calling #{@api_name}.#{method_sym} in #{@region}", MU::DEBUG, details: arguments
1565
1668
 
1566
1669
  retval = if !arguments.nil? and arguments.size == 1
1567
1670
  @api.method(method_sym).call(arguments[0])
@@ -1590,11 +1693,22 @@ end
1590
1693
 
1591
1694
  if paginator and new_page and !new_page.empty?
1592
1695
  resp = retval.respond_to?(:__getobj__) ? retval.__getobj__ : retval
1593
- concat_to = resp.class.instance_methods(false).reject { |m|
1696
+ concat_to = MU.structToHash(resp).keys.reject { |m|
1594
1697
  m.to_s.match(/=$/) or m == paginator or resp.send(m).nil? or !resp.send(m).is_a?(Array)
1595
1698
  }
1699
+
1700
+ if concat_to.empty? and known_concats[@api_name] and
1701
+ known_concats[@api_name][method_sym]
1702
+ concat_to << known_concats[@api_name][method_sym]
1703
+ end
1704
+
1705
+ if concat_to.empty? and method_sym.to_s.match(/^(?:describe|list)_(.*)/)
1706
+ my_attr = Regexp.last_match[1].to_sym
1707
+ concat_to << my_attr if resp.respond_to?(my_attr)
1708
+ end
1709
+
1596
1710
  if concat_to.size != 1
1597
- MU.log "Tried to figure out where I might append paginated results for a #{resp.class.name}, but failed", MU::DEBUG, details: concat_to
1711
+ raise MuError.new "Tried to figure out where I might append paginated results for a #{@api_name}.#{method_sym}, but failed", details: MU.structToHash(resp).keys
1598
1712
  else
1599
1713
  concat_to = concat_to.first
1600
1714
  new_args = arguments ? arguments.dup : [{}]