cloud-mu 3.1.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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,722 @@
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
+ @myhome = Etc.getpwuid(Process.uid).dir
22
+ @nagios_home = "/opt/mu/var/nagios_user_home"
23
+ @locks = Hash.new
24
+ @deploy_cache = Hash.new
25
+
26
+ # Return a {MU::MommaCat} instance for an existing deploy. Use this instead
27
+ # of using #initialize directly to avoid loading deploys multiple times or
28
+ # stepping on the global context for the deployment you're really working
29
+ # on..
30
+ # @param deploy_id [String]: The deploy ID of the deploy to load.
31
+ # @param set_context_to_me [Boolean]: Whether new MommaCat objects should overwrite any existing per-thread global deploy variables.
32
+ # @param use_cache [Boolean]: If we have an existing object for this deploy, use that
33
+ # @return [MU::MommaCat]
34
+ def self.getLitter(deploy_id, set_context_to_me: false, use_cache: true)
35
+ if deploy_id.nil? or deploy_id.empty?
36
+ raise MuError, "Cannot fetch a deployment without a deploy_id"
37
+ end
38
+
39
+ # XXX this caching may be harmful, causing stale resource objects to stick
40
+ # around. Have we fixed this? Sort of. Bad entries seem to have no kittens,
41
+ # so force a reload if we see that. That's probably not the root problem.
42
+ littercache = nil
43
+ begin
44
+ @@litter_semaphore.synchronize {
45
+ littercache = @@litters.dup
46
+ }
47
+ if littercache[deploy_id] and @@litters_loadtime[deploy_id]
48
+ deploy_root = File.expand_path(MU.dataDir+"/deployments")
49
+ this_deploy_dir = deploy_root+"/"+deploy_id
50
+ if File.exist?("#{this_deploy_dir}/deployment.json")
51
+ lastmod = File.mtime("#{this_deploy_dir}/deployment.json")
52
+ if lastmod > @@litters_loadtime[deploy_id]
53
+ MU.log "Deployment metadata for #{deploy_id} was modified on disk, reload", MU::NOTICE
54
+ use_cache = false
55
+ end
56
+ end
57
+ end
58
+ rescue ThreadError => e
59
+ # already locked by a parent caller and this is a read op, so this is ok
60
+ raise e if !e.message.match(/recursive locking/)
61
+ littercache = @@litters.dup
62
+ end
63
+
64
+ if !use_cache or littercache[deploy_id].nil?
65
+ need_gc = !littercache[deploy_id].nil?
66
+ newlitter = MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
67
+ # This, we have to synchronize, as it's a write
68
+ @@litter_semaphore.synchronize {
69
+ @@litters[deploy_id] = newlitter
70
+ @@litters_loadtime[deploy_id] = Time.now
71
+ }
72
+ GC.start if need_gc
73
+ elsif set_context_to_me
74
+ MU::MommaCat.setThreadContext(@@litters[deploy_id])
75
+ end
76
+ return @@litters[deploy_id]
77
+ # MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
78
+ end
79
+
80
+ # List the currently held flock() locks.
81
+ def self.trapSafeLocks;
82
+ @locks
83
+ end
84
+ # List the currently held flock() locks.
85
+ def self.locks;
86
+ @lock_semaphore.synchronize {
87
+ @locks
88
+ }
89
+ end
90
+
91
+
92
+ # Overwrite this deployment's configuration with a new version. Save the
93
+ # previous version as well.
94
+ # @param new_conf [Hash]: A new configuration, fully resolved by {MU::Config}
95
+ def updateBasketofKittens(new_conf, skip_validation: false, new_metadata: nil, save_now: false)
96
+ loadDeploy
97
+ if new_conf == @original_config
98
+ return
99
+ end
100
+
101
+ scrub_with = nil
102
+
103
+ # Make sure the new config that we were just handed resolves and makes
104
+ # sense
105
+ if !skip_validation
106
+ f = Tempfile.new(@deploy_id)
107
+ f.write JSON.parse(JSON.generate(new_conf)).to_yaml
108
+ conf_engine = MU::Config.new(f.path) # will throw an exception if it's bad, adoption should catch this and cope reasonably
109
+ scrub_with = conf_engine.config
110
+ f.close
111
+ end
112
+
113
+ backup = "#{deploy_dir}/basket_of_kittens.json.#{Time.now.to_i.to_s}"
114
+ MU.log "Saving previous config of #{@deploy_id} to #{backup}"
115
+ config = File.new(backup, File::CREAT|File::TRUNC|File::RDWR, 0600)
116
+ config.flock(File::LOCK_EX)
117
+ config.puts JSON.pretty_generate(@original_config)
118
+ config.flock(File::LOCK_UN)
119
+ config.close
120
+
121
+ @original_config = new_conf.clone
122
+
123
+ MU::Cloud.resource_types.each_pair { |res_type, attrs|
124
+ next if !@deployment.has_key?(attrs[:cfg_plural])
125
+ deletia = []
126
+ @deployment[attrs[:cfg_plural]].each_pair { |res_name, data|
127
+ orig_cfg = findResourceConfig(attrs[:cfg_plural], res_name, (scrub_with || @original_config))
128
+
129
+ if orig_cfg.nil?
130
+ MU.log "#{res_type} #{res_name} no longer configured, will remove deployment metadata", MU::NOTICE
131
+ deletia << res_name
132
+ end
133
+ }
134
+ @deployment[attrs[:cfg_plural]].reject! { |k, v| deletia.include?(k) }
135
+ }
136
+
137
+ if save_now
138
+ save!
139
+ MU.log "New config saved to #{deploy_dir}/basket_of_kittens.json"
140
+ end
141
+ end
142
+
143
+ @lock_semaphore = Mutex.new
144
+ # Release all flock() locks held by the current thread.
145
+ def self.unlockAll
146
+ if !@locks.nil? and !@locks[Thread.current.object_id].nil?
147
+ # Work from a copy so we can iterate without worrying about contention
148
+ # in lock() or unlock(). We can't just wrap our iterator block in a
149
+ # semaphore here, because we're calling another method that uses the
150
+ # same semaphore.
151
+ @lock_semaphore.synchronize {
152
+ delete_list = []
153
+ @locks[Thread.current.object_id].keys.each { |id|
154
+ MU.log "Releasing lock on #{deploy_dir(MU.deploy_id)}/locks/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
155
+ begin
156
+ @locks[Thread.current.object_id][id].flock(File::LOCK_UN)
157
+ @locks[Thread.current.object_id][id].close
158
+ rescue IOError => e
159
+ MU.log "Got #{e.inspect} unlocking #{id} on #{Thread.current.object_id}", MU::WARN
160
+ end
161
+ delete_list << id
162
+ }
163
+ # We do this here because we can't mangle a Hash while we're iterating
164
+ # over it.
165
+ delete_list.each { |id|
166
+ @locks[Thread.current.object_id].delete(id)
167
+ }
168
+ if @locks[Thread.current.object_id].size == 0
169
+ @locks.delete(Thread.current.object_id)
170
+ end
171
+ }
172
+ end
173
+ end
174
+
175
+ # Create/hold a flock() lock.
176
+ # @param id [String]: The lock identifier to release.
177
+ # @param nonblock [Boolean]: Whether to block while waiting for the lock. In non-blocking mode, we simply return false if the lock is not available.
178
+ # return [false, nil]
179
+ def self.lock(id, nonblock = false, global = false, deploy_id: MU.deploy_id)
180
+ raise MuError, "Can't pass a nil id to MU::MommaCat.lock" if id.nil?
181
+
182
+ if !global
183
+ lockdir = "#{deploy_dir(deploy_id)}/locks"
184
+ else
185
+ lockdir = File.expand_path(MU.dataDir+"/locks")
186
+ end
187
+
188
+ if !Dir.exist?(lockdir)
189
+ MU.log "Creating #{lockdir}", MU::DEBUG
190
+ Dir.mkdir(lockdir, 0700)
191
+ end
192
+
193
+ @lock_semaphore.synchronize {
194
+ if @locks[Thread.current.object_id].nil?
195
+ @locks[Thread.current.object_id] = Hash.new
196
+ end
197
+
198
+ @locks[Thread.current.object_id][id] = File.open("#{lockdir}/#{id}.lock", File::CREAT|File::RDWR, 0600)
199
+ }
200
+ MU.log "Getting a lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})...", MU::DEBUG
201
+ begin
202
+ if nonblock
203
+ if !@locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
204
+ return false
205
+ end
206
+ else
207
+ @locks[Thread.current.object_id][id].flock(File::LOCK_EX)
208
+ end
209
+ rescue IOError
210
+ raise MU::BootstrapTempFail, "Interrupted waiting for lock on thread #{Thread.current.object_id}, probably just a node rebooting as part of a synchronous install"
211
+ end
212
+ MU.log "Lock on #{lockdir}/#{id}.lock on thread #{Thread.current.object_id} acquired", MU::DEBUG
213
+ return true
214
+ end
215
+
216
+ # Release a flock() lock.
217
+ # @param id [String]: The lock identifier to release.
218
+ def self.unlock(id, global = false, deploy_id: MU.deploy_id)
219
+ raise MuError, "Can't pass a nil id to MU::MommaCat.unlock" if id.nil?
220
+ lockdir = nil
221
+ if !global
222
+ lockdir = "#{deploy_dir(deploy_id)}/locks"
223
+ else
224
+ lockdir = File.expand_path(MU.dataDir+"/locks")
225
+ end
226
+ @lock_semaphore.synchronize {
227
+ return if @locks.nil? or @locks[Thread.current.object_id].nil? or @locks[Thread.current.object_id][id].nil?
228
+ }
229
+ MU.log "Releasing lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
230
+ begin
231
+ @locks[Thread.current.object_id][id].flock(File::LOCK_UN)
232
+ @locks[Thread.current.object_id][id].close
233
+ if !@locks[Thread.current.object_id].nil?
234
+ @locks[Thread.current.object_id].delete(id)
235
+ end
236
+ if @locks[Thread.current.object_id].size == 0
237
+ @locks.delete(Thread.current.object_id)
238
+ end
239
+ rescue IOError => e
240
+ MU.log "Got #{e.inspect} unlocking #{id} on #{Thread.current.object_id}", MU::WARN
241
+ end
242
+ end
243
+
244
+ # Remove a deployment's metadata.
245
+ # @param deploy_id [String]: The deployment identifier to remove.
246
+ def self.purge(deploy_id)
247
+ if deploy_id.nil? or deploy_id.empty?
248
+ raise MuError, "Got nil deploy_id in MU::MommaCat.purge"
249
+ end
250
+ # XXX archiving is better than annihilating
251
+ path = File.expand_path(MU.dataDir+"/deployments")
252
+ if Dir.exist?(path+"/"+deploy_id)
253
+ unlockAll
254
+ MU.log "Purging #{path}/#{deploy_id}" if File.exist?(path+"/"+deploy_id+"/deployment.json")
255
+
256
+ FileUtils.rm_rf(path+"/"+deploy_id, :secure => true)
257
+ end
258
+ if File.exist?(path+"/unique_ids")
259
+ File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
260
+ newlines = []
261
+ f.flock(File::LOCK_EX)
262
+ f.readlines.each { |line|
263
+ newlines << line if !line.match(/:#{deploy_id}$/)
264
+ }
265
+ f.rewind
266
+ f.truncate(0)
267
+ f.puts(newlines)
268
+ f.flush
269
+ f.flock(File::LOCK_UN)
270
+ }
271
+ end
272
+ end
273
+
274
+ # Remove the metadata of the currently loaded deployment.
275
+ def purge!
276
+ MU::MommaCat.purge(MU.deploy_id)
277
+ end
278
+
279
+ # Return a list of all currently active deploy identifiers.
280
+ # @return [Array<String>]
281
+ def self.listDeploys
282
+ return [] if !Dir.exist?("#{MU.dataDir}/deployments")
283
+ deploys = []
284
+ Dir.entries("#{MU.dataDir}/deployments").reverse_each { |muid|
285
+ next if !Dir.exist?("#{MU.dataDir}/deployments/#{muid}") or muid == "." or muid == ".."
286
+ deploys << muid
287
+ }
288
+ return deploys
289
+ end
290
+
291
+ # Return a list of all nodes in all deployments. Does so without loading
292
+ # deployments fully.
293
+ # @return [Hash]
294
+ def self.listAllNodes
295
+ nodes = Hash.new
296
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
297
+ MU::MommaCat.listDeploys.each { |deploy|
298
+ if !Dir.exist?(MU::MommaCat.deploy_dir(deploy)) or
299
+ !File.size?("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json")
300
+ MU.log "Didn't see deployment metadata for '#{deploy}'", MU::WARN
301
+ next
302
+ end
303
+ data = File.open("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json", File::RDONLY)
304
+ MU.log "Getting lock to read #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::DEBUG
305
+ data.flock(File::LOCK_EX)
306
+ begin
307
+ deployment = JSON.parse(File.read("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json"))
308
+ deployment["deploy_id"] = deploy
309
+ if deployment.has_key?("servers")
310
+ deployment["servers"].each_key { |nodeclass|
311
+ deployment["servers"][nodeclass].each_pair { |mu_name, metadata|
312
+ nodes[mu_name] = metadata
313
+ }
314
+ }
315
+ end
316
+ rescue JSON::ParserError => e
317
+ MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR, details: e.message
318
+ end
319
+ data.flock(File::LOCK_UN)
320
+ data.close
321
+ }
322
+ }
323
+ return nodes
324
+ end
325
+
326
+ # @return [String]: The Mu Master filesystem directory holding metadata for the current deployment
327
+ def deploy_dir
328
+ MU::MommaCat.deploy_dir(@deploy_id)
329
+ end
330
+
331
+ # Locate and return the deploy, if any, which matches the provided origin
332
+ # description
333
+ # @param origin [Hash]
334
+ def self.findMatchingDeploy(origin)
335
+ MU::MommaCat.listDeploys.each { |deploy_id|
336
+ o_path = deploy_dir(deploy_id)+"/origin.json"
337
+ next if !File.exist?(o_path)
338
+ this_origin = JSON.parse(File.read(o_path))
339
+ if origin == this_origin
340
+ MU.log "Deploy #{deploy_id} matches origin hash, loading", details: origin
341
+ return MU::MommaCat.new(deploy_id)
342
+ end
343
+ }
344
+ nil
345
+ end
346
+
347
+ # Synchronize all in-memory information related to this to deployment to
348
+ # disk.
349
+ # @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
350
+ # @param force [Boolean]: Save even if +no_artifacts+ is set
351
+ # @param origin [Hash]: Optional blob of data indicating how this deploy was created
352
+ def save!(triggering_node = nil, force: false, origin: nil)
353
+
354
+ return if @no_artifacts and !force
355
+
356
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
357
+ MU.log "Saving deployment #{MU.deploy_id}", MU::DEBUG
358
+
359
+ if !Dir.exist?(deploy_dir)
360
+ MU.log "Creating #{deploy_dir}", MU::DEBUG
361
+ Dir.mkdir(deploy_dir, 0700)
362
+ end
363
+
364
+ writeFile("origin.json", JSON.pretty_generate(origin)) if !origin.nil?
365
+ writeFile("private_key", @private_key) if !@private_key.nil?
366
+ writeFile("public_key", @public_key) if !@public_key.nil?
367
+
368
+ if !@deployment.nil? and @deployment.size > 0
369
+ @deployment['handle'] = MU.handle if @deployment['handle'].nil? and !MU.handle.nil?
370
+ [:public_key, :timestamp, :seed, :appname, :handle, :ssh_public_key].each { |var|
371
+ value = instance_variable_get(("@"+var.to_s).to_sym)
372
+ @deployment[var.to_s] = value if value
373
+ }
374
+
375
+ begin
376
+ # XXX doing this to trigger JSON errors before stomping the stored
377
+ # file...
378
+ JSON.pretty_generate(@deployment, max_nesting: false)
379
+ deploy = File.new("#{deploy_dir}/deployment.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
380
+ MU.log "Getting lock to write #{deploy_dir}/deployment.json", MU::DEBUG
381
+ deploy.flock(File::LOCK_EX)
382
+ deploy.puts JSON.pretty_generate(@deployment, max_nesting: false)
383
+ rescue JSON::NestingError => e
384
+ MU.log e.inspect, MU::ERR, details: @deployment
385
+ raise MuError, "Got #{e.message} trying to save deployment"
386
+ rescue Encoding::UndefinedConversionError => e
387
+ MU.log e.inspect, MU::ERR, details: @deployment
388
+ raise MuError, "Got #{e.message} at #{e.error_char.dump} (#{e.source_encoding_name} => #{e.destination_encoding_name}) trying to save deployment"
389
+ end
390
+ deploy.flock(File::LOCK_UN)
391
+ deploy.close
392
+ @need_deploy_flush = false
393
+ MU::MommaCat.updateLitter(@deploy_id, self)
394
+ end
395
+
396
+ if !@original_config.nil? and @original_config.is_a?(Hash)
397
+ writeFile("basket_of_kittens.json", JSON.pretty_generate(MU::Config.manxify(@original_config)))
398
+ end
399
+
400
+ writeFile("node_ssh.key", @ssh_private_key) if !@ssh_private_key.nil?
401
+ writeFile("node_ssh.pub", @ssh_public_key) if !@ssh_public_key.nil?
402
+ writeFile("ssh_key_name", @ssh_key_name) if !@ssh_key_name.nil?
403
+ writeFile("environment_name", @environment) if !@environment.nil?
404
+ writeFile("deploy_secret", @deploy_secret) if !@deploy_secret.nil?
405
+
406
+ if !@secrets.nil?
407
+ secretdir = "#{deploy_dir}/secrets"
408
+ if !Dir.exist?(secretdir)
409
+ MU.log "Creating #{secretdir}", MU::DEBUG
410
+ Dir.mkdir(secretdir, 0700)
411
+ end
412
+ @secrets.each_pair { |type, servers|
413
+ servers.each_pair { |server, svr_secret|
414
+ writeFile("secrets/#{type}.#{server}", svr_secret)
415
+ }
416
+ }
417
+ end
418
+ }
419
+
420
+ # Update groomer copies of this metadata
421
+ syncLitter(@deployment['servers'].keys, triggering_node: triggering_node, save_only: true) if @deployment.has_key?("servers")
422
+ end
423
+
424
+ # Read all of our +deployment.json+ files in and stick them in a hash. Used
425
+ # by search routines that just need to skim this data without loading
426
+ # entire {MU::MommaCat} objects.
427
+ def self.cacheDeployMetadata(deploy_id = nil, use_cache: false)
428
+ deploy_root = File.expand_path(MU.dataDir+"/deployments")
429
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
430
+ @@deploy_cache ||= {}
431
+ return if !Dir.exist?(deploy_root)
432
+
433
+ Dir.entries(deploy_root).each { |deploy|
434
+ this_deploy_dir = deploy_root+"/"+deploy
435
+ this_deploy_file = this_deploy_dir+"/deployment.json"
436
+
437
+ if deploy == "." or deploy == ".." or !Dir.exist?(this_deploy_dir) or
438
+ (deploy_id and deploy_id != deploy) or
439
+ !File.size?(this_deploy_file) or
440
+ (use_cache and @@deploy_cache[deploy] and @@deploy_cache[deploy]['mtime'] == File.mtime(this_deploy_file))
441
+ next
442
+ end
443
+
444
+ @@deploy_cache[deploy] ||= {}
445
+
446
+ MU.log "Caching deploy #{deploy}", MU::DEBUG
447
+ lock = File.open(this_deploy_file, File::RDONLY)
448
+ lock.flock(File::LOCK_EX)
449
+ @@deploy_cache[deploy]['mtime'] = File.mtime(this_deploy_file)
450
+
451
+ begin
452
+ @@deploy_cache[deploy]['data'] = JSON.parse(File.read(this_deploy_file))
453
+ next if @@deploy_cache[deploy]['data'].nil?
454
+ # Populate some generable entries that should be in the deploy
455
+ # data. Also, bounce out if we realize we've found exactly what
456
+ # we needed already.
457
+ MU::Cloud.resource_types.values.each { |attrs|
458
+
459
+ next if @@deploy_cache[deploy]['data'][attrs[:cfg_plural]].nil?
460
+ if attrs[:has_multiples]
461
+ @@deploy_cache[deploy]['data'][attrs[:cfg_plural]].each_pair { |node_class, nodes|
462
+ next if nodes.nil? or !nodes.is_a?(Hash)
463
+ nodes.each_pair { |nodename, data|
464
+ next if !data.is_a?(Hash)
465
+ data['#MU_NODE_CLASS'] ||= node_class
466
+ data['#MU_NAME'] ||= nodename
467
+ data["cloud"] ||= MU::Config.defaultCloud
468
+ }
469
+ }
470
+ end
471
+ }
472
+ rescue JSON::ParserError
473
+ raise MuError, "JSON parse failed on #{this_deploy_file}\n\n"+File.read(this_deploy_file)
474
+ ensure
475
+ lock.flock(File::LOCK_UN)
476
+ lock.close
477
+ end
478
+ }
479
+ }
480
+
481
+ @@deploy_cache
482
+ end
483
+
484
+ # Get the deploy directory
485
+ # @param deploy_id [String]
486
+ # @return [String]
487
+ def self.deploy_dir(deploy_id)
488
+ raise MuError, "deploy_dir must get a deploy_id if called as class method (from #{caller[0]}; #{caller[1]})" if deploy_id.nil?
489
+ # XXX this will blow up if someone sticks MU in /
490
+ path = File.expand_path(MU.dataDir+"/deployments")
491
+ if !Dir.exist?(path)
492
+ MU.log "Creating #{path}", MU::DEBUG
493
+ Dir.mkdir(path, 0700)
494
+ end
495
+ path = path+"/"+deploy_id
496
+ return path
497
+ end
498
+
499
+ # Does the deploy with the given id exist?
500
+ # @param deploy_id [String]
501
+ # @return [String]
502
+ def self.deploy_exists?(deploy_id)
503
+ if deploy_id.nil? or deploy_id.empty?
504
+ MU.log "Got nil deploy_id in MU::MommaCat.deploy_exists?", MU::WARN
505
+ return
506
+ end
507
+ path = File.expand_path(MU.dataDir+"/deployments")
508
+ if !Dir.exist?(path)
509
+ Dir.mkdir(path, 0700)
510
+ end
511
+ deploy_path = File.expand_path(path+"/"+deploy_id)
512
+ return Dir.exist?(deploy_path)
513
+ end
514
+
515
+ private
516
+
517
+ def writeFile(filename, contents)
518
+ file = File.new("#{deploy_dir}/#{filename}", File::CREAT|File::TRUNC|File::RDWR, 0600)
519
+ file.puts contents
520
+ file.close
521
+ end
522
+
523
+ # Helper for +initialize+
524
+ def setDeploySecret
525
+ credsets = {}
526
+ MU::Cloud.resource_types.values.each { |attrs|
527
+ if !@original_config[attrs[:cfg_plural]].nil? and @original_config[attrs[:cfg_plural]].size > 0
528
+ @original_config[attrs[:cfg_plural]].each { |resource|
529
+
530
+ credsets[resource['cloud']] ||= []
531
+ credsets[resource['cloud']] << resource['credentials']
532
+ @clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
533
+ @clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
534
+
535
+ }
536
+ end
537
+ }
538
+
539
+ MU.log "Creating deploy secret for #{MU.deploy_id}"
540
+ @deploy_secret = Password.random(256)
541
+ if !@original_config['scrub_mu_isms'] and !@no_artifacts
542
+ credsets.each_pair { |cloud, creds|
543
+ creds.uniq!
544
+ creds.each { |credentials|
545
+ MU::Cloud.cloudClass(cloud).writeDeploySecret(@deploy_id, @deploy_secret, credentials: credentials)
546
+ }
547
+ }
548
+ end
549
+ end
550
+
551
+ def loadObjects(delay_descriptor_load)
552
+ # Load up MU::Cloud objects for all our kittens in this deploy
553
+
554
+ MU::Cloud.resource_types.each_pair { |res_type, attrs|
555
+ type = attrs[:cfg_plural]
556
+ next if !@deployment.has_key?(type)
557
+
558
+ deletia = {}
559
+ @deployment[type].each_pair { |res_name, data|
560
+ orig_cfg = findResourceConfig(type, res_name)
561
+
562
+ if orig_cfg.nil?
563
+ 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
564
+ next
565
+ end
566
+
567
+ if orig_cfg['vpc']
568
+ ref = if orig_cfg['vpc']['id'] and orig_cfg['vpc']['id'].is_a?(Hash)
569
+ orig_cfg['vpc']['id']['mommacat'] = self
570
+ MU::Config::Ref.get(orig_cfg['vpc']['id'])
571
+ else
572
+ orig_cfg['vpc']['mommacat'] = self
573
+ MU::Config::Ref.get(orig_cfg['vpc'])
574
+ end
575
+ orig_cfg['vpc'].delete('mommacat')
576
+ orig_cfg['vpc'] = ref if ref.kitten(shallow: true)
577
+ end
578
+
579
+ begin
580
+ if attrs[:has_multiples]
581
+ data.keys.each { |mu_name|
582
+ addKitten(type, res_name, attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name, delay_descriptor_load: delay_descriptor_load))
583
+ }
584
+ else
585
+ addKitten(type, res_name, attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id']))
586
+ end
587
+ rescue StandardError => e
588
+ if e.class != MU::Cloud::MuCloudResourceNotImplemented
589
+ MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
590
+ end
591
+ end
592
+ }
593
+
594
+ }
595
+ end
596
+
597
+ # Helper for +initialize+
598
+ def initDeployDirectory
599
+ if !Dir.exist?(MU.dataDir+"/deployments")
600
+ MU.log "Creating #{MU.dataDir}/deployments", MU::DEBUG
601
+ Dir.mkdir(MU.dataDir+"/deployments", 0700)
602
+ end
603
+ path = File.expand_path(MU.dataDir+"/deployments")+"/"+@deploy_id
604
+ if !Dir.exist?(path)
605
+ MU.log "Creating #{path}", MU::DEBUG
606
+ Dir.mkdir(path, 0700)
607
+ end
608
+
609
+ @ssh_key_name, @ssh_private_key, @ssh_public_key = self.SSHKey
610
+ if !File.exist?(deploy_dir+"/private_key")
611
+ @private_key, @public_key = createDeployKey
612
+ end
613
+
614
+ end
615
+
616
+ ###########################################################################
617
+ ###########################################################################
618
+ def loadDeployFromCache(set_context_to_me = true)
619
+ return false if !File.size?(deploy_dir+"/deployment.json")
620
+
621
+ deploy = File.open("#{deploy_dir}/deployment.json", File::RDONLY)
622
+ MU.log "Getting lock to read #{deploy_dir}/deployment.json", MU::DEBUG
623
+ # deploy.flock(File::LOCK_EX)
624
+ begin
625
+ Timeout::timeout(90) {deploy.flock(File::LOCK_EX)}
626
+ rescue Timeout::Error
627
+ raise MuError, "Timed out trying to get an exclusive lock on #{deploy_dir}/deployment.json"
628
+ end
629
+
630
+ begin
631
+ @deployment = JSON.parse(File.read("#{deploy_dir}/deployment.json"))
632
+ rescue JSON::ParserError => e
633
+ MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR, details: e.message
634
+ end
635
+
636
+ deploy.flock(File::LOCK_UN)
637
+ deploy.close
638
+
639
+ setThreadContextToMe if set_context_to_me
640
+
641
+ @timestamp = @deployment['timestamp']
642
+ @seed = @deployment['seed']
643
+ @appname = @deployment['appname']
644
+ @handle = @deployment['handle']
645
+
646
+ true
647
+ end
648
+
649
+ ###########################################################################
650
+ ###########################################################################
651
+ def loadDeploy(deployment_json_only = false, set_context_to_me: true)
652
+ MU::MommaCat.deploy_struct_semaphore.synchronize {
653
+ success = loadDeployFromCache(set_context_to_me)
654
+
655
+ return if deployment_json_only and success
656
+
657
+ if File.exist?(deploy_dir+"/private_key")
658
+ @private_key = File.read("#{deploy_dir}/private_key")
659
+ @public_key = File.read("#{deploy_dir}/public_key")
660
+ end
661
+
662
+ if File.exist?(deploy_dir+"/basket_of_kittens.json")
663
+ begin
664
+ @original_config = JSON.parse(File.read("#{deploy_dir}/basket_of_kittens.json"))
665
+ rescue JSON::ParserError => e
666
+ MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR, details: e.message
667
+ end
668
+ end
669
+ if File.exist?(deploy_dir+"/ssh_key_name")
670
+ @ssh_key_name = File.read("#{deploy_dir}/ssh_key_name").chomp!
671
+ end
672
+ if File.exist?(deploy_dir+"/node_ssh.key")
673
+ @ssh_private_key = File.read("#{deploy_dir}/node_ssh.key")
674
+ end
675
+ if File.exist?(deploy_dir+"/node_ssh.pub")
676
+ @ssh_public_key = File.read("#{deploy_dir}/node_ssh.pub")
677
+ end
678
+ if File.exist?(deploy_dir+"/environment_name")
679
+ @environment = File.read("#{deploy_dir}/environment_name").chomp!
680
+ end
681
+ if File.exist?(deploy_dir+"/deploy_secret")
682
+ @deploy_secret = File.read("#{deploy_dir}/deploy_secret")
683
+ end
684
+ if Dir.exist?("#{deploy_dir}/secrets")
685
+ @secrets.each_key { |type|
686
+ Dir.glob("#{deploy_dir}/secrets/#{type}.*") { |filename|
687
+ server = File.basename(filename).split(/\./)[1]
688
+
689
+ @secrets[type][server] = File.read(filename).chomp!
690
+ }
691
+ }
692
+ end
693
+ }
694
+ end
695
+
696
+ def findResourceConfig(type, name, config = @original_config)
697
+ orig_cfg = nil
698
+ if config.has_key?(type)
699
+ config[type].each { |resource|
700
+ if resource["name"] == name
701
+ orig_cfg = resource
702
+ break
703
+ end
704
+ }
705
+ end
706
+
707
+ # Some Server objects originated from ServerPools, get their
708
+ # configs from there
709
+ if type == "servers" and orig_cfg.nil? and config.has_key?("server_pools")
710
+ config["server_pools"].each { |resource|
711
+ if resource["name"] == name
712
+ orig_cfg = resource
713
+ break
714
+ end
715
+ }
716
+ end
717
+
718
+ orig_cfg
719
+ end
720
+
721
+ end #class
722
+ end #module