cloud-mu 3.1.6 → 3.2.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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mu-adopt +4 -12
  3. data/bin/mu-azure-tests +57 -0
  4. data/bin/mu-cleanup +2 -4
  5. data/bin/mu-configure +37 -1
  6. data/bin/mu-deploy +3 -3
  7. data/bin/mu-findstray-tests +25 -0
  8. data/bin/mu-gen-docs +2 -4
  9. data/bin/mu-run-tests +23 -10
  10. data/cloud-mu.gemspec +2 -2
  11. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  12. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  13. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  14. data/extras/generate-stock-images +1 -0
  15. data/modules/mu.rb +82 -95
  16. data/modules/mu/adoption.rb +356 -56
  17. data/modules/mu/cleanup.rb +21 -20
  18. data/modules/mu/cloud.rb +79 -1753
  19. data/modules/mu/cloud/database.rb +49 -0
  20. data/modules/mu/cloud/dnszone.rb +46 -0
  21. data/modules/mu/cloud/machine_images.rb +212 -0
  22. data/modules/mu/cloud/providers.rb +81 -0
  23. data/modules/mu/cloud/resource_base.rb +920 -0
  24. data/modules/mu/cloud/server.rb +40 -0
  25. data/modules/mu/cloud/server_pool.rb +1 -0
  26. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  27. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  28. data/modules/mu/cloud/wrappers.rb +165 -0
  29. data/modules/mu/config.rb +122 -80
  30. data/modules/mu/config/alarm.rb +2 -6
  31. data/modules/mu/config/bucket.rb +1 -1
  32. data/modules/mu/config/cache_cluster.rb +1 -1
  33. data/modules/mu/config/collection.rb +1 -1
  34. data/modules/mu/config/container_cluster.rb +2 -2
  35. data/modules/mu/config/database.rb +83 -104
  36. data/modules/mu/config/database.yml +1 -2
  37. data/modules/mu/config/dnszone.rb +1 -1
  38. data/modules/mu/config/doc_helpers.rb +4 -5
  39. data/modules/mu/config/endpoint.rb +1 -1
  40. data/modules/mu/config/firewall_rule.rb +3 -19
  41. data/modules/mu/config/folder.rb +1 -1
  42. data/modules/mu/config/function.rb +1 -1
  43. data/modules/mu/config/group.rb +1 -1
  44. data/modules/mu/config/habitat.rb +1 -1
  45. data/modules/mu/config/loadbalancer.rb +57 -11
  46. data/modules/mu/config/log.rb +1 -1
  47. data/modules/mu/config/msg_queue.rb +1 -1
  48. data/modules/mu/config/nosqldb.rb +1 -1
  49. data/modules/mu/config/notifier.rb +1 -1
  50. data/modules/mu/config/ref.rb +30 -4
  51. data/modules/mu/config/role.rb +1 -1
  52. data/modules/mu/config/schema_helpers.rb +30 -34
  53. data/modules/mu/config/search_domain.rb +1 -1
  54. data/modules/mu/config/server.rb +4 -12
  55. data/modules/mu/config/server_pool.rb +3 -7
  56. data/modules/mu/config/storage_pool.rb +1 -1
  57. data/modules/mu/config/tail.rb +10 -0
  58. data/modules/mu/config/user.rb +1 -1
  59. data/modules/mu/config/vpc.rb +12 -17
  60. data/modules/mu/defaults/AWS.yaml +32 -32
  61. data/modules/mu/defaults/Azure.yaml +1 -0
  62. data/modules/mu/defaults/Google.yaml +1 -0
  63. data/modules/mu/deploy.rb +16 -15
  64. data/modules/mu/groomer.rb +15 -0
  65. data/modules/mu/groomers/chef.rb +3 -0
  66. data/modules/mu/logger.rb +120 -144
  67. data/modules/mu/master.rb +1 -1
  68. data/modules/mu/mommacat.rb +54 -25
  69. data/modules/mu/mommacat/daemon.rb +10 -7
  70. data/modules/mu/mommacat/naming.rb +82 -3
  71. data/modules/mu/mommacat/search.rb +47 -15
  72. data/modules/mu/mommacat/storage.rb +72 -41
  73. data/modules/mu/{clouds → providers}/README.md +1 -1
  74. data/modules/mu/{clouds → providers}/aws.rb +114 -47
  75. data/modules/mu/{clouds → providers}/aws/alarm.rb +1 -1
  76. data/modules/mu/{clouds → providers}/aws/bucket.rb +2 -2
  77. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +10 -46
  78. data/modules/mu/{clouds → providers}/aws/collection.rb +3 -3
  79. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +15 -33
  80. data/modules/mu/providers/aws/database.rb +1744 -0
  81. data/modules/mu/{clouds → providers}/aws/dnszone.rb +2 -5
  82. data/modules/mu/{clouds → providers}/aws/endpoint.rb +2 -11
  83. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +33 -29
  84. data/modules/mu/{clouds → providers}/aws/folder.rb +0 -0
  85. data/modules/mu/{clouds → providers}/aws/function.rb +2 -10
  86. data/modules/mu/{clouds → providers}/aws/group.rb +9 -13
  87. data/modules/mu/{clouds → providers}/aws/habitat.rb +1 -1
  88. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +41 -33
  89. data/modules/mu/{clouds → providers}/aws/log.rb +2 -2
  90. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +2 -8
  91. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +0 -0
  92. data/modules/mu/{clouds → providers}/aws/notifier.rb +0 -0
  93. data/modules/mu/{clouds → providers}/aws/role.rb +7 -7
  94. data/modules/mu/{clouds → providers}/aws/search_domain.rb +8 -13
  95. data/modules/mu/{clouds → providers}/aws/server.rb +55 -90
  96. data/modules/mu/{clouds → providers}/aws/server_pool.rb +10 -33
  97. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +19 -36
  98. data/modules/mu/{clouds → providers}/aws/user.rb +8 -12
  99. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  100. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  101. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  102. data/modules/mu/{clouds → providers}/aws/vpc.rb +135 -70
  103. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  104. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  105. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  106. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  107. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  108. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  109. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  110. data/modules/mu/{clouds → providers}/azure/server.rb +30 -23
  111. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  112. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  113. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  114. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  115. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  116. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  117. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  118. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  119. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  120. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  121. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  122. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  123. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  124. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  125. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  126. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  127. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  128. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  129. data/modules/mu/{clouds → providers}/google.rb +14 -6
  130. data/modules/mu/{clouds → providers}/google/bucket.rb +1 -1
  131. data/modules/mu/{clouds → providers}/google/container_cluster.rb +28 -13
  132. data/modules/mu/{clouds → providers}/google/database.rb +1 -8
  133. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +2 -2
  134. data/modules/mu/{clouds → providers}/google/folder.rb +4 -8
  135. data/modules/mu/{clouds → providers}/google/function.rb +3 -3
  136. data/modules/mu/{clouds → providers}/google/group.rb +8 -16
  137. data/modules/mu/{clouds → providers}/google/habitat.rb +3 -7
  138. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +1 -1
  139. data/modules/mu/{clouds → providers}/google/role.rb +42 -34
  140. data/modules/mu/{clouds → providers}/google/server.rb +25 -10
  141. data/modules/mu/{clouds → providers}/google/server_pool.rb +10 -10
  142. data/modules/mu/{clouds → providers}/google/user.rb +31 -21
  143. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  144. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  145. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  146. data/modules/mu/{clouds → providers}/google/vpc.rb +37 -2
  147. data/modules/tests/centos6.yaml +11 -0
  148. data/modules/tests/centos7.yaml +11 -0
  149. data/modules/tests/centos8.yaml +12 -0
  150. data/modules/tests/rds.yaml +108 -0
  151. data/modules/tests/regrooms/rds.yaml +123 -0
  152. data/spec/mu/clouds/azure_spec.rb +2 -2
  153. metadata +108 -89
  154. data/modules/mu/clouds/aws/database.rb +0 -1974
