cloud-mu 3.1.3 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  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 +21 -13
  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 +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -37
  23. data/cloud-mu.gemspec +22 -20
  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 +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +926 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +169 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -30,6 +30,21 @@ module MU
30
30
  ["Chef", "Ansible"]
31
31
  end
32
32
 
33
+ # List of known/supported groomers which are installed and appear to be working
34
+ # @return [Array<String>]
35
+ def self.availableGroomers
36
+ available = []
37
+ MU::Groomer.supportedGroomers.each { |groomer|
38
+ begin
39
+ groomerbase = loadGroomer(groomer)
40
+ available << groomer if groomerbase.available?
41
+ rescue LoadError
42
+ end
43
+ }
44
+
45
+ available
46
+ end
47
+
33
48
  # Instance methods that any Groomer plugin must implement
34
49
  def self.requiredMethods
35
50
  [:preClean, :bootstrap, :haveBootstrapped?, :run, :saveDeployData, :getSecret, :saveSecret, :deleteSecret, :reinstall]
@@ -37,7 +52,7 @@ module MU
37
52
 
38
53
  # Class methods that any Groomer plugin must implement
39
54
  def self.requiredClassMethods
40
- [:getSecret, :cleanup, :saveSecret, :deleteSecret]
55
+ [:getSecret, :cleanup, :saveSecret, :deleteSecret, :available?]
41
56
  end
42
57
 
43
58
  class Ansible;
@@ -121,7 +136,7 @@ module MU
121
136
  else
122
137
  retval = @groomer_obj.method(method).call
123
138
  end
124
- rescue Exception => e
139
+ rescue StandardError => e
125
140
  pp e.backtrace
126
141
  raise MU::Groomer::RunError, e.message, e.backtrace
127
142
  end
@@ -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?
@@ -66,6 +78,7 @@ module MU
66
78
  # @param permissions [Boolean]: If true, save the secret under the current active deploy (if any), rather than in the global location for this user
67
79
  # @param deploy_dir [String]: If permissions is +true+, save the secret here
68
80
  def self.saveSecret(vault: nil, item: nil, data: nil, permissions: false, deploy_dir: nil)
81
+
69
82
  if vault.nil? or vault.empty? or item.nil? or item.empty?
70
83
  raise MuError, "Must call saveSecret with vault and item names"
71
84
  end
@@ -73,7 +86,6 @@ module MU
73
86
  raise MuError, "Ansible vault/item names cannot include forward slashes"
74
87
  end
75
88
  pwfile = vaultPasswordFile
76
-
77
89
 
78
90
  dir = if permissions
79
91
  if deploy_dir
@@ -95,8 +107,9 @@ module MU
95
107
  if File.exist?(path)
96
108
  MU.log "Overwriting existing vault #{vault} item #{item}"
97
109
  end
110
+
98
111
  File.open(path, File::CREAT|File::RDWR|File::TRUNC, 0600) { |f|
99
- f.write data
112
+ f.write data.to_yaml
100
113
  }
101
114
 
