cloud-mu 2.1.0beta → 3.0.0beta

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -53,21 +53,34 @@ module MU
53
53
  if deploy_id.nil? or deploy_id.empty?
54
54
  raise MuError, "Cannot fetch a deployment without a deploy_id"
55
55
  end
56
+
56
57
  # XXX this caching may be harmful, causing stale resource objects to stick
57
58
  # around. Have we fixed this? Sort of. Bad entries seem to have no kittens,
58
59
  # so force a reload if we see that. That's probably not the root problem.
59
- @@litter_semaphore.synchronize {
60
-
61
- if !use_cache or !@@litters.has_key?(deploy_id) or @@litters[deploy_id].kittens.nil? or @@litters[deploy_id].kittens.size == 0
62
- @@litters[deploy_id] = MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
63
- elsif set_context_to_me
64
- MU::MommaCat.setThreadContext(@@litters[deploy_id])
65
- end
66
- return @@litters[deploy_id]
67
- }
60
+ littercache = nil
61
+ begin
62
+ @@litter_semaphore.synchronize {
63
+ littercache = @@litters.dup
64
+ }
65
+ rescue ThreadError => e
66
+ # already locked by a parent caller and this is a read op, so this is ok
67
+ raise e if !e.message.match(/recursive locking/)
68
+ littercache = @@litters.dup
69
+ end
70
+ if !use_cache or littercache[deploy_id].nil?
71
+ newlitter = MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
72
+ # This, we have to synchronize, as it's a write
73
+ @@litter_semaphore.synchronize {
74
+ @@litters[deploy_id] ||= newlitter
75
+ }
76
+ elsif set_context_to_me
77
+ MU::MommaCat.setThreadContext(@@litters[deploy_id])
78
+ end
79
+ return @@litters[deploy_id]
68
80
  # MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
69
81
  end
70
82
 
83
+ attr_reader :initializing
71
84
  attr_reader :public_key
72
85
  attr_reader :deploy_secret
73
86
  attr_reader :deployment
@@ -155,16 +168,20 @@ module MU
155
168
  ssh_private_key: nil,
156
169
  ssh_public_key: nil,
157
170
  nocleanup: false,
171
+ appname: nil,
172
+ timestamp: nil,
158
173
  set_context_to_me: true,
159
174
  skip_resource_objects: false,
160
175
  no_artifacts: false,
161
176
  deployment_data: {},
177
+ delay_descriptor_load: false,
162
178
  mu_user: Etc.getpwuid(Process.uid).name
163
179
  )
164
180
  if deploy_id.nil? or deploy_id.empty?
165
181
  raise DeployInitializeError, "MommaCat objects must specify a deploy_id"
166
182
  end
167
183
  set_context_to_me = true if create
184
+ @initializing = true
168
185
 
169
186
  @deploy_id = deploy_id
170
187
  @mu_user = mu_user.dup
@@ -179,10 +196,11 @@ module MU
179
196
  end
180
197
  @kitten_semaphore = Mutex.new
181
198
  @kittens = {}
182
- @original_config = config
199
+ @original_config = MU::Config.manxify(config)
183
200
  @nocleanup = nocleanup
184
201
  @secret_semaphore = Mutex.new
185
202
  @notify_semaphore = Mutex.new
203
+ @need_deploy_flush = false
186
204
  @node_cert_semaphore = Mutex.new
187
205
  @deployment = deployment_data
188
206
  @deployment['mu_public_ip'] = MU.mu_public_ip
@@ -190,16 +208,21 @@ module MU
190
208
  @public_key = nil
191
209
  @secrets = Hash.new
192
210
  @secrets['instance_secret'] = Hash.new
193
- @environment = environment
194
211
  @ssh_key_name = ssh_key_name
195
212
  @ssh_private_key = ssh_private_key
196
213
  @ssh_public_key = ssh_public_key
197
214
  @clouds = {}
198
215
  @seed = MU.seed # pass this in
199
216
  @handle = MU.handle # pass this in
217
+ @appname = appname
218
+ @appname ||= @original_config['name'] if @original_config
219
+ @timestamp = timestamp
220
+ @environment = environment
221
+
200
222
  if set_context_to_me
201
223
  MU::MommaCat.setThreadContext(self)
202
224
  end
225
+
203
226
  if create and !@no_artifacts
204
227
  if !Dir.exist?(MU.dataDir+"/deployments")
205
228
  MU.log "Creating #{MU.dataDir}/deployments", MU::DEBUG
@@ -214,23 +237,27 @@ module MU
214
237
  raise DeployInitializeError, "New MommaCat repository requires config hash"
215
238
  end
216
239
  credsets = {}
217
- @appname = @original_config['name']
218
- MU::Cloud.resource_types.each { |cloudclass, data|
240
+
241
+ MU::Cloud.resource_types.values { |data|
219
242
  if !@original_config[data[:cfg_plural]].nil? and @original_config[data[:cfg_plural]].size > 0
220
243
  @original_config[data[:cfg_plural]].each { |resource|
244
+
221
245
  credsets[resource['cloud']] ||= []
222
246
  credsets[resource['cloud']] << resource['credentials']
223
247
  @clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
224
248
  @clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
249
+
225
250
  }
226
251
  end
227
252
  }
253
+
228
254
  @ssh_key_name, @ssh_private_key, @ssh_public_key = self.SSHKey
229
255
  if !File.exist?(deploy_dir+"/private_key")
230
256
  @private_key, @public_key = createDeployKey
231
257
  end
232
258
  MU.log "Creating deploy secret for #{MU.deploy_id}"
233
259
  @deploy_secret = Password.random(256)
260
+
234
261
  if !@original_config['scrub_mu_isms']
235
262
  credsets.each_pair { |cloud, creds|
236
263
  creds.uniq!
@@ -243,9 +270,14 @@ module MU
243
270
  if set_context_to_me
244
271
  MU::MommaCat.setThreadContext(self)
245
272
  end
273
+
246
274
  save!
275
+
247
276
  end
248
277
 
278
+ @appname ||= MU.appname
279
+ @timestamp ||= MU.timestamp
280
+ @environment ||= MU.environment
249
281
 
250
282
  loadDeploy(set_context_to_me: set_context_to_me)
251
283
  if !deploy_secret.nil?
@@ -255,13 +287,19 @@ module MU
255
287
  end
256
288
 
257
289
 
290
+ @@litter_semaphore.synchronize {
291
+ @@litters[@deploy_id] ||= self
292
+ }
293
+
258
294
  # Initialize a MU::Cloud object for each resource belonging to this
259
295
  # deploy, IF it already exists, which is to say if we're loading an
260
296
  # existing deploy instead of creating a new one.
261
297
  if !create and @deployment and @original_config and !skip_resource_objects
298
+
262
299
  MU::Cloud.resource_types.each_pair { |res_type, attrs|
263
300
  type = attrs[:cfg_plural]
264
301
  if @deployment.has_key?(type)
302
+
265
303
  @deployment[type].each_pair { |res_name, data|
266
304
  orig_cfg = nil
267
305
  if @original_config.has_key?(type)
@@ -284,16 +322,23 @@ module MU
284
322
  end
285
323
  }
286
324
  end
325
+
287
326
  if orig_cfg.nil?
288
327
  MU.log "Failed to locate original config for #{attrs[:cfg_name]} #{res_name} in #{@deploy_id}", MU::WARN if !["firewall_rules", "databases", "storage_pools", "cache_clusters", "alarms"].include?(type) # XXX shaddap
289
328
  next
290
329
  end
330
+
331
+ if orig_cfg['vpc']
332
+ ref = MU::Config::Ref.get(orig_cfg['vpc'])
333
+ orig_cfg['vpc']['id'] = ref if ref.kitten
334
+ end
335
+
291
336
  begin
292
337
  # Load up MU::Cloud objects for all our kittens in this deploy
293
338
  orig_cfg['environment'] = @environment # not always set in old deploys
294
339
  if attrs[:has_multiples]
295
- data.each_pair { |mu_name, actual_data|
296
- attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name)
340
+ data.keys.each { |mu_name|
341
+ attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name, delay_descriptor_load: delay_descriptor_load)
297
342
  }
298
343
  else
299
344
  # XXX hack for old deployments, this can go away some day
@@ -315,19 +360,99 @@ module MU
315
360
  attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id'])
316
361
  end
317
362
  rescue Exception => e
318
- MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
363
+ if e.class != MU::Cloud::MuCloudResourceNotImplemented
364
+ MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
365
+ end
319
366
  end
320
367
  }
368
+
321
369
  end
322
370
  }
323
371
  end
324
372
 
373
+ @initializing = false
374
+
325
375
  # XXX this .owned? method may get changed by the Ruby maintainers
326
376
  # if !@@litter_semaphore.owned?
327
- # @@litter_semaphore.synchronize {
328
- # @@litters[@deploy_id] = self
329
- # }
330
- # end
377
+ end # end of initialize()
378
+
379
+ # List all the cloud providers declared by resources in our deploy.
380
+ def cloudsUsed
381
+ seen = []
382
+ seen << @original_config['cloud'] if @original_config['cloud']
383
+ MU::Cloud.resource_types.values.each { |attrs|
384
+ type = attrs[:cfg_plural]
385
+ if @original_config.has_key?(type)
386
+ @original_config[type].each { |resource|
387
+ seen << resource['cloud'] if resource['cloud']
388
+ }
389
+ end
390
+ }
391
+ seen.uniq
392
+ end
393
+
394
+ # Assay this deployment for a list of credentials (from mu.yaml) which are
395
+ # used. Our Cleanup module can leverage this to skip unnecessary checks.
396
+ # @return [Array<String>]
397
+ def credsUsed
398
+ return [] if !@original_config
399
+ seen = []
400
+ # clouds = []
401
+ seen << @original_config['credentials'] if @original_config['credentials']
402
+ # defaultcloud = @original_config['cloud']
403
+ MU::Cloud.resource_types.values.each { |attrs|
404
+ type = attrs[:cfg_plural]
405
+ if @original_config.has_key?(type)
406
+ @original_config[type].each { |resource|
407
+ if resource['credentials']
408
+ seen << resource['credentials']
409
+ else
410
+ cloudclass = if @original_config['cloud']
411
+ Object.const_get("MU").const_get("Cloud").const_get(@original_config['cloud'])
412
+ else
413
+ Object.const_get("MU").const_get("Cloud").const_get(MU::Config.defaultCloud)
414
+ end
415
+ seen << cloudclass.credConfig(name_only: true)
416
+ end
417
+ }
418
+ end
419
+ }
420
+ # XXX insert default for each cloud provider if not explicitly seen
421
+ seen.uniq
422
+ end
423
+
424
+ # List the regions used by each resource in our deploy. This will just be
425
+ # a flat list of strings with no regard to which region belongs with what
426
+ # cloud provider- things mostly use this as a lookup table so they can
427
+ # safely skip unnecessary regions when creating/cleaning deploy artifacts.
428
+ # @return [Array<String>]
429
+ def regionsUsed
430
+ return [] if !@original_config
431
+ regions = []
432
+ regions << @original_config['region'] if @original_config['region']
433
+ MU::Cloud.resource_types.each_pair { |res_type, attrs|
434
+ type = attrs[:cfg_plural]
435
+ if @original_config.has_key?(type)
436
+ @original_config[type].each { |resource|
437
+ if resource['cloud']
438
+ cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud'])
439
+ resclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud']).const_get(res_type.to_s)
440
+ if resclass.isGlobal?
441
+ regions.concat(cloudclass.listRegions)
442
+ next
443
+ elsif !resource['region']
444
+ regions << cloudclass.myRegion
445
+ end
446
+ end
447
+ if resource['region']
448
+ regions << resource['region'] if resource['region']
449
+ else
450
+ end
451
+ }
452
+ end
453
+ }
454
+
455
+ regions.uniq
331
456
  end
332
457
 
333
458
  # Tell us the number of first-class resources we've configured, optionally
@@ -341,13 +466,13 @@ module MU
341
466
  return 0 if @original_config.nil?
342
467
  if !types.nil? and types.size > 0
343
468
  types.each { |type|
344
- shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
469
+ cfg_plural = MU::Cloud.getResourceNames(type)[2]
345
470
  realtypes << cfg_plural
346
471
  }
347
472
  end
348
473
 
349
474
  count = 0