@@ -602,7 +602,7 @@ module MU
602
602
  return
603
603
  end
604
604
  if ssh_key_name.nil? or ssh_key_name.empty?
605
- MU.log "Failed to extract ssh_key_name for #{ssh_key_name.mu_name} in addHostToSSHConfig", MU::ERR
605
+ MU.log "Failed to extract ssh_key_name for #{server.mu_name} in addHostToSSHConfig", MU::ERR
606
606
  return
607
607
  end
608
608
 
@@ -167,6 +167,7 @@ module MU
167
167
  @need_deploy_flush = false
168
168
  @node_cert_semaphore = Mutex.new
169
169
  @deployment = deployment_data
170
+
170
171
  @deployment['mu_public_ip'] = MU.mu_public_ip
171
172
  @private_key = nil
172
173
  @public_key = nil
@@ -182,6 +183,7 @@ module MU
182
183
  @appname ||= @original_config['name'] if @original_config
183
184
  @timestamp = timestamp
184
185
  @environment = environment
186
+ @original_config['environment'] ||= @environment if @original_config
185
187
 
186
188
  if set_context_to_me
187
189
  MU::MommaCat.setThreadContext(self)
@@ -253,8 +255,7 @@ module MU
253
255
  seen << resource['credentials']
254
256
  else
