cloud-mu 3.0.0beta → 3.0.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -8
  3. data/ansible/roles/mu-nat/README.md +33 -0
  4. data/ansible/roles/mu-nat/defaults/main.yml +3 -0
  5. data/ansible/roles/mu-nat/handlers/main.yml +2 -0
  6. data/ansible/roles/mu-nat/meta/main.yml +60 -0
  7. data/ansible/roles/mu-nat/tasks/main.yml +65 -0
  8. data/ansible/roles/mu-nat/tests/inventory +2 -0
  9. data/ansible/roles/mu-nat/tests/test.yml +5 -0
  10. data/ansible/roles/mu-nat/vars/main.yml +2 -0
  11. data/bin/mu-cleanup +2 -1
  12. data/bin/mu-configure +950 -948
  13. data/bin/mu-gen-docs +6 -0
  14. data/cloud-mu.gemspec +2 -2
  15. data/cookbooks/mu-tools/recipes/gcloud.rb +8 -1
  16. data/modules/mommacat.ru +1 -1
  17. data/modules/mu.rb +31 -39
  18. data/modules/mu/cloud.rb +11 -1
  19. data/modules/mu/clouds/aws.rb +8 -3
  20. data/modules/mu/clouds/aws/alarm.rb +5 -8
  21. data/modules/mu/clouds/aws/bucket.rb +15 -9
  22. data/modules/mu/clouds/aws/cache_cluster.rb +60 -26
  23. data/modules/mu/clouds/aws/collection.rb +4 -4
  24. data/modules/mu/clouds/aws/container_cluster.rb +50 -33
  25. data/modules/mu/clouds/aws/database.rb +25 -21
  26. data/modules/mu/clouds/aws/dnszone.rb +12 -14
  27. data/modules/mu/clouds/aws/endpoint.rb +5 -8
  28. data/modules/mu/clouds/aws/firewall_rule.rb +9 -4
  29. data/modules/mu/clouds/aws/folder.rb +4 -7
  30. data/modules/mu/clouds/aws/function.rb +5 -8
  31. data/modules/mu/clouds/aws/group.rb +5 -8
  32. data/modules/mu/clouds/aws/habitat.rb +2 -5
  33. data/modules/mu/clouds/aws/loadbalancer.rb +12 -16
  34. data/modules/mu/clouds/aws/log.rb +6 -9
  35. data/modules/mu/clouds/aws/msg_queue.rb +16 -19
  36. data/modules/mu/clouds/aws/nosqldb.rb +27 -18
  37. data/modules/mu/clouds/aws/notifier.rb +6 -9
  38. data/modules/mu/clouds/aws/role.rb +4 -7
  39. data/modules/mu/clouds/aws/search_domain.rb +50 -23
  40. data/modules/mu/clouds/aws/server.rb +20 -14
  41. data/modules/mu/clouds/aws/server_pool.rb +22 -12
  42. data/modules/mu/clouds/aws/storage_pool.rb +9 -14
  43. data/modules/mu/clouds/aws/user.rb +5 -8
  44. data/modules/mu/clouds/aws/userdata/linux.erb +7 -1
  45. data/modules/mu/clouds/aws/vpc.rb +16 -14
  46. data/modules/mu/clouds/azure.rb +1 -1
  47. data/modules/mu/clouds/azure/container_cluster.rb +1 -1
  48. data/modules/mu/clouds/azure/server.rb +16 -2
  49. data/modules/mu/clouds/azure/user.rb +1 -1
  50. data/modules/mu/clouds/azure/userdata/linux.erb +84 -80
  51. data/modules/mu/clouds/azure/vpc.rb +32 -13
  52. data/modules/mu/clouds/cloudformation/server.rb +1 -1
  53. data/modules/mu/clouds/google.rb +2 -3
  54. data/modules/mu/clouds/google/container_cluster.rb +9 -1
  55. data/modules/mu/clouds/google/firewall_rule.rb +6 -0
  56. data/modules/mu/clouds/google/role.rb +1 -3
  57. data/modules/mu/clouds/google/server.rb +25 -4
  58. data/modules/mu/clouds/google/user.rb +1 -1
  59. data/modules/mu/clouds/google/userdata/linux.erb +9 -5
  60. data/modules/mu/clouds/google/vpc.rb +102 -21
  61. data/modules/mu/config.rb +250 -49
  62. data/modules/mu/config/alarm.rb +1 -0
  63. data/modules/mu/config/container_cluster.yml +0 -1
  64. data/modules/mu/config/database.yml +4 -1
  65. data/modules/mu/config/search_domain.yml +4 -3
  66. data/modules/mu/config/server.rb +7 -3
  67. data/modules/mu/config/server.yml +4 -1
  68. data/modules/mu/config/server_pool.yml +2 -0
  69. data/modules/mu/config/vpc.rb +42 -29
  70. data/modules/mu/deploy.rb +12 -5
  71. data/modules/mu/groomers/ansible.rb +4 -1
  72. data/modules/mu/groomers/chef.rb +5 -1
  73. data/modules/mu/kittens.rb +60 -11
  74. data/modules/mu/logger.rb +6 -4
  75. data/modules/mu/mommacat.rb +39 -19
  76. data/modules/mu/mu.yaml.rb +276 -0
  77. metadata +13 -4
