cloud-mu 3.1.4 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +16 -12
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -1
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +37 -12
  23. data/cloud-mu.gemspec +5 -3
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  27. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  28. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  29. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  30. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  31. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  32. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  33. data/extras/clean-stock-amis +25 -19
  34. data/extras/generate-stock-images +1 -0
  35. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  36. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  37. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  38. data/modules/mommacat.ru +1 -1
  39. data/modules/mu.rb +158 -107
  40. data/modules/mu/adoption.rb +386 -59
  41. data/modules/mu/cleanup.rb +214 -303
  42. data/modules/mu/cloud.rb +128 -1632
  43. data/modules/mu/cloud/database.rb +49 -0
  44. data/modules/mu/cloud/dnszone.rb +44 -0
  45. data/modules/mu/cloud/machine_images.rb +212 -0
  46. data/modules/mu/cloud/providers.rb +81 -0
  47. data/modules/mu/cloud/resource_base.rb +926 -0
  48. data/modules/mu/cloud/server.rb +40 -0
  49. data/modules/mu/cloud/server_pool.rb +1 -0
  50. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  51. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  52. data/modules/mu/cloud/wrappers.rb +169 -0
  53. data/modules/mu/config.rb +135 -82
  54. data/modules/mu/config/alarm.rb +2 -6
  55. data/modules/mu/config/bucket.rb +32 -3
  56. data/modules/mu/config/cache_cluster.rb +2 -2
  57. data/modules/mu/config/cdn.rb +100 -0
  58. data/modules/mu/config/collection.rb +1 -1
  59. data/modules/mu/config/container_cluster.rb +7 -2
  60. data/modules/mu/config/database.rb +84 -105
  61. data/modules/mu/config/database.yml +1 -2
  62. data/modules/mu/config/dnszone.rb +5 -4
  63. data/modules/mu/config/doc_helpers.rb +5 -6
  64. data/modules/mu/config/endpoint.rb +2 -1
  65. data/modules/mu/config/firewall_rule.rb +3 -19
  66. data/modules/mu/config/folder.rb +1 -1
  67. data/modules/mu/config/function.rb +17 -8
  68. data/modules/mu/config/group.rb +1 -1
  69. data/modules/mu/config/habitat.rb +1 -1
  70. data/modules/mu/config/job.rb +89 -0
  71. data/modules/mu/config/loadbalancer.rb +57 -11
  72. data/modules/mu/config/log.rb +1 -1
  73. data/modules/mu/config/msg_queue.rb +1 -1
  74. data/modules/mu/config/nosqldb.rb +1 -1
  75. data/modules/mu/config/notifier.rb +8 -19
  76. data/modules/mu/config/ref.rb +92 -14
  77. data/modules/mu/config/role.rb +1 -1
  78. data/modules/mu/config/schema_helpers.rb +38 -37
  79. data/modules/mu/config/search_domain.rb +1 -1
  80. data/modules/mu/config/server.rb +12 -13
  81. data/modules/mu/config/server.yml +1 -0
  82. data/modules/mu/config/server_pool.rb +3 -7
  83. data/modules/mu/config/storage_pool.rb +1 -1
  84. data/modules/mu/config/tail.rb +11 -0
  85. data/modules/mu/config/user.rb +1 -1
  86. data/modules/mu/config/vpc.rb +27 -23
  87. data/modules/mu/config/vpc.yml +0 -1
  88. data/modules/mu/defaults/AWS.yaml +91 -68
  89. data/modules/mu/defaults/Azure.yaml +1 -0
  90. data/modules/mu/defaults/Google.yaml +1 -0
  91. data/modules/mu/deploy.rb +33 -19
  92. data/modules/mu/groomer.rb +16 -1
  93. data/modules/mu/groomers/ansible.rb +123 -21
  94. data/modules/mu/groomers/chef.rb +64 -11
  95. data/modules/mu/logger.rb +120 -144
  96. data/modules/mu/master.rb +97 -4
  97. data/modules/mu/master/ssl.rb +0 -1
  98. data/modules/mu/mommacat.rb +154 -867
  99. data/modules/mu/mommacat/daemon.rb +23 -14
  100. data/modules/mu/mommacat/naming.rb +110 -3
  101. data/modules/mu/mommacat/search.rb +495 -0
  102. data/modules/mu/mommacat/storage.rb +225 -192
  103. data/modules/mu/{clouds → providers}/README.md +1 -1
  104. data/modules/mu/{clouds → providers}/aws.rb +281 -64
  105. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  106. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  107. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  108. data/modules/mu/providers/aws/cdn.rb +782 -0
  109. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  110. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +708 -749
  111. data/modules/mu/providers/aws/database.rb +1744 -0
  112. data/modules/mu/{clouds → providers}/aws/dnszone.rb +75 -57
  113. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  114. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +212 -242
  115. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  116. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  117. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  118. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  119. data/modules/mu/providers/aws/job.rb +466 -0
  120. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +50 -41
  121. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  122. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  123. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  124. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  125. data/modules/mu/{clouds → providers}/aws/role.rb +94 -57
  126. data/modules/mu/{clouds → providers}/aws/search_domain.rb +173 -42
  127. data/modules/mu/{clouds → providers}/aws/server.rb +782 -1107
  128. data/modules/mu/{clouds → providers}/aws/server_pool.rb +36 -46
  129. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  130. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  131. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  132. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  133. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  134. data/modules/mu/{clouds → providers}/aws/vpc.rb +429 -849
  135. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  136. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  137. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  138. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  139. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  140. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  141. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  142. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  143. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  144. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  145. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  146. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  147. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  148. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  149. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  150. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  151. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  152. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  153. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  156. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  158. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  159. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  160. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  161. data/modules/mu/{clouds → providers}/google.rb +29 -6
  162. data/modules/mu/{clouds → providers}/google/bucket.rb +5 -5
  163. data/modules/mu/{clouds → providers}/google/container_cluster.rb +59 -37
  164. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  165. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  166. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  167. data/modules/mu/{clouds → providers}/google/function.rb +14 -8
  168. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  169. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  170. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  171. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  172. data/modules/mu/{clouds → providers}/google/server.rb +142 -55
  173. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  174. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  175. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  176. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  177. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  178. data/modules/mu/{clouds → providers}/google/vpc.rb +46 -15
  179. data/modules/tests/aws-jobs-functions.yaml +46 -0
  180. data/modules/tests/centos6.yaml +15 -0
  181. data/modules/tests/centos7.yaml +15 -0
  182. data/modules/tests/centos8.yaml +12 -0
  183. data/modules/tests/ecs.yaml +23 -0
  184. data/modules/tests/eks.yaml +1 -1
  185. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  186. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/microservice_app.yaml +288 -0
  189. data/modules/tests/rds.yaml +108 -0
  190. data/modules/tests/regrooms/rds.yaml +123 -0
  191. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  192. data/modules/tests/super_complex_bok.yml +2 -2
  193. data/modules/tests/super_simple_bok.yml +3 -5
  194. data/modules/tests/win2k12.yaml +25 -0
  195. data/modules/tests/win2k16.yaml +25 -0
  196. data/modules/tests/win2k19.yaml +25 -0
  197. data/requirements.txt +1 -0
  198. data/spec/mu/clouds/azure_spec.rb +2 -2
  199. metadata +169 -93
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1974
  202. data/modules/mu/clouds/aws/endpoint.rb +0 -596
  203. data/modules/tests/needwork/win2k12.yaml +0 -13