255
257
  cloudconst = @original_config['cloud'] ? @original_config['cloud'] : MU::Config.defaultCloud
256
- Object.const_get("MU").const_get("Cloud").const_get(cloudconst)
257
- seen << cloudclass.credConfig(name_only: true)
258
+ seen << MU::Cloud.cloudClass(cloudconst).credConfig(name_only: true)
258
259
  end
259
260
  }
260
261
  end
@@ -289,11 +290,10 @@ module MU
289
290
  habitats << hab_ref.id
290
291
  end
291
292
  elsif resource['cloud']
292
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud'])
293
293
  # XXX this should be a general method implemented by each cloud
294
294
  # provider
295
295
  if resource['cloud'] == "Google"
296
- habitats << cloudclass.defaultProject(resource['credentials'])
296
+ habitats << MU::Cloud.cloudClass(resource['cloud']).defaultProject(resource['credentials'])
297
297
  end
298
298
  end
299
299
  }
@@ -317,13 +317,11 @@ module MU
317
317
  if @original_config[type]
318
318
  @original_config[type].each { |resource|
319
319
  if resource['cloud']
320
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud'])
321
- resclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud']).const_get(res_type.to_s)
322
- if resclass.isGlobal?
320
+ if MU::Cloud.resourceClass(resource['cloud'], res_type).isGlobal?
323
321
  # XXX why was I doing this, urgh
324
322
  next
325
323
  elsif !resource['region']
326
- regions << cloudclass.myRegion
324
+ regions << MU::Cloud.cloudClass(resource['cloud']).myRegion(resource['credentials'])
327
325
  end
328
326
  end
329
327
  if resource['region']
@@ -401,7 +399,7 @@ module MU
401
399
  # @param type [String]:
402
400
  # @param name [String]:
403
401
  # @param object [MU::Cloud]:
404
- def addKitten(type, name, object)
402
+ def addKitten(type, name, object, do_notify: false)
405
403
  if !type or !name or !object or !object.mu_name
406
404
  raise MuError, "Nil arguments to addKitten are not allowed (got type: #{type}, name: #{name}, and '#{object}' to add)"
407
405
  end
@@ -409,7 +407,7 @@ module MU
409
407
  _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
410
408
  object.intoDeploy(self)
411
409
 
412
- @kitten_semaphore.synchronize {
410
+ add_block = Proc.new {
413
411
  @kittens[type] ||= {}
414
412
  @kittens[type][object.habitat] ||= {}
415
413
  if attrs[:has_multiples]
@@ -418,7 +416,20 @@ module MU
418
416
  else
419
417
  @kittens[type][object.habitat][name] = object
420
418
  end
419
+ if do_notify
420
+ notify(type, name, object.notify, triggering_node: object, delayed_save: true)
421
+ end
421
422
  }
423
+
424
+ begin
425
+ @kitten_semaphore.synchronize {
426
+ add_block.call()
427
+ }
428
+ rescue ThreadError => e
429
+ # already locked by a parent call to this method, so this should be safe
430
+ raise e if !e.message.match(/recursive locking/)
431
+ add_block.call()
432
+ end
422
433
  end
423
434
 
424
435
  # Encrypt a string with the deployment's public key.
@@ -536,10 +547,9 @@ module MU
536
547
  # @param remove [Boolean]: Remove this resource from the deploy structure, instead of adding it.
537
548
  # @return [void]
538
549
  def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
539
- return if @no_artifacts
540
550
 
541
551
  begin
542
- MU::MommaCat.lock("deployment-notification")
552
+ MU::MommaCat.lock("deployment-notification", deploy_id: @deploy_id) if !@no_artifacts
543
553
 
544
554
  if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
545
555
  loadDeploy(true) # make sure we're saving the latest and greatest
@@ -578,7 +588,7 @@ module MU
578
588
  @deployment[type][key] = data
579
589
  MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
580
590
  end
581
- save!(key) if !delayed_save
591
+ save!(key) if !delayed_save and !@no_artifacts
582
592
  else
583
593
  have_deploy = true
584
594
  if @deployment[type].nil? or @deployment[type][key].nil?
@@ -603,10 +613,10 @@ module MU
603
613
  end
604
614
  }
605
615
  end
606
- save! if !delayed_save
616
+ save! if !delayed_save and !@no_artifacts
607
617
  end
608
618
  ensure
609
- MU::MommaCat.unlock("deployment-notification")
619
+ MU::MommaCat.unlock("deployment-notification", deploy_id: @deploy_id) if !@no_artifacts
610
620
  end
611
621
  end
612
622
 
@@ -614,18 +624,37 @@ module MU
614
624
  # @param subject [String]: The subject line of the message.
615
625
  # @param msg [String]: The message body.
616
626
  # @return [void]
617
- def sendAdminSlack(subject, msg: "")
618
- if $MU_CFG['slack'] and $MU_CFG['slack']['webhook'] and
619
- (!$MU_CFG['slack']['skip_environments'] or !$MU_CFG['slack']['skip_environments'].any?{ |s| s.casecmp(MU.environment)==0 })
627
+ def sendAdminSlack(subject, msg: "", scrub_mu_isms: true, snippets: [], noop: false)
628
+ if MU.muCfg['slack'] and MU.muCfg['slack']['webhook'] and
629
+ (!MU.muCfg['slack']['skip_environments'] or !MU.muCfg['slack']['skip_environments'].any?{ |s| s.casecmp(MU.environment)==0 })
620
630
  require 'slack-notifier'
621
- slack = Slack::Notifier.new $MU_CFG['slack']['webhook']
631
+ slackargs = nil
632
+ keyword_args = { channel: MU.muCfg['slack']['channel'] }
633
+ begin
634
+ slack = Slack::Notifier.new MU.muCfg['slack']['webhook']
635
+ prefix = scrub_mu_isms ? subject : "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}"
636
+
637
+ text = if msg and !msg.empty?
638
+ "#{prefix}:\n\n```#{msg}```"
639
+ else
640
+ prefix
641
+ end
622
642
 
623
- if msg and !msg.empty?
624
- slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}:\n\n```#{msg}\n```", channel: $MU_CFG['slack']['channel']
625
- else
626
- slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}", channel: $MU_CFG['slack']['channel']
643
+ if snippets and snippets.size > 0
644
+ keyword_args[:attachments] = snippets
645
+ end
646
+
647
+ if !noop
648
+ slack.ping(text, **keyword_args)
649
+ else
650
+ MU.log "Would send to #{MU.muCfg['slack']['channel']}", MU::NOTICE, details: [ text, keyword_args ]
651
+ end
652
+ rescue Slack::Notifier::APIError => e
653
+ MU.log "Failed to send message to slack: #{e.message}", MU::ERR, details: keyword_args
654
+ return false
627
655
  end
