cloud-mu 3.1.5 → 3.1.6

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 (61) 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 +2 -1
  7. data/bin/mu-configure +16 -0
  8. data/bin/mu-node-manage +15 -16
  9. data/cloud-mu.gemspec +2 -2
  10. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  11. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  12. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  13. data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
  14. data/extras/clean-stock-amis +25 -19
  15. data/extras/image-generators/AWS/win2k12.yaml +2 -0
  16. data/extras/image-generators/AWS/win2k16.yaml +2 -0
  17. data/extras/image-generators/AWS/win2k19.yaml +2 -0
  18. data/modules/mommacat.ru +1 -1
  19. data/modules/mu.rb +6 -5
  20. data/modules/mu/adoption.rb +19 -4
  21. data/modules/mu/cleanup.rb +181 -293
  22. data/modules/mu/cloud.rb +58 -17
  23. data/modules/mu/clouds/aws.rb +36 -1
  24. data/modules/mu/clouds/aws/container_cluster.rb +30 -21
  25. data/modules/mu/clouds/aws/role.rb +1 -1
  26. data/modules/mu/clouds/aws/vpc.rb +5 -1
  27. data/modules/mu/clouds/azure.rb +10 -0
  28. data/modules/mu/clouds/cloudformation.rb +10 -0
  29. data/modules/mu/clouds/google.rb +18 -4
  30. data/modules/mu/clouds/google/bucket.rb +2 -2
  31. data/modules/mu/clouds/google/container_cluster.rb +10 -7
  32. data/modules/mu/clouds/google/database.rb +3 -3
  33. data/modules/mu/clouds/google/firewall_rule.rb +3 -3
  34. data/modules/mu/clouds/google/function.rb +3 -3
  35. data/modules/mu/clouds/google/loadbalancer.rb +4 -4
  36. data/modules/mu/clouds/google/role.rb +18 -9
  37. data/modules/mu/clouds/google/server.rb +16 -14
  38. data/modules/mu/clouds/google/server_pool.rb +4 -4
  39. data/modules/mu/clouds/google/user.rb +2 -2
  40. data/modules/mu/clouds/google/vpc.rb +9 -13
  41. data/modules/mu/config.rb +1 -1
  42. data/modules/mu/config/container_cluster.rb +5 -0
  43. data/modules/mu/config/doc_helpers.rb +1 -1
  44. data/modules/mu/config/ref.rb +12 -6
  45. data/modules/mu/config/schema_helpers.rb +8 -3
  46. data/modules/mu/config/server.rb +7 -0
  47. data/modules/mu/config/tail.rb +1 -0
  48. data/modules/mu/config/vpc.rb +15 -7
  49. data/modules/mu/config/vpc.yml +0 -1
  50. data/modules/mu/defaults/AWS.yaml +48 -48
  51. data/modules/mu/deploy.rb +1 -1
  52. data/modules/mu/groomer.rb +1 -1
  53. data/modules/mu/groomers/ansible.rb +69 -4
  54. data/modules/mu/groomers/chef.rb +48 -4
  55. data/modules/mu/master.rb +75 -3
  56. data/modules/mu/mommacat.rb +104 -855
  57. data/modules/mu/mommacat/naming.rb +28 -0
  58. data/modules/mu/mommacat/search.rb +463 -0
  59. data/modules/mu/mommacat/storage.rb +185 -183
  60. data/modules/tests/super_simple_bok.yml +1 -3
  61. metadata +8 -5
@@ -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
@@ -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|
@@ -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
@@ -187,54 +188,10 @@ module MU
187
188
  end
188
189
 
189
190
  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
-
191
+ initDeployDirectory
192
+ setDeploySecret
193
+ MU::MommaCat.setThreadContext(self) if set_context_to_me
236
194
  save!
237
-
238
195
  end
239
196
 
240
197
  @appname ||= MU.appname
@@ -242,10 +199,8 @@ module MU
242
199
  @environment ||= MU.environment
243
200
 
244
201
  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
202
+ if !deploy_secret.nil? and !authKey(deploy_secret)
203
+ raise DeployInitializeError, "Client request did not include a valid deploy authorization secret. Verify that userdata runs correctly?"
249
204
  end
