cloud-mu 2.1.0beta → 3.0.0beta

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 (291) hide show
  1. checksums.yaml +5 -5
  2. data/Berksfile +4 -5
  3. data/Berksfile.lock +179 -0
  4. data/README.md +1 -6
  5. data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +0 -0
  6. data/ansible/roles/mu-installer/README.md +33 -0
  7. data/ansible/roles/mu-installer/defaults/main.yml +2 -0
  8. data/ansible/roles/mu-installer/handlers/main.yml +2 -0
  9. data/ansible/roles/mu-installer/meta/main.yml +60 -0
  10. data/ansible/roles/mu-installer/tasks/main.yml +13 -0
  11. data/ansible/roles/mu-installer/tests/inventory +2 -0
  12. data/ansible/roles/mu-installer/tests/test.yml +5 -0
  13. data/ansible/roles/mu-installer/vars/main.yml +2 -0
  14. data/bin/mu-adopt +125 -0
  15. data/bin/mu-aws-setup +4 -4
  16. data/bin/mu-azure-setup +265 -0
  17. data/bin/mu-azure-tests +43 -0
  18. data/bin/mu-cleanup +20 -8
  19. data/bin/mu-configure +224 -98
  20. data/bin/mu-deploy +8 -3
  21. data/bin/mu-gcp-setup +16 -8
  22. data/bin/mu-gen-docs +92 -8
  23. data/bin/mu-load-config.rb +52 -12
  24. data/bin/mu-momma-cat +36 -0
  25. data/bin/mu-node-manage +34 -27
  26. data/bin/mu-self-update +2 -2
  27. data/bin/mu-ssh +12 -8
  28. data/bin/mu-upload-chef-artifacts +11 -4
  29. data/bin/mu-user-manage +3 -0
  30. data/cloud-mu.gemspec +8 -11
  31. data/cookbooks/firewall/libraries/helpers_iptables.rb +2 -2
  32. data/cookbooks/firewall/metadata.json +1 -1
  33. data/cookbooks/firewall/recipes/default.rb +5 -9
  34. data/cookbooks/mu-firewall/attributes/default.rb +2 -0
  35. data/cookbooks/mu-firewall/metadata.rb +1 -1
  36. data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +0 -0
  37. data/cookbooks/mu-master/Berksfile +2 -2
  38. data/cookbooks/mu-master/files/default/check_mem.pl +0 -0
  39. data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
  40. data/cookbooks/mu-master/metadata.rb +5 -4
  41. data/cookbooks/mu-master/recipes/389ds.rb +1 -1
  42. data/cookbooks/mu-master/recipes/basepackages.rb +30 -10
  43. data/cookbooks/mu-master/recipes/default.rb +59 -7
  44. data/cookbooks/mu-master/recipes/firewall-holes.rb +1 -1
  45. data/cookbooks/mu-master/recipes/init.rb +65 -47
  46. data/cookbooks/mu-master/recipes/{eks-kubectl.rb → kubectl.rb} +4 -10
  47. data/cookbooks/mu-master/recipes/sssd.rb +2 -1
  48. data/cookbooks/mu-master/recipes/update_nagios_only.rb +6 -6
  49. data/cookbooks/mu-master/templates/default/web_app.conf.erb +2 -2
  50. data/cookbooks/mu-master/templates/mods/ldap.conf.erb +4 -0
  51. data/cookbooks/mu-php54/Berksfile +1 -2
  52. data/cookbooks/mu-php54/metadata.rb +4 -5
  53. data/cookbooks/mu-php54/recipes/default.rb +1 -1
  54. data/cookbooks/mu-splunk/templates/default/splunk-init.erb +0 -0
  55. data/cookbooks/mu-tools/Berksfile +3 -2
  56. data/cookbooks/mu-tools/files/default/Mu_CA.pem +33 -0
  57. data/cookbooks/mu-tools/libraries/helper.rb +20 -8
  58. data/cookbooks/mu-tools/metadata.rb +5 -2
  59. data/cookbooks/mu-tools/recipes/apply_security.rb +2 -3
  60. data/cookbooks/mu-tools/recipes/eks.rb +1 -1
  61. data/cookbooks/mu-tools/recipes/gcloud.rb +5 -30
  62. data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
  63. data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -0
  64. data/cookbooks/mu-tools/recipes/selinux.rb +19 -0
  65. data/cookbooks/mu-tools/recipes/split_var_partitions.rb +0 -1
  66. data/cookbooks/mu-tools/recipes/windows-client.rb +256 -122
  67. data/cookbooks/mu-tools/resources/disk.rb +3 -1
  68. data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +1 -1
  69. data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
  70. data/cookbooks/mu-tools/templates/default/{kubeconfig.erb → kubeconfig-eks.erb} +0 -0
  71. data/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb +27 -0
  72. data/cookbooks/mu-tools/templates/windows-10/sshd_config.erb +137 -0
  73. data/cookbooks/mu-utility/recipes/nat.rb +4 -0
  74. data/extras/alpha.png +0 -0
  75. data/extras/beta.png +0 -0
  76. data/extras/clean-stock-amis +2 -2
  77. data/extras/generate-stock-images +131 -0
  78. data/extras/git-fix-permissions-hook +0 -0
  79. data/extras/image-generators/AWS/centos6.yaml +17 -0
  80. data/extras/image-generators/{aws → AWS}/centos7-govcloud.yaml +0 -0
  81. data/extras/image-generators/{aws → AWS}/centos7.yaml +0 -0
  82. data/extras/image-generators/{aws → AWS}/rhel7.yaml +0 -0
  83. data/extras/image-generators/{aws → AWS}/win2k12.yaml +0 -0
  84. data/extras/image-generators/{aws → AWS}/win2k16.yaml +0 -0
  85. data/extras/image-generators/{aws → AWS}/windows.yaml +0 -0
  86. data/extras/image-generators/{gcp → Google}/centos6.yaml +1 -0
  87. data/extras/image-generators/Google/centos7.yaml +18 -0
  88. data/extras/python_rpm/build.sh +0 -0
  89. data/extras/release.png +0 -0
  90. data/extras/ruby_rpm/build.sh +0 -0
  91. data/extras/ruby_rpm/muby.spec +1 -1
  92. data/install/README.md +43 -5
  93. data/install/deprecated-bash-library.sh +0 -0
  94. data/install/installer +1 -1
  95. data/install/jenkinskeys.rb +0 -0
  96. data/install/mu-master.yaml +55 -0
  97. data/modules/mommacat.ru +41 -7
  98. data/modules/mu.rb +444 -149
  99. data/modules/mu/adoption.rb +500 -0
  100. data/modules/mu/cleanup.rb +235 -158
  101. data/modules/mu/cloud.rb +675 -138
  102. data/modules/mu/clouds/aws.rb +156 -24
  103. data/modules/mu/clouds/aws/alarm.rb +4 -14
  104. data/modules/mu/clouds/aws/bucket.rb +60 -18
  105. data/modules/mu/clouds/aws/cache_cluster.rb +8 -20
  106. data/modules/mu/clouds/aws/collection.rb +12 -22
  107. data/modules/mu/clouds/aws/container_cluster.rb +209 -118
  108. data/modules/mu/clouds/aws/database.rb +120 -45
  109. data/modules/mu/clouds/aws/dnszone.rb +7 -18
  110. data/modules/mu/clouds/aws/endpoint.rb +5 -15
  111. data/modules/mu/clouds/aws/firewall_rule.rb +144 -72
  112. data/modules/mu/clouds/aws/folder.rb +4 -11
  113. data/modules/mu/clouds/aws/function.rb +6 -16
  114. data/modules/mu/clouds/aws/group.rb +4 -12
  115. data/modules/mu/clouds/aws/habitat.rb +11 -13
  116. data/modules/mu/clouds/aws/loadbalancer.rb +40 -28
  117. data/modules/mu/clouds/aws/log.rb +5 -13
  118. data/modules/mu/clouds/aws/msg_queue.rb +9 -24
  119. data/modules/mu/clouds/aws/nosqldb.rb +4 -12
  120. data/modules/mu/clouds/aws/notifier.rb +6 -13
  121. data/modules/mu/clouds/aws/role.rb +69 -40
  122. data/modules/mu/clouds/aws/search_domain.rb +17 -20
  123. data/modules/mu/clouds/aws/server.rb +184 -94
  124. data/modules/mu/clouds/aws/server_pool.rb +33 -38
  125. data/modules/mu/clouds/aws/storage_pool.rb +5 -12
  126. data/modules/mu/clouds/aws/user.rb +59 -33
  127. data/modules/mu/clouds/aws/userdata/linux.erb +18 -30
  128. data/modules/mu/clouds/aws/userdata/windows.erb +9 -9
  129. data/modules/mu/clouds/aws/vpc.rb +214 -145
  130. data/modules/mu/clouds/azure.rb +978 -44
  131. data/modules/mu/clouds/azure/container_cluster.rb +413 -0
  132. data/modules/mu/clouds/azure/firewall_rule.rb +500 -0
  133. data/modules/mu/clouds/azure/habitat.rb +167 -0
  134. data/modules/mu/clouds/azure/loadbalancer.rb +205 -0
  135. data/modules/mu/clouds/azure/role.rb +211 -0
  136. data/modules/mu/clouds/azure/server.rb +810 -0
  137. data/modules/mu/clouds/azure/user.rb +257 -0
  138. data/modules/mu/clouds/azure/userdata/README.md +4 -0
  139. data/modules/mu/clouds/azure/userdata/linux.erb +137 -0
  140. data/modules/mu/clouds/azure/userdata/windows.erb +275 -0
  141. data/modules/mu/clouds/azure/vpc.rb +782 -0
  142. data/modules/mu/clouds/cloudformation.rb +12 -9
  143. data/modules/mu/clouds/cloudformation/firewall_rule.rb +5 -13
  144. data/modules/mu/clouds/cloudformation/server.rb +10 -1
  145. data/modules/mu/clouds/cloudformation/server_pool.rb +1 -0
  146. data/modules/mu/clouds/cloudformation/vpc.rb +0 -2
  147. data/modules/mu/clouds/google.rb +554 -117
  148. data/modules/mu/clouds/google/bucket.rb +173 -32
  149. data/modules/mu/clouds/google/container_cluster.rb +1112 -157
  150. data/modules/mu/clouds/google/database.rb +24 -47
  151. data/modules/mu/clouds/google/firewall_rule.rb +344 -89
  152. data/modules/mu/clouds/google/folder.rb +156 -79
  153. data/modules/mu/clouds/google/group.rb +272 -82
  154. data/modules/mu/clouds/google/habitat.rb +177 -52
  155. data/modules/mu/clouds/google/loadbalancer.rb +9 -34
  156. data/modules/mu/clouds/google/role.rb +1211 -0
  157. data/modules/mu/clouds/google/server.rb +491 -227
  158. data/modules/mu/clouds/google/server_pool.rb +233 -48
  159. data/modules/mu/clouds/google/user.rb +479 -125
  160. data/modules/mu/clouds/google/userdata/linux.erb +3 -3
  161. data/modules/mu/clouds/google/userdata/windows.erb +9 -9
  162. data/modules/mu/clouds/google/vpc.rb +381 -223
  163. data/modules/mu/config.rb +689 -214
  164. data/modules/mu/config/bucket.rb +1 -1
  165. data/modules/mu/config/cache_cluster.rb +1 -1
  166. data/modules/mu/config/cache_cluster.yml +0 -4
  167. data/modules/mu/config/container_cluster.rb +18 -9
  168. data/modules/mu/config/database.rb +6 -23
  169. data/modules/mu/config/firewall_rule.rb +9 -15
  170. data/modules/mu/config/folder.rb +22 -21
  171. data/modules/mu/config/habitat.rb +22 -21
  172. data/modules/mu/config/loadbalancer.rb +2 -2
  173. data/modules/mu/config/role.rb +9 -40
  174. data/modules/mu/config/server.rb +26 -5
  175. data/modules/mu/config/server_pool.rb +1 -1
  176. data/modules/mu/config/storage_pool.rb +2 -2
  177. data/modules/mu/config/user.rb +4 -0
  178. data/modules/mu/config/vpc.rb +350 -110
  179. data/modules/mu/defaults/{amazon_images.yaml → AWS.yaml} +37 -39
  180. data/modules/mu/defaults/Azure.yaml +17 -0
  181. data/modules/mu/defaults/Google.yaml +24 -0
  182. data/modules/mu/defaults/README.md +1 -1
  183. data/modules/mu/deploy.rb +168 -125
  184. data/modules/mu/groomer.rb +2 -1
  185. data/modules/mu/groomers/ansible.rb +104 -32
  186. data/modules/mu/groomers/chef.rb +96 -44
  187. data/modules/mu/kittens.rb +20602 -0
  188. data/modules/mu/logger.rb +38 -11
  189. data/modules/mu/master.rb +90 -8
  190. data/modules/mu/master/chef.rb +2 -3
  191. data/modules/mu/master/ldap.rb +0 -1
  192. data/modules/mu/master/ssl.rb +250 -0
  193. data/modules/mu/mommacat.rb +917 -513
  194. data/modules/scratchpad.erb +1 -1
  195. data/modules/tests/super_complex_bok.yml +0 -0
  196. data/modules/tests/super_simple_bok.yml +0 -0
  197. data/roles/mu-master.json +2 -1
  198. data/spec/azure_creds +5 -0
  199. data/spec/mu.yaml +56 -0
  200. data/spec/mu/clouds/azure_spec.rb +164 -27
  201. data/spec/spec_helper.rb +5 -0
  202. data/test/clean_up.py +0 -0
  203. data/test/exec_inspec.py +0 -0
  204. data/test/exec_mu_install.py +0 -0
  205. data/test/exec_retry.py +0 -0
  206. data/test/smoke_test.rb +0 -0
  207. metadata +90 -118
  208. data/cookbooks/mu-jenkins/Berksfile +0 -14
  209. data/cookbooks/mu-jenkins/CHANGELOG.md +0 -13
  210. data/cookbooks/mu-jenkins/LICENSE +0 -37
  211. data/cookbooks/mu-jenkins/README.md +0 -105
  212. data/cookbooks/mu-jenkins/attributes/default.rb +0 -42
  213. data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +0 -73
  214. data/cookbooks/mu-jenkins/files/default/deploy_config.xml +0 -44
  215. data/cookbooks/mu-jenkins/metadata.rb +0 -21
  216. data/cookbooks/mu-jenkins/recipes/default.rb +0 -195
  217. data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +0 -54
  218. data/cookbooks/mu-jenkins/recipes/public_key.rb +0 -24
  219. data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +0 -24
  220. data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +0 -14
  221. data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +0 -6
  222. data/cookbooks/nagios/Berksfile +0 -11
  223. data/cookbooks/nagios/CHANGELOG.md +0 -589
  224. data/cookbooks/nagios/CONTRIBUTING.md +0 -11
  225. data/cookbooks/nagios/LICENSE +0 -37
  226. data/cookbooks/nagios/README.md +0 -328
  227. data/cookbooks/nagios/TESTING.md +0 -2
  228. data/cookbooks/nagios/attributes/config.rb +0 -171
  229. data/cookbooks/nagios/attributes/default.rb +0 -228
  230. data/cookbooks/nagios/chefignore +0 -102
  231. data/cookbooks/nagios/definitions/command.rb +0 -33
  232. data/cookbooks/nagios/definitions/contact.rb +0 -33
  233. data/cookbooks/nagios/definitions/contactgroup.rb +0 -33
  234. data/cookbooks/nagios/definitions/host.rb +0 -33
  235. data/cookbooks/nagios/definitions/hostdependency.rb +0 -33
  236. data/cookbooks/nagios/definitions/hostescalation.rb +0 -34
  237. data/cookbooks/nagios/definitions/hostgroup.rb +0 -33
  238. data/cookbooks/nagios/definitions/nagios_conf.rb +0 -38
  239. data/cookbooks/nagios/definitions/resource.rb +0 -33
  240. data/cookbooks/nagios/definitions/service.rb +0 -33
  241. data/cookbooks/nagios/definitions/servicedependency.rb +0 -33
  242. data/cookbooks/nagios/definitions/serviceescalation.rb +0 -34
  243. data/cookbooks/nagios/definitions/servicegroup.rb +0 -33
  244. data/cookbooks/nagios/definitions/timeperiod.rb +0 -33
  245. data/cookbooks/nagios/libraries/base.rb +0 -314
  246. data/cookbooks/nagios/libraries/command.rb +0 -91
  247. data/cookbooks/nagios/libraries/contact.rb +0 -230
  248. data/cookbooks/nagios/libraries/contactgroup.rb +0 -112
  249. data/cookbooks/nagios/libraries/custom_option.rb +0 -36
  250. data/cookbooks/nagios/libraries/data_bag_helper.rb +0 -23
  251. data/cookbooks/nagios/libraries/default.rb +0 -90
  252. data/cookbooks/nagios/libraries/host.rb +0 -412
  253. data/cookbooks/nagios/libraries/hostdependency.rb +0 -181
  254. data/cookbooks/nagios/libraries/hostescalation.rb +0 -173
  255. data/cookbooks/nagios/libraries/hostgroup.rb +0 -119
  256. data/cookbooks/nagios/libraries/nagios.rb +0 -282
  257. data/cookbooks/nagios/libraries/resource.rb +0 -59
  258. data/cookbooks/nagios/libraries/service.rb +0 -455
  259. data/cookbooks/nagios/libraries/servicedependency.rb +0 -215
  260. data/cookbooks/nagios/libraries/serviceescalation.rb +0 -195
  261. data/cookbooks/nagios/libraries/servicegroup.rb +0 -144
  262. data/cookbooks/nagios/libraries/timeperiod.rb +0 -160
  263. data/cookbooks/nagios/libraries/users_helper.rb +0 -54
  264. data/cookbooks/nagios/metadata.rb +0 -25
  265. data/cookbooks/nagios/recipes/_load_databag_config.rb +0 -153
  266. data/cookbooks/nagios/recipes/_load_default_config.rb +0 -241
  267. data/cookbooks/nagios/recipes/apache.rb +0 -48
  268. data/cookbooks/nagios/recipes/default.rb +0 -204
  269. data/cookbooks/nagios/recipes/nginx.rb +0 -82
  270. data/cookbooks/nagios/recipes/pagerduty.rb +0 -143
  271. data/cookbooks/nagios/recipes/server_package.rb +0 -40
  272. data/cookbooks/nagios/recipes/server_source.rb +0 -164
  273. data/cookbooks/nagios/templates/default/apache2.conf.erb +0 -96
  274. data/cookbooks/nagios/templates/default/cgi.cfg.erb +0 -266
  275. data/cookbooks/nagios/templates/default/commands.cfg.erb +0 -13
  276. data/cookbooks/nagios/templates/default/contacts.cfg.erb +0 -37
  277. data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +0 -25
  278. data/cookbooks/nagios/templates/default/hosts.cfg.erb +0 -15
  279. data/cookbooks/nagios/templates/default/htpasswd.users.erb +0 -6
  280. data/cookbooks/nagios/templates/default/nagios.cfg.erb +0 -22
  281. data/cookbooks/nagios/templates/default/nginx.conf.erb +0 -62
  282. data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +0 -185
  283. data/cookbooks/nagios/templates/default/resource.cfg.erb +0 -27
  284. data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +0 -15
  285. data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +0 -14
  286. data/cookbooks/nagios/templates/default/services.cfg.erb +0 -14
  287. data/cookbooks/nagios/templates/default/templates.cfg.erb +0 -31
  288. data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +0 -13
  289. data/extras/image-generators/aws/centos6.yaml +0 -18
  290. data/modules/mu/defaults/google_images.yaml +0 -16
  291. data/roles/mu-master-jenkins.json +0 -24