628
656
  end
657
+ true
629
658
  end
630
659
 
631
660
  # Send an email notification to a deployment's administrators.
@@ -754,7 +783,7 @@ MAIL_HEAD_END
754
783
  end
755
784
 
756
785
  siblings = findLitterMate(type: "server", return_all: true)
757
- return if siblings.nil? or siblings.empty?
786
+ return if siblings.nil? or (siblings.respond_to?(:empty?) and siblings.empty?)
758
787
 
759
788
  update_servers = []
760
789
  siblings.each_pair { |mu_name, node|
@@ -838,7 +867,7 @@ MAIL_HEAD_END
838
867
  end
839
868
 
840
869
  if resource and resource.config and resource.config['cloud']
841
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource.config['cloud'])
870
+ cloudclass = MU::Cloud.cloudClass(resource.config['cloud'])
842
871
 
843
872
  cloudclass.writeDeploySecret(@deploy_id, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
844
873
  cloudclass.writeDeploySecret(@deploy_id, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])
@@ -33,7 +33,7 @@ module MU
33
33
  my_key = OpenSSL::PKey::RSA.new(@private_key)
34
34
 
35
35
  begin
36
- if my_key.private_decrypt(ciphertext).force_encoding("UTF-8") == @deploy_secret.force_encoding("UTF-8")
36
+ if my_key.private_decrypt(ciphertext).force_encoding("UTF-8").chomp == @deploy_secret.force_encoding("UTF-8").chomp
37
37
  MU.log "Matched ciphertext for #{MU.deploy_id}", MU::INFO
