cloud-mu 3.1.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +10 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -3
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +135 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +165 -111
  45. data/modules/mu/adoption.rb +401 -68
  46. data/modules/mu/cleanup.rb +199 -306
  47. data/modules/mu/cloud.rb +100 -1632
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +46 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +920 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +165 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +4 -4
  61. data/modules/mu/config/cache_cluster.rb +1 -1
  62. data/modules/mu/config/collection.rb +4 -4
  63. data/modules/mu/config/container_cluster.rb +9 -4
  64. data/modules/mu/config/database.rb +83 -104
  65. data/modules/mu/config/database.yml +1 -2
  66. data/modules/mu/config/dnszone.rb +6 -6
  67. data/modules/mu/config/doc_helpers.rb +516 -0
  68. data/modules/mu/config/endpoint.rb +4 -4
  69. data/modules/mu/config/firewall_rule.rb +103 -4
  70. data/modules/mu/config/folder.rb +4 -4
  71. data/modules/mu/config/function.rb +3 -3
  72. data/modules/mu/config/group.rb +4 -4
  73. data/modules/mu/config/habitat.rb +4 -4
  74. data/modules/mu/config/loadbalancer.rb +60 -14
  75. data/modules/mu/config/log.rb +4 -4
  76. data/modules/mu/config/msg_queue.rb +4 -4
  77. data/modules/mu/config/nosqldb.rb +4 -4
  78. data/modules/mu/config/notifier.rb +3 -3
  79. data/modules/mu/config/ref.rb +365 -0
  80. data/modules/mu/config/role.rb +4 -4
  81. data/modules/mu/config/schema_helpers.rb +509 -0
  82. data/modules/mu/config/search_domain.rb +4 -4
  83. data/modules/mu/config/server.rb +97 -70
  84. data/modules/mu/config/server.yml +1 -0
  85. data/modules/mu/config/server_pool.rb +5 -9
  86. data/modules/mu/config/storage_pool.rb +1 -1
  87. data/modules/mu/config/tail.rb +200 -0
  88. data/modules/mu/config/user.rb +4 -4
  89. data/modules/mu/config/vpc.rb +70 -27
  90. data/modules/mu/config/vpc.yml +0 -1
  91. data/modules/mu/defaults/AWS.yaml +83 -60
  92. data/modules/mu/defaults/Azure.yaml +1 -0
  93. data/modules/mu/defaults/Google.yaml +3 -2
  94. data/modules/mu/deploy.rb +30 -26
  95. data/modules/mu/groomer.rb +17 -2
  96. data/modules/mu/groomers/ansible.rb +188 -41
  97. data/modules/mu/groomers/chef.rb +116 -55
  98. data/modules/mu/logger.rb +127 -148
  99. data/modules/mu/master.rb +389 -2
  100. data/modules/mu/master/chef.rb +3 -4
  101. data/modules/mu/master/ldap.rb +3 -3
  102. data/modules/mu/master/ssl.rb +12 -3
  103. data/modules/mu/mommacat.rb +217 -2612
  104. data/modules/mu/mommacat/daemon.rb +397 -0
  105. data/modules/mu/mommacat/naming.rb +473 -0
  106. data/modules/mu/mommacat/search.rb +495 -0
  107. data/modules/mu/mommacat/storage.rb +722 -0
  108. data/modules/mu/{clouds → providers}/README.md +1 -1
  109. data/modules/mu/{clouds → providers}/aws.rb +271 -112
  110. data/modules/mu/{clouds → providers}/aws/alarm.rb +5 -3
  111. data/modules/mu/{clouds → providers}/aws/bucket.rb +26 -22
  112. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +33 -67
  113. data/modules/mu/{clouds → providers}/aws/collection.rb +24 -23
  114. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +681 -721
  115. data/modules/mu/providers/aws/database.rb +1744 -0
  116. data/modules/mu/{clouds → providers}/aws/dnszone.rb +64 -63
  117. data/modules/mu/{clouds → providers}/aws/endpoint.rb +22 -27
  118. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +214 -244
  119. data/modules/mu/{clouds → providers}/aws/folder.rb +7 -7
  120. data/modules/mu/{clouds → providers}/aws/function.rb +17 -22
  121. data/modules/mu/{clouds → providers}/aws/group.rb +23 -23
  122. data/modules/mu/{clouds → providers}/aws/habitat.rb +17 -14
  123. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +57 -48
  124. data/modules/mu/{clouds → providers}/aws/log.rb +15 -12
  125. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +17 -16
  126. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +18 -11
  127. data/modules/mu/{clouds → providers}/aws/notifier.rb +11 -6
  128. data/modules/mu/{clouds → providers}/aws/role.rb +112 -86
  129. data/modules/mu/{clouds → providers}/aws/search_domain.rb +39 -33
  130. data/modules/mu/{clouds → providers}/aws/server.rb +835 -1133
  131. data/modules/mu/{clouds → providers}/aws/server_pool.rb +56 -60
  132. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +24 -42
  133. data/modules/mu/{clouds → providers}/aws/user.rb +21 -22
  134. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  135. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  136. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  137. data/modules/mu/{clouds → providers}/aws/vpc.rb +523 -929
  138. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  139. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  140. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  141. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  142. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  143. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  144. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  145. data/modules/mu/{clouds → providers}/azure/server.rb +95 -48
  146. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  147. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  148. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  149. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  150. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  151. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  152. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  153. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  156. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  160. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  161. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  162. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  163. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  164. data/modules/mu/{clouds → providers}/google.rb +67 -30
  165. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  166. data/modules/mu/{clouds → providers}/google/container_cluster.rb +84 -77
  167. data/modules/mu/{clouds → providers}/google/database.rb +10 -20
  168. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  169. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  170. data/modules/mu/{clouds → providers}/google/function.rb +139 -167
  171. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  172. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  173. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +18 -20
  174. data/modules/mu/{clouds → providers}/google/role.rb +92 -58
  175. data/modules/mu/{clouds → providers}/google/server.rb +242 -155
  176. data/modules/mu/{clouds → providers}/google/server_pool.rb +25 -44
  177. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  178. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  179. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  180. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  181. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  182. data/modules/tests/bucket.yml +4 -0
  183. data/modules/tests/centos6.yaml +11 -0
  184. data/modules/tests/centos7.yaml +11 -0
  185. data/modules/tests/centos8.yaml +12 -0
  186. data/modules/tests/ecs.yaml +23 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/rds.yaml +108 -0
  189. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  190. data/modules/tests/regrooms/bucket.yml +19 -0
  191. data/modules/tests/regrooms/rds.yaml +123 -0
  192. data/modules/tests/server-with-scrub-muisms.yaml +1 -0
  193. data/modules/tests/super_simple_bok.yml +1 -3
  194. data/modules/tests/win2k12.yaml +17 -5
  195. data/modules/tests/win2k16.yaml +25 -0
  196. data/modules/tests/win2k19.yaml +25 -0
  197. data/requirements.txt +1 -0
  198. data/spec/mu/clouds/azure_spec.rb +2 -2
  199. metadata +232 -154
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1985
@@ -14,7 +14,7 @@
14
14
 
