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,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