38
38
  return true
39
39
  else
@@ -165,11 +165,11 @@ module MU
165
165
  if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exist?(deploy_dir+"/.cleanup."+cloud_id) and !File.exist?(deploy_dir+"/.cleanup")
166
166
  MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
167
167
  sendAdminSlack("Grooming FAILED for `#{kitten.mu_name}` with `#{e.message}` :crying_cat_face:", msg: e.backtrace.join("\n"))
168
- sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
169
- msg: e.inspect,
170
- data: e.backtrace,
171
- debug: true
172
- )
168
+ sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
169
+ msg: e.inspect,
170
+ data: e.backtrace,
171
+ debug: true
172
+ )
173
173
  raise e if reraise_fail
174
174
  else
175
175
  MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
@@ -342,6 +342,8 @@ module MU
342
342
  return $?.exitstatus
343
343
  end
344
344
 
345
+ @@notified_on_pid = {}
346
+
345
347
  # Return true if the Momma Cat daemon appears to be running
346
348
  # @return [Boolean]
347
349
  def self.status
@@ -352,7 +354,8 @@ module MU
352
354
  pid = File.read(daemonPidFile).chomp.to_i
353
355
  begin
354
356
  Process.getpgid(pid)
355
- MU.log "Momma Cat running with pid #{pid.to_s}"
357
+ MU.log "Momma Cat running with pid #{pid.to_s}", (@@notified_on_pid[pid] ? MU::DEBUG : MU::INFO) # shush
358
+ @@notified_on_pid[pid] = true
356
359
  return true
357
360
  rescue Errno::ESRCH
358
361
  end
