cloud-mu 3.1.6 → 3.4.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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +15 -12
  4. data/bin/mu-azure-tests +57 -0
  5. data/bin/mu-cleanup +2 -4
  6. data/bin/mu-configure +37 -1
  7. data/bin/mu-deploy +3 -3
  8. data/bin/mu-findstray-tests +25 -0
  9. data/bin/mu-gen-docs +2 -4
  10. data/bin/mu-load-config.rb +2 -1
  11. data/bin/mu-run-tests +37 -12
  12. data/cloud-mu.gemspec +4 -4
  13. data/cookbooks/mu-tools/attributes/default.rb +7 -0
  14. data/cookbooks/mu-tools/libraries/helper.rb +87 -3
  15. data/cookbooks/mu-tools/recipes/apply_security.rb +39 -23
  16. data/cookbooks/mu-tools/recipes/aws_api.rb +13 -0
  17. data/cookbooks/mu-tools/recipes/google_api.rb +4 -0
  18. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  19. data/cookbooks/mu-tools/resources/disk.rb +33 -12
  20. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  21. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  22. data/extras/clean-stock-amis +10 -2
  23. data/extras/generate-stock-images +7 -3
  24. data/extras/image-generators/AWS/centos7.yaml +19 -16
  25. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  26. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  27. data/modules/mommacat.ru +2 -2
  28. data/modules/mu.rb +84 -97
  29. data/modules/mu/adoption.rb +359 -59
  30. data/modules/mu/cleanup.rb +67 -44
  31. data/modules/mu/cloud.rb +108 -1754
  32. data/modules/mu/cloud/database.rb +49 -0
  33. data/modules/mu/cloud/dnszone.rb +44 -0
  34. data/modules/mu/cloud/machine_images.rb +212 -0
  35. data/modules/mu/cloud/providers.rb +81 -0
  36. data/modules/mu/cloud/resource_base.rb +929 -0
  37. data/modules/mu/cloud/server.rb +40 -0
  38. data/modules/mu/cloud/server_pool.rb +1 -0
  39. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  40. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  41. data/modules/mu/cloud/wrappers.rb +178 -0
  42. data/modules/mu/config.rb +122 -80
  43. data/modules/mu/config/alarm.rb +2 -6
  44. data/modules/mu/config/bucket.rb +32 -3
  45. data/modules/mu/config/cache_cluster.rb +2 -2
  46. data/modules/mu/config/cdn.rb +100 -0
  47. data/modules/mu/config/collection.rb +1 -1
  48. data/modules/mu/config/container_cluster.rb +2 -2
  49. data/modules/mu/config/database.rb +84 -105
  50. data/modules/mu/config/database.yml +1 -2
  51. data/modules/mu/config/dnszone.rb +5 -4
  52. data/modules/mu/config/doc_helpers.rb +4 -5
  53. data/modules/mu/config/endpoint.rb +2 -1
  54. data/modules/mu/config/firewall_rule.rb +3 -19
  55. data/modules/mu/config/folder.rb +1 -1
  56. data/modules/mu/config/function.rb +17 -8
  57. data/modules/mu/config/group.rb +1 -1
  58. data/modules/mu/config/habitat.rb +1 -1
  59. data/modules/mu/config/job.rb +89 -0
  60. data/modules/mu/config/loadbalancer.rb +57 -11
  61. data/modules/mu/config/log.rb +1 -1
  62. data/modules/mu/config/msg_queue.rb +1 -1
  63. data/modules/mu/config/nosqldb.rb +1 -1
  64. data/modules/mu/config/notifier.rb +8 -19
  65. data/modules/mu/config/ref.rb +81 -9
  66. data/modules/mu/config/role.rb +1 -1
  67. data/modules/mu/config/schema_helpers.rb +30 -34
  68. data/modules/mu/config/search_domain.rb +1 -1
  69. data/modules/mu/config/server.rb +5 -13
  70. data/modules/mu/config/server_pool.rb +3 -7
  71. data/modules/mu/config/storage_pool.rb +1 -1
  72. data/modules/mu/config/tail.rb +10 -0
  73. data/modules/mu/config/user.rb +1 -1
  74. data/modules/mu/config/vpc.rb +13 -17
  75. data/modules/mu/defaults/AWS.yaml +106 -106
  76. data/modules/mu/defaults/Azure.yaml +1 -0
  77. data/modules/mu/defaults/Google.yaml +1 -0
  78. data/modules/mu/deploy.rb +33 -19
  79. data/modules/mu/groomer.rb +15 -0
  80. data/modules/mu/groomers/chef.rb +3 -0
  81. data/modules/mu/logger.rb +120 -144
  82. data/modules/mu/master.rb +22 -1
  83. data/modules/mu/mommacat.rb +71 -26
  84. data/modules/mu/mommacat/daemon.rb +23 -14
  85. data/modules/mu/mommacat/naming.rb +82 -3
  86. data/modules/mu/mommacat/search.rb +59 -16
  87. data/modules/mu/mommacat/storage.rb +119 -48
  88. data/modules/mu/{clouds → providers}/README.md +1 -1
  89. data/modules/mu/{clouds → providers}/aws.rb +248 -62
  90. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  91. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  92. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  93. data/modules/mu/providers/aws/cdn.rb +782 -0
  94. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  95. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +65 -63
  96. data/modules/mu/providers/aws/database.rb +1747 -0
  97. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  98. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  99. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  100. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  101. data/modules/mu/{clouds → providers}/aws/function.rb +291 -133
  102. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  103. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  104. data/modules/mu/providers/aws/job.rb +469 -0
  105. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  106. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  107. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  108. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  109. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  110. data/modules/mu/{clouds → providers}/aws/role.rb +112 -78
  111. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  112. data/modules/mu/{clouds → providers}/aws/server.rb +120 -145
  113. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  114. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  115. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  116. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  117. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  118. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  119. data/modules/mu/{clouds → providers}/aws/vpc.rb +141 -73
  120. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  121. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  122. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  123. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  124. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  126. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  127. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  128. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  129. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  130. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  132. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  133. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  134. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  135. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  136. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  137. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  138. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  142. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  143. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  144. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  145. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  146. data/modules/mu/{clouds → providers}/google.rb +15 -6
  147. data/modules/mu/{clouds → providers}/google/bucket.rb +2 -2
  148. data/modules/mu/{clouds → providers}/google/container_cluster.rb +29 -14
  149. data/modules/mu/{clouds → providers}/google/database.rb +2 -9
  150. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +3 -3
  151. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  152. data/modules/mu/{clouds → providers}/google/function.rb +4 -4
  153. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  154. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  155. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +2 -2
  156. data/modules/mu/{clouds → providers}/google/role.rb +46 -35
  157. data/modules/mu/{clouds → providers}/google/server.rb +26 -11
  158. data/modules/mu/{clouds → providers}/google/server_pool.rb +11 -11
  159. data/modules/mu/{clouds → providers}/google/user.rb +32 -22
  160. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  161. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  162. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  163. data/modules/mu/{clouds → providers}/google/vpc.rb +38 -3
  164. data/modules/tests/aws-jobs-functions.yaml +46 -0
  165. data/modules/tests/centos6.yaml +15 -0
  166. data/modules/tests/centos7.yaml +15 -0
  167. data/modules/tests/centos8.yaml +12 -0
  168. data/modules/tests/ecs.yaml +2 -2
  169. data/modules/tests/eks.yaml +1 -1
  170. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  171. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  172. data/modules/tests/microservice_app.yaml +288 -0
  173. data/modules/tests/rds.yaml +108 -0
  174. data/modules/tests/regrooms/rds.yaml +123 -0
  175. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  176. data/modules/tests/super_complex_bok.yml +2 -2
  177. data/modules/tests/super_simple_bok.yml +2 -2
  178. data/spec/mu/clouds/azure_spec.rb +2 -2
  179. metadata +126 -98
  180. data/modules/mu/clouds/aws/database.rb +0 -1974
  181. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -33,7 +33,7 @@ module MU
