cloud-mu 3.1.5 → 3.3.2

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  4. data/ansible/roles/mu-windows/files/config.xml +76 -0
  5. data/ansible/roles/mu-windows/tasks/main.yml +16 -0
  6. data/bin/mu-adopt +16 -12
  7. data/bin/mu-azure-tests +57 -0
  8. data/bin/mu-cleanup +2 -4
  9. data/bin/mu-configure +52 -0
  10. data/bin/mu-deploy +3 -3
  11. data/bin/mu-findstray-tests +25 -0
  12. data/bin/mu-gen-docs +2 -4
  13. data/bin/mu-load-config.rb +2 -1
  14. data/bin/mu-node-manage +15 -16
  15. data/bin/mu-run-tests +37 -12
  16. data/cloud-mu.gemspec +3 -3
  17. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  18. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  19. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  20. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  21. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  22. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  23. data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
  24. data/extras/clean-stock-amis +25 -19
  25. data/extras/generate-stock-images +1 -0
  26. data/extras/image-generators/AWS/win2k12.yaml +2 -0
  27. data/extras/image-generators/AWS/win2k16.yaml +2 -0
  28. data/extras/image-generators/AWS/win2k19.yaml +2 -0
  29. data/modules/mommacat.ru +1 -1
  30. data/modules/mu.rb +86 -98
  31. data/modules/mu/adoption.rb +373 -58
  32. data/modules/mu/cleanup.rb +214 -303
  33. data/modules/mu/cloud.rb +128 -1733
  34. data/modules/mu/cloud/database.rb +49 -0
  35. data/modules/mu/cloud/dnszone.rb +44 -0
  36. data/modules/mu/cloud/machine_images.rb +212 -0
  37. data/modules/mu/cloud/providers.rb +81 -0
  38. data/modules/mu/cloud/resource_base.rb +929 -0
  39. data/modules/mu/cloud/server.rb +40 -0
  40. data/modules/mu/cloud/server_pool.rb +1 -0
  41. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  42. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  43. data/modules/mu/cloud/wrappers.rb +169 -0
  44. data/modules/mu/config.rb +123 -81
  45. data/modules/mu/config/alarm.rb +2 -6
  46. data/modules/mu/config/bucket.rb +32 -3
  47. data/modules/mu/config/cache_cluster.rb +2 -2
  48. data/modules/mu/config/cdn.rb +100 -0
  49. data/modules/mu/config/collection.rb +1 -1
  50. data/modules/mu/config/container_cluster.rb +7 -2
  51. data/modules/mu/config/database.rb +84 -105
  52. data/modules/mu/config/database.yml +1 -2
  53. data/modules/mu/config/dnszone.rb +5 -4
  54. data/modules/mu/config/doc_helpers.rb +5 -6
  55. data/modules/mu/config/endpoint.rb +2 -1
  56. data/modules/mu/config/firewall_rule.rb +3 -19
  57. data/modules/mu/config/folder.rb +1 -1
  58. data/modules/mu/config/function.rb +17 -8
  59. data/modules/mu/config/group.rb +1 -1
  60. data/modules/mu/config/habitat.rb +1 -1
  61. data/modules/mu/config/job.rb +89 -0
  62. data/modules/mu/config/loadbalancer.rb +57 -11
  63. data/modules/mu/config/log.rb +1 -1
  64. data/modules/mu/config/msg_queue.rb +1 -1
  65. data/modules/mu/config/nosqldb.rb +1 -1
  66. data/modules/mu/config/notifier.rb +8 -19
  67. data/modules/mu/config/ref.rb +92 -14
  68. data/modules/mu/config/role.rb +1 -1
  69. data/modules/mu/config/schema_helpers.rb +38 -37
  70. data/modules/mu/config/search_domain.rb +1 -1
  71. data/modules/mu/config/server.rb +12 -13
  72. data/modules/mu/config/server_pool.rb +3 -7
  73. data/modules/mu/config/storage_pool.rb +1 -1
  74. data/modules/mu/config/tail.rb +11 -0
  75. data/modules/mu/config/user.rb +1 -1
  76. data/modules/mu/config/vpc.rb +27 -23
  77. data/modules/mu/config/vpc.yml +0 -1
  78. data/modules/mu/defaults/AWS.yaml +90 -90
  79. data/modules/mu/defaults/Azure.yaml +1 -0
  80. data/modules/mu/defaults/Google.yaml +1 -0
  81. data/modules/mu/deploy.rb +34 -20
  82. data/modules/mu/groomer.rb +16 -1
  83. data/modules/mu/groomers/ansible.rb +69 -4
  84. data/modules/mu/groomers/chef.rb +51 -4
  85. data/modules/mu/logger.rb +120 -144
  86. data/modules/mu/master.rb +97 -4
  87. data/modules/mu/mommacat.rb +160 -874
  88. data/modules/mu/mommacat/daemon.rb +23 -14
  89. data/modules/mu/mommacat/naming.rb +110 -3
  90. data/modules/mu/mommacat/search.rb +497 -0
  91. data/modules/mu/mommacat/storage.rb +252 -194
  92. data/modules/mu/{clouds → providers}/README.md +1 -1
  93. data/modules/mu/{clouds → providers}/aws.rb +258 -57
  94. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  95. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  96. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  99. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +95 -84
  100. data/modules/mu/providers/aws/database.rb +1744 -0
  101. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  102. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  103. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  104. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  105. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  106. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  107. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  108. data/modules/mu/providers/aws/job.rb +466 -0
  109. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  110. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  111. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  112. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  113. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  114. data/modules/mu/{clouds → providers}/aws/role.rb +76 -48
  115. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  116. data/modules/mu/{clouds → providers}/aws/server.rb +66 -98
  117. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  118. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  119. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  120. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  121. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  122. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  123. data/modules/mu/{clouds → providers}/aws/vpc.rb +143 -74
  124. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  126. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  127. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  128. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  129. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  130. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  132. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  133. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  134. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  135. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  136. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  137. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  138. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  142. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  143. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  144. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  145. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  146. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  147. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  148. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  149. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  150. data/modules/mu/{clouds → providers}/google.rb +29 -6
  151. data/modules/mu/{clouds → providers}/google/bucket.rb +4 -4
  152. data/modules/mu/{clouds → providers}/google/container_cluster.rb +38 -20
  153. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  154. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  155. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  156. data/modules/mu/{clouds → providers}/google/function.rb +6 -6
  157. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  158. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  159. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  160. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  161. data/modules/mu/{clouds → providers}/google/server.rb +41 -24
  162. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  163. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  164. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  165. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  166. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  167. data/modules/mu/{clouds → providers}/google/vpc.rb +45 -14
  168. data/modules/tests/aws-jobs-functions.yaml +46 -0
  169. data/modules/tests/centos6.yaml +15 -0
  170. data/modules/tests/centos7.yaml +15 -0
  171. data/modules/tests/centos8.yaml +12 -0
  172. data/modules/tests/ecs.yaml +2 -2
  173. data/modules/tests/eks.yaml +1 -1
  174. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  175. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  176. data/modules/tests/microservice_app.yaml +288 -0
  177. data/modules/tests/rds.yaml +108 -0
  178. data/modules/tests/regrooms/rds.yaml +123 -0
  179. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  180. data/modules/tests/super_complex_bok.yml +2 -2
  181. data/modules/tests/super_simple_bok.yml +3 -5
  182. data/spec/mu/clouds/azure_spec.rb +2 -2
  183. metadata +122 -92
  184. data/modules/mu/clouds/aws/database.rb +0 -1974
  185. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -91,6 +91,7 @@ $opts[:clouds].each { |cloud|
91
91
  end
92
92
  next if !needed
93
93
  end
94
+ MU.log "Loading "+bok_dir+"/"+cloud+"/"+platform+".yaml"
94
95
  conf_engine = MU::Config.new(
95
96
  bok_dir+"/"+cloud+"/"+platform+".yaml",
96
97
  default_credentials: $opts[(cloud.downcase+"_creds").to_sym]
@@ -12,6 +12,8 @@
12
12
  groomer: Ansible
13
13
  run_list:
14
14
  - mu-windows
15
+ ansible_vars:
16
+ mu_build_image: true
15
17
  create_image:
16
18
  image_then_destroy: true
17
19
  public: true
@@ -12,6 +12,8 @@
12
12
  groomer: Ansible
13
13
  run_list:
14
14
  - mu-windows
15
+ ansible_vars:
16
+ mu_build_image: true
15
17
  create_image:
16
18
  image_then_destroy: true
17
19
  public: true
@@ -12,6 +12,8 @@
12
12
  groomer: Ansible
13
13
  run_list:
14
14
  - mu-windows
15
+ ansible_vars:
16
+ mu_build_image: true
15
17
  create_image:
16
18
  image_then_destroy: true
17
19
  public: true
@@ -51,7 +51,7 @@ Signal.trap("URG") do
51
51
  end
52
52
 
53
53
  begin
54
- MU::MommaCat.syncMonitoringConfig(false)
54
+ MU::Master.syncMonitoringConfig(false)
55
55
  rescue StandardError => e
56
56
  MU.log e.inspect, MU::ERR, details: e.backtrace
57
57
  # ...but don't die!
@@ -79,38 +79,40 @@ class Hash
79
79
  }
80
80
  return 0 if self == other # that was easy!
81
81
  # compare elements and decide who's "bigger" based on their totals?
82
- 0
82
+
83
+ # fine, try some brute force and just hope everything implements to_s
84
+ self.flatten.map { |e| e.to_s }.join() <=> other.flatten.map { |e| e.to_s }.join()
83
85
  end
84
86
 
85
- # Recursively compare two hashes
86
- def diff(with, on = self, level: 0, parents: [])
87
+ # Recursively compare two Mu Basket of Kittens hashes and report the differences
88
+ def diff(with, on = self, level: 0, parents: [], report: {}, habitat: nil)
87
89
  return if with.nil? and on.nil?
88
90
  if with.nil? or on.nil? or with.class != on.class
89
91
  return # XXX ...however we're flagging differences
90
92
  end
91
93
  return if on == with
92
94
 
93
- tree = ""
94
- indentsize = 0
95
- parents.each { |p|
96
- tree += (" " * indentsize) + p + " => \n"
97
- indentsize += 2
98
- }
99
- indent = (" " * indentsize)
100
-
101
95
  changes = []
96
+ report ||= {}
102
97
  if on.is_a?(Hash)
103
98
  on_unique = (on.keys - with.keys)
104
99
  with_unique = (with.keys - on.keys)
105
100
  shared = (with.keys & on.keys)
106
101
  shared.each { |k|
107
- diff(with[k], on[k], level: level+1, parents: parents + [k])
102
+
103
+ report_data = diff(with[k], on[k], level: level+1, parents: parents + [k], report: report[k], habitat: habitat)
104
+ if report_data and !report_data.empty?
105
+ report ||= {}
106
+ report[k] = report_data
107
+ end
108
108
  }
109
109
  on_unique.each { |k|
110
- changes << "- ".red+PP.pp({k => on[k] }, '')
110
+ report[k] = { :action => :removed, :parents => parents, :value => on[k].clone }
111
+ report[k][:habitat] = habitat if habitat
111
112
  }
112
113
  with_unique.each { |k|
113
- changes << "+ ".green+PP.pp({k => with[k]}, '')
114
+ report[k] = { :action => :added, :parents => parents, :value => with[k].clone }
115
+ report[k][:habitat] = habitat if habitat
114
116
  }
115
117
  elsif on.is_a?(Array)
116
118
  return if with == on
@@ -122,29 +124,27 @@ class Hash
122
124
  # sorting arrays full of weird, non-primitive types.
123
125
  done = []
124
126
  on.sort.each { |elt|
125
- if elt.is_a?(Hash) and elt['name'] or elt['entity']# or elt['cloud_id']
126
- with.sort.each { |other_elt|
127
- # Figure out what convention this thing is using for resource identification
128
- compare_a, compare_b = if elt['name'].nil? and elt["id"].nil? and !elt["entity"].nil? and !other_elt["entity"].nil?
129
- [elt["entity"], other_elt["entity"]]
130
- else
131
- [elt, other_elt]
132
- end
127
+ if elt.is_a?(Hash) and !MU::MommaCat.getChunkName(elt).first.nil?
128
+ elt_namestr, elt_location, elt_location_list = MU::MommaCat.getChunkName(elt)
133
129
 
134
- if (compare_a['name'] and compare_b['name'] == compare_a['name']) or
135
- (compare_a['name'].nil? and !compare_a["id"].nil? and compare_a["id"] == compare_b["id"])
136
- break if elt == other_elt
130
+ with.sort.each { |other_elt|
131
+ other_elt_namestr, other_elt_location, other_elt_location_list = MU::MommaCat.getChunkName(other_elt)
132
+
133
+ # Case 1: The array element exists in both version of this array
134
+ if elt_namestr and other_elt_namestr and
135
+ elt_namestr == other_elt_namestr and
136
+ (elt_location.nil? or other_elt_location.nil? or
137
+ elt_location == other_elt_location or
138
+ !(elt_location_list & other_elt_location_list).empty?
139
+ )
137
140
  done << elt
138
141
  done << other_elt
139
- namestr = if elt['type']
140
- "#{elt['type']}[#{elt['name']}]"
141
- elsif elt['name']
142
- elt['name']
143
- elsif elt['entity'] and elt["entity"]["id"]
144
- elt['entity']['id']
142
+ break if elt == other_elt # if they're identical, we're done
143
+ report_data = diff(other_elt, elt, level: level+1, parents: parents + [elt_namestr], habitat: (elt_location || habitat))
144
+ if report_data and !report_data.empty?
145
+ report ||= {}
146
+ report[elt_namestr] = report_data
145
147
  end
146
-
147
- diff(other_elt, elt, level: level+1, parents: parents + [namestr])
148
148
  break
149
149
  end
150
150
  }
@@ -152,43 +152,34 @@ class Hash
152
152
  }
153
153
  on_unique = (on - with) - done
154
154
  with_unique = (with - on) - done
155
- # if on_unique.size > 0 or with_unique.size > 0
156
- # if before_a != after_a
157
- # MU.log "A BEFORE", MU::NOTICE, details: before_a
158
- # MU.log "A AFTER", MU::NOTICE, details: after_a
159
- # end
160
- # if before_b != after_b
161
- # MU.log "B BEFORE", MU::NOTICE, details: before_b
162
- # MU.log "B AFTER", MU::NOTICE, details: after_b
163
- # end
164
- # end
155
+
156
+ # Case 2: This array entry exists in the old version, but not the new one
165
157
  on_unique.each { |e|
166
- changes << if e.is_a?(Hash)
167
- "- ".red+PP.pp(Hash.bok_minimize(e), '').gsub(/\n/, "\n "+(indent))
168
- else
169
- "- ".red+e.to_s
170
- end
158
+ namestr, loc = MU::MommaCat.getChunkName(e)
159
+
160
+ report ||= {}
161
+ report[namestr] = { :action => :removed, :parents => parents, :value => e.clone }
162
+ report[namestr][:habitat] = loc if loc
171
163
  }
164
+
165
+ # Case 3: This array entry exists in the new version, but not the old one
172
166
  with_unique.each { |e|
173
- changes << if e.is_a?(Hash)
174
- "+ ".green+PP.pp(Hash.bok_minimize(e), '').gsub(/\n/, "\n "+(indent))
175
- else
176
- "+ ".green+e.to_s
177
- end
167
+ namestr, loc = MU::MommaCat.getChunkName(e)
168
+
169
+ report ||= {}
170
+ report[namestr] = { :action => :added, :parents => parents, :value => e.clone }
171
+ report[namestr][:habitat] = loc if loc
178
172
  }
173
+
174
+ # A plain old leaf node of data
179
175
  else
180
176
  if on != with
181
- changes << "-".red+" #{on.to_s}"
182
- changes << "+".green+" #{with.to_s}"
177
+ report = { :action => :changed, :parents => parents, :oldvalue => on, :value => with.clone }
178
+ report[:habitat] = habitat if habitat
183
179
  end
184
180
  end
185
181
 
186
- if changes.size > 0
187
- puts tree
188
- changes.each { |c|
189
- puts indent+c
190
- }
191
- end
182
+ report.freeze
192
183
  end
193
184
 
194
185
  # Implement a merge! that just updates each hash leaf as needed, not
@@ -212,8 +203,29 @@ class Hash
212
203
  end
213
204
 
214
205
  ENV['HOME'] = Etc.getpwuid(Process.uid).dir
206
+ module MU
207
+
208
+ # For log entries that should only be logged when we're in verbose mode
209
+ DEBUG = 0.freeze
210
+ # For ordinary log entries
211
+ INFO = 1.freeze
212
+ # For more interesting log entries which are not errors
213
+ NOTICE = 2.freeze
214
+ # Log entries for non-fatal errors
215
+ WARN = 3.freeze
216
+ # Log entries for non-fatal errors
217
+ WARNING = 3.freeze
218
+ # Log entries for fatal errors
219
+ ERR = 4.freeze
220
+ # Log entries for fatal errors
221
+ ERROR = 4.freeze
222
+ # Log entries that will be held and displayed/emailed at the end of deploy,
223
+ # cleanup, etc.
224
+ SUMMARY = 5.freeze
225
+ end
215
226
 
216
227
  require 'mu/logger'
228
+
217
229
  module MU
218
230
 
219
231
  # Subclass core thread so we can gracefully handle it when we hit system
@@ -273,8 +285,9 @@ module MU
273
285
  # Wrapper class for fatal Exceptions. Gives our internals something to
274
286
  # inherit that will log an error message appropriately before bubbling up.
275
287
  class MuError < StandardError
276
- def initialize(message = nil)
277
- MU.log message, MU::ERR, details: caller[2] if !message.nil?
288
+ def initialize(message = nil, silent: false, details: nil)
289
+ details ||= caller[2]
290
+ MU.log message, MU::ERR, details: details if !message.nil? and !silent
278
291
  if MU.verbosity == MU::Logger::SILENT
279
292
  super ""
280
293
  else
@@ -286,8 +299,8 @@ module MU
286
299
  # Wrapper class for temporary Exceptions. Gives our internals something to
287
300
  # inherit that will log a notice message appropriately before bubbling up.
288
301
  class MuNonFatal < StandardError
289
- def initialize(message = nil)
290
- MU.log message, MU::NOTICE if !message.nil?
302
+ def initialize(message = nil, silent: false, details: nil)
303
+ MU.log message, MU::NOTICE, details: details if !message.nil? and !silent
291
304
  if MU.verbosity == MU::Logger::SILENT
292
305
  super ""
293
306
  else
@@ -598,9 +611,10 @@ module MU
598
611
  end
599
612
 
600
613
  # Shortcut to invoke {MU::Logger#log}
601
- def self.log(msg, level = MU::INFO, details: nil, html: false, verbosity: nil, color: true)
614
+ def self.log(msg, level = MU::INFO, shorthand_details = nil, details: nil, html: false, verbosity: nil, color: true)
602
615
  return if (level == MU::DEBUG and verbosity and verbosity <= MU::Logger::LOUD)
603
616
  return if verbosity and verbosity == MU::Logger::SILENT
617
+ details ||= shorthand_details
604
618
 
605
619
  if (level == MU::ERR or
606
620
  level == MU::WARN or
@@ -619,25 +633,6 @@ module MU
619
633
  @@logger.log(msg, level, details: details, html: html, verbosity: verbosity, color: color)
620
634
  end
621
635
 
622
- # For log entries that should only be logged when we're in verbose mode
623
- DEBUG = 0.freeze
624
- # For ordinary log entries
625
- INFO = 1.freeze
626
- # For more interesting log entries which are not errors
627
- NOTICE = 2.freeze
628
- # Log entries for non-fatal errors
629
- WARN = 3.freeze
630
- # Log entries for non-fatal errors
631
- WARNING = 3.freeze
632
- # Log entries for fatal errors
633
- ERR = 4.freeze
634
- # Log entries for fatal errors
635
- ERROR = 4.freeze
636
- # Log entries that will be held and displayed/emailed at the end of deploy,
637
- # cleanup, etc.
638
- SUMMARY = 5.freeze
639
-
640
-
641
636
  autoload :Cleanup, 'mu/cleanup'
642
637
  autoload :Deploy, 'mu/deploy'
643
638
  autoload :MommaCat, 'mu/mommacat'
@@ -651,7 +646,7 @@ module MU
651
646
  new_cfg = $MU_CFG.dup
652
647
  examples = {}
653
648
  MU::Cloud.supportedClouds.each { |cloud|
654
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
649
+ cloudclass = MU::Cloud.cloudClass(cloud)
655
650
  begin
656
651
  if cloudclass.hosted? and !$MU_CFG[cloud.downcase]
657
652
  cfg_blob = cloudclass.hosted_config
@@ -807,11 +802,7 @@ module MU
807
802
  # @param groomer [String]: The grooming agent to load.
808
803
  # @return [Class]: The class object implementing this groomer agent
809
804
  def self.loadGroomer(groomer)
810
- if !File.size?(MU.myRoot+"/modules/mu/groomers/#{groomer.downcase}.rb")
811
- raise MuError, "Requested to use unsupported grooming agent #{groomer}"
812
- end
813
- require "mu/groomers/#{groomer.downcase}"
814
- return Object.const_get("MU").const_get("Groomer").const_get(groomer)
805
+ MU::Groomer.loadGroomer(groomer)
815
806
  end
816
807
 
817
808
  @@myRegion_var = nil
@@ -965,8 +956,7 @@ module MU
965
956
 
966
957
  @@myCloudDescriptor = nil
967
958
  if MU.myCloud
968
- svrclass = const_get("MU").const_get("Cloud").const_get(MU.myCloud).const_get("Server")
969
- found = svrclass.find(cloud_id: @@myInstanceId, region: MU.myRegion) # XXX need habitat arg for google et al
959
+ found = MU::Cloud.resourceClass(MU.myCloud, "Server").find(cloud_id: @@myInstanceId, region: MU.myRegion) # XXX need habitat arg for google et al
970
960
  # found = MU::MommaCat.findStray(MU.myCloud, "server", cloud_id: @@myInstanceId, dummy_ok: true, region: MU.myRegion)
971
961
  if !found.nil? and found.size == 1
972
962
  @@myCloudDescriptor = found.values.first
@@ -979,8 +969,7 @@ module MU
979
969
  def self.myVPCObj
980
970
  return nil if MU.myCloud.nil?
981
971
  return @@myVPCObj_var if @@myVPCObj_var
982
- cloudclass = const_get("MU").const_get("Cloud").const_get(MU.myCloud)
983
- @@myVPCObj_var ||= cloudclass.myVPCObj
972
+ @@myVPCObj_var ||= MU::Cloud.cloudClass(MU.myCloud).myVPCObj
984
973
  @@myVPCObj_var
985
974
  end
986
975
 
@@ -1105,10 +1094,9 @@ module MU
1105
1094
 
1106
1095
  clouds = platform.nil? ? MU::Cloud.supportedClouds : [platform]
1107
1096
  clouds.each { |cloud|
1108
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1109
- bucketname = cloudclass.adminBucketName(credentials)
1097
+ bucketname = MU::Cloud.cloudClass(cloud).adminBucketName(credentials)
1110
1098
  begin
1111
- if platform or (cloudclass.hosted? and platform.nil?) or cloud == MU::Config.defaultCloud
1099
+ if platform or (MU::Cloud.cloudClass(cloud).hosted? and platform.nil?) or cloud == MU::Config.defaultCloud
1112
1100
  return bucketname
1113
1101
  end
1114
1102
  end
@@ -30,7 +30,7 @@ module MU
30
30
  :omnibus => "Jam everything into one monolothic configuration"
31
31
  }
32
32
 
33
- def initialize(clouds: MU::Cloud.supportedClouds, types: MU::Cloud.resource_types.keys, parent: nil, billing: nil, sources: nil, credentials: nil, group_by: :logical, savedeploys: false, diff: false, habitats: [])
33
+ def initialize(clouds: MU::Cloud.supportedClouds, types: MU::Cloud.resource_types.keys, parent: nil, billing: nil, sources: nil, credentials: nil, group_by: :logical, savedeploys: false, diff: false, habitats: [], scrub_mu_isms: false, regions: [], merge: false, pattern: nil)
34
34
  @scraped = {}
35
35
  @clouds = clouds
36
36
  @types = types
@@ -44,7 +44,11 @@ module MU
44
44
  @savedeploys = savedeploys
45
45
  @diff = diff
46
46
  @habitats = habitats
47
+ @regions = regions
47
48
  @habitats ||= []
49
+ @scrub_mu_isms = scrub_mu_isms
50
+ @merge = merge
51
+ @pattern = pattern
48
52
  end
49
53
 
50
54
  # Walk cloud providers with available credentials to discover resources
@@ -52,7 +56,7 @@ module MU
52
56
  @default_parent = nil
53
57
 
54
58
  @clouds.each { |cloud|
55
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
59
+ cloudclass = MU::Cloud.cloudClass(cloud)
56
60
  next if cloudclass.listCredentials.nil?
57
61
 
58
62
  if cloud == "Google" and !@parent and @target_creds
@@ -64,6 +68,10 @@ module MU
64
68
 
65
69
  cloudclass.listCredentials.each { |credset|
66
70
  next if @sources and !@sources.include?(credset)
71
+ cfg = cloudclass.credConfig(credset)
72
+ if cfg and cfg['restrict_to_habitats']
73
+ cfg['restrict_to_habitats'] << cfg['project'] if cfg['project']
74
+ end
67
75
 
68
76
  if @parent
69
77
  # TODO handle different inputs (cloud_id, etc)
@@ -84,7 +92,7 @@ module MU
84
92
 
85
93
  @types.each { |type|
86
94
  begin
87
- resclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
95
+ resclass = MU::Cloud.resourceClass(cloud, type)
88
96
  rescue ::MU::Cloud::MuCloudResourceNotImplemented
89
97
  next
90
98
  end
@@ -100,17 +108,31 @@ module MU
100
108
  credentials: credset,
101
109
  allow_multi: true,
102
110
  habitats: @habitats.dup,
111
+ region: @regions,
103
112
  dummy_ok: true,
104
- debug: false,
105
- flags: { "skip_provider_owned" => true }
113
+ skip_provider_owned: true,
114
+ # debug: false#,
106
115
  )
107
116
 
108
117
 
109
118
  if found and found.size > 0
119
+ if resclass.cfg_plural == "habitats"
120
+ found.reject! { |h|
121
+ !cloudclass.listHabitats(credset).include?(h.cloud_id)
122
+ }
123
+ end
110
124
  MU.log "Found #{found.size.to_s} raw #{resclass.cfg_plural} in #{cloud}"
111
125
  @scraped[type] ||= {}
112
126
  found.each { |obj|
127
+ if obj.habitat and !cloudclass.listHabitats(credset).include?(obj.habitat)
128
+ next
129
+ end
130
+
113
131
  # XXX apply any filters (e.g. MU-ID tags)
132
+ if obj.cloud_id.nil?
133
+ MU.log "This damn thing gave me no cloud id, what do I even do with that", MU::ERR, details: obj
134
+ exit
135
+ end
114
136
  @scraped[type][obj.cloud_id] = obj
115
137
  }
116
138
  end
@@ -188,33 +210,62 @@ module MU
188
210
  prefix = "mu" if prefix.empty? # so that appnames aren't ever empty
189
211
  end
190
212
 
213
+ # Find any previous deploys with this particular profile, which we'll use
214
+ # later for --diff.
215
+ @existing_deploys = {}
216
+ @existing_deploys_by_id = {}
217
+ @origins = {}
218
+ @types_found_in = {}
191
219
  groupings.each_pair { |appname, types|
192
- bok = { "appname" => prefix+appname }
193
- if @target_creds
194
- bok["credentials"] = @target_creds
195
- end
196
-
197
- count = 0
198
220
  allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
199
221
  next if (types & allowed_types).size == 0
200
222
  origin = {
201
- "appname" => bok['appname'],
223
+ "appname" => prefix+appname,
202
224
  "types" => (types & allowed_types).sort,
203
225
  "habitats" => @habitats.sort,
204
226
  "group_by" => @group_by.to_s
205
227
  }
206
228
 
207
- deploy = MU::MommaCat.findMatchingDeploy(origin)
208
- if @diff and !deploy
209
- MU.log "--diff was set but I failed to find a deploy like me to compare to", MU::ERR, details: origin
210
- exit 1
229
+ @existing_deploys[appname] = MU::MommaCat.findMatchingDeploy(origin)
230
+ if @existing_deploys[appname]
231
+ @existing_deploys_by_id[@existing_deploys[appname].deploy_id] = @existing_deploys[appname]
232
+ @origins[appname] = origin
233
+ origin['types'].each { |t|
234
+ @types_found_in[t] = @existing_deploys[appname]
235
+ }
236
+ end
237
+ }
238
+
239
+ groupings.each_pair { |appname, types|
240
+ allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
241
+ next if (types & allowed_types).size == 0
242
+
243
+ bok = { "appname" => prefix+appname }
244
+ if @scrub_mu_isms
245
+ bok["scrub_mu_isms"] = true
246
+ end
247
+ if @target_creds
248
+ bok["credentials"] = @target_creds
249
+ end
250
+
251
+ count = 0
252
+ if @diff
253
+ if !@existing_deploys[appname]
254
+ MU.log "--diff was set but I failed to find a deploy like '#{appname}' to compare to (have #{@existing_deploys.keys.join(", ")})", MU::ERR, details: @origins[appname]
255
+ exit 1
256
+ else
257
+ MU.log "Will diff current live resources against #{@existing_deploys[appname].deploy_id}", MU::NOTICE, details: @origins[appname]
258
+ end
211
259
  end
212
260
 
213
261
  threads = []
262
+ timers = {}
263
+ walltimers = {}
214
264
  @clouds.each { |cloud|
215
265
  @scraped.each_pair { |type, resources|
266
+ typestart = Time.now
216
267
  res_class = begin
217
- MU::Cloud.loadCloudType(cloud, type)
268
+ MU::Cloud.resourceClass(cloud, type)
218
269
  rescue MU::Cloud::MuCloudResourceNotImplemented
219
270
  # XXX I don't think this can actually happen
220
271
  next
@@ -222,6 +273,7 @@ module MU
222
273
  next if !types.include?(res_class.cfg_plural)
223
274
 
224
275
  bok[res_class.cfg_plural] ||= []
276
+ timers[type] ||= {}
225
277
 
226
278
  class_semaphore = Mutex.new
227
279
 
@@ -238,13 +290,18 @@ module MU
238
290
  end
239
291
  end
240
292
  threads << Thread.new(obj_thr) { |obj|
293
+ start = Time.now
241
294
 
242
- kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats)
243
- if kitten_cfg
295
+ kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats, types: @types)
296
+ if kitten_cfg and (!@pattern or @pattern.match(kitten_cfg['name']))
244
297
  print "."
245
298
  kitten_cfg.delete("credentials") if @target_creds
246
299
  class_semaphore.synchronize {
247
300
  bok[res_class.cfg_plural] << kitten_cfg
301
+ if !kitten_cfg['cloud_id']
302
+ MU.log "No cloud id in this #{res_class.cfg_name} kitten!", MU::ERR, details: kitten_cfg
303
+ end
304
+ timers[type][kitten_cfg['cloud_id']] = (Time.now - start)
248
305
  }
249
306
  count += 1
250
307
  end
@@ -255,6 +312,7 @@ module MU
255
312
  threads.each { |t|
256
313
  t.join
257
314
  }
315
+
258
316
  puts ""
259
317
  bok[res_class.cfg_plural].sort! { |a, b|
260
318
  strs = [a, b].map { |x|
@@ -276,24 +334,30 @@ module MU
276
334
  bok[res_class.cfg_plural].each { |sibling|
277
335
  next if kitten_cfg == sibling
278
336
  if sibling['name'] == kitten_cfg['name']
279
- MU.log "#{res_class.cfg_name} name #{sibling['name']} unavailable, will attempt to rename duplicate object", MU::DEBUG, details: kitten_cfg
280
- if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
281
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['parent'].id
282
- elsif kitten_cfg['project']
283
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['project']
284
- elsif kitten_cfg['region']
285
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['region']
286
- elsif kitten_cfg['cloud_id']
287
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
288
- else
289
- raise MU::Config::DuplicateNameError, "Saw duplicate #{res_class.cfg_name} name #{sibling['name']} and couldn't come up with a good way to differentiate them"
290
- end
337
+ MU::Adoption.deDuplicateName(kitten_cfg, res_class)
291
338
  MU.log "De-duplication: Renamed #{res_class.cfg_name} name '#{sibling['name']}' => '#{kitten_cfg['name']}'", MU::NOTICE
292
339
  break
293
340
  end
294
341
  }
295
342
  }
343
+ walltimers[type] ||= 0
344
+ walltimers[type] += (Time.now - typestart)
345
+ }
346
+ }
347
+
348
+ timers.each_pair { |type, resources|
349
+ next if resources.empty?
350
+ total = resources.values.sum
351
+ top_5 = resources.keys.sort { |a, b|
352
+ resources[b] <=> resources[a]
353
+ }.slice(0, 5).map { |k|
354
+ k.to_s+": "+sprintf("%.2fs", resources[k])
296
355
  }
356
+ if walltimers[type] < 45
357
+ MU.log "Kittened #{resources.size.to_s} eligible #{type}s in #{sprintf("%.2fs", walltimers[type])}"
358
+ else
359
+ MU.log "Kittened #{resources.size.to_s} eligible #{type}s in #{sprintf("%.2fs", walltimers[type])} (CPU time #{sprintf("%.2fs", total)}, avg #{sprintf("%.2fs", total/resources.size)}). Top 5:", MU::NOTICE, details: top_5
360
+ end
297
361
  }
298
362
 
299
363
  # No matching resources isn't necessarily an error
@@ -302,23 +366,36 @@ module MU
302
366
  # Now walk through all of the Refs in these objects, resolve them, and minimize
303
367
  # their config footprint
304
368
  MU.log "Minimizing footprint of #{count.to_s} found resources", MU::DEBUG
305
- @boks[bok['appname']] = vacuum(bok, origin: origin, save: @savedeploys)
306
369
 
307
- if @diff and !deploy
370
+ generated_deploy = generateStubDeploy(bok)
371
+ @boks[bok['appname']] = vacuum(bok, origin: @origins[appname], deploy: generated_deploy, save: @savedeploys)
372
+
373
+ if @diff and !@existing_deploys[appname]
308
374
  MU.log "diff flag set, but no comparable deploy provided for #{bok['appname']}", MU::ERR
309
375
  exit 1
310
376
  end
311
377
 
312
- if deploy and @diff
313
- prevcfg = MU::Config.manxify(vacuum(deploy.original_config, deploy: deploy))
378
+ if @diff
379
+ prev_vacuumed = vacuum(@existing_deploys[appname].original_config, deploy: @existing_deploys[appname], keep_missing: true, copy_from: generated_deploy)
380
+ prevcfg = MU::Config.manxify(prev_vacuumed)
314
381
  if !prevcfg
315
- MU.log "#{deploy.deploy_id} didn't have a working original config for me to compare", MU::ERR
382
+ MU.log "#{@existing_deploys[appname].deploy_id} didn't have a working original config for me to compare", MU::ERR
316
383
  exit 1
317
384
  end
318
385
  newcfg = MU::Config.manxify(@boks[bok['appname']])
386
+ report = prevcfg.diff(newcfg)
387
+
388
+ if report
389
+
390
+ if MU.muCfg['adopt_change_notify']
391
+ notifyChanges(@existing_deploys[appname], report.freeze)
392
+ end
393
+ if @merge
394
+ MU.log "Saving changes to #{@existing_deploys[appname].deploy_id}"
395
+ @existing_deploys[appname].updateBasketofKittens(newcfg, save_now: true)
396
+ end
397
+ end
319
398
 
320
- prevcfg.diff(newcfg)
321
- exit
322
399
  end
323
400
  }
324
401
  @boks
@@ -326,6 +403,183 @@ module MU
326
403
 
327
404
  private
328
405
 
406
+ # @param tier [Hash]
407
+ # @param parent_key [String]
408
+ def crawlChangeReport(tier, parent_key = nil, indent: "")
409
+ report = []
410
+ if tier.is_a?(Array)
411
+ tier.each { |a|
412
+ sub_report = crawlChangeReport(a, parent_key)
413
+ report.concat(sub_report) if sub_report and !sub_report.empty?
414
+ }
415
+ elsif tier.is_a?(Hash)
416
+ if tier[:action]
417
+ preposition = if tier[:action] == :added
418
+ "to"
419
+ elsif tier[:action] == :removed
420
+ "from"
421
+ else
422
+ "in"
423
+ end
424
+
425
+ name = ""
426
+ type_of = parent_key.sub(/s$|\[.*/, '') if parent_key
427
+ loc = tier[:habitat]
428
+
429
+ if tier[:value] and tier[:value].is_a?(Hash)
430
+ name, loc = MU::MommaCat.getChunkName(tier[:value], type_of)
431
+ elsif parent_key
432
+ name = parent_key
433
+ end
434
+
435
+ path_str = []
436
+ slack_path_str = ""
437
+ if tier[:parents] and tier[:parents].size > 2
438
+ path = tier[:parents].clone
439
+ slack_path_str += "#{preposition} \*"+path.join(" ⇨ ")+"\*" if path.size > 0
440
+ path.shift
441
+ path.shift
442
+ path.pop if path.last == name
443
+ for c in (0..(path.size-1)) do
444
+ path_str << (" " * (c+2)) + (path[c] || "<nil>")
445
+ end
446
+ end
447
+ path_str << "" if !path_str.empty?
448
+
449
+ plain = (name ? name : type_of) if name or type_of
450
+ plain ||= "" # XXX but this is a problem
451
+ slack = "`"+plain+"`"
452
+
453
+ plain += " ("+loc+")" if loc and !loc.empty?
454
+ color = plain
455
+
456
+ if tier[:action] == :added
457
+ color = "+ ".green + plain
458
+ plain = "+ " + plain
459
+ slack += " added"
460
+ elsif tier[:action] == :removed
461
+ color = "- ".red + plain
462
+ plain = "- " + plain
463
+ slack += " removed"
464
+ end
465
+
466
+ slack += " #{tier[:action]} #{preposition} \*#{loc}\*" if loc and !loc.empty? and [Array, Hash].include?(tier[:value].class)
467
+
468
+ plain = path_str.join(" => \n") + indent + plain
469
+ color = path_str.join(" => \n") + indent + color
470
+
471
+ slack += " "+slack_path_str if !slack_path_str.empty?
472
+ myreport = {
473
+ "slack" => slack,
474
+ "plain" => plain,
475
+ "color" => color
476
+ }
477
+
478
+ append = ""
479
+ if tier[:value] and (tier[:value].is_a?(Array) or tier[:value].is_a?(Hash))
480
+ if tier[:value].is_a?(Hash)
481
+ if name
482
+ tier[:value].delete("entity")
483
+ tier[:value].delete(name.sub(/\[.*/, '')) if name
484
+ end
485
+ if (tier[:value].keys - ["id", "name", "type"]).size > 0
486
+ myreport["details"] = tier[:value].clone
487
+ append = PP.pp(tier[:value], '').gsub(/(^|\n)/, '\1'+indent)
488
+ end
489
+ else
490
+ append = indent+"["+tier[:value].map { |v| MU::MommaCat.getChunkName(v, type_of).reverse.join("/") || v.to_s.light_blue }.join(", ")+"]"
491
+ slack += " #{tier[:action].to_s}: "+tier[:value].map { |v| MU::MommaCat.getChunkName(v, type_of).reverse.join("/") || v.to_s }.join(", ")
492
+ end
493
+ else
494
+ tier[:value] ||= "<nil>"
495
+ if ![:removed].include?(tier[:action])
496
+ myreport["slack"] += ". New #{tier[:field] ? "`"+tier[:field]+"`" : :value}: \*#{tier[:value]}\*"
497
+ else
498
+ myreport["slack"] += " (was \*#{tier[:value]}\*)"
499
+ end
500
+ append = tier[:value].to_s.bold
501
+ end
502
+
503
+ if append and !append.empty?
504
+ myreport["plain"] += " =>\n "+indent+append
505
+ myreport["color"] += " =>\n "+indent+append
506
+ end
507
+
508
+ report << myreport if tier[:action]
509
+ end
510
+
511
+ # Just because we've got changes at this level doesn't mean there aren't
512
+ # more further down.
513
+ tier.each_pair { |k, v|
514
+ next if !(v.is_a?(Hash) or v.is_a?(Array))
515
+ sub_report = crawlChangeReport(v, k, indent: indent+" ")
516
+ report.concat(sub_report) if sub_report and !sub_report.empty?
517
+ }
518
+ end
519
+
520
+ report
521
+ end
522
+
523
+
524
+ def notifyChanges(deploy, report)
525
+ snippet_threshold = (MU.muCfg['adopt_change_notify'] && MU.muCfg['adopt_change_notify']['slack_snippet_threshold']) || 5
526
+
527
+ report.each_pair { |res_type, resources|
528
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(res_type, false)
529
+ next if !shortclass # we don't really care about Mu metadata changes
530
+ resources.each_pair { |name, data|
531
+ if MU::MommaCat.getChunkName(data[:value], res_type).first.nil?
532
+ symbol = if data[:action] == :added
533
+ "+".green
534
+ elsif data[:action] == :removed
535
+ "-".red
536
+ else
537
+ "~".yellow
538
+ end
539
+ puts (symbol+" "+res_type+"["+name+"]")
540
+ end
541
+
542
+ noun = shortclass ? shortclass.to_s : res_type.capitalize
543
+ verb = if data[:action]
544
+ data[:action].to_s
545
+ else
546
+ "modified"
547
+ end
548
+
549
+ changes = crawlChangeReport(data.freeze, res_type)
550
+
551
+ slacktext = "#{noun} \*#{name}\* was #{verb}"
552
+ if data[:habitat]
553
+ slacktext += " in \*#{data[:habitat]}\*"
554
+ end
555
+ snippets = []
556
+
557
+ if [:added, :removed].include?(data[:action]) and data[:value]
558
+ snippets << { text: "```"+JSON.pretty_generate(data[:value])+"```" }
559
+ else
560
+ changes.each { |c|
561
+ slacktext += "\n • "+c["slack"]
562
+ if c["details"]
563
+ details = JSON.pretty_generate(c["details"])
564
+ snippets << { text: "```"+JSON.pretty_generate(c["details"])+"```" }
565
+ end
566
+ }
567
+ end
568
+
569
+ changes.each { |c|
570
+ puts c["color"]
571
+ }
572
+ puts ""
573
+
574
+ if MU.muCfg['adopt_change_notify'] and MU.muCfg['adopt_change_notify']['slack']
575
+ deploy.sendAdminSlack(slacktext, scrub_mu_isms: MU.muCfg['adopt_scrub_mu_isms'], snippets: snippets, noop: false)
576
+ end
577
+
578
+ }
579
+ }
580
+
581
+ end
582
+
329
583
  def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, type: nil)
330
584
  return if schema_chunk.nil?
331
585
 
@@ -333,7 +587,7 @@ module MU
333
587
  deletia = []
334
588
  schema_chunk["properties"].each_pair { |key, subschema|
335
589
  next if !conf_chunk[key]
336
- shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key)
590
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key, false)
337
591
 
338
592
  if subschema["default_if"]
339
593
  subschema["default_if"].each { |cond|
@@ -357,8 +611,7 @@ module MU
357
611
  # theory
358
612
  realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
359
613
 
360
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
361
- _toplevel_required, cloudschema = cloudclass.schema(self)
614
+ _toplevel_required, cloudschema = MU::Cloud.resourceClass(item['cloud'], type).schema(self)
362
615
 
363
616
  newschema = schema_chunk["items"].dup
364
617
  newschema["properties"].merge!(cloudschema)
@@ -382,8 +635,7 @@ module MU
382
635
  # Do the same for our main objects: if they all use the same credentials,
383
636
  # for example, remove the explicit +credentials+ attributes and set that
384
637
  # value globally, once.
385
- def vacuum(bok, origin: nil, save: false, deploy: nil)
386
- deploy ||= generateStubDeploy(bok)
638
+ def vacuum(bok, origin: nil, save: false, deploy: nil, copy_from: nil, keep_missing: false)
387
639
 
388
640
  globals = {
389
641
  'cloud' => {},
@@ -403,11 +655,24 @@ module MU
403
655
  end
404
656
  }
405
657
  obj = deploy.findLitterMate(type: attrs[:cfg_plural], name: resource['name'])
658
+ inject_metadata = save
659
+ if obj.nil? and copy_from
660
+ obj = copy_from.findLitterMate(type: attrs[:cfg_plural], name: resource['name'])
661
+ if obj
662
+ inject_metadata = true
663
+ obj.intoDeploy(deploy, force: true)
664
+ end
665
+ end
666
+
406
667
  begin
407
668
  raise Incomplete if obj.nil?
669
+ if inject_metadata
670
+ deploydata = obj.notify
671
+ deploy.notify(attrs[:cfg_plural], resource['name'], deploydata, triggering_node: obj)
672
+ end
408
673
  new_cfg = resolveReferences(resource, deploy, obj)
409
674
  new_cfg.delete("cloud_id")
410
- cred_cfg = MU::Cloud.const_get(obj.cloud).credConfig(obj.credentials)
675
+ cred_cfg = MU::Cloud.cloudClass(obj.cloud).credConfig(obj.credentials)
411
676
  if cred_cfg['region'] == new_cfg['region']
412
677
  new_cfg.delete('region')
413
678
  end
@@ -417,6 +682,11 @@ module MU
417
682
  end
418
683
  processed << new_cfg
419
684
  rescue Incomplete
685
+ if keep_missing
686
+ processed << resource
687
+ else
688
+ MU.log "#{attrs[:cfg_name]} #{resource['name']} didn't show up from findLitterMate", MU::WARN, details: deploy.original_config[attrs[:cfg_plural]].reject { |r| r['name'] != "" }
689
+ end
420
690
  end
421
691
  }
422
692
 
@@ -427,24 +697,23 @@ module MU
427
697
 
428
698
  # Pare out global values like +cloud+ or +region+ that appear to be
429
699
  # universal in the deploy we're creating.
430
- def scrub_globals(h, field)
700
+ scrub_globals = Proc.new { |h, field|
431
701
  if h.is_a?(Hash)
432
702
  newhash = {}
433
703
  h.each_pair { |k, v|
434
704
  next if k == field
435
- newhash[k] = scrub_globals(v, field)
705
+ newhash[k] = scrub_globals.call(v, field)
436
706
  }
437
707
  h = newhash
438
708
  elsif h.is_a?(Array)
439
709
  newarr = []
440
710
  h.each { |v|
441
- newarr << scrub_globals(v, field)
711
+ newarr << scrub_globals.call(v, field)
442
712
  }
443
- h = newarr
713
+ h = newarr.uniq
444
714
  end
445
-
446
715
  h
447
- end
716
+ }
448
717
 
449
718
  globals.each_pair { |field, counts|
450
719
  next if counts.size != 1
@@ -454,7 +723,7 @@ module MU
454
723
  if bok[attrs[:cfg_plural]]
455
724
  new_resources = []
456
725
  bok[attrs[:cfg_plural]].each { |resource|
457
- new_resources << scrub_globals(resource, field)
726
+ new_resources << scrub_globals.call(resource, field)
458
727
  }
459
728
  bok[attrs[:cfg_plural]] = new_resources
460
729
  end
@@ -472,11 +741,33 @@ module MU
472
741
  end
473
742
 
474
743
  def resolveReferences(cfg, deploy, parent)
744
+ mask_deploy_id = false
745
+
746
+ check_deploy_id = Proc.new { |cfgblob|
747
+ (deploy and
748
+ (cfgblob.is_a?(MU::Config::Ref) or cfgblob.is_a?(Hash)) and
749
+ cfgblob['deploy_id'] and
750
+ cfgblob['deploy_id'] != deploy.deploy_id and
751
+ @diff and
752
+ @types_found_in[cfgblob['type']] and
753
+ @types_found_in[cfgblob['type']].deploy_id == cfgblob['deploy_id']
754
+ )
755
+ }
756
+
757
+ mask_deploy_id = check_deploy_id.call(cfg)
758
+
475
759
  if cfg.is_a?(MU::Config::Ref)
476
- cfg.kitten(deploy) || cfg.kitten
760
+ if mask_deploy_id
761
+ cfg.delete("deploy_id")
762
+ cfg.delete("mommacat")
763
+ cfg.kitten(deploy)
764
+ else
765
+ cfg.kitten(deploy) || cfg.kitten
766
+ end
767
+
477
768
  hashcfg = cfg.to_h
478
769
 
479
- if cfg.kitten(deploy)
770
+ if cfg.kitten
480
771
  littermate = deploy.findLitterMate(type: cfg.type, name: cfg.name, cloud_id: cfg.id, habitat: cfg.habitat)
481
772
 
482
773
  if littermate and littermate.config['name']
@@ -501,13 +792,12 @@ module MU
501
792
  elsif hashcfg["id"] and !hashcfg["name"]
502
793
  hashcfg.delete("deploy_id")
503
794
  else
504
- pp parent.cloud_desc
505
- raise Incomplete, "Failed to resolve reference on behalf of #{parent}"
795
+ raise Incomplete.new "Failed to resolve reference on behalf of #{parent}", details: hashcfg
506
796
  end
507
797
  hashcfg.delete("deploy_id") if hashcfg['deploy_id'] == deploy.deploy_id
508
798
 
509
799
  if parent and parent.config
510
- cred_cfg = MU::Cloud.const_get(parent.cloud).credConfig(parent.credentials)
800
+ cred_cfg = MU::Cloud.cloudClass(parent.cloud).credConfig(parent.credentials)
511
801
 
512
802
  if parent.config['region'] == hashcfg['region'] or
513
803
  cred_cfg['region'] == hashcfg['region']
@@ -566,7 +856,12 @@ module MU
566
856
  MU.log "Dropping unresolved value", MU::WARN, details: value
567
857
  end
568
858
  }
569
- cfg = new_array
859
+ cfg = new_array.uniq
860
+ end
861
+
862
+ if mask_deploy_id or check_deploy_id.call(cfg)
863
+ cfg.delete("deploy_id")
864
+ MU.log "#{parent} in #{deploy.deploy_id} references something in #{@types_found_in[cfg['type']].deploy_id}, ditching extraneous deploy_id", MU::DEBUG, details: cfg.to_h
570
865
  end
571
866
 
572
867
  cfg
@@ -616,6 +911,10 @@ module MU
616
911
 
617
912
  if !@scraped[typename][kitten['cloud_id']]
618
913
  MU.log "No object in scraped tree for #{attrs[:cfg_name]} #{kitten['cloud_id']} (#{kitten['name']})", MU::ERR, details: kitten
914
+ if kitten['cloud_id'].nil?
915
+ pp caller
916
+ exit
917
+ end
619
918
  next
620
919
  end
621
920
 
@@ -626,7 +925,8 @@ module MU
626
925
  deploy.addKitten(
627
926
  attrs[:cfg_plural],
628
927
  kitten['name'],
629
- @scraped[typename][kitten['cloud_id']]
928
+ @scraped[typename][kitten['cloud_id']],
929
+ do_notify: true
630
930
  )
631
931
  }
632
932
  end
@@ -635,6 +935,21 @@ module MU
635
935
  deploy
636
936
  end
637
937
 
938
+ def self.deDuplicateName(kitten_cfg, res_class)
939
+ orig_name = kitten_cfg['name'].dup
940
+ if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
941
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['parent'].id
942
+ elsif kitten_cfg['project']
943
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['project']
944
+ elsif kitten_cfg['region']
945
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['region']
946
+ elsif kitten_cfg['cloud_id']
947
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
948
+ else
949
+ raise MU::Config::DuplicateNameError, "Saw duplicate #{res_class.cfg_name} name #{orig_name} and couldn't come up with a good way to differentiate them"
950
+ end
951
+ end
952
+
638
953
  # Go through everything we've scraped and update our mappings of cloud ids
639
954
  # and bare name fields, so that resources can reference one another
640
955
  # portably by name.