@@ -19,6 +19,16 @@ module MU
19
19
  # the normal synchronous deploy sequence invoked by *mu-deploy*.
20
20
  class MommaCat
21
21
 
22
+ # Lookup table to translate the word "habitat" back to its
23
+ # provider-specific jargon
24
+ HABITAT_SYNONYMS = {
25
+ "AWS" => "account",
26
+ "CloudFormation" => "account",
27
+ "Google" => "project",
28
+ "Azure" => "subscription",
29
+ "VMWare" => "sddc"
30
+ }
31
+
22
32
  # Given a cloud provider's native descriptor for a resource, make some
23
33
  # reasonable guesses about what the thing's name should be.
24
34
  def self.guessName(desc, resourceclass, cloud_id: nil, tag_value: nil)
@@ -47,6 +57,74 @@ module MU
47
57
 
48
58
  end
49
59
 
60
+ # Given a piece of a BoK resource descriptor Hash, come up with shorthand
61
+ # strings to give it a name for human readers. If nothing reasonable can be
62
+ # extracted, returns nil.
63
+ # @param obj [Hash]
64
+ # @param array_of [String]
65
+ # @param habitat_translate [String]
66
+ # @return [Array<String,nil>]
67
+ def self.getChunkName(obj, array_of = nil, habitat_translate: nil)
68
+ return [nil, nil] if obj.nil?
69
+ if [String, Integer, Boolean].include?(obj.class)
70
+ return [obj, nil]
71
+ end
72
+ obj_type = array_of || obj['type']
73
+ obj_name = obj['name'] || obj['id'] || obj['mu_name'] || obj['cloud_id']
74
+
75
+ name_string = if obj_name
76
+ if obj_type
77
+ "#{obj_type}[#{obj_name}]"
78
+ else
79
+ obj_name.dup
80
+ end
81
+ else
82
+ found_it = nil
83
+ using = nil
84
+ ["entity", "role"].each { |subtype|
85
+ if obj[subtype] and obj[subtype].is_a?(Hash)
86
+ found_it = if obj[subtype]["id"]
87
+ obj[subtype]['id'].dup
88
+ elsif obj[subtype]["type"] and obj[subtype]["name"]
89
+ "#{obj[subtype]['type']}[#{obj[subtype]['name']}]"
90
+ end
91
+ break
92
+ end
93
+ }
94
+ found_it
95
+ end
96
+ if name_string
97
+ name_string.gsub!(/\[.+?\](\[.+?\]$)/, '\1')
98
+ if habitat_translate and HABITAT_SYNONYMS[habitat_translate]
99
+ name_string.sub!(/^habitats?\[(.+?)\]/i, HABITAT_SYNONYMS[habitat_translate]+'[\1]')
100
+ end
101
+ end
102
+
103
+ location_list = []
104
+
105
+ location = if obj['project']
106
+ obj['project']
107
+ elsif obj['habitat'] and (obj['habitat']['id'] or obj['habitat']['name'])
108
+ obj['habitat']['name'] || obj['habitat']['id']
109
+ else
110
+ hab_str = nil
111
+ ['projects', 'habitats'].each { |key|
112
+
113
+ if obj[key] and obj[key].is_a?(Array)
114
+ location_list = obj[key].sort.map { |p|
115
+ (p["name"] || p["id"]).gsub(/^.*?[^\/]+\/([^\/]+)$/, '\1')
116
+ }
117
+ hab_str = location_list.join(", ")
118
+ name_string.gsub!(/^.*?[^\/]+\/([^\/]+)$/, '\1') if name_string
119
+ break
120
+ end
121
+ }
122
+ hab_str
123
+ end
124
+
125
+ [name_string, location, location_list]
126
+ end
127
+
50
128
  # Generate a three-character string which can be used to unique-ify the
51
129
  # names of resources which might potentially collide, e.g. Windows local
52
130
  # hostnames, Amazon Elastic Load Balancers, or server pool instances.