File without changes
@@ -2,7 +2,7 @@
2
2
 
3
3
  BOLD=`tput bold`
4
4
  NORM=`tput sgr0`
5
- CHEF_CLIENT_VERSION="14.11.21"
5
+ CHEF_CLIENT_VERSION="14.13.11"
6
6
  if [ "$MU_BRANCH" == "" ];then
7
7
  MU_BRANCH="master"
8
8
  mydir="`dirname $0`"
File without changes
@@ -0,0 +1,55 @@
1
+ ---
2
+ appname: mu
3
+ parameters:
4
+ - name: cloud
5
+ default: <%= MU.myCloud || "AWS" %>
6
+ valid_values:
7
+ <% MU::Cloud.availableClouds.each { |c| %>
8
+ - <%= c %>
9
+ <% } %>
10
+ - name: public
11
+ default: true
12
+ - name: name
13
+ default: mu-master
14
+ scrub_mu_isms: true
15
+ servers:
16
+ - name: <%= name %>
17
+ groomer: Ansible
18
+ run_list:
19
+ - mu-installer
20
+ platform: centos7
21
+ cloud: <%= cloud %>
22
+ <% if cloud == "AWS" %>
23
+ size: t2.medium
24
+ <% elsif cloud == "Azure" %>
25
+ size: Standard_DS1_v2
26
+ <% elsif cloud == "Google" %>
27
+ size: n1-standard-1
28
+ <% end %>
29
+ vpc:
30
+ name: <%= name %>-vpc
31
+ <% if public == "true" %>
32
+ subnet_pref: public
33
+ static_ip:
34
+ assign_ip: true
35
+ associate_public_ip: true
36
+ <% else %>
37
+ subnet_pref: private
38
+ <% end %>
39
+ <% if cloud == "AWS" %>
40
+ canned_iam_policies:
41
+ - AdministratorAccess
42
+ <% elsif cloud == "Azure" %>
43
+ roles:
44
+ - Owner
45
+ <% elsif cloud == "Google" %>
46
+ roles:
47
+ - role:
48
+ id: roles/owner
49
+ <% end %>
50
+ vpcs:
51
+ - name: <%= name %>-vpc
52
+ cloud: <%= cloud %>
53
+ <% if public %>
54
+ create_bastion: false
55
+ <% end %>
@@ -32,7 +32,12 @@ $LOAD_PATH << "#{$MUDIR}/modules"
32
32
  require File.realpath(File.expand_path(File.dirname(__FILE__)+"/mu-load-config.rb"))
