cloud-mu 3.1.6 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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