33
33
  my_key = OpenSSL::PKey::RSA.new(@private_key)
34
34
 
35
35
  begin
36
- if my_key.private_decrypt(ciphertext).force_encoding("UTF-8") == @deploy_secret.force_encoding("UTF-8")
36
+ if my_key.private_decrypt(ciphertext).force_encoding("UTF-8").chomp == @deploy_secret.force_encoding("UTF-8").chomp
37
37
  MU.log "Matched ciphertext for #{MU.deploy_id}", MU::INFO
38
38
  return true
39
39
  else
@@ -165,11 +165,11 @@ module MU
165
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
166
  MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
167
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
- )
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
173
  raise e if reraise_fail
174
174
  else
175
175
  MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
@@ -288,8 +288,8 @@ module MU
288
288
 
289
289
  # Path to the PID file used by the Momma Cat daemon
290
290
  # @return [String]
291
- def self.daemonPidFile
292
- base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
291
+ def self.daemonPidFile(root = false)
292
+ base = ((Process.uid == 0 or root) and !MU.localOnly) ? "/var" : MU.dataDir
293
293
  "#{base}/run/mommacat.pid"
294
294
  end
295
295
 
@@ -306,8 +306,14 @@ module MU
306
306
  Dir.mkdir(dir)
307
307
  end