@@ -47,6 +47,7 @@ if !Dir.exist?(docdir)
47
47
  FileUtils.mkdir_p(docdir, mode: 0755)
48
48
  end
49
49
 
50
+ MU::Config.emitConfigAsRuby
50
51
  MU::Config.emitSchemaAsRuby
51
52
  if Process.uid == 0
52
53
  MU.log "Generating YARD documentation in #{docdir} (see http://#{$MU_CFG['public_address']}/docs/frames.html)"
@@ -68,6 +69,7 @@ Dir.chdir(MU.myRoot) do
68
69
  EOF
69
70
 
70
71
  impl_counts = {}
72
+ cloud_is_useful = {}
71
73
  cloudlist = MU::Cloud.supportedClouds.sort { |a, b|
72
74
  counts = {
73
75
  a => 0,
@@ -80,9 +82,11 @@ EOF
80
82
  myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
81
83
  case myclass.quality
82
84
  when MU::Cloud::RELEASE
85
+ cloud_is_useful[cloud] = true
83
86
  counts[cloud] += 4
84
87
  impl_counts[type] += 4
85
88
  when MU::Cloud::BETA
89
+ cloud_is_useful[cloud] = true
86
90
  counts[cloud] += 2
87
91
  impl_counts[type] += 2
88
92
  when MU::Cloud::ALPHA
@@ -96,6 +100,8 @@ EOF
96
100
  counts[b] <=> counts[a]
97
101
  }
98
102
 
103
+ cloudlist.reject! { |c| !cloud_is_useful[c] }
104
+
99
105
  readme += "\n\n<table><tr><th></th>"
100
106
  cloudlist.each { |cloud|
101
107
  readme += "<th>"+cloud+"</th>"
@@ -17,8 +17,8 @@ end
17
17
 
18
18
  Gem::Specification.new do |s|
19
19
  s.name = 'cloud-mu'
20
- s.version = '3.0.0beta'
21
- s.date = '2019-11-01'
20
+ s.version = '3.0.0'
21
+ s.date = '2019-11-11'
22
22
  s.require_paths = ['modules']
23
23
  s.required_ruby_version = '>= 2.4'
24
24
  s.summary = "The eGTLabs Mu toolkit for unified cloud deployments"
@@ -41,7 +41,14 @@ if platform_family?("rhel") or platform_family?("amazon")
41
41
  cwd "/opt"
42
42
  code <<-EOH
43
43
  tar -xzf #{Chef::Config[:file_cache_path]}/gcloud-cli.tar.gz
44
- CLOUDSDK_PYTHON="`/bin/rpm -ql muthon | grep '/bin/python$'`" ./google-cloud-sdk/install.sh -q
44
+ if [ -f /opt/rh/python27/root/usr/bin/python ];then
45
+ if [ ! -f /etc/ld.so.conf.d/python27.conf ];then
46
+ echo "/opt/rh/python27/root/usr/lib64" > /etc/ld.so.conf.d/python27.conf
47
+ echo "/opt/rh/python27/root/usr/lib" >> /etc/ld.so.conf.d/python27.conf
48
+ /sbin/ldconfig
49
+ fi
50
+ fi
51
+ CLOUDSDK_PYTHON="`/bin/rpm -ql muthon python27-python | grep '/bin/python$'`" ./google-cloud-sdk/install.sh -q
45
52
  EOH
46
53
  notifies :create, "remote_file[#{Chef::Config[:file_cache_path]}/gcloud-cli.sh]", :before
47
54
  notifies :create, "remote_file[#{Chef::Config[:file_cache_path]}/gcloud-cli.tar.gz]", :before
@@ -46,7 +46,7 @@ end
46
46
  Signal.trap("URG") do
47
47
  puts "------------------------------"
48
48
  puts "Open flock() locks:"
49
- pp MU::MommaCat.locks
49
+ pp MU::MommaCat.trapSafeLocks
50
50
  puts "------------------------------"
51
51
  end
52
52
 
@@ -404,41 +404,49 @@ module MU
404
404
 
405
405
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
406
406
  def self.mommacat;
407
+ @@globals[Thread.current.object_id] ||= {}
407
408
  @@globals[Thread.current.object_id]['mommacat']
408
409
  end
409
410
 
410
411
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
411
412
  def self.deploy_id;
413
+ @@globals[Thread.current.object_id] ||= {}
412
414
  @@globals[Thread.current.object_id]['deploy_id']
413
415
  end
414
416
 
415
417
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
416
418
  def self.appname;
419
+ @@globals[Thread.current.object_id] ||= {}
417
420
  @@globals[Thread.current.object_id]['appname']
418
421
  end
419
422
 
420
423
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
421
424
  def self.environment;
425
+ @@globals[Thread.current.object_id] ||= {}
422
426
  @@globals[Thread.current.object_id]['environment']
423
427
  end
424
428
 
425
429
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
426
430
  def self.timestamp;
431
+ @@globals[Thread.current.object_id] ||= {}
427
432
  @@globals[Thread.current.object_id]['timestamp']
428
433
  end
429
434
 
430
435
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
431
436
  def self.seed;
437
+ @@globals[Thread.current.object_id] ||= {}
432
438
  @@globals[Thread.current.object_id]['seed']
433
439
  end
434
440
 
435
441
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
436
442
  def self.handle;
443
+ @@globals[Thread.current.object_id] ||= {}
437
444
  @@globals[Thread.current.object_id]['handle']
438
445
  end
439
446
 
440
447
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
441
448
  def self.chef_user;
449
+ @@globals[Thread.current.object_id] ||= {}
442
450
  if @@globals.has_key?(Thread.current.object_id) and @@globals[Thread.current.object_id].has_key?('chef_user')
443
451
  @@globals[Thread.current.object_id]['chef_user']
444
452
  elsif Etc.getpwuid(Process.uid).name == "root"
@@ -450,6 +458,7 @@ module MU
450
458
 
451
459
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
452
460
  def self.mu_user
461
+ @@globals[Thread.current.object_id] ||= {}
453
462
  if @@globals.has_key?(Thread.current.object_id) and @@globals[Thread.current.object_id].has_key?('mu_user')
454
463
  return @@globals[Thread.current.object_id]['mu_user']
455
464
  elsif Etc.getpwuid(Process.uid).name == "root"
@@ -461,11 +470,13 @@ module MU
461
470
 
462
471
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
463
472
  def self.curRegion
473
+ @@globals[Thread.current.object_id] ||= {}
464
474
  @@globals[Thread.current.object_id]['curRegion'] ||= myRegion || ENV['EC2_REGION']
465
475
  end
466
476
 
467
477
  # Accessor for per-thread global variable. There is probably a Ruby-clever way to define this.
468
478
  def self.syncLitterThread;
479
+ @@globals[Thread.current.object_id] ||= {}
469
480
  @@globals[Thread.current.object_id]['syncLitterThread']
470
481
  end
471
482
 
@@ -487,26 +498,13 @@ module MU
487
498
  end
488
499
  end
489
500
 
490
- # The verbose logging flag merits a default value.
501
+ # Return the verbosity setting of the default @@logger object
491
502
  def self.verbosity
492
- if @@globals[Thread.current.object_id].nil? or @@globals[Thread.current.object_id]['verbosity'].nil?
493
- MU.setVar("verbosity", MU::Logger::NORMAL)
494
- end
495
- @@globals[Thread.current.object_id]['verbosity']
496
- end
497
-
498
- # The color logging flag merits a default value.
499
- def self.color
500
- if @@globals[Thread.current.object_id].nil? or @@globals[Thread.current.object_id]['color'].nil?
501
- MU.setVar("color", true)
502
- end
503
- @@globals[Thread.current.object_id]['color']
503
+ @@logger ? @@logger.verbosity : MU::Logger::NORMAL
504
504
  end
505
505
 
506
506
  # Set parameters parameters for calls to {MU#log}
507
507
  def self.setLogging(verbosity, webify_logs = false, handle = STDOUT, color = true)
508
- MU.setVar("verbosity", verbosity)
509
- MU.setVar("color", color)
510
508
  @@logger ||= MU::Logger.new(verbosity, webify_logs, handle, color)
511
509
  @@logger.html = webify_logs
512
510
  @@logger.verbosity = verbosity
@@ -523,33 +521,25 @@ module MU
523
521
  end
524
522
 
525
523
  # Shortcut to invoke {MU::Logger#log}
526
- def self.log(msg, level = MU::INFO, details: nil, html: false, verbosity: MU.verbosity, color: true)
527
- return if (level == MU::DEBUG and verbosity <= MU::Logger::LOUD)
528
- return if verbosity == MU::Logger::SILENT
524
+ def self.log(msg, level = MU::INFO, details: nil, html: false, verbosity: nil, color: true)
525
+ return if (level == MU::DEBUG and verbosity and verbosity <= MU::Logger::LOUD)
526
+ return if verbosity and verbosity == MU::Logger::SILENT
529
527
 
530
528
  if (level == MU::ERR or
531
529
  level == MU::WARN or
532
530
  level == MU::DEBUG or
533
- verbosity >= MU::Logger::LOUD or
534
- (level == MU::NOTICE and !details.nil?)
535
- )
536
- # TODO add more stuff to details here (e.g. call stack)
537
- extra = nil
538
- if Thread.current.thread_variable_get("name") and (level > MU::NOTICE or verbosity >= MU::Logger::LOUD)
539
- extra = Hash.new
540
- extra = {
541
- :thread => Thread.current.object_id,
542
- :name => Thread.current.thread_variable_get("name")
543
- }
544
- end
545
- if !details.nil?
546
- extra = Hash.new if extra.nil?
547
- extra[:details] = details
548
- end
549
- @@logger.log(msg, level, details: extra, verbosity: MU::Logger::LOUD, html: html, color: color)
550
- else
551
- @@logger.log(msg, level, html: html, verbosity: verbosity, color: color)
531
+ (verbosity and verbosity >= MU::Logger::LOUD) or
532
+ (level == MU::NOTICE and !details.nil?)) and
533
+ Thread.current.thread_variable_get("name")
534
+ newdetails = {
535
+ :thread => Thread.current.object_id,
536
+ :name => Thread.current.thread_variable_get("name")
537
+ }
538
+ newdetails[:details] = details.dup if details
539
+ details = newdetails
552
540
  end
541
+
542
+ @@logger.log(msg, level, details: details, html: html, verbosity: verbosity, color: color)
553
543
  end
554
544
 
555
545
  # For log entries that should only be logged when we're in verbose mode
@@ -894,9 +884,11 @@ module MU
894
884
 
895
885
  @@myCloudDescriptor = nil
896
886
  if MU.myCloud
897
- found = MU::MommaCat.findStray(MU.myCloud, "server", cloud_id: @@myInstanceId, dummy_ok: true, region: MU.myRegion)
887
+ svrclass = const_get("MU").const_get("Cloud").const_get(MU.myCloud).const_get("Server")
888
+ found = svrclass.find(cloud_id: @@myInstanceId, region: MU.myRegion) # XXX need habitat arg for google et al
889
+ # found = MU::MommaCat.findStray(MU.myCloud, "server", cloud_id: @@myInstanceId, dummy_ok: true, region: MU.myRegion)
898
890
  if !found.nil? and found.size == 1
899
- @@myCloudDescriptor = found.first.cloud_desc
891
+ @@myCloudDescriptor = found.values.first
900
892
  end
901
893
  end
902
894
 
@@ -1351,6 +1351,8 @@ module MU
1351
1351
 
1352
1352
  # Special dependencies: my containing VPC
1353
1353
  if self.class.can_live_in_vpc and !@config['vpc'].nil?
1354
+ @config['vpc']["id"] ||= @config['vpc']["vpc_id"] # old deploys
1355
+ @config['vpc']["name"] ||= @config['vpc']["vpc_name"] # old deploys
1354
1356
  # If something hash-ified a MU::Config::Ref here, fix it
1355
1357
  if !@config['vpc']["id"].nil? and @config['vpc']["id"].is_a?(Hash)
1356
1358
  @config['vpc']["id"] = MU::Config::Ref.new(@config['vpc']["id"])
@@ -1930,6 +1932,8 @@ puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1930
1932
  session = nil
1931
1933
  retries = 0
1932
1934
 
1935
+ vpc_class = Object.const_get("MU").const_get("Cloud").const_get(@cloud).const_get("VPC")
1936
+
1933
1937
  # XXX WHY is this a thing
1934
1938
  Thread.handle_interrupt(Errno::ECONNREFUSED => :never) {
1935
1939
  }
@@ -1953,6 +1957,7 @@ puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1953
1957
  :proxy => proxy
1954
1958
  )
