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
@@ -276,15 +276,20 @@ module MU
276
276
  schema_chunk["properties"]["creation_style"] != "existing"
277
277
  schema_chunk["properties"].each_pair { |key, subschema|
278
278
  shortclass = if conf_chunk[key]
279
- shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key)
279
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key, false)
280
280
  shortclass
281
281
  else
282
282
  nil
283
283
  end
284
284
 
285
285
  new_val = applySchemaDefaults(conf_chunk[key], subschema, depth+1, conf_chunk, type: shortclass).dup
286
-
287
- conf_chunk[key] = Marshal.load(Marshal.dump(new_val)) if !new_val.nil?
286
+ if !new_val.nil?
287
+ begin
288
+ conf_chunk[key] = Marshal.load(Marshal.dump(new_val))
289
+ rescue TypeError
290
+ conf_chunk[key] = new_val.clone
291
+ end
292
+ end
288
293
  }
289
294
  end
290
295
  elsif schema_chunk["type"] == "array" and conf_chunk.kind_of?(Array)
@@ -625,6 +625,13 @@ module MU
625
625
  server['vault_access'] << {"vault" => "splunk", "item" => "admin_user"}
626
626
  ok = false if !MU::Config::Server.checkVaultRefs(server)
627
627
 
628
+ server['groomer'] ||= self.defaultGroomer
629
+ groomclass = MU::Groomer.loadGroomer(server['groomer'])
630
+ if !groomclass.available?(server['platform'].match(/^win/))
631
+ MU.log "Groomer #{server['groomer']} for #{server['name']} is missing or has incomplete dependencies", MU::ERR
632
+ ok = false
633
+ end
634
+
628
635
  if server["cloud"] != "Azure"
629
636
  server['dependencies'] << configurator.adminFirewallRuleset(vpc: server['vpc'], region: server['region'], cloud: server['cloud'], credentials: server['credentials'])
630
637
  end
@@ -133,6 +133,7 @@ module MU
133
133
  # @param pseudo [<Boolean>]: This is a pseudo-parameter, automatically provided, and not available as user input.
134
134
  # @param runtimecode [<String>]: Actual code to allow the cloud layer to interpret literally in its own idiom, e.g. '"Ref" : "AWS::StackName"' for CloudFormation
135
135
  def getTail(param, value: nil, prettyname: nil, cloudtype: "String", valid_values: [], description: nil, list_of: nil, prefix: "", suffix: "", pseudo: false, runtimecode: nil)
136
+ param = param.gsub(/[^a-z0-9_]/i, "_")
136
137
  if value.nil?
137
138
  if @@parameters.nil? or !@@parameters.has_key?(param)
138
139
  MU.log "Parameter '#{param}' (#{param.class.name}) referenced in config but not provided (#{caller[0]})", MU::DEBUG, details: @@parameters
@@ -493,6 +493,7 @@ module MU
493
493
  # See if we'll be able to create peering connections
494
494
  can_peer = false
495
495
  already_peered = false
496
+
496
497
  if MU.myCloud == vpc["cloud"] and MU.myVPCObj
497
498
  if vpc['peers']
498
499
  vpc['peers'].each { |peer|
@@ -636,7 +637,7 @@ module MU
636
637
  MU.log "VPC peering connections to non-local accounts must specify the vpc_id of the peer.", MU::ERR
637
638
  ok = false
638
639
  end
639
- elsif !processReference(peer['vpc'], "vpcs", "vpc '#{vpc['name']}'", configurator, dflt_region: peer["vpc"]['region'])
640
+ elsif !processReference(peer['vpc'], "vpcs", vpc, configurator, dflt_region: peer["vpc"]['region'])
640
641
  ok = false
641
642
  end
642
643
  end
@@ -735,8 +736,8 @@ module MU
735
736
  vpc_block["subnet_pref"] = "all_private" if vpc_block["subnet_pref"] == "private"
736
737
  end
737
738
 
738
- flags = {}
739
- flags["subnet_pref"] = vpc_block["subnet_pref"] if !vpc_block["subnet_pref"].nil?
739
+ # flags = {}
740
+ # flags["subnet_pref"] = vpc_block["subnet_pref"] if !vpc_block["subnet_pref"].nil?
740
741
  hab_arg = if vpc_block['habitat']
741
742
  if vpc_block['habitat'].is_a?(MU::Config::Ref)
742
743
  [vpc_block['habitat'].id] # XXX actually, findStray it
@@ -770,9 +771,9 @@ MU.log "VPC lookup cache hit", MU::WARN, details: vpc_block
770
771
  tag_key: tag_key,
771
772
  tag_value: tag_value,
772
773
  region: vpc_block["region"],
773
- flags: flags,
774
774
  habitats: hab_arg,
775
- dummy_ok: true
775
+ dummy_ok: true,
776
+ subnet_pref: vpc_block["subnet_pref"]
776
777
  )