102
115
  cmd = %Q{#{ansibleExecDir}/ansible-vault encrypt #{path} --vault-password-file #{pwfile}}
@@ -115,14 +128,23 @@ module MU
115
128
  # @param item [String]: The item within the repository to retrieve
116
129
  # @param field [String]: OPTIONAL - A specific field within the item to return.
117
130
  # @return [Hash]
118
- def self.getSecret(vault: nil, item: nil, field: nil)
131
+ def self.getSecret(vault: nil, item: nil, field: nil, deploy_dir: nil)
119
132
  if vault.nil? or vault.empty?
120
133
  raise MuError, "Must call getSecret with at least a vault name"
121
134
  end
122
-
123
135
  pwfile = vaultPasswordFile
124
- dir = secret_dir+"/"+vault
125
- if !Dir.exist?(dir)
136
+
137
+ dir = nil
138
+ try = [secret_dir+"/"+vault]
139
+ try << deploy_dir+"/ansible/vaults/"+vault if deploy_dir
140
+ try << MU.mommacat.deploy_dir+"/ansible/vaults/"+vault if MU.mommacat.deploy_dir
141
+ try.each { |maybe_dir|
142
+ if Dir.exist?(maybe_dir) and (item.nil? or File.exist?(maybe_dir+"/"+item))
143
+ dir = maybe_dir
144
+ break
145
+ end
146
+ }
147
+ if dir.nil?
126
148
  raise MuNoSuchSecret, "No such vault #{vault}"
127
149
  end
128
150
 
@@ -135,17 +157,23 @@ module MU
135
157
  cmd = %Q{#{ansibleExecDir}/ansible-vault view #{itempath} --vault-password-file #{pwfile}}
136
158
  MU.log cmd
137
159
  a = `#{cmd}`
138
- # If we happen to have stored recognizeable JSON, return it as parsed,
139
- # which is a behavior we're used to from Chef vault. Otherwise, return
140
- # a String.
160
+ # If we happen to have stored recognizeable JSON or YAML, return it
161
+ # as parsed, which is a behavior we're used to from Chef vault.
162
+ # Otherwise, return a String.
141
163
  begin
142
164
  data = JSON.parse(a)
143
- if field and data[field]
144
- data = data[field]
145
- end
146
165
  rescue JSON::ParserError
147
- data = a
166
+ begin
167
+ data = YAML.load(a)
168
+ rescue Psych::SyntaxError => e
169
+ data = a
170
+ end
148
171
  end
172
+ [vault, item, field].each { |tier|
173
+ if data and data.is_a?(Hash) and tier and data[tier]
174
+ data = data[tier]
175
+ end
176
+ }
149
177
  else
150
178
  data = []
151
179
  Dir.foreach(dir) { |entry|
@@ -160,7 +188,7 @@ module MU
160
188
 
161
189
  # see {MU::Groomer::Ansible.getSecret}
162
190
  def getSecret(vault: nil, item: nil, field: nil)
163
- self.class.getSecret(vault: vault, item: item, field: field)
191
+ self.class.getSecret(vault: vault, item: item, field: field, deploy_dir: @server.deploy.deploy_dir)
164
192
  end
165
193
 
166
194
  # Delete a Ansible data bag / Vault
@@ -174,7 +202,6 @@ module MU
174
202
  raise MuNoSuchSecret, "No such vault #{vault}"
175
203
  end
176
204
 
177
- data = nil
178
205
  if item
179
206
  itempath = dir+"/"+item
180
207
  if !File.exist?(itempath)
@@ -191,7 +218,7 @@ module MU
191
218
 
192
219
  # see {MU::Groomer::Ansible.deleteSecret}
193
220
  def deleteSecret(vault: nil, item: nil)
194
- self.class.deleteSecret(vault: vault, item: nil)
221
+ self.class.deleteSecret(vault: vault, item: item)
195
222
  end
196
223
 
197
224
  # Invoke the Ansible client on the node at the other end of a provided SSH
@@ -207,22 +234,63 @@ module MU
207
234
 
208
235
  ssh_user = @server.config['ssh_user'] || "root"
209
236
 
210
- cmd = %Q{cd #{@ansible_path} && #{@ansible_execs}/ansible-playbook -i hosts #{@server.config['name']}.yml --limit=#{@server.mu_name} --vault-password-file #{pwfile} --timeout=30 --vault-password-file #{@ansible_path}/.vault_pw -u #{ssh_user}}
237
+ if update_runlist
238
+ bootstrap
239
+ end
240
+
241
+ tmpfile = nil
242
+ playbook = if override_runlist and !override_runlist.empty?
243
+ play = {
244
+ "hosts" => @server.config['name']
245
+ }
246
+ if !@server.windows? and @server.config['ssh_user'] != "root"
247
+ play["become"] = "yes"
248
+ end
249
+ play["roles"] = override_runlist if @server.config['run_list'] and !@server.config['run_list'].empty?
250
+ play["vars"] = @server.config['ansible_vars'] if @server.config['ansible_vars']
251
+
252
+ tmpfile = Tempfile.new("#{@server.config['name']}-override-runlist.yml")
253
+ tmpfile.puts [play].to_yaml
254
+ tmpfile.close
255
+ tmpfile.path
256
+ else
257
+ "#{@server.config['name']}.yml"
258
+ end
259
+
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}}
211
261
 
212
262
  retries = 0
213
263
  begin
214
264
  MU.log cmd
215
- raise MU::Groomer::RunError, "Failed Ansible command: #{cmd}" if !system(cmd)
216
- rescue MU::Groomer::RunError => e
265
+ Timeout::timeout(timeout) {
266
+ if output
267
+ system("#{cmd}")
268
+ else
269
+ %x{#{cmd} 2>&1}
270
+ end
271
+
272
+ if $?.exitstatus != 0
273
+ raise MU::Groomer::RunError, "Failed Ansible command: #{cmd}"
274
+ end
275
+ }
276
+ rescue Timeout::Error, MU::Groomer::RunError => e
217
277
  if retries < max_retries
278
+ if reboot_first_fail and e.class.name == "MU::Groomer::RunError"
279
+ @server.reboot
280
+ reboot_first_fail = false
281
+ end
218
282
  sleep 30
219
283
  retries += 1
220
284
  MU.log "Failed Ansible run, will retry (#{retries.to_s}/#{max_retries.to_s})", MU::NOTICE, details: cmd
285
+
221
286
  retry
222
287
  else
288
+ tmpfile.unlink if tmpfile
223
289
  raise MuError, "Failed Ansible command: #{cmd}"
224
290
  end
225
291
  end
292
+
293
+ tmpfile.unlink if tmpfile
226
294
  end
227
295
 
228
296
  # This is a stub; since Ansible is effectively agentless, this operation
@@ -238,12 +306,12 @@ module MU
238
306
  # Bootstrap our server with Ansible- basically, just make sure this node
239
307
  # is listed in our deployment's Ansible inventory.
240
308
  def bootstrap
241
- @inventory.add(@server.config['name'], @server.mu_name)
309
+ @inventory.add(@server.config['name'], @server.windows? ? @server.canonicalIP : @server.mu_name)
242
310
  play = {
243
311
  "hosts" => @server.config['name']
244
312
  }
245
313
 
246
- if @server.config['ssh_user'] != "root"
314
+ if !@server.windows? and @server.config['ssh_user'] != "root"
247
315
  play["become"] = "yes"
248
316
  end
249
317
 
@@ -255,9 +323,26 @@ module MU
255
323
  play["vars"] = @server.config['ansible_vars']
256
324
  end
257
325
 
326
+ if @server.windows?
327
+ play["vars"] ||= {}
328
+ play["vars"]["ansible_connection"] = "winrm"
329
+ play["vars"]["ansible_winrm_scheme"] = "https"
330
+ play["vars"]["ansible_winrm_transport"] = "ntlm"
331
+ play["vars"]["ansible_winrm_server_cert_validation"] = "ignore" # XXX this sucks; use Mu_CA.pem if we can get it to work
332
+ # play["vars"]["ansible_winrm_ca_trust_path"] = "#{MU.mySSLDir}/Mu_CA.pem"
333
+ play["vars"]["ansible_user"] = @server.config['windows_admin_username']
334
+ win_pw = @server.getWindowsAdminPassword
335
+
336
+ pwfile = MU::Groomer::Ansible.vaultPasswordFile
337
+ cmd = %Q{#{MU::Groomer::Ansible.ansibleExecDir}/ansible-vault}
338
+ output = %x{#{cmd} encrypt_string '#{win_pw.gsub(/'/, "\\\\'")}' --vault-password-file #{pwfile}}
339
+
340
+ play["vars"]["ansible_password"] = output
341
+ end
342
+
258
343
  File.open(@ansible_path+"/"+@server.config['name']+".yml", File::CREAT|File::RDWR|File::TRUNC, 0600) { |f|
259
344
  f.flock(File::LOCK_EX)
260
- f.puts [play].to_yaml
345
+ f.puts [play].to_yaml.sub(/ansible_password: \|-?[\n\s]+/, 'ansible_password: ') # Ansible doesn't like this (legal) YAML
261
346
  f.flock(File::LOCK_UN)
262
347
  }
263
348
  end
@@ -265,7 +350,7 @@ module MU
265
350
  # Synchronize the deployment structure managed by {MU::MommaCat} into some Ansible variables, so that nodes can access this metadata.
266
351
  # @return [Hash]: The data synchronized.
267
352
  def saveDeployData
268
- @server.describe(update_cache: true) # Make sure we're fresh
353
+ @server.describe
269
354
 
270
355
  allvars = {
271
356
  "mu_deployment" => MU::Config.stripConfig(@server.deploy.deployment),
@@ -314,16 +399,24 @@ module MU
314
399
  allvars['deployment']
315
400
  end
316
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
+
317
409
  # Expunge Ansible resources associated with a node.
318
410
  # @param node [String]: The Mu name of the node in question.
319
- # @param vaults_to_clean [Array<Hash>]: Some vaults to expunge
411
+ # @param _vaults_to_clean [Array<Hash>]: Dummy argument, part of this method's interface but not used by the Ansible layer
320
412
  # @param noop [Boolean]: Skip actual deletion, just state what we'd do
321
- # @param nodeonly [Boolean]: Just delete the node and its keys, but leave other artifacts
322
- def self.cleanup(node, vaults_to_clean = [], noop = false, nodeonly: false)
413
+ def self.purge(node, _vaults_to_clean = [], noop = false)
323
414
  deploy = MU::MommaCat.new(MU.deploy_id)
324
415
  inventory = Inventory.new(deploy)
325
- ansible_path = deploy.deploy_dir+"/ansible"
326
- inventory.remove(node)
416
+ # ansible_path = deploy.deploy_dir+"/ansible"
417
+ if !noop
418
+ inventory.remove(node)
419
+ end
327
420
  end
328
421
 
329
422
  # List the Ansible vaults, if any, owned by the specified Mu user
@@ -344,23 +437,69 @@ module MU
344
437
  # the results to +STDOUT+.
345
438
  # @param name [String]: The variable name to use for the string's YAML key
346
439
  # @param string [String]: The string to encrypt
347
- # @param for_user [String]: Encrypt using the Vault password of the specified Mu user
348
- def self.encryptString(name, string, for_user = nil)
440
+ def self.encryptString(name, string)
349
441
  pwfile = vaultPasswordFile
350
442
  cmd = %Q{#{ansibleExecDir}/ansible-vault}
351
443
  if !system(cmd, "encrypt_string", string, "--name", name, "--vault-password-file", pwfile)
352
444
  raise MuError, "Failed Ansible command: #{cmd} encrypt_string <redacted> --name #{name} --vault-password-file"
353
445
  end
446
+ output
354
447
  end
355
448
 
356
- private
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
357
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
+
493
+ # Hunt down and return a path for Ansible executables
494
+ # @return [String]
358
495
  def self.ansibleExecDir
359
496
  path = nil
360
497
  if File.exist?(BINDIR+"/ansible-playbook")
361
498
  path = BINDIR
362
499
  else
363
- ENV['PATH'].split(/:/).each { |bindir|
500
+ paths = ENV['PATH'].split(/:/)
501
+ paths << "/usr/bin"
502
+ paths.uniq.each { |bindir|
364
503
  if File.exist?(bindir+"/ansible-playbook")
365
504
  path = bindir
366
505
  if !File.exist?(bindir+"/ansible-vault")
@@ -376,8 +515,12 @@ module MU
376
515
  path
377
516
  end
378
517
 
379
- # Get the +.vault_pw+ file for the appropriate user. If it doesn't exist,
380
- # generate one.
518
+ # Get path to the +.vault_pw+ file for the appropriate user. If it
519
+ # doesn't exist, generate it.
520
+ #
521
+ # @param for_user [String]:
522
+ # @param pwfile [String]
523
+ # @return [String]
381
524
  def self.vaultPasswordFile(for_user = nil, pwfile: nil)
382
525
  pwfile ||= secret_dir(for_user)+"/.vault_pw"
383
526
  @@pwfile_semaphore.synchronize {
@@ -392,11 +535,8 @@ module MU
392
535
  end
393
536
 
394
537
  # Figure out where our main stash of secrets is, and make sure it exists
395
- def secret_dir
396
- MU::Groomer::Ansible.secret_dir(@mu_user)
397
- end
398
-
399
- # Figure out where our main stash of secrets is, and make sure it exists
538
+ # @param user [String]:
539
+ # @return [String]
400
540
  def self.secret_dir(user = MU.mu_user)
401
541
  path = MU.dataDir(user) + "/ansible-secrets"
402
542
  Dir.mkdir(path, 0755) if !Dir.exist?(path)
@@ -404,6 +544,13 @@ module MU
404
544
  path
405
545
  end
406
546
 
547
+ private
548
+
549
+ # Figure out where our main stash of secrets is, and make sure it exists
550
+ def secret_dir
551
+ MU::Groomer::Ansible.secret_dir(@mu_user)
552
+ end
553
+
407
554
  # Make an effort to distinguish an Ansible role from other sorts of
408
555
  # artifacts, since 'roles' is an awfully generic name for a directory.
409
556
  # Short of a full, slow syntax check, this is the best we're liable to do.
@@ -543,7 +690,7 @@ module MU
543
690
  def haveNode?(name)
544
691
  lock
545
692
  read
546
- @inv.each_pair { |group, nodes|
693
+ @inv.values.each { |nodes|
547
694
  if nodes.include?(name)
548
695
  unlock
549
696
  return true
@@ -574,7 +721,7 @@ module MU
574
721
  def remove(name)
575
722
  lock
576
723
  read
577
- @inv.each_pair { |group, nodes|
724
+ @inv.each_pair { |_group, nodes|
578
725
  nodes.delete(name)
579
726
  }
580
727
  save!