1955
1959
  else
1960
+
1956
1961
  MU.log "Attempting SSH to #{canonical_ip} (#{@mu_name}) as #{ssh_user} with key #{ssh_keydir}/#{@deploy.ssh_key_name}" if retries == 0
1957
1962
  session = Net::SSH.start(
1958
1963
  canonical_ip,
@@ -1986,9 +1991,14 @@ puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
1986
1991
 
1987
1992
  if retries < max_retries
1988
1993
  retries = retries + 1
1989
- msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})", MU::WARN
1994
+ msg = "ssh #{ssh_user}@#{@mu_name}: #{e.message}, waiting #{retry_interval}s (attempt #{retries}/#{max_retries})"
1990
1995
  if retries == 1 or (retries/max_retries <= 0.5 and (retries % 3) == 0)
1991
1996
  MU.log msg, MU::NOTICE
1997
+ if !vpc_class.haveRouteToInstance?(cloud_desc, credentials: @credentials) and
1998
+ canonical_ip.match(/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/) and
1999
+ !nat_ssh_host
2000
+ MU.log "Node #{@mu_name} at #{canonical_ip} looks like it's in a private address space, and I don't appear to have a direct route to it. It may not be possible to connect with this routing!", MU::WARN
2001
+ end
1992
2002
  elsif retries/max_retries > 0.5