308
308
  }
309
- return 0 if status
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
310
315
 
316
+ File.unlink(daemonPidFile) if File.exists?(daemonPidFile)
311
317
  MU.log "Starting Momma Cat on port #{MU.mommaCatPort}, logging to #{daemonLogFile}, PID file #{daemonPidFile}"
312
318
  origdir = Dir.getwd
313
319
  Dir.chdir(MU.myRoot+"/modules")
@@ -342,22 +348,25 @@ module MU
342
348
  return $?.exitstatus
343
349
  end
344
350
 
351
+ @@notified_on_pid = {}
352
+
345
353
  # Return true if the Momma Cat daemon appears to be running
346
354
  # @return [Boolean]
347
- def self.status
355
+ def self.status(root = false)
348
356
  if MU.inGem? and MU.muCfg['disable_mommacat']
349
357
  return true
350
358
  end
351
- if File.exist?(daemonPidFile)
352
- pid = File.read(daemonPidFile).chomp.to_i
359
+ if File.exist?(daemonPidFile(root))
360
+ pid = File.read(daemonPidFile(root)).chomp.to_i
353
361
  begin
354
362
  Process.getpgid(pid)
355
- MU.log "Momma Cat running with pid #{pid.to_s}"
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
356
365
  return true
357
366
  rescue Errno::ESRCH
358
367
  end
359
368
  end
360
- MU.log "Momma Cat daemon not running", MU::NOTICE, details: daemonPidFile
369
+ MU.log "Momma Cat daemon not running", MU::NOTICE, details: daemonPidFile(root)
361
370
  false
362
371
  end
363
372
 
@@ -19,6 +19,16 @@ module MU
19
19
  # the normal synchronous deploy sequence invoked by *mu-deploy*.
20
20
  class MommaCat
21
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
+
22
32
  # Given a cloud provider's native descriptor for a resource, make some
23
33
  # reasonable guesses about what the thing's name should be.
24
34
  def self.guessName(desc, resourceclass, cloud_id: nil, tag_value: nil)
@@ -47,6 +57,74 @@ module MU
47
57
 
48
58
  end
49
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
+
50
128
  # Generate a three-character string which can be used to unique-ify the
51
129
  # names of resources which might potentially collide, e.g. Windows local
52
130
  # hostnames, Amazon Elastic Load Balancers, or server pool instances.
@@ -218,17 +296,18 @@ module MU
218
296
  # SSH config entries, etc.
219
297
  # @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
220
298
  # @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
221
- def self.nameKitten(server, sync_wait: false)
299
+ def self.nameKitten(server, sync_wait: false, no_dns: false)
222
300
  node, config, _deploydata = server.describe
223
301
 
224
302
  mu_zone = nil
225
303
  # XXX GCP!
226
- if MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud?
304
+ if !no_dns and MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud?
227
305
  zones = MU::Cloud::DNSZone.find(cloud_id: "platform-mu")