33
33
  require 'mu'
34
34
 
35
- MU::Groomer::Chef.loadChefLib # pre-cache this so we don't take a hit on a user-interactive need
35
+ begin
36
+ MU::Groomer::Chef.loadChefLib # pre-cache this so we don't take a hit on a user-interactive need
37
+ $ENABLE_SCRATCHPAD = true
38
+ rescue LoadError
39
+ MU.log "Chef libraries not available, disabling Scratchpad", MU::WARN
40
+ end
36
41
  #MU.setLogging($opts[:verbose], $opts[:web])
37
42
  if MU.myCloud == "AWS"
38
43
  MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
@@ -57,7 +62,7 @@ Thread.new {
57
62
  MU.dupGlobals(parent_thread_id)
58
63
  begin
59
64
  MU::MommaCat.cleanTerminatedInstances
60
- MU::Master.cleanExpiredScratchpads
65
+ MU::Master.cleanExpiredScratchpads if $ENABLE_SCRATCHPAD
61
66
  sleep 60
62
67
  rescue Exception => e
63
68
  MU.log "Error in cleanTerminatedInstances thread: #{e.inspect}", MU::ERR, details: e.backtrace
@@ -199,6 +204,17 @@ app = proc do |env|
199
204
  ]
200
205
  begin
201
206
  if !env.nil? and !env['REQUEST_PATH'].nil? and env['REQUEST_PATH'].match(/^\/scratchpad/)
