cloud-mu 3.1.3 → 3.1.4

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +10 -2
  3. data/bin/mu-adopt +5 -1
  4. data/bin/mu-load-config.rb +2 -3
  5. data/bin/mu-run-tests +112 -27
  6. data/cloud-mu.gemspec +20 -20
  7. data/cookbooks/mu-tools/libraries/helper.rb +2 -1
  8. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  9. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  10. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  11. data/extras/image-generators/Google/centos6.yaml +1 -0
  12. data/extras/image-generators/Google/centos7.yaml +1 -1
  13. data/modules/mommacat.ru +5 -15
  14. data/modules/mu.rb +10 -14
  15. data/modules/mu/adoption.rb +20 -14
  16. data/modules/mu/cleanup.rb +13 -9
  17. data/modules/mu/cloud.rb +26 -26
  18. data/modules/mu/clouds/aws.rb +100 -59
  19. data/modules/mu/clouds/aws/alarm.rb +4 -2
  20. data/modules/mu/clouds/aws/bucket.rb +25 -21
  21. data/modules/mu/clouds/aws/cache_cluster.rb +25 -23
  22. data/modules/mu/clouds/aws/collection.rb +21 -20
  23. data/modules/mu/clouds/aws/container_cluster.rb +47 -26
  24. data/modules/mu/clouds/aws/database.rb +57 -68
  25. data/modules/mu/clouds/aws/dnszone.rb +14 -14
  26. data/modules/mu/clouds/aws/endpoint.rb +20 -16
  27. data/modules/mu/clouds/aws/firewall_rule.rb +19 -16
  28. data/modules/mu/clouds/aws/folder.rb +7 -7
  29. data/modules/mu/clouds/aws/function.rb +15 -12
  30. data/modules/mu/clouds/aws/group.rb +14 -10
  31. data/modules/mu/clouds/aws/habitat.rb +16 -13
  32. data/modules/mu/clouds/aws/loadbalancer.rb +16 -15
  33. data/modules/mu/clouds/aws/log.rb +13 -10
  34. data/modules/mu/clouds/aws/msg_queue.rb +15 -8
  35. data/modules/mu/clouds/aws/nosqldb.rb +18 -11
  36. data/modules/mu/clouds/aws/notifier.rb +11 -6
  37. data/modules/mu/clouds/aws/role.rb +87 -70
  38. data/modules/mu/clouds/aws/search_domain.rb +30 -19
  39. data/modules/mu/clouds/aws/server.rb +102 -72
  40. data/modules/mu/clouds/aws/server_pool.rb +47 -28
  41. data/modules/mu/clouds/aws/storage_pool.rb +5 -6
  42. data/modules/mu/clouds/aws/user.rb +13 -10
  43. data/modules/mu/clouds/aws/vpc.rb +135 -121
  44. data/modules/mu/clouds/azure.rb +16 -9
  45. data/modules/mu/clouds/azure/container_cluster.rb +2 -3
  46. data/modules/mu/clouds/azure/firewall_rule.rb +10 -10
  47. data/modules/mu/clouds/azure/habitat.rb +8 -6
  48. data/modules/mu/clouds/azure/loadbalancer.rb +5 -5
  49. data/modules/mu/clouds/azure/role.rb +8 -10
  50. data/modules/mu/clouds/azure/server.rb +65 -25
  51. data/modules/mu/clouds/azure/user.rb +5 -7
  52. data/modules/mu/clouds/azure/vpc.rb +12 -15
  53. data/modules/mu/clouds/cloudformation.rb +8 -7
  54. data/modules/mu/clouds/cloudformation/vpc.rb +2 -4
  55. data/modules/mu/clouds/google.rb +39 -24
  56. data/modules/mu/clouds/google/bucket.rb +9 -11
  57. data/modules/mu/clouds/google/container_cluster.rb +27 -42
  58. data/modules/mu/clouds/google/database.rb +6 -9
  59. data/modules/mu/clouds/google/firewall_rule.rb +11 -10
  60. data/modules/mu/clouds/google/folder.rb +16 -9
  61. data/modules/mu/clouds/google/function.rb +127 -161
  62. data/modules/mu/clouds/google/group.rb +21 -18
  63. data/modules/mu/clouds/google/habitat.rb +18 -15
  64. data/modules/mu/clouds/google/loadbalancer.rb +14 -16
  65. data/modules/mu/clouds/google/role.rb +48 -31
  66. data/modules/mu/clouds/google/server.rb +105 -105
  67. data/modules/mu/clouds/google/server_pool.rb +12 -31
  68. data/modules/mu/clouds/google/user.rb +67 -13
  69. data/modules/mu/clouds/google/vpc.rb +58 -65
  70. data/modules/mu/config.rb +89 -1738
  71. data/modules/mu/config/bucket.rb +3 -3
  72. data/modules/mu/config/collection.rb +3 -3
  73. data/modules/mu/config/container_cluster.rb +2 -2
  74. data/modules/mu/config/dnszone.rb +5 -5
  75. data/modules/mu/config/doc_helpers.rb +517 -0
  76. data/modules/mu/config/endpoint.rb +3 -3
  77. data/modules/mu/config/firewall_rule.rb +118 -3
  78. data/modules/mu/config/folder.rb +3 -3
  79. data/modules/mu/config/function.rb +2 -2
  80. data/modules/mu/config/group.rb +3 -3
  81. data/modules/mu/config/habitat.rb +3 -3
  82. data/modules/mu/config/loadbalancer.rb +3 -3
  83. data/modules/mu/config/log.rb +3 -3
  84. data/modules/mu/config/msg_queue.rb +3 -3
  85. data/modules/mu/config/nosqldb.rb +3 -3
  86. data/modules/mu/config/notifier.rb +2 -2
  87. data/modules/mu/config/ref.rb +333 -0
  88. data/modules/mu/config/role.rb +3 -3
  89. data/modules/mu/config/schema_helpers.rb +508 -0
  90. data/modules/mu/config/search_domain.rb +3 -3
  91. data/modules/mu/config/server.rb +86 -58
  92. data/modules/mu/config/server_pool.rb +2 -2
  93. data/modules/mu/config/tail.rb +189 -0
  94. data/modules/mu/config/user.rb +3 -3
  95. data/modules/mu/config/vpc.rb +44 -4
  96. data/modules/mu/defaults/Google.yaml +2 -2
  97. data/modules/mu/deploy.rb +13 -10
  98. data/modules/mu/groomer.rb +1 -1
  99. data/modules/mu/groomers/ansible.rb +69 -24
  100. data/modules/mu/groomers/chef.rb +52 -44
  101. data/modules/mu/logger.rb +17 -14
  102. data/modules/mu/master.rb +317 -2
  103. data/modules/mu/master/chef.rb +3 -4
  104. data/modules/mu/master/ldap.rb +3 -3
  105. data/modules/mu/master/ssl.rb +12 -2
  106. data/modules/mu/mommacat.rb +85 -1766
  107. data/modules/mu/mommacat/daemon.rb +394 -0
  108. data/modules/mu/mommacat/naming.rb +366 -0
  109. data/modules/mu/mommacat/storage.rb +689 -0
  110. data/modules/tests/bucket.yml +4 -0
  111. data/modules/tests/{win2k12.yaml → needwork/win2k12.yaml} +0 -0
  112. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  113. data/modules/tests/regrooms/bucket.yml +19 -0
  114. metadata +112 -102