1993
2003
  MU.log msg, MU::WARN, details: e.inspect
1994
2004
  end
@@ -164,6 +164,7 @@ module MU
164
164
  # @param r [String]
165
165
  # @return [String]
166
166
  def self.validate_region(r, credentials: nil)
167
+ require "aws-sdk-core"
167
168
  begin
168
169
  MU::Cloud::AWS.ec2(region: r, credentials: credentials).describe_availability_zones.availability_zones.first.region_name
169
170
  rescue ::Aws::EC2::Errors::UnauthorizedOperation => e
@@ -207,15 +208,19 @@ module MU
207
208
  MU.log "Created standard tags for resource #{resource}", MU::DEBUG, details: caller
208
209
  end
209
210
 
211
+ @@myVPCObj = nil
212
+
210
213
  # If we reside in this cloud, return the VPC in which we, the Mu Master, reside.
211
214
  # @return [MU::Cloud::VPC]
212
215
  def self.myVPCObj
216
+ return @@myVPCObj if @@myVPCObj
213
217
  return nil if !hosted?
214
218
  instance = MU.myCloudDescriptor
215
219
  return nil if !instance or !instance.vpc_id
216
- vpc = MU::MommaCat.findStray("AWS", "vpc", cloud_id: instance.vpc_id, dummy_ok: true)
220
+ vpc = MU::MommaCat.findStray("AWS", "vpc", cloud_id: instance.vpc_id, dummy_ok: true, no_deploy_search: true)
217
221
  return nil if vpc.nil? or vpc.size == 0