@@ -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
@@ -160,7 +160,6 @@ module MU
160
160
 
161
161
  key = getKey(name, for_user: for_user)
162
162
 
163
- puts cn_str
164
163
  cn = OpenSSL::X509::Name.parse(cn_str)
165
164
 
166
165
  # If we're generating our local CA, we're not really doing a CSR, but
@@ -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,656 +537,124 @@ 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
- else
980
- try = nil
981
- [:display_name, :name, (resourceclass.cfg_name+"_name").to_sym].each { |field|
982
- if descriptor.respond_to?(field) and descriptor.send(field).is_a?(String)
983
- try = descriptor.send(field)
984
- break
985
- end
986
-
987
- }
988
- try ||= if !tag_value.nil?
989
- tag_value
990
- else
991
- kitten_cloud_id
992
- end
993
- try
994
- end
995
- else
996
- name
997
- end
998
- if use_name.nil?
999
- 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
1000
- next
1001
- end
1002
- cfg = {
1003
- "name" => use_name,
1004
- "cloud" => cloud,
1005
- "credentials" => creds
1006
- }
1007
- if !r.nil? and !resourceclass.isGlobal?
1008
- cfg["region"] = r
1009
- end
1010
-
1011
- if !p.nil? and resourceclass.canLiveIn.include?(:Habitat)
1012
- cfg["project"] = p
1013
- end
1014
- # If we can at least find the config from the deploy this will
1015
- # belong with, use that, even if it's an ungroomed resource.
1016
- if !calling_deploy.nil? and
1017
- !calling_deploy.original_config.nil? and
1018
- !calling_deploy.original_config[type+"s"].nil?
1019
- calling_deploy.original_config[type+"s"].each { |s|
1020
- if s["name"] == use_name
1021
- cfg = s.dup
1022
- break
1023
- end
1024
- }
1025
-
1026
- newkitten = resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
1027
- desc_semaphore.synchronize {
1028
- matches << newkitten
1029
- }
1030
- else
1031
- if !@@dummy_cache[cfg_plural] or !@@dummy_cache[cfg_plural][cfg.to_s]
1032
- 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
1033
- resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
1034
- desc_semaphore.synchronize {
1035
- @@dummy_cache[cfg_plural] ||= {}
1036
- @@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)
1037
- MU.log "findStray: Finished generating dummy '#{resourceclass.to_s}' cloudobj - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1038
- }
1039
- end
1040
- desc_semaphore.synchronize {
1041
- matches << @@dummy_cache[cfg_plural][cfg.to_s]
1042
- }
1043
- end
1044
- end
1045
- }
1046
- } }
1047
- MU.log "findStray: tying up #{region_threads.size.to_s} region threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1048
- region_threads.each { |t|
1049
- t.join
1050
- }
1051
- } }
1052
- MU.log "findStray: tying up #{habitat_threads.size.to_s} habitat threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1053
- habitat_threads.each { |t|
1054
- t.join
1055
- }
1056
- end
1057
- }
1058
- rescue StandardError => e
1059
- MU.log e.inspect, MU::ERR, details: e.backtrace
1060
- end
1061
- MU.log "findStray: returning #{matches ? matches.size.to_s : "0"} matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1062
-
1063
- matches
1064
- end
1065
-
1066
- # Return the resource object of another member of this deployment
1067
- # @param type [String,Symbol]: The type of resource
1068
- # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field
1069
- # @param mu_name [String]: The fully-resolved and deployed name of the resource
1070
- # @param cloud_id [String]: The cloud provider's unique identifier for this resource
1071
- # @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
1072
- # @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.
1073
- # @return [MU::Cloud]
1074
- 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: "")
1075
- shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1076
- type = cfg_plural
1077
- has_multiples = attrs[:has_multiples]
1078
-
1079
- loglevel = debug ? MU::NOTICE : MU::DEBUG
1080
-
1081
- argstring = [:type, :name, :mu_name, :cloud_id, :created_only, :credentials, :habitat, :has_multiples].reject { |a|
1082
- binding.local_variable_get(a).nil?
1083
- }.map { |v|
1084
- v.to_s+": "+binding.local_variable_get(v).to_s
1085
- }.join(", ")
1086
-
1087
- # Fun times: if we specified a habitat, which we may also have done by
1088
- # its shorthand sibling name, let's... call ourselves first to make sure
1089
- # we're fishing for the right thing.
1090
- if habitat
1091
- if habitat.is_a?(MU::Config::Ref) and habitat.id
1092
- habitat = habitat.id
1093
- else
1094
- MU.log indent+"findLitterMate(#{argstring}): Attempting to resolve habitat name #{habitat}", loglevel
1095
- realhabitat = findLitterMate(type: "habitat", name: habitat, debug: debug, credentials: credentials, indent: indent+" ")
1096
- if realhabitat and realhabitat.mu_name
1097
- MU.log indent+"findLitterMate: Resolved habitat name #{habitat} to #{realhabitat.mu_name}", loglevel, details: [realhabitat.mu_name, realhabitat.cloud_id, realhabitat.config.keys]
1098
- habitat = realhabitat.cloud_id
1099
- elsif debug
1100
- MU.log indent+"findLitterMate(#{argstring}): Failed to resolve habitat name #{habitat}", MU::WARN
1101
- end
1102
- end
1103
- end
1104
-
1105
-
1106
- @kitten_semaphore.synchronize {
1107
- if !@kittens.has_key?(type)
1108
- if debug
1109
- MU.log indent+"NO SUCH KEY #{type} findLitterMate(#{argstring})", MU::WARN, details: @kittens.keys
1110
- end
1111
- return nil
1112
- end
1113
- MU.log indent+"START findLitterMate(#{argstring}), caller: #{caller[2]}", loglevel, details: @kittens[type].keys.map { |hab| hab.to_s+": "+@kittens[type][hab].keys.join(", ") }
1114
- matches = []
1115
-
1116
- @kittens[type].each { |habitat_group, sib_classes|
1117
- next if habitat and habitat_group != habitat and !habitat_group.nil?
1118
- sib_classes.each_pair { |sib_class, data|
1119
- virtual_name = nil
1120
-
1121
- 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']
1122
- virtual_name = data.config['virtual_name']
1123
- elsif !name.nil? and name != sib_class
1124
- next
1125
- end
1126
- if has_multiples
1127
- if !name.nil?
1128
- if return_all
1129
- MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
1130
- return data.dup
1131
- end
1132
- if data.size == 1 and (cloud_id.nil? or data.values.first.cloud_id == cloud_id)
1133
- return data.values.first
1134
- elsif mu_name.nil? and cloud_id.nil?
1135
- 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
1136
- return data.values.first
1137
- end
1138
- end
1139
- data.each_pair { |sib_mu_name, obj|
1140
- if (!mu_name.nil? and mu_name == sib_mu_name) or
1141
- (!cloud_id.nil? and cloud_id == obj.cloud_id) or
1142
- (!credentials.nil? and credentials == obj.credentials)
1143
- if !created_only or !obj.cloud_id.nil?
1144
- if return_all
1145
- MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
1146
- return data.dup
1147
- else
1148
- MU.log indent+"MULTI-MATCH findLitterMate(#{argstring})", loglevel, details: data.keys
1149
- return obj
1150
- end
1151
- end
1152
- end
1153
- }
1154
- else
1155
-
1156
- 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
1157
-
1158
- data_cloud_id = data.cloud_id.nil? ? nil : data.cloud_id.to_s
1159
-
1160
- 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
1161
- 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
1162
- 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
1163
-
1164
- if (name.nil? or sib_class == name.to_s or virtual_name == name.to_s) and
1165
- (cloud_id.nil? or cloud_id.to_s == data_cloud_id) and
1166
- (credentials.nil? or data.credentials.nil? or credentials.to_s == data.credentials.to_s)
1167
- 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
1168
- if !created_only or !data_cloud_id.nil?
1169
- MU.log indent+"SINGLE MATCH findLitterMate(#{argstring})", loglevel, details: [data.mu_name, data_cloud_id, data.config.keys]
1170
- matches << data
1171
- end
1172
- end
1173
- end
1174
- }
1175
- }
1176
-
1177
- return matches.first if matches.size == 1
1178
- if return_all and matches.size > 1
1179
- return matches
1180
- end
1181
- }
1182
-
1183
- MU.log indent+"NO MATCH findLitterMate(#{argstring})", loglevel
1184
-
1185
- return nil
1186
- end
1187
-
1188
540
  # Add or remove a resource's metadata to this deployment's structure and