@@ -59,10 +59,10 @@ module MU
59
59
  end
60
60
 
61
61
  # Generic pre-processing of {MU::Config::BasketofKittens::buckets}, bare and unvalidated.
62
- # @param bucket [Hash]: The resource to process and validate
63
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
62
+ # @param _bucket [Hash]: The resource to process and validate
63
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
64
64
  # @return [Boolean]: True if validation succeeded, False otherwise
65
- def self.validate(bucket, configurator)
65
+ def self.validate(_bucket, _configurator)
66
66
  ok = true
67
67
 
68
68
  ok
@@ -74,10 +74,10 @@ module MU
74
74
  end
75
75
 
76
76
  # Generic pre-processing of {MU::Config::BasketofKittens::collections}, bare and unvalidated.
77
- # @param stack [Hash]: The resource to process and validate
78
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
77
+ # @param _stack [Hash]: The resource to process and validate
78
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
79
79
  # @return [Boolean]: True if validation succeeded, False otherwise
80
- def self.validate(stack, configurator)
80
+ def self.validate(_stack, _configurator)
81
81
  ok = true
82
82
  ok
83
83
  end
@@ -94,9 +94,9 @@ module MU
94
94
 
95
95
  # Generic pre-processing of {MU::Config::BasketofKittens::container_clusters}, bare and unvalidated.