207
+ if !$ENABLE_SCRATCHPAD
208
+ msg = "Scratchpad disabled in non-Chef Mu installations"
209
+ return [
210
+ 504,
211
+ {
212
+ 'Content-Type' => 'text/html',
213
+ 'Content-Length' => msg.length.to_s
214
+ },
215
+ [msg]
216
+ ]
217
+ end
202
218
  itemname = env['REQUEST_PATH'].sub(/^\/scratchpad\//, "")
203
219
  begin
204
220
  if itemname.sub!(/\/secret$/, "")
@@ -257,7 +273,7 @@ app = proc do |env|
257
273
  [page]
258
274
  ]
259
275
  end
260
- rescue MU::Groomer::Chef::MuNoSuchSecret
276
+ rescue MU::Groomer::MuNoSuchSecret
261
277
  page = nil
262
278
  if $MU_CFG.has_key?('scratchpad') and
263
279
  $MU_CFG['scratchpad'].has_key?("template_path") and
@@ -287,6 +303,7 @@ app = proc do |env|
287
303
  ]
288
304
  end
289
305
  elsif !env.nil? and !env['REQUEST_PATH'].nil? and env['REQUEST_PATH'].match(/^\/rest\//)
306
+
290
307
  action, filter, path = env['REQUEST_PATH'].sub(/^\/rest\/?/, "").split(/\//, 3)
291
308
  # Don't give away the store. This can't be public until we can
292
309
  # authenticate and access-control properly.
@@ -295,7 +312,23 @@ app = proc do |env|
295
312
  next
296
313
  end
297
314
 
298
- if action == "deploy"
315
+ if action == "hosts_add"
316
+ if Process.uid != 0
317
+ returnval = throw500 "Service not available"
318
+ elsif !filter or !path
319
+ returnval = throw404 env['REQUEST_PATH']
320
+ else
321
+ MU::MommaCat.addInstanceToEtcHosts(path, filter)
322
+ returnval = [
323
+ 200,
324
+ {
325
+ 'Content-Type' => 'text/plain',
326
+ 'Content-Length' => 2
327
+ },
328
+ ["ok"]
329
+ ]
330
+ end
331
+ elsif action == "deploy"
299
332
  returnval = throw404 env['REQUEST_PATH'] if !filter
300
333
  MU.log "Loading deploy data for #{filter} #{path}"
301
334
  kittenpile = MU::MommaCat.getLitter(filter)
@@ -313,9 +346,9 @@ app = proc do |env|
313
346
  200,
314
347
  {
315
348
  'Content-Type' => 'text/plain',
316
- 'Content-Length' => MU.adminBucketName.length.to_s
349
+ 'Content-Length' => MU.adminBucketName(filter, credentials: path).length.to_s
317
350
  },
318
- [MU.adminBucketName]
351
+ [MU.adminBucketName(filter, credentials: path)]
319
352
  ]
320
353
  else
321
354
  returnval = throw404 env['REQUEST_PATH']
@@ -389,7 +422,8 @@ app = proc do |env|
389
422
  if instance.respond_to?(:addVolume)
390
423
  # XXX make sure we handle mangled input safely
391
424
  params = JSON.parse(Base64.decode64(req["add_volume"]))
392
- instance.addVolume(params["dev"], params["size"])
425
+ MU.log "ADDVOLUME REQUEST", MU::WARN, details: params
426
+ instance.addVolume(params["dev"], params["size"], delete_on_termination: params["delete_on_termination"])
393
427
  else
394
428
  returnval = throw500 "I don't know how to add a volume for #{instance}"
395
429
  ok = false
@@ -36,20 +36,252 @@ class Object
36
36
  end
37
37
  end
38
38
 
39
+ # Mu extensions to Ruby's {Hash} type for internal Mu use
40
+ class Hash
41
+
42
+ # Strip extraneous fields out of a {MU::Config} hash to make it suitable for
43
+ # shorthand printing, such as with <tt>mu-adopt --diff</tt>
44
+ def self.bok_minimize(o)
45
+ if o.is_a?(Hash)
46
+ newhash = o.reject { |k, v|
47
+ !v.is_a?(Array) and !v.is_a?(Hash) and !["name", "id", "cloud_id"].include?(k)
48
+ }
49
+ # newhash.delete("cloud_id") if newhash["name"] or newhash["id"]
50
+ newhash.each_pair { |k, v|
51
+ newhash[k] = bok_minimize(v)
52
+ }
53
+ newhash.reject! { |_k, v| v.nil? or v.empty? }
54
+ newhash = newhash.values.first if newhash.size == 1
55
+ return newhash
56
+ elsif o.is_a?(Array)
57
+ newarray = []
58
+ o.each { |v|
59
+ newvalue = bok_minimize(v)
60
+ newarray << newvalue if !newvalue.nil? and !newvalue.empty?
61
+ }
62
+ newarray = newarray.first if newarray.size == 1
63
+ return newarray
64
+ end
65
+
66
+ o
67
+ end
68
+
69
+ # A comparison function for sorting arrays of hashes
70
+ def <=>(other)
71
+ return 1 if other.nil? or self.size > other.size
72
+ return -1 if other.size > self.size
73
+ # Sort any array children we have
74
+ self.each_pair { |k, v|
75
+ self[k] = v.sort if v.is_a?(Array)
76
+ }
77
+ other.each_pair { |k, v|
78
+ other[k] = v.sort if v.is_a?(Array)
79
+ }
80
+ return 0 if self == other # that was easy!
81
+ # compare elements and decide who's "bigger" based on their totals?
82
+ 0
83
+ end
84
+
85
+ # Recursively compare two hashes
86
+ def diff(with, on = self, level: 0, parents: [])
87
+ return if with.nil? and on.nil?
88
+ if with.nil? or on.nil? or with.class != on.class
89
+ return # XXX ...however we're flagging differences
90
+ end
91
+ return if on == with
92
+
93
+ tree = ""
94
+ indentsize = 0
95
+ parents.each { |p|
96
+ tree += (" " * indentsize) + p + " => \n"
97
+ indentsize += 2
98
+ }
99
+ indent = (" " * indentsize)
100
+
101
+ changes = []
102
+ if on.is_a?(Hash)
103
+ on_unique = (on.keys - with.keys)
104
+ with_unique = (with.keys - on.keys)
105
+ shared = (with.keys & on.keys)
106
+ shared.each { |k|
107
+ diff(with[k], on[k], level: level+1, parents: parents + [k])
108
+ }
109
+ on_unique.each { |k|
110
+ changes << "- ".red+PP.pp({k => on[k] }, '')
111
+ }
112
+ with_unique.each { |k|
113
+ changes << "+ ".green+PP.pp({k => with[k]}, '')
114
+ }
115
+ elsif on.is_a?(Array)
116
+ return if with == on
117
+ # special case- Basket of Kittens lists of declared resources of a type;
118
+ # we use this to decide if we can compare two array elements as if they
119
+ # should be equivalent
120
+ # We also implement comparison operators for {Hash} and our various
121
+ # custom objects which we might find in here so that we can get away with
122
+ # sorting arrays full of weird, non-primitive types.
123
+ done = []
124
+ # before_a = on.dup
125
+ # after_a = on.dup.sort
126
+ # before_b = with.dup
127
+ # after_b = with.dup.sort
128
+ on.sort.each { |elt|
129
+ if elt.is_a?(Hash) and elt['name'] or elt['entity']# or elt['cloud_id']
130
+ with.sort.each { |other_elt|
131
+ if (elt['name'] and other_elt['name'] == elt['name']) or
132
+ (elt['name'].nil? and !elt["id"].nil? and elt["id"] == other_elt["id"]) or
133
+ (elt['name'].nil? and elt["id"].nil? and
134
+ !elt["entity"].nil? and !other_elt["entity"].nil? and
135
+ (
136
+ (elt["entity"]["id"] and elt["entity"]["id"] == other_elt["entity"]["id"]) or
137
+ (elt["entity"]["name"] and elt["entity"]["name"] == other_elt["entity"]["name"])
138
+ )
139
+ )
140
+ break if elt == other_elt
141
+ done << elt
142
+ done << other_elt
143
+ namestr = if elt['type']
144
+ "#{elt['type']}[#{elt['name']}]"
145
+ elsif elt['name']
146
+ elt['name']
147
+ elsif elt['entity'] and elt["entity"]["id"]
148
+ elt['entity']['id']
149
+ end
150
+
151
+ diff(other_elt, elt, level: level+1, parents: parents + [namestr])
152
+ break
153
+ end
154
+ }
155
+ end
156
+ }
157
+ on_unique = (on - with) - done
158
+ with_unique = (with - on) - done
159
+ # if on_unique.size > 0 or with_unique.size > 0
160
+ # if before_a != after_a
161
+ # MU.log "A BEFORE", MU::NOTICE, details: before_a
162
+ # MU.log "A AFTER", MU::NOTICE, details: after_a
163
+ # end
164
+ # if before_b != after_b
165
+ # MU.log "B BEFORE", MU::NOTICE, details: before_b
166
+ # MU.log "B AFTER", MU::NOTICE, details: after_b
167
+ # end
168
+ # end
169
+ on_unique.each { |e|
170
+ changes << if e.is_a?(Hash)
171
+ "- ".red+PP.pp(Hash.bok_minimize(e), '').gsub(/\n/, "\n "+(indent))
172
+ else
173
+ "- ".red+e.to_s
174
+ end
175
+ }
176
+ with_unique.each { |e|
177
+ changes << if e.is_a?(Hash)
178
+ "+ ".green+PP.pp(Hash.bok_minimize(e), '').gsub(/\n/, "\n "+(indent))
179
+ else
180
+ "+ ".green+e.to_s
181
+ end
182
+ }
183
+ else
184
+ if on != with
185
+ changes << "-".red+" #{on.to_s}"
186
+ changes << "+".green+" #{with.to_s}"
187
+ end
188
+ end
189
+
190
+ if changes.size > 0
191
+ puts tree
192
+ changes.each { |c|
193
+ puts indent+c
194
+ }
195
+ end
196
+ end
197
+
198
+ # Implement a merge! that just updates each hash leaf as needed, not
199
+ # trashing the branch on the way there.
200
+ def deep_merge!(with, on = self)
201
+
202
+ if on and with and with.is_a?(Hash)
203
+ with.each_pair { |k, v|
204
+ if !on[k] or !on[k].is_a?(Hash)
205
+ on[k] = v
206
+ else
207
+ deep_merge!(with[k], on[k])
208
+ end
209
+ }
210
+ elsif with
211
+ on = with
212
+ end
213
+
214
+ on
215
+ end
216
+ end
217
+
39
218
  ENV['HOME'] = Etc.getpwuid(Process.uid).dir
40
219
 
41
220
  require 'mu/logger'
42
221
  module MU
43
222
 
223
+ # Subclass core thread so we can gracefully handle it when we hit system
224
+ # thread limits. Back off and wait makes sense for us, since most of our
225
+ # threads are terminal (in the dependency sense) and this is unlikely to get
226
+ # us deadlocks.
227
+ class Thread < ::Thread
228
+ @@mu_global_threads = []
229
+ @@mu_global_thread_semaphore = Mutex.new
230
+
231
+ def initialize(*args, &block)
232
+ @@mu_global_thread_semaphore.synchronize {
233
+ @@mu_global_threads.reject! { |t| t.nil? or !t.status }
234
+ }
235
+ newguy = nil
236
+ start = Time.now
237
+ begin
238
+ newguy = super(*args, &block)
239
+ if newguy.nil?
240
+ MU.log "I somehow got a nil trying to create a thread", MU::WARN, details: caller
241
+ sleep 1
242
+ end
243
+ rescue ::ThreadError => e
244
+ if e.message.match(/Resource temporarily unavailable/)
245
+ toomany = @@mu_global_threads.size
246
+ MU.log "Hit the wall at #{toomany.to_s} threads, waiting until there are fewer", MU::WARN
247
+ if @@mu_global_threads.size >= toomany
248
+ sleep 1
249
+ begin
250
+ @@mu_global_thread_semaphore.synchronize {
251
+ @@mu_global_threads.each { |t|
252
+ next if t == ::Thread.current
253
+ t.join(0.1)
254
+ }
255
+ @@mu_global_threads.reject! { |t| t.nil? or !t.status }
256
+ }
257
+ if (Time.now - start) > 150
258
+ MU.log "Failed to get a free thread slot after 150 seconds- are we in a deadlock situation?", MU::ERR, details: caller
259
+ raise e
260
+ end
261
+ end while @@mu_global_threads.size >= toomany
262
+ end
263
+ retry
264
+ else
265
+ raise e
266
+ end
267
+ end while newguy.nil?
268
+
269
+ @@mu_global_thread_semaphore.synchronize {
270
+ @@mu_global_threads << newguy
271
+ }
272
+
273
+ end
274
+ end
275
+
44
276
  # Wrapper class for fatal Exceptions. Gives our internals something to
45
277
  # inherit that will log an error message appropriately before bubbling up.
46
278
  class MuError < StandardError
47
279
  def initialize(message = nil)
48
- MU.log message, MU::ERR if !message.nil?
280
+ MU.log message, MU::ERR, details: caller[2] if !message.nil?
49
281
  if MU.verbosity == MU::Logger::SILENT
50
- super
51
- else
52
282
  super ""
283
+ else
284
+ super message
53
285
  end
54
286
  end
55
287
  end
@@ -60,9 +292,9 @@ module MU
60
292
  def initialize(message = nil)
61
293
  MU.log message, MU::NOTICE if !message.nil?
62
294
  if MU.verbosity == MU::Logger::SILENT
63
- super
64
- else
65
295
  super ""
296
+ else
297
+ super message
66
298
  end
67
299
  end
68
300
  end
@@ -80,11 +312,37 @@ module MU
80
312
  @@myRoot
81
313
  end
82
314
 
315
+ # utility routine for sorting semantic versioning strings
316
+ def self.version_sort(a, b)
317
+ a_parts = a.split(/[^a-z0-9]/)
318
+ b_parts = b.split(/[^a-z0-9]/)
319
+ for i in 0..a_parts.size
320
+ matchval = if a_parts[i] and b_parts[i] and
321
+ a_parts[i].match(/^\d+/) and b_parts[i].match(/^\d+/)
322
+ a_parts[i].to_i <=> b_parts[i].to_i
323
+ elsif a_parts[i] and !b_parts[i]
324
+ 1
325
+ elsif !a_parts[i] and b_parts[i]
326
+ -1
327
+ else
328
+ a_parts[i] <=> b_parts[i]
329
+ end
330
+ return matchval if matchval != 0
331
+ end
332
+ 0
333
+ end
334
+
83
335
  # Front our global $MU_CFG hash with a read-only copy
84
336
  def self.muCfg
85
337
  Marshal.load(Marshal.dump($MU_CFG)).freeze
86
338
  end
87
339
 
340
+ # Returns true if we're running without a full systemwide Mu Master install,
341
+ # typically as a gem.
342
+ def self.localOnly
343
+ ((Gem.paths and Gem.paths.home and File.realpath(File.expand_path(File.dirname(__FILE__))).match(/^#{Gem.paths.home}/)) or !Dir.exist?("/opt/mu"))
344
+ end
345
+
88
346
  # The main (root) Mu user's data directory.
89
347
  @@mainDataDir = File.expand_path(@@myRoot+"/../var")
90
348
  # The main (root) Mu user's data directory.
@@ -133,6 +391,7 @@ module MU
133
391
  # Copy the set of global variables in use by another thread, typically our
134
392
  # parent thread.
135
393
  def self.dupGlobals(parent_thread_id)
394
+ @@globals[parent_thread_id] ||= {}
136
395
  @@globals[parent_thread_id].each_pair { |name, value|
137
396
  setVar(name, value)
138
397
  }
@@ -215,13 +474,15 @@ module MU
215
474
  @myDataDir = @@mainDataDir if @myDataDir.nil?
216
475
  # Mu's deployment metadata directory.
217
476
  def self.dataDir(for_user = MU.mu_user)
218
- if for_user.nil? or for_user.empty? or for_user == "mu" or for_user == "root"
477
+ if !localOnly and
478
+ ((Process.uid == 0 and (for_user.nil? or for_user.empty?)) or
479
+ for_user == "mu" or for_user == "root")
219
480
  return @myDataDir
220
481
  else
221
482
  for_user ||= MU.mu_user
222
483
  basepath = Etc.getpwnam(for_user).dir+"/.mu"
223
- Dir.mkdir(basepath, 0755) if !Dir.exists?(basepath)
224
- Dir.mkdir(basepath+"/var", 0755) if !Dir.exists?(basepath+"/var")
484
+ Dir.mkdir(basepath, 0755) if !Dir.exist?(basepath)
485
+ Dir.mkdir(basepath+"/var", 0755) if !Dir.exist?(basepath+"/var")
225
486
  return basepath+"/var"
226
487
  end
227
488
  end
@@ -233,14 +494,24 @@ module MU
233
494
  end
234
495
  @@globals[Thread.current.object_id]['verbosity']
235
496
  end
497
+
498
+ # The color logging flag merits a default value.
499
+ def self.color
500
+ if @@globals[Thread.current.object_id].nil? or @@globals[Thread.current.object_id]['color'].nil?
501
+ MU.setVar("color", true)
502
+ end
503
+ @@globals[Thread.current.object_id]['color']
504
+ end
236
505
 
237
506
  # Set parameters parameters for calls to {MU#log}
238
- def self.setLogging(verbosity, webify_logs = false, handle = STDOUT)
507
+ def self.setLogging(verbosity, webify_logs = false, handle = STDOUT, color = true)
239
508
  MU.setVar("verbosity", verbosity)
240
- @@logger ||= MU::Logger.new(verbosity, webify_logs, handle)
509
+ MU.setVar("color", color)
510
+ @@logger ||= MU::Logger.new(verbosity, webify_logs, handle, color)
241
511
  @@logger.html = webify_logs
242
512
  @@logger.verbosity = verbosity
243
513
  @@logger.handle = handle
514
+ @@logger.color = color
244
515
  end
245
516
 
246
517
  setLogging(MU::Logger::NORMAL, false)
@@ -252,7 +523,7 @@ module MU
252
523
  end
253
524
 
254
525
  # Shortcut to invoke {MU::Logger#log}
255
- def self.log(msg, level = MU::INFO, details: nil, html: html = false, verbosity: MU.verbosity)
526
+ def self.log(msg, level = MU::INFO, details: nil, html: false, verbosity: MU.verbosity, color: true)
256
527
  return if (level == MU::DEBUG and verbosity <= MU::Logger::LOUD)
257
528
  return if verbosity == MU::Logger::SILENT
258
529
 
@@ -275,9 +546,9 @@ module MU
275
546
  extra = Hash.new if extra.nil?
276
547
  extra[:details] = details
277
548
  end
278
- @@logger.log(msg, level, details: extra, verbosity: MU::Logger::LOUD, html: html)
549
+ @@logger.log(msg, level, details: extra, verbosity: MU::Logger::LOUD, html: html, color: color)
279
550
  else
280
- @@logger.log(msg, level, html: html, verbosity: verbosity)
551
+ @@logger.log(msg, level, html: html, verbosity: verbosity, color: color)
281
552
  end
282
553
  end
283
554
 
@@ -308,49 +579,66 @@ module MU
308
579
  require 'mu/groomer'
309
580
 
310
581
  # Little hack to initialize library-only environments' config files
311
- if !$MU_CFG
312
- require "#{@@myRoot}/bin/mu-load-config.rb"
313
-
314
- if !$MU_CFG['auto_detection_done'] and (!$MU_CFG['multiuser'] or !cfgExists?)
315
- MU.log "Auto-detecting cloud providers"
316
- new_cfg = $MU_CFG.dup
317
- examples = {}
318
- MU::Cloud.supportedClouds.each { |cloud|
319
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
320
- begin
321
- if cloudclass.hosted? and !$MU_CFG[cloud.downcase]
322
- cfg_blob = cloudclass.hosted_config
323
- if cfg_blob
324
- new_cfg[cloud.downcase] = cfg_blob
325
- MU.log "Adding #{cloud} stanza to #{cfgPath}", MU::NOTICE
326
- end
327
- elsif !$MU_CFG[cloud.downcase] and !cloudclass.config_example.nil?
328
- examples[cloud.downcase] = cloudclass.config_example
582
+ def self.detectCloudProviders
583
+ MU.log "Auto-detecting cloud providers"
584
+ new_cfg = $MU_CFG.dup
585
+ examples = {}
586
+ MU::Cloud.supportedClouds.each { |cloud|
587
+ cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
588
+ begin
589
+ if cloudclass.hosted? and !$MU_CFG[cloud.downcase]
590
+ cfg_blob = cloudclass.hosted_config
591
+ if cfg_blob
592
+ new_cfg[cloud.downcase] = cfg_blob
593
+ MU.log "Adding auto-detected #{cloud} stanza", MU::NOTICE
329
594
  end
330
- rescue NoMethodError => e
331
- # missing .hosted? is normal for dummy layers like CloudFormation
332
- MU.log e.message, MU::WARN
595
+ elsif !$MU_CFG[cloud.downcase] and !cloudclass.config_example.nil?
596
+ examples[cloud.downcase] = cloudclass.config_example
333
597
  end
334
- }
335
- new_cfg['auto_detection_done'] = true
336
- if new_cfg != $MU_CFG or !cfgExists?
337
- MU.log "Generating #{cfgPath}"
338
- saveMuConfig(new_cfg, examples) # XXX and reload it
598
+ rescue NoMethodError => e
599
+ # missing .hosted? is normal for dummy layers like CloudFormation
600
+ MU.log e.message, MU::WARN
339
601
  end
602
+ }
603
+ new_cfg['auto_detection_done'] = true
604
+ if new_cfg != $MU_CFG or !cfgExists?
605
+ MU.log "Generating #{cfgPath}"
606
+ saveMuConfig(new_cfg, examples) # XXX and reload it
340
607
  end
608
+ new_cfg
609
+ end
610
+
611
+ if !$MU_CFG
612
+ require "#{@@myRoot}/bin/mu-load-config.rb"
613
+ if !$MU_CFG['auto_detection_done'] and (!$MU_CFG['multiuser'] or !cfgExists?)
614
+ detectCloudProviders
615
+ end
616
+ end
617
+
618
+ @@mommacat_port = 2260
619
+ if !$MU_CFG.nil? and !$MU_CFG['mommacat_port'].nil? and
620
+ !$MU_CFG['mommacat_port'] != "" and $MU_CFG['mommacat_port'].to_i > 0 and
621
+ $MU_CFG['mommacat_port'].to_i < 65536
622
+ @@mommacat_port = $MU_CFG['mommacat_port'].to_i
623
+ end
624
+ # The port on which the Momma Cat daemon should listen for requests
625
+ # @return [Integer]
626
+ def self.mommaCatPort
627
+ @@mommacat_port
341
628
  end
342
629
 
343
630
  @@my_private_ip = nil
344
631
  @@my_public_ip = nil
345
632
  @@mu_public_addr = nil
346
633
  @@mu_public_ip = nil
347
- if $MU_CFG['aws'] # XXX this should be abstracted to elsewhere
634
+ if MU::Cloud::AWS.hosted?
348
635
  @@my_private_ip = MU::Cloud::AWS.getAWSMetaData("local-ipv4")
349
636
  @@my_public_ip = MU::Cloud::AWS.getAWSMetaData("public-ipv4")
350
637
  @@mu_public_addr = @@my_public_ip
351
638
  @@mu_public_ip = @@my_public_ip
352
639
  end
353
- if !$MU_CFG.nil? and !$MU_CFG['public_address'].nil? and !$MU_CFG['public_address'].empty? and @@my_public_ip != $MU_CFG['public_address']
640
+ if !$MU_CFG.nil? and !$MU_CFG['public_address'].nil? and
641
+ !$MU_CFG['public_address'].empty? and @@my_public_ip != $MU_CFG['public_address']
354
642
  @@mu_public_addr = $MU_CFG['public_address']
355
643
  if !@@mu_public_addr.match(/^\d+\.\d+\.\d+\.\d+$/)
356
644
  resolver = Resolv::DNS.new
@@ -400,7 +688,9 @@ module MU
400
688
  def self.userEmail(user = MU.mu_user)
401
689
  @userlist ||= MU::Master.listUsers
402
690
  user = "mu" if user == "root"
403
- if Dir.exists?("#{MU.mainDataDir}/users/#{user}")
691
+ if Dir.exist?("#{MU.mainDataDir}/users/#{user}") and
692
+ File.readable?("#{MU.mainDataDir}/users/#{user}/email") and
693
+ File.size?("#{MU.mainDataDir}/users/#{user}/email")
404
694
  return File.read("#{MU.mainDataDir}/users/#{user}/email").chomp
405
695
  elsif @userlist.has_key?(user)
406
696
  return @userlist[user]['email']
@@ -413,7 +703,9 @@ module MU
413
703
  # Fetch the real-world name of a given Mu user
414
704
  def self.userName(user = MU.mu_user)
415
705
  @userlist ||= MU::Master.listUsers
416
- if Dir.exists?("#{MU.mainDataDir}/users/#{user}")
706
+ if Dir.exist?("#{MU.mainDataDir}/users/#{user}") and
707
+ File.readable?("#{MU.mainDataDir}/users/#{user}/realname") and
708
+ File.size?("#{MU.mainDataDir}/users/#{user}/realname")
417
709
  return File.read("#{MU.mainDataDir}/users/#{user}/realname").chomp
418
710
  elsif @userlist.has_key?(user)
419
711
  return @userlist[user]['email']
@@ -430,6 +722,14 @@ module MU
430
722
  ["Chef", "Ansible"]
431
723
  end
432
724
 
725
+ # The version of Chef we will install on nodes.
726
+ @@chefVersion = "14.0.190"
727
+ # The version of Chef we will install on nodes.
728
+ # @return [String]
729
+ def self.chefVersion
730
+ @@chefVersion
731
+ end
732
+
433
733
  MU.supportedGroomers.each { |groomer|
434
734
  require "mu/groomers/#{groomer.downcase}"
435
735
  }
@@ -451,6 +751,8 @@ module MU
451
751
  @@myRegion_var = zone.gsub(/^.*?\/|\-\d+$/, "")
452
752
  elsif MU::Cloud::AWS.hosted?
453
753
  @@myRegion_var ||= MU::Cloud::AWS.myRegion
754
+ elsif MU::Cloud::Azure.hosted?
755
+ @@myRegion_var ||= MU::Cloud::Azure.myRegion
454
756
  else
455
757
  @@myRegion_var = nil
456
758
  end
@@ -458,6 +760,7 @@ module MU
458
760
  end
459
761
 
460
762
  require 'mu/config'
763
+ require 'mu/adoption'
461
764
 
462
765
  # Figure out what cloud provider we're in, if any.
463
766
  # @return [String]: Google, AWS, etc. Returns nil if we don't seem to be in a cloud.
@@ -468,6 +771,10 @@ module MU
468
771
  elsif MU::Cloud::AWS.hosted?
469
772
  @@myInstanceId = MU::Cloud::AWS.getAWSMetaData("instance-id")
470
773
  return "AWS"
774
+ elsif MU::Cloud::Azure.hosted?
775
+ metadata = MU::Cloud::Azure.get_metadata()["compute"]
776
+ @@myInstanceId = MU::Cloud::Azure::Id.new("/subscriptions/"+metadata["subscriptionId"]+"/resourceGroups/"+metadata["resourceGroupName"]+"/providers/Microsoft.Compute/virtualMachines/"+metadata["name"])
777
+ return "Azure"
471
778
  end
472
779
  nil
473
780
  end
@@ -513,63 +820,105 @@ module MU
513
820
  @@myAZ_var
514
821
  end
515
822
 
516
- @@myCloudDescriptor = nil
517
- if MU::Cloud::Google.hosted?
518
- @@myCloudDescriptor = MU::Cloud::Google.compute.get_instance(
519
- MU::Cloud::Google.myProject,
520
- MU.myAZ,
521
- MU.myInstanceId
522
- )
523
- elsif MU::Cloud::AWS.hosted?
823
+ # Recursively turn a Ruby OpenStruct into a Hash
824
+ # @param struct [OpenStruct]
825
+ # @param stringify_keys [Boolean]
826
+ # @return [Hash]
827
+ def self.structToHash(struct, stringify_keys: false)
828
+ google_struct = false
524
829
  begin
525
- @@myCloudDescriptor = MU::Cloud::AWS.ec2(region: MU.myRegion).describe_instances(instance_ids: [MU.myInstanceId]).reservations.first.instances.first
526
- rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
527
- rescue Aws::Errors::MissingCredentialsError => e
528
- MU.log "I'm hosted in AWS, but I can't make API calls. Does this instance have an appropriate IAM profile?", MU::WARN
830
+ google_struct = struct.class.ancestors.include?(::Google::Apis::Core::Hashable)
831
+ rescue NameError
529
832
  end
530
- end
531
833
 
834
+ aws_struct = false
835
+ begin
836
+ aws_struct = struct.class.ancestors.include?(::Seahorse::Client::Response)
837
+ rescue NameError
838
+ end
532
839
 
533
- @@myVPC_var = nil
534
- # The VPC/Network in which this Mu master resides
535
- # XXX account for Google and non-cloud situations
536
- def self.myVPC
537
- return nil if MU.myCloudDescriptor.nil?
840
+ azure_struct = false
538
841
  begin
539
- if MU::Cloud::AWS.hosted?
540
- @@myVPC_var ||= MU.myCloudDescriptor.vpc_id
541
- elsif MU::Cloud::Google.hosted?
542
- @@myVPC_var = MU.myCloudDescriptor.network_interfaces.first.network.gsub(/.*?\/([^\/]+)$/, '\1')
842
+ azure_struct = struct.class.ancestors.include?(::MsRestAzure) or struct.class.name.match(/Azure::.*?::Mgmt::.*?::Models::/)
843
+ rescue NameError
844
+ end
845
+
846
+ if struct.is_a?(Struct) or struct.class.ancestors.include?(Struct) or
847
+ google_struct or aws_struct or azure_struct
848
+
849
+ hash = if azure_struct
850
+ MU::Cloud::Azure.respToHash(struct)
543
851
  else
544
- nil
852
+ struct.to_h
853
+ end
854
+
855
+ if stringify_keys
856
+ newhash = {}
857
+ hash.each_pair { |k, v|
858
+ newhash[k.to_s] = v
859
+ }
860
+ hash = newhash
545
861
  end
546
- rescue Aws::EC2::Errors::InternalError => e
547
- MU.log "Got #{e.inspect} on MU::Cloud::AWS.ec2(region: #{MU.myRegion}).describe_instances(instance_ids: [#{@@myInstanceId}])", MU::WARN
548
- sleep 10
862
+
863
+ hash.each_pair { |key, value|
864
+ hash[key] = self.structToHash(value, stringify_keys: stringify_keys)
865
+ }
866
+ return hash
867
+ elsif struct.is_a?(MU::Config::Ref)
868
+ struct = struct.to_h
869
+ elsif struct.is_a?(MU::Cloud::Azure::Id)
870
+ struct = struct.to_s
871
+ elsif struct.is_a?(Hash)
872
+ if stringify_keys
873
+ newhash = {}
874
+ struct.each_pair { |k, v|
875
+ newhash[k.to_s] = v
876
+ }
877
+ struct = newhash
878
+ end
879
+ struct.each_pair { |key, value|
880
+ struct[key] = self.structToHash(value, stringify_keys: stringify_keys)
881
+ }
882
+ return struct
883
+ elsif struct.is_a?(Array)
884
+ struct.map! { |elt|
885
+ self.structToHash(elt, stringify_keys: stringify_keys)
886
+ }
887
+ elsif struct.is_a?(String)
888
+ # Cleanse weird encoding problems
889
+ return struct.dup.to_s.force_encoding("ASCII-8BIT").encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
890
+ else
891
+ return struct
549
892
  end
550
- @@myVPC_var
551
893
  end
552
894
 
553
- @@mySubnets_var = nil
554
- # The AWS Subnets associated with the VPC this MU Master is in
555
- # XXX account for Google and non-cloud situations
556
- def self.mySubnets
557
- @@mySubnets_var ||= MU::Cloud::AWS.ec2(region: MU.myRegion).describe_subnets(
558
- filters: [
559
- {
560
- name: "vpc-id",
561
- values: [MU.myVPC]
562
- }
563
- ]
564
- ).subnets
895
+ @@myCloudDescriptor = nil
896
+ if MU.myCloud
897
+ found = MU::MommaCat.findStray(MU.myCloud, "server", cloud_id: @@myInstanceId, dummy_ok: true, region: MU.myRegion)
898
+ if !found.nil? and found.size == 1
899
+ @@myCloudDescriptor = found.first.cloud_desc
900
+ end
565
901
  end
566
902
 
567
- # The version of Chef we will install on nodes.
568
- @@chefVersion = "14.0.190"
569
- # The version of Chef we will install on nodes.
570
- # @return [String]
571
- def self.chefVersion;
572
- @@chefVersion
903
+
904
+ @@myVPCObj_var = nil
905
+ # The VPC/Network in which this Mu master resides
906
+ def self.myVPCObj
907
+ return nil if MU.myCloud.nil?
908
+ return @@myVPCObj_var if @@myVPCObj_var
909
+ cloudclass = const_get("MU").const_get("Cloud").const_get(MU.myCloud)
910
+ @@myVPCObj_var ||= cloudclass.myVPCObj
911
+ @@myVPCObj_var
912
+ end
913
+
914
+ @@myVPC_var = nil
915
+ # The VPC/Network in which this Mu master resides
916
+ def self.myVPC
917
+ return nil if MU.myCloud.nil?
918
+ return @@myVPC_var if @@myVPC_var
919
+ my_vpc_desc = MU.myVPCObj
920
+ @@myVPC_var ||= my_vpc_desc.cloud_id if my_vpc_desc
921
+ @@myVPC_var
573
922
  end
574
923
 
575
924
  # Mu's SSL certificate directory
@@ -590,20 +939,20 @@ module MU
590
939
  # @return [Boolean]
591
940
  def self.hashCmp(hash1, hash2, missing_is_default: false)
592
941
  return false if hash1.nil?
593
- hash2.each_pair { |k, v|
942
+ hash2.keys.each { |k|
594
943
  if hash1[k].nil?
595
944
  return false
596
945
  end
597
946
  }
598
947
  if !missing_is_default
599
- hash1.each_pair { |k, v|
948
+ hash1.keys.each { |k|
600
949
  if hash2[k].nil?
601
950
  return false
602
951
  end
603
952
  }
604
953
  end
605
954
 
606
- hash1.each_pair { |k, v|
955
+ hash1.keys.each { |k|
607
956
  if hash1[k].is_a?(Array)
608
957
  return false if !missing_is_default and hash2[k].nil?
609
958
  if !hash2[k].nil?
@@ -654,75 +1003,21 @@ module MU
654
1003
  end
655
1004
 
656
1005
 
657
- # Recursively turn a Ruby OpenStruct into a Hash
658
- # @param struct [OpenStruct]
659
- # @param stringify_keys [Boolean]
660
- # @return [Hash]
661
- def self.structToHash(struct, stringify_keys: false)
662
- google_struct = false
663
- begin
664
- google_struct = struct.class.ancestors.include?(::Google::Apis::Core::Hashable)
665
- rescue NameError
666
- end
667
-
668
- aws_struct = false
669
- begin
670
- aws_struct = struct.class.ancestors.include?(::Seahorse::Client::Response)
671
- rescue NameError
672
- end
673
-
674
- if struct.is_a?(Struct) or struct.class.ancestors.include?(Struct) or
675
- google_struct or aws_struct
676
-
677
- hash = struct.to_h
678
- if stringify_keys
679
- newhash = {}
680
- hash.each_pair { |k, v|
681
- newhash[k.to_s] = v
682
- }
683
- hash = newhash
684
- end
685
-
686
- hash.each_pair { |key, value|
687
- hash[key] = self.structToHash(value, stringify_keys: stringify_keys)
688
- }
689
- return hash
690
- elsif struct.is_a?(Hash)
691
- if stringify_keys
692
- newhash = {}
693
- struct.each_pair { |k, v|
694
- newhash[k.to_s] = v
695
- }
696
- struct = newhash
697
- end
698
- struct.each_pair { |key, value|
699
- struct[key] = self.structToHash(value, stringify_keys: stringify_keys)
700
- }
701
- return struct
702
- elsif struct.is_a?(Array)
703
- struct.map! { |elt|
704
- self.structToHash(elt, stringify_keys: stringify_keys)
705
- }
706
- else
707
- return struct
708
- end
709
- end
710
-
711
1006
  # Generate a random password which will satisfy the complexity requirements of stock Amazon Windows AMIs.
712
1007
  # return [String]: A password string.
713
- def self.generateWindowsPassword
1008
+ def self.generateWindowsPassword(safe_pattern: '~!@#%^&*_-+=`|(){}[]:;<>,.?', retries: 25)
714
1009
  # We have dopey complexity requirements, be stringent here.
715
1010
  # I'll be nice and not condense this into one elegant-but-unreadable regular expression
716
1011
  attempts = 0
717
- safe_metachars = Regexp.escape('~!@#%^&*_-+=`|(){}[]:;<>,.?')
1012
+ safe_metachars = Regexp.escape(safe_pattern)
718
1013
  begin
719
- if attempts > 25
1014
+ if attempts > retries
720
1015
  MU.log "Failed to generate an adequate Windows password after #{attempts}", MU::ERR
721
1016
  raise MuError, "Failed to generate an adequate Windows password after #{attempts}"
722
1017
  end
723
1018
  winpass = Password.random(14..16)
724
1019
  attempts += 1
725
- end while winpass.nil? or !winpass.match(/[A-Z]/) or !winpass.match(/[a-z]/) or !winpass.match(/\d/) or !winpass.match(/[#{safe_metachars}]/) or winpass.match(/[^\w\d#{safe_metachars}]/)
1020
+ end while winpass.nil? or !winpass.match(/^[a-z]/i) or !winpass.match(/[A-Z]/) or !winpass.match(/[a-z]/) or !winpass.match(/\d/) or !winpass.match(/[#{safe_metachars}]/) or winpass.match(/[^\w\d#{safe_metachars}]/)
726
1021
 
727
1022
  MU.log "Generated Windows password after #{attempts} attempts", MU::DEBUG
728
1023
  return winpass