1189
541
  # flush it to disk.
1190
542
  # @param type [String]: The type of resource (e.g. *server*, *database*).
1191
543
  # @param key [String]: The name field of this resource.
544
+ # @param mu_name [String]: The mu_name of this resource.
1192
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
1193
547
  # @param remove [Boolean]: Remove this resource from the deploy structure, instead of adding it.
1194
548
  # @return [void]
1195
549
  def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
1196
- return if @no_artifacts
1197
- MU::MommaCat.lock("deployment-notification")
1198
550
 
1199
- if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
1200
- loadDeploy(true) # make sure we're saving the latest and greatest
1201
- end
551
+ begin
552
+ MU::MommaCat.lock("deployment-notification", deploy_id: @deploy_id) if !@no_artifacts
1202
553
 
1203
- _shortclass, _cfg_name, cfg_plural, _classname, attrs = MU::Cloud.getResourceNames(type)
1204
- has_multiples = false
554
+ if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
555
+ loadDeploy(true) # make sure we're saving the latest and greatest
556
+ end
1205
557
 
1206
- # it's not always the case that we're logging data for a legal resource
1207
- # type, though that's what we're usually for
1208
- if cfg_plural
1209
- type = cfg_plural
1210
- has_multiples = attrs[:has_multiples]
1211
- end
558
+ _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type, false)
559
+ has_multiples = attrs[:has_multiples] ? true : false
1212
560
 