228
306
  mu_zone = zones.values.first if !zones.nil?
229
307
  end
308
+
230
309
  if !mu_zone.nil?
231
- MU::Cloud::DNSZone.genericMuDNSEntry(name: node, target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
310
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: node.gsub(/[^a-z0-9!"\#$%&'\(\)\*\+,\-\/:;<=>\?@\[\]\^_`{\|}~\.]/, '-').gsub(/--|^-/, ''), target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
232
311
  else
233
312
  MU::Master.addInstanceToEtcHosts(server.canonicalIP, node)
234
313
  end
@@ -60,7 +60,7 @@ module MU
60
60
  )
61
61
  _shortclass, _cfg_name, type, _classname, _attrs = MU::Cloud.getResourceNames(type, true)
62
62
 
63
- cloudclass = MU::Cloud.assertSupportedCloud(cloud)
63
+ cloudclass = MU::Cloud.cloudClass(cloud)
64
64
  return nil if cloudclass.virtual?
65
65
 
66
66
  if (tag_key and !tag_value) or (!tag_key and tag_value)
@@ -107,7 +107,17 @@ module MU
107
107
  matches = []
108
108
 
109
109
  credlist.each { |creds|
110
- cloud_descs = search_cloud_provider(type, cloud, habitats, region, cloud_id: cloud_id, tag_key: tag_key, tag_value: tag_value, credentials: creds, flags: flags)
110
+ cur_habitats = []
111
+
112
+ if habitats and !habitats.empty? and habitats != [nil]
113
+ valid_habitats = cloudclass.listHabitats(creds)
114
+ cur_habitats = (habitats & valid_habitats)
115
+ next if cur_habitats.empty?
116
+ else
117
+ cur_habitats = cloudclass.listHabitats(creds)
118
+ end
119
+
120
+ cloud_descs = search_cloud_provider(type, cloud, cur_habitats, region, cloud_id: cloud_id, tag_key: tag_key, tag_value: tag_value, credentials: creds, flags: flags)
111
121
 
112
122
  cloud_descs.each_pair.each { |p, regions|
113
123
  regions.each_pair.each { |r, results|
@@ -127,6 +137,8 @@ module MU
127
137
  matches
128
138
  end
129
139
 
140
+ @object_load_fails = false
141
+
130
142
  # Return the resource object of another member of this deployment
131
143
  # @param type [String,Symbol]: The type of resource
132
144
  # @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field
@@ -135,7 +147,7 @@ module MU
135
147
  # @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
136
148
  # @param return_all [Boolean]: Return a Hash of matching objects indexed by their mu_name, instead of a single match. Only valid for resource types where has_multiples is true.
137
149
  # @return [MU::Cloud]
138
- def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, **flags)
150
+ def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, ignore_missing: false, debug: false, **flags)
139
151
  _shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
140
152
 
141
153
  # If we specified a habitat, which we may also have done by its shorthand
@@ -159,17 +171,35 @@ module MU
159
171
  }
160
172
 
161
173
  @kitten_semaphore.synchronize {
162
- return nil if !@kittens.has_key?(type)
163
- matches = []
164
174
 
175
+ if !@kittens.has_key?(type)
176
+ return nil if !@original_config or @original_config[type].nil? or @original_config[type].empty?
177
+ begin
178
+ loadObjects(false)
179
+ rescue ThreadError => e
180
+ if e.message !~ /deadlock/
181
+ raise e
182
+ end
183
+ end
184
+ if @object_load_fails or !@kittens[type]
185
+ if !ignore_missing
186
+ MU.log "#{@deploy_id}'s original config has #{@original_config[type].size == 1 ? "a" : @original_config[type].size.to_s} #{type}, but loadObjects could not populate anything from deployment metadata", MU::ERR if !@object_load_fails
187
+ @object_load_fails = true
188
+ end
189
+ return nil
190
+ end
191
+ end
192
+ matches = {}
165
193
  @kittens[type].each { |habitat_group, sib_classes|
166
194
  next if habitat and habitat_group and habitat_group != habitat
167
195
  sib_classes.each_pair { |sib_class, cloud_objs|
196
+
168
197
  if attrs[:has_multiples]
169
198
  next if !name.nil? and name != sib_class or cloud_objs.empty?
170
199
  if !name.nil?
171
200
  if return_all
172
- return cloud_objs.dup
201
+ matches.merge!(cloud_objs.clone)
202
+ next
173
203
  elsif cloud_objs.size == 1 and does_match.call(cloud_objs.values.first)
174
204
  return cloud_objs.values.first
175
205
  end
@@ -177,19 +207,24 @@ module MU
177
207
 
178
208
  cloud_objs.each_value { |obj|
179
209
  if does_match.call(obj)
180
- return (return_all ? cloud_objs.clone : obj.clone)
210
+ if return_all
211
+ matches.merge!(cloud_objs.clone)
212
+ else
213
+ return obj.clone
214
+ end
181
215
  end
182
216
  }
183
- # has_multiples is false
217
+ # has_multiples is false, "cloud_objs" is actually a singular object
184
218
  elsif (name.nil? and does_match.call(cloud_objs)) or [sib_class, cloud_objs.virtual_name(name)].include?(name.to_s)
185
- matches << cloud_objs.clone
219
+ matches[cloud_objs.config['name']] = cloud_objs.clone
186
220
  end
187
221
  }
188
222
  }
189
223
 
190
- return matches.first if matches.size == 1
224
+ return matches if return_all and matches.size >= 1
225
+
226
+ return matches.values.first if matches.size == 1
191
227
 
192
- return matches if return_all and matches.size > 1
193
228
  }
194
229
 
195
230
  return nil
@@ -213,7 +248,7 @@ module MU
213
248
  end
214
249
 
215
250
  def self.generate_dummy_object(type, cloud, name, mu_name, cloud_id, desc, region, habitat, tag_value, calling_deploy, credentials)
216
- resourceclass = MU::Cloud.loadCloudType(cloud, type)
251
+ resourceclass = MU::Cloud.resourceClass(cloud, type)
217
252
 
218
253
  use_name = if (name.nil? or name.empty?)
219
254
  if !mu_name.nil?
@@ -269,15 +304,23 @@ module MU
269
304
  private_class_method :generate_dummy_object
270
305
 
271
306
  def self.search_cloud_provider(type, cloud, habitats, region, cloud_id: nil, tag_key: nil, tag_value: nil, credentials: nil, flags: nil)
272
- cloudclass = MU::Cloud.assertSupportedCloud(cloud)
273
- resourceclass = MU::Cloud.loadCloudType(cloud, type)
307
+ cloudclass = MU::Cloud.cloudClass(cloud)
308
+ resourceclass = MU::Cloud.resourceClass(cloud, type)
274
309
 
275
310
  # Decide what regions we'll search, if applicable for this resource
276
311
  # type.
277
312
  regions = if resourceclass.isGlobal?
278
313
  [nil]
279
314
  else
280
- region ? [region] : cloudclass.listRegions(credentials: credentials)
315
+ if region
316
+ if region.is_a?(Array) and !region.empty?
317
+ region
318
+ else
319
+ [region]
320
+ end
321
+ else
322
+ cloudclass.listRegions(credentials: credentials)
323
+ end
281
324
  end
282
325
 
283
326
  # Decide what habitats (accounts/projects/subscriptions) we'll
@@ -288,7 +331,7 @@ module MU
288
331
  habitats << nil
289
332
  end
290
333
  if resourceclass.canLiveIn.include?(:Habitat)
291
- habitats.concat(cloudclass.listHabitats(credentials))
334
+ habitats.concat(cloudclass.listHabitats(credentials, use_cache: false))
292
335
  end
293
336
  end
294
337
  habitats << nil if habitats.empty?
@@ -88,16 +88,28 @@ module MU
88
88
  }
89
89
  end
90
90
 
91
+
91
92
  # Overwrite this deployment's configuration with a new version. Save the
92
93
  # previous version as well.
93
94
  # @param new_conf [Hash]: A new configuration, fully resolved by {MU::Config}
94
- def updateBasketofKittens(new_conf)
95
+ def updateBasketofKittens(new_conf, skip_validation: false, new_metadata: nil, save_now: false)
95
96
  loadDeploy
96
97
  if new_conf == @original_config
97
- MU.log "#{@deploy_id}", MU::WARN
98
98
  return
99
99
  end
100
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
+
101
113
  backup = "#{deploy_dir}/basket_of_kittens.json.#{Time.now.to_i.to_s}"
102
114
  MU.log "Saving previous config of #{@deploy_id} to #{backup}"
103
115
  config = File.new(backup, File::CREAT|File::TRUNC|File::RDWR, 0600)
@@ -106,9 +118,27 @@ module MU
106
118
  config.flock(File::LOCK_UN)
107
119
  config.close
108
120
 
109
- @original_config = new_conf
110
- # save! # XXX this will happen later, more sensibly
111
- MU.log "New config saved to #{deploy_dir}/basket_of_kittens.json"
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
+ # existing_deploys
127
+ @deployment[attrs[:cfg_plural]].each_pair { |res_name, data|
128
+ orig_cfg = findResourceConfig(attrs[:cfg_plural], res_name, (scrub_with || @original_config))
129
+
130
+ if orig_cfg.nil? and (!data['mu_name'] or data['mu_name'] =~ /^#{Regexp.quote(@deploy_id)}/)
131
+ MU.log "#{res_type} #{res_name} no longer configured, will remove deployment metadata", MU::NOTICE, details: data
132
+ deletia << res_name
133
+ end
134
+ }
135
+ @deployment[attrs[:cfg_plural]].reject! { |k, v| deletia.include?(k) }
136
+ }
137
+
138
+ if save_now
139
+ save!
140
+ MU.log "New config saved to #{deploy_dir}/basket_of_kittens.json"
141
+ end
112
142
  end
113
143
 
114
144
  @lock_semaphore = Mutex.new
@@ -147,11 +177,11 @@ module MU
147
177
  # @param id [String]: The lock identifier to release.
148
178
  # @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.
149
179
  # return [false, nil]
150
- def self.lock(id, nonblock = false, global = false)
180
+ def self.lock(id, nonblock = false, global = false, retries: 0, deploy_id: MU.deploy_id)
151
181
  raise MuError, "Can't pass a nil id to MU::MommaCat.lock" if id.nil?
152
182
 
153
183
  if !global
154
- lockdir = "#{deploy_dir(MU.deploy_id)}/locks"
184
+ lockdir = "#{deploy_dir(deploy_id)}/locks"
155
185
  else
156
186
  lockdir = File.expand_path(MU.dataDir+"/locks")
157
187
  end
@@ -160,6 +190,7 @@ module MU
160
190
  MU.log "Creating #{lockdir}", MU::DEBUG
161
191
  Dir.mkdir(lockdir, 0700)
162
192
  end
193
+ nonblock = true if retries > 0
163
194
 
164
195
  @lock_semaphore.synchronize {
165
196
  if @locks[Thread.current.object_id].nil?
@@ -168,11 +199,38 @@ module MU
168
199
 
169
200
  @locks[Thread.current.object_id][id] = File.open("#{lockdir}/#{id}.lock", File::CREAT|File::RDWR, 0600)
170
201
  }
171
- MU.log "Getting a lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})...", MU::DEBUG
202
+
203
+ MU.log "Getting a lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})...", MU::DEBUG, details: caller
204
+ show_relevant = Proc.new {
205
+ @lock_semaphore.synchronize {
206
+ @locks.each_pair { |thread_id, lock|
207
+ lock.each_pair { |lockid, lockpath|
208
+ if lockid == id
209
+ thread = Thread.list.select { |t| t.object_id == thread_id }.first
210
+ if thread.object_id != Thread.current.object_id
211
+ MU.log "#{thread_id} sitting on #{id}", MU::WARN, thread.backtrace
212
+ end
213
+ end
214
+ }
215
+ }
216
+ }
217
+ }
172
218
  begin
173
219
  if nonblock
174
220
  if !@locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
175
- return false
221
+ if retries > 0
222
+ success = false
223
+ MU.retrier([], loop_if: Proc.new { !success }, loop_msg: "Waiting for lock on #{lockdir}/#{id}.lock...", max: retries) { |cur_retries, _wait|
224
+ success = @locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
225
+ if !success and cur_retries > 0 and (cur_retries % 3) == 0
226
+ show_relevant.call(cur_retries)
227
+ end
228
+ }
229
+ show_relevant.call(cur_retries) if !success
230
+ return success
231
+ else
232
+ return false
233
+ end
176
234
  end
177
235
  else
178
236
  @locks[Thread.current.object_id][id].flock(File::LOCK_EX)
@@ -186,11 +244,11 @@ module MU
186
244
 
187
245
  # Release a flock() lock.
188
246
  # @param id [String]: The lock identifier to release.
189
- def self.unlock(id, global = false)
247
+ def self.unlock(id, global = false, deploy_id: MU.deploy_id)
190
248
  raise MuError, "Can't pass a nil id to MU::MommaCat.unlock" if id.nil?
191
249
  lockdir = nil
192
250
  if !global
193
- lockdir = "#{deploy_dir(MU.deploy_id)}/locks"
251
+ lockdir = "#{deploy_dir(deploy_id)}/locks"
194
252
  else
195
253
  lockdir = File.expand_path(MU.dataDir+"/locks")
196
254
  end
@@ -361,6 +419,7 @@ module MU
361
419
  deploy.flock(File::LOCK_UN)
362
420
  deploy.close
363
421
  @need_deploy_flush = false
422
+ @last_modified = nil
364
423
  MU::MommaCat.updateLitter(@deploy_id, self)
365
424
  end
366
425
 
@@ -512,48 +571,30 @@ module MU
512
571
  if !@original_config['scrub_mu_isms'] and !@no_artifacts
513
572
  credsets.each_pair { |cloud, creds|
514
573
  creds.uniq!
515
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
516
574
  creds.each { |credentials|
517
- cloudclass.writeDeploySecret(@deploy_id, @deploy_secret, credentials: credentials)
575
+ MU::Cloud.cloudClass(cloud).writeDeploySecret(@deploy_id, @deploy_secret, credentials: credentials)
518
576
  }
519
577
  }
520
578
  end
521
579
  end
522
580
 
523
581
  def loadObjects(delay_descriptor_load)
582
+ # Load up MU::Cloud objects for all our kittens in this deploy
583
+
524
584
  MU::Cloud.resource_types.each_pair { |res_type, attrs|
525
585
  type = attrs[:cfg_plural]
526
586
  next if !@deployment.has_key?(type)
527
587
 
588
+ deletia = {}
528
589
  @deployment[type].each_pair { |res_name, data|
529
- orig_cfg = nil
530
- if @original_config.has_key?(type)
531
- @original_config[type].each { |resource|
532
- if resource["name"] == res_name
533
- orig_cfg = resource
534
- break
535
- end
536
- }
537
- end
538
-
539
- # Some Server objects originated from ServerPools, get their
540
- # configs from there
541
- if type == "servers" and orig_cfg.nil? and
542
- @original_config.has_key?("server_pools")
543
- @original_config["server_pools"].each { |resource|
544
- if resource["name"] == res_name
545
- orig_cfg = resource
546
- break
547
- end
548
- }
549
- end
590
+ orig_cfg = findResourceConfig(type, res_name)
550
591
 
551
592
  if orig_cfg.nil?
552
593
  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
553
594
  next
554
595
  end
555
596
 
556
- if orig_cfg['vpc'] and orig_cfg['vpc'].is_a?(Hash)
597
+ if orig_cfg['vpc']
557
598
  ref = if orig_cfg['vpc']['id'] and orig_cfg['vpc']['id'].is_a?(Hash)
558
599
  orig_cfg['vpc']['id']['mommacat'] = self
559
600
  MU::Config::Ref.get(orig_cfg['vpc']['id'])
@@ -566,18 +607,12 @@ module MU
566
607
  end
567
608
 
568
609
  begin
569
- # Load up MU::Cloud objects for all our kittens in this deploy
570
- orig_cfg['environment'] = @environment # not always set in old deploys
571
610
  if attrs[:has_multiples]
572
611
  data.keys.each { |mu_name|
573
- attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name, delay_descriptor_load: delay_descriptor_load)
612
+ addKitten(type, res_name, attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name, delay_descriptor_load: delay_descriptor_load))
574
613
  }
575
614
  else
576
- # XXX hack for old deployments, this can go away some day
577
- if data['mu_name'].nil?
578
- raise MuError, "Unable to find or guess a Mu name for #{res_type}: #{res_name} in #{@deploy_id}"
579
- end
580
- attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id'])
615
+ addKitten(type, res_name, attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id']))
581
616
  end
582
617
  rescue StandardError => e
583
618
  if e.class != MU::Cloud::MuCloudResourceNotImplemented
@@ -585,6 +620,7 @@ module MU
585
620
  end
586
621
  end
587
622
  }
623
+
588
624
  }
