cloud-mu 3.1.5 → 3.3.2

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  4. data/ansible/roles/mu-windows/files/config.xml +76 -0
  5. data/ansible/roles/mu-windows/tasks/main.yml +16 -0
  6. data/bin/mu-adopt +16 -12
  7. data/bin/mu-azure-tests +57 -0
  8. data/bin/mu-cleanup +2 -4
  9. data/bin/mu-configure +52 -0
  10. data/bin/mu-deploy +3 -3
  11. data/bin/mu-findstray-tests +25 -0
  12. data/bin/mu-gen-docs +2 -4
  13. data/bin/mu-load-config.rb +2 -1
  14. data/bin/mu-node-manage +15 -16
  15. data/bin/mu-run-tests +37 -12
  16. data/cloud-mu.gemspec +3 -3
  17. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  18. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  19. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  20. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  21. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  22. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  23. data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
  24. data/extras/clean-stock-amis +25 -19
  25. data/extras/generate-stock-images +1 -0
  26. data/extras/image-generators/AWS/win2k12.yaml +2 -0
  27. data/extras/image-generators/AWS/win2k16.yaml +2 -0
  28. data/extras/image-generators/AWS/win2k19.yaml +2 -0
  29. data/modules/mommacat.ru +1 -1
  30. data/modules/mu.rb +86 -98
  31. data/modules/mu/adoption.rb +373 -58
  32. data/modules/mu/cleanup.rb +214 -303
  33. data/modules/mu/cloud.rb +128 -1733
  34. data/modules/mu/cloud/database.rb +49 -0
  35. data/modules/mu/cloud/dnszone.rb +44 -0
  36. data/modules/mu/cloud/machine_images.rb +212 -0
  37. data/modules/mu/cloud/providers.rb +81 -0
  38. data/modules/mu/cloud/resource_base.rb +929 -0
  39. data/modules/mu/cloud/server.rb +40 -0
  40. data/modules/mu/cloud/server_pool.rb +1 -0
  41. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  42. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  43. data/modules/mu/cloud/wrappers.rb +169 -0
  44. data/modules/mu/config.rb +123 -81
  45. data/modules/mu/config/alarm.rb +2 -6
  46. data/modules/mu/config/bucket.rb +32 -3
  47. data/modules/mu/config/cache_cluster.rb +2 -2
  48. data/modules/mu/config/cdn.rb +100 -0
  49. data/modules/mu/config/collection.rb +1 -1
  50. data/modules/mu/config/container_cluster.rb +7 -2
  51. data/modules/mu/config/database.rb +84 -105
  52. data/modules/mu/config/database.yml +1 -2
  53. data/modules/mu/config/dnszone.rb +5 -4
  54. data/modules/mu/config/doc_helpers.rb +5 -6
  55. data/modules/mu/config/endpoint.rb +2 -1
  56. data/modules/mu/config/firewall_rule.rb +3 -19
  57. data/modules/mu/config/folder.rb +1 -1
  58. data/modules/mu/config/function.rb +17 -8
  59. data/modules/mu/config/group.rb +1 -1
  60. data/modules/mu/config/habitat.rb +1 -1
  61. data/modules/mu/config/job.rb +89 -0
  62. data/modules/mu/config/loadbalancer.rb +57 -11
  63. data/modules/mu/config/log.rb +1 -1
  64. data/modules/mu/config/msg_queue.rb +1 -1
  65. data/modules/mu/config/nosqldb.rb +1 -1
  66. data/modules/mu/config/notifier.rb +8 -19
  67. data/modules/mu/config/ref.rb +92 -14
  68. data/modules/mu/config/role.rb +1 -1
  69. data/modules/mu/config/schema_helpers.rb +38 -37
  70. data/modules/mu/config/search_domain.rb +1 -1
  71. data/modules/mu/config/server.rb +12 -13
  72. data/modules/mu/config/server_pool.rb +3 -7
  73. data/modules/mu/config/storage_pool.rb +1 -1
  74. data/modules/mu/config/tail.rb +11 -0
  75. data/modules/mu/config/user.rb +1 -1
  76. data/modules/mu/config/vpc.rb +27 -23
  77. data/modules/mu/config/vpc.yml +0 -1
  78. data/modules/mu/defaults/AWS.yaml +90 -90
  79. data/modules/mu/defaults/Azure.yaml +1 -0
  80. data/modules/mu/defaults/Google.yaml +1 -0
  81. data/modules/mu/deploy.rb +34 -20
  82. data/modules/mu/groomer.rb +16 -1
  83. data/modules/mu/groomers/ansible.rb +69 -4
  84. data/modules/mu/groomers/chef.rb +51 -4
  85. data/modules/mu/logger.rb +120 -144
  86. data/modules/mu/master.rb +97 -4
  87. data/modules/mu/mommacat.rb +160 -874
  88. data/modules/mu/mommacat/daemon.rb +23 -14
  89. data/modules/mu/mommacat/naming.rb +110 -3
  90. data/modules/mu/mommacat/search.rb +497 -0
  91. data/modules/mu/mommacat/storage.rb +252 -194
  92. data/modules/mu/{clouds → providers}/README.md +1 -1
  93. data/modules/mu/{clouds → providers}/aws.rb +258 -57
  94. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  95. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  96. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  99. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +95 -84
  100. data/modules/mu/providers/aws/database.rb +1744 -0
  101. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  102. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  103. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  104. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  105. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  106. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  107. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  108. data/modules/mu/providers/aws/job.rb +466 -0
  109. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  110. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  111. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  112. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  113. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  114. data/modules/mu/{clouds → providers}/aws/role.rb +76 -48
  115. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  116. data/modules/mu/{clouds → providers}/aws/server.rb +66 -98
  117. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  118. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  119. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  120. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  121. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  122. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  123. data/modules/mu/{clouds → providers}/aws/vpc.rb +143 -74
  124. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  126. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  127. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  128. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  129. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  130. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  132. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  133. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  134. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  135. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  136. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  137. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  138. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  142. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  143. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  144. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  145. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  146. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  147. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  148. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  149. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  150. data/modules/mu/{clouds → providers}/google.rb +29 -6
  151. data/modules/mu/{clouds → providers}/google/bucket.rb +4 -4
  152. data/modules/mu/{clouds → providers}/google/container_cluster.rb +38 -20
  153. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  154. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  155. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  156. data/modules/mu/{clouds → providers}/google/function.rb +6 -6
  157. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  158. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  159. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  160. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  161. data/modules/mu/{clouds → providers}/google/server.rb +41 -24
  162. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  163. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  164. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  165. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  166. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  167. data/modules/mu/{clouds → providers}/google/vpc.rb +45 -14
  168. data/modules/tests/aws-jobs-functions.yaml +46 -0
  169. data/modules/tests/centos6.yaml +15 -0
  170. data/modules/tests/centos7.yaml +15 -0
  171. data/modules/tests/centos8.yaml +12 -0
  172. data/modules/tests/ecs.yaml +2 -2
  173. data/modules/tests/eks.yaml +1 -1
  174. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  175. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  176. data/modules/tests/microservice_app.yaml +288 -0
  177. data/modules/tests/rds.yaml +108 -0
  178. data/modules/tests/regrooms/rds.yaml +123 -0
  179. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  180. data/modules/tests/super_complex_bok.yml +2 -2
  181. data/modules/tests/super_simple_bok.yml +3 -5
  182. data/spec/mu/clouds/azure_spec.rb +2 -2
  183. metadata +122 -92
  184. data/modules/mu/clouds/aws/database.rb +0 -1974
  185. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -386,6 +386,7 @@ module MU
386
386
  best = nil
387
387
  best_version = nil