1213
- if mu_name.nil?
1214
- if !data.nil? and !data["mu_name"].nil?
1215
- mu_name = data["mu_name"]
561
+ mu_name ||= if !data.nil? and !data["mu_name"].nil?
562
+ data["mu_name"]
1216
563
  elsif !triggering_node.nil? and !triggering_node.mu_name.nil?
1217
- mu_name = triggering_node.mu_name
564
+ triggering_node.mu_name
1218
565
  end
1219
566
  if mu_name.nil? and has_multiples
1220
- 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
1221
- MU::MommaCat.unlock("deployment-notification")
567
+ 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
1222
568
  return
1223
569
  end
1224
- end
1225
570
 
1226
- @need_deploy_flush = true
571
+ @need_deploy_flush = true
1227
572
 
1228
- if !remove
1229
- if data.nil?
1230
- MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
1231
- MU::MommaCat.unlock("deployment-notification")
1232
- return
1233
- end
1234
- @notify_semaphore.synchronize {
1235
- @deployment[type] ||= {}
1236
- }
1237
- if has_multiples
573
+ if !remove
574
+ if data.nil?
575
+ MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
576
+ return
577
+ end
1238
578
  @notify_semaphore.synchronize {
1239
- @deployment[type][key] ||= {}
579
+ @deployment[type] ||= {}
1240
580
  }