589
625
  end
590
626
 
@@ -612,6 +648,14 @@ module MU
612
648
  def loadDeployFromCache(set_context_to_me = true)
613
649
  return false if !File.size?(deploy_dir+"/deployment.json")
614
650
 
651
+ lastmod = File.mtime("#{deploy_dir}/deployment.json")
652
+ if @last_modified and lastmod < @last_modified
653
+ MU.log "#{deploy_dir}/deployment.json last written at #{lastmod}, live meta at #{@last_modified}, not loading", MU::WARN if @last_modified
654
+ # this is a weird place for this
655
+ setThreadContextToMe if set_context_to_me
656
+ return true
657
+ end
658
+
615
659
  deploy = File.open("#{deploy_dir}/deployment.json", File::RDONLY)
616
660
  MU.log "Getting lock to read #{deploy_dir}/deployment.json", MU::DEBUG
617
661
  # deploy.flock(File::LOCK_EX)
@@ -623,6 +667,7 @@ module MU
623
667
 
624
668
  begin
625
669
  @deployment = JSON.parse(File.read("#{deploy_dir}/deployment.json"))
670
+ # XXX is it worthwhile to merge fuckery?
626
671
  rescue JSON::ParserError => e
627
672
  MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR, details: e.message
628
673
  end
@@ -632,20 +677,21 @@ module MU
632
677
 