388
388
  paths.uniq.each { |path|
389
+ path.sub!(/^~/, MY_HOME)
389
390
  if File.exist?(path+"/kubectl")
390
391
  version = %x{#{path}/kubectl version --short --client}.chomp.sub(/.*Client version:\s+v/i, '')
391
392
  next if !$?.success?
@@ -546,7 +547,7 @@ module MU
546
547
  rescue Errno::ECONNRESET, Errno::ECONNREFUSED
547
548
  end
548
549
  if response != "ok"
549
- MU.log "Error adding #{public_ip} to /etc/hosts via MommaCat request", MU::ERR
550
+ MU.log "Unable to add #{public_ip} to /etc/hosts via MommaCat request", MU::WARN
550
551
  end
551
552
  return
552
553
  end
@@ -601,7 +602,7 @@ module MU
601
602
  return
602
603
  end
603
604
  if ssh_key_name.nil? or ssh_key_name.empty?
604
- 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
605
606
  return
606
607
  end
607
608
 
@@ -709,6 +710,77 @@ module MU
709
710
  end
710
711
  end
711
712
 
713
+ # Evict ssh keys associated with a particular deploy from our ssh config
714
+ # and key directory.
715
+ # @param deploy_id [String]
716
+ # @param noop [Boolean]
717
+ def self.purgeDeployFromSSH(deploy_id, noop: false)
718
+ myhome = Etc.getpwuid(Process.uid).dir
719
+ sshdir = "#{myhome}/.ssh"
720
+ sshconf = "#{sshdir}/config"
721
+ ssharchive = "#{sshdir}/archive"
722
+
723
+ Dir.mkdir(sshdir, 0700) if !Dir.exist?(sshdir) and !noop
724
+ Dir.mkdir(ssharchive, 0700) if !Dir.exist?(ssharchive) and !noop
725
+
726
+ keyname = "deploy-#{deploy_id}"
727
+ if File.exist?("#{sshdir}/#{keyname}")
728
+ MU.log "Moving #{sshdir}/#{keyname} to #{ssharchive}/#{keyname}"
729
+ if !noop
730
+ File.rename("#{sshdir}/#{keyname}", "#{ssharchive}/#{keyname}")
731
+ end
732
+ end
733
+ if File.exist?(sshconf) and File.open(sshconf).read.match(/\/deploy\-#{deploy_id}$/)
734
+ MU.log "Expunging #{deploy_id} from #{sshconf}"
735
+ if !noop
736
+ FileUtils.copy(sshconf, "#{ssharchive}/config-#{deploy_id}")
737
+ File.open(sshconf, File::CREAT|File::RDWR, 0600) { |f|
738
+ f.flock(File::LOCK_EX)
739
+ newlines = Array.new
740
+ delete_block = false
741
+ f.readlines.each { |line|
742
+ if line.match(/^Host #{deploy_id}\-/)
743
+ delete_block = true
744
+ elsif line.match(/^Host /)
745
+ delete_block = false
746
+ end
747
+ newlines << line if !delete_block
748
+ }
749
+ f.rewind
750
+ f.truncate(0)
751
+ f.puts(newlines)
752
+ f.flush
753
+ f.flock(File::LOCK_UN)
754
+ }
755
+ end
756
+ end
757
+ # XXX refactor with above? They're similar, ish.
758
+ hostsfile = "/etc/hosts"
759
+ if File.open(hostsfile).read.match(/ #{deploy_id}\-/)
760
+ if Process.uid == 0
761
+ MU.log "Expunging traces of #{deploy_id} from #{hostsfile}"
762
+ if !noop
763
+ FileUtils.copy(hostsfile, "#{hostsfile}.cleanup-#{deploy_id}")
764
+ File.open(hostsfile, File::CREAT|File::RDWR, 0644) { |f|
765
+ f.flock(File::LOCK_EX)
766
+ newlines = Array.new
767
+ f.readlines.each { |line|
768
+ newlines << line if !line.match(/ #{deploy_id}\-/)
769
+ }
770
+ f.rewind
771
+ f.truncate(0)
772
+ f.puts(newlines)
773
+ f.flush
774
+ f.flock(File::LOCK_UN)
775
+ }
776
+ end
777
+ else
778
+ MU.log "Residual /etc/hosts entries for #{deploy_id} must be removed by root user", MU::WARN
779
+ end
780
+ end
781
+
782
+ end
783
+
712
784
  # Ensure that the Nagios configuration local to the MU master has been
713
785
  # updated, and make sure Nagios has all of the ssh keys it needs to tunnel
714
786
  # to client nodes.
@@ -738,7 +810,7 @@ module MU
738
810
  ssh_conf.puts " IdentityFile #{NAGIOS_HOME}/.ssh/id_rsa"
739
811
  ssh_conf.puts " StrictHostKeyChecking no"
740
812
  ssh_conf.close
741
- FileUtils.cp("#{@myhome}/.ssh/id_rsa", "#{NAGIOS_HOME}/.ssh/id_rsa")
813
+ FileUtils.cp("#{Etc.getpwuid(Process.uid).dir}/.ssh/id_rsa", "#{NAGIOS_HOME}/.ssh/id_rsa")
742
814
  File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{NAGIOS_HOME}/.ssh/id_rsa")
743
815
  threads = []
744
816
 
@@ -751,7 +823,7 @@ module MU
751
823
  MU.log "Failed to extract ssh key name from #{deploy_id} in syncMonitoringConfig", MU::ERR if deploy.kittens.has_key?("servers")
752
824
  next
753
825
  end
754
- FileUtils.cp("#{@myhome}/.ssh/#{deploy.ssh_key_name}", "#{NAGIOS_HOME}/.ssh/#{deploy.ssh_key_name}")
826
+ FileUtils.cp("#{Etc.getpwuid(Process.uid).dir}/.ssh/#{deploy.ssh_key_name}", "#{NAGIOS_HOME}/.ssh/#{deploy.ssh_key_name}")
755
827
  File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{NAGIOS_HOME}/.ssh/#{deploy.ssh_key_name}")
756
828
  if deploy.kittens.has_key?("servers")
757
829
  deploy.kittens["servers"].values.each { |nodeclasses|
@@ -808,5 +880,26 @@ module MU
808
880
  end
809
881
  end
810
882
 
883
+ # Recursively zip a directory
884
+ # @param srcdir [String]
885
+ # @param outfile [String]
886
+ def self.zipDir(srcdir, outfile)
887
+ require 'zip'
888
+ ::Zip::File.open(outfile, ::Zip::File::CREATE) { |zipfile|
889
+ addpath = Proc.new { |zip_path, parent_path|
890
+ Dir.entries(parent_path).reject{ |d| [".", ".."].include?(d) }.each { |entry|
891
+ src = File.join(parent_path, entry)
892
+ dst = File.join(zip_path, entry).sub(/^\//, '')
893
+ if File.directory?(src)
894
+ addpath.call(dst, src)
895
+ else
896
+ zipfile.add(dst, src)
897
+ end
898
+ }
899
+ }
900
+ addpath.call("", srcdir)
901
+ }
902
+ end
903
+
811
904
  end
812
905
  end
@@ -19,6 +19,7 @@ require 'stringio'
19
19
  require 'securerandom'
20
20
  require 'timeout'
21
21
  require 'mu/mommacat/storage'
22
+ require 'mu/mommacat/search'
22
23
  require 'mu/mommacat/daemon'
23
24
  require 'mu/mommacat/naming'
24
25
 
@@ -154,7 +155,7 @@ module MU
154
155
  if @mu_user == "root"
155
156
  @chef_user = "mu"
156
157
  else
157
- @chef_user = @mu_user.dup.gsub(/\./, "")
158
+ @chef_user = @mu_user.dup.delete(".")
158
159
  @mu_user = "root" if @mu_user == "mu"
159
160
  end
160
161
  @kitten_semaphore = Mutex.new
@@ -166,6 +167,7 @@ module MU
166
167
  @need_deploy_flush = false
167
168
  @node_cert_semaphore = Mutex.new
168
169
  @deployment = deployment_data
170
+
169
171
  @deployment['mu_public_ip'] = MU.mu_public_ip
170
172
  @private_key = nil
171
173
  @public_key = nil
@@ -181,60 +183,17 @@ module MU
181
183
  @appname ||= @original_config['name'] if @original_config
182
184
  @timestamp = timestamp
183
185
  @environment = environment
186
+ @original_config['environment'] ||= @environment if @original_config
184
187
 
185
188
  if set_context_to_me
186
189
  MU::MommaCat.setThreadContext(self)
187
190
  end
188
191
 
189
192
  if create and !@no_artifacts
190
- if !Dir.exist?(MU.dataDir+"/deployments")
191
- MU.log "Creating #{MU.dataDir}/deployments", MU::DEBUG
192
- Dir.mkdir(MU.dataDir+"/deployments", 0700)
193
- end
194
- path = File.expand_path(MU.dataDir+"/deployments")+"/"+@deploy_id
195
- if !Dir.exist?(path)
196
- MU.log "Creating #{path}", MU::DEBUG
197
- Dir.mkdir(path, 0700)
198
- end
199
- if @original_config.nil? or !@original_config.is_a?(Hash)
200
- raise DeployInitializeError, "New MommaCat repository requires config hash"
201
- end
202
- credsets = {}
203
-
204
- MU::Cloud.resource_types.values.each { |attrs|
205
- if !@original_config[attrs[:cfg_plural]].nil? and @original_config[attrs[:cfg_plural]].size > 0
206
- @original_config[attrs[:cfg_plural]].each { |resource|
207
-
208
- credsets[resource['cloud']] ||= []
209
- credsets[resource['cloud']] << resource['credentials']
210
- @clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
211
- @clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
212
-
213
- }
214
- end
215
- }
216
-
217
- @ssh_key_name, @ssh_private_key, @ssh_public_key = self.SSHKey
218
- if !File.exist?(deploy_dir+"/private_key")
219
- @private_key, @public_key = createDeployKey
220
- end
221
- MU.log "Creating deploy secret for #{MU.deploy_id}"
222
- @deploy_secret = Password.random(256)
223
- if !@original_config['scrub_mu_isms'] and !@no_artifacts
224
- credsets.each_pair { |cloud, creds|
225
- creds.uniq!
226
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
227
- creds.each { |credentials|
228
- cloudclass.writeDeploySecret(@deploy_id, @deploy_secret, credentials: credentials)
229
- }
230
- }
231
- end
232
- if set_context_to_me
233
- MU::MommaCat.setThreadContext(self)
234
- end
235
-
193
+ initDeployDirectory
194
+ setDeploySecret
195
+ MU::MommaCat.setThreadContext(self) if set_context_to_me
236
196
  save!
237
-
238
197
  end
239
198
 
240
199
  @appname ||= MU.appname
@@ -242,10 +201,8 @@ module MU
242
201
  @environment ||= MU.environment
243
202
 
244
203
  loadDeploy(set_context_to_me: set_context_to_me)
245
- if !deploy_secret.nil?
246
- if !authKey(deploy_secret)
247
- raise DeployInitializeError, "Client request did not include a valid deploy authorization secret. Verify that userdata runs correctly?"
248
- end
204
+ if !deploy_secret.nil? and !authKey(deploy_secret)
205
+ raise DeployInitializeError, "Client request did not include a valid deploy authorization secret. Verify that userdata runs correctly?"
249
206
  end
250
207
 
251
208
 
@@ -257,86 +214,7 @@ module MU
257
214
  # deploy, IF it already exists, which is to say if we're loading an
258
215
  # existing deploy instead of creating a new one.
259
216
  if !create and @deployment and @original_config and !skip_resource_objects
260
-
261
- MU::Cloud.resource_types.each_pair { |res_type, attrs|
262
- type = attrs[:cfg_plural]
263
- if @deployment.has_key?(type)
264
-
265
- @deployment[type].each_pair { |res_name, data|
266
- orig_cfg = nil
267
- if @original_config.has_key?(type)
268
- @original_config[type].each { |resource|
269
- if resource["name"] == res_name
270
- orig_cfg = resource
271
- break
272
- end
273
- }
274
- end
275
-
276
- # Some Server objects originated from ServerPools, get their
277
- # configs from there
278
- if type == "servers" and orig_cfg.nil? and
279
- @original_config.has_key?("server_pools")
280
- @original_config["server_pools"].each { |resource|
281
- if resource["name"] == res_name
282
- orig_cfg = resource
283
- break
284
- end
285
- }
286
- end
287
-
288
- if orig_cfg.nil?
289
- MU.log "Failed to locate original config for #{attrs[:cfg_name]} #{res_name} in #{@deploy_id}", MU::WARN if !["firewall_rules", "databases", "storage_pools", "cache_clusters", "alarms"].include?(type) # XXX shaddap
290
- next
291
- end
292
-
293
- if orig_cfg['vpc'] and orig_cfg['vpc'].is_a?(Hash)
294
- ref = if orig_cfg['vpc']['id'] and orig_cfg['vpc']['id'].is_a?(Hash)
295
- orig_cfg['vpc']['id']['mommacat'] = self
296
- MU::Config::Ref.get(orig_cfg['vpc']['id'])
297
- else
298
- orig_cfg['vpc']['mommacat'] = self
299
- MU::Config::Ref.get(orig_cfg['vpc'])
300
- end
301
- orig_cfg['vpc'].delete('mommacat')
302
- orig_cfg['vpc'] = ref if ref.kitten(shallow: true)
303
- end
304
-
305
- begin
306
- # Load up MU::Cloud objects for all our kittens in this deploy
307
- orig_cfg['environment'] = @environment # not always set in old deploys
308
- if attrs[:has_multiples]
309
- data.keys.each { |mu_name|
310
- attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name, delay_descriptor_load: delay_descriptor_load)
311
- }
312
- else
313
- # XXX hack for old deployments, this can go away some day
314
- if data['mu_name'].nil? or data['mu_name'].empty?
315
- if res_type.to_s == "LoadBalancer" and !data['awsname'].nil?
316
- data['mu_name'] = data['awsname'].dup
317
- elsif res_type.to_s == "FirewallRule" and !data['group_name'].nil?
318
- data['mu_name'] = data['group_name'].dup
319
- elsif res_type.to_s == "Database" and !data['identifier'].nil?
320
- data['mu_name'] = data['identifier'].dup.upcase
321
- elsif res_type.to_s == "VPC"
322
- # VPC names are deterministic, just generate the things
323
- data['mu_name'] = getResourceName(data['name'])
324
- end
325
- end
326
- if data['mu_name'].nil?
327
- raise MuError, "Unable to find or guess a Mu name for #{res_type}: #{res_name} in #{@deploy_id}"
328
- end
329
- attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id'])
330
- end
331
- rescue StandardError => e
332
- if e.class != MU::Cloud::MuCloudResourceNotImplemented
333
- MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
334
- end
335
- end
336
- }
337
-
338
- end
339
- }
217
+ loadObjects(delay_descriptor_load)
340
218
  end
341
219
 
342
220
  @initializing = false
@@ -349,7 +227,7 @@ module MU
349
227
  def cloudsUsed
350
228
  seen = []
351
229
  seen << @original_config['cloud'] if @original_config['cloud']
352
- MU::Cloud.resource_types.values.each { |attrs|
230
+ MU::Cloud.resource_types.each_value { |attrs|
353
231
  type = attrs[:cfg_plural]
354
232
  if @original_config[type]
355
233
  @original_config[type].each { |resource|
@@ -369,19 +247,15 @@ module MU
369
247
  # clouds = []
370
248
  seen << @original_config['credentials'] if @original_config['credentials']
371
249
  # defaultcloud = @original_config['cloud']
372
- MU::Cloud.resource_types.values.each { |attrs|
250
+ MU::Cloud.resource_types.each_value { |attrs|
373
251
  type = attrs[:cfg_plural]
374
252
  if @original_config[type]
375
253
  @original_config[type].each { |resource|
376
254
  if resource['credentials']
377
255
  seen << resource['credentials']
378
256
  else
379
- cloudclass = if @original_config['cloud']
380
- Object.const_get("MU").const_get("Cloud").const_get(@original_config['cloud'])
381
- else
382
- Object.const_get("MU").const_get("Cloud").const_get(MU::Config.defaultCloud)
383
- end
384
- seen << cloudclass.credConfig(name_only: true)
257
+ cloudconst = @original_config['cloud'] ? @original_config['cloud'] : MU::Config.defaultCloud
258
+ seen << MU::Cloud.cloudClass(cloudconst).credConfig(name_only: true)
385
259
  end
386
260
  }
387
261
  end
@@ -404,7 +278,7 @@ module MU
404
278
  end
405
279
  end
406
280
 
407
- MU::Cloud.resource_types.values.each { |attrs|
281
+ MU::Cloud.resource_types.each_value { |attrs|
408
282
  type = attrs[:cfg_plural]
409
283
  if @original_config[type]
410
284
  @original_config[type].each { |resource|
@@ -416,11 +290,10 @@ module MU
416
290
  habitats << hab_ref.id
417
291
  end
418
292
  elsif resource['cloud']
419
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud'])
420
293
  # XXX this should be a general method implemented by each cloud
421
294
  # provider
422
295
  if resource['cloud'] == "Google"
423
- habitats << cloudclass.defaultProject(resource['credentials'])
296
+ habitats << MU::Cloud.cloudClass(resource['cloud']).defaultProject(resource['credentials'])
424
297
  end
425
298
  end
426
299
  }
@@ -444,13 +317,11 @@ module MU
444
317
  if @original_config[type]
445
318
  @original_config[type].each { |resource|
446
319
  if resource['cloud']
447
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud'])
448
- resclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud']).const_get(res_type.to_s)
449
- if resclass.isGlobal?
320
+ if MU::Cloud.resourceClass(resource['cloud'], res_type).isGlobal?
450
321
  # XXX why was I doing this, urgh
451
322
  next
452
323
  elsif !resource['region']
453
- regions << cloudclass.myRegion
324
+ regions << MU::Cloud.cloudClass(resource['cloud']).myRegion(resource['credentials'])
454
325
  end
455
326
  end
456
327
  if resource['region']
@@ -481,7 +352,7 @@ module MU
481
352
  end
482
353
 
483
354
  count = 0
484
- MU::Cloud.resource_types.values.each { |data|
355
+ MU::Cloud.resource_types.each_value { |data|
485
356
  next if @original_config[data[:cfg_plural]].nil?
486
357
  next if realtypes.size > 0 and (!negate and !realtypes.include?(data[:cfg_plural]))
487
358
  @original_config[data[:cfg_plural]].each { |resource|
@@ -499,13 +370,13 @@ module MU
499
370
  raise MuError, "Nil arguments to removeKitten are not allowed"
500
371
  end
501
372
  @kitten_semaphore.synchronize {
502
- MU::Cloud.resource_types.values.each { |attrs|
373
+ MU::Cloud.resource_types.each_value { |attrs|
503
374
  type = attrs[:cfg_plural]
504
375
  next if !@kittens.has_key?(type)
505
376
  tmplitter = @kittens[type].values.dup
506
377
  tmplitter.each { |nodeclass, data|
507
378
  if data.is_a?(Hash)
508
- data.keys.each { |mu_name|
379
+ data.each_key { |mu_name|
509
380
  if data == object
510
381
  @kittens[type][nodeclass].delete(mu_name)
511
382
  return
@@ -528,25 +399,37 @@ module MU
528
399
  # @param type [String]:
529
400
  # @param name [String]:
530
401
  # @param object [MU::Cloud]:
531
- def addKitten(type, name, object)
402
+ def addKitten(type, name, object, do_notify: false)
532
403
  if !type or !name or !object or !object.mu_name
533
404
  raise MuError, "Nil arguments to addKitten are not allowed (got type: #{type}, name: #{name}, and '#{object}' to add)"
534
405
  end
535
406
 
536
407
  _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
537
- has_multiples = attrs[:has_multiples]
538
408
  object.intoDeploy(self)
539
409
 
540
- @kitten_semaphore.synchronize {
410
+ add_block = Proc.new {
541
411
  @kittens[type] ||= {}
542
412
  @kittens[type][object.habitat] ||= {}
543
- if has_multiples
413
+ if attrs[:has_multiples]
544
414
  @kittens[type][object.habitat][name] ||= {}
545
415
  @kittens[type][object.habitat][name][object.mu_name] = object
546
416
  else
547
417
  @kittens[type][object.habitat][name] = object
548
418
  end
419
+ if do_notify
420
+ notify(type, name, object.notify, triggering_node: object, delayed_save: true)
421
+ end
549
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
550
433
  end
551
434
 
552
435
  # Encrypt a string with the deployment's public key.
@@ -577,7 +460,7 @@ module MU
577
460
  loadDeploy(true) # make sure we're not trampling deployment data
578
461
  @secret_semaphore.synchronize {
579
462
  if @secrets[type].nil?
580
- raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
463
+ raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.join(", ")})"
581
464
  end
582
465
  @secrets[type][instance_id] = encryptWithDeployKey(raw_secret)
583
466
  }
@@ -593,7 +476,7 @@ module MU
593
476
  @secret_semaphore.synchronize {
594
477
  if @secrets[type].nil?
595
478
  return nil if quiet
596
- raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
479
+ raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.join(", ")})"
597
480
  end
598
481
  if @secrets[type][instance_id].nil?
599
482
  return nil if quiet
@@ -654,663 +537,131 @@ module MU
654
537
 
655
538
  @@dummy_cache = {}
656
539
 
657
- # Locate a resource that's either a member of another deployment, or of no
658
- # deployment at all, and return a {MU::Cloud} object for it.
659
- # @param cloud [String]: The Cloud provider to use.
660
- # @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
661
- # @param deploy_id [String]: The identifier of an outside deploy to search.
662
- # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field, typically used in conjunction with deploy_id.
663
- # @param mu_name [String]: The fully-resolved and deployed name of the resource, typically used in conjunction with deploy_id.
664
- # @param cloud_id [String]: A cloud provider identifier for this resource.
665
- # @param region [String]: The cloud provider region
666
- # @param tag_key [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_value.
667
- # @param tag_value [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_key.
668
- # @param allow_multi [Boolean]: Permit an array of matching resources to be returned (if applicable) instead of just one.
669
- # @param dummy_ok [Boolean]: Permit return of a faked {MU::Cloud} object if we don't have enough information to identify a real live one.
670
- # @param flags [Hash]: Other cloud or resource type specific options to pass to that resource's find() method
671
- # @return [Array<MU::Cloud>]
672
- def self.findStray(
673
- cloud,
674
- type,
675
- deploy_id: nil,
676
- name: nil,
677
- mu_name: nil,
678
- cloud_id: nil,
679
- credentials: nil,
680
- region: nil,
681
- tag_key: nil,
682
- tag_value: nil,
683
- allow_multi: false,
684
- calling_deploy: MU.mommacat,
685
- flags: {},
686
- habitats: [],
687
- dummy_ok: false,
688
- debug: false,
689
- no_deploy_search: false
690
- )
691
- start = Time.now
692
- callstr = "findStray(cloud: #{cloud}, type: #{type}, deploy_id: #{deploy_id}, calling_deploy: #{calling_deploy.deploy_id if !calling_deploy.nil?}, name: #{name}, cloud_id: #{cloud_id}, tag_key: #{tag_key}, tag_value: #{tag_value}, credentials: #{credentials}, habitats: #{habitats ? habitats.to_s : "[]"}, dummy_ok: #{dummy_ok.to_s}, flags: #{flags.to_s}) from #{caller[0]}"
693
- # callstack = caller.dup
694
-
695
- return nil if cloud == "CloudFormation" and !cloud_id.nil?
696
- shortclass, _cfg_name, cfg_plural, classname, _attrs = MU::Cloud.getResourceNames(type)
697
- if !MU::Cloud.supportedClouds.include?(cloud) or shortclass.nil?
698
- MU.log "findStray was called with bogus cloud argument '#{cloud}'", MU::WARN, details: callstr
699
- return nil
700
- end
701
-
702
- begin
703
- # TODO this is dumb as hell, clean this up.. and while we're at it
704
- # .dup everything so we don't mangle referenced values from the caller
705
- deploy_id = deploy_id.to_s if deploy_id.class.to_s == "MU::Config::Tail"
706
- name = name.to_s if name.class.to_s == "MU::Config::Tail"
707
- cloud_id = cloud_id.to_s if !cloud_id.nil?
708
- mu_name = mu_name.to_s if mu_name.class.to_s == "MU::Config::Tail"
709
- tag_key = tag_key.to_s if tag_key.class.to_s == "MU::Config::Tail"
710
- tag_value = tag_value.to_s if tag_value.class.to_s == "MU::Config::Tail"
711
- type = cfg_plural
712
- resourceclass = MU::Cloud.loadCloudType(cloud, shortclass)
713
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
714
-
715
- credlist = if credentials
716
- [credentials]
717
- else
718
- cloudclass.listCredentials
719
- end
720
-
721
- if (tag_key and !tag_value) or (!tag_key and tag_value)
722
- raise MuError, "Can't call findStray with only one of tag_key and tag_value set, must be both or neither"
723
- end
724
- # Help ourselves by making more refined parameters out of mu_name, if
725
- # they weren't passed explicitly
726
- if mu_name
727
- if !tag_key and !tag_value
728
- # XXX "Name" is an AWS-ism, perhaps those plugins should do this bit?
729
- tag_key="Name"
730
- tag_value=mu_name
731
- end
732
- # We can extract a deploy_id from mu_name if we don't have one already
733
- if !deploy_id and mu_name
734
- deploy_id = mu_name.sub(/^(\w+-\w+-\d{10}-[A-Z]{2})-/, '\1')
735
- end
736
- end
737
- loglevel = debug ? MU::NOTICE : MU::DEBUG
738
-
739
- MU.log callstr, loglevel, details: caller
740
-
741
- # See if the thing we're looking for is a member of the deploy that's
742
- # asking after it.
743
- if !deploy_id.nil? and !calling_deploy.nil? and
744
- calling_deploy.deploy_id == deploy_id and (!name.nil? or !mu_name.nil?)
745
- handle = calling_deploy.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id, credentials: credentials)
746
- return [handle] if !handle.nil?
747
- end
748
-
749
- kittens = {}
750
- # Search our other deploys for matching resources
751
- if !no_deploy_search and (deploy_id or name or mu_name or cloud_id)
752
- MU.log "findStray: searching my deployments (#{cfg_plural}, name: #{name}, deploy_id: #{deploy_id}, mu_name: #{mu_name}) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
753
-
754
- # Check our in-memory cache of live deploys before resorting to
755
- # metadata
756
- littercache = nil
757
- # Sometimes we're called inside a locked thread, sometimes not. Deal
758
- # with locking gracefully.
759
- begin
760
- @@litter_semaphore.synchronize {
761
- littercache = @@litters.dup
762
- }
763
- rescue ThreadError => e
764
- raise e if !e.message.match(/recursive locking/)
765
- littercache = @@litters.dup
766
- end
767
-
768
- littercache.each_pair { |cur_deploy, momma|
769
- next if deploy_id and deploy_id != cur_deploy
770
-
771
- straykitten = momma.findLitterMate(type: type, cloud_id: cloud_id, name: name, mu_name: mu_name, credentials: credentials, created_only: true)
772
- if straykitten
773
- MU.log "Found matching kitten #{straykitten.mu_name} in-memory - #{sprintf("%.2fs", (Time.now-start))}", loglevel
774
- # Peace out if we found the exact resource we want
775
- if cloud_id and straykitten.cloud_id.to_s == cloud_id.to_s
776
- return [straykitten]
777
- elsif mu_name and straykitten.mu_name == mu_name
778
- return [straykitten]
779
- else
780
- kittens[straykitten.cloud_id] ||= straykitten
781
- end
782
- end
783
- }
784
-
785
- mu_descs = MU::MommaCat.getResourceMetadata(cfg_plural, name: name, deploy_id: deploy_id, mu_name: mu_name)
786
- MU.log "findStray: #{mu_descs.size.to_s} deploys had matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
787
-
788
- mu_descs.each_pair { |cur_deploy_id, matches|
789
- MU.log "findStray: #{cur_deploy_id} had #{matches.size.to_s} initial matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
790
- next if matches.nil? or matches.size == 0
791
-
792
- momma = MU::MommaCat.getLitter(cur_deploy_id)
793
-
794
- straykitten = nil
795
-
796
- # If we found exactly one match in this deploy, use its metadata to
797
- # guess at resource names we weren't told.
798
- if matches.size > 1 and cloud_id
799
- MU.log "findStray: attempting to narrow down multiple matches with cloud_id #{cloud_id} - #{sprintf("%.2fs", (Time.now-start))}", loglevel
800
- straykitten = momma.findLitterMate(type: type, cloud_id: cloud_id, credentials: credentials, created_only: true)
801
- elsif matches.size == 1 and name.nil? and mu_name.nil?
802
- if cloud_id.nil?
803
- straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: matches.first["cloud_id"], credentials: credentials)
804
- else
805
- MU.log "findStray: fetching single match with cloud_id #{cloud_id} - #{sprintf("%.2fs", (Time.now-start))}", loglevel
806
- straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: cloud_id, credentials: credentials)
807
- end
808
- # elsif !flags.nil? and !flags.empty? # XXX eh, maybe later
809
- # # see if we can narrow it down further with some flags
810
- # filtered = []
811
- # matches.each { |m|
812
- # f = resourceclass.find(cloud_id: m['mu_name'], flags: flags)
813
- # filtered << m if !f.nil? and f.size > 0
814
- # MU.log "RESULT FROM find(cloud_id: #{m['mu_name']}, flags: #{flags})", MU::WARN, details: f
815
- # }
816
- # if filtered.size == 1
817
- # straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: filtered.first['cloud_id'])
818
- # end
819
- else
820
- # There's more than one of this type of resource in the target
821
- # deploy, so see if findLitterMate can narrow it down for us
822
- straykitten = momma.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id, credentials: credentials)
823
- end
824
-
825
- next if straykitten.nil?
826
- straykitten.intoDeploy(momma)
827
-
828
- if straykitten.cloud_id.nil?
829
- MU.log "findStray: kitten #{straykitten.mu_name} came back with nil cloud_id", MU::WARN
830
- next
831
- end
832
-
833
- kittens[straykitten.cloud_id] ||= straykitten
834
-
835
- # Peace out if we found the exact resource we want
836
- if cloud_id and straykitten.cloud_id.to_s == cloud_id.to_s
837
- return [straykitten]
838
- # ...or if we've validated our one possible match
839
- elsif !cloud_id and mu_descs.size == 1 and matches.size == 1
840
- return [straykitten]
841
- elsif credentials and credlist.size == 1 and straykitten.credentials == credentials
842
- return [straykitten]
843
- end
844
- }
845
-
846
-
847
- # if !mu_descs.nil? and mu_descs.size > 0 and !deploy_id.nil? and !deploy_id.empty? and !mu_descs.first.empty?
848
- # MU.log "I found descriptions that might match #{resourceclass.cfg_plural} name: #{name}, deploy_id: #{deploy_id}, mu_name: #{mu_name}, but couldn't isolate my target kitten", MU::WARN, details: caller
849
- # puts File.read(deploy_dir(deploy_id)+"/deployment.json")
850
- # end
851
-
852
- # We can't refine any further by asking the cloud provider...
853
- if !cloud_id and !tag_key and !tag_value and kittens.size > 1
854
- if !allow_multi
855
- raise MuError, "Multiple matches in MU::MommaCat.findStray where none allowed from deploy_id: '#{deploy_id}', name: '#{name}', mu_name: '#{mu_name}' (#{caller[0]})"
856
- else
857
- return kittens.values
858
- end
859
- end
860
- end
861
-
862
- matches = []
863
-
864
- found_the_thing = false
865
- credlist.each { |creds|
866
- break if found_the_thing
867
- if cloud_id or (tag_key and tag_value) or !flags.empty? or allow_multi
868
-
869
- regions = begin
870
- region ? [region] : cloudclass.listRegions(credentials: creds)
871
- rescue NoMethodError # Not all cloud providers have regions
872
- [nil]
873
- end
874
-
875
- # ..not all resource types care about regions either
876
- if resourceclass.isGlobal?
877
- regions = [nil]
878
- end
879
-
880
- # Decide what habitats (accounts/projects/subscriptions) we'll
881
- # search, if applicable for this resource type.
882
- habitats ||= []
883
- begin
884
- if flags["project"] # backwards-compat
885
- habitats << flags["project"]
886
- end
887
- if habitats.empty?
888
- if resourceclass.canLiveIn.include?(nil)
889
- habitats << nil
890
- end
891
- if resourceclass.canLiveIn.include?(:Habitat)
892
- habitats.concat(cloudclass.listProjects(creds))
893
- end
894
- end
895
- rescue NoMethodError # we only expect this to work on Google atm
896
- end
897
-
898
- if habitats.empty?
899
- habitats << nil
900
- end
901
- habitats.uniq!
902
-
903
- habitat_threads = []
904
- desc_semaphore = Mutex.new
905
-
906
- cloud_descs = {}
907
- habitats.each { |hab|
908
- begin
909
- habitat_threads.each { |t| t.join(0.1) }
910
- habitat_threads.reject! { |t| t.nil? or !t.status }
911
- sleep 1 if habitat_threads.size > 5
912
- end while habitat_threads.size > 5
913
- habitat_threads << Thread.new(hab) { |p|
914
- MU.log "findStray: Searching #{p} (#{habitat_threads.size.to_s} habitat threads running) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
915
- cloud_descs[p] = {}
916
- region_threads = []
917
- regions.each { |reg| region_threads << Thread.new(reg) { |r|
918
- MU.log "findStray: Searching #{r} in #{p} (#{region_threads.size.to_s} region threads running) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
919
- MU.log "findStray: calling #{classname}.find(cloud_id: #{cloud_id}, region: #{r}, tag_key: #{tag_key}, tag_value: #{tag_value}, flags: #{flags}, credentials: #{creds}, project: #{p}) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
920
- found = resourceclass.find(cloud_id: cloud_id, region: r, tag_key: tag_key, tag_value: tag_value, flags: flags, credentials: creds, habitat: p)
921
- MU.log "findStray: #{found ? found.size.to_s : "nil"} results - #{sprintf("%.2fs", (Time.now-start))}", loglevel
922
-
923
- if found
924
- desc_semaphore.synchronize {
925
- cloud_descs[p][r] = found
926
- }
927
- end
928
- # Stop if you found the thing by a specific cloud_id
929
- if cloud_id and found and !found.empty?
930
- found_the_thing = true
931
- Thread.exit
932
- end
933
- } }
934
- begin
935
- region_threads.each { |t| t.join(0.1) }
936
- region_threads.reject! { |t| t.nil? or !t.status }
937
- if region_threads.size > 0
938
- MU.log "#{region_threads.size.to_s} regions still running in #{p}", loglevel
939
- sleep 3
940
- end
941
- end while region_threads.size > 0
942
- }
943
- }
944
- begin
945
- habitat_threads.each { |t| t.join(0.1) }
946
- habitat_threads.reject! { |t| t.nil? or !t.status }
947
- if habitat_threads.size > 0
948
- MU.log "#{habitat_threads.size.to_s} habitats still running", loglevel
949
- sleep 3
950
- end
951
- end while habitat_threads.size > 0
952
-
953
- habitat_threads = []
954
- habitats.each { |hab| habitat_threads << Thread.new(hab) { |p|
955
- region_threads = []
956
- regions.each { |reg| region_threads << Thread.new(reg) { |r|
957
- next if cloud_descs[p][r].nil?
958
- cloud_descs[p][r].each_pair { |kitten_cloud_id, descriptor|
959
-
960
- # We already have a MU::Cloud object for this guy, use it
961
- if kittens.has_key?(kitten_cloud_id)
962
- desc_semaphore.synchronize {
963
- matches << kittens[kitten_cloud_id]
964
- }
965
- elsif kittens.size == 0
966
- if !dummy_ok
967
- next
968
- end
969
-
970
- # If we don't have a MU::Cloud object, manufacture a dummy
971
- # one. Give it a fake name if we have to and have decided
972
- # that's ok. Wild inferences from the cloud descriptor are
973
- # ok to try here.
974
- use_name = if (name.nil? or name.empty?)
975
- if !dummy_ok
976
- nil
977
- elsif !mu_name.nil?
978
- mu_name
979
- # AWS-style tags
980
- elsif descriptor.respond_to?(:tags) and
981
- descriptor.tags.is_a?(Array) and
982
- descriptor.tags.first.respond_to?(:key) and
983
- descriptor.tags.map { |t| t.key }.include?("Name")
984
- descriptor.tags.select { |t| t.key == "Name" }.first.value
985
- else
986
- try = nil
987
- # Various GCP fields
988
- [:display_name, :name, (resourceclass.cfg_name+"_name").to_sym].each { |field|
989
- if descriptor.respond_to?(field) and descriptor.send(field).is_a?(String)
990
- try = descriptor.send(field)
991
- break
992
- end
993
-
994
- }
995
- try ||= if !tag_value.nil?
996
- tag_value
997
- else
998
- kitten_cloud_id
999
- end
1000
- try
1001
- end
1002
- else
1003
- name
1004
- end
1005
- if use_name.nil?
1006
- MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return - #{sprintf("%.2fs", (Time.now-start))}", loglevel, details: caller
1007
- next
1008
- end
1009
- cfg = {
1010
- "name" => use_name,
1011
- "cloud" => cloud,
1012
- "credentials" => creds
1013
- }
1014
- if !r.nil? and !resourceclass.isGlobal?
1015
- cfg["region"] = r
1016
- end
1017
-
1018
- if !p.nil? and resourceclass.canLiveIn.include?(:Habitat)
1019
- cfg["project"] = p
1020
- end
1021
- # If we can at least find the config from the deploy this will
1022
- # belong with, use that, even if it's an ungroomed resource.
1023
- if !calling_deploy.nil? and
1024
- !calling_deploy.original_config.nil? and
1025
- !calling_deploy.original_config[type+"s"].nil?
1026
- calling_deploy.original_config[type+"s"].each { |s|
1027
- if s["name"] == use_name
1028
- cfg = s.dup
1029
- break
1030
- end
1031
- }
1032
-
1033
- newkitten = resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
1034
- desc_semaphore.synchronize {
1035
- matches << newkitten
1036
- }
1037
- else
1038
- if !@@dummy_cache[cfg_plural] or !@@dummy_cache[cfg_plural][cfg.to_s]
1039
- MU.log "findStray: Generating dummy '#{resourceclass.to_s}' cloudobj with name: #{use_name}, cloud_id: #{kitten_cloud_id.to_s} - #{sprintf("%.2fs", (Time.now-start))}", loglevel, details: cfg
1040
- resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
1041
- desc_semaphore.synchronize {
1042
- @@dummy_cache[cfg_plural] ||= {}
1043
- @@dummy_cache[cfg_plural][cfg.to_s] = resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
1044
- MU.log "findStray: Finished generating dummy '#{resourceclass.to_s}' cloudobj - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1045
- }
1046
- end
1047
- desc_semaphore.synchronize {
1048
- matches << @@dummy_cache[cfg_plural][cfg.to_s]
1049
- }
1050
- end
1051
- end
1052
- }
1053
- } }
1054
- MU.log "findStray: tying up #{region_threads.size.to_s} region threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1055
- region_threads.each { |t|
1056
- t.join
1057
- }
1058
- } }
1059
- MU.log "findStray: tying up #{habitat_threads.size.to_s} habitat threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1060
- habitat_threads.each { |t|
1061
- t.join
1062
- }
1063
- end
1064
- }
1065
- rescue StandardError => e
1066
- MU.log e.inspect, MU::ERR, details: e.backtrace
1067
- end
1068
- MU.log "findStray: returning #{matches ? matches.size.to_s : "0"} matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1069
-
1070
- matches
1071
- end
1072
-
1073
- # Return the resource object of another member of this deployment
1074
- # @param type [String,Symbol]: The type of resource
1075
- # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field
1076
- # @param mu_name [String]: The fully-resolved and deployed name of the resource
1077
- # @param cloud_id [String]: The cloud provider's unique identifier for this resource
1078
- # @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
1079
- # @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.
1080
- # @return [MU::Cloud]
1081
- def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, debug: false, indent: "")
1082
- shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1083
- type = cfg_plural
1084
- has_multiples = attrs[:has_multiples]
1085
-
1086
- loglevel = debug ? MU::NOTICE : MU::DEBUG
1087
-
1088
- argstring = [:type, :name, :mu_name, :cloud_id, :created_only, :credentials, :habitat, :has_multiples].reject { |a|
1089
- binding.local_variable_get(a).nil?
1090
- }.map { |v|
1091
- v.to_s+": "+binding.local_variable_get(v).to_s
1092
- }.join(", ")
1093
-
1094
- # Fun times: if we specified a habitat, which we may also have done by
1095
- # its shorthand sibling name, let's... call ourselves first to make sure
1096
- # we're fishing for the right thing.
1097
- if habitat
1098
- if habitat.is_a?(MU::Config::Ref) and habitat.id
1099
- habitat = habitat.id
1100
- else
1101
- MU.log indent+"findLitterMate(#{argstring}): Attempting to resolve habitat name #{habitat}", loglevel
1102
- realhabitat = findLitterMate(type: "habitat", name: habitat, debug: debug, credentials: credentials, indent: indent+" ")
1103
- if realhabitat and realhabitat.mu_name
1104
- MU.log indent+"findLitterMate: Resolved habitat name #{habitat} to #{realhabitat.mu_name}", loglevel, details: [realhabitat.mu_name, realhabitat.cloud_id, realhabitat.config.keys]
1105
- habitat = realhabitat.cloud_id
1106
- elsif debug
1107
- MU.log indent+"findLitterMate(#{argstring}): Failed to resolve habitat name #{habitat}", MU::WARN
1108
- end
1109
- end
1110
- end
1111
-
1112
-
1113
- @kitten_semaphore.synchronize {
1114
- if !@kittens.has_key?(type)
1115
- if debug
1116
- MU.log indent+"NO SUCH KEY #{type} findLitterMate(#{argstring})", MU::WARN, details: @kittens.keys
1117
- end
1118
- return nil
1119
- end
1120
- MU.log indent+"START findLitterMate(#{argstring}), caller: #{caller[2]}", loglevel, details: @kittens[type].keys.map { |hab| hab.to_s+": "+@kittens[type][hab].keys.join(", ") }
1121
- matches = []
1122
-
1123
- @kittens[type].each { |habitat_group, sib_classes|
1124
- next if habitat and habitat_group != habitat and !habitat_group.nil?
1125
- sib_classes.each_pair { |sib_class, data|
1126
- virtual_name = nil
1127
-
1128
- if !has_multiples and data and !data.is_a?(Hash) and data.config and data.config.is_a?(Hash) and data.config['virtual_name'] and name == data.config['virtual_name']
1129
- virtual_name = data.config['virtual_name']
1130
- elsif !name.nil? and name != sib_class
1131
- next
1132
- end
1133
- if has_multiples
1134
- if !name.nil?
1135
- if return_all
1136
- MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
1137
- return data.dup
1138
- end
1139
- if data.size == 1 and (cloud_id.nil? or data.values.first.cloud_id == cloud_id)
1140
- return data.values.first
1141
- elsif mu_name.nil? and cloud_id.nil?
1142
- MU.log indent+"#{@deploy_id}: Found multiple matches in findLitterMate based on #{type}: #{name}, and not enough info to narrow down further. Returning an arbitrary result. Caller: #{caller[2]}", MU::WARN, details: data.keys
1143
- return data.values.first
1144
- end
1145
- end
1146
- data.each_pair { |sib_mu_name, obj|
1147
- if (!mu_name.nil? and mu_name == sib_mu_name) or
1148
- (!cloud_id.nil? and cloud_id == obj.cloud_id) or
1149
- (!credentials.nil? and credentials == obj.credentials)
1150
- if !created_only or !obj.cloud_id.nil?
1151
- if return_all
1152
- MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
1153
- return data.dup
1154
- else
1155
- MU.log indent+"MULTI-MATCH findLitterMate(#{argstring})", loglevel, details: data.keys
1156
- return obj
1157
- end
1158
- end
1159
- end
1160
- }
1161
- else
1162
-
1163
- MU.log indent+"CHECKING AGAINST findLitterMate #{habitat_group}/#{type}/#{sib_class} data.cloud_id: #{data.cloud_id}, data.credentials: #{data.credentials}, sib_class: #{sib_class}, virtual_name: #{virtual_name}", loglevel, details: argstring
1164
-
1165
- data_cloud_id = data.cloud_id.nil? ? nil : data.cloud_id.to_s
1166
-
1167
- MU.log indent+"(name.nil? or sib_class == name or virtual_name == name)", loglevel, details: (name.nil? or sib_class == name or virtual_name == name).to_s
1168
- MU.log indent+"(cloud_id.nil? or cloud_id[#{cloud_id.class.name}:#{cloud_id.to_s}] == data_cloud_id[#{data_cloud_id.class.name}:#{data_cloud_id}])", loglevel, details: (cloud_id.nil? or cloud_id == data_cloud_id).to_s
1169
- MU.log indent+"(credentials.nil? or data.credentials.nil? or credentials[#{credentials.class.name}:#{credentials}] == data.credentials[#{data.credentials.class.name}:#{data.credentials}])", loglevel, details: (credentials.nil? or data.credentials.nil? or credentials == data.credentials).to_s
1170
-
1171
- if (name.nil? or sib_class == name.to_s or virtual_name == name.to_s) and
1172
- (cloud_id.nil? or cloud_id.to_s == data_cloud_id) and
1173
- (credentials.nil? or data.credentials.nil? or credentials.to_s == data.credentials.to_s)
1174
- MU.log indent+"OUTER MATCH PASSED, NEED !created_only (#{created_only.to_s}) or !data_cloud_id.nil? (#{data_cloud_id})", loglevel, details: (cloud_id.nil? or cloud_id == data_cloud_id).to_s
1175
- if !created_only or !data_cloud_id.nil?
1176
- MU.log indent+"SINGLE MATCH findLitterMate(#{argstring})", loglevel, details: [data.mu_name, data_cloud_id, data.config.keys]
1177
- matches << data
1178
- end
1179
- end
1180
- end
1181
- }
1182
- }
1183
-
1184
- return matches.first if matches.size == 1
1185
- if return_all and matches.size > 1
1186
- return matches
1187
- end
1188
- }
1189
-
1190
- MU.log indent+"NO MATCH findLitterMate(#{argstring})", loglevel
1191
-
1192
- return nil
1193
- end
1194
-
1195
540
  # Add or remove a resource's metadata to this deployment's structure and
1196
541
  # flush it to disk.
1197
542
  # @param type [String]: The type of resource (e.g. *server*, *database*).
1198
543
  # @param key [String]: The name field of this resource.
544
+ # @param mu_name [String]: The mu_name of this resource.
1199
545
  # @param data [Hash]: The resource's metadata.
546
+ # @param triggering_node [MU::Cloud]: A cloud object calling this notify, usually on behalf of itself
1200
547
  # @param remove [Boolean]: Remove this resource from the deploy structure, instead of adding it.
1201
548
  # @return [void]
1202
549
  def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
1203
- return if @no_artifacts
1204
- MU::MommaCat.lock("deployment-notification")
550
+ no_write = (@no_artifacts or caller.grep(/\/mommacat\.rb:\d+:in `notify'/))
1205
551
 
1206
- if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
1207
- loadDeploy(true) # make sure we're saving the latest and greatest
1208
- end
552
+ begin
553
+ if !no_write
554
+ if !MU::MommaCat.lock("deployment-notification", deploy_id: @deploy_id, retries: 10)
555
+ raise MuError, "Failed to get deployment-notifcation lock for #{@deploy_id}"
556
+ end
557
+ end
1209
558
 
1210
- _shortclass, _cfg_name, cfg_plural, _classname, attrs = MU::Cloud.getResourceNames(type)
1211
- has_multiples = false
559
+ if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
560
+ loadDeploy(true) # make sure we're saving the latest and greatest
561
+ end
1212
562
 
1213
- # it's not always the case that we're logging data for a legal resource
1214
- # type, though that's what we're usually for
1215
- if cfg_plural
1216
- type = cfg_plural
1217
- has_multiples = attrs[:has_multiples]
1218
- end
563
+ _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type, false)
564
+ has_multiples = attrs[:has_multiples] ? true : false
1219
565
 
1220
- if mu_name.nil?
1221
- if !data.nil? and !data["mu_name"].nil?
1222
- mu_name = data["mu_name"]
566
+ mu_name ||= if !data.nil? and !data["mu_name"].nil?
567
+ data["mu_name"]
1223
568
  elsif !triggering_node.nil? and !triggering_node.mu_name.nil?
1224
- mu_name = triggering_node.mu_name
569
+ triggering_node.mu_name
1225
570
  end
1226
571
  if mu_name.nil? and has_multiples
1227
- MU.log "MU::MommaCat.notify called to modify deployment struct for a type (#{type}) with :has_multiples, but no mu_name available to look under #{key}. Call was #{caller[0]}", MU::WARN, details: data
1228
- MU::MommaCat.unlock("deployment-notification")
572
+ MU.log "MU::MommaCat.notify called to modify deployment struct for a type (#{type}) with :has_multiples, but no mu_name available to look under #{key}. Call was #{caller(1..1)}", MU::WARN, details: data
1229
573
  return
1230
574
  end
1231
- end
1232
575
 
1233
- @need_deploy_flush = true
576
+ @need_deploy_flush = true
1234
577
 
1235
- if !remove
1236
- if data.nil?
1237
- MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
1238
- MU::MommaCat.unlock("deployment-notification")
1239
- return
1240
- end
1241
- @notify_semaphore.synchronize {
1242
- @deployment[type] ||= {}
1243
- }
1244
- if has_multiples
578
+ if !remove
579
+ if data.nil?
580
+ MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
581
+ return
582
+ end
1245
583
  @notify_semaphore.synchronize {
1246
- @deployment[type][key] ||= {}
584
+ @deployment[type] ||= {}
1247
585
  }
1248
- # fix has_multiples classes that weren't tiered correctly
1249
- if @deployment[type][key].is_a?(Hash) and @deployment[type][key].has_key?("mu_name")
1250
- olddata = @deployment[type][key].dup
1251
- @deployment[type][key][olddata["mu_name"]] = olddata
1252
- end
1253
- @deployment[type][key][mu_name] = data
1254
- MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
1255
- else
1256
- @deployment[type][key] = data
1257
- MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
1258
- end
1259
- save!(key) if !delayed_save
1260
- else
1261
- have_deploy = true
1262
- if @deployment[type].nil? or @deployment[type][key].nil?
1263
-
1264
586
  if has_multiples
1265
- MU.log "MU::MommaCat.notify called to remove #{type} #{key} #{mu_name} deployment struct, but no such data exist", MU::DEBUG
587
+ @notify_semaphore.synchronize {
588
+ @deployment[type][key] ||= {}
589
+ }
590
+ @deployment[type][key][mu_name] = data
591
+ MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
1266
592
  else
1267
- MU.log "MU::MommaCat.notify called to remove #{type} #{key} deployment struct, but no such data exist", MU::DEBUG
593
+ @deployment[type][key] = data
594
+ MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
595
+ end
596
+ if !delayed_save and !no_write
597
+ save!(key)
598
+ end
599
+ else
600
+ have_deploy = true
601
+ if @deployment[type].nil? or @deployment[type][key].nil?
602
+ MU.log "MU::MommaCat.notify called to remove #{type} #{key}#{has_multiples ? " "+mu_name : ""} deployment struct, but no such data exist", MU::DEBUG
603
+ return
1268
604
  end
1269
- MU::MommaCat.unlock("deployment-notification")
1270
605
 
1271
- return
1272
- end
606
+ if have_deploy
607
+ @notify_semaphore.synchronize {
608
+ if has_multiples
609
+ MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
610
+ @deployment[type][key].delete(mu_name)
611
+ end
1273
612
 
1274
- if have_deploy
1275
- @notify_semaphore.synchronize {
1276
- if has_multiples
1277
- MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
1278
- @deployment[type][key].delete(mu_name)
1279
- if @deployment[type][key].size == 0
613
+ if @deployment[type][key].empty? or !has_multiples
614
+ MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1280
615
  @deployment[type].delete(key)
1281
616
  end
1282
- else
1283
- MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1284
- @deployment[type].delete(key)
1285
- end
1286
- if @deployment[type].size == 0
1287
- @deployment.delete(type)
1288
- end
1289
- }
1290
- end
1291
- save! if !delayed_save
1292
617
 
618
+ if @deployment[type].empty?
619
+ @deployment.delete(type)
620
+ end
621
+ }
622
+ end
623
+ save! if !delayed_save and !no_write
624
+ end
625
+ ensure
626
+ MU::MommaCat.unlock("deployment-notification", deploy_id: @deploy_id) if !no_write
1293
627
  end
1294
-
1295
- MU::MommaCat.unlock("deployment-notification")
1296
628
  end
1297
629
 
1298
630
  # Send a Slack notification to a deployment's administrators.
1299
631
  # @param subject [String]: The subject line of the message.
1300
632
  # @param msg [String]: The message body.
1301
633
  # @return [void]
1302
- def sendAdminSlack(subject, msg: "")
1303
- if $MU_CFG['slack'] and $MU_CFG['slack']['webhook'] and
1304
- (!$MU_CFG['slack']['skip_environments'] or !$MU_CFG['slack']['skip_environments'].any?{ |s| s.casecmp(MU.environment)==0 })
634
+ def sendAdminSlack(subject, msg: "", scrub_mu_isms: true, snippets: [], noop: false)
635
+ if MU.muCfg['slack'] and MU.muCfg['slack']['webhook'] and
636
+ (!MU.muCfg['slack']['skip_environments'] or !MU.muCfg['slack']['skip_environments'].any?{ |s| s.casecmp(MU.environment)==0 })
1305
637
  require 'slack-notifier'
1306
- slack = Slack::Notifier.new $MU_CFG['slack']['webhook']
638
+ slackargs = nil
639
+ keyword_args = { channel: MU.muCfg['slack']['channel'] }
640
+ begin
641
+ slack = Slack::Notifier.new MU.muCfg['slack']['webhook']
642
+ prefix = scrub_mu_isms ? subject : "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}"
643
+
644
+ text = if msg and !msg.empty?
645
+ "#{prefix}:\n\n```#{msg}```"
646
+ else
647
+ prefix
648
+ end
1307
649
 
1308
- if msg and !msg.empty?
1309
- slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}:\n\n```#{msg}\n```", channel: $MU_CFG['slack']['channel']
1310
- else
1311
- slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}", channel: $MU_CFG['slack']['channel']
650
+ if snippets and snippets.size > 0
651
+ keyword_args[:attachments] = snippets
652
+ end
653
+
654
+ if !noop
655
+ slack.ping(text, **keyword_args)
656
+ else
657
+ MU.log "Would send to #{MU.muCfg['slack']['channel']}", MU::NOTICE, details: [ text, keyword_args ]
658
+ end
659
+ rescue Slack::Notifier::APIError => e
660
+ MU.log "Failed to send message to slack: #{e.message}", MU::ERR, details: keyword_args
661
+ return false
1312
662
  end
1313
663
  end
664
+ true
1314
665
  end
1315
666
 
1316
667
  # Send an email notification to a deployment's administrators.
@@ -1331,13 +682,13 @@ module MU
1331
682
  to << "#{admin['name']} <#{admin['email']}>"
1332
683
  }
1333
684
  end
1334
- message = <<MESSAGE_END
685
+ message = <<MAIL_HEAD_END
1335
686
  From: #{MU.handle} <root@localhost>
1336
687
  To: #{to.join(",")}
1337
688
  Subject: #{subject}
1338
689
 
1339
690
  #{msg}
1340
- MESSAGE_END
691
+ MAIL_HEAD_END
1341
692
  if !kitten.nil? and kitten.kind_of?(MU::Cloud)
1342
693
  message = message + "\n\n**** #{kitten}:\n"
1343
694
  if !kitten.report.nil?
@@ -1425,124 +776,59 @@ MESSAGE_END
1425
776
  MU::Master::SSL.sign(csr_path, sans, for_user: MU.mu_user)
1426
777
  end
1427
778
 
1428
- # Make sure deployment data is synchronized to/from each node in the
779
+ # Make sure deployment data is synchronized to/from each +Server+ in the
1429
780
  # currently-loaded deployment.
781
+ # @param nodeclasses [Array<String>]
782
+ # @param triggering_node [String,MU::Cloud::Server]
783
+ # @param save_only [Boolean]
1430
784
  def syncLitter(nodeclasses = [], triggering_node: nil, save_only: false)
1431
- # XXX take some config logic to decide what nodeclasses to hit? like, make
1432
- # inferences from dependencies or something?
1433
-
1434
- return if MU.syncLitterThread
785
+ return if MU.syncLitterThread # don't run recursively by accident
1435
786
  return if !Dir.exist?(deploy_dir)
1436
- svrs = MU::Cloud.resource_types[:Server][:cfg_plural] # legibility shorthand
1437
- if !triggering_node.nil? and nodeclasses.size > 0
1438
- nodeclasses.reject! { |n| n == triggering_node.to_s }
1439
- return if nodeclasses.size == 0
1440
- end
1441
787
 
1442
- @kitten_semaphore.synchronize {
1443
- if @kittens.nil? or
1444
- @kittens[svrs].nil?
1445
- MU.log "No #{svrs} as yet available in #{@deploy_id}", MU::DEBUG, details: @kittens
1446
- return
1447
- end
788
+ if !triggering_node.nil? and triggering_node.is_a?(MU::Cloud::Server)
789
+ triggering_node = triggering_node.mu_name
790
+ end
1448
791
 
1449
-
1450
- MU.log "Updating these node classes in #{@deploy_id}", MU::DEBUG, details: nodeclasses
1451
- }
792
+ siblings = findLitterMate(type: "server", return_all: true)
793
+ return if siblings.nil? or (siblings.respond_to?(:empty?) and siblings.empty?)
1452
794
 
1453
795
  update_servers = []
1454
- if nodeclasses.nil? or nodeclasses.size == 0
1455
- litter = findLitterMate(type: "server", return_all: true)
1456
- return if litter.nil?
1457
- litter.each_pair { |mu_name, node|
1458
- if !triggering_node.nil? and (
1459
- (triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
1460
- (triggering_node.is_a?(String) and mu_name == triggering_node)
1461
- )
1462
- next
1463
- end
1464
-
1465
- if !node.groomer.nil?
1466
- update_servers << node
1467
- end
1468
- }
1469
- else
1470
- litter = {}
1471
- nodeclasses.each { |nodeclass|
1472
- mates = findLitterMate(type: "server", name: nodeclass, return_all: true)
1473
- litter.merge!(mates) if mates
1474
- }
1475
- litter.each_pair { |mu_name, node|
1476
- if !triggering_node.nil? and (
1477
- (triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
1478
- (triggering_node.is_a?(String) and mu_name == triggering_node)
1479
- )
1480
- next
1481
- end
1482
-
1483
- if !node.deploydata or !node.deploydata.keys.include?('nodename')
1484
- details = node.deploydata ? node.deploydata.keys : nil
1485
- MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::WARN, details: details
1486
- else
1487
- update_servers << node
1488
- end
1489
- }
1490
- end
1491
- return if update_servers.size == 0
1492
-
1493
- MU.log "Updating these nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
1494
-
1495
- update_servers.each { |node|
1496
- # Not clear where this pollution comes from, but let's stick a temp
1497
- # fix in here.
1498
- if node.deploydata['nodename'] != node.mu_name and
1499
- !node.deploydata['nodename'].nil? and !node.deploydata['nodename'].emty?
1500
- MU.log "Node #{node.mu_name} had wrong or missing nodename (#{node.deploydata['nodename']}), correcting", MU::WARN
1501
- node.deploydata['nodename'] = node.mu_name
1502
- if @deployment[svrs] and @deployment[svrs][node.config['name']] and
1503
- @deployment[svrs][node.config['name']][node.mu_name]
1504
- @deployment[svrs][node.config['name']][node.mu_name]['nodename'] = node.mu_name
1505
- end
1506
- save!
1507
- end
796
+ siblings.each_pair { |mu_name, node|
797
+ next if mu_name == triggering_node or node.groomer.nil?
798
+ next if nodeclasses.size > 0 and !nodeclasses.include?(node.config['name'])
799
+ if !node.deploydata or !node.deploydata['nodename']
800
+ MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::NOTICE
801
+ next
802
+ end
803
+
804
+ if @deployment["servers"][node.config['name']][node.mu_name].nil? or
805
+ @deployment["servers"][node.config['name']][node.mu_name] != node.deploydata
806
+ @deployment["servers"][node.config['name']][node.mu_name] = node.deploydata
807
+ elsif !save_only
808
+ # Don't bother running grooms on nodes that don't need to be updated,
809
+ # unless we're just going to do a save.
810
+ next
811
+ end
812
+ update_servers << node
1508
813
  }
1509
814
 
1510
- # Merge everyone's deploydata together
1511
- if !save_only
1512
- skip = []
1513
- update_servers.each { |node|
1514
- if node.mu_name.nil? or node.deploydata.nil? or node.config.nil?
1515
- MU.log "Missing mu_name #{node.mu_name}, deploydata, or config from #{node} in syncLitter", MU::ERR, details: node.deploydata
1516
- next
1517
- end
815
+ return if update_servers.empty?
1518
816
 
1519
- if !@deployment[svrs][node.config['name']].has_key?(node.mu_name) or @deployment[svrs][node.config['name']][node.mu_name] != node.deploydata
1520
- @deployment[svrs][node.config['name']][node.mu_name] = node.deploydata
1521
- else
1522
- skip << node
1523
- end
1524
- }
1525
- update_servers = update_servers - skip
1526
- end
817
+ MU.log "Updating nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
1527
818
 
1528
- return if MU.inGem? || update_servers.size < 1
1529
819
  threads = []
1530
- parent_thread_id = Thread.current.object_id
1531
820
  update_servers.each { |sibling|
821
+ next if sibling.config.has_key?("groom") and !sibling.config["groom"]
1532
822
  threads << Thread.new {
1533
823
  Thread.abort_on_exception = true
1534
- MU.dupGlobals(parent_thread_id)
1535
824
  Thread.current.thread_variable_set("name", "sync-"+sibling.mu_name.downcase)
1536
825
  MU.setVar("syncLitterThread", true)
1537
826
  begin
1538
- if sibling.config['groom'].nil? or sibling.config['groom']
1539
- sibling.groomer.saveDeployData
1540
- sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
1541
- end
827
+ sibling.groomer.saveDeployData
828
+ sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
1542
829
  rescue MU::Groomer::RunError => e
1543
- MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
830
+ MU.log "Sync of #{sibling.mu_name} failed", MU::WARN, details: e.inspect
1544
831
  end
1545
- MU.purgeGlobals
1546
832
  }
1547
833
  }
1548
834
 
@@ -1589,7 +875,7 @@ MESSAGE_END
1589
875
  end
1590
876
 
1591
877
  if resource and resource.config and resource.config['cloud']
1592
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource.config['cloud'])
878
+ cloudclass = MU::Cloud.cloudClass(resource.config['cloud'])
1593
879
 
1594
880
  cloudclass.writeDeploySecret(@deploy_id, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
1595
881
  cloudclass.writeDeploySecret(@deploy_id, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])