1241
- # fix has_multiples classes that weren't tiered correctly
1242
- if @deployment[type][key].is_a?(Hash) and @deployment[type][key].has_key?("mu_name")
1243
- olddata = @deployment[type][key].dup
1244
- @deployment[type][key][olddata["mu_name"]] = olddata
1245
- end
1246
- @deployment[type][key][mu_name] = data
1247
- MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
1248
- else
1249
- @deployment[type][key] = data
1250
- MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
1251
- end
1252
- save!(key) if !delayed_save
1253
- else
1254
- have_deploy = true
1255
- if @deployment[type].nil? or @deployment[type][key].nil?
1256
-
1257
581
  if has_multiples
1258
- MU.log "MU::MommaCat.notify called to remove #{type} #{key} #{mu_name} deployment struct, but no such data exist", MU::DEBUG
582
+ @notify_semaphore.synchronize {
583
+ @deployment[type][key] ||= {}
584
+ }
585
+ @deployment[type][key][mu_name] = data
586
+ MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
1259
587
  else
1260
- MU.log "MU::MommaCat.notify called to remove #{type} #{key} deployment struct, but no such data exist", MU::DEBUG
588
+ @deployment[type][key] = data
589
+ MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
590
+ end
591
+ save!(key) if !delayed_save and !@no_artifacts
592
+ else
593
+ have_deploy = true
594
+ if @deployment[type].nil? or @deployment[type][key].nil?
595
+ MU.log "MU::MommaCat.notify called to remove #{type} #{key}#{has_multiples ? " "+mu_name : ""} deployment struct, but no such data exist", MU::DEBUG
596
+ return
1261
597
  end