777
778
 
778
779
  found.first if found and found.size == 1
@@ -799,7 +800,7 @@ MU.log "VPC lookup cache hit", MU::WARN, details: vpc_block
799
800
  @@reference_cache[vpc_block] ||= ext_vpc if ok
800
801
  end
801
802
  rescue StandardError => e
802
- raise MuError, e.inspect, e.backtrace
803
+ raise MuError, e.inspect, [caller, e.backtrace]
803
804
  ensure
804
805
  if !ext_vpc and vpc_block['cloud'] != "CloudFormation"
805
806
  MU.log "Couldn't resolve VPC reference to a unique live VPC in #{parent_type} #{parent['name']} (called by #{caller[0]})", MU::ERR, details: vpc_block
@@ -923,7 +924,14 @@ MU.log "VPC lookup cache hit", MU::WARN, details: vpc_block
923
924
  ext_vpc.subnets.each { |subnet|
924
925
  next if dflt_region and vpc_block["cloud"] == "Google" and subnet.az != dflt_region
925
926
  if subnet.private? and (vpc_block['subnet_pref'] != "all_public" and vpc_block['subnet_pref'] != "public")
926
- private_subnets << { "subnet_id" => configurator.getTail("#{parent['name']} Private Subnet #{priv}", value: subnet.cloud_id, prettyname: "#{parent['name']} Private Subnet #{priv}", cloudtype: "AWS::EC2::Subnet::Id"), "az" => subnet.az }
927
+ private_subnets << {
928
+ "subnet_id" => configurator.getTail(
929
+ "#{parent['name']} Private Subnet #{priv}",
930
+ value: subnet.cloud_id,
931
+ prettyname: "#{parent['name']} Private Subnet #{priv}",
932
+ cloudtype: "AWS::EC2::Subnet::Id"),
933
+ "az" => subnet.az
934
+ }
927
935
  private_subnets_map[subnet.cloud_id] = subnet
928
936
  priv = priv + 1
929
937
  elsif !subnet.private? and vpc_block['subnet_pref'] != "all_private" and vpc_block['subnet_pref'] != "private"
@@ -1,7 +1,6 @@
1
1
  <% if complexity == 'complex' %>
2
2
  name: <%= vpc_name %>
3
3
  create_nat_gateway: true
4
- ip_block: 10.231.0.0/16
5
4
  enable_traffic_logging: true
6
5
  region: us-east-2
7
6
  availability_zones:
@@ -73,56 +73,56 @@ ubuntu14:
73
73
  ap-southeast-1: ami-2855964b
74
74
  ap-southeast-2: ami-d19fc4b2
75
75
  win2k12r2: &1
76
- us-east-1: ami-00f7cf8d57d29a8a7
77
- us-east-2: ami-0c14a2a9b1d88428d
78
- ca-central-1: ami-0210e4efc4186f89d
79
- us-west-2: ami-036681205605cba8c
80
- us-west-1: ami-072d0f2b03f351e5c
81
- eu-west-1: ami-061524b3efcc026da
82
- eu-west-2: ami-0a7aeb2dae7c7154b
83
- eu-west-3: ami-0b16adff6701f08bb
84
- eu-north-1: ami-09bd34c6465aa914b
85
- sa-east-1: ami-078221cae70b179c4
86
- eu-central-1: ami-047d37ec58a8469fb
87
- ap-northeast-1: ami-0ce23ffef990003d2
88
- ap-south-1: ami-0106284f16a19651a
89
- ap-northeast-2: ami-0518e43d0367f1a6d
90
- ap-southeast-1: ami-0858019a829a5169d
91
- ap-southeast-2: ami-0e0d7d3acb6427f53
76
+ us-east-1: ami-003aea65bc2e7136a
77
+ us-east-2: ami-0163293e39ba504c2
78
+ ca-central-1: ami-055689dd92f29d2aa
79
+ us-west-2: ami-0ce87dda2c9244e57
80
+ us-west-1: ami-00d9cf64bd2fafa44
81
+ eu-west-1: ami-026d7427b9fadad40
82
+ eu-west-2: ami-036a22c0780551794
83
+ eu-west-3: ami-05e3d9b79bdc10861
84
+ eu-north-1: ami-063eb48504c7d73f1
85
+ sa-east-1: ami-0a8c1829a5e650bc5
86
+ eu-central-1: ami-0ea20cef52335b008
87
+ ap-northeast-1: ami-08db2dc67228dbb90
88
+ ap-south-1: ami-012241411db3f09c3
89
+ ap-northeast-2: ami-0368c224de1d20502
90
+ ap-southeast-1: ami-028ef74e1edc3943a
91
+ ap-southeast-2: ami-09e03eab1b1bc151b
92
92
  win2k16: &5
93
- us-east-1: ami-090dd1749dc0be91d
94
- us-east-2: ami-09c9eeb95291e63d7
95
- ca-central-1: ami-0c63a53a15fca4238
96
- us-west-2: ami-0b8540e9a207143eb
97
- eu-west-1: ami-067b5d8d85d3c8cf8
98
- us-west-1: ami-0a2dc7e2cf21ab3e9
99
- eu-west-2: ami-0cece465ae4b18027
100
- eu-west-3: ami-0cc5f29ed6e0e8a67
101
- eu-central-1: ami-02d4da18299373531
102
- sa-east-1: ami-021d5c906c1898430
103
- ap-northeast-1: ami-01129c98f812a3be7
104
- ap-south-1: ami-005c7910458e9541d
105
- ap-northeast-2: ami-024840a881c449d78
106
- ap-southeast-2: ami-070af17644a596301
107
- ap-southeast-1: ami-0bc2e098a07140bc4
108
- eu-north-1: ami-03dcc78d77d2ee027
93
+ us-east-1: ami-02801a2c8dcbfb883
94
+ us-east-2: ami-0ca4f779a2a58a7ea
95
+ ca-central-1: ami-05d3854d9d6e9bcc5
96
+ us-west-2: ami-091f4a88ce32d28b6
97
+ eu-west-1: ami-0b938c9b23ed7d18c
98
+ us-west-1: ami-0fd744c3fbe8260f2
99
+ eu-west-2: ami-071a89b959c5eda27
100
+ eu-west-3: ami-0b206e3dbda9ff9eb
101
+ eu-central-1: ami-0dd9bdad31dd0d3ce
102
+ sa-east-1: ami-0d69b8d6c0f9a7bae
103
+ ap-northeast-1: ami-02eb4a6f519bc3190
104
+ ap-south-1: ami-0666fd543ac8b5501
105
+ ap-northeast-2: ami-01277c81f9b91cf77
106
+ ap-southeast-2: ami-0426a246f9b0ccadd
107
+ ap-southeast-1: ami-07ecb0d55c2eb7247
108
+ eu-north-1: ami-047811530583b6d08
109
109
  win2k19:
110
- us-east-1: ami-09946f18cbcdce65c
111
- us-east-2: ami-02ab72768678bb7d0
112
- ca-central-1: ami-0fcf1b24169d88d7f
113
- us-west-2: ami-025ca67c85e4e147d
114
- eu-west-2: ami-08a0e09c469e6a557
115
- us-west-1: ami-05960eb854f91cbb8
116
- eu-west-1: ami-0f91d02c05561cf5b
117
- eu-central-1: ami-0faafc220143c941c
118
- eu-west-3: ami-0d2a0b6f21c7ce4a2
119
- eu-north-1: ami-0fecae116e9e331bf
120
- sa-east-1: ami-050693ece618acaa5
121
- ap-northeast-2: ami-0e0ebd96765911d72
122
- ap-northeast-1: ami-0729606d0a4051499
123
- ap-southeast-1: ami-06ba249ee50ee9669
124
- ap-southeast-2: ami-03a051a6d0f2b79d5
125
- ap-south-1: ami-02c287a24c5e872f6
110
+ us-east-1: ami-00820419bf212df7e
111
+ us-east-2: ami-0a7916b90aa4629d5
112
+ ca-central-1: ami-0d704529661e19185
113
+ us-west-2: ami-0ee6a198d7ac35eb1
114
+ eu-west-2: ami-0f6ac1634bd7add92
115
+ us-west-1: ami-039e3816b4cac1e27
116
+ eu-west-1: ami-03a771d99091199b7
117
+ eu-central-1: ami-03b648d5b45f51a4f
118
+ eu-west-3: ami-068839907c18c3a6e
119
+ eu-north-1: ami-0db851ee76f7deefb
120
+ sa-east-1: ami-0c2cc60c62159f87c
121
+ ap-northeast-2: ami-06bdf8ae9ae9add92
122
+ ap-northeast-1: ami-02306d959c7f175b9
123
+ ap-southeast-1: ami-0d5b4a3d73e0f471f
124
+ ap-southeast-2: ami-00fa88caff4f64937
125
+ ap-south-1: ami-0b44feae4bb9f497a
126
126
  amazon:
127
127
  us-east-1: ami-b73b63a0
128
128
  us-east-2: ami-58277d3d
@@ -394,7 +394,7 @@ module MU
394
394
  Thread.handle_interrupt(MU::Cloud::MuCloudResourceNotImplemented => :never) {
395
395
  begin
396
396
  Thread.handle_interrupt(MU::Cloud::MuCloudResourceNotImplemented => :immediate) {
397
- MU.log "Cost calculator not available for this stack, as it uses a resource not implemented in Mu's CloudFormation layer.", MU::WARN, verbosity: MU::Logger::NORMAL
397
+ MU.log "Cost calculator not available for this stack, as it uses a resource not implemented in Mu's CloudFormation layer.", MU::DEBUG, verbosity: MU::Logger::NORMAL
398
398
  Thread.current.exit
399
399
  }
400
400
  ensure
@@ -37,7 +37,7 @@ module MU
37
37
 
38
38
  # Class methods that any Groomer plugin must implement
39
39
  def self.requiredClassMethods
40
- [:getSecret, :cleanup, :saveSecret, :deleteSecret]
40
+ [:getSecret, :cleanup, :saveSecret, :deleteSecret, :available?]
41
41
  end
42
42
 
43
43
  class Ansible;
@@ -24,6 +24,10 @@ module MU
24
24
  class NoAnsibleExecError < MuError;
25
25
  end
26
26
 
27
+ # One or more Python dependencies missing
28
+ class AnsibleLibrariesError < MuError;
29
+ end
30
+
27
31
  # Location in which we'll find our Ansible executables. This only applies
28
32
  # to full-grown Mu masters; minimalist gem installs will have to make do
29
33
  # with whatever Ansible executables they can find in $PATH.
@@ -40,6 +44,10 @@ module MU
40
44
  @ansible_path = node.deploy.deploy_dir+"/ansible"
41
45
  @ansible_execs = MU::Groomer::Ansible.ansibleExecDir
42
46
 
47
+ if !MU::Groomer::Ansible.checkPythonDependencies(@server.windows?)
48
+ raise AnsibleLibrariesError, "One or more python dependencies not available"
49
+ end
50
+
43
51
  if !@ansible_execs or @ansible_execs.empty?
44
52
  raise NoAnsibleExecError, "No Ansible executables found in visible paths"
45
53
  end
@@ -54,6 +62,10 @@ module MU
54
62
  installRoles
55
63
  end
56
64
 
65
+ # Are Ansible executables and key libraries present and accounted for?
66
+ def self.available?(windows = false)
67
+ MU::Groomer::Ansible.checkPythonDependencies(windows)
68
+ end
57
69
 
58
70
  # Indicate whether our server has been bootstrapped with Ansible
59
71
  def haveBootstrapped?
@@ -245,7 +257,7 @@ module MU
245
257
  "#{@server.config['name']}.yml"
246
258
  end
247
259
 
248
- cmd = %Q{cd #{@ansible_path} && echo "#{purpose}" && #{@ansible_execs}/ansible-playbook -i hosts #{playbook} --limit=#{@server.mu_name} --vault-password-file #{pwfile} --timeout=30 --vault-password-file #{@ansible_path}/.vault_pw -u #{ssh_user}}
260
+ cmd = %Q{cd #{@ansible_path} && echo "#{purpose}" && #{@ansible_execs}/ansible-playbook -i hosts #{playbook} --limit=#{@server.windows? ? @server.canonicalIP : @server.mu_name} --vault-password-file #{pwfile} --timeout=30 --vault-password-file #{@ansible_path}/.vault_pw -u #{ssh_user}}
249
261
 
250
262
  retries = 0
251
263
  begin
@@ -294,7 +306,7 @@ module MU
294
306
  # Bootstrap our server with Ansible- basically, just make sure this node
295
307
  # is listed in our deployment's Ansible inventory.
296
308
  def bootstrap
297
- @inventory.add(@server.config['name'], @server.mu_name)
309
+ @inventory.add(@server.config['name'], @server.windows? ? @server.canonicalIP : @server.mu_name)
298
310
  play = {
299
311
  "hosts" => @server.config['name']
300
312
  }
@@ -387,11 +399,18 @@ module MU
387
399
  allvars['deployment']
388
400
  end
389
401
 
402
+ # Nuke everything associated with a deploy. Since we're just some files
403
+ # in the deploy directory, this doesn't have to do anything.
404
+ def self.cleanup(deploy_id, noop = false)
405
+ # deploy = MU::MommaCat.new(MU.deploy_id)
406
+ # inventory = Inventory.new(deploy)
407
+ end
408
+
390
409
  # Expunge Ansible resources associated with a node.
391
410
  # @param node [String]: The Mu name of the node in question.
392
411
  # @param _vaults_to_clean [Array<Hash>]: Dummy argument, part of this method's interface but not used by the Ansible layer
393
412
  # @param noop [Boolean]: Skip actual deletion, just state what we'd do
394
- def self.cleanup(node, _vaults_to_clean = [], noop = false)
413
+ def self.purge(node, _vaults_to_clean = [], noop = false)
395
414
  deploy = MU::MommaCat.new(MU.deploy_id)
396
415
  inventory = Inventory.new(deploy)
397
416
  # ansible_path = deploy.deploy_dir+"/ansible"
@@ -427,6 +446,50 @@ module MU
427
446
  output
428
447
  end
429
448
 
449
+ # Hunt down and return a path for a Python executable
450
+ # @return [String]
451
+ def self.pythonExecDir
452
+ path = nil
453
+
454
+ if File.exist?(BINDIR+"/python")
455
+ path = BINDIR
456
+ else
457
+ paths = [ansibleExecDir]
458
+ paths.concat(ENV['PATH'].split(/:/))
459
+ paths << "/usr/bin" # not always in path, esp in pared-down Docker images
460
+ paths.reject! { |p| p.nil? }
461
+ paths.uniq.each { |bindir|
462
+ if File.exist?(bindir+"/python")
463
+ path = bindir
464
+ break
465
+ end
466
+ }
467
+ end
468
+ path
469
+ end
470
+
471
+ # Make sure what's in our Python requirements.txt is reflected in the
472
+ # Python we're about to run for Ansible
473
+ def self.checkPythonDependencies(windows = false)
474
+ return nil if !ansibleExecDir
475
+
476
+ execline = File.readlines(ansibleExecDir+"/ansible-playbook").first.chomp.sub(/^#!/, '')
477
+ if !execline
478
+ MU.log "Unable to extract a Python executable from #{ansibleExecDir}/ansible-playbook", MU::ERR
479
+ return false
480
+ end
481
+
482
+ require 'tempfile'
483
+ f = Tempfile.new("pythoncheck")
484
+ f.puts "import ansible"
485
+ f.puts "import winrm" if windows
486
+ f.close
487
+
488
+ system(%Q{#{execline} #{f.path}})
489
+ f.unlink
490
+ $?.exitstatus == 0 ? true : false
491
+ end
492
+
430
493
  # Hunt down and return a path for Ansible executables
431
494
  # @return [String]
432
495
  def self.ansibleExecDir
@@ -434,7 +497,9 @@ module MU
434
497
  if File.exist?(BINDIR+"/ansible-playbook")
435
498
  path = BINDIR
436
499
  else
437
- ENV['PATH'].split(/:/).each { |bindir|
500
+ paths = ENV['PATH'].split(/:/)
501
+ paths << "/usr/bin"
502
+ paths.uniq.each { |bindir|
438
503
  if File.exist?(bindir+"/ansible-playbook")
439
504
  path = bindir
440
505
  if !File.exist?(bindir+"/ansible-vault")
@@ -35,6 +35,12 @@ module MU
35
35
  end
36
36
  }
37
37
 
38
+ # Are the Chef libraries present and accounted for?
39
+ def self.available?(windows = false)
40
+ loadChefLib
41
+ @chefloaded
42
+ end
43
+
38
44
  @chefloaded = false
39
45
  @chefload_semaphore = Mutex.new
40
46
  # Autoload is too brain-damaged to get Chef's subclasses/submodules, so
@@ -362,7 +368,7 @@ module MU
362
368
  }
363
369
 
364
370
  if resp.exitcode == 1 and output_lines.join("\n").match(/Chef Client finished/)
365
- MU.log "resp.exit code 1"
371
+ MU.log output_lines.last
366
372
  elsif resp.exitcode != 0
367
373
  raise MU::Cloud::BootstrapTempFail if resp.exitcode == 35 or output_lines.join("\n").match(/REBOOT_SCHEDULED| WARN: Reboot requested:|Rebooting server at a recipe's request|Chef::Exceptions::Reboot/)
368
374
  raise MU::Groomer::RunError, output_lines.slice(output_lines.length-50, output_lines.length).join("")
@@ -619,15 +625,16 @@ module MU
619
625
  kb.name_args = [@server.mu_name]
620
626
  kb.config[:manual] = true
621
627
  kb.config[:winrm_transport] = :ssl
622
- kb.config[:host] = @server.mu_name
623
628
  kb.config[:winrm_port] = 5986
624
629
  kb.config[:session_timeout] = timeout
625
630
  kb.config[:operation_timeout] = timeout
626
631
  if retries % 2 == 0
632
+ kb.config[:host] = canonical_addr
627
633
  kb.config[:winrm_authentication_protocol] = :basic
628
634
  kb.config[:winrm_user] = @server.config['windows_admin_username']
629
635
  kb.config[:winrm_password] = @server.getWindowsAdminPassword
630
636
  else
637
+ kb.config[:host] = @server.mu_name
631
638
  kb.config[:winrm_authentication_protocol] = :cert
632
639
  kb.config[:winrm_client_cert] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.crt"
633
640
  kb.config[:winrm_client_key] = "#{MU.mySSLDir}/#{@server.mu_name}-winrm.key"
@@ -681,7 +688,7 @@ module MU
681
688
  preClean(false) # it's ok for this to fail
682
689
  rescue StandardError => e
683
690
  end
684
- MU::Groomer::Chef.cleanup(@server.mu_name, nodeonly: true)
691
+ MU::Groomer::Chef.purge(@server.mu_name, nodeonly: true)
685
692
  @config['forced_preclean'] = true
686
693
  @server.reboot if @server.windows? # *sigh*
687
694
  end
@@ -798,12 +805,49 @@ retry
798
805
  end
799
806
  end
800
807
 
808
+ def self.cleanup(deploy_id, noop = false)
809
+ return nil if deploy_id.nil? or deploy_id.empty?
810
+ begin
811
+ if File.exist?(Etc.getpwuid(Process.uid).dir+"/.chef/knife.rb")
812
+ ::Chef::Config.from_file(Etc.getpwuid(Process.uid).dir+"/.chef/knife.rb")
813
+ end
814
+ deadnodes = []
815
+ ::Chef::Config[:environment] ||= MU.environment
816
+ q = ::Chef::Search::Query.new
817
+ begin
818
+ q.search("node", "tags_MU-ID:#{deploy_id}").each { |item|
819
+ next if item.is_a?(Integer)
820
+ item.each { |node|
821
+ deadnodes << node.name
822
+ }
823
+ }
824
+ rescue Net::HTTPServerException
825
+ end
826
+
827
+ begin
828
+ q.search("node", "name:#{deploy_id}-*").each { |item|
829
+ next if item.is_a?(Integer)
830
+ item.each { |node|
831
+ deadnodes << node.name
832
+ }
833
+ }
834
+ rescue Net::HTTPServerException
835
+ end
836
+ MU.log "Missed some Chef resources in node cleanup, purging now", MU::NOTICE if deadnodes.size > 0
837
+ deadnodes.uniq.each { |node|
838
+ MU::Groomer::Chef.purge(node, [], noop)
839
+ }
840
+ rescue LoadError
841
+ end
842
+
843
+ end
844
+
801
845
  # Expunge Chef resources associated with a node.
802
846
  # @param node [String]: The Mu name of the node in question.
803
847
  # @param vaults_to_clean [Array<Hash>]: Some vaults to expunge
804
848
  # @param noop [Boolean]: Skip actual deletion, just state what we'd do
805
849
  # @param nodeonly [Boolean]: Just delete the node and its keys, but leave other artifacts
806
- def self.cleanup(node, vaults_to_clean = [], noop = false, nodeonly: false)
850
+ def self.purge(node, vaults_to_clean = [], noop = false, nodeonly: false)
807
851
  loadChefLib
808
852
  MU.log "Deleting Chef resources associated with #{node}"
809
853
  if !nodeonly