15
15
  module MU
16
16
  class Config
17
- # Basket of Kittens config schema and parser logic. See modules/mu/clouds/*/notifier.rb
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/notifier.rb
18
18
  class Notifier
19
19
 
20
20
  # Base configuration schema for a Notifier
@@ -51,9 +51,9 @@ module MU
51
51
 
52
52
  # Generic pre-processing of {MU::Config::BasketofKittens::notifiers}, bare and unvalidated.
53
53
  # @param notifier [Hash]: The resource to process and validate
54
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
54
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
55
55
  # @return [Boolean]: True if validation succeeded, False otherwise
56
- def self.validate(notifier, configurator)
56
+ def self.validate(notifier, _configurator)
57
57
  ok = true
58
58
 
59
59
  if notifier['subscriptions']
@@ -0,0 +1,365 @@
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
+ # A wrapper class for resources to refer to other resources, whether they
21
+ # be a sibling object in the current deploy, an object in another deploy,
22
+ # or a plain cloud id from outside of Mu.
23
+ class Ref
24
+ attr_reader :name
25
+ attr_reader :type
26
+ attr_reader :cloud
27
+ attr_reader :deploy_id
28
+ attr_reader :region
29
+ attr_reader :credentials
30
+ attr_reader :habitat
31
+ attr_reader :mommacat
32
+ attr_reader :tag_key
33
+ attr_reader :tag_value
34
+ attr_reader :obj
35
+
36
+ @@refs = []
37
+ @@ref_semaphore = Mutex.new
38
+
39
+ # Little bit of a factory pattern... given a hash of options for a {MU::Config::Ref} objects, first see if we have an existing one that matches our more immutable attributes (+cloud+, +id+, etc). If we do, return that. If we do not, create one, add that to our inventory, and return that instead.
40
+ # @param cfg [Hash]:
41
+ # @return [MU::Config::Ref]
42
+ def self.get(cfg)
43
+ return cfg if cfg.is_a?(MU::Config::Ref)
44
+ checkfields = cfg.keys.map { |k| k.to_sym }
45
+ required = [:id, :type]
46
+
47
+ @@ref_semaphore.synchronize {
48
+ @@refs.each { |ref|
49
+ saw_mismatch = false
50
+ saw_match = false
51
+ needed_values = []
52
+ checkfields.each { |field|
53
+ next if !cfg[field]
54
+ ext_value = ref.instance_variable_get("@#{field.to_s}".to_sym)
55
+ if !ext_value
56
+ needed_values << field
57
+ next
58
+ end
59
+ if cfg[field] != ext_value
60
+ saw_mismatch = true
61
+ elsif required.include?(field) and cfg[field] == ext_value
62
+ saw_match = true
63
+ end
64
+ }
65
+ if saw_match and !saw_mismatch
66
+ # populate empty fields we got from this request
67
+ if needed_values.size > 0
68
+ newref = ref.dup
69
+ needed_values.each { |field|
70
+ newref.instance_variable_set("@#{field.to_s}".to_sym, cfg[field])
71
+ if !newref.respond_to?(field)
72
+ newref.singleton_class.instance_eval { attr_reader field.to_sym }
73
+ end
74
+ }
75
+ @@refs << newref
76
+ return newref
77
+ else
78
+ return ref
79
+ end
80
+ end
81
+ }
82
+
83
+ }
84
+
85
+ # if we get here, there was no match
86
+ newref = MU::Config::Ref.new(cfg)
87
+ @@ref_semaphore.synchronize {
88
+ @@refs << newref
89
+ return newref
90
+ }
91
+ end
92
+
93
+ # A way of dynamically defining +attr_reader+ without leaking memory
94
+ def self.define_reader(name)
95
+ define_method(name) {
96
+ instance_variable_get("@#{name.to_s}")
97
+ }
98
+ end
99
+
100
+ # @param cfg [Hash]: A Basket of Kittens configuration hash containing
101
+ # lookup information for a cloud object
102
+ def initialize(cfg)
103
+ cfg.keys.each { |field|
104
+ next if field == "tag"
105
+ if !cfg[field].nil?
106
+ self.instance_variable_set("@#{field}".to_sym, cfg[field])
107
+ elsif !cfg[field.to_sym].nil?
108
+ self.instance_variable_set("@#{field.to_s}".to_sym, cfg[field.to_sym])
109
+ end
110
+ MU::Config::Ref.define_reader(field)
111
+ }
112
+ if cfg['tag'] and cfg['tag']['key'] and
113
+ !cfg['tag']['key'].empty? and cfg['tag']['value']
114
+ @tag_key = cfg['tag']['key']
115
+ @tag_value = cfg['tag']['value']
116
+ end
117
+
118
+ if @deploy_id and !@mommacat
119
+ @mommacat = MU::MommaCat.getLitter(@deploy_id, set_context_to_me: false)
120
+ elsif @mommacat and !@deploy_id
121
+ @deploy_id = @mommacat.deploy_id
122
+ end
123
+
124
+ kitten(shallow: true) if @mommacat # try to populate the actual cloud object for this
125
+ end
126
+
127
+ # Comparison operator
128
+ def <=>(other)
129
+ return 1 if other.nil?
130
+ self.to_s <=> other.to_s
131
+ end
132
+
133
+ # Lets callers access us like a {Hash}
134
+ # @param attribute [String,Symbol]
135
+ def [](attribute)
136
+ if respond_to?(attribute.to_sym)
137
+ send(attribute.to_sym)
138
+ else
139
+ nil
140
+ end
141
+ end
142
+
143
+ # Unset an attribute. Sort of. We can't actually do that, so nil it out
144
+ # and we get the behavior we want.
145
+ def delete(attribute)
146
+ attribute = ("@"+attribute).to_sym if attribute.to_s !~ /^@/
147
+ instance_variable_set(attribute.to_sym, nil)
148
+ end
149
+
150
+ # Base configuration schema for declared kittens referencing other cloud objects. This is essentially a set of filters that we're going to pass to {MU::MommaCat.findStray}.
151
+ # @param aliases [Array<Hash>]: Key => value mappings to set backwards-compatibility aliases for attributes, such as the ubiquitous +vpc_id+ (+vpc_id+ => +id+).
152
+ # @return [Hash]
153
+ def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil, omit_fields: [])
154
+ parent_obj ||= caller[1].gsub(/.*?\/([^\.\/]+)\.rb:.*/, '\1')
155
+ desc ||= "Reference a #{type ? "'#{type}' resource" : "resource" } from this #{parent_obj ? "'#{parent_obj}'" : "" } resource"
156
+ schema = {
157
+ "type" => "object",
158
+ "#MU_REFERENCE" => true,
159
+ "minProperties" => 1,
160
+ "description" => desc,
161
+ "properties" => {
162
+ "id" => {
163
+ "type" => "string",
164
+ "description" => "Cloud identifier of a resource we want to reference, typically used when leveraging resources not managed by MU"
165
+ },
166
+ "name" => {
167
+ "type" => "string",
168
+ "description" => "The short (internal Mu) name of a resource we're attempting to reference. Typically used when referring to a sibling resource elsewhere in the same deploy, or in another known Mu deploy in conjunction with +deploy_id+."
169
+ },
170
+ "type" => {
171
+ "type" => "string",
172
+ "description" => "The resource type we're attempting to reference.",
173
+ "enum" => MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }
174
+ },
175
+ "deploy_id" => {
176
+ "type" => "string",
177
+ "description" => "Our target resource should be found in this Mu deploy."
178
+ },
179
+ "credentials" => MU::Config.credentials_primitive,
180
+ "region" => MU::Config.region_primitive,
181
+ "cloud" => MU::Config.cloud_primitive,
182
+ "tag" => {
183
+ "type" => "object",
184
+ "description" => "If the target resource supports tagging and our resource implementations +find+ method supports it, we can attempt to locate it by tag.",
185
+ "properties" => {
186
+ "key" => {
187
+ "type" => "string",
188
+ "description" => "The tag or label key to search against"
189
+ },
190
+ "value" => {
191
+ "type" => "string",
192
+ "description" => "The tag or label value to match"
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ if !["folders", "habitats"].include?(type)
199
+ schema["properties"]["habitat"] = MU::Config::Habitat.reference
200
+ end
201
+
202
+ if omit_fields
203
+ omit_fields.each { |f|
204
+ schema["properties"].delete(f)
205
+ }
206
+ end
207
+
208
+ if !type.nil?
209
+ schema["required"] = ["type"]
210
+ schema["properties"]["type"]["default"] = type
211
+ schema["properties"]["type"]["enum"] = [type]
212
+ end
213
+
214
+ aliases.each { |a|
215
+ a.each_pair { |k, v|
216
+ if schema["properties"][v]
217
+ schema["properties"][k] = schema["properties"][v].dup
218
+ schema["properties"][k]["description"] = "Alias for <tt>#{v}</tt>"
219
+ else
220
+ MU.log "Reference schema alias #{k} wants to alias #{v}, but no such attribute exists", MU::WARN, details: caller[4]
221
+ end
222
+ }
223
+ }
224
+
225
+ schema
226
+ end
227
+
228
+ # Decompose into a plain-jane {MU::Config::BasketOfKittens} hash fragment,
229
+ # of the sort that would have been used to declare this reference in the
230
+ # first place.
231
+ def to_h
232
+ me = { }
233
+
234
+ self.instance_variables.each { |var|
235
+ next if [:@obj, :@mommacat, :@tag_key, :@tag_value].include?(var)
236
+ val = self.instance_variable_get(var)
237
+ next if val.nil?
238
+ val = val.to_h if val.is_a?(MU::Config::Ref)
239
+ me[var.to_s.sub(/^@/, '')] = val
240
+ }
241
+ if @tag_key and !@tag_key.empty?
242
+ me['tag'] = {
243
+ 'key' => @tag_key,
244
+ 'value' => @tag_value
245
+ }
246
+ end
247
+ me
248
+ end
249
+
250
+ # Getter for the #{id} instance variable that attempts to populate it if
251
+ # it's not set.
252
+ # @return [String,nil]
253
+ def id
254
+ return @id if @id
255
+ kitten # if it's not defined, attempt to define it
256
+ @id
257
+ end
258
+
259
+ # Alias for {id}
260
+ # @return [String,nil]
261
+ def cloud_id
262
+ id
263
+ end
264
+
265
+ # Return a {MU::Cloud} object for this reference. This is only meant to be
266
+ # called in a live deploy, which is to say that if called during initial
267
+ # configuration parsing, results may be incorrect.
268
+ # @param mommacat [MU::MommaCat]: A deploy object which will be searched for the referenced resource if provided, before restoring to broader, less efficient searches.
269
+ def kitten(mommacat = @mommacat, shallow: false, debug: false)
270
+ return nil if !@cloud or !@type
271
+ loglevel = debug ? MU::NOTICE : MU::DEBUG
272
+
273
+ if @obj
274
+ @deploy_id ||= @obj.deploy_id
275
+ @id ||= @obj.cloud_id
276
+ @name ||= @obj.config['name'] if @obj.config
277
+ return @obj
278
+ end
279
+
280
+ if mommacat and !caller.grep(/`findLitterMate'/) # XXX the dumbest
281
+ MU.log "Looking for #{@type} #{@name} #{@id} in deploy #{mommacat.deploy_id}", loglevel
282
+ begin
283
+ @obj = mommacat.findLitterMate(type: @type, name: @name, cloud_id: @id, credentials: @credentials, debug: debug)
284
+ rescue StandardError => e
285
+ if e.message =~ /deadlock/
286
+ MU.log "Saw a recursive deadlock trying to fetch kitten for Ref object in deploy #{mmommacat.deploy_id}", MU::ERR, details: to_h
287
+ end
288
+ raise e
289
+ end
290
+ if @obj # initialize missing attributes, if we can
291
+ @id ||= @obj.cloud_id
292
+ @mommacat ||= mommacat
293
+ @obj.intoDeploy(@mommacat) # make real sure these are set
294
+ @deploy_id ||= mommacat.deploy_id
295
+
296
+ if !@name
297
+ if @obj.config and @obj.config['name']
298
+ @name = @obj.config['name']
299
+ elsif @obj.mu_name
300
+ if @type == "folders"
301
+ MU.log "would assign name '#{@obj.mu_name}' in ref to this folder if I were feeling aggressive", MU::WARN, details: self.to_h
302
+ end
303
+ # @name = @obj.mu_name
304
+ end
305
+ end
306
+ return @obj
307
+ else
308
+ # MU.log "Failed to find a live '#{@type.to_s}' object named #{@name}#{@id ? " (#{@id})" : "" }#{ @habitat ? " in habitat #{@habitat}" : "" }", MU::WARN, details: self
309
+ end
310
+ end
311
+
312
+ if !@obj and !(@cloud == "Google" and @id and @type == "users" and MU::Cloud.resourceClass("Google", "User").cannedServiceAcctName?(@id)) and !shallow
313
+ try_deploy_id = @deploy_id
314
+
315
+ begin
316
+ hab_arg = if @habitat.nil?
317
+ [nil]
318
+ elsif @habitat.is_a?(MU::Config::Ref)
319
+ [@habitat.id]
320
+ elsif @habitat.is_a?(Hash)
321
+ [@habitat["id"]]
322
+ else
323
+ [@habitat.to_s]
324
+ end
325
+
326
+ found = MU::MommaCat.findStray(
327
+ @cloud,
328
+ @type,
329
+ name: @name,
330
+ cloud_id: @id,
331
+ deploy_id: try_deploy_id,
332
+ region: @region,
333
+ habitats: hab_arg,
334
+ credentials: @credentials,
335
+ dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type))
336
+ )
337
+ @obj ||= found.first if found
338
+ rescue MU::MommaCat::MultipleMatches => e
339
+ if try_deploy_id.nil? and MU.deploy_id
340
+ MU.log "Attempting to narrow down #{@cloud} #{@type} to #{MU.deploy_id}", MU::NOTICE
341
+ try_deploy_id = MU.deploy_id
342
+ retry
343
+ else
344
+ raise e
345
+ end
346
+ rescue ThreadError => e
347
+ # Sometimes MommaCat calls us in a potential deadlock situation;
348
+ # don't be the cause of a fatal error if so, we don't need this
349
+ # object that badly.
350
+ raise e if !e.message.match(/recursive locking/)
351
+ end
352
+ end
353
+
354
+ if @obj
355
+ @deploy_id ||= @obj.deploy_id
356
+ @id ||= @obj.cloud_id
357
+ @name ||= @obj.config['name']
358
+ end
359
+
360
+ @obj
361
+ end
362
+
363
+ end
364
+ end
365
+ end
@@ -14,7 +14,7 @@
14
14
 
15
15
  module MU
16
16
  class Config
17
- # Basket of Kittens config schema and parser logic. See modules/mu/clouds/*/role.rb
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/role.rb
18
18
  class Role
19
19
 
20
20
  # Base configuration schema for a Group
@@ -131,10 +131,10 @@ module MU
131
131
  end
132
132
 
133
133
  # Generic pre-processing of {MU::Config::BasketofKittens::role}, bare and unvalidated.
134
- # @param role [Hash]: The resource to process and validate
135
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
134
+ # @param _role [Hash]: The resource to process and validate
135
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
136
136
  # @return [Boolean]: True if validation succeeded, False otherwise
137
- def self.validate(role, configurator)
137
+ def self.validate(_role, _configurator)
138
138
  ok = true
139
139
  ok
140
140
  end
@@ -0,0 +1,509 @@
1
+ # Copyright:: Copyright (c) 2014 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
+ # The default cloud provider for new resources. Must exist in MU.supportedClouds
21
+ # return [String]
22
+ def self.defaultCloud
23
+ configured = {}
24
+ MU::Cloud.supportedClouds.each { |cloud|
25
+ if $MU_CFG[cloud.downcase] and !$MU_CFG[cloud.downcase].empty?
26
+ configured[cloud] = $MU_CFG[cloud.downcase].size
27
+ configured[cloud] += 0.5 if MU::Cloud.cloudClass(cloud).hosted? # tiebreaker
28
+ end
29
+ }
30
+ if configured.size > 0
31
+ return configured.keys.sort { |a, b|
32
+ configured[b] <=> configured[a]
33
+ }.first
34
+ else
35
+ MU::Cloud.supportedClouds.each { |cloud|
36
+ return cloud if MU::Cloud.cloudClass(cloud).hosted?
37
+ }
38
+ return MU::Cloud.supportedClouds.first
39
+ end
40
+ end
41
+
42
+ # The default grooming agent for new resources. Must exist in MU.supportedGroomers.
43
+ def self.defaultGroomer
44
+ MU.localOnly ? "Ansible" : "Chef"
45
+ end
46
+
47
+ # Accessor for our Basket of Kittens schema definition
48
+ def self.schema
49
+ @@schema
50
+ end
51
+
52
+ # Deep merge a configuration hash so we can meld different cloud providers'
53
+ # schemas together, while preserving documentation differences
54
+ def self.schemaMerge(orig, new, cloud)
55
+ if new.is_a?(Hash)
56
+ new.each_pair { |k, v|
57
+ if cloud and k == "description" and v.is_a?(String) and !v.match(/\b#{Regexp.quote(cloud.upcase)}\b/) and !v.empty?
58
+ new[k] = "+"+cloud.upcase+"+: "+v
59
+ end
60
+ if orig and orig.has_key?(k)
61
+ elsif orig
62
+ orig[k] = new[k]
63
+ else
64
+ orig = new
65
+ end
66
+ schemaMerge(orig[k], new[k], cloud)
67
+ }
68
+ elsif orig.is_a?(Array) and new
69
+ orig.concat(new)
70
+ orig.uniq!
71
+ elsif new.is_a?(String)
72
+ orig ||= ""
73
+ orig += "\n" if !orig.empty?
74
+ orig += "+#{cloud.upcase}+: "+new
75
+ else
76
+ # XXX I think this is a NOOP?
77
+ end
78
+ end
79
+
80
+ @@allregions = []
81
+ @@loadfails = []
82
+ MU::Cloud.availableClouds.each { |cloud|
83
+ next if @@loadfails.include?(cloud)
84
+ begin
85
+ regions = MU::Cloud.cloudClass(cloud).listRegions()
86
+ @@allregions.concat(regions) if regions
87
+ rescue MU::MuError => e
88
+ @@loadfails << cloud
89
+ MU.log e.message, MU::WARN
90
+ end
91
+ }
92
+
93
+ # Configuration chunk for choosing a provider region
94
+ # @return [Hash]
95
+ def self.region_primitive
96
+ if !@@allregions or @@allregions.empty?
97
+ @@allregions = []
98
+ MU::Cloud.availableClouds.each { |cloud|
99
+ next if @@loadfails.include?(cloud)
100
+ cloudclass = MU::Cloud.cloudClass(cloud)
101
+ begin
102
+ return @@allregions if !cloudclass.listRegions()
103
+ @@allregions.concat(cloudclass.listRegions())
104
+ rescue MU::MuError => e
105
+ @@loadfails << cloud
106
+ MU.log e.message, MU::WARN
107
+ end
108
+ }
109
+ end
110
+ {
111
+ "type" => "string",
112
+ "enum" => @@allregions
113
+ }
114
+ end
115
+
116
+ # Configuration chunk for choosing a set of cloud credentials
117
+ # @return [Hash]
118
+ def self.credentials_primitive
119
+ {
120
+ "type" => "string",
121
+ "description" => "Specify a non-default set of credentials to use when authenticating to cloud provider APIs, as listed in `mu.yaml` under each provider's subsection. If "
122
+ }
123
+ end
124
+
125
+ # Configuration chunk for creating resource tags as an array of key/value
126
+ # pairs.
127
+ # @return [Hash]
128
+ def self.optional_tags_primitive
129
+ {
130
+ "type" => "boolean",
131
+ "description" => "Tag the resource with our optional tags (+MU-HANDLE+, +MU-MASTER-NAME+, +MU-OWNER+).",
132
+ "default" => true
133
+ }
134
+ end
135
+
136
+ # Configuration chunk for creating resource tags as an array of key/value
137
+ # pairs.
138
+ # @return [Hash]
139
+ def self.tags_primitive
140
+ {
141
+ "type" => "array",
142
+ "minItems" => 1,
143
+ "items" => {
144
+ "description" => "Tags to apply to this resource. Will apply at the cloud provider level and in node groomers, where applicable.",
145
+ "type" => "object",
146
+ "title" => "tags",
147
+ "required" => ["key", "value"],
148
+ "additionalProperties" => false,
149
+ "properties" => {
150
+ "key" => {
151
+ "type" => "string",
152
+ },
153
+ "value" => {
154
+ "type" => "string",
155
+ }
156
+ }
157
+ }
158
+ }
159
+ end
160
+
161
+ # Configuration chunk for choosing a cloud provider
162
+ # @return [Hash]
163
+ def self.cloud_primitive
164
+ {
165
+ "type" => "string",
166
+ # "default" => MU::Config.defaultCloud, # applyInheritedDefaults does this better
167
+ "enum" => MU::Cloud.supportedClouds
168
+ }
169
+ end
170
+
171
+
172
+ # JSON-schema for resource dependencies
173
+ # @return [Hash]
174
+ def self.dependencies_primitive
175
+ {
176
+ "type" => "array",
177
+ "items" => {
178
+ "type" => "object",
179
+ "description" => "Declare other objects which this resource requires. This resource will wait until the others are available to create itself.",
180
+ "required" => ["name", "type"],
181
+ "additionalProperties" => false,
182
+ "properties" => {
183
+ "name" => {"type" => "string"},
184
+ "type" => {
185
+ "type" => "string",
186
+ "enum" => MU::Cloud.resource_types.values.map { |v| v[:cfg_name] }
187
+ },
188
+ "phase" => {
189
+ "type" => "string",
190
+ "description" => "Which part of the creation process of the resource we depend on should we wait for before starting our own creation? Defaults are usually sensible, but sometimes you want, say, a Server to wait on another Server to be completely ready (through its groom phase) before starting up.",
191
+ "enum" => ["create", "groom"]
192
+ },
193
+ "no_create_wait" => {
194
+ "type" => "boolean",
195
+ "default" => false,
196
+ "description" => "By default, it's assumed that we want to wait on our parents' creation phase, in addition to whatever is declared in this stanza. Setting this flag will bypass waiting on our parent resource's creation, so that our create or groom phase can instead depend only on the parent's groom phase. "
197
+ }
198
+ }
199
+ }
200
+ }
201
+ end
202
+
203
+ # Have a default value available for config schema elements that take an
204
+ # email address.
205
+ # @return [String]
206
+ def self.notification_email
207
+ if MU.chef_user == "mu"
208
+ ENV['MU_ADMIN_EMAIL']
209
+ else
210
+ MU.userEmail
211
+ end
212
+ end
213
+
214
+ # Load and validate the schema for an individual resource class, optionally
215
+ # merging cloud-specific schema components.
216
+ # @param type [String]: The resource type to load
217
+ # @param cloud [String]: A specific cloud, whose implementation's schema of this resource we will merge
218
+ # @return [Hash]
219
+ def self.loadResourceSchema(type, cloud: nil)
220
+ valid = true
221
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
222
+ schemaclass = Object.const_get("MU").const_get("Config").const_get(shortclass)
223
+
224
+ [:schema, :validate].each { |method|
225
+ if !schemaclass.respond_to?(method)
226
+ MU.log "MU::Config::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
227
+ return [nil, false] if method == :schema
228
+ valid = false
229
+ end
230
+ }
231
+
232
+ schema = schemaclass.schema.dup
233
+
234
+ schema["properties"]["virtual_name"] = {
235
+ "description" => "Internal use.",
236
+ "type" => "string"
237
+ }
238
+ schema["properties"]["dependencies"] = MU::Config.dependencies_primitive
239
+ schema["properties"]["cloud"] = MU::Config.cloud_primitive
240
+ schema["properties"]["credentials"] = MU::Config.credentials_primitive
241
+ schema["title"] = type.to_s
242
+
243
+ if cloud
244
+ cloudclass = MU::Cloud.resourceClass(cloud, type)
245
+
246
+ if cloudclass.respond_to?(:schema)
247
+ _reqd, cloudschema = cloudclass.schema
248
+ cloudschema.each { |key, cfg|
249
+ if schema["properties"][key]
250
+ schemaMerge(schema["properties"][key], cfg, cloud)
251
+ else
252
+ schema["properties"][key] = cfg.dup
253
+ end
254
+ }
255
+ else
256
+ MU.log "MU::Cloud::#{cloud}::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
257
+ valid = false
258
+ end
259
+
260
+ end
261
+
262
+ return [schema, valid]
263
+ end
264
+
265
+ private
266
+
267
+ def applySchemaDefaults(conf_chunk = config, schema_chunk = schema, depth = 0, siblings = nil, type: nil)
268
+ return if schema_chunk.nil?
269
+
270
+ if conf_chunk != nil and schema_chunk["properties"].kind_of?(Hash) and conf_chunk.is_a?(Hash)
271
+
272
+ if schema_chunk["properties"]["creation_style"].nil? or
273
+ schema_chunk["properties"]["creation_style"] != "existing"
274
+ schema_chunk["properties"].each_pair { |key, subschema|
275
+ shortclass = if conf_chunk[key]
276
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(key, false)
277
+ shortclass
278
+ else
279
+ nil
280
+ end
281
+
282
+ new_val = applySchemaDefaults(conf_chunk[key], subschema, depth+1, conf_chunk, type: shortclass).dup
283
+ if !new_val.nil?
284
+ begin
285
+ conf_chunk[key] = Marshal.load(Marshal.dump(new_val))
286
+ rescue TypeError
287
+ conf_chunk[key] = new_val.clone
288
+ end
289
+ end
290
+ }
291
+ end
292
+ elsif schema_chunk["type"] == "array" and conf_chunk.kind_of?(Array)
293
+ conf_chunk.map! { |item|
294
+ # If we're working on a resource type, go get implementation-specific
295
+ # schema information so that we set those defaults correctly.
296
+ realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
297
+
298
+ _toplevel_required, cloudschema = MU::Cloud.resourceClass(item["cloud"], type).schema(self)
299
+
300
+ newschema = schema_chunk["items"].dup
301
+ MU::Config.schemaMerge(newschema["properties"], cloudschema, item["cloud"])
302
+ newschema
303
+ else
304
+ schema_chunk["items"].dup
305
+ end
306
+
307
+ applySchemaDefaults(item, realschema, depth+1, conf_chunk, type: type).dup
308
+ }
309
+ else
310
+ if conf_chunk.nil? and !schema_chunk["default_if"].nil? and !siblings.nil?
311
+ schema_chunk["default_if"].each { |cond|
312
+ if siblings[cond["key_is"]] == cond["value_is"]
313
+ return Marshal.load(Marshal.dump(cond["set"]))
314
+ end
315
+ }
316
+ end
317
+ if conf_chunk.nil? and schema_chunk["default"] != nil
318
+ return Marshal.load(Marshal.dump(schema_chunk["default"]))
319
+ end
320
+ end
321
+
322
+ return conf_chunk
323
+ end
324
+
325
+ # Given a bare hash describing a resource, insert default values which can
326
+ # be inherited from its parent or from the root of the BoK.
327
+ # @param kitten [Hash]: A resource descriptor
328
+ # @param type [String]: The type of resource this is ("servers" etc)
329
+ def applyInheritedDefaults(kitten, type)
330
+ return if !kitten.is_a?(Hash)
331
+ kitten['cloud'] ||= @config['cloud']
332
+ kitten['cloud'] ||= MU::Config.defaultCloud
333
+
334
+ if !MU::Cloud.supportedClouds.include?(kitten['cloud'])
335
+ return
336
+ end
337
+
338
+ cloudclass = MU::Cloud.cloudClass(kitten['cloud'])
339
+
340
+ resclass = MU::Cloud.resourceClass(kitten['cloud'], type)
341
+
342
+ schema_fields = ["us_only", "scrub_mu_isms", "credentials", "billing_acct"]
343
+ if !resclass.isGlobal?
344
+ kitten['region'] ||= @config['region']
345
+ kitten['region'] ||= cloudclass.myRegion(kitten['credentials'])
346
+ schema_fields << "region"
347
+ end
348
+
349
+ kitten['credentials'] ||= @config['credentials']
350
+ kitten['credentials'] ||= cloudclass.credConfig(name_only: true)
351
+
352
+ kitten['us_only'] ||= @config['us_only']
353
+ kitten['us_only'] ||= false
354
+
355
+ kitten['scrub_mu_isms'] ||= @config['scrub_mu_isms']
356
+ kitten['scrub_mu_isms'] ||= false
357
+
358
+ if kitten['cloud'] == "Google"
359
+ # TODO this should be cloud-generic (handle AWS accounts, Azure subscriptions)
360
+ if resclass.canLiveIn.include?(:Habitat)
361
+ kitten["project"] ||= MU::Cloud::Google.defaultProject(kitten['credentials'])
362
+ schema_fields << "project"
363
+ end
364
+ if kitten['region'].nil? and !kitten['#MU_CLOUDCLASS'].nil? and
365
+ !resclass.isGlobal? and
366
+ ![MU::Cloud::VPC, MU::Cloud::FirewallRule].include?(kitten['#MU_CLOUDCLASS'])
367
+ if MU::Cloud::Google.myRegion((kitten['credentials'])).nil?
368
+ raise ValidationError, "Google '#{type}' resource '#{kitten['name']}' declared without a region, but no default Google region declared in mu.yaml under #{kitten['credentials'].nil? ? "default" : kitten['credentials']} credential set"
369
+ end
370
+ kitten['region'] ||= MU::Cloud::Google.myRegion
371
+ end
372
+ elsif kitten["cloud"] == "AWS" and !resclass.isGlobal? and !kitten['region']
373
+ if MU::Cloud::AWS.myRegion.nil?
374
+ raise ValidationError, "AWS resource declared without a region, but no default AWS region found"
375
+ end
376
+ kitten['region'] ||= MU::Cloud::AWS.myRegion
377
+ end
378
+
379
+
380
+ kitten['billing_acct'] ||= @config['billing_acct'] if @config['billing_acct']
381
+
382
+ kitten["dependencies"] ||= []
383
+
384
+ # Make sure the schema knows about these "new" fields, so that validation
385
+ # doesn't trip over them.
386
+ schema_fields.each { |field|
387
+ if @@schema["properties"][field]
388
+ MU.log "Adding #{field} to schema for #{type} #{kitten['cloud']}", MU::DEBUG, details: @@schema["properties"][field]
389
+ @@schema["properties"][type]["items"]["properties"][field] ||= @@schema["properties"][field]
390
+ end
391
+ }
392
+ end
393
+
394
+ CIDR_PATTERN = "^\\d+\\.\\d+\\.\\d+\\.\\d+\/[0-9]{1,2}$"
395
+ CIDR_DESCRIPTION = "CIDR-formatted IP block, e.g. 1.2.3.4/32"
396
+ CIDR_PRIMITIVE = {
397
+ "type" => "string",
398
+ "pattern" => CIDR_PATTERN,
399
+ "description" => CIDR_DESCRIPTION
400
+ }
401
+
402
+
403
+ @@schema = {
404
+ "$schema" => "http://json-schema.org/draft-04/schema#",
405
+ "title" => "MU Application",
406
+ "type" => "object",
407
+ "description" => "A MU application stack, consisting of at least one resource.",
408
+ "required" => ["admins", "appname"],
409
+ "properties" => {
410
+ "appname" => {
411
+ "type" => "string",
412
+ "description" => "A name for your application stack. Should be short, but easy to differentiate from other applications.",
413
+ },
414
+ "scrub_mu_isms" => {
415
+ "type" => "boolean",
416
+ "description" => "When 'cloud' is set to 'CloudFormation,' use this flag to strip out Mu-specific artifacts (tags, standard userdata, naming conventions, etc) to yield a clean, source-agnostic template. Setting this flag here will override declarations in individual resources."
417
+ },
418
+ "project" => {
419
+ "type" => "string",
420
+ "description" => "**GOOGLE ONLY**: The project into which to deploy resources"
421
+ },
422
+ "billing_acct" => {
423
+ "type" => "string",
424
+ "description" => "**GOOGLE ONLY**: Billing account ID to associate with a newly-created Google Project. If not specified, will attempt to locate a billing account associated with the default project for our credentials.",
425
+ },
426
+ "region" => MU::Config.region_primitive,
427
+ "credentials" => MU::Config.credentials_primitive,
428
+ "us_only" => {
429
+ "type" => "boolean",
430
+ "description" => "For resources which span regions, restrict to regions inside the United States",
431
+ "default" => false
432
+ },
433
+ "conditions" => {
434
+ "type" => "array",
435
+ "items" => {
436
+ "type" => "object",
437
+ "required" => ["name", "cloudcode"],
438
+ "description" => "CloudFormation-specific. Define Conditions as in http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html. Arguments must use the cloudCode() macro.",
439
+ "properties" => {
440
+ "name" => { "required" => true, "type" => "string" },
441
+ "cloudcode" => { "required" => true, "type" => "string" },
442
+ }
443
+ }
444
+ },
445
+ "parameters" => {
446
+ "type" => "array",
447
+ "items" => {
448
+ "type" => "object",
449
+ "title" => "parameter",
450
+ "description" => "Parameters to be substituted elsewhere in this Basket of Kittens as ERB variables (<%= varname %>)",
451
+ "additionalProperties" => false,
452
+ "properties" => {
453
+ "name" => { "required" => true, "type" => "string" },
454
+ "default" => { "type" => "string" },
455
+ "list_of" => {
456
+ "type" => "string",
457
+ "description" => "Treat the value as a comma-separated list of values with this key name, equivalent to CloudFormation's various List<> types. For example, set to 'subnet_id' to pass values as an array of subnet identifiers as the 'subnets' argument of a VPC stanza."
458
+ },
459
+ "prettyname" => {
460
+ "type" => "string",
461
+ "description" => "An alternative name to use when generating parameter fields in, for example, CloudFormation templates"
462
+ },
463
+ "description" => {"type" => "string"},
464
+ "cloudtype" => {
465
+ "type" => "string",
466
+ "description" => "A platform-specific string describing the type of validation to use for this parameter. E.g. when generating a CloudFormation template, set to AWS::EC2::Image::Id to validate input as an AMI identifier."
467
+ },
468
+ "required" => {
469
+ "type" => "boolean",
470
+ "default" => true
471
+ },
472
+ "valid_values" => {
473
+ "type" => "array",
474
+ "description" => "List of valid values for this parameter. Can only be a list of static strings, for now.",
475
+ "items" => {
476
+ "type" => "string"
477
+ }
478
+ }
479
+ }
480
+ }
481
+ },
482
+ # TODO availability zones (or an array thereof)
483
+
484
+ "admins" => {
485
+ "type" => "array",
486
+ "items" => {
487
+ "type" => "object",
488
+ "title" => "admin",
489
+ "description" => "Administrative contacts for this application stack. Will be automatically set to invoking Mu user, if not specified.",
490
+ "required" => ["name", "email"],
491
+ "additionalProperties" => false,
492
+ "properties" => {
493
+ "name" => {"type" => "string"},
494
+ "email" => {"type" => "string"},
495
+ "public_key" => {
496
+ "type" => "string",
497
+ "description" => "An OpenSSH-style public key string. This will be installed on all instances created in this deployment."
498
+ }
499
+ }
500
+ },
501
+ "minItems" => 1,
502
+ "uniqueItems" => true
503
+ }
504
+ },
505
+ "additionalProperties" => false
506
+ }
507
+
508
+ end #class
509
+ end #module