1262
- MU::MommaCat.unlock("deployment-notification")
1263
598
 
1264
- return
1265
- end
599
+ if have_deploy
600
+ @notify_semaphore.synchronize {
601
+ if has_multiples
602
+ MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
603
+ @deployment[type][key].delete(mu_name)
604
+ end
1266
605
 
1267
- if have_deploy
1268
- @notify_semaphore.synchronize {
1269
- if has_multiples
1270
- MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
1271
- @deployment[type][key].delete(mu_name)
1272
- if @deployment[type][key].size == 0
606
+ if @deployment[type][key].empty? or !has_multiples
607
+ MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1273
608
  @deployment[type].delete(key)
1274
609
  end
1275
- else
1276
- MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1277
- @deployment[type].delete(key)
1278
- end
1279
- if @deployment[type].size == 0
1280
- @deployment.delete(type)
1281
- end
1282
- }
1283
- end
1284
- save! if !delayed_save
1285
610
 
611
+ if @deployment[type].empty?
612
+ @deployment.delete(type)
613
+ end
614
+ }
615
+ end
616
+ save! if !delayed_save and !@no_artifacts
617
+ end
618
+ ensure
619
+ MU::MommaCat.unlock("deployment-notification", deploy_id: @deploy_id) if !@no_artifacts
1286
620
  end
1287
-
1288
- MU::MommaCat.unlock("deployment-notification")
1289
621
  end
1290
622
 
1291
623
  # Send a Slack notification to a deployment's administrators.
1292
624
  # @param subject [String]: The subject line of the message.
1293
625
  # @param msg [String]: The message body.
1294
626
  # @return [void]
1295
- def sendAdminSlack(subject, msg: "")
1296
- if $MU_CFG['slack'] and $MU_CFG['slack']['webhook'] and
1297
- (!$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 })
1298
630
  require 'slack-notifier'
1299
- 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
1300
642
 
1301
- if msg and !msg.empty?
1302
- slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}:\n\n```#{msg}\n```", channel: $MU_CFG['slack']['channel']
1303
- else
1304
- 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
1305
655
  end
1306
656
  end
657
+ true
1307
658
  end
1308
659
 
1309
660
  # Send an email notification to a deployment's administrators.
@@ -1324,13 +675,13 @@ module MU
1324
675
  to << "#{admin['name']} <#{admin['email']}>"
1325
676
  }
1326
677
  end
1327
- message = <<MESSAGE_END
678
+ message = <<MAIL_HEAD_END
1328
679
  From: #{MU.handle} <root@localhost>
1329
680
  To: #{to.join(",")}
1330
681
  Subject: #{subject}
1331
682
 
1332
683
  #{msg}
1333
- MESSAGE_END
684
+ MAIL_HEAD_END
1334
685
  if !kitten.nil? and kitten.kind_of?(MU::Cloud)
1335
686
  message = message + "\n\n**** #{kitten}:\n"
1336
687
  if !kitten.report.nil?
@@ -1418,124 +769,59 @@ MESSAGE_END
1418
769
  MU::Master::SSL.sign(csr_path, sans, for_user: MU.mu_user)
1419
770
  end
1420
771
 
1421
- # Make sure deployment data is synchronized to/from each node in the
772
+ # Make sure deployment data is synchronized to/from each +Server+ in the
1422
773
  # currently-loaded deployment.