96
96
  # @param cluster [Hash]: The resource to process and validate
97
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
97
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
98
98
  # @return [Boolean]: True if validation succeeded, False otherwise
99
- def self.validate(cluster, configurator)
99
+ def self.validate(cluster, _configurator)
100
100
  ok = true
101
101
 
102
102
  if cluster["max_size"] or cluster["min_size"]
@@ -312,15 +312,15 @@ module MU
312
312
  end
313
313
 
314
314
  # Generic pre-processing of {MU::Config::BasketofKittens::dnszones}, bare and unvalidated.
315
- # @param zone [Hash]: The resource to process and validate
316
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
315
+ # @param _zone [Hash]: The resource to process and validate
316
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
317
317
  # @return [Boolean]: True if validation succeeded, False otherwise
318
- def self.validate(zone, configurator)
318
+ def self.validate(_zone, _configurator)
319
319
  ok = true
320
- ok
321
- end
322
320
  # TODO non-local VPCs are valid, but require an account field, which insertKitten doesn't know anything about
323
321
  # if !zone['account'].nil? and zone['account'] != MU.account_number
322
+ ok
323
+ end
324
324
 
325
325
  end
326
326
  end
@@ -0,0 +1,517 @@
1
+ # Copyright:: Copyright (c) 2020 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module MU
16
+
17
+ # Methods and structures for parsing Mu's configuration files. See also {MU::Config::BasketofKittens}.
18
+ class Config
19
+
20
+ # Accessor for our Basket of Kittens schema definition, with various
21
+ # cloud-specific details merged so we can generate documentation for them.
22
+ def self.docSchema
23
+ docschema = Marshal.load(Marshal.dump(@@schema))
24
+ only_children = {}
25
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
26
+ MU::Cloud.supportedClouds.each { |cloud|
27
+ begin
28
+ require "mu/clouds/#{cloud.downcase}/#{attrs[:cfg_name]}"
29
+ rescue LoadError
30
+ next
31
+ end
32
+ res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
33
+ _required, res_schema = res_class.schema(self)
34
+ docschema["properties"][attrs[:cfg_plural]]["items"]["description"] ||= ""
35
+ docschema["properties"][attrs[:cfg_plural]]["items"]["description"] += "\n#\n# `#{cloud}`: "+res_class.quality
36
+ res_schema.each { |key, cfg|
37
+ if !docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
38
+ only_children[attrs[:cfg_plural]] ||= {}
39
+ only_children[attrs[:cfg_plural]][key] ||= {}
40
+ only_children[attrs[:cfg_plural]][key][cloud] = cfg
41
+ end
42
+ }
43
+ }
44
+ }
45
+
46
+ # recursively chase down description fields in arrays and objects of our
47
+ # schema and prepend stuff to them for documentation
48
+ def self.prepend_descriptions(prefix, cfg)
49
+ cfg["prefix"] = prefix
50
+ if cfg["type"] == "array" and cfg["items"]
51
+ cfg["items"] = prepend_descriptions(prefix, cfg["items"])
52
+ elsif cfg["type"] == "object" and cfg["properties"]
53
+ cfg["properties"].keys.each { |key|
54
+ cfg["properties"][key] = prepend_descriptions(prefix, cfg["properties"][key])
55
+ }
56
+ end
57
+ cfg
58
+ end
59
+
60
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
61
+ MU::Cloud.supportedClouds.each { |cloud|
62
+ res_class = nil
63
+ begin
64
+ res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
65
+ rescue MU::Cloud::MuCloudResourceNotImplemented
66
+ next
67
+ end
68
+ required, res_schema = res_class.schema(self)
69
+ next if required.size == 0 and res_schema.size == 0
70
+ res_schema.each { |key, cfg|
71
+ cfg["description"] ||= ""
72
+ if !cfg["description"].empty?
73
+ cfg["description"] = "\n# +"+cloud.upcase+"+: "+cfg["description"]
74
+ end
75
+ if docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
76
+ schemaMerge(docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key], cfg, cloud)
77
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] ||= ""
78
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] += "\n"+(cfg["description"].match(/^#/) ? "" : "# ")+cfg["description"]
79
+ MU.log "Munging #{cloud}-specific #{classname.to_s} schema into BasketofKittens => #{attrs[:cfg_plural]} => #{key}", MU::DEBUG, details: docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
80
+ else
81
+ if only_children[attrs[:cfg_plural]][key]
82
+ prefix = only_children[attrs[:cfg_plural]][key].keys.map{ |x| x.upcase }.join(" & ")+" ONLY"
83
+ cfg["description"].gsub!(/^\n#/, '') # so we don't leave the description blank in the "optional parameters" section
84
+ cfg = prepend_descriptions(prefix, cfg)
85
+ end
86
+
87
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key] = cfg
88
+ end
89
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["clouds"] = {}
90
+ docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["clouds"][cloud] = cfg
91
+ }
92
+
93
+ docschema['required'].concat(required)
94
+ docschema['required'].uniq!
95
+ }
96
+ }
97
+
98
+ docschema
99
+ end
100
+
101
+ # Output the dependencies of this BoK stack as a directed acyclic graph.
102
+ # Very useful for debugging.
103
+ def visualizeDependencies
104
+ # GraphViz won't like MU::Config::Tail, pare down to plain Strings
105
+ config = MU::Config.stripConfig(@config)
106
+ begin
107
+ g = GraphViz.new(:G, :type => :digraph)
108
+ # Generate a GraphViz node for each resource in this stack
109
+ nodes = {}
110
+ MU::Cloud.resource_types.each_pair { |classname, attrs|
111
+ nodes[attrs[:cfg_name]] = {}
112
+ if config.has_key?(attrs[:cfg_plural]) and config[attrs[:cfg_plural]]
113
+ config[attrs[:cfg_plural]].each { |resource|
114
+ nodes[attrs[:cfg_name]][resource['name']] = g.add_nodes("#{classname}: #{resource['name']}")
115
+ }
116
+ end
117
+ }
118
+ # Now add edges corresponding to the dependencies they list
119
+ MU::Cloud.resource_types.values.each { |attrs|
120
+ if config.has_key?(attrs[:cfg_plural]) and config[attrs[:cfg_plural]]
121
+ config[attrs[:cfg_plural]].each { |resource|
122
+ if resource.has_key?("dependencies")
123
+ me = nodes[attrs[:cfg_name]][resource['name']]
124
+ resource["dependencies"].each { |dep|
125
+ parent = nodes[dep['type']][dep['name']]
126
+ g.add_edges(me, parent)
127
+ }
128
+ end
129
+ }
130
+ end
131
+ }
132
+ # Spew some output?
133
+ MU.log "Emitting dependency graph as /tmp/#{config['appname']}.jpg", MU::NOTICE
134
+ g.output(:jpg => "/tmp/#{config['appname']}.jpg")
135
+ rescue StandardError => e
136
+ MU.log "Failed to generate GraphViz dependency tree: #{e.inspect}. This should only matter to developers.", MU::WARN, details: e.backtrace
137
+ end
138
+ end
139
+
140
+ # Generate a documentation-friendly dummy Ruby class for our mu.yaml main
141
+ # config.
142
+ def self.emitConfigAsRuby
143
+ example = %Q{---
144
+ public_address: 1.2.3.4
145
+ mu_admin_email: egtlabs@eglobaltech.com
146
+ mu_admin_name: Joe Schmoe
147
+ mommacat_port: 2260
148
+ banner: My Example Mu Master
149
+ mu_repository: git://github.com/cloudamatic/mu.git
150
+ repos:
151
+ - https://github.com/cloudamatic/mu_demo_platform
152
+ allow_invade_foreign_vpcs: true
153
+ ansible_dir:
154
+ aws:
155
+ egtdev:
156
+ region: us-east-1
157
+ log_bucket_name: egt-mu-log-bucket
158
+ default: true
159
+ name: egtdev
160
+ personal:
161
+ region: us-east-2
162
+ log_bucket_name: my-mu-log-bucket
163
+ name: personal
164
+ google:
165
+ egtlabs:
166
+ project: egt-labs-admin
167
+ credentials_file: /opt/mu/etc/google.json
168
+ region: us-east4
169
+ log_bucket_name: hexabucket-761234
170
+ default: true
171
+ }
172
+ mu_yaml_schema = eval(%Q{
173
+ $NOOP = true
174
+ load "#{MU.myRoot}/bin/mu-configure"
175
+ $CONFIGURABLES
176
+ })
177
+ return if mu_yaml_schema.nil? or !mu_yaml_schema.is_a?(Hash)
178
+ muyamlpath = "#{MU.myRoot}/modules/mu/mu.yaml.rb"
179
+ MU.log "Converting mu.yaml schema to Ruby objects in #{muyamlpath}"
180
+ muyaml_rb = File.new(muyamlpath, File::CREAT|File::TRUNC|File::RDWR, 0644)
181
+ muyaml_rb.puts "# Configuration schema for mu.yaml. See also {https://github.com/cloudamatic/mu/wiki/Configuration the Mu wiki}."
182
+ muyaml_rb.puts "#"
183
+ muyaml_rb.puts "# Example:"
184
+ muyaml_rb.puts "#"
185
+ muyaml_rb.puts "# <pre>"
186
+ example.split(/\n/).each { |line|
187
+ muyaml_rb.puts "# "+line+" " # markdooooown
188
+ }
189
+ muyaml_rb.puts "# </pre>"
190
+ muyaml_rb.puts "module MuYAML"
191
+ muyaml_rb.puts "\t# The configuration file format for Mu's main config file."
192
+ MU::Config.printMuYamlSchema(muyaml_rb, [], { "subtree" => mu_yaml_schema })
193
+ muyaml_rb.puts "end"
194
+ muyaml_rb.close
195
+ end
196
+
197
+ # Take the schema we've defined and create a dummy Ruby class tree out of
198
+ # it, basically so we can leverage Yard to document it.
199
+ def self.emitSchemaAsRuby
200
+ kittenpath = "#{MU.myRoot}/modules/mu/kittens.rb"
201
+ MU.log "Converting Basket of Kittens schema to Ruby objects in #{kittenpath}"
202
+ kitten_rb = File.new(kittenpath, File::CREAT|File::TRUNC|File::RDWR, 0644)
203
+ kitten_rb.puts "### THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ###"
204
+ kitten_rb.puts "#"
205
+ kitten_rb.puts "#"
206
+ kitten_rb.puts "#"
207
+ kitten_rb.puts "module MU"
208
+ kitten_rb.puts "class Config"
209
+ kitten_rb.puts "\t# The configuration file format for Mu application stacks."
210
+ self.printSchema(kitten_rb, ["BasketofKittens"], MU::Config.docSchema)
211
+ kitten_rb.puts "end"
212
+ kitten_rb.puts "end"
213
+ kitten_rb.close
214
+
215
+ end
216
+
217
+ # Emit our Basket of Kittens schema in a format that YARD can comprehend
218
+ # and turn into documentation.
219
+ def self.printSchema(kitten_rb, class_hierarchy, schema, in_array = false, required = false, prefix: nil)
220
+ return if schema.nil?
221
+
222
+ if schema["type"] == "object"
223
+ printme = []
224
+
225
+ if !schema["properties"].nil?
226
+ # order sub-elements by whether they're required, so we can use YARD's
227
+ # grouping tags on them
228
+ if !schema["required"].nil? and schema["required"].size > 0
229
+ prop_list = schema["properties"].keys.sort_by { |name|
230
+ schema["required"].include?(name) ? 0 : 1
231
+ }
232
+ else
233
+ prop_list = schema["properties"].keys
234
+ end
235
+ req = false
236
+ printme << "# @!group Optional parameters" if schema["required"].nil? or schema["required"].size == 0
237
+ prop_list.each { |name|
238
+ prop = schema["properties"][name]
239
+
240
+ if class_hierarchy.size == 1
241
+
242
+ _shortclass, cfg_name, cfg_plural, _classname = MU::Cloud.getResourceNames(name)
243
+ if cfg_name
244
+ example_path = MU.myRoot+"/modules/mu/config/"+cfg_name+".yml"
245
+ if File.exist?(example_path)
246
+ example = "#\n# Examples:\n#\n"
247
+ # XXX these variables are all parameters from the BoKs in
248
+ # modules/tests. A really clever implementation would read
249
+ # and parse them to get default values, perhaps, instead of
250
+ # hard-coding them here.
251
+ instance_type = "t2.medium"
252
+ db_size = "db.t2.medium"
253
+ vpc_name = "some_vpc"
254
+ logs_name = "some_loggroup"
255
+ queues_name = "some_queue"
256
+ server_pools_name = "some_server_pool"
257
+ ["simple", "complex"].each { |complexity|
258
+ erb = ERB.new(File.read(example_path), nil, "<>")
259
+ example += "# !!!yaml\n"
260
+ example += "# ---\n"
261
+ example += "# appname: #{complexity}\n"
262
+ example += "# #{cfg_plural}:\n"
263
+ firstline = true
264
+ erb.result(binding).split(/\n/).each { |l|
265
+ l.chomp!
266
+ l.sub!(/#.*/, "") if !l.match(/#(?:INTERNET|NAT|DENY)/)
267
+ next if l.empty? or l.match(/^\s+$/)
268
+ if firstline
269
+ l = "- "+l
270
+ firstline = false
271
+ else
272
+ l = " "+l
273
+ end
274
+ example += "# "+l+" "+"\n"
275
+ }
276
+ example += "# &nbsp;\n#\n" if complexity == "simple"
277
+ }
278
+ schema["properties"][name]["items"]["description"] ||= ""
279
+ if !schema["properties"][name]["items"]["description"].empty?
280
+ schema["properties"][name]["items"]["description"] += "\n"
281
+ end
282
+ schema["properties"][name]["items"]["description"] += example
283
+ end
284
+ end
285
+ end
286
+
287
+ if !schema["required"].nil? and schema["required"].include?(name)
288
+ printme << "# @!group Required parameters" if !req
289
+ req = true
290
+ else
291
+ if req
292
+ printme << "# @!endgroup"
293
+ printme << "# @!group Optional parameters"
294
+ end
295
+ req = false
296
+ end
297
+
298
+ printme << self.printSchema(kitten_rb, class_hierarchy+ [name], prop, false, req, prefix: schema["prefix"])
299
+ }
300
+ printme << "# @!endgroup"
301
+ end
302
+
303
+ tabs = 1
304
+ class_hierarchy.each { |classname|
305
+ if classname == class_hierarchy.last and !schema['description'].nil?
306
+ kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + "# #{schema['description']}\n"
307
+ end
308
+ kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + "class #{classname}"
309
+ tabs = tabs + 1
310
+ }
311
+ printme.each { |lines|
312
+ if !lines.nil? and lines.is_a?(String)
313
+ lines.lines.each { |line|
314
+ kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + line
315
+ }
316
+ end
317
+ }
318
+
319
+ i = class_hierarchy.size
320
+ until i == 0 do
321
+ tabs = tabs - 1
322
+ kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + "end"
323
+ i -= 1
324
+ end
325
+
326
+ # And now that we've dealt with our children, pass our own rendered
327
+ # commentary back up to our caller.
328
+ name = class_hierarchy.last
329
+ if in_array
330
+ type = "Array<#{class_hierarchy.join("::")}>"
331
+ else
332
+ type = class_hierarchy.join("::")
333
+ end
334
+
335
+ docstring = "\n"
336
+ docstring = docstring + "# **REQUIRED**\n" if required
337
+ docstring = docstring + "# **"+schema["prefix"]+"**\n" if schema["prefix"]
338
+ docstring = docstring + "# #{schema['description'].gsub(/\n/, "\n#")}\n" if !schema['description'].nil?
339
+ docstring = docstring + "#\n"
340
+ docstring = docstring + "# @return [#{type}]\n"
341
+ docstring = docstring + "# @see #{class_hierarchy.join("::")}\n"
342
+ docstring = docstring + "attr_accessor :#{name}"
343
+ return docstring
344
+
345
+ elsif schema["type"] == "array"
346
+ return self.printSchema(kitten_rb, class_hierarchy, schema['items'], true, required, prefix: prefix)
347
+ else
348
+ name = class_hierarchy.last
349
+ if schema['type'].nil?
350
+ MU.log "Couldn't determine schema type in #{class_hierarchy.join(" => ")}", MU::WARN, details: schema
351
+ return nil
352
+ end
353
+ if in_array
354
+ type = "Array<#{schema['type'].capitalize}>"
355
+ else
356
+ type = schema['type'].capitalize
357
+ end
358
+ docstring = "\n"
359
+
360
+ prefixes = []
361
+ prefixes << "# **REQUIRED**" if required and schema['default'].nil?
362
+ prefixes << "# **"+schema["prefix"]+"**" if schema["prefix"]
363
+ prefixes << "# **Default: `#{schema['default']}`**" if !schema['default'].nil?
364
+ if !schema['enum'].nil? and !schema["enum"].empty?
365
+ prefixes << "# **Must be one of: `#{schema['enum'].join(', ')}`**"
366
+ elsif !schema['pattern'].nil?
367
+ # XXX unquoted regex chars confuse the hell out of YARD. How do we
368
+ # quote {}[] etc in YARD-speak?
369
+ prefixes << "# **Must match pattern `#{schema['pattern'].gsub(/\n/, "\n#")}`**"
370
+ end
371
+
372
+ if prefixes.size > 0
373
+ docstring += prefixes.join(",\n")
374
+ if schema['description'] and schema['description'].size > 1
375
+ docstring += " - "
376
+ end
377
+ docstring += "\n"
378
+ end
379
+
380
+ docstring = docstring + "# #{schema['description'].gsub(/\n/, "\n#")}\n" if !schema['description'].nil?
381
+ docstring = docstring + "#\n"
382
+ docstring = docstring + "# @return [#{type}]\n"
383
+ docstring = docstring + "attr_accessor :#{name}"
384
+
385
+ return docstring
386
+ end
387
+ end
388
+
389
+ # Emit our mu.yaml schema in a format that YARD can comprehend and turn into
390
+ # documentation.
391
+ def self.printMuYamlSchema(muyaml_rb, class_hierarchy, schema, in_array = false, required = false)
392
+ return if schema.nil?
393
+ if schema["subtree"]
394
+ printme = Array.new
395
+ # order sub-elements by whether they're required, so we can use YARD's
396
+ # grouping tags on them
397
+ have_required = schema["subtree"].keys.any? { |k| schema["subtree"][k]["required"] }
398
+ prop_list = schema["subtree"].keys.sort { |a, b|
399
+ if schema["subtree"][a]["required"] and !schema["subtree"][b]["required"]
400
+ -1
401
+ elsif !schema["subtree"][a]["required"] and schema["subtree"][b]["required"]
402
+ 1
403
+ else
404
+ a <=> b
405
+ end
406
+ }
407
+
408
+ req = false
409
+ printme << "# @!group Optional parameters" if !have_required
410
+ prop_list.each { |name|
411
+ prop = schema["subtree"][name]
412
+ if prop["required"]
413
+ printme << "# @!group Required parameters" if !req
414
+ req = true
415
+ else
416
+ if req
417
+ printme << "# @!endgroup"
418
+ printme << "# @!group Optional parameters"
419
+ end
420
+ req = false
421
+ end
422
+
423
+ printme << self.printMuYamlSchema(muyaml_rb, class_hierarchy+ [name], prop, false, req)
424
+ }
425
+ printme << "# @!endgroup"
426
+
427
+ desc = (schema['desc'] || schema['title'])
428
+
429
+ tabs = 1
430
+ class_hierarchy.each { |classname|
431
+ if classname == class_hierarchy.last and desc
432
+ muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "# #{desc}\n"
433
+ end
434
+ muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "class #{classname}"
435
+ tabs = tabs + 1
436
+ }
437
+ printme.each { |lines|
438
+ if !lines.nil? and lines.is_a?(String)
439
+ lines.lines.each { |line|
440
+ muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + line
441
+ }
442
+ end
443
+ }
444
+
445
+ # class_hierarchy.each { |classname|
446
+ # tabs = tabs - 1
447
+ # muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "end"
448
+ # }
449
+ i = class_hierarchy.size
450
+ until i == 0 do
451
+ tabs = tabs - 1
452
+ muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "end"
453
+ i -= 1
454
+ end
455
+
456
+ # And now that we've dealt with our children, pass our own rendered
457
+ # commentary back up to our caller.
458
+ name = class_hierarchy.last
459
+ if in_array
460
+ type = "Array<#{class_hierarchy.join("::")}>"
461
+ else
462
+ type = class_hierarchy.join("::")
463
+ end
464
+
465
+ docstring = "\n"
466
+ docstring = docstring + "# **REQUIRED**\n" if required
467
+ # docstring = docstring + "# **"+schema["prefix"]+"**\n" if schema["prefix"]
468
+ docstring = docstring + "# #{desc.gsub(/\n/, "\n#")}\n" if desc
469
+ docstring = docstring + "#\n"
470
+ docstring = docstring + "# @return [#{type}]\n"
471
+ docstring = docstring + "# @see #{class_hierarchy.join("::")}\n"
472
+ docstring = docstring + "attr_accessor :#{name}"
473
+ return docstring
474
+
475
+ else
476
+ in_array = schema["array"]
477
+ name = class_hierarchy.last
478
+ type = if schema['boolean']
479
+ "Boolean"
480
+ else
481
+ "String"
482
+ end
483
+ if in_array
484
+ type = "Array<#{type}>"
485
+ end
486
+ docstring = "\n"
487
+
488
+ prefixes = []
489
+ prefixes << "# **REQUIRED**" if schema["required"] and schema['default'].nil?
490
+ # prefixes << "# **"+schema["prefix"]+"**" if schema["prefix"]
491
+ prefixes << "# **Default: `#{schema['default']}`**" if !schema['default'].nil?
492
+ if !schema['pattern'].nil?
493
+ # XXX unquoted regex chars confuse the hell out of YARD. How do we
494
+ # quote {}[] etc in YARD-speak?
495
+ prefixes << "# **Must match pattern `#{schema['pattern'].to_s.gsub(/\n/, "\n#")}`**"
496
+ end
497
+
498
+ desc = (schema['desc'] || schema['title'])
499
+ if prefixes.size > 0
500
+ docstring += prefixes.join(",\n")
501
+ if desc and desc.size > 1
502
+ docstring += " - "
503
+ end
504
+ docstring += "\n"
505
+ end
506
+
507
+ docstring = docstring + "# #{desc.gsub(/\n/, "\n#")}\n" if !desc.nil?
508
+ docstring = docstring + "#\n"
509
+ docstring = docstring + "# @return [#{type}]\n"
510
+ docstring = docstring + "attr_accessor :#{name}"
511
+
512
+ return docstring
513
+ end
514
+ end
515
+
516
+ end #class
517
+ end #module