cloud-mu 3.1.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) 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 +21 -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 +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -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 +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -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 +926 -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 +169 -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 +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -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