218
- vpc.first
222
+ @@myVPCObj = vpc.first
223
+ @@myVPCObj
219
224
  end
220
225
 
221
226
  # If we've configured AWS as a provider, or are simply hosted in AWS,
@@ -1334,8 +1339,8 @@ module MU
1334
1339
  # @param region [String]: Amazon region so we know what endpoint to use
1335
1340
  # @param api [String]: Which API are we wrapping?
1336
1341
  def initialize(region: MU.curRegion, api: "EC2", credentials: nil)
1337
- @cred_obj = MU::Cloud::AWS.loadCredentials(credentials)
1338
1342
  @credentials = MU::Cloud::AWS.credConfig(credentials, name_only: true)
1343
+ @cred_obj = MU::Cloud::AWS.loadCredentials(credentials)
1339
1344
 
1340
1345
  if !@cred_obj
1341
1346
  raise MuError, "Unable to locate valid AWS credentials for #{api} API. #{credentials ? "Credentials requested were '#{credentials}'": ""}"
@@ -147,12 +147,9 @@ module MU
147
147
  end
148
148
 
149
149
  # Locate an existing alarm.
150
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
151
- # @param region [String]: The cloud provider region.
152
- # @param flags [Hash]: Optional flags
153
150
  # @return [OpenStruct]: The cloud provider's complete descriptions of matching alarm.
154
- def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
155
- MU::Cloud::AWS::Alarm.getAlarmByName(cloud_id, region: region, credentials: credentials)
151
+ def self.find(**args)
152
+ MU::Cloud::AWS::Alarm.getAlarmByName(args[:cloud_id], region: args[:region], credentials: args[:credentials])
156
153
  end