350
- MU::Cloud.resource_types.each { |cloudclass, data|
475
+ MU::Cloud.resource_types.values.each { |data|
351
476
  next if @original_config[data[:cfg_plural]].nil?
352
477
  next if realtypes.size > 0 and (!negate and !realtypes.include?(data[:cfg_plural]))
353
478
  @original_config[data[:cfg_plural]].each { |resource|
@@ -365,13 +490,13 @@ module MU
365
490
  raise MuError, "Nil arguments to removeKitten are not allowed"
366
491
  end
367
492
  @kitten_semaphore.synchronize {
368
- MU::Cloud.resource_types.each_pair { |name, attrs|
493
+ MU::Cloud.resource_types.values.each { |attrs|
369
494
  type = attrs[:cfg_plural]
370
495
  next if !@kittens.has_key?(type)
371
496
  tmplitter = @kittens[type].values.dup
372
497
  tmplitter.each { |nodeclass, data|
373
498
  if data.is_a?(Hash)
374
- data.each_pair { |mu_name, obj|
499
+ data.keys.each { |mu_name|
375
500
  if data == object
376
501
  @kittens[type][nodeclass].delete(mu_name)
377
502
  return
@@ -421,17 +546,19 @@ module MU
421
546
  if !type or !name or !object or !object.mu_name
422
547
  raise MuError, "Nil arguments to addKitten are not allowed (got type: #{type}, name: #{name}, and '#{object}' to add)"
423
548
  end
424
- shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
425
- type = cfg_plural
549
+
550
+ _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
426
551
  has_multiples = attrs[:has_multiples]
552
+ object.intoDeploy(self)
427
553
 
428
554
  @kitten_semaphore.synchronize {
429
555
  @kittens[type] ||= {}
556
+ @kittens[type][object.habitat] ||= {}
430
557
  if has_multiples
431
- @kittens[type][name] ||= {}
432
- @kittens[type][name][object.mu_name] = object
558
+ @kittens[type][object.habitat][name] ||= {}
559
+ @kittens[type][object.habitat][name][object.mu_name] = object
433
560
  else
434
- @kittens[type][name] = object
561
+ @kittens[type][object.habitat][name] = object
435
562
  end
436
563
  }
437
564
  end
@@ -458,7 +585,7 @@ module MU
458
585
  return false
459
586
  end
460
587
  rescue OpenSSL::PKey::RSAError => e
461
- MU.log e.inspect, MU::ERR
588
+ MU.log "Error decrypting provided ciphertext using private key from #{deploy_dir}/private_key: #{e.message}", MU::ERR, details: ciphertext
462
589
  return false
463
590
  end
464
591
  end
@@ -506,7 +633,7 @@ module MU
506
633
  raise MuError, "Got no argument to MU::MommaCat.getResourceName"
507
634
  end
508
635
  if @appname.nil? or @environment.nil? or @timestamp.nil? or @seed.nil?
509
- MU.log "Missing global deploy variables in thread #{Thread.current.object_id}, using bare name '#{name}' (appname: #{@appname}, environment: #{@environment}, timestamp: #{@timestamp}, seed: #{@seed}", MU::WARN, details: caller
636
+ MU.log "getResourceName: Missing global deploy variables in thread #{Thread.current.object_id}, using bare name '#{name}' (appname: #{@appname}, environment: #{@environment}, timestamp: #{@timestamp}, seed: #{@seed}, deploy_id: #{@deploy_id}", MU::WARN, details: caller
510
637
  return name
511
638
  end
512
639
  need_unique_string = false if scrub_mu_isms
@@ -597,7 +724,7 @@ module MU
597
724
  def saveNodeSecret(instance_id, raw_secret, type)
598
725
  return if @no_artifacts
599
726
  if instance_id.nil? or instance_id.empty? or raw_secret.nil? or raw_secret.empty? or type.nil? or type.empty?
600
- raise SecretError, "saveNodeSecret requires instance_id, raw_secret, and type args"
727
+ raise SecretError, "saveNodeSecret requires instance_id (#{instance_id}), raw_secret (#{raw_secret}), and type (#{type}) args"
601
728
  end
602
729
  MU::MommaCat.lock("deployment-notification")
603
730
  loadDeploy(true) # make sure we're not trampling deployment data
@@ -746,14 +873,14 @@ module MU
746
873
  kitten.groom
747
874
  rescue Exception => e
748
875
  MU::MommaCat.unlockAll
749
- if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exists?(deploy_dir+"/.cleanup."+cloud_id) and !File.exists?(deploy_dir+"/.cleanup")
876
+ if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exist?(deploy_dir+"/.cleanup."+cloud_id) and !File.exist?(deploy_dir+"/.cleanup")
750
877
  MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
751
- # sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
752
- # msg: e.inspect,
753
- # kitten: kitten,
754
- # data: e.backtrace,
755
- # debug: true
756
- # )
878
+ sendAdminSlack("Grooming FAILED for `#{kitten.mu_name}` with `#{e.message}` :crying_cat_face:", msg: e.backtrace.join("\n"))
879
+ sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
880
+ msg: e.inspect,
881
+ data: e.backtrace,
882
+ debug: true
883
+ )
757
884
  raise e if reraise_fail
758
885
  else
759
886
  MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
@@ -761,7 +888,7 @@ module MU
761
888
  return
762
889
  end
763
890
 
764
- if !@deployment['servers'].nil?
891
+ if !@deployment['servers'].nil? and !sync_wait
765
892
  syncLitter(@deployment["servers"].keys, triggering_node: kitten)
766
893
  end
767
894
  MU::MommaCat.unlock(cloud_id+"-mommagroom")
@@ -770,12 +897,12 @@ module MU
770
897
  end
771
898
  MU::MommaCat.getLitter(MU.deploy_id, use_cache: false)
772
899
  MU::MommaCat.syncMonitoringConfig(false)
773
- MU::MommaCat.createStandardTags(cloud_id, region: kitten.config["region"])
774
900
  MU.log "Grooming complete for '#{name}' mu_name on \"#{MU.handle}\" (#{MU.deploy_id})"
775
- FileUtils.touch("/opt/mu/var/deployments/#{MU.deploy_id}/#{name}_done.txt")
901
+ FileUtils.touch(MU.dataDir+"/deployments/#{MU.deploy_id}/#{name}_done.txt")
776
902
  MU::MommaCat.unlockAll
777
903
  if first_groom
778
- sendAdminMail("Grooming complete for '#{name}' (#{mu_name}) on deploy \"#{MU.handle}\" (#{MU.deploy_id})", kitten: kitten)
904
+ sendAdminSlack("Grooming complete for #{mu_name} :heart_eyes_cat:")
905
+ sendAdminMail("Grooming complete for '#{name}' (#{mu_name}) on deploy \"#{MU.handle}\" (#{MU.deploy_id})")
779
906
  end
780
907
  return
781
908
  end
@@ -797,7 +924,7 @@ module MU
797
924
  ssh_dir.chown(Etc.getpwnam(@mu_user).uid, Etc.getpwnam(@mu_user).gid)
798
925
  end
799
926
  end
800
- if !File.exists?("#{ssh_dir}/#{@ssh_key_name}")
927
+ if !File.exist?("#{ssh_dir}/#{@ssh_key_name}")
801
928
  MU.log "Generating SSH key #{@ssh_key_name}"
802
929
  %x{/usr/bin/ssh-keygen -N "" -f #{ssh_dir}/#{@ssh_key_name}}
803
930
  end
@@ -811,6 +938,7 @@ module MU
811
938
  ["servers", "server_pools", "container_clusters"].each { |type|
812
939
  next if @original_config[type].nil?
813
940
  @original_config[type].each { |descriptor|
941
+ next if descriptor['cloud'] != "AWS"
814
942
  if descriptor['credentials']
815
943
  creds_used << descriptor['credentials']
816
944
  else
@@ -836,10 +964,9 @@ module MU
836
964
  # in lock() or unlock(). We can't just wrap our iterator block in a
837
965
  # semaphore here, because we're calling another method that uses the
838
966
  # same semaphore.
839
- lock_copy = nil
840
967
  @lock_semaphore.synchronize {
841
968
  delete_list = []
842
- @locks[Thread.current.object_id].each_pair { |id, fh|
969
+ @locks[Thread.current.object_id].keys.each { |id|
843
970
  MU.log "Releasing lock on #{deploy_dir(MU.deploy_id)}/locks/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
844
971
  begin
845
972
  @locks[Thread.current.object_id][id].flock(File::LOCK_UN)
@@ -895,7 +1022,7 @@ module MU
895
1022
  else
896
1023
  @locks[Thread.current.object_id][id].flock(File::LOCK_EX)
897
1024
  end
898
- rescue IOError => e
1025
+ rescue IOError
899
1026
  raise MU::BootstrapTempFail, "Interrupted waiting for lock on thread #{Thread.current.object_id}, probably just a node rebooting as part of a synchronous install"
900
1027
  end
901
1028
  MU.log "Lock on #{lockdir}/#{id}.lock on thread #{Thread.current.object_id} acquired", MU::DEBUG
@@ -940,11 +1067,11 @@ module MU
940
1067
  path = File.expand_path(MU.dataDir+"/deployments")
941
1068
  if Dir.exist?(path+"/"+deploy_id)
942
1069
  unlockAll
943
- MU.log "Purging #{path}/#{deploy_id}" if File.exists?(path+"/"+deploy_id+"/deployment.json")
1070
+ MU.log "Purging #{path}/#{deploy_id}" if File.exist?(path+"/"+deploy_id+"/deployment.json")
944
1071
 
945
1072
  FileUtils.rm_rf(path+"/"+deploy_id, :secure => true)
946
1073
  end
947
- if File.exists?(path+"/unique_ids")
1074
+ if File.exist?(path+"/unique_ids")
948
1075
  File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
949
1076
  newlines = []
950
1077
  f.flock(File::LOCK_EX)
@@ -973,10 +1100,9 @@ module MU
973
1100
  MU::MommaCat.lock("clean-terminated-instances", false, true)
974
1101
  MU.log "Checking for harvested instances in need of cleanup", MU::DEBUG
975
1102
  parent_thread_id = Thread.current.object_id
976
- cleanup_threads = []
977
1103
  purged = 0
978
1104
  MU::MommaCat.listDeploys.each { |deploy_id|
979
- next if File.exists?(deploy_dir(deploy_id)+"/.cleanup")
1105
+ next if File.exist?(deploy_dir(deploy_id)+"/.cleanup")
980
1106
  MU.log "Checking for dead wood in #{deploy_id}", MU::DEBUG
981
1107
  @cleanup_threads << Thread.new {
982
1108
  MU.dupGlobals(parent_thread_id)
@@ -984,32 +1110,35 @@ module MU
984
1110
  deploy = MU::MommaCat.getLitter(deploy_id, set_context_to_me: true, use_cache: false)
985
1111
  purged_this_deploy = 0
986
1112
  if deploy.kittens.has_key?("servers")
987
- deploy.kittens["servers"].each_pair { |nodeclass, servers|
988
- deletia = []
989
- servers.each_pair { |mu_name, server|
990
- server.describe
991
- if !server.cloud_id
992
- MU.log "Checking for deletion of #{mu_name}, but unable to fetch its cloud_id", MU::WARN, details: server
993
- elsif !server.active?
994
- next if File.exists?(deploy_dir(deploy_id)+"/.cleanup-"+server.cloud_id)
995
- deletia << mu_name
996
- MU.log "Deleting #{server} (#{nodeclass}), formerly #{server.cloud_id}", MU::NOTICE
997
- begin
998
- server.destroy
999
- deploy.sendAdminMail("Retired terminated node #{mu_name}", kitten: server)
1000
- rescue Exception => e
1001
- MU.log "Saw #{e.message} while retiring #{mu_name}", MU::ERR, details: e.backtrace
1002
- next
1113
+ deploy.kittens["servers"].values.each { |nodeclasses|
1114
+ nodeclasses.each_pair { |nodeclass, servers|
1115
+ deletia = []
1116
+ servers.each_pair { |mu_name, server|
1117
+ server.describe
1118
+ if !server.cloud_id
1119
+ MU.log "Checking for presence of #{mu_name}, but unable to fetch its cloud_id", MU::WARN, details: server
1120
+ elsif !server.active?
1121
+ next if File.exist?(deploy_dir(deploy_id)+"/.cleanup-"+server.cloud_id)
1122
+ deletia << mu_name
1123
+ MU.log "Cleaning up metadata for #{server} (#{nodeclass}), formerly #{server.cloud_id}, which appears to have been terminated", MU::NOTICE
1124
+ begin
1125
+ server.destroy
1126
+ deploy.sendAdminMail("Retired metadata for terminated node #{mu_name}")
1127
+ deploy.sendAdminSlack("Retired metadata for terminated node `#{mu_name}`")
1128
+ rescue Exception => e
1129
+ MU.log "Saw #{e.message} while retiring #{mu_name}", MU::ERR, details: e.backtrace
1130
+ next
1131
+ end
1132
+ MU.log "Cleanup of metadata for #{server} (#{nodeclass}), formerly #{server.cloud_id} complete", MU::NOTICE
1133
+ purged = purged + 1
1134
+ purged_this_deploy = purged_this_deploy + 1
1003
1135
  end
1004
- MU.log "Deletion of #{server} (#{nodeclass}), formerly #{server.cloud_id} complete", MU::NOTICE
1005
- purged = purged + 1
1006
- purged_this_deploy = purged_this_deploy + 1
1136
+ }
1137
+ if purged_this_deploy > 0
1138
+ # XXX some kind of filter (obey sync_siblings on nodes' configs)
1139
+ deploy.syncLitter(servers.keys)
1007
1140
  end
1008
1141
  }
1009
- if purged_this_deploy > 0
1010
- # XXX some kind of filter (obey sync_siblings on nodes' configs)
1011
- deploy.syncLitter(servers.keys)
1012
- end
1013
1142
  }
1014
1143
  end
1015
1144
  MU.purgeGlobals
@@ -1029,6 +1158,7 @@ module MU
1029
1158
  MU::MommaCat.unlock("clean-terminated-instances", true)
1030
1159
  end
1031
1160
 
1161
+ @@dummy_cache = {}
1032
1162
 
1033
1163
  # Locate a resource that's either a member of another deployment, or of no
1034
1164
  # deployment at all, and return a {MU::Cloud} object for it.
@@ -1045,7 +1175,8 @@ module MU
1045
1175
  # @param dummy_ok [Boolean]: Permit return of a faked {MU::Cloud} object if we don't have enough information to identify a real live one.
1046
1176
  # @param flags [Hash]: Other cloud or resource type specific options to pass to that resource's find() method
1047
1177
  # @return [Array<MU::Cloud>]
1048
- def self.findStray(cloud,
1178
+ def self.findStray(
1179
+ cloud,
1049
1180
  type,
1050
1181
  deploy_id: nil,
1051
1182
  name: nil,
@@ -1058,18 +1189,31 @@ module MU
1058
1189
  allow_multi: false,
1059
1190
  calling_deploy: MU.mommacat,
1060
1191
  flags: {},
1192
+ habitats: [],
1061
1193
  dummy_ok: false,
1062
1194
  debug: false
1063
- )
1195
+ )
1196
+ start = Time.now
1197
+ callstr = "findStray(cloud: #{cloud}, type: #{type}, deploy_id: #{deploy_id}, calling_deploy: #{calling_deploy.deploy_id if !calling_deploy.nil?}, name: #{name}, cloud_id: #{cloud_id}, tag_key: #{tag_key}, tag_value: #{tag_value}, credentials: #{credentials}, habitats: #{habitats ? habitats.to_s : "[]"}, dummy_ok: #{dummy_ok.to_s}, flags: #{flags.to_s}) from #{caller[0]}"
1198
+ callstack = caller.dup
1199
+
1064
1200
  return nil if cloud == "CloudFormation" and !cloud_id.nil?
1201
+ shortclass, _cfg_name, cfg_plural, classname, _attrs = MU::Cloud.getResourceNames(type)
1202
+ if !MU::Cloud.supportedClouds.include?(cloud) or shortclass.nil?
1203
+ MU.log "findStray was called with bogus cloud argument '#{cloud}'", MU::WARN, details: callstr
1204
+ return nil
1205
+ end
1206
+
1065
1207
  begin
1208
+ # TODO this is dumb as hell, clean this up.. and while we're at it
1209
+ # .dup everything so we don't mangle referenced values from the caller
1066
1210
  deploy_id = deploy_id.to_s if deploy_id.class.to_s == "MU::Config::Tail"
1067
1211
  name = name.to_s if name.class.to_s == "MU::Config::Tail"
1068
1212
  cloud_id = cloud_id.to_s if !cloud_id.nil?
1069
1213
  mu_name = mu_name.to_s if mu_name.class.to_s == "MU::Config::Tail"
1070
1214
  tag_key = tag_key.to_s if tag_key.class.to_s == "MU::Config::Tail"
1071
1215
  tag_value = tag_value.to_s if tag_value.class.to_s == "MU::Config::Tail"
1072
- shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1216
+ type = cfg_plural
1073
1217
  resourceclass = MU::Cloud.loadCloudType(cloud, shortclass)
1074
1218
  cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1075
1219
 
@@ -1097,11 +1241,11 @@ module MU
1097
1241
  end
1098
1242
  loglevel = debug ? MU::NOTICE : MU::DEBUG
1099
1243
 
1100
- MU.log "findStray(cloud: #{cloud}, type: #{type}, deploy_id: #{deploy_id}, calling_deploy: #{calling_deploy.deploy_id if !calling_deploy.nil?}, name: #{name}, cloud_id: #{cloud_id}, tag_key: #{tag_key}, tag_value: #{tag_value}, credentials: #{credentials})", loglevel, details: flags
1244
+ MU.log callstr, loglevel, details: caller
1101
1245
 
1102
1246
  # See if the thing we're looking for is a member of the deploy that's
1103
1247
  # asking after it.
1104
- if !deploy_id.nil? and !calling_deploy.nil? and flags.empty? and
1248
+ if !deploy_id.nil? and !calling_deploy.nil? and
1105
1249
  calling_deploy.deploy_id == deploy_id and (!name.nil? or !mu_name.nil?)
1106
1250
  handle = calling_deploy.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id, credentials: credentials)
1107
1251
  return [handle] if !handle.nil?
@@ -1109,23 +1253,61 @@ module MU
1109
1253
 
1110
1254
  kittens = {}
1111
1255
  # Search our other deploys for matching resources
1112
- if (deploy_id or name or mu_name or cloud_id)# and flags.empty?
1256
+ if (deploy_id or name or mu_name or cloud_id)
1257
+ MU.log "findStray: searching my deployments (#{cfg_plural}, name: #{name}, deploy_id: #{deploy_id}, mu_name: #{mu_name}) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1258
+
1259
+ # Check our in-memory cache of live deploys before resorting to
1260
+ # metadata
1261
+ littercache = nil
1262
+ # Sometimes we're called inside a locked thread, sometimes not. Deal
1263
+ # with locking gracefully.
1264
+ begin
1265
+ @@litter_semaphore.synchronize {
1266
+ littercache = @@litters.dup
1267
+ }
1268
+ rescue ThreadError => e
1269
+ raise e if !e.message.match(/recursive locking/)
1270
+ littercache = @@litters.dup
1271
+ end
1272
+
1273
+ littercache.each_pair { |cur_deploy, momma|
1274
+ next if deploy_id and deploy_id != cur_deploy
1275
+
1276
+ straykitten = momma.findLitterMate(type: type, cloud_id: cloud_id, name: name, mu_name: mu_name, credentials: credentials, created_only: true)
1277
+ if straykitten
1278
+ MU.log "Found matching kitten #{straykitten.mu_name} in-memory - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1279
+ # Peace out if we found the exact resource we want
1280
+ if cloud_id and straykitten.cloud_id.to_s == cloud_id.to_s
1281
+ return [straykitten]
1282
+ elsif mu_name and straykitten.mu_name == mu_name
1283
+ return [straykitten]
1284
+ else
1285
+ kittens[straykitten.cloud_id] ||= straykitten
1286
+ end
1287
+ end
1288
+ }
1289
+
1113
1290
  mu_descs = MU::MommaCat.getResourceMetadata(cfg_plural, name: name, deploy_id: deploy_id, mu_name: mu_name)
1291
+ MU.log "findStray: #{mu_descs.size.to_s} deploys had matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1114
1292
 
1115
- mu_descs.each_pair { |deploy_id, matches|
1116
- MU.log "findStray: #{deploy_id} had #{matches.size.to_s} initial matches", loglevel
1293
+ mu_descs.each_pair { |cur_deploy_id, matches|
1294
+ MU.log "findStray: #{cur_deploy_id} had #{matches.size.to_s} initial matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1117
1295
  next if matches.nil? or matches.size == 0
1118
- momma = MU::MommaCat.getLitter(deploy_id)
1119
- straykitten = nil
1120
1296
 
1297
+ momma = MU::MommaCat.getLitter(cur_deploy_id)
1298
+
1299
+ straykitten = nil
1121
1300
 
1122
1301
  # If we found exactly one match in this deploy, use its metadata to
1123
1302
  # guess at resource names we weren't told.
1124
- if matches.size == 1 and name.nil? and mu_name.nil?
1303
+ if matches.size > 1 and cloud_id
1304
+ MU.log "findStray: attempting to narrow down multiple matches with cloud_id #{cloud_id} - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1305
+ straykitten = momma.findLitterMate(type: type, cloud_id: cloud_id, credentials: credentials, created_only: true)
1306
+ elsif matches.size == 1 and name.nil? and mu_name.nil?
1125
1307
  if cloud_id.nil?
1126
1308
  straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: matches.first["cloud_id"], credentials: credentials)
1127
1309
  else
1128
- MU.log "findStray: attempting to narrow down with cloud_id #{cloud_id}", loglevel
1310
+ MU.log "findStray: fetching single match with cloud_id #{cloud_id} - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1129
1311
  straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: cloud_id, credentials: credentials)
1130
1312
  end
1131
1313
  # elsif !flags.nil? and !flags.empty? # XXX eh, maybe later
@@ -1146,16 +1328,17 @@ module MU
1146
1328
  end
1147
1329
 
1148
1330
  next if straykitten.nil?
1331
+ straykitten.intoDeploy(momma)
1149
1332
 
1150
1333
  if straykitten.cloud_id.nil?
1151
1334
  MU.log "findStray: kitten #{straykitten.mu_name} came back with nil cloud_id", MU::WARN
1152
1335
  next
1153
1336
  end
1154
1337
 
1155
- kittens[straykitten.cloud_id] = straykitten
1338
+ kittens[straykitten.cloud_id] ||= straykitten
1156
1339
 
1157
1340
  # Peace out if we found the exact resource we want
1158
- if cloud_id and straykitten.cloud_id == cloud_id
1341
+ if cloud_id and straykitten.cloud_id.to_s == cloud_id.to_s
1159
1342
  return [straykitten]
1160
1343
  # ...or if we've validated our one possible match
1161
1344
  elsif !cloud_id and mu_descs.size == 1 and matches.size == 1
@@ -1186,87 +1369,208 @@ module MU
1186
1369
  found_the_thing = false
1187
1370
  credlist.each { |creds|
1188
1371
  break if found_the_thing
1189
- if cloud_id or (tag_key and tag_value) or !flags.empty?
1190
- regions = []
1191
- begin
1192
- if region
1193
- regions << region
1194
- else
1195
- regions = cloudclass.listRegions(credentials: creds)
1196
- end
1372
+ if cloud_id or (tag_key and tag_value) or !flags.empty? or allow_multi
1373
+
1374
+ regions = begin
1375
+ region ? [region] : cloudclass.listRegions(credentials: creds)
1197
1376
  rescue NoMethodError # Not all cloud providers have regions
1198
- regions = [""]
1377
+ [nil]
1199
1378
  end
1200
1379
 
1201
- if cloud == "Google" and ["vpcs", "firewall_rules"].include?(cfg_plural)
1380
+ # ..not all resource types care about regions either
1381
+ if resourceclass.isGlobal?
1202
1382
  regions = [nil]
1203
1383
  end
1204
1384
 
1205
- cloud_descs = {}
1206
- regions.each { |r|
1207
- cloud_descs[r] = resourceclass.find(cloud_id: cloud_id, region: r, tag_key: tag_key, tag_value: tag_value, flags: flags, credentials: creds)
1208
- # Stop if you found the thing
1209
- if cloud_id and cloud_descs[r] and !cloud_descs[r].empty?
1210
- found_the_thing = true
1211
- break
1385
+ # Decide what habitats (accounts/projects/subscriptions) we'll
1386
+ # search, if applicable for this resource type.
1387
+ habitats ||= []
1388
+ begin
1389
+ if flags["project"] # backwards-compat
1390
+ habitats << flags["project"]
1212
1391
  end
1213
- }
1214
- regions.each { |r|
1215
- next if cloud_descs[r].nil?
1216
- cloud_descs[r].each_pair { |kitten_cloud_id, descriptor|
1217
- # We already have a MU::Cloud object for this guy, use it
1218
- if kittens.has_key?(kitten_cloud_id)
1219
- matches << kittens[kitten_cloud_id]
1220
- elsif kittens.size == 0
1221
- if !dummy_ok
1222
- next
1392
+ if habitats.empty?
1393
+ if resourceclass.canLiveIn.include?(nil)
1394
+ habitats << nil
1395
+ end
1396
+ if resourceclass.canLiveIn.include?(:Habitat)
1397
+ habitats.concat(cloudclass.listProjects(creds))
1398
+ end
1399
+ end
1400
+ rescue NoMethodError # we only expect this to work on Google atm
1401
+ end
1402
+
1403
+ if habitats.empty?
1404
+ habitats << nil
1405
+ end
1406
+ habitats.uniq!
1407
+
1408
+ habitat_threads = []
1409
+ desc_semaphore = Mutex.new
1410
+
1411
+ cloud_descs = {}
1412
+ habitats.each { |hab|
1413
+ begin
1414
+ habitat_threads.each { |t| t.join(0.1) }
1415
+ habitat_threads.reject! { |t| t.nil? or !t.status }
1416
+ sleep 1 if habitat_threads.size > 5
1417
+ end while habitat_threads.size > 5
1418
+ habitat_threads << Thread.new(hab) { |p|
1419
+ MU.log "findStray: Searching #{p} (#{habitat_threads.size.to_s} habitat threads running) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1420
+ cloud_descs[p] = {}
1421
+ region_threads = []
1422
+ regions.each { |reg| region_threads << Thread.new(reg) { |r|
1423
+ MU.log "findStray: Searching #{r} in #{p} (#{region_threads.size.to_s} region threads running) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1424
+ MU.log "findStray: calling #{classname}.find(cloud_id: #{cloud_id}, region: #{r}, tag_key: #{tag_key}, tag_value: #{tag_value}, flags: #{flags}, credentials: #{creds}, project: #{p}) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1425
+ begin
1426
+ found = resourceclass.find(cloud_id: cloud_id, region: r, tag_key: tag_key, tag_value: tag_value, flags: flags, credentials: creds, habitat: p)
1427
+ MU.log "findStray: #{found ? found.size.to_s : "nil"} results - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1428
+ rescue Exception => e
1429
+ MU.log "#{e.class.name} THREW A FIND EXCEPTION "+e.message, MU::WARN, details: caller
1430
+ pp e.backtrace
1431
+ MU.log "#{callstr}", MU::WARN, details: callstack
1432
+ exit
1433
+ end
1434
+ if found
1435
+ desc_semaphore.synchronize {
1436
+ cloud_descs[p][r] = found
1437
+ }
1438
+ end
1439
+ # Stop if you found the thing by a specific cloud_id
1440
+ if cloud_id and found and !found.empty?
1441
+ found_the_thing = true
1442
+ Thread.exit
1443
+ end
1444
+ } }
1445
+ begin
1446
+ region_threads.each { |t| t.join(0.1) }
1447
+ region_threads.reject! { |t| t.nil? or !t.status }
1448
+ if region_threads.size > 0
1449
+ MU.log "#{region_threads.size.to_s} regions still running in #{p}", loglevel
1450
+ sleep 3
1223
1451
  end
1224
- # If we don't have a MU::Cloud object, manufacture a dummy one.
1225
- # Give it a fake name if we have to and have decided that's ok.
1226
- if (name.nil? or name.empty?)
1452
+ end while region_threads.size > 0
1453
+ }
1454
+ }
1455
+ begin
1456
+ habitat_threads.each { |t| t.join(0.1) }
1457
+ habitat_threads.reject! { |t| t.nil? or !t.status }
1458
+ if habitat_threads.size > 0
1459
+ MU.log "#{habitat_threads.size.to_s} habitats still running", loglevel
1460
+ sleep 3
1461
+ end
1462
+ end while habitat_threads.size > 0
1463
+
1464
+ habitat_threads = []
1465
+ habitats.each { |hab| habitat_threads << Thread.new(hab) { |p|
1466
+ region_threads = []
1467
+ regions.each { |reg| region_threads << Thread.new(reg) { |r|
1468
+ next if cloud_descs[p][r].nil?
1469
+ cloud_descs[p][r].each_pair { |kitten_cloud_id, descriptor|
1470
+
1471
+ # We already have a MU::Cloud object for this guy, use it
1472
+ if kittens.has_key?(kitten_cloud_id)
1473
+ desc_semaphore.synchronize {
1474
+ matches << kittens[kitten_cloud_id]
1475
+ }
1476
+ elsif kittens.size == 0
1227
1477
  if !dummy_ok
1228
- MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return", loglevel, details: caller
1229
1478
  next
1230
- else
1231
- if !mu_name.nil?
1232
- name = mu_name
1233
- elsif !tag_value.nil?
1234
- name = tag_value
1479
+ end
1480
+
1481
+ # If we don't have a MU::Cloud object, manufacture a dummy
1482
+ # one. Give it a fake name if we have to and have decided
1483
+ # that's ok. Wild inferences from the cloud descriptor are
1484
+ # ok to try here.
1485
+ use_name = if (name.nil? or name.empty?)
1486
+ if !dummy_ok
1487
+ nil
1488
+ elsif !mu_name.nil?
1489
+ mu_name
1235
1490
  else
1236
- name = kitten_cloud_id
1491
+ try = nil
1492
+ [:display_name, :name, (resourceclass.cfg_name+"_name").to_sym].each { |field|
1493
+ if descriptor.respond_to?(field) and descriptor.send(field).is_a?(String)
1494
+ try = descriptor.send(field)
1495
+ break
1496
+ end
1497
+
1498
+ }
1499
+ try ||= if !tag_value.nil?
1500
+ tag_value
1501
+ else
1502
+ kitten_cloud_id
1503
+ end
1504
+ try
1237
1505
  end
1506
+ else
1507
+ name
1238
1508
  end
1239
- end
1240
- cfg = {
1241
- "name" => name,
1242
- "cloud" => cloud,
1243
- "region" => r,
1244
- "credentials" => creds
1245
- }
1246
- # If we can at least find the config from the deploy this will
1247
- # belong with, use that, even if it's an ungroomed resource.
1248
- if !calling_deploy.nil? and
1249
- !calling_deploy.original_config.nil? and
1250
- !calling_deploy.original_config[type+"s"].nil?
1251
- calling_deploy.original_config[type+"s"].each { |s|
1252
- if s["name"] == name
1253
- cfg = s.dup
1254
- break
1255
- end
1509
+ if use_name.nil?
1510
+ MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return - #{sprintf("%.2fs", (Time.now-start))}", loglevel, details: caller
1511
+ next
1512
+ end
1513
+ cfg = {
1514
+ "name" => use_name,
1515
+ "cloud" => cloud,
1516
+ "credentials" => creds
1256
1517
  }
1518
+ if !r.nil? and !resourceclass.isGlobal?
1519
+ cfg["region"] = r
1520
+ end
1257
1521
 
1258
- matches << resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
1259
- else
1260
- matches << resourceclass.new(mu_name: name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s)
1522
+ if !p.nil? and resourceclass.canLiveIn.include?(:Habitat)
1523
+ cfg["project"] = p
1524
+ end
1525
+ # If we can at least find the config from the deploy this will
1526
+ # belong with, use that, even if it's an ungroomed resource.
1527
+ if !calling_deploy.nil? and
1528
+ !calling_deploy.original_config.nil? and
1529
+ !calling_deploy.original_config[type+"s"].nil?
1530
+ calling_deploy.original_config[type+"s"].each { |s|
1531
+ if s["name"] == use_name
1532
+ cfg = s.dup
1533
+ break
1534
+ end
1535
+ }
1536
+
1537
+ newkitten = resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
1538
+ desc_semaphore.synchronize {
1539
+ matches << newkitten
1540
+ }
1541
+ else
1542
+ if !@@dummy_cache[cfg_plural] or !@@dummy_cache[cfg_plural][cfg.to_s]
1543
+ MU.log "findStray: Generating dummy '#{resourceclass.to_s}' cloudobj with name: #{use_name}, cloud_id: #{kitten_cloud_id.to_s} - #{sprintf("%.2fs", (Time.now-start))}", loglevel, details: cfg
1544
+ resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
1545
+ desc_semaphore.synchronize {
1546
+ @@dummy_cache[cfg_plural] ||= {}
1547
+ @@dummy_cache[cfg_plural][cfg.to_s] = resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
1548
+ MU.log "findStray: Finished generating dummy '#{resourceclass.to_s}' cloudobj - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1549
+ }
1550
+ end
1551
+ desc_semaphore.synchronize {
1552
+ matches << @@dummy_cache[cfg_plural][cfg.to_s]
1553
+ }
1554
+ end
1261
1555
  end
1262
- end
1556
+ }
1557
+ } }
1558
+ MU.log "findStray: tying up #{region_threads.size.to_s} region threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1559
+ region_threads.each { |t|
1560
+ t.join
1263
1561
  }
1562
+ } }
1563
+ MU.log "findStray: tying up #{habitat_threads.size.to_s} habitat threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1564
+ habitat_threads.each { |t|
1565
+ t.join
1264
1566
  }
1265
1567
  end
1266
1568
  }
1267
1569
  rescue Exception => e
1268
1570
  MU.log e.inspect, MU::ERR, details: e.backtrace
1269
1571
  end
1572
+ MU.log "findStray: returning #{matches.size.to_s} matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
1573
+
1270
1574
  matches
1271
1575
  end
1272
1576
 
@@ -1278,19 +1582,47 @@ module MU
1278
1582
  # @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
1279
1583
  # @param return_all [Boolean]: Return a Hash of matching objects indexed by their mu_name, instead of a single match. Only valid for resource types where has_multiples is true.
1280
1584
  # @return [MU::Cloud]
1281
- def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil)
1585
+ def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, debug: false, indent: "")
1282
1586
  shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1283
1587
  type = cfg_plural
1284
1588
  has_multiples = attrs[:has_multiples]
1285
1589
 
1590
+ loglevel = debug ? MU::NOTICE : MU::DEBUG
1591
+
1592
+ argstring = [:type, :name, :mu_name, :cloud_id, :created_only, :credentials, :habitat, :has_multiples].reject { |a|
1593
+ binding.local_variable_get(a).nil?
1594
+ }.map { |v|
1595
+ v.to_s+": "+binding.local_variable_get(v).to_s
1596
+ }.join(", ")
1597
+
1598
+ # Fun times: if we specified a habitat, which we may also have done by
1599
+ # its shorthand sibling name, let's... call ourselves first to make sure
1600
+ # we're fishing for the right thing.
1601
+ if habitat
1602
+ MU.log indent+"findLitterMate(#{argstring}): Attempting to resolve habitat name #{habitat}", loglevel
1603
+ realhabitat = findLitterMate(type: "habitat", name: habitat, debug: debug, credentials: credentials, indent: indent+" ")
1604
+ if realhabitat and realhabitat.mu_name
1605
+ MU.log indent+"findLitterMate: Resolved habitat name #{habitat} to #{realhabitat.mu_name}", loglevel, details: [realhabitat.mu_name, realhabitat.cloud_id, realhabitat.config.keys]
1606
+ habitat = realhabitat.cloud_id
1607
+ elsif debug
1608
+ MU.log indent+"findLitterMate(#{argstring}): Failed to resolve habitat name #{habitat}", MU::WARN
1609
+ end
1610
+ end
1611
+
1612
+
1286
1613
  @kitten_semaphore.synchronize {
1287
1614
  if !@kittens.has_key?(type)
1615
+ if debug
1616
+ MU.log indent+"NO SUCH KEY #{type} findLitterMate(#{argstring})", MU::WARN, details: @kittens.keys
1617
+ end
1288
1618
  return nil
1289
1619
  end
1290
- MU.log "findLitterMate(type: #{type}, name: #{name}, mu_name: #{mu_name}, cloud_id: #{cloud_id}, created_only: #{created_only}, credentials: #{credentials}). has_multiples is #{attrs[:has_multiples].to_s}. Caller: #{caller[2]}", MU::DEBUG, details: @kittens.keys.map { |k| k.to_s+": "+@kittens[k].keys.join(", ") }
1620
+ MU.log indent+"START findLitterMate(#{argstring}), caller: #{caller[2]}", loglevel, details: @kittens[type].keys.map { |hab| hab.to_s+": "+@kittens[type][hab].keys.join(", ") }
1291
1621
  matches = []
1292
1622
 
1293
- @kittens[type].each { |sib_class, data|
1623
+ @kittens[type].each { |habitat_group, sib_classes|
1624
+ next if habitat and habitat_group != habitat
1625
+ sib_classes.each_pair { |sib_class, data|
1294
1626
  virtual_name = nil
1295
1627
 
1296
1628
  if !has_multiples and data and !data.is_a?(Hash) and data.config and data.config.is_a?(Hash) and data.config['virtual_name'] and name == data.config['virtual_name']
@@ -1301,14 +1633,13 @@ module MU
1301
1633
  if has_multiples
1302
1634
  if !name.nil?
1303
1635
  if return_all
1636
+ MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
1304
1637
  return data.dup
1305
1638
  end
1306
1639
  if data.size == 1 and (cloud_id.nil? or data.values.first.cloud_id == cloud_id)
1307
- obj = data.values.first
1308
- return obj
1640
+ return data.values.first
1309
1641
  elsif mu_name.nil? and cloud_id.nil?
1310
- obj = data.values.first
1311
- MU.log "#{@deploy_id}: Found multiple matches in findLitterMate based on #{type}: #{name}, and not enough info to narrow down further. Returning an arbitrary result. Caller: #{caller[2]}", MU::WARN, details: data.keys
1642
+ MU.log indent+"#{@deploy_id}: Found multiple matches in findLitterMate based on #{type}: #{name}, and not enough info to narrow down further. Returning an arbitrary result. Caller: #{caller[2]}", MU::WARN, details: data.keys
1312
1643
  return data.values.first
1313
1644
  end
1314
1645
  end
@@ -1318,20 +1649,35 @@ module MU
1318
1649
  (!credentials.nil? and credentials == obj.credentials)
1319
1650
  if !created_only or !obj.cloud_id.nil?
1320
1651
  if return_all
1652
+ MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
1321
1653
  return data.dup
1322
1654
  else
1655
+ MU.log indent+"MULTI-MATCH findLitterMate(#{argstring})", loglevel, details: data.keys
1323
1656
  return obj
1324
1657
  end
1325
1658
  end
1326
1659
  end
1327
1660
  }
1328
1661
  else
1329
- if (name.nil? or sib_class == name or virtual_name == name) and
1330
- (cloud_id.nil? or cloud_id == data.cloud_id) and
1331
- (credentials.nil? or data.credentials.nil? or credentials == data.credentials)
1332
- matches << data if !created_only or !data.cloud_id.nil?
1662
+
1663
+ MU.log indent+"CHECKING AGAINST findLitterMate #{habitat_group}/#{type}/#{sib_class} data.cloud_id: #{data.cloud_id}, data.credentials: #{data.credentials}, sib_class: #{sib_class}, virtual_name: #{virtual_name}", loglevel, details: argstring
1664
+
1665
+ data_cloud_id = data.cloud_id.nil? ? nil : data.cloud_id.to_s
1666
+
1667
+ MU.log indent+"(name.nil? or sib_class == name or virtual_name == name)", loglevel, details: (name.nil? or sib_class == name or virtual_name == name).to_s
1668
+ MU.log indent+"(cloud_id.nil? or cloud_id[#{cloud_id.class.name}:#{cloud_id.to_s}] == data_cloud_id[#{data_cloud_id.class.name}:#{data_cloud_id}])", loglevel, details: (cloud_id.nil? or cloud_id == data_cloud_id).to_s
1669
+ MU.log indent+"(credentials.nil? or data.credentials.nil? or credentials[#{credentials.class.name}:#{credentials}] == data.credentials[#{data.credentials.class.name}:#{data.credentials}])", loglevel, details: (credentials.nil? or data.credentials.nil? or credentials == data.credentials).to_s
1670
+
1671
+ if (name.nil? or sib_class == name.to_s or virtual_name == name.to_s) and
1672
+ (cloud_id.nil? or cloud_id.to_s == data_cloud_id) and
1673
+ (credentials.nil? or data.credentials.nil? or credentials.to_s == data.credentials.to_s)
1674
+ if !created_only or !data_cloud_id.nil?
1675
+ MU.log indent+"SINGLE MATCH findLitterMate(#{argstring})", loglevel, details: [data.mu_name, data_cloud_id, data.config.keys]
1676
+ matches << data
1677
+ end
1333
1678
  end
1334
1679
  end
1680
+ }
1335
1681
  }
1336
1682
 
1337
1683
  return matches.first if matches.size == 1
@@ -1340,6 +1686,7 @@ module MU
1340
1686
  end
1341
1687
  }
1342
1688
 
1689
+ MU.log indent+"NO MATCH findLitterMate(#{argstring})", loglevel
1343
1690
 
1344
1691
  return nil
1345
1692
  end
@@ -1354,11 +1701,20 @@ module MU
1354
1701
  def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
1355
1702
  return if @no_artifacts
1356
1703
  MU::MommaCat.lock("deployment-notification")
1357
- loadDeploy(true) # make sure we're saving the latest and greatest
1358
- have_deploy = true
1359
- shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
1360
- type = cfg_plural
1361
- has_multiples = attrs[:has_multiples]
1704
+
1705
+ if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
1706
+ loadDeploy(true) # make sure we're saving the latest and greatest
1707
+ end
1708
+
1709
+ _shortclass, _cfg_name, cfg_plural, _classname, attrs = MU::Cloud.getResourceNames(type)
1710
+ has_multiples = false
1711
+
1712
+ # it's not always the case that we're logging data for a legal resource
1713
+ # type, though that's what we're usually for
1714
+ if cfg_plural
1715
+ type = cfg_plural
1716
+ has_multiples = attrs[:has_multiples]
1717
+ end
1362
1718
 
1363
1719
  if mu_name.nil?
1364
1720
  if !data.nil? and !data["mu_name"].nil?
@@ -1373,15 +1729,21 @@ module MU
1373
1729
  end
1374
1730
  end
1375
1731
 
1732
+ @need_deploy_flush = true
1733
+
1376
1734
  if !remove
1377
1735
  if data.nil?
1378
1736
  MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
1379
1737
  MU::MommaCat.unlock("deployment-notification")
1380
1738
  return
1381
1739
  end
1382
- @deployment[type] = {} if @deployment[type].nil?
1740
+ @notify_semaphore.synchronize {
1741
+ @deployment[type] ||= {}
1742
+ }
1383
1743
  if has_multiples
1384
- @deployment[type][key] = {} if @deployment[type][key].nil?
1744
+ @notify_semaphore.synchronize {
1745
+ @deployment[type][key] ||= {}
1746
+ }
1385
1747
  # fix has_multiples classes that weren't tiered correctly
1386
1748
  if @deployment[type][key].is_a?(Hash) and @deployment[type][key].has_key?("mu_name")
1387
1749
  olddata = @deployment[type][key].dup
@@ -1409,23 +1771,26 @@ module MU
1409
1771
  end
1410
1772
 
1411
1773
  if have_deploy
1412
- if has_multiples
1413
- MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
1414
- @deployment[type][key].delete(mu_name)
1415
- if @deployment[type][key].size == 0
1774
+ @notify_semaphore.synchronize {
1775
+ if has_multiples
1776
+ MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
1777
+ @deployment[type][key].delete(mu_name)
1778
+ if @deployment[type][key].size == 0
1779
+ @deployment[type].delete(key)
1780
+ end
1781
+ else
1782
+ MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1416
1783
  @deployment[type].delete(key)
1417
1784
  end
1418
- else
1419
- MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
1420
- @deployment[type].delete(key)
1421
- end
1422
- if @deployment[type].size == 0
1423
- @deployment.delete(type)
1424
- end
1785
+ if @deployment[type].size == 0
1786
+ @deployment.delete(type)
1787
+ end
1788
+ }
1425
1789
  end
1426
1790
  save! if !delayed_save
1427
1791
 
1428
1792
  end
1793
+
1429
1794
  MU::MommaCat.unlock("deployment-notification")
1430
1795
  end
1431
1796
 
@@ -1475,51 +1840,27 @@ module MU
1475
1840
  end
1476
1841
  end
1477
1842
 
1478
- # XXX this belongs in MU::Cloud::AWS
1479
- # Tag a resource with all of our standard identifying tags.
1480
- #
1481
- # @param resource [String]: The cloud provider identifier of the resource to tag
1482
- # @param region [String]: The cloud provider region
1483
- # @return [void]
1484
- def self.createStandardTags(resource = nil, region: MU.curRegion, credentials: nil)
1485
- tags = []
1486
- listStandardTags.each_pair { |name, value|
1487
- if !value.nil?
1488
- tags << {key: name, value: value}
1489
- end
1490
- }
1491
- if MU::Cloud::CloudFormation.emitCloudFormation
1492
- return tags
1493
- end
1494
-
1495
- attempts = 0
1496
- begin
1497
- MU::Cloud::AWS.ec2(region: region, credentials: credentials).create_tags(
1498
- resources: [resource],
1499
- tags: tags
1500
- )
1501
- rescue Aws::EC2::Errors::ServiceError => e
1502
- MU.log "Got #{e.inspect} tagging #{resource} in #{region}, will retry", MU::WARN, details: caller.concat(tags) if attempts > 1
1503
- if attempts < 5
1504
- attempts = attempts + 1
1505
- sleep 15
1506
- retry
1507
- else
1508
- raise e
1509
- end
1510
- end
1511
- MU.log "Created standard tags for resource #{resource}", MU::DEBUG, details: caller
1512
- end
1513
-
1514
1843
  # List the name/value pairs for our mandatory standard set of resource tags, which
1515
1844
  # should be applied to all taggable cloud provider resources.
1516
1845
  # @return [Hash<String,String>]
1517
1846
  def self.listStandardTags
1518
- return {
1519
- "MU-ID" => MU.deploy_id,
1520
- "MU-APP" => MU.appname,
1521
- "MU-ENV" => MU.environment,
1522
- "MU-MASTER-IP" => MU.mu_public_ip
1847
+ return {} if !MU.deploy_id
1848
+ {
1849
+ "MU-ID" => MU.deploy_id,
1850
+ "MU-APP" => MU.appname,
1851
+ "MU-ENV" => MU.environment,
1852
+ "MU-MASTER-IP" => MU.mu_public_ip
1853
+ }
1854
+ end
1855
+ # List the name/value pairs for our mandatory standard set of resource tags
1856
+ # for this deploy.
1857
+ # @return [Hash<String,String>]
1858
+ def listStandardTags
1859
+ {
1860
+ "MU-ID" => @deploy_id,
1861
+ "MU-APP" => @appname,
1862
+ "MU-ENV" => @environment,
1863
+ "MU-MASTER-IP" => MU.mu_public_ip
1523
1864
  }
1524
1865
  end
1525
1866
 
@@ -1541,7 +1882,7 @@ module MU
1541
1882
  sshdir = "#{@myhome}/.ssh"
1542
1883
  sshconf = "#{sshdir}/config"
1543
1884
 
1544
- if File.exists?(sshconf) and File.open(sshconf).read.match(/ #{node} /)
1885
+ if File.exist?(sshconf) and File.open(sshconf).read.match(/ #{node} /)
1545
1886
  MU.log "Expunging old #{node} entry from #{sshconf}", MU::DEBUG
1546
1887
  if !@noop
1547
1888
  File.open(sshconf, File::CREAT|File::RDWR, 0600) { |f|
@@ -1572,8 +1913,7 @@ module MU
1572
1913
  # @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
1573
1914
  # @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
1574
1915
  def self.nameKitten(server, sync_wait: false)
1575
- node, config, deploydata = server.describe
1576
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_addr, ssh_user, ssh_key_name = server.getSSHConfig
1916
+ node, config, _deploydata = server.describe
1577
1917
 
1578
1918
  mu_zone = nil
1579
1919
  # XXX GCP!
@@ -1659,9 +1999,10 @@ module MU
1659
1999
  MU.log "Called addHostToSSHConfig without a MU::Cloud::Server object", MU::ERR, details: caller
1660
2000
  return nil
1661
2001
  end
1662
- begin
1663
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = server.getSSHConfig
1664
- rescue MU::MuError => e
2002
+
2003
+ _nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = begin
2004
+ server.getSSHConfig
2005
+ rescue MU::MuError
1665
2006
  return
1666
2007
  end
1667
2008
 
@@ -1680,7 +2021,7 @@ module MU
1680
2021
 
1681
2022
  @ssh_semaphore.synchronize {
1682
2023
 
1683
- if File.exists?(ssh_conf)
2024
+ if File.exist?(ssh_conf)
1684
2025
  File.readlines(ssh_conf).each { |line|
1685
2026
  if line.match(/^Host #{server.mu_name} /)
1686
2027
  MU.log("Attempt to add duplicate #{ssh_conf} entry for #{server.mu_name}", MU::WARN)
@@ -1753,7 +2094,6 @@ module MU
1753
2094
  # @param system_name [String]: The node's local system name
1754
2095
  # @return [void]
1755
2096
  def self.addInstanceToEtcHosts(public_ip, chef_name = nil, system_name = nil)
1756
- return if !["mu", "root"].include?(MU.mu_user)
1757
2097
 
1758
2098
  # XXX cover ipv6 case
1759
2099
  if public_ip.nil? or !public_ip.match(/^\d+\.\d+\.\d+\.\d+$/) or (chef_name.nil? and system_name.nil?)
@@ -1762,6 +2102,19 @@ module MU
1762
2102
  if chef_name == "localhost" or system_name == "localhost"
1763
2103
  raise MuError, "Can't set localhost as a name in addInstanceToEtcHosts"
1764
2104
  end
2105
+
2106
+ if !["mu", "root"].include?(MU.mu_user)
2107
+ response = nil
2108
+ begin
2109
+ response = open("https://127.0.0.1:#{MU.mommaCatPort.to_s}/rest/hosts_add/#{chef_name}/#{public_ip}").read
2110
+ rescue Errno::ECONNRESET, Errno::ECONNREFUSED
2111
+ end
2112
+ if response != "ok"
2113
+ MU.log "Error adding #{public_ip} to /etc/hosts via MommaCat request", MU::ERR
2114
+ end
2115
+ return
2116
+ end
2117
+
1765
2118
  File.readlines("/etc/hosts").each { |line|
1766
2119
  if line.match(/^#{public_ip} /) or (chef_name != nil and line.match(/ #{chef_name}(\s|$)/)) or (system_name != nil and line.match(/ #{system_name}(\s|$)/))
1767
2120
  MU.log "Ignoring attempt to add duplicate /etc/hosts entry: #{public_ip} #{chef_name} #{system_name}", MU::DEBUG
@@ -1776,13 +2129,32 @@ module MU
1776
2129
  MU.log("Added to /etc/hosts: #{public_ip} #{chef_name} #{system_name}")
1777
2130
  end
1778
2131
 
1779
- # Send a notification to a deployment's administrators.
2132
+
2133
+ # Send a Slack notification to a deployment's administrators.
2134
+ # @param subject [String]: The subject line of the message.
2135
+ # @param msg [String]: The message body.
2136
+ # @return [void]
2137
+ def sendAdminSlack(subject, msg: "")
2138
+ if $MU_CFG['slack'] and $MU_CFG['slack']['webhook'] and
2139
+ (!$MU_CFG['slack']['skip_environments'] or !$MU_CFG['slack']['skip_environments'].any?{ |s| s.casecmp(MU.environment)==0 })
2140
+ require 'slack-notifier'
2141
+ slack = Slack::Notifier.new $MU_CFG['slack']['webhook']
2142
+
2143
+ if msg and !msg.empty?
2144
+ slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}:\n\n```#{msg}\n```", channel: $MU_CFG['slack']['channel']
2145
+ else
2146
+ slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}", channel: $MU_CFG['slack']['channel']
2147
+ end
2148
+ end
2149
+ end
2150
+
2151
+ # Send an email notification to a deployment's administrators.
1780
2152
  # @param subject [String]: The subject line of the message.
1781
2153
  # @param msg [String]: The message body.
1782
2154
  # @param data [Array]: Supplemental data to add to the message body.
1783
2155
  # @param debug [Boolean]: If set, will include the full deployment structure and original {MU::Config}-parsed configuration.
1784
2156
  # @return [void]
1785
- def sendAdminMail(subject, msg: msg = "", kitten: nil, data: nil, debug: debug = false)
2157
+ def sendAdminMail(subject, msg: "", kitten: nil, data: nil, debug: false)
1786
2158
  require 'net/smtp'
1787
2159
  if @deployment.nil?
1788
2160
  MU.log "Can't send admin mail without a loaded deployment", MU::ERR
@@ -1890,11 +2262,11 @@ MESSAGE_END
1890
2262
  MU.dupGlobals(parent_thread_id)
1891
2263
  realhome = Etc.getpwnam("nagios").dir
1892
2264
  [@nagios_home, "#{@nagios_home}/.ssh"].each { |dir|
1893
- Dir.mkdir(dir, 0711) if !Dir.exists?(dir)
2265
+ Dir.mkdir(dir, 0711) if !Dir.exist?(dir)
1894
2266
  File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, dir)
1895
2267
  }
1896
- if realhome != @nagios_home and Dir.exists?(realhome) and !File.symlink?("#{realhome}/.ssh")
1897
- File.rename("#{realhome}/.ssh", "#{realhome}/.ssh.#{$$}") if Dir.exists?("#{realhome}/.ssh")
2268
+ if realhome != @nagios_home and Dir.exist?(realhome) and !File.symlink?("#{realhome}/.ssh")
2269
+ File.rename("#{realhome}/.ssh", "#{realhome}/.ssh.#{$$}") if Dir.exist?("#{realhome}/.ssh")
1898
2270
  File.symlink("#{@nagios_home}/.ssh", Etc.getpwnam("nagios").dir+"/.ssh")
1899
2271
  end
1900
2272
  MU.log "Updating #{@nagios_home}/.ssh/config..."
@@ -1910,12 +2282,6 @@ MESSAGE_END
1910
2282
  FileUtils.cp("#{@myhome}/.ssh/id_rsa", "#{@nagios_home}/.ssh/id_rsa")
1911
2283
  File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/id_rsa")
1912
2284
  threads = []
1913
- if !MU::Cloud::AWS.isGovCloud?
1914
- mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
1915
- end
1916
- # XXX what if we're in GCP?
1917
- # XXX need a MU::Cloud::DNSZone.lookup for bulk lookups
1918
- # XXX also grab things like mu_windows_name out of deploy data if we can
1919
2285
 
1920
2286
  parent_thread_id = Thread.current.object_id
1921
2287
  MU::MommaCat.listDeploys.sort.each { |deploy_id|
@@ -1929,19 +2295,21 @@ MESSAGE_END
1929
2295
  FileUtils.cp("#{@myhome}/.ssh/#{deploy.ssh_key_name}", "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
1930
2296
  File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
1931
2297
  if deploy.kittens.has_key?("servers")
1932
- deploy.kittens["servers"].each_pair { |nodeclass, nodes|
1933
- nodes.each_pair { |mu_name, server|
1934
- MU.dupGlobals(parent_thread_id)
1935
- threads << Thread.new {
1936
- MU::MommaCat.setThreadContext(deploy)
1937
- MU.log "Adding #{server.mu_name} to #{@nagios_home}/.ssh/config", MU::DEBUG
1938
- MU::MommaCat.addHostToSSHConfig(
1939
- server,
1940
- ssh_dir: "#{@nagios_home}/.ssh",
1941
- ssh_conf: "#{@nagios_home}/.ssh/config.tmp",
1942
- ssh_owner: "nagios"
1943
- )
1944
- MU.purgeGlobals
2298
+ deploy.kittens["servers"].values.each { |nodeclasses|
2299
+ nodeclasses.values.each { |nodes|
2300
+ nodes.values.each { |server|
2301
+ MU.dupGlobals(parent_thread_id)
2302
+ threads << Thread.new {
2303
+ MU::MommaCat.setThreadContext(deploy)
2304
+ MU.log "Adding #{server.mu_name} to #{@nagios_home}/.ssh/config", MU::DEBUG
2305
+ MU::MommaCat.addHostToSSHConfig(
2306
+ server,
2307
+ ssh_dir: "#{@nagios_home}/.ssh",
2308
+ ssh_conf: "#{@nagios_home}/.ssh/config.tmp",
2309
+ ssh_owner: "nagios"
2310
+ )
2311
+ MU.purgeGlobals
2312
+ }
1945
2313
  }
1946
2314
  }
1947
2315
  }
@@ -1983,10 +2351,10 @@ MESSAGE_END
1983
2351
  # Return a list of all currently active deploy identifiers.
1984
2352
  # @return [Array<String>]
1985
2353
  def self.listDeploys
1986
- return [] if !Dir.exists?("#{MU.dataDir}/deployments")
2354
+ return [] if !Dir.exist?("#{MU.dataDir}/deployments")
1987
2355
  deploys = []
1988
2356
  Dir.entries("#{MU.dataDir}/deployments").reverse_each { |muid|
1989
- next if !Dir.exists?("#{MU.dataDir}/deployments/#{muid}") or muid == "." or muid == ".."
2357
+ next if !Dir.exist?("#{MU.dataDir}/deployments/#{muid}") or muid == "." or muid == ".."
1990
2358
  deploys << muid
1991
2359
  }
1992
2360
  return deploys
@@ -1999,7 +2367,7 @@ MESSAGE_END
1999
2367
  nodes = Hash.new
2000
2368
  MU::MommaCat.deploy_struct_semaphore.synchronize {
2001
2369
  MU::MommaCat.listDeploys.each { |deploy|
2002
- if !Dir.exists?(MU::MommaCat.deploy_dir(deploy)) or
2370
+ if !Dir.exist?(MU::MommaCat.deploy_dir(deploy)) or
2003
2371
  !File.size?("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json")
2004
2372
  MU.log "Didn't see deployment metadata for '#{deploy}'", MU::WARN
2005
2373
  next
@@ -2018,7 +2386,7 @@ MESSAGE_END
2018
2386
  }
2019
2387
  end
2020
2388
  rescue JSON::ParserError => e
2021
- MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR
2389
+ MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR, details: e.message
2022
2390
  end
2023
2391
  data.flock(File::LOCK_UN)
2024
2392
  data.close
@@ -2084,82 +2452,25 @@ MESSAGE_END
2084
2452
  end
2085
2453
 
2086
2454
  # Given a Certificate Signing Request, sign it with our internal CA and
2087
- # writers the resulting signed certificate. Only works on local files.
2455
+ # write the resulting signed certificate. Only works on local files.
2088
2456
  # @param csr_path [String]: The CSR to sign, as a file.
2089
2457
  def signSSLCert(csr_path, sans = [])
2090
- # XXX more sanity here, this feels unsafe
2091
- certdir = File.dirname(csr_path)
2092
- certname = File.basename(csr_path, ".csr")
2093
- if File.exists?("#{certdir}/#{certname}.crt")
2094
- MU.log "Not re-signing SSL certificate request #{csr_path}, #{certdir}/#{certname}.crt already exists", MU::WARN
2095
- return
2096
- end
2097
- MU.log "Signing SSL certificate request #{csr_path} with #{MU.mySSLDir}/Mu_CA.pem"
2098
-
2099
- begin
2100
- csr = OpenSSL::X509::Request.new File.read csr_path
2101
- rescue Exception => e
2102
- MU.log e.message, MU::ERR, details: File.read(csr_path)
2103
- raise e
2104
- end
2105
- key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key"
2106
-
2107
- # Load up the Mu Certificate Authority
2108
- cakey = OpenSSL::PKey::RSA.new File.read "#{MU.mySSLDir}/Mu_CA.key"
2109
- cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
2110
- cur_serial = 0
2111
- File.open("#{MU.mySSLDir}/serial", File::CREAT|File::RDWR, 0600) { |f|
2112
- f.flock(File::LOCK_EX)
2113
- cur_serial = f.read.chomp!.to_i
2114
- cur_serial = cur_serial + 1
2115
- f.rewind
2116
- f.truncate(0)
2117
- f.puts cur_serial
2118
- f.flush
2119
- f.flock(File::LOCK_UN)
2120
- }
2121
-
2122
- # Create a certificate from our CSR, signed by the Mu CA
2123
- cert = OpenSSL::X509::Certificate.new
2124
- cert.serial = cur_serial
2125
- cert.version = 3
2126
- cert.not_before = Time.now
2127
- cert.not_after = Time.now + 180000000
2128
- cert.subject = csr.subject
2129
- cert.public_key = csr.public_key
2130
- cert.issuer = cacert.subject
2131
- if !sans.nil? and sans.size > 0
2132
- MU.log "Incorporting Subject Alternative Names: #{sans.join(",")}"
2133
- ef = OpenSSL::X509::ExtensionFactory.new
2134
- ef.issuer_certificate = cacert
2135
- #v3_req_client
2136
- ef.subject_certificate = cert
2137
- ef.subject_request = csr
2138
- cert.add_extension(ef.create_extension("keyUsage","nonRepudiation,digitalSignature,keyEncipherment", false))
2139
- cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false))
2140
- # XXX only do this if we see the otherName thinger in the san list
2141
- cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth,serverAuth,codeSigning,emailProtection",false))
2142
- end
2143
- cert.sign cakey, OpenSSL::Digest::SHA256.new
2144
-
2145
- open("#{certdir}/#{certname}.crt", 'w', 0644) { |io|
2146
- io.write cert.to_pem
2147
- }
2148
- if MU.mu_user != "mu"
2149
- owner_uid = Etc.getpwnam(MU.mu_user).uid
2150
- File.chown(owner_uid, nil, "#{certdir}/#{certname}.crt")
2151
- end
2152
-
2458
+ MU::Master::SSL.sign(csr_path, sans, for_user: MU.mu_user)
2153
2459
  end
2154
2460
 
2155
2461
  # Make sure deployment data is synchronized to/from each node in the
2156
2462
  # currently-loaded deployment.
2157
- def syncLitter(nodeclasses = [], triggering_node: nil, save_all_only: false)
2158
- # XXX take some config logic to decide what nodeclasses to hit
2159
- # XXX don't run on triggering node, duh
2463
+ def syncLitter(nodeclasses = [], triggering_node: nil, save_only: false)
2464
+ # XXX take some config logic to decide what nodeclasses to hit? like, make
2465
+ # inferences from dependencies or something?
2466
+
2160
2467
  return if MU.syncLitterThread
2161
- return if !Dir.exists?(deploy_dir)
2468
+ return if !Dir.exist?(deploy_dir)
2162
2469
  svrs = MU::Cloud.resource_types[:Server][:cfg_plural] # legibility shorthand
2470
+ if !triggering_node.nil? and nodeclasses.size > 0
2471
+ nodeclasses.reject! { |n| n == triggering_node.to_s }
2472
+ return if nodeclasses.size == 0
2473
+ end
2163
2474
 
2164
2475
  @kitten_semaphore.synchronize {
2165
2476
  if @kittens.nil? or
@@ -2168,14 +2479,22 @@ MESSAGE_END
2168
2479
  return
2169
2480
  end
2170
2481
 
2171
- MU.log "Updating these siblings in #{@deploy_id}: #{nodeclasses.join(', ')}", MU::DEBUG, details: @kittens[svrs].map { |nodeclass, instance| instance.keys }
2482
+
2483
+ MU.log "Updating these node classes in #{@deploy_id}", MU::DEBUG, details: nodeclasses
2172
2484
  }
2173
2485
 
2174
2486
  update_servers = []
2175
2487
  if nodeclasses.nil? or nodeclasses.size == 0
2176
2488
  litter = findLitterMate(type: "server", return_all: true)
2489
+ return if litter.nil?
2177
2490
  litter.each_pair { |mu_name, node|
2178
- next if !triggering_node.nil? and mu_name == triggering_node.mu_name
2491
+ if !triggering_node.nil? and (
2492
+ (triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
2493
+ (triggering_node.is_a?(String) and mu_name == triggering_node)
2494
+ )
2495
+ next
2496
+ end
2497
+
2179
2498
  if !node.groomer.nil?
2180
2499
  update_servers << node
2181
2500
  end
@@ -2187,10 +2506,16 @@ MESSAGE_END
2187
2506
  litter.merge!(mates) if mates
2188
2507
  }
2189
2508
  litter.each_pair { |mu_name, node|
2190
- next if !triggering_node.nil? and mu_name == triggering_node.mu_name
2509
+ if !triggering_node.nil? and (
2510
+ (triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
2511
+ (triggering_node.is_a?(String) and mu_name == triggering_node)
2512
+ )
2513
+ next
2514
+ end
2515
+
2191
2516
  if !node.deploydata or !node.deploydata.keys.include?('nodename')
2192
2517
  details = node.deploydata ? node.deploydata.keys : nil
2193
- MU.log "#{mu_name} deploy data is missing (possibly retired), not syncing it", MU::WARN, details: details
2518
+ MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::WARN, details: details
2194
2519
  else
2195
2520
  update_servers << node
2196
2521
  end
@@ -2198,6 +2523,8 @@ MESSAGE_END
2198
2523
  end
2199
2524
  return if update_servers.size == 0
2200
2525
 
2526
+ MU.log "Updating these nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
2527
+
2201
2528
  update_servers.each { |node|
2202
2529
  # Not clear where this pollution comes from, but let's stick a temp
2203
2530
  # fix in here.
@@ -2210,7 +2537,7 @@ MESSAGE_END
2210
2537
  }
2211
2538
 
2212
2539
  # Merge everyone's deploydata together
2213
- if !save_all_only
2540
+ if !save_only
2214
2541
  skip = []
2215
2542
  update_servers.each { |node|
2216
2543
  if node.mu_name.nil? or node.deploydata.nil? or node.config.nil?
@@ -2239,7 +2566,7 @@ MESSAGE_END
2239
2566
  begin
2240
2567
  if sibling.config['groom'].nil? or sibling.config['groom']
2241
2568
  sibling.groomer.saveDeployData
2242
- sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_all_only
2569
+ sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
2243
2570
  end
2244
2571
  rescue MU::Groomer::RunError => e
2245
2572
  MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
@@ -2263,125 +2590,45 @@ MESSAGE_END
2263
2590
  # @param poolname [Boolean]: If true, generate certificates for the base name of the server pool of which this node is a member, rather than for the individual node
2264
2591
  # @param keysize [Integer]: The size of the private key to use when generating this certificate
2265
2592
  def nodeSSLCerts(resource, poolname = false, keysize = 4096)
2266
- nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = resource.getSSHConfig if resource.respond_to?(:getSSHConfig)
2593
+ _nat_ssh_key, _nat_ssh_user, _nat_ssh_host, canonical_ip, _ssh_user, _ssh_key_name = resource.getSSHConfig if resource.respond_to?(:getSSHConfig)
2267
2594
 
2268
2595
  deploy_id = resource.deploy_id || @deploy_id || resource.deploy.deploy_id
2269
2596
 
2270
2597
  cert_cn = poolname ? deploy_id + "-" + resource.config['name'].upcase : resource.mu_name
2271
2598
 
2272
- certs = {}
2273
2599
  results = {}
2274
2600
 
2275
- @node_cert_semaphore.synchronize {
2276
- if File.exists?("#{MU.mySSLDir}/#{cert_cn}.crt") and
2277
- File.exists?("#{MU.mySSLDir}/#{cert_cn}.key")
2278
- ext_cert = OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt"))
2279
- if ext_cert.not_after < Time.now
2280
- MU.log "Node certificate for #{cert_cn} is expired, regenerating", MU::WARN
2281
- ["crt", "key", "csr"].each { |suffix|
2282
- if File.exists?("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
2283
- File.unlink("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
2284
- end
2285
- }
2286
- else
2287
- results[cert_cn] = [
2288
- OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt")),
2289
- OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{cert_cn}.key"))
2290
- ]
2291
- end
2292
- end
2601
+ is_windows = (resource.respond_to?(:windows?) and resource.windows?)
2293
2602
 
2294
- if results.size == 0
2295
- certs[cert_cn] = {
2296
- # "sans" => ["IP:#{canonical_ip}"],
2297
- "cn" => cert_cn
2298
- }
2299
- if canonical_ip
2300
- certs[cert_cn]["sans"] = ["IP:#{canonical_ip}"]
2603
+ @node_cert_semaphore.synchronize {
2604
+ MU::Master::SSL.bootstrap
2605
+ sans = []
2606
+ sans << canonical_ip if canonical_ip
2607
+ # XXX were there other names we wanted to include?
2608
+ key = MU::Master::SSL.getKey(cert_cn, keysize: keysize)
2609
+ cert, pfx_cert = MU::Master::SSL.getCert(cert_cn, "/CN=#{cert_cn}/O=Mu/C=US", sans: sans, pfx: is_windows)
2610
+ results[cert_cn] = [key, cert]
2611
+
2612
+ winrm_cert = nil
2613
+ if is_windows
2614
+ winrm_key = MU::Master::SSL.getKey(cert_cn+"-winrm", keysize: keysize)
2615
+ winrm_cert = MU::Master::SSL.getCert(cert_cn+"-winrm", "/CN=#{resource.config['windows_admin_username']}/O=Mu/C=US", sans: ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{resource.config['windows_admin_username']}@localhost"], pfx: true)[0]
2616
+ results[cert_cn+"-winrm"] = [winrm_key, winrm_cert]
2617
+ end
2618
+
2619
+ if resource and resource.config and resource.config['cloud']
2620
+ cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource.config['cloud'])
2621
+
2622
+ cloudclass.writeDeploySecret(@deploy_id, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
2623
+ cloudclass.writeDeploySecret(@deploy_id, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])
2624
+ if pfx_cert
2625
+ cloudclass.writeDeploySecret(@deploy_id, pfx_cert.to_der, cert_cn+".pfx", credentials: resource.config['credentials'])
2301
2626
  end
2302
- end
2303
-
2304
- if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
2305
- if File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.crt") and
2306
- File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.key")
2307
- results[cert_cn+"-winrm"] = [File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.crt"), File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.key")]
2308
- else
2309
- certs[cert_cn+"-winrm"] = {
2310
- "sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{resource.config['windows_admin_username']}@localhost"],
2311
- "cn" => resource.config['windows_admin_username']
2312
- }
2627
+ if winrm_cert
2628
+ cloudclass.writeDeploySecret(@deploy_id, winrm_cert.to_pem, cert_cn+"-winrm.crt", credentials: resource.config['credentials'])
2313
2629
  end
2314
2630
  end
2315
2631
 
2316
- certs.each { |certname, data|
2317
- MU.log "Generating SSL certificate #{certname} for #{resource} with key size #{keysize.to_s}"
2318
-
2319
- # Create and save a key
2320
- key = OpenSSL::PKey::RSA.new keysize
2321
- if !Dir.exist?(MU.mySSLDir)
2322
- Dir.mkdir(MU.mySSLDir, 0700)
2323
- end
2324
-
2325
- open("#{MU.mySSLDir}/#{certname}.key", 'w', 0600) { |io|
2326
- io.write key.to_pem
2327
- }
2328
- # Create a certificate request for this node
2329
- csr = OpenSSL::X509::Request.new
2330
- csr.version = 3
2331
- csr.subject = OpenSSL::X509::Name.parse "/CN=#{data['cn']}/O=Mu/C=US"
2332
- csr.public_key = key.public_key
2333
- csr.sign key, OpenSSL::Digest::SHA256.new
2334
- open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io|
2335
- io.write csr.to_pem
2336
- }
2337
- if MU.chef_user == "mu"
2338
- signSSLCert("#{MU.mySSLDir}/#{certname}.csr", data['sans'])
2339
- else
2340
- deploykey = OpenSSL::PKey::RSA.new(public_key)
2341
- deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(deploy_secret))
2342
- # XXX things that aren't servers
2343
- res_type = "server"
2344
- res_type = "server_pool" if !resource.config['basis'].nil?
2345
- uri = URI("https://#{MU.mu_public_addr}:2260/")
2346
- req = Net::HTTP::Post.new(uri)
2347
- req.set_form_data(
2348
- "mu_id" => MU.deploy_id,
2349
- "mu_resource_name" => resource.config['name'],
2350
- "mu_resource_type" => res_type,
2351
- "mu_ssl_sign" => "#{MU.mySSLDir}/#{certname}.csr",
2352
- "mu_ssl_sans" => data["sans"].join(","),
2353
- "mu_user" => MU.mu_user,
2354
- "mu_deploy_secret" => deploysecret
2355
- )
2356
- http = Net::HTTP.new(uri.hostname, uri.port)
2357
- http.ca_file = "/etc/pki/Mu_CA.pem" # XXX why no worky?
2358
- http.use_ssl = true
2359
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX this sucks
2360
- response = http.request(req)
2361
- MU.log "Got error back on signing request for #{MU.mySSLDir}/#{certname}.csr", MU::ERR if response.code != "200"
2362
- end
2363
-
2364
- pfx = nil
2365
- cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{certname}.crt"
2366
- if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
2367
- cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
2368
- pfx = OpenSSL::PKCS12.create(nil, nil, key, cert, [cacert], nil, nil, nil, nil)
2369
- open("#{MU.mySSLDir}/#{certname}.pfx", 'w', 0644) { |io|
2370
- io.write pfx.to_der
2371
- }
2372
- end
2373
-
2374
- results[certname] = [cert, key]
2375
-
2376
- if resource.config['cloud'] == "AWS"
2377
- MU::Cloud::AWS.writeDeploySecret(@deploy_id, cert.to_pem, certname+".crt")
2378
- MU::Cloud::AWS.writeDeploySecret(@deploy_id, key.to_pem, certname+".key")
2379
- if pfx
2380
- MU::Cloud::AWS.writeDeploySecret(@deploy_id, pfx.to_der, certname+".pfx")
2381
- end
2382
- # XXX add google logic, or better yet abstract this method
2383
- end
2384
- }
2385
2632
  }
2386
2633
 
2387
2634
  results[cert_cn]
@@ -2392,80 +2639,129 @@ MESSAGE_END
2392
2639
  MU::MommaCat.deploy_dir(@deploy_id)
2393
2640
  end
2394
2641
 
2395
- private
2642
+ # Path to the log file used by the Momma Cat daemon
2643
+ # @return [String]
2644
+ def self.daemonLogFile
2645
+ base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
2646
+ "#{base}/log/mu-momma-cat.log"
2647
+ end
2396
2648
 
2397
- # Check to see whether a given resource name is unique across all
2398
- # deployments on this Mu server. We only enforce this for certain classes
2399
- # of names. If the name in question is available, add it to our cache of
2400
- # said names. See #{MU::MommaCat.getResourceName}
2401
- # @param name [String]: The name to attempt to allocate.
2402
- # @return [Boolean]: True if allocation was successful.
2403
- def allocateUniqueResourceName(name)
2404
- raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
2405
- path = File.expand_path(MU.dataDir+"/deployments")
2406
- File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
2407
- existing = []
2408
- f.flock(File::LOCK_EX)
2409
- f.readlines.each { |line|
2410
- existing << line.chomp
2411
- }
2412
- begin
2413
- existing.each { |used|
2414
- if used.match(/^#{name}:/)
2415
- if !used.match(/^#{name}:#{@deploy_id}$/)
2416
- MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
2417
- return false
2418
- else
2419
- return true
2420
- end
2421
- end
2422
- }
2423
- f.puts name+":"+@deploy_id
2424
- return true
2425
- ensure
2426
- f.flock(File::LOCK_UN)
2427
- end
2428
- }
2649
+ # Path to the PID file used by the Momma Cat daemon
2650
+ # @return [String]
2651
+ def self.daemonPidFile
2652
+ base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
2653
+ "#{base}/run/mommacat.pid"
2429
2654
  end
2430
2655
 
2431
- ###########################################################################
2432
- ###########################################################################
2433
- def self.deploy_dir(deploy_id)
2434
- raise MuError, "deploy_dir must get a deploy_id if called as class method (from #{caller[0]}; #{caller[1]})" if deploy_id.nil?
2435
- # XXX this will blow up if someone sticks MU in /
2436
- path = File.expand_path(MU.dataDir+"/deployments")
2437
- if !Dir.exist?(path)
2438
- MU.log "Creating #{path}", MU::DEBUG
2439
- Dir.mkdir(path, 0700)
2656
+ # Start the Momma Cat daemon and return the exit status of the command used
2657
+ # @return [Integer]
2658
+ def self.start
2659
+ base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
2660
+ [base, "#{base}/log", "#{base}/run"].each { |dir|
2661
+ if !Dir.exist?(dir)
2662
+ MU.log "Creating #{dir}"
2663
+ Dir.mkdir(dir)
2664
+ end
2665
+ }
2666
+ return 0 if status
2667
+
2668
+ MU.log "Starting Momma Cat on port #{MU.mommaCatPort}, logging to #{daemonLogFile}, PID file #{daemonPidFile}"
2669
+ origdir = Dir.getwd
2670
+ Dir.chdir(MU.myRoot+"/modules")
2671
+
2672
+ # XXX what's the safest way to find the 'bundle' executable in both gem and non-gem installs?
2673
+ cmd = %Q{bundle exec thin --threaded --daemonize --port #{MU.mommaCatPort} --pid #{daemonPidFile} --log #{daemonLogFile} --ssl --ssl-key-file #{MU.mySSLDir}/mommacat.key --ssl-cert-file #{MU.mySSLDir}/mommacat.pem --ssl-disable-verify --tag mu-momma-cat -R mommacat.ru start}
2674
+ MU.log cmd, MU::NOTICE
2675
+ output = %x{#{cmd}}
2676
+ Dir.chdir(origdir)
2677
+
2678
+ retries = 0
2679
+ begin
2680
+ sleep 1
2681
+ retries += 1
2682
+ if retries >= 10
2683
+ MU.log "MommaCat failed to start (command was #{cmd})", MU::WARN, details: output
2684
+ pp caller
2685
+ return $?.exitstatus
2686
+ end
2687
+ end while !status
2688
+
2689
+ if $?.exitstatus != 0
2690
+ exit 1
2440
2691
  end
2441
- path = path+"/"+deploy_id
2442
- return path
2692
+
2693
+ return $?.exitstatus
2443
2694
  end
2444
2695
 
2445
- def self.deploy_exists?(deploy_id)
2446
- if deploy_id.nil? or deploy_id.empty?
2447
- MU.log "Got nil deploy_id in MU::MommaCat.deploy_exists?", MU::WARN
2448
- return
2696
+ # Return true if the Momma Cat daemon appears to be running
2697
+ # @return [Boolean]
2698
+ def self.status
2699
+ if File.exist?(daemonPidFile)
2700
+ pid = File.read(daemonPidFile).chomp.to_i
2701
+ begin
2702
+ Process.getpgid(pid)
2703
+ MU.log "Momma Cat running with pid #{pid.to_s}"
2704
+ return true
2705
+ rescue Errno::ESRCH
2706
+ end
2449
2707
  end
2450
- path = File.expand_path(MU.dataDir+"/deployments")
2451
- if !Dir.exists?(path)
2452
- Dir.mkdir(path, 0700)
2708
+ MU.log "Momma Cat daemon not running", MU::NOTICE, details: daemonPidFile
2709
+ false
2710
+ end
2711
+
2712
+ # Stop the Momma Cat daemon, if it's running
2713
+ def self.stop
2714
+ if File.exist?(daemonPidFile)
2715
+ pid = File.read(daemonPidFile).chomp.to_i
2716
+ MU.log "Stopping Momma Cat with pid #{pid.to_s}"
2717
+ Process.kill("INT", pid)
2718
+ killed = false
2719
+ begin
2720
+ Process.getpgid(pid)
2721
+ sleep 1
2722
+ rescue Errno::ESRCH
2723
+ killed = true
2724
+ end while killed
2725
+ MU.log "Momma Cat with pid #{pid.to_s} stopped", MU::DEBUG, details: daemonPidFile
2726
+
2727
+ begin
2728
+ File.unlink(daemonPidFile)
2729
+ rescue Errno::ENOENT
2730
+ end
2453
2731
  end
2454
- deploy_path = File.expand_path(path+"/"+deploy_id)
2455
- return Dir.exist?(deploy_path)
2456
2732
  end
2457
2733
 
2734
+ # (Re)start the Momma Cat daemon and return the exit status of the start command
2735
+ # @return [Integer]
2736
+ def self.restart
2737
+ stop
2738
+ start
2739
+ end
2458
2740
 
2459
- def createDeployKey
2460
- key = OpenSSL::PKey::RSA.generate(4096)
2461
- MU.log "Generated deploy key for #{MU.deploy_id}", MU::DEBUG, details: key.public_key.export
2462
- return [key.export, key.public_key.export]
2741
+ # Locate and return the deploy, if any, which matches the provided origin
2742
+ # description
2743
+ # @param origin [Hash]
2744
+ def self.findMatchingDeploy(origin)
2745
+ MU::MommaCat.listDeploys.each { |deploy_id|
2746
+ o_path = deploy_dir(deploy_id)+"/origin.json"
2747
+ next if !File.exist?(o_path)
2748
+ this_origin = JSON.parse(File.read(o_path))
2749
+ if origin == this_origin
2750
+ MU.log "Deploy #{deploy_id} matches origin hash, loading", details: origin
2751
+ return MU::MommaCat.new(deploy_id)
2752
+ end
2753
+ }
2754
+ nil
2463
2755
  end
2464
2756
 
2465
2757
  # Synchronize all in-memory information related to this to deployment to
2466
2758
  # disk.
2467
- def save!(triggering_node = nil)
2468
- return if @no_artifacts
2759
+ # @param triggering_node [MU::Cloud::Server]: If we're being triggered by the addition/removal/update of a node, this allows us to notify any sibling or dependent nodes of changes
2760
+ # @param force [Boolean]: Save even if +no_artifacts+ is set
2761
+ # @param origin [Hash]: Optional blob of data indicating how this deploy was created
2762
+ def save!(triggering_node = nil, force: false, origin: nil)
2763
+
2764
+ return if @no_artifacts and !force
2469
2765
  MU::MommaCat.deploy_struct_semaphore.synchronize {
2470
2766
  MU.log "Saving deployment #{MU.deploy_id}", MU::DEBUG
2471
2767
 
@@ -2474,6 +2770,12 @@ MESSAGE_END
2474
2770
  Dir.mkdir(deploy_dir, 0700)
2475
2771
  end
2476
2772
 
2773
+ if !origin.nil?
2774
+ o_file = File.new("#{deploy_dir}/origin.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
2775
+ o_file.puts JSON.pretty_generate(origin)
2776
+ o_file.close
2777
+ end
2778
+
2477
2779
  if !@private_key.nil?
2478
2780
  privkey = File.new("#{deploy_dir}/private_key", File::CREAT|File::TRUNC|File::RDWR, 0600)
2479
2781
  privkey.puts @private_key
@@ -2489,6 +2791,11 @@ MESSAGE_END
2489
2791
  if !@deployment.nil? and @deployment.size > 0
2490
2792
  @deployment['handle'] = MU.handle if @deployment['handle'].nil? and !MU.handle.nil?
2491
2793
  @deployment['public_key'] = @public_key
2794
+ @deployment['timestamp'] ||= @timestamp
2795
+ @deployment['seed'] ||= @seed
2796
+ @deployment['appname'] ||= @appname
2797
+ @deployment['handle'] ||= @handle
2798
+ @deployment['ssh_public_key'] ||= @ssh_public_key if @ssh_public_key
2492
2799
  begin
2493
2800
  # XXX doing this to trigger JSON errors before stomping the stored
2494
2801
  # file...
@@ -2498,15 +2805,20 @@ MESSAGE_END
2498
2805
  deploy.flock(File::LOCK_EX)
2499
2806
  deploy.puts JSON.pretty_generate(@deployment, max_nesting: false)
2500
2807
  rescue JSON::NestingError => e
2501
- raise MuError, e.inspect+"\n\n"+@deployment.to_s
2808
+ MU.log e.inspect, MU::ERR, details: @deployment
2809
+ raise MuError, "Got #{e.message} trying to save deployment"
2810
+ rescue Encoding::UndefinedConversionError => e
2811
+ MU.log e.inspect, MU::ERR, details: @deployment
2812
+ raise MuError, "Got #{e.message} at #{e.error_char.dump} (#{e.source_encoding_name} => #{e.destination_encoding_name}) trying to save deployment"
2502
2813
  end
2503
2814
  deploy.flock(File::LOCK_UN)
2504
2815
  deploy.close
2816
+ @need_deploy_flush = false
2505
2817
  end
2506
2818
 
2507
2819
  if !@original_config.nil? and @original_config.is_a?(Hash)
2508
2820
  config = File.new("#{deploy_dir}/basket_of_kittens.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
2509
- config.puts JSON.pretty_generate(@original_config)
2821
+ config.puts JSON.pretty_generate(MU::Config.manxify(@original_config))
2510
2822
  config.close
2511
2823
  end
2512
2824
 
@@ -2541,10 +2853,10 @@ MESSAGE_END
2541
2853
  MU.log "Creating #{secretdir}", MU::DEBUG
2542
2854
  Dir.mkdir(secretdir, 0700)
2543
2855
  end
2544
- @secrets.each_pair { |type, server|
2545
- server.each_pair { |server, secret|
2856
+ @secrets.each_pair { |type, servers|
2857
+ servers.each_pair { |server, svr_secret|
2546
2858
  key = File.new("#{secretdir}/#{type}.#{server}", File::CREAT|File::TRUNC|File::RDWR, 0600)
2547
- key.puts secret
2859
+ key.puts svr_secret
2548
2860
  key.close
2549
2861
  }
2550
2862
  }
@@ -2552,7 +2864,7 @@ MESSAGE_END
2552
2864
  }
2553
2865
 
2554
2866
  # Update groomer copies of this metadata
2555
- syncLitter(@deployment['servers'].keys, save_all_only: true) if @deployment.has_key?("servers")
2867
+ syncLitter(@deployment['servers'].keys, triggering_node: triggering_node, save_only: true) if @deployment.has_key?("servers")
2556
2868
  end
2557
2869
 
2558
2870
  # Find one or more resources by their Mu resource name, and return
@@ -2562,21 +2874,112 @@ MESSAGE_END
2562
2874
  # @param type [String]: The type of resource, e.g. "vpc" or "server."
2563
2875
  # @param name [String]: The Mu resource class, typically the name field of a Basket of Kittens resource declaration.
2564
2876
  # @param mu_name [String]: The fully-expanded Mu resource name, e.g. MGMT-PROD-2015040115-FR-ADMGMT2
2877
+
2878
+ private
2879
+
2880
+ # Check to see whether a given resource name is unique across all
2881
+ # deployments on this Mu server. We only enforce this for certain classes
2882
+ # of names. If the name in question is available, add it to our cache of
2883
+ # said names. See #{MU::MommaCat.getResourceName}
2884
+ # @param name [String]: The name to attempt to allocate.
2885
+ # @return [Boolean]: True if allocation was successful.
2886
+ def allocateUniqueResourceName(name)
2887
+ raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
2888
+ path = File.expand_path(MU.dataDir+"/deployments")
2889
+ File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
2890
+ existing = []
2891
+ f.flock(File::LOCK_EX)
2892
+ f.readlines.each { |line|
2893
+ existing << line.chomp
2894
+ }
2895
+ begin
2896
+ existing.each { |used|
2897
+ if used.match(/^#{name}:/)
2898
+ if !used.match(/^#{name}:#{@deploy_id}$/)
2899
+ MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
2900
+ return false
2901
+ else
2902
+ return true
2903
+ end
2904
+ end
2905
+ }
2906
+ f.puts name+":"+@deploy_id
2907
+ return true
2908
+ ensure
2909
+ f.flock(File::LOCK_UN)
2910
+ end
2911
+ }
2912
+ end
2913
+
2914
+ ###########################################################################
2915
+ ###########################################################################
2916
+ def self.deploy_dir(deploy_id)
2917
+ raise MuError, "deploy_dir must get a deploy_id if called as class method (from #{caller[0]}; #{caller[1]})" if deploy_id.nil?
2918
+ # XXX this will blow up if someone sticks MU in /
2919
+ path = File.expand_path(MU.dataDir+"/deployments")
2920
+ if !Dir.exist?(path)
2921
+ MU.log "Creating #{path}", MU::DEBUG
2922
+ Dir.mkdir(path, 0700)
2923
+ end
2924
+ path = path+"/"+deploy_id
2925
+ return path
2926
+ end
2927
+
2928
+ def self.deploy_exists?(deploy_id)
2929
+ if deploy_id.nil? or deploy_id.empty?
2930
+ MU.log "Got nil deploy_id in MU::MommaCat.deploy_exists?", MU::WARN
2931
+ return
2932
+ end
2933
+ path = File.expand_path(MU.dataDir+"/deployments")
2934
+ if !Dir.exist?(path)
2935
+ Dir.mkdir(path, 0700)
2936
+ end
2937
+ deploy_path = File.expand_path(path+"/"+deploy_id)
2938
+ return Dir.exist?(deploy_path)
2939
+ end
2940
+
2941
+
2942
+ def createDeployKey
2943
+ key = OpenSSL::PKey::RSA.generate(4096)
2944
+ MU.log "Generated deploy key for #{MU.deploy_id}", MU::DEBUG, details: key.public_key.export
2945
+ return [key.export, key.public_key.export]
2946
+ end
2947
+
2565
2948
  # @param deploy_id [String]: The deployment to search. Will search all deployments if not specified.
2566
2949
  # @return [Hash,Array<Hash>]
2567
2950
  def self.getResourceMetadata(type, name: nil, deploy_id: nil, use_cache: true, mu_name: nil)
2568
2951
  if type.nil?
2569
2952
  raise MuError, "Can't call getResourceMetadata without a type argument"
2570
2953
  end
2571
- shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
2572
- type = cfg_plural
2954
+ _shortclass, _cfg_name, type, _classname = MU::Cloud.getResourceNames(type)
2955
+
2956
+ # first, check our in-memory deploys, which may or may not have been
2957
+ # written to disk yet.
2958
+ littercache = nil
2959
+ begin
2960
+ @@litter_semaphore.synchronize {
2961
+ littercache = @@litters.dup
2962
+ }
2963
+ rescue ThreadError => e
2964
+ # already locked by a parent caller and this is a read op, so this is ok
2965
+ raise e if !e.message.match(/recursive locking/)
2966
+ littercache = @@litters.dup
2967
+ end
2968
+ littercache.each_pair { |deploy, momma|
2969
+ @@deploy_struct_semaphore.synchronize {
2970
+ @deploy_cache[deploy] = {
2971
+ "mtime" => Time.now,
2972
+ "data" => momma.deployment
2973
+ }
2974
+ }
2975
+ }
2573
2976
 
2574
2977
  deploy_root = File.expand_path(MU.dataDir+"/deployments")
2575
2978
  MU::MommaCat.deploy_struct_semaphore.synchronize {
2576
- if Dir.exists?(deploy_root)
2979
+ if Dir.exist?(deploy_root)
2577
2980
  Dir.entries(deploy_root).each { |deploy|
2578
2981
  this_deploy_dir = deploy_root+"/"+deploy
2579
- next if deploy == "." or deploy == ".." or !Dir.exists?(this_deploy_dir)
2982
+ next if deploy == "." or deploy == ".." or !Dir.exist?(this_deploy_dir)
2580
2983
  next if deploy_id and deploy_id != deploy
2581
2984
 
2582
2985
  if !File.size?(this_deploy_dir+"/deployment.json")
@@ -2605,7 +3008,7 @@ MESSAGE_END
2605
3008
  # Populate some generable entries that should be in the deploy
2606
3009
  # data. Also, bounce out if we realize we've found exactly what
2607
3010
  # we needed already.
2608
- MU::Cloud.resource_types.each_pair { |res_type, attrs|
3011
+ MU::Cloud.resource_types.values.each { |attrs|
2609
3012
 
2610
3013
  next if @deploy_cache[deploy]['data'][attrs[:cfg_plural]].nil?
2611
3014
  if !attrs[:has_multiples]
@@ -2650,10 +3053,10 @@ MESSAGE_END
2650
3053
  next if !@deploy_cache[deploy]['data'].has_key?(type)
2651
3054
  if !name.nil?
2652
3055
  next if @deploy_cache[deploy]['data'][type][name].nil?
2653
- matches[deploy] = [] if !matches.has_key?(deploy)
3056
+ matches[deploy] ||= []
2654
3057
  matches[deploy] << @deploy_cache[deploy]['data'][type][name].dup
2655
3058
  else
2656
- matches[deploy] = [] if !matches.has_key?(deploy)
3059
+ matches[deploy] ||= []
2657
3060
  matches[deploy].concat(@deploy_cache[deploy]['data'][type].values)
2658
3061
  end
2659
3062
  }
@@ -2663,7 +3066,7 @@ MESSAGE_END
2663
3066
  !@deploy_cache[deploy_id]['data'][type].nil?
2664
3067
  if !name.nil?
2665
3068
  if !@deploy_cache[deploy_id]['data'][type][name].nil?
2666
- matches[deploy_id] = [] if !matches.has_key?(deploy_id)
3069
+ matches[deploy_id] ||= []
2667
3070
  matches[deploy_id] << @deploy_cache[deploy_id]['data'][type][name].dup
2668
3071
  else
2669
3072
  return matches # nothing, actually
@@ -2694,13 +3097,14 @@ MESSAGE_END
2694
3097
  begin
2695
3098
  @deployment = JSON.parse(File.read("#{deploy_dir}/deployment.json"))
2696
3099
  rescue JSON::ParserError => e
2697
- MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR
3100
+ MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR, details: e.message
2698
3101
  end
2699
3102
 
2700
3103
  deploy.flock(File::LOCK_UN)
2701
3104
  deploy.close
2702
3105
  if set_context_to_me
2703
3106
  ["appname", "environment", "timestamp", "seed", "handle"].each { |var|
3107
+ @deployment[var] ||= instance_variable_get("@#{var}".to_sym)
2704
3108
  if @deployment[var]
2705
3109
  if var != "handle"
2706
3110
  MU.setVar(var, @deployment[var].upcase)
@@ -2727,7 +3131,7 @@ MESSAGE_END
2727
3131
  begin
2728
3132
  @original_config = JSON.parse(File.read("#{deploy_dir}/basket_of_kittens.json"))
2729
3133
  rescue JSON::ParserError => e
2730
- MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR
3134
+ MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR, details: e.message
2731
3135
  end
2732
3136
  end
2733
3137
  if File.exist?(deploy_dir+"/ssh_key_name")
@@ -2748,7 +3152,7 @@ MESSAGE_END
2748
3152
  if Dir.exist?("#{deploy_dir}/secrets")
2749
3153
  @secrets.each_key { |type|
2750
3154
  Dir.glob("#{deploy_dir}/secrets/#{type}.*") { |filename|
2751
- base, server = File.basename(filename).split(/\./)
3155
+ server = File.basename(filename).split(/\./)[1]
2752
3156
 
2753
3157
  @secrets[type][server] = File.read(filename).chomp!
2754
3158
  }
@@ -2757,9 +3161,10 @@ MESSAGE_END
2757
3161
  }
2758
3162
  end
2759
3163
 
2760
- @catadjs = %w{fuzzy ginger lilac chocolate xanthic wiggly itty}
2761
- @catnouns = %w{bastet biscuits bobcat catnip cheetah chonk dot felix jaguar kitty leopard lion lynx maru mittens moggy neko nip ocelot panther patches paws phoebe purr queen roar saber sekhmet skogkatt socks sphinx spot tail tiger tom whiskers wildcat yowl floof beans ailurophile dander dewclaw grimalkin kibble quick tuft misty simba mew quat eek ziggy}
2762
- @catmixed = %w{abyssinian angora bengal birman bobtail bombay burmese calico chartreux cheshire cornish-rex curl devon egyptian-mau feline furever fumbs havana himilayan japanese-bobtail javanese khao-manee maine-coon manx marmalade mau munchkin norwegian pallas persian peterbald polydactyl ragdoll russian-blue savannah scottish-fold serengeti shorthair siamese siberian singapura snowshoe stray tabby tonkinese tortoiseshell turkish-van tuxedo uncia caterwaul lilac-point chocolate-point mackerel maltese knead whitenose vorpal}
3164
+ # 2019-06-03 adding things from https://aiweirdness.com/post/185339301987/once-again-a-neural-net-tries-to-name-cats
3165
+ @catadjs = %w{fuzzy ginger lilac chocolate xanthic wiggly itty chonky norty slonky floofy}
3166
+ @catnouns = %w{bastet biscuits bobcat catnip cheetah chonk dot felix hamb jaguar kitty leopard lion lynx maru mittens moggy neko nip ocelot panther patches paws phoebe purr queen roar saber sekhmet skogkatt socks sphinx spot tail tiger tom whiskers wildcat yowl floof beans ailurophile dander dewclaw grimalkin kibble quick tuft misty simba slonk mew quat eek ziggy whiskeridoo cromch monch screm}
3167
+ @catmixed = %w{abyssinian angora bengal birman bobtail bombay burmese calico chartreux cheshire cornish-rex curl devon egyptian-mau feline furever fumbs havana himilayan japanese-bobtail javanese khao-manee maine-coon manx marmalade mau munchkin norwegian pallas persian peterbald polydactyl ragdoll russian-blue savannah scottish-fold serengeti shorthair siamese siberian singapura snowshoe stray tabby tonkinese tortoiseshell turkish-van tuxedo uncia caterwaul lilac-point chocolate-point mackerel maltese knead whitenose vorpal chewie-bean chicken-whiskey fish-especially thelonious-monsieur tom-glitter serendipitous-kill sparky-buttons}
2763
3168
  @catwords = @catadjs + @catnouns + @catmixed
2764
3169
 
2765
3170
  @jaegeradjs = %w{azure fearless lucky olive vivid electric grey yarely violet ivory jade cinnamon crimson tacit umber mammoth ultra iron zodiac}
@@ -2771,4 +3176,3 @@ MESSAGE_END
2771
3176
 
2772
3177
  end #class
2773
3178
  end #module
2774
-