250
205
 
251
206
 
@@ -257,86 +212,7 @@ module MU
257
212
  # deploy, IF it already exists, which is to say if we're loading an
258
213
  # existing deploy instead of creating a new one.
259
214
  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
- }
215
+ loadObjects(delay_descriptor_load)
340
216
  end
341
217
 
342
218
  @initializing = false
@@ -349,7 +225,7 @@ module MU
349
225
  def cloudsUsed
350
226
  seen = []
351
227
  seen << @original_config['cloud'] if @original_config['cloud']
352
- MU::Cloud.resource_types.values.each { |attrs|
228
+ MU::Cloud.resource_types.each_value { |attrs|
353
229
  type = attrs[:cfg_plural]
354
230
  if @original_config[type]
355
231
  @original_config[type].each { |resource|
@@ -369,18 +245,15 @@ module MU
369
245
  # clouds = []
370
246
  seen << @original_config['credentials'] if @original_config['credentials']
371
247
  # defaultcloud = @original_config['cloud']
372
- MU::Cloud.resource_types.values.each { |attrs|
248
+ MU::Cloud.resource_types.each_value { |attrs|
373
249
  type = attrs[:cfg_plural]
374
250
  if @original_config[type]
375
251
  @original_config[type].each { |resource|
376
252
  if resource['credentials']
377
253
  seen << resource['credentials']
378
254
  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
255
+ cloudconst = @original_config['cloud'] ? @original_config['cloud'] : MU::Config.defaultCloud
256
+ Object.const_get("MU").const_get("Cloud").const_get(cloudconst)
384
257
  seen << cloudclass.credConfig(name_only: true)
385
258
  end
386
259
  }
@@ -404,7 +277,7 @@ module MU
404
277
  end
405
278
  end
406
279
 
407
- MU::Cloud.resource_types.values.each { |attrs|
280
+ MU::Cloud.resource_types.each_value { |attrs|
408
281
  type = attrs[:cfg_plural]
409
282
  if @original_config[type]
410
283
  @original_config[type].each { |resource|
@@ -481,7 +354,7 @@ module MU
481
354
  end
482
355
 
483
356
  count = 0
484
- MU::Cloud.resource_types.values.each { |data|
357
+ MU::Cloud.resource_types.each_value { |data|
485
358
  next if @original_config[data[:cfg_plural]].nil?
486
359
  next if realtypes.size > 0 and (!negate and !realtypes.include?(data[:cfg_plural]))
487
360
  @original_config[data[:cfg_plural]].each { |resource|
@@ -499,13 +372,13 @@ module MU
499
372
  raise MuError, "Nil arguments to removeKitten are not allowed"
500
373
  end
501
374
  @kitten_semaphore.synchronize {
502
- MU::Cloud.resource_types.values.each { |attrs|
375
+ MU::Cloud.resource_types.each_value { |attrs|
503
376
  type = attrs[:cfg_plural]
504
377
  next if !@kittens.has_key?(type)
505
378
  tmplitter = @kittens[type].values.dup
506
379
  tmplitter.each { |nodeclass, data|
507
380
  if data.is_a?(Hash)
508
- data.keys.each { |mu_name|
381
+ data.each_key { |mu_name|
509
382
  if data == object
510
383
  @kittens[type][nodeclass].delete(mu_name)
511
384
  return
@@ -534,13 +407,12 @@ module MU
534
407
  end
535
408
 
536
409
  _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
537
- has_multiples = attrs[:has_multiples]
538
410
  object.intoDeploy(self)
539
411
 
540
412
  @kitten_semaphore.synchronize {
541
413
  @kittens[type] ||= {}
542
414
  @kittens[type][object.habitat] ||= {}
543
- if has_multiples
415
+ if attrs[:has_multiples]
544
416
  @kittens[type][object.habitat][name] ||= {}
545
417
  @kittens[type][object.habitat][name][object.mu_name] = object
546
418
  else
@@ -577,7 +449,7 @@ module MU
577
449
  loadDeploy(true) # make sure we're not trampling deployment data
578
450
  @secret_semaphore.synchronize {
579
451
  if @secrets[type].nil?
580
- raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
452
+ raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.join(", ")})"
581
453
  end
582
454
  @secrets[type][instance_id] = encryptWithDeployKey(raw_secret)
583
455
  }
@@ -593,7 +465,7 @@ module MU
593
465
  @secret_semaphore.synchronize {
594
466
  if @secrets[type].nil?
595
467
  return nil if quiet
596
- raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
468
+ raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.join(", ")})"
597
469
  end
598
470
  if @secrets[type][instance_id].nil?
599
471
  return nil if quiet
@@ -654,645 +526,88 @@ module MU
654
526
 
655
527
  @@dummy_cache = {}
656
528
 
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
529
  # Add or remove a resource's metadata to this deployment's structure and
1196
530
  # flush it to disk.
1197
531
  # @param type [String]: The type of resource (e.g. *server*, *database*).
1198
532
  # @param key [String]: The name field of this resource.
533
+ # @param mu_name [String]: The mu_name of this resource.
1199
534
  # @param data [Hash]: The resource's metadata.
535
+ # @param triggering_node [MU::Cloud]: A cloud object calling this notify, usually on behalf of itself
1200
536
  # @param remove [Boolean]: Remove this resource from the deploy structure, instead of adding it.
1201
537
  # @return [void]
1202
538
  def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
1203
539
  return if @no_artifacts
1204
- MU::MommaCat.lock("deployment-notification")
1205
540
 
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
541
+ begin
542
+ MU::MommaCat.lock("deployment-notification")
1209
543
 
1210
- _shortclass, _cfg_name, cfg_plural, _classname, attrs = MU::Cloud.getResourceNames(type)
1211
- has_multiples = false
544
+ if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
545
+ loadDeploy(true) # make sure we're saving the latest and greatest
546
+ end
1212
547
 
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
548
+ _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type, false)
549
+ has_multiples = attrs[:has_multiples] ? true : false
1219
550
 
1220
- if mu_name.nil?
1221
- if !data.nil? and !data["mu_name"].nil?
1222
- mu_name = data["mu_name"]
551
+ mu_name ||= if !data.nil? and !data["mu_name"].nil?
552
+ data["mu_name"]
1223
553
  elsif !triggering_node.nil? and !triggering_node.mu_name.nil?
1224
- mu_name = triggering_node.mu_name
554
+ triggering_node.mu_name
1225
555
  end
1226
556
  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")
557
+ 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
558
  return
1230
559
  end
1231
- end
1232
560
 
1233
- @need_deploy_flush = true
561
+ @need_deploy_flush = true
1234
562
 
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
563
+ if !remove
564
+ if data.nil?
565
+ MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
566
+ return
567
+ end
1245
568
  @notify_semaphore.synchronize {
1246
- @deployment[type][key] ||= {}
569
+ @deployment[type] ||= {}
1247
570
  }
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
571
  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
572
+ @notify_semaphore.synchronize {
573
+ @deployment[type][key] ||= {}
574
+ }
575
+ @deployment[type][key][mu_name] = data
576
+ MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
1266
577
  else
1267
- MU.log "MU::MommaCat.notify called to remove #{type} #{key} deployment struct, but no such data exist", MU::DEBUG
578
+ @deployment[type][key] = data
579
+ MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
580
+ end
581
+ save!(key) if !delayed_save
582
+ else
583
+ have_deploy = true
584
+ if @deployment[type].nil? or @deployment[type][key].nil?
585
+ MU.log "MU::MommaCat.notify called to remove #{type} #{key}#{has_multiples ? " "+mu_name : ""} deployment struct, but no such data exist", MU::DEBUG
586
+ return
1268
587
  end
1269
- MU::MommaCat.unlock("deployment-notification")
1270
588
 
1271
- return
1272
- end
589
+ if have_deploy
590
+ @notify_semaphore.synchronize {
591
+ if has_multiples
592
+ MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
593
+ @deployment[type][key].delete(mu_name)
594
+ end
1273
595
 
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
596
+ if @deployment[type][key].empty? or !has_multiples
597
+ MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1280
598
  @deployment[type].delete(key)
1281
599
  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
600
 
601
+ if @deployment[type].empty?
602
+ @deployment.delete(type)
603
+ end
604
+ }
605
+ end
606
+ save! if !delayed_save
607
+ end
608
+ ensure
609
+ MU::MommaCat.unlock("deployment-notification")
1293
610
  end
1294
-
1295
- MU::MommaCat.unlock("deployment-notification")
1296
611
  end
1297
612
 
1298
613
  # Send a Slack notification to a deployment's administrators.
@@ -1331,13 +646,13 @@ module MU
1331
646
  to << "#{admin['name']} <#{admin['email']}>"
1332
647
  }
1333
648
  end
1334
- message = <<MESSAGE_END
649
+ message = <<MAIL_HEAD_END
1335
650
  From: #{MU.handle} <root@localhost>
1336
651
  To: #{to.join(",")}
1337
652
  Subject: #{subject}
1338
653
 
1339
654
  #{msg}
1340
- MESSAGE_END
655
+ MAIL_HEAD_END
1341
656
  if !kitten.nil? and kitten.kind_of?(MU::Cloud)
1342
657
  message = message + "\n\n**** #{kitten}:\n"
1343
658
  if !kitten.report.nil?
@@ -1425,124 +740,58 @@ MESSAGE_END
1425
740
  MU::Master::SSL.sign(csr_path, sans, for_user: MU.mu_user)
1426
741
  end
1427
742
 
1428
- # Make sure deployment data is synchronized to/from each node in the
743
+ # Make sure deployment data is synchronized to/from each +Server+ in the
1429
744
  # currently-loaded deployment.
745
+ # @param nodeclasses [Array<String>]
746
+ # @param triggering_node [String,MU::Cloud::Server]
747
+ # @param save_only [Boolean]
1430
748
  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
749
+ return if MU.syncLitterThread # don't run recursively by accident
1435
750
  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
-
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
1448
751
 
752
+ if !triggering_node.nil? and triggering_node.is_a?(MU::Cloud::Server)
753
+ triggering_node = triggering_node.mu_name
754
+ end
1449
755
 
1450
- MU.log "Updating these node classes in #{@deploy_id}", MU::DEBUG, details: nodeclasses
1451
- }
756
+ siblings = findLitterMate(type: "server", return_all: true)
757
+ return if siblings.nil? or siblings.empty?
1452
758
 
1453
759
  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
760
+ siblings.each_pair { |mu_name, node|
761
+ next if mu_name == triggering_node or node.groomer.nil?
762
+ next if nodeclasses.size > 0 and !nodeclasses.include?(node.config['name'])
763
+ if !node.deploydata or !node.deploydata['nodename']
764
+ MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::NOTICE
765
+ next
766
+ end
767
+
768
+ if @deployment["servers"][node.config['name']][node.mu_name].nil? or
769
+ @deployment["servers"][node.config['name']][node.mu_name] != node.deploydata
770
+ @deployment["servers"][node.config['name']][node.mu_name] = node.deploydata
771
+ elsif !save_only
772
+ # Don't bother running grooms on nodes that don't need to be updated,
773
+ # unless we're just going to do a save.
774
+ next
775
+ end
776
+ update_servers << node
1508
777
  }
1509
778
 
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
779
+ return if update_servers.empty?
1518
780
 
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
781
+ MU.log "Updating nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
1527
782
 
1528
- return if MU.inGem? || update_servers.size < 1
1529
783
  threads = []
1530
- parent_thread_id = Thread.current.object_id
1531
784
  update_servers.each { |sibling|
1532
785
  threads << Thread.new {
1533
786
  Thread.abort_on_exception = true
1534
- MU.dupGlobals(parent_thread_id)
1535
787
  Thread.current.thread_variable_set("name", "sync-"+sibling.mu_name.downcase)
1536
788
  MU.setVar("syncLitterThread", true)
1537
789
  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
790
+ sibling.groomer.saveDeployData
791
+ sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
1542
792
  rescue MU::Groomer::RunError => e
1543
- MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
793
+ MU.log "Sync of #{sibling.mu_name} failed", MU::WARN, details: e.inspect
1544
794
  end
1545
- MU.purgeGlobals
1546
795
  }
1547
796
  }
1548
797