157
154
 
158
155
  # Create an alarm.
@@ -260,13 +257,13 @@ module MU
260
257
  alarm["dimensions"] ||= []
261
258
 
262
259
  if alarm["#TARGETCLASS"] == "cache_cluster"
263
- alarm['dimensions'] << { "name" => alarm["#TARGETCLASS"], "cloud_class" => "CacheClusterId" }
260
+ alarm['dimensions'] << { "name" => alarm["#TARGETNAME"], "cloud_class" => "CacheClusterId" }
264
261
  alarm["namespace"] = "AWS/ElastiCache" if alarm["namespace"].nil?
265
262
  elsif alarm["#TARGETCLASS"] == "server"
266
- alarm['dimensions'] << { "name" => alarm["#TARGETCLASS"], "cloud_class" => "InstanceId" }
263
+ alarm['dimensions'] << { "name" => alarm["#TARGETNAME"], "cloud_class" => "InstanceId" }
267
264
  alarm["namespace"] = "AWS/EC2" if alarm["namespace"].nil?
268
265
  elsif alarm["#TARGETCLASS"] == "database"
269
- alarm['dimensions'] << { "name" => alarm["#TARGETCLASS"], "cloud_class" => "DBInstanceIdentifier" }
266
+ alarm['dimensions'] << { "name" => alarm["#TARGETNAME"], "cloud_class" => "DBInstanceIdentifier" }
270
267
  alarm["namespace"] = "AWS/RDS" if alarm["namespace"].nil?
271
268
  end
272
269
 
@@ -33,11 +33,17 @@ module MU
33
33
  bucket_name = @deploy.getResourceName(@config["name"], max_length: 63).downcase
34
34
 
35
35
  MU.log "Creating S3 bucket #{bucket_name}"
36
- MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).create_bucket(
36
+ resp = MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).create_bucket(
37
37
  acl: @config['acl'],
38
38
  bucket: bucket_name
39
39
  )
40
+
40
41
  @cloud_id = bucket_name
42
+ is_live = MU::Cloud::AWS::Bucket.find(cloud_id: @cloud_id, region: @config['region'], credentials: @credentials).values.first
43
+ begin
44
+ is_live = MU::Cloud::AWS::Bucket.find(cloud_id: @cloud_id, region: @config['region'], credentials: @credentials).values.first
45
+ sleep 3
46
+ end while !is_live
41
47
 
42
48
  @@region_cache_semaphore.synchronize {
43
49
  @@region_cache[@cloud_id] ||= @config['region']
@@ -216,7 +222,7 @@ puts path
216
222
  else
217
223
  @@region_cache[bucket.name] = location
218
224
  end
219
- rescue Aws::S3::Errors::AccessDenied => e
225
+ rescue Aws::S3::Errors::NoSuchBucket, Aws::S3::Errors::AccessDenied
220
226
  # this is routine- we saw a bucket that's not our business
221
227
  next
222
228
  end
@@ -261,14 +267,14 @@ puts path
261
267
  end
262
268
 
263
269
  # Locate an existing bucket.
264
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
265
- # @param region [String]: The cloud provider region.
266
- # @param flags [Hash]: Optional flags
267
- # @return [OpenStruct]: The cloud provider's complete descriptions of matching bucket.
268
- def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
270
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching bucket.
271
+ def self.find(**args)
269
272
  found = {}
270
- if cloud_id
271
- found[cloud_id] = describe_bucket(cloud_id, minimal: true, credentials: credentials, region: region)
273
+ if args[:cloud_id]
274
+ begin
275
+ found[args[:cloud_id]] = describe_bucket(args[:cloud_id], minimal: true, credentials: args[:credentials], region: args[:region])
276
+ rescue ::Aws::S3::Errors::NoSuchBucket
277
+ end
272
278
  end
273
279
  found
274
280
  end
@@ -39,27 +39,22 @@ module MU
39
39
  end
40
40
 
41
41
  # Locate an existing Cache Cluster or Cache Clusters and return an array containing matching AWS resource descriptors for those that match.
42
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
43
- # @param region [String]: The cloud provider region.
44
- # @param tag_key [String]: A tag key to search.
45
- # @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
46
- # @param flags [Hash]: Optional flags
47
- # @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching Cache Clusters.
48
- def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, credentials: nil, flags: {})
42
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching Cache Clusters.
43
+ def self.find(**args)
49
44
  map = {}