774
+ # @param nodeclasses [Array<String>]
775
+ # @param triggering_node [String,MU::Cloud::Server]
776
+ # @param save_only [Boolean]
1423
777
  def syncLitter(nodeclasses = [], triggering_node: nil, save_only: false)
1424
- # XXX take some config logic to decide what nodeclasses to hit? like, make
1425
- # inferences from dependencies or something?
1426
-
1427
- return if MU.syncLitterThread
778
+ return if MU.syncLitterThread # don't run recursively by accident
1428
779
  return if !Dir.exist?(deploy_dir)
1429
- svrs = MU::Cloud.resource_types[:Server][:cfg_plural] # legibility shorthand
1430
- if !triggering_node.nil? and nodeclasses.size > 0
1431
- nodeclasses.reject! { |n| n == triggering_node.to_s }
1432
- return if nodeclasses.size == 0
1433
- end
1434
-
1435
- @kitten_semaphore.synchronize {
1436
- if @kittens.nil? or
1437
- @kittens[svrs].nil?
1438
- MU.log "No #{svrs} as yet available in #{@deploy_id}", MU::DEBUG, details: @kittens
1439
- return
1440
- end
1441
780
 
781
+ if !triggering_node.nil? and triggering_node.is_a?(MU::Cloud::Server)
782
+ triggering_node = triggering_node.mu_name
783
+ end
1442
784
 
1443
- MU.log "Updating these node classes in #{@deploy_id}", MU::DEBUG, details: nodeclasses
1444
- }
785
+ siblings = findLitterMate(type: "server", return_all: true)
786
+ return if siblings.nil? or (siblings.respond_to?(:empty?) and siblings.empty?)
1445
787
 
1446
788
  update_servers = []
