cloud-mu 3.0.0beta → 3.0.0

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