50
- if cloud_id
51
- cache_cluster = MU::Cloud::AWS::CacheCluster.getCacheClusterById(cloud_id, region: region)
52
- map[cloud_id] = cache_cluster if cache_cluster
45
+ if args[:cloud_id]
46
+ cache_cluster = MU::Cloud::AWS::CacheCluster.getCacheClusterById(args[:cloud_id], region: args[:region], credentials: args[:credentials])
47
+ map[args[:cloud_id]] = cache_cluster if cache_cluster
53
48
  end
54
49
 
55
- if tag_value
56
- MU::Cloud::AWS.elasticache(region: region, credentials: credentials).describe_cache_clusters.cache_clusters.each { |cc|
57
- resp = MU::Cloud::AWS.elasticache(region: region, credentials: credentials).list_tags_for_resource(
58
- resource_name: MU::Cloud::AWS::CacheCluster.getARN(cc.cache_cluster_id, "cluster", "elasticache", region: region, credentials: credentials)
50
+ if args[:tag_value]
51
+ MU::Cloud::AWS.elasticache(region: args[:region], credentials: args[:credentials]).describe_cache_clusters.cache_clusters.each { |cc|
52
+ resp = MU::Cloud::AWS.elasticache(region: args[:region], credentials: args[:credentials]).list_tags_for_resource(
53
+ resource_name: MU::Cloud::AWS::CacheCluster.getARN(cc.cache_cluster_id, "cluster", "elasticache", region: args[:region], credentials: args[:credentials])
59
54
  )
60
55
  if resp && resp.tag_list && !resp.tag_list.empty?
61
56
  resp.tag_list.each { |tag|
62
- map[cc.cache_cluster_id] = cc if tag.key == tag_key and tag.value == tag_value
57
+ map[cc.cache_cluster_id] = cc if tag.key == args[:tag_key] and tag.value == args[:tag_value]
63
58
  }
64
59
  end
65
60
  }
@@ -222,14 +217,25 @@ module MU
222
217
  @cloud_id = resp.replication_group_id
223
218
  else
224
219
  config_struct[:cache_cluster_id] = @config['identifier']
225
- config_struct[:az_mode] = @config["az_mode"]
220
+ config_struct[:az_mode] = @config["multi_az"] ? "cross-az" : "single-az"
226
221
  config_struct[:num_cache_nodes] = @config["node_count"]
227
222
  # config_struct[:replication_group_id] = @config["replication_group_id"] if @config["replication_group_id"]
228
223
  # config_struct[:preferred_availability_zone] = @config["preferred_availability_zone"] if @config["preferred_availability_zone"] && @config["az_mode"] == "single-az"
229
224
  # config_struct[:preferred_availability_zones] = @config["preferred_availability_zones"] if @config["preferred_availability_zones"] && @config["az_mode"] == "cross-az"
230
225
 
231
226
  MU.log "Creating cache cluster #{@config['identifier']}"
232
- resp = MU::Cloud::AWS.elasticache(region: @config['region'], credentials: @config['credentials']).create_cache_cluster(config_struct).cache_cluster
227
+ begin
228
+ resp = MU::Cloud::AWS.elasticache(region: @config['region'], credentials: @config['credentials']).create_cache_cluster(config_struct).cache_cluster
229
+ rescue ::Aws::ElastiCache::Errors::InvalidParameterValue => e
230
+ if e.message.match(/security group (sg-[^\s]+)/)
231
+ bad_sg = Regexp.last_match[1]
232
+ MU.log "Removing invalid security group #{bad_sg} from Cache Cluster #{@mu_name}", MU::WARN, details: e.message
233
+ config_struct[:security_group_ids].delete(bad_sg)
234
+ retry
235
+ else
236
+ raise e
237
+ end
238
+ end
233
239
 
234
240
  wait_start_time = Time.now
235
241
  retries = 0
@@ -260,7 +266,6 @@ module MU
260
266
  # Create a subnet group for a Cache Cluster with the given config.
261
267
  def createSubnetGroup
262
268
  subnet_ids = []
263
-
264
269
  if @config["vpc"] && !@config["vpc"].empty?
265
270
  raise MuError, "Didn't find the VPC specified in #{@config["vpc"]}" unless @vpc
266
271
 
@@ -306,8 +311,8 @@ module MU
306
311
  }
307
312
 
308
313
  @config['vpc'] = {
309
- "vpc_id" => vpc_id,
310
- "subnets" => mu_subnets
314
+ "vpc_id" => vpc_id,
315
+ "subnets" => mu_subnets
311
316
  }
312
317
  using_default_vpc = true
313
318
  MU.log "Using default VPC for cache cluster #{@config['identifier']}"
@@ -346,8 +351,8 @@ module MU
346
351
 
347
352
  if @dependencies.has_key?('firewall_rule')
348
353
  @config["security_group_ids"] = []
349
- @dependencies['firewall_rule'].values.each { |sg|
350
- @config["security_group_ids"] << sg.cloud_id
354
+ @dependencies['firewall_rule'].values.each { |sg|
355
+ @config["security_group_ids"] << sg.cloud_id
351
356
  }
352
357
  end
353
358
  end
@@ -691,6 +696,10 @@ module MU
691
696
  def self.schema(config)
692
697
  toplevel_required = []
693
698
  schema = {
699
+ "create_replication_group" => {
700
+ "type" => "boolean",
701
+ "description" => "Create a replication group; will be set automatically if +engine+ is +redis+ and +node_count+ is greated than one."
702
+ },
694
703
  "ingress_rules" => {
695
704
  "items" => {
696
705
  "properties" => {
@@ -722,6 +731,32 @@ module MU
722
731
  def self.validateConfig(cache, configurator)
723
732
  ok = true
724
733
 
734
+ if !cache['vpc']
735
+ siblings = configurator.haveLitterMate?(nil, "vpcs", has_multiple: true)
736
+ if siblings.size == 1
737
+ MU.log "CacheCluster #{cache['name']} did not declare a VPC. Inserting into sibling VPC #{siblings[0]['name']}.", MU::WARN
738
+ cache["vpc"] = {
739
+ "name" => siblings[0]['name'],
740
+ "subnet_pref" => "all_private"
741
+ }
742
+ elsif MU::Cloud::AWS.hosted? and MU::Cloud::AWS.myVPCObj
743
+ cache["vpc"] = {
744
+ "id" => MU.myVPC,
745
+ "subnet_pref" => "all_private"
746
+ }
747
+ else
748
+ MU.log "CacheCluster #{cache['name']} must declare a VPC", MU::ERR
749
+ ok = false
750
+ end
751
+
752
+ # Re-insert ourselves with this modification so that our child
753
+ # resources get this VPC we just shoved in
754
+ if ok and cache['vpc']
755
+ cache.delete("#MU_VALIDATED")
756
+ return configurator.insertKitten(cache, "cache_clusters", overwrite: true)
757
+ end
758
+ end
759
+
725
760
  if cache.has_key?("parameter_group_parameters") && cache["parameter_group_family"].nil?
726
761
  MU.log "parameter_group_family must be set when setting parameter_group_parameters", MU::ERR
727
762
  ok = false
@@ -743,7 +778,6 @@ module MU
743
778
  end
744
779
  elsif cache["engine"] == "memcached"
745
780
  cache["create_replication_group"] = false
746
- cache["az_mode"] = cache["multi_az"] ? "cross-az" : "single-az"
747
781
 
748
782
  if cache["node_count"] > 20
749
783
  MU.log "#{cache['engine']} supports up to 20 nodes per cache cluster", MU::ERR
@@ -868,7 +902,7 @@ module MU
868
902
  # @param region [String]: The cloud provider's region in which to operate.
869
903
  # @param cloud_id [String]: The cloud provider's identifier for this resource.
870
904
  # @return [void]
871
- def self.terminate_replication_group(repl_group, noop: false, skipsnapshots: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil, cloud_id: nil)
905
+ def self.terminate_replication_group(repl_group, noop: false, skipsnapshots: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil, cloud_id: nil, credentials: nil)
872
906
  raise MuError, "terminate_replication_group requires a non-nil cache replication group descriptor" if repl_group.nil? || repl_group.empty?
873
907
 
874
908
  repl_group_id = repl_group.replication_group_id
@@ -908,9 +942,9 @@ module MU
908
942
  )
909
943
  end
910
944
 
911
- def self.createSnap(repl_group_id, region)
945
+ def self.createSnap(repl_group_id, region, credentials)
912
946
  MU.log "Terminating #{repl_group_id}. Final snapshot name: #{repl_group_id}-mufinal"
913
- MU::Cloud::AWS.elasticache(region: region).delete_replication_group(
947
+ MU::Cloud::AWS.elasticache(region: region, credentials: credentials).delete_replication_group(
914
948
  replication_group_id: repl_group_id,
915
949
  retain_primary_cluster: false,
916
950
  final_snapshot_identifier: "#{repl_group_id}-mufinal"