633
678
  setThreadContextToMe if set_context_to_me
634
679
 
635
- @timestamp = @deployment['timestamp']
636
- @seed = @deployment['seed']
637
- @appname = @deployment['appname']
638
- @handle = @deployment['handle']
639
-
640
680
  true
641
681
  end
642
682
 
683
+
643
684
  ###########################################################################
644
685
  ###########################################################################
645
686
  def loadDeploy(deployment_json_only = false, set_context_to_me: true)
646
687
  MU::MommaCat.deploy_struct_semaphore.synchronize {
647
688
  success = loadDeployFromCache(set_context_to_me)
648
689
 
690
+ @timestamp ||= @deployment['timestamp']
691
+ @seed ||= @deployment['seed']
692
+ @appname ||= @deployment['appname']
693
+ @handle ||= @deployment['handle']
694
+
649
695
  return if deployment_json_only and success
650
696
 
651
697
  if File.exist?(deploy_dir+"/private_key")
@@ -687,5 +733,30 @@ module MU
687
733
  }
688
734
  end
689
735
 
736
+ def findResourceConfig(type, name, config = @original_config)
737
+ orig_cfg = nil
738
+ if config.has_key?(type)
739
+ config[type].each { |resource|
740
+ if resource["name"] == name
741
+ orig_cfg = resource
742
+ break
743
+ end
744
+ }
745
+ end
746
+
747
+ # Some Server objects originated from ServerPools, get their
748
+ # configs from there
749
+ if type == "servers" and orig_cfg.nil? and config.has_key?("server_pools")
750
+ config["server_pools"].each { |resource|
751
+ if resource["name"] == name
752
+ orig_cfg = resource
753
+ break
754
+ end
755
+ }
756
+ end
757
+
758
+ orig_cfg
759
+ end
760
+
690
761
  end #class
691
762
  end #module