@@ -218,17 +296,18 @@ module MU
218
296
  # SSH config entries, etc.
219
297
  # @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
220
298
  # @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
221
- def self.nameKitten(server, sync_wait: false)
299
+ def self.nameKitten(server, sync_wait: false, no_dns: false)
222
300
  node, config, _deploydata = server.describe
223
301
 
224
302
  mu_zone = nil
225
303
  # XXX GCP!
226
- if MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud?
304
+ if !no_dns and MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud?
227
305
  zones = MU::Cloud::DNSZone.find(cloud_id: "platform-mu")
228
306
  mu_zone = zones.values.first if !zones.nil?
229
307
  end
308
+
230
309
  if !mu_zone.nil?
231
- MU::Cloud::DNSZone.genericMuDNSEntry(name: node, target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
310
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: node.gsub(/[^a-z0-9!"\#$%&'\(\)\*\+,\-\/:;<=>\?@\[\]\^_`{\|}~\.]/, '-').gsub(/--|^-/, ''), target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
232
311
  else
233
312
  MU::Master.addInstanceToEtcHosts(server.canonicalIP, node)
234
313
  end
@@ -60,7 +60,7 @@ module MU
60
60
  )
61
61
  _shortclass, _cfg_name, type, _classname, _attrs = MU::Cloud.getResourceNames(type, true)
62
62
 
63
- cloudclass = MU::Cloud.assertSupportedCloud(cloud)
63
+ cloudclass = MU::Cloud.cloudClass(cloud)
64
64
  return nil if cloudclass.virtual?
65
65
 
66
66
  if (tag_key and !tag_value) or (!tag_key and tag_value)
@@ -107,6 +107,7 @@ module MU
107
107
  matches = []
108
108
 
109
109
  credlist.each { |creds|
110
+ # next if region and region.is_a?(Array) and !region.empty? and !region.include?(r)
110
111
  cloud_descs = search_cloud_provider(type, cloud, habitats, region, cloud_id: cloud_id, tag_key: tag_key, tag_value: tag_value, credentials: creds, flags: flags)
111
112
 
112
113
  cloud_descs.each_pair.each { |p, regions|
@@ -127,6 +128,8 @@ module MU
127
128
  matches
128
129
  end
129
130
 
131
+ @object_load_fails = false
132
+
130
133
  # Return the resource object of another member of this deployment
131
134
  # @param type [String,Symbol]: The type of resource
132
135
  # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field
@@ -135,7 +138,7 @@ module MU
135
138
  # @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
136
139
  # @param return_all [Boolean]: Return a Hash of matching objects indexed by their mu_name, instead of a single match. Only valid for resource types where has_multiples is true.
137
140
  # @return [MU::Cloud]
138
- def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, **flags)
141
+ def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, debug: false, **flags)
139
142
  _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
140
143
 
141
144
  # If we specified a habitat, which we may also have done by its shorthand
@@ -159,17 +162,33 @@ module MU
159
162
  }
160
163
 
161
164
  @kitten_semaphore.synchronize {
162
- return nil if !@kittens.has_key?(type)
163
- matches = []
164
165
 
166
+ if !@kittens.has_key?(type)
167
+ return nil if !@original_config or @original_config[type].nil? or @original_config[type].empty?
168
+ begin
169
+ loadObjects(false)
170
+ rescue ThreadError => e
171
+ if e.message !~ /deadlock/
172
+ raise e
173
+ end
174
+ end
175
+ if @object_load_fails or !@kittens[type]
176
+ MU.log "#{@deploy_id}'s original config has #{@original_config[type].size == 1 ? "a" : @original_config[type].size.to_s} #{type}, but loadObjects could not populate anything from deployment metadata", MU::ERR if !@object_load_fails
177
+ @object_load_fails = true
178
+ return nil
179
+ end
180
+ end
181
+ matches = {}
165
182
  @kittens[type].each { |habitat_group, sib_classes|
166
183
  next if habitat and habitat_group and habitat_group != habitat
167
184
  sib_classes.each_pair { |sib_class, cloud_objs|
185
+
168
186
  if attrs[:has_multiples]
169
187
  next if !name.nil? and name != sib_class or cloud_objs.empty?
170
188
  if !name.nil?
171
189
  if return_all
172
- return cloud_objs.dup
190
+ matches.merge!(cloud_objs.clone)
191
+ next
173
192
  elsif cloud_objs.size == 1 and does_match.call(cloud_objs.values.first)
174
193
  return cloud_objs.values.first
175
194
  end
@@ -177,19 +196,24 @@ module MU
177
196
 
178
197
  cloud_objs.each_value { |obj|
179
198
  if does_match.call(obj)
180
- return (return_all ? cloud_objs.clone : obj.clone)
199
+ if return_all
200
+ matches.merge!(cloud_objs.clone)
201
+ else
202
+ return obj.clone
203
+ end
181
204
  end
182
205
  }
183
- # has_multiples is false
206
+ # has_multiples is false, "cloud_objs" is actually a singular object
184
207
  elsif (name.nil? and does_match.call(cloud_objs)) or [sib_class, cloud_objs.virtual_name(name)].include?(name.to_s)
185
- matches << cloud_objs.clone
208
+ matches[cloud_objs.config['name']] = cloud_objs.clone
186
209
  end
187
210
  }
188
211
  }
189
212
 
190
- return matches.first if matches.size == 1
213
+ return matches if return_all and matches.size >= 1
214
+
215
+ return matches.values.first if matches.size == 1
191
216
 
192
- return matches if return_all and matches.size > 1
193
217
  }
194
218
 
195
219
  return nil
@@ -213,7 +237,7 @@ module MU
213
237
  end
214
238
 
215
239
  def self.generate_dummy_object(type, cloud, name, mu_name, cloud_id, desc, region, habitat, tag_value, calling_deploy, credentials)
216
- resourceclass = MU::Cloud.loadCloudType(cloud, type)
240
+ resourceclass = MU::Cloud.resourceClass(cloud, type)
217
241
 
218
242
  use_name = if (name.nil? or name.empty?)
219
243
  if !mu_name.nil?
@@ -269,15 +293,23 @@ module MU
269
293
  private_class_method :generate_dummy_object
270
294
 
271
295
  def self.search_cloud_provider(type, cloud, habitats, region, cloud_id: nil, tag_key: nil, tag_value: nil, credentials: nil, flags: nil)
272
- cloudclass = MU::Cloud.assertSupportedCloud(cloud)
273
- resourceclass = MU::Cloud.loadCloudType(cloud, type)
296
+ cloudclass = MU::Cloud.cloudClass(cloud)
297
+ resourceclass = MU::Cloud.resourceClass(cloud, type)
274
298
 
275
299
  # Decide what regions we'll search, if applicable for this resource
276
300
  # type.
277
301
  regions = if resourceclass.isGlobal?
278
302
  [nil]
279
303
  else
280
- region ? [region] : cloudclass.listRegions(credentials: credentials)
304
+ if region
305
+ if region.is_a?(Array) and !region.empty?
306
+ region
307
+ else
308
+ [region]
309
+ end
310
+ else
311
+ cloudclass.listRegions(credentials: credentials)
312
+ end
281
313
  end
282
314
 
283
315
  # Decide what habitats (accounts/projects/subscriptions) we'll
@@ -288,7 +320,7 @@ module MU
288
320
  habitats << nil
289
321
  end
290
322
  if resourceclass.canLiveIn.include?(:Habitat)
291
- habitats.concat(cloudclass.listHabitats(credentials))
323
+ habitats.concat(cloudclass.listHabitats(credentials, use_cache: false))
292
324
  end
293
325
  end
294
326
  habitats << nil if habitats.empty?