cloud-mu 3.1.3 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +21 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +926 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +169 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -0,0 +1,403 @@
1
+ # Copyright:: Copyright (c) 2020 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module MU
16
+
17
+ # MommaCat is in charge of managing metadata about resources we've created,
18
+ # as well as orchestrating amongst them and bootstrapping nodes outside of
19
+ # the normal synchronous deploy sequence invoked by *mu-deploy*.
20
+ class MommaCat
21
+
22
+ # Check a provided deploy key against our stored version. The instance has
23
+ # in theory accessed a secret via S3 and encrypted it with the deploy's
24
+ # public key. If it decrypts correctly, we assume this instance is indeed
25
+ # one of ours.
26
+ # @param ciphertext [String]: The text to decrypt.
27
+ # return [Boolean]: Whether the provided text was encrypted with the correct key
28
+ def authKey(ciphertext)
29
+ if @private_key.nil? or @deploy_secret.nil?
30
+ MU.log "Missing auth metadata, can't authorize node in authKey", MU::ERR
31
+ return false
32
+ end
33
+ my_key = OpenSSL::PKey::RSA.new(@private_key)
34
+
35
+ begin
36
+ if my_key.private_decrypt(ciphertext).force_encoding("UTF-8").chomp == @deploy_secret.force_encoding("UTF-8").chomp
37
+ MU.log "Matched ciphertext for #{MU.deploy_id}", MU::INFO
38
+ return true
39
+ else
40
+ MU.log "Mis-matched ciphertext for #{MU.deploy_id}", MU::ERR
41
+ return false
42
+ end
43
+ rescue OpenSSL::PKey::RSAError => e
44
+ MU.log "Error decrypting provided ciphertext using private key from #{deploy_dir}/private_key: #{e.message}", MU::ERR, details: ciphertext
45
+ return false
46
+ end
47
+ end
48
+
49
+ # Run {MU::Cloud::Server#postBoot} and {MU::Cloud::Server#groom} on a node.
50
+ # @param cloud_id [OpenStruct]: The cloud provider's identifier for this node.
51
+ # @param name [String]: The MU resource name of the node being created.
52
+ # @param mu_name [String]: The full #{MU::MommaCat.getResourceName} name of the server we're grooming, if it's been initialized already.
53
+ # @param type [String]: The type of resource that created this node (either *server* or *serverpool*).
54
+ def groomNode(cloud_id, name, type, mu_name: nil, reraise_fail: false, sync_wait: true)
55
+ if cloud_id.nil?
56
+ raise GroomError, "MU::MommaCat.groomNode requires a {MU::Cloud::Server} object"
57
+ end
58
+ if name.nil? or name.empty?
59
+ raise GroomError, "MU::MommaCat.groomNode requires a resource name"
60
+ end
61
+ if type.nil? or type.empty?
62
+ raise GroomError, "MU::MommaCat.groomNode requires a resource type"
63
+ end
64
+
65
+ if !MU::MommaCat.lock(cloud_id+"-mommagroom", true)
66
+ MU.log "Instance #{cloud_id} on #{MU.deploy_id} (#{type}: #{name}) is already being groomed, ignoring this extra request.", MU::NOTICE
67
+ MU::MommaCat.unlockAll
68
+ if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
69
+ puts "------------------------------"
70
+ puts "Open flock() locks:"
71
+ pp MU::MommaCat.locks
72
+ puts "------------------------------"
73
+ end
74
+ return
75
+ end
76
+ loadDeploy
77
+
78
+ # XXX this is to stop Net::SSH from killing our entire stack when it
79
+ # throws an exception. See ECAP-139 in JIRA. Far as we can tell, it's
80
+ # just not entirely thread safe.
81
+ Thread.handle_interrupt(Net::SSH::Disconnect => :never) {
82
+ begin
83
+ Thread.handle_interrupt(Net::SSH::Disconnect => :immediate) {
84
+ MU.log "(Probably harmless) Caught a Net::SSH::Disconnect in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
85
+ }
86
+ ensure
87
+ end
88
+ }
89
+
90
+ if @original_config[type+"s"].nil?
91
+ raise GroomError, "I see no configured resources of type #{type} (bootstrap request for #{name} on #{@deploy_id})"
92
+ end
93
+ kitten = nil
94
+
95
+ kitten = findLitterMate(type: "server", name: name, mu_name: mu_name, cloud_id: cloud_id)
96
+ if !kitten.nil?
97
+ MU.log "Re-grooming #{mu_name}", details: kitten.deploydata
98
+ else
99
+ first_groom = true
100
+ @original_config[type+"s"].each { |svr|
101
+ if svr['name'] == name
102
+ svr["instance_id"] = cloud_id
103
+
104
+ # This will almost always be true in server pools, but lets be safe. Somewhat problematic because we are only
105
+ # looking at deploy_id, but we still know this is our DNS record and not a custom one.
106
+ if svr['dns_records'] && !svr['dns_records'].empty?
107
+ svr['dns_records'].each { |dnsrec|
108
+ if dnsrec.has_key?("name") && dnsrec['name'].start_with?(MU.deploy_id.downcase)
109
+ MU.log "DNS record for #{MU.deploy_id.downcase}, #{name} is probably wrong, deleting", MU::WARN, details: dnsrec
110
+ dnsrec.delete('name')
111
+ dnsrec.delete('target')
112
+ end
113
+ }
114
+ end
115
+
116
+ kitten = MU::Cloud::Server.new(mommacat: self, kitten_cfg: svr, cloud_id: cloud_id)
117
+ mu_name = kitten.mu_name if mu_name.nil?
118
+ MU.log "Grooming #{mu_name} for the first time", details: svr
119
+ break
120
+ end
121
+ }
122
+ end
123
+
124
+ begin
125
+ # This is a shared lock with MU::Cloud::AWS::Server.create, to keep from
126
+ # stomping on synchronous deploys that are still running. This
127
+ # means we're going to wait here if this instance is still being
128
+ # bootstrapped by "regular" means.
129
+ if !MU::MommaCat.lock(cloud_id+"-create", true)
130
+ MU.log "#{mu_name} is still in mid-creation, skipping", MU::NOTICE
131
+ MU::MommaCat.unlockAll
132
+ if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
133
+ puts "------------------------------"
134
+ puts "Open flock() locks:"
135
+ pp MU::MommaCat.locks
136
+ puts "------------------------------"
137
+ end
138
+ return
139
+ end
140
+ MU::MommaCat.unlock(cloud_id+"-create")
141
+
142
+ if !kitten.postBoot(cloud_id)
143
+ MU.log "#{mu_name} is already being groomed, skipping", MU::NOTICE
144
+ MU::MommaCat.unlockAll
145
+ if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
146
+ puts "------------------------------"
147
+ puts "Open flock() locks:"
148
+ pp MU::MommaCat.locks
149
+ puts "------------------------------"
150
+ end
151
+ return
152
+ end
153
+
154
+ # This is a shared lock with MU::Deploy.createResources, simulating the
155
+ # thread logic that tells MU::Cloud::AWS::Server.deploy to wait until
156
+ # its dependencies are ready. We don't, for example, want to start
157
+ # deploying if we rely on an RDS instance that isn't ready yet. We can
158
+ # release this immediately, once we successfully grab it.
159
+ MU::MommaCat.lock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies")
160
+ MU::MommaCat.unlock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies")
161
+
162
+ kitten.groom
163
+ rescue StandardError => e
164
+ MU::MommaCat.unlockAll
165
+ if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exist?(deploy_dir+"/.cleanup."+cloud_id) and !File.exist?(deploy_dir+"/.cleanup")
166
+ MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
167
+ sendAdminSlack("Grooming FAILED for `#{kitten.mu_name}` with `#{e.message}` :crying_cat_face:", msg: e.backtrace.join("\n"))
168
+ sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
169
+ msg: e.inspect,
170
+ data: e.backtrace,
171
+ debug: true
172
+ )
173
+ raise e if reraise_fail
174
+ else
175
+ MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
176
+ end
177
+ return
178
+ end
179
+
180
+ if !@deployment['servers'].nil? and !sync_wait
181
+ syncLitter(@deployment["servers"].keys, triggering_node: kitten)
182
+ end
183
+ MU::MommaCat.unlock(cloud_id+"-mommagroom")
184
+ if MU.myCloud == "AWS"
185
+ MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
186
+ end
187
+ MU::MommaCat.getLitter(MU.deploy_id)
188
+ MU::Master.syncMonitoringConfig(false)
189
+ MU.log "Grooming complete for '#{name}' mu_name on \"#{MU.handle}\" (#{MU.deploy_id})"
190
+ FileUtils.touch(MU.dataDir+"/deployments/#{MU.deploy_id}/#{name}_done.txt")
191
+ MU::MommaCat.unlockAll
192
+ if first_groom
193
+ sendAdminSlack("Grooming complete for #{mu_name} :heart_eyes_cat:")
194
+ sendAdminMail("Grooming complete for '#{name}' (#{mu_name}) on deploy \"#{MU.handle}\" (#{MU.deploy_id})")
195
+ end
196
+ return
197
+ end
198
+
199
+ @cleanup_threads = []
200
+
201
+ # Iterate over all known deployments and look for instances that have been
202
+ # terminated, but not yet cleaned up, then clean them up.
203
+ def self.cleanTerminatedInstances(debug = false)
204
+ loglevel = debug ? MU::NOTICE : MU::DEBUG
205
+ MU::MommaCat.lock("clean-terminated-instances", false, true)
206
+ MU.log "Checking for harvested instances in need of cleanup", loglevel
207
+ parent_thread_id = Thread.current.object_id
208
+ purged = 0
209
+
210
+ MU::MommaCat.listDeploys.each { |deploy_id|
211
+ next if File.exist?(deploy_dir(deploy_id)+"/.cleanup")
212
+ MU.log "Checking for dead wood in #{deploy_id}", loglevel
213
+ need_reload = false
214
+ @cleanup_threads << Thread.new {
215
+ MU.dupGlobals(parent_thread_id)
216
+ deploy = MU::MommaCat.getLitter(deploy_id, set_context_to_me: true)
217
+ purged_this_deploy = 0
218
+ MU.log "#{deploy_id} has some kittens in it", loglevel, details: deploy.kittens.keys
219
+ if deploy.kittens.has_key?("servers")
220
+ MU.log "#{deploy_id} has some servers declared", loglevel, details: deploy.object_id
221
+ deploy.kittens["servers"].values.each { |nodeclasses|
222
+ nodeclasses.each_pair { |nodeclass, servers|
223
+ deletia = []
224
+ MU.log "Checking status of servers under '#{nodeclass}'", loglevel, details: servers.keys
225
+ servers.each_pair { |mu_name, server|
226
+ server.describe
227
+ if !server.cloud_id
228
+ MU.log "Checking for presence of #{mu_name}, but unable to fetch its cloud_id", MU::WARN, details: server
229
+ elsif !server.active?
230
+ next if File.exist?(deploy_dir(deploy_id)+"/.cleanup-"+server.cloud_id)
231
+ deletia << mu_name
232
+ need_reload = true
233
+ MU.log "Cleaning up metadata for #{server} (#{nodeclass}), formerly #{server.cloud_id}, which appears to have been terminated", MU::NOTICE
234
+ begin
235
+ server.destroy
236
+ deploy.sendAdminMail("Retired metadata for terminated node #{mu_name}")
237
+ deploy.sendAdminSlack("Retired metadata for terminated node `#{mu_name}`")
238
+ rescue StandardError => e
239
+ MU.log "Saw #{e.message} while retiring #{mu_name}", MU::ERR, details: e.backtrace
240
+ next
241
+ end
242
+ MU.log "Cleanup of metadata for #{server} (#{nodeclass}), formerly #{server.cloud_id} complete", MU::NOTICE
243
+ purged = purged + 1
244
+ purged_this_deploy = purged_this_deploy + 1
245
+ end
246
+ }
247
+ deletia.each { |mu_name|
248
+ servers.delete(mu_name)
249
+ }
250
+ if purged_this_deploy > 0
251
+ # XXX triggering_node needs to take more than one node name
252
+ deploy.syncLitter(servers.keys, triggering_node: deletia.first)
253
+ end
254
+ }
255
+ }
256
+ end
257
+ if need_reload
258
+ MU.log "Saving modified deploy #{deploy_id}", loglevel
259
+ deploy.save!
260
+ MU::MommaCat.getLitter(deploy_id)
261
+ end
262
+ MU.purgeGlobals
263
+ }
264
+ }
265
+ @cleanup_threads.each { |t|
266
+ t.join
267
+ }
268
+ MU.log "cleanTerminatedInstances threads complete", loglevel
269
+ MU::MommaCat.unlock("clean-terminated-instances", true)
270
+ @cleanup_threads = []
271
+
272
+ if purged > 0
273
+ if MU.myCloud == "AWS"
274
+ MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
275
+ end
276
+ MU::Master.syncMonitoringConfig
277
+ GC.start
278
+ end
279
+ MU.log "cleanTerminatedInstances returning", loglevel
280
+ end
281
+
282
+ # Path to the log file used by the Momma Cat daemon
283
+ # @return [String]
284
+ def self.daemonLogFile
285
+ base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
286
+ "#{base}/log/mu-momma-cat.log"
287
+ end
288
+
289
+ # Path to the PID file used by the Momma Cat daemon
290
+ # @return [String]
291
+ def self.daemonPidFile(root = false)
292
+ base = ((Process.uid == 0 or root) and !MU.localOnly) ? "/var" : MU.dataDir
293
+ "#{base}/run/mommacat.pid"
294
+ end
295
+
296
+ # Start the Momma Cat daemon and return the exit status of the command used
297
+ # @return [Integer]
298
+ def self.start
299
+ if MU.inGem? and MU.muCfg['disable_mommacat']
300
+ return
301
+ end
302
+ base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
303
+ [base, "#{base}/log", "#{base}/run"].each { |dir|
304
+ if !Dir.exist?(dir)
305
+ MU.log "Creating #{dir}"
306
+ Dir.mkdir(dir)
307
+ end
308
+ }
309
+ if (Process.uid != 0 and
310
+ (!$MU_CFG['overridden_keys'] or !$MU_CFG['overridden_keys'].include?("mommacat_port")) and
311
+ status(true)
312
+ ) or status
313
+ return 0
314
+ end
315
+
316
+ File.unlink(daemonPidFile) if File.exists?(daemonPidFile)
317
+ MU.log "Starting Momma Cat on port #{MU.mommaCatPort}, logging to #{daemonLogFile}, PID file #{daemonPidFile}"
318
+ origdir = Dir.getwd
319
+ Dir.chdir(MU.myRoot+"/modules")
320
+
321
+ # XXX what's the safest way to find the 'bundle' executable in both gem and non-gem installs?
322
+ if MU.inGem?
323
+ cmd = %Q{thin --threaded --daemonize --port #{MU.mommaCatPort} --pid #{daemonPidFile} --log #{daemonLogFile} --ssl --ssl-key-file #{MU.muCfg['ssl']['key']} --ssl-cert-file #{MU.muCfg['ssl']['cert']} --ssl-disable-verify --tag mu-momma-cat -R mommacat.ru start}
324
+ else
325
+ cmd = %Q{bundle exec thin --threaded --daemonize --port #{MU.mommaCatPort} --pid #{daemonPidFile} --log #{daemonLogFile} --ssl --ssl-key-file #{MU.muCfg['ssl']['key']} --ssl-cert-file #{MU.muCfg['ssl']['cert']} --ssl-disable-verify --tag mu-momma-cat -R mommacat.ru start}
326
+ end
327
+
328
+ MU.log cmd, MU::NOTICE
329
+
330
+ retries = 0
331
+ begin
332
+ output = %x{#{cmd}}
333
+ sleep 1
334
+ retries += 1
335
+ if retries >= 10
336
+ MU.log "MommaCat failed to start (command was #{cmd}, working directory #{MU.myRoot}/modules)", MU::WARN, details: output
337
+ pp caller
338
+ return $?.exitstatus
339
+ end
340
+ end while !status
341
+
342
+ Dir.chdir(origdir)
343
+
344
+ if $?.exitstatus != 0
345
+ exit 1
346
+ end
347
+
348
+ return $?.exitstatus
349
+ end
350
+
351
+ @@notified_on_pid = {}
352
+
353
+ # Return true if the Momma Cat daemon appears to be running
354
+ # @return [Boolean]
355
+ def self.status(root = false)
356
+ if MU.inGem? and MU.muCfg['disable_mommacat']
357
+ return true
358
+ end
359
+ if File.exist?(daemonPidFile(root))
360
+ pid = File.read(daemonPidFile(root)).chomp.to_i
361
+ begin
362
+ Process.getpgid(pid)
363
+ MU.log "Momma Cat running with pid #{pid.to_s}", (@@notified_on_pid[pid] ? MU::DEBUG : MU::INFO) # shush
364
+ @@notified_on_pid[pid] = true
365
+ return true
366
+ rescue Errno::ESRCH
367
+ end
368
+ end
369
+ MU.log "Momma Cat daemon not running", MU::NOTICE, details: daemonPidFile(root)
370
+ false
371
+ end
372
+
373
+ # Stop the Momma Cat daemon, if it's running
374
+ def self.stop
375
+ if File.exist?(daemonPidFile)
376
+ pid = File.read(daemonPidFile).chomp.to_i
377
+ MU.log "Stopping Momma Cat with pid #{pid.to_s}"
378
+ Process.kill("INT", pid)
379
+ killed = false
380
+ begin
381
+ Process.getpgid(pid)
382
+ sleep 1
383
+ rescue Errno::ESRCH
384
+ killed = true
385
+ end while killed
386
+ MU.log "Momma Cat with pid #{pid.to_s} stopped", MU::DEBUG, details: daemonPidFile
387
+
388
+ begin
389
+ File.unlink(daemonPidFile)
390
+ rescue Errno::ENOENT
391
+ end
392
+ end
393
+ end
394
+
395
+ # (Re)start the Momma Cat daemon and return the exit status of the start command
396
+ # @return [Integer]
397
+ def self.restart
398
+ stop
399
+ start
400
+ end
401
+
402
+ end #class
403
+ end #module
@@ -0,0 +1,473 @@
1
+ # Copyright:: Copyright (c) 2020 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module MU
16
+
17
+ # MommaCat is in charge of managing metadata about resources we've created,
18
+ # as well as orchestrating amongst them and bootstrapping nodes outside of
19
+ # the normal synchronous deploy sequence invoked by *mu-deploy*.
20
+ class MommaCat
21
+
22
+ # Lookup table to translate the word "habitat" back to its
23
+ # provider-specific jargon
24
+ HABITAT_SYNONYMS = {
25
+ "AWS" => "account",
26
+ "CloudFormation" => "account",
27
+ "Google" => "project",
28
+ "Azure" => "subscription",
29
+ "VMWare" => "sddc"
30
+ }
31
+
32
+ # Given a cloud provider's native descriptor for a resource, make some
33
+ # reasonable guesses about what the thing's name should be.
34
+ def self.guessName(desc, resourceclass, cloud_id: nil, tag_value: nil)
35
+ if desc.respond_to?(:tags) and
36
+ desc.tags.is_a?(Array) and
37
+ desc.tags.first.respond_to?(:key) and
38
+ desc.tags.map { |t| t.key }.include?("Name")
39
+ desc.tags.select { |t| t.key == "Name" }.first.value
40
+ else
41
+ try = nil
42
+ # Various GCP fields
43
+ [:display_name, :name, (resourceclass.cfg_name+"_name").to_sym].each { |field|
44
+ if desc.respond_to?(field) and desc.send(field).is_a?(String)
45
+ try = desc.send(field)
46
+ break
47
+ end
48
+
49
+ }
50
+ try ||= if !tag_value.nil?
51
+ tag_value
52
+ else
53
+ cloud_id
54
+ end
55
+ try
56
+ end
57
+
58
+ end
59
+
60
+ # Given a piece of a BoK resource descriptor Hash, come up with shorthand
61
+ # strings to give it a name for human readers. If nothing reasonable can be
62
+ # extracted, returns nil.
63
+ # @param obj [Hash]
64
+ # @param array_of [String]
65
+ # @param habitat_translate [String]
66
+ # @return [Array<String,nil>]
67
+ def self.getChunkName(obj, array_of = nil, habitat_translate: nil)
68
+ return [nil, nil] if obj.nil?
69
+ if [String, Integer, Boolean].include?(obj.class)
70
+ return [obj, nil]
71
+ end
72
+ obj_type = array_of || obj['type']
73
+ obj_name = obj['name'] || obj['id'] || obj['mu_name'] || obj['cloud_id']
74
+
75
+ name_string = if obj_name
76
+ if obj_type
77
+ "#{obj_type}[#{obj_name}]"
78
+ else
79
+ obj_name.dup
80
+ end
81
+ else
82
+ found_it = nil
83
+ using = nil
84
+ ["entity", "role"].each { |subtype|
85
+ if obj[subtype] and obj[subtype].is_a?(Hash)
86
+ found_it = if obj[subtype]["id"]
87
+ obj[subtype]['id'].dup
88
+ elsif obj[subtype]["type"] and obj[subtype]["name"]
89
+ "#{obj[subtype]['type']}[#{obj[subtype]['name']}]"
90
+ end
91
+ break
92
+ end
93
+ }
94
+ found_it
95
+ end
96
+ if name_string
97
+ name_string.gsub!(/\[.+?\](\[.+?\]$)/, '\1')
98
+ if habitat_translate and HABITAT_SYNONYMS[habitat_translate]
99
+ name_string.sub!(/^habitats?\[(.+?)\]/i, HABITAT_SYNONYMS[habitat_translate]+'[\1]')
100
+ end
101
+ end
102
+
103
+ location_list = []
104
+
105
+ location = if obj['project']
106
+ obj['project']
107
+ elsif obj['habitat'] and (obj['habitat']['id'] or obj['habitat']['name'])
108
+ obj['habitat']['name'] || obj['habitat']['id']
109
+ else
110
+ hab_str = nil
111
+ ['projects', 'habitats'].each { |key|
112
+
113
+ if obj[key] and obj[key].is_a?(Array)
114
+ location_list = obj[key].sort.map { |p|
115
+ (p["name"] || p["id"]).gsub(/^.*?[^\/]+\/([^\/]+)$/, '\1')
116
+ }
117
+ hab_str = location_list.join(", ")
118
+ name_string.gsub!(/^.*?[^\/]+\/([^\/]+)$/, '\1') if name_string
119
+ break
120
+ end
121
+ }
122
+ hab_str
123
+ end
124
+
125
+ [name_string, location, location_list]
126
+ end
127
+
128
+ # Generate a three-character string which can be used to unique-ify the
129
+ # names of resources which might potentially collide, e.g. Windows local
130
+ # hostnames, Amazon Elastic Load Balancers, or server pool instances.
131
+ # @return [String]: A three-character string consisting of two alphnumeric
132
+ # characters (uppercase) and one number.
133
+ def self.genUniquenessString
134
+ begin
135
+ candidate = SecureRandom.base64(2).slice(0..1) + SecureRandom.random_number(9).to_s
136
+ candidate.upcase!
137
+ end while candidate.match(/[^A-Z0-9]/)
138
+ return candidate
139
+ end
140
+
141
+ @unique_map_semaphore = Mutex.new
142
+ @name_unique_str_map = {}
143
+ # Keep a map of the uniqueness strings we assign to various full names, in
144
+ # case we want to reuse them later.
145
+ # @return [Hash<String>]
146
+ def self.name_unique_str_map
147
+ @name_unique_str_map
148
+ end
149
+
150
+ # Keep a map of the uniqueness strings we assign to various full names, in
151
+ # case we want to reuse them later.
152
+ # @return [Mutex]
153
+ def self.unique_map_semaphore
154
+ @unique_map_semaphore
155
+ end
156
+
157
+ # Generate a name string for a resource, incorporate the MU identifier
158
+ # for this deployment. Will dynamically shorten the name to fit for
159
+ # restrictive uses (e.g. Windows local hostnames, Amazon Elastic Load
160
+ # Balancers).
161
+ # @param name [String]: The shorthand name of the resource, usually the value of the "name" field in an Mu resource declaration.
162
+ # @param max_length [Integer]: The maximum length of the resulting resource name.
163
+ # @param need_unique_string [Boolean]: Whether to forcibly append a random three-character string to the name to ensure it's unique. Note that this behavior will be automatically invoked if the name must be truncated.
164
+ # @param scrub_mu_isms [Boolean]: Don't bother with generating names specific to this deployment. Used to generate generic CloudFormation templates, amongst other purposes.
165
+ # @param disallowed_chars [Regexp]: A pattern of characters that are illegal for this resource name, such as +/[^a-zA-Z0-9-]/+
166
+ # @return [String]: A full name string for this resource
167
+ def getResourceName(name, max_length: 255, need_unique_string: false, use_unique_string: nil, reuse_unique_string: false, scrub_mu_isms: @original_config['scrub_mu_isms'], disallowed_chars: nil)
168
+ if name.nil?
169
+ raise MuError, "Got no argument to MU::MommaCat.getResourceName"
170
+ end
171
+ if @appname.nil? or @environment.nil? or @timestamp.nil? or @seed.nil?
172
+ 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
173
+ return name
174
+ end
175
+ need_unique_string = false if scrub_mu_isms
176
+
177
+ muname = nil
178
+ if need_unique_string
179
+ reserved = 4
180
+ else
181
+ reserved = 0
182
+ end
183
+
184
+ # First, pare down the base name string until it will fit
185
+ basename = @appname.upcase + "-" + @environment.upcase + "-" + @timestamp + "-" + @seed.upcase + "-" + name.upcase
186
+ if scrub_mu_isms
187
+ basename = @appname.upcase + "-" + @environment.upcase + name.upcase
188
+ end
189
+
190
+ subchar = if disallowed_chars
191
+ if "-".match(disallowed_chars)
192
+ if !"_".match(disallowed_chars)
193
+ "_"
194
+ else
195
+ ""
196
+ end
197
+ else
198
+ "-"
199
+ end
200
+ end
201
+
202
+ if disallowed_chars
203
+ basename.gsub!(disallowed_chars, subchar) if disallowed_chars
204
+ end
205
+ attempts = 0
206
+ begin
207
+ if (basename.length + reserved) > max_length
208
+ MU.log "Stripping name down from #{basename}[#{basename.length.to_s}] (reserved: #{reserved.to_s}, max_length: #{max_length.to_s})", MU::DEBUG
209
+ if basename == @appname.upcase + "-" + @seed.upcase + "-" + name.upcase
210
+ # If we've run out of stuff to strip, truncate what's left and
211
+ # just leave room for the deploy seed and uniqueness string. This
212
+ # is the bare minimum, and probably what you'll see for most Windows
213
+ # hostnames.
214
+ basename = name.upcase + "-" + @appname.upcase
215
+ basename.slice!((max_length-(reserved+3))..basename.length)
216
+ basename.sub!(/-$/, "")
217
+ basename = basename + "-" + @seed.upcase
218
+ basename.gsub!(disallowed_chars, subchar) if disallowed_chars
219
+ else
220
+ # If we have to strip anything, assume we've lost uniqueness and
221
+ # will have to compensate with #genUniquenessString.
222
+ need_unique_string = true
223
+ reserved = 4
224
+ basename.sub!(/-[^-]+-#{@seed.upcase}-#{Regexp.escape(name.upcase)}$/, "")
225
+ basename = basename + "-" + @seed.upcase + "-" + name.upcase
226
+ basename.gsub!(disallowed_chars, subchar) if disallowed_chars
227
+ end
228
+ end
229
+ attempts += 1
230
+ raise MuError, "Failed to generate a reasonable name getResourceName(#{name}, max_length: #{max_length.to_s}, need_unique_string: #{need_unique_string.to_s}, use_unique_string: #{use_unique_string.to_s}, reuse_unique_string: #{reuse_unique_string.to_s}, scrub_mu_isms: #{scrub_mu_isms.to_s}, disallowed_chars: #{disallowed_chars})" if attempts > 10
231
+ end while (basename.length + reserved) > max_length
232
+
233
+ # Finally, apply our short random differentiator, if it's needed.
234
+ if need_unique_string
235
+ # Preferentially use a requested one, if it's not already in use.
236
+ if !use_unique_string.nil?
237
+ muname = basename + "-" + use_unique_string
238
+ if !allocateUniqueResourceName(muname) and !reuse_unique_string
239
+ MU.log "Requested to use #{use_unique_string} as differentiator when naming #{name}, but the name #{muname} is unavailable.", MU::WARN
240
+ muname = nil
241
+ end
242
+ end
243
+ if !muname
244
+ begin
245
+ unique_string = MU::MommaCat.genUniquenessString
246
+ muname = basename + "-" + unique_string
247
+ end while !allocateUniqueResourceName(muname)
248
+ MU::MommaCat.unique_map_semaphore.synchronize {
249
+ MU::MommaCat.name_unique_str_map[muname] = unique_string
250
+ }
251
+ end
252
+ else
253
+ muname = basename
254
+ end
255
+ muname.gsub!(disallowed_chars, subchar) if disallowed_chars
256
+
257
+ return muname
258
+ end
259
+
260
+ # List the name/value pairs for our mandatory standard set of resource tags, which
261
+ # should be applied to all taggable cloud provider resources.
262
+ # @return [Hash<String,String>]
263
+ def self.listStandardTags
264
+ return {} if !MU.deploy_id
265
+ {
266
+ "MU-ID" => MU.deploy_id,
267
+ "MU-APP" => MU.appname,
268
+ "MU-ENV" => MU.environment,
269
+ "MU-MASTER-IP" => MU.mu_public_ip
270
+ }
271
+ end
272
+ # List the name/value pairs for our mandatory standard set of resource tags
273
+ # for this deploy.
274
+ # @return [Hash<String,String>]
275
+ def listStandardTags
276
+ {
277
+ "MU-ID" => @deploy_id,
278
+ "MU-APP" => @appname,
279
+ "MU-ENV" => @environment,
280
+ "MU-MASTER-IP" => MU.mu_public_ip
281
+ }
282
+ end
283
+
284
+ # List the name/value pairs of our optional set of resource tags which
285
+ # should be applied to all taggable cloud provider resources.
286
+ # @return [Hash<String,String>]
287
+ def self.listOptionalTags
288
+ return {
289
+ "MU-HANDLE" => MU.handle,
290
+ "MU-MASTER-NAME" => Socket.gethostname,
291
+ "MU-OWNER" => MU.mu_user
292
+ }
293
+ end
294
+
295
+ # Make sure the given node has proper DNS entries, /etc/hosts entries,
296
+ # SSH config entries, etc.
297
+ # @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
298
+ # @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
299
+ def self.nameKitten(server, sync_wait: false, no_dns: false)
300
+ node, config, _deploydata = server.describe
301
+
302
+ mu_zone = nil
303
+ # XXX GCP!
304
+ if !no_dns and MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud?
305
+ zones = MU::Cloud::DNSZone.find(cloud_id: "platform-mu")
306
+ mu_zone = zones.values.first if !zones.nil?
307
+ end
308
+
309
+ if !mu_zone.nil?
310
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: node.gsub(/[^a-z0-9!"\#$%&'\(\)\*\+,\-\/:;<=>\?@\[\]\^_`{\|}~\.]/, '-').gsub(/--|^-/, ''), target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
311
+ else
312
+ MU::Master.addInstanceToEtcHosts(server.canonicalIP, node)
313
+ end
314
+
315
+ ## TO DO: Do DNS registration of "real" records as the last stage after the groomer completes
316
+ if config && config['dns_records'] && !config['dns_records'].empty?
317
+ dnscfg = config['dns_records'].dup
318
+ dnscfg.each { |dnsrec|
319
+ if !dnsrec.has_key?('name')
320
+ dnsrec['name'] = node.downcase
321
+ dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/)
322
+ end
323
+
324
+ if !dnsrec.has_key?("target")
325
+ # Default to register public endpoint
326
+ public = true
327
+
328
+ if dnsrec.has_key?("target_type")
329
+ # See if we have a preference for pubic/private endpoint
330
+ public = dnsrec["target_type"] == "private" ? false : true
331
+ end
332
+
333
+ dnsrec["target"] =
334
+ if dnsrec["type"] == "CNAME"
335
+ if public
336
+ # Make sure we have a public canonical name to register. Use the private one if we don't
337
+ server.cloud_desc.public_dns_name.empty? ? server.cloud_desc.private_dns_name : server.cloud_desc.public_dns_name
338
+ else
339
+ # If we specifically requested to register the private canonical name lets use that
340
+ server.cloud_desc.private_dns_name
341
+ end
342
+ elsif dnsrec["type"] == "A"
343
+ if public
344
+ # Make sure we have a public IP address to register. Use the private one if we don't
345
+ server.cloud_desc.public_ip_address ? server.cloud_desc.public_ip_address : server.cloud_desc.private_ip_address
346
+ else
347
+ # If we specifically requested to register the private IP lets use that
348
+ server.cloud_desc.private_ip_address
349
+ end
350
+ end
351
+ end
352
+ }
353
+ if !MU::Cloud::AWS.isGovCloud?
354
+ MU::Cloud::DNSZone.createRecordsFromConfig(dnscfg)
355
+ end
356
+ end
357
+
358
+ MU::Master.removeHostFromSSHConfig(node)
359
+ if server and server.canonicalIP
360
+ MU::Master.removeIPFromSSHKnownHosts(server.canonicalIP)
361
+ end
362
+ # XXX add names paramater with useful stuff
363
+ MU::Master.addHostToSSHConfig(
364
+ server,
365
+ ssh_owner: server.deploy.mu_user,
366
+ ssh_dir: Etc.getpwnam(server.deploy.mu_user).dir+"/.ssh"
367
+ )
368
+ end
369
+
370
+ # Manufactures a human-readable deployment name from the random
371
+ # two-character seed in MU-ID. Cat-themed when possible.
372
+ # @param seed [String]: A two-character seed from which we'll generate a name.
373
+ # @return [String]: Two words
374
+ def self.generateHandle(seed)
375
+ word_one=word_two=nil
376
+
377
+ # Unless we've got two letters that don't have corresponding cat-themed
378
+ # words, we'll insist that our generated handle have at least one cat
379
+ # element to it.
380
+ require_cat_words = true
381
+ if @catwords.select { |word| word.match(/^#{seed[0]}/i) }.size == 0 and
382
+ @catwords.select { |word| word.match(/^#{seed[1]}/i) }.size == 0
383
+ require_cat_words = false
384
+ MU.log "Got an annoying pair of letters #{seed}, not forcing cat-theming", MU::DEBUG
385
+ end
386
+ allnouns = @catnouns + @jaegernouns
387
+ alladjs = @catadjs + @jaegeradjs
388
+
389
+ tries = 0
390
+ begin
391
+ # Try to avoid picking something "nouny" for the first word
392
+ source = @catadjs + @catmixed + @jaegeradjs + @jaegermixed
393
+ first_ltr = source.select { |word| word.match(/^#{seed[0]}/i) }
394
+ if !first_ltr or first_ltr.size == 0
395
+ first_ltr = @words.select { |word| word.match(/^#{seed[0]}/i) }
396
+ end
397
+ word_one = first_ltr.shuffle.first
398
+
399
+ # If we got a paired set that happen to match our letters, go with it
400
+ if !word_one.nil? and word_one.match(/-#{seed[1]}/i)
401
+ word_one, word_two = word_one.split(/-/)
402
+ else
403
+ source = @words
404
+ if @catwords.include?(word_one)
405
+ source = @jaegerwords
406
+ elsif require_cat_words
407
+ source = @catwords
408
+ end
409
+ second_ltr = source.select { |word| word.match(/^#{seed[1]}/i) and !word.match(/-/i) }
410
+ word_two = second_ltr.shuffle.first
411
+ end
412
+ tries = tries + 1
413
+ end while tries < 50 and (word_one.nil? or word_two.nil? or word_one.match(/-/) or word_one == word_two or (allnouns.include?(word_one) and allnouns.include?(word_two)) or (alladjs.include?(word_one) and alladjs.include?(word_two)) or (require_cat_words and !@catwords.include?(word_one) and !@catwords.include?(word_two) and !@catwords.include?(word_one+"-"+word_two)))
414
+
415
+ if tries >= 50 and (word_one.nil? or word_two.nil?)
416
+ MU.log "I failed to generated a valid handle from #{seed}, faking it", MU::ERR
417
+ return "#{seed[0].capitalize} #{seed[1].capitalize}"
418
+ end
419
+
420
+ return "#{word_one.capitalize} #{word_two.capitalize}"
421
+ end
422
+
423
+ private
424
+
425
+ # Check to see whether a given resource name is unique across all
426
+ # deployments on this Mu server. We only enforce this for certain classes
427
+ # of names. If the name in question is available, add it to our cache of
428
+ # said names. See #{MU::MommaCat.getResourceName}
429
+ # @param name [String]: The name to attempt to allocate.
430
+ # @return [Boolean]: True if allocation was successful.
431
+ def allocateUniqueResourceName(name)
432
+ raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
433
+ path = File.expand_path(MU.dataDir+"/deployments")
434
+ File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
435
+ existing = []
436
+ f.flock(File::LOCK_EX)
437
+ f.readlines.each { |line|
438
+ existing << line.chomp
439
+ }
440
+ begin
441
+ existing.each { |used|
442
+ if used.match(/^#{name}:/)
443
+ if !used.match(/^#{name}:#{@deploy_id}$/)
444
+ MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
445
+ return false
446
+ else
447
+ return true
448
+ end
449
+ end
450
+ }
451
+ f.puts name+":"+@deploy_id
452
+ return true
453
+ ensure
454
+ f.flock(File::LOCK_UN)
455
+ end
456
+ }
457
+ end
458
+
459
+ # 2019-06-03 adding things from https://aiweirdness.com/post/185339301987/once-again-a-neural-net-tries-to-name-cats
460
+ @catadjs = %w{fuzzy ginger lilac chocolate xanthic wiggly itty chonky norty slonky floofy heckin bebby}
461
+ @catnouns = %w{bastet biscuits bobcat catnip cheetah chonk dot felix hamb hambina 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}
462
+ @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 nip-nops murder-mittens bite}
463
+ @catwords = @catadjs + @catnouns + @catmixed
464
+
465
+ @jaegeradjs = %w{azure fearless lucky olive vivid electric grey yarely violet ivory jade cinnamon crimson tacit umber mammoth ultra iron zodiac}
466
+ @jaegernouns = %w{horizon hulk ultimatum yardarm watchman whilrwind wright rhythm ocean enigma eruption typhoon jaeger brawler blaze vandal excalibur paladin juliet kaleidoscope romeo}
467
+ @jaegermixed = %w{alpha ajax amber avenger brave bravo charlie chocolate chrome corinthian dancer danger dash delta duet echo edge elite eureka foxtrot guardian gold hyperion illusion imperative india intercept kilo lancer night nova november oscar omega pacer quickstrike rogue ronin striker tango titan valor victor vulcan warder xenomorph xenon xray xylem yankee yell yukon zeal zero zoner zodiac}
468
+ @jaegerwords = @jaegeradjs + @jaegernouns + @jaegermixed
469
+
470
+ @words = @catwords + @jaegerwords
471
+
472
+ end #class
473
+ end #module