1447
- if nodeclasses.nil? or nodeclasses.size == 0
1448
- litter = findLitterMate(type: "server", return_all: true)
1449
- return if litter.nil?
1450
- litter.each_pair { |mu_name, node|
1451
- if !triggering_node.nil? and (
1452
- (triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
1453
- (triggering_node.is_a?(String) and mu_name == triggering_node)
1454
- )
1455
- next
1456
- end
1457
-
1458
- if !node.groomer.nil?
1459
- update_servers << node
1460
- end
1461
- }
1462
- else
1463
- litter = {}
1464
- nodeclasses.each { |nodeclass|
1465
- mates = findLitterMate(type: "server", name: nodeclass, return_all: true)
1466
- litter.merge!(mates) if mates
1467
- }
1468
- litter.each_pair { |mu_name, node|
1469
- if !triggering_node.nil? and (
1470
- (triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
1471
- (triggering_node.is_a?(String) and mu_name == triggering_node)
1472
- )
1473
- next
1474
- end
1475
-
1476
- if !node.deploydata or !node.deploydata.keys.include?('nodename')
1477
- details = node.deploydata ? node.deploydata.keys : nil
1478
- MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::WARN, details: details
1479
- else
1480
- update_servers << node
1481
- end
1482
- }
1483
- end
1484
- return if update_servers.size == 0
1485
-
1486
- MU.log "Updating these nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
1487
-
1488
- update_servers.each { |node|
1489
- # Not clear where this pollution comes from, but let's stick a temp
1490
- # fix in here.
1491
- if node.deploydata['nodename'] != node.mu_name and
1492
- !node.deploydata['nodename'].nil? and !node.deploydata['nodename'].emty?
1493
- MU.log "Node #{node.mu_name} had wrong or missing nodename (#{node.deploydata['nodename']}), correcting", MU::WARN
1494
- node.deploydata['nodename'] = node.mu_name
1495
- if @deployment[svrs] and @deployment[svrs][node.config['name']] and
1496
- @deployment[svrs][node.config['name']][node.mu_name]
1497
- @deployment[svrs][node.config['name']][node.mu_name]['nodename'] = node.mu_name
1498
- end
1499
- save!
1500
- end
789
+ siblings.each_pair { |mu_name, node|
790
+ next if mu_name == triggering_node or node.groomer.nil?
791
+ next if nodeclasses.size > 0 and !nodeclasses.include?(node.config['name'])
792
+ if !node.deploydata or !node.deploydata['nodename']
793
+ MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::NOTICE
794
+ next
795
+ end
796
+
797
+ if @deployment["servers"][node.config['name']][node.mu_name].nil? or
798
+ @deployment["servers"][node.config['name']][node.mu_name] != node.deploydata
799
+ @deployment["servers"][node.config['name']][node.mu_name] = node.deploydata
800
+ elsif !save_only
801
+ # Don't bother running grooms on nodes that don't need to be updated,
802
+ # unless we're just going to do a save.
803
+ next
804
+ end
805
+ update_servers << node
1501
806
  }
1502
807
 
1503
- # Merge everyone's deploydata together
1504
- if !save_only
1505
- skip = []
1506
- update_servers.each { |node|
1507
- if node.mu_name.nil? or node.deploydata.nil? or node.config.nil?
1508
- MU.log "Missing mu_name #{node.mu_name}, deploydata, or config from #{node} in syncLitter", MU::ERR, details: node.deploydata
1509
- next
1510
- end
808
+ return if update_servers.empty?
1511
809
 
1512
- if !@deployment[svrs][node.config['name']].has_key?(node.mu_name) or @deployment[svrs][node.config['name']][node.mu_name] != node.deploydata
1513
- @deployment[svrs][node.config['name']][node.mu_name] = node.deploydata
1514
- else
1515
- skip << node
1516
- end
1517
- }
1518
- update_servers = update_servers - skip
1519
- end
810
+ MU.log "Updating nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
1520
811
 
1521
- return if MU.inGem? || update_servers.size < 1
1522
812
  threads = []
1523
- parent_thread_id = Thread.current.object_id
1524
813
  update_servers.each { |sibling|
814
+ next if sibling.config.has_key?("groom") and !sibling.config["groom"]
1525
815
  threads << Thread.new {
1526
816
  Thread.abort_on_exception = true
1527
- MU.dupGlobals(parent_thread_id)
1528
817
  Thread.current.thread_variable_set("name", "sync-"+sibling.mu_name.downcase)
1529
818
  MU.setVar("syncLitterThread", true)
1530
819
  begin
1531
- if sibling.config['groom'].nil? or sibling.config['groom']
1532
- sibling.groomer.saveDeployData
1533
- sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
1534
- end
820
+ sibling.groomer.saveDeployData
821
+ sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
1535
822
  rescue MU::Groomer::RunError => e
1536
- MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
823
+ MU.log "Sync of #{sibling.mu_name} failed", MU::WARN, details: e.inspect
1537
824
  end
1538
- MU.purgeGlobals
1539
825
  }
1540
826
  }
1541
827
 
@@ -1568,6 +854,7 @@ MESSAGE_END
1568
854
  MU::Master::SSL.bootstrap
1569
855
  sans = []
1570
856
  sans << canonical_ip if canonical_ip
857
+ sans << resource.mu_name.downcase if resource.mu_name and resource.mu_name != cert_cn
1571
858
  # XXX were there other names we wanted to include?
1572
859
  key = MU::Master::SSL.getKey(cert_cn, keysize: keysize)
1573
860
  cert, pfx_cert = MU::Master::SSL.getCert(cert_cn, "/CN=#{cert_cn}/O=Mu/C=US", sans: sans, pfx: is_windows)
@@ -1581,7 +868,7 @@ MESSAGE_END
1581
868
  end
1582
869
 
1583
870
  if resource and resource.config and resource.config['cloud']
1584
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource.config['cloud'])
871
+ cloudclass = MU::Cloud.cloudClass(resource.config['cloud'])
1585
872
 
1586
873
  cloudclass.writeDeploySecret(@deploy_id, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
1587
874
  cloudclass.writeDeploySecret(@deploy_id, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])