cloud-mu 3.1.3 → 3.3.0
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.
- checksums.yaml +4 -4
- data/Dockerfile +15 -3
- data/ansible/roles/mu-windows/README.md +33 -0
- data/ansible/roles/mu-windows/defaults/main.yml +2 -0
- data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
- data/ansible/roles/mu-windows/files/config.xml +76 -0
- data/ansible/roles/mu-windows/handlers/main.yml +2 -0
- data/ansible/roles/mu-windows/meta/main.yml +53 -0
- data/ansible/roles/mu-windows/tasks/main.yml +36 -0
- data/ansible/roles/mu-windows/tests/inventory +2 -0
- data/ansible/roles/mu-windows/tests/test.yml +5 -0
- data/ansible/roles/mu-windows/vars/main.yml +2 -0
- data/bin/mu-adopt +21 -13
- data/bin/mu-azure-tests +57 -0
- data/bin/mu-cleanup +2 -4
- data/bin/mu-configure +52 -0
- data/bin/mu-deploy +3 -3
- data/bin/mu-findstray-tests +25 -0
- data/bin/mu-gen-docs +2 -4
- data/bin/mu-load-config.rb +4 -4
- data/bin/mu-node-manage +15 -16
- data/bin/mu-run-tests +147 -37
- data/cloud-mu.gemspec +22 -20
- data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
- data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
- data/cookbooks/mu-tools/libraries/helper.rb +3 -2
- data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
- data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
- data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
- data/cookbooks/mu-tools/recipes/eks.rb +2 -2
- data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
- data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
- data/cookbooks/mu-tools/resources/disk.rb +1 -1
- data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
- data/extras/clean-stock-amis +25 -19
- data/extras/generate-stock-images +1 -0
- data/extras/image-generators/AWS/win2k12.yaml +18 -13
- data/extras/image-generators/AWS/win2k16.yaml +18 -13
- data/extras/image-generators/AWS/win2k19.yaml +21 -0
- data/extras/image-generators/Google/centos6.yaml +1 -0
- data/extras/image-generators/Google/centos7.yaml +1 -1
- data/modules/mommacat.ru +6 -16
- data/modules/mu.rb +158 -111
- data/modules/mu/adoption.rb +404 -71
- data/modules/mu/cleanup.rb +221 -306
- data/modules/mu/cloud.rb +129 -1633
- data/modules/mu/cloud/database.rb +49 -0
- data/modules/mu/cloud/dnszone.rb +44 -0
- data/modules/mu/cloud/machine_images.rb +212 -0
- data/modules/mu/cloud/providers.rb +81 -0
- data/modules/mu/cloud/resource_base.rb +926 -0
- data/modules/mu/cloud/server.rb +40 -0
- data/modules/mu/cloud/server_pool.rb +1 -0
- data/modules/mu/cloud/ssh_sessions.rb +228 -0
- data/modules/mu/cloud/winrm_sessions.rb +237 -0
- data/modules/mu/cloud/wrappers.rb +169 -0
- data/modules/mu/config.rb +171 -1767
- data/modules/mu/config/alarm.rb +2 -6
- data/modules/mu/config/bucket.rb +32 -3
- data/modules/mu/config/cache_cluster.rb +2 -2
- data/modules/mu/config/cdn.rb +100 -0
- data/modules/mu/config/collection.rb +4 -4
- data/modules/mu/config/container_cluster.rb +9 -4
- data/modules/mu/config/database.rb +84 -105
- data/modules/mu/config/database.yml +1 -2
- data/modules/mu/config/dnszone.rb +10 -9
- data/modules/mu/config/doc_helpers.rb +516 -0
- data/modules/mu/config/endpoint.rb +5 -4
- data/modules/mu/config/firewall_rule.rb +103 -4
- data/modules/mu/config/folder.rb +4 -4
- data/modules/mu/config/function.rb +19 -10
- data/modules/mu/config/group.rb +4 -4
- data/modules/mu/config/habitat.rb +4 -4
- data/modules/mu/config/job.rb +89 -0
- data/modules/mu/config/loadbalancer.rb +60 -14
- data/modules/mu/config/log.rb +4 -4
- data/modules/mu/config/msg_queue.rb +4 -4
- data/modules/mu/config/nosqldb.rb +4 -4
- data/modules/mu/config/notifier.rb +10 -21
- data/modules/mu/config/ref.rb +411 -0
- data/modules/mu/config/role.rb +4 -4
- data/modules/mu/config/schema_helpers.rb +509 -0
- data/modules/mu/config/search_domain.rb +4 -4
- data/modules/mu/config/server.rb +98 -71
- data/modules/mu/config/server.yml +1 -0
- data/modules/mu/config/server_pool.rb +5 -9
- data/modules/mu/config/storage_pool.rb +1 -1
- data/modules/mu/config/tail.rb +200 -0
- data/modules/mu/config/user.rb +4 -4
- data/modules/mu/config/vpc.rb +71 -27
- data/modules/mu/config/vpc.yml +0 -1
- data/modules/mu/defaults/AWS.yaml +91 -68
- data/modules/mu/defaults/Azure.yaml +1 -0
- data/modules/mu/defaults/Google.yaml +3 -2
- data/modules/mu/deploy.rb +43 -26
- data/modules/mu/groomer.rb +17 -2
- data/modules/mu/groomers/ansible.rb +188 -41
- data/modules/mu/groomers/chef.rb +116 -55
- data/modules/mu/logger.rb +127 -148
- data/modules/mu/master.rb +410 -2
- data/modules/mu/master/chef.rb +3 -4
- data/modules/mu/master/ldap.rb +3 -3
- data/modules/mu/master/ssl.rb +12 -3
- data/modules/mu/mommacat.rb +218 -2612
- data/modules/mu/mommacat/daemon.rb +403 -0
- data/modules/mu/mommacat/naming.rb +473 -0
- data/modules/mu/mommacat/search.rb +495 -0
- data/modules/mu/mommacat/storage.rb +722 -0
- data/modules/mu/{clouds → providers}/README.md +1 -1
- data/modules/mu/{clouds → providers}/aws.rb +380 -122
- data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
- data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
- data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
- data/modules/mu/providers/aws/cdn.rb +782 -0
- data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
- data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
- data/modules/mu/providers/aws/database.rb +1744 -0
- data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
- data/modules/mu/providers/aws/endpoint.rb +1072 -0
- data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
- data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
- data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
- data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
- data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
- data/modules/mu/providers/aws/job.rb +466 -0
- data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
- data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
- data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
- data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
- data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
- data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
- data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
- data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
- data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
- data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
- data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
- data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
- data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
- data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
- data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
- data/modules/mu/{clouds → providers}/azure.rb +29 -9
- data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
- data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
- data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
- data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
- data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
- data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
- data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
- data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
- data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
- data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
- data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
- data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
- data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
- data/modules/mu/{clouds → providers}/docker.rb +0 -0
- data/modules/mu/{clouds → providers}/google.rb +68 -30
- data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
- data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
- data/modules/mu/{clouds → providers}/google/database.rb +11 -21
- data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
- data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
- data/modules/mu/{clouds → providers}/google/function.rb +140 -168
- data/modules/mu/{clouds → providers}/google/group.rb +29 -34
- data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
- data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
- data/modules/mu/{clouds → providers}/google/role.rb +94 -58
- data/modules/mu/{clouds → providers}/google/server.rb +243 -156
- data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
- data/modules/mu/{clouds → providers}/google/user.rb +95 -31
- data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
- data/modules/tests/aws-jobs-functions.yaml +46 -0
- data/modules/tests/bucket.yml +4 -0
- data/modules/tests/centos6.yaml +15 -0
- data/modules/tests/centos7.yaml +15 -0
- data/modules/tests/centos8.yaml +12 -0
- data/modules/tests/ecs.yaml +23 -0
- data/modules/tests/eks.yaml +1 -1
- data/modules/tests/functions/node-function/lambda_function.js +10 -0
- data/modules/tests/functions/python-function/lambda_function.py +12 -0
- data/modules/tests/includes-and-params.yaml +2 -1
- data/modules/tests/microservice_app.yaml +288 -0
- data/modules/tests/rds.yaml +108 -0
- data/modules/tests/regrooms/aws-iam.yaml +201 -0
- data/modules/tests/regrooms/bucket.yml +19 -0
- data/modules/tests/regrooms/rds.yaml +123 -0
- data/modules/tests/server-with-scrub-muisms.yaml +2 -1
- data/modules/tests/super_complex_bok.yml +2 -2
- data/modules/tests/super_simple_bok.yml +3 -5
- data/modules/tests/win2k12.yaml +17 -5
- data/modules/tests/win2k16.yaml +25 -0
- data/modules/tests/win2k19.yaml +25 -0
- data/requirements.txt +1 -0
- data/spec/mu/clouds/azure_spec.rb +2 -2
- metadata +240 -154
- data/extras/image-generators/AWS/windows.yaml +0 -18
- data/modules/mu/clouds/aws/database.rb +0 -1985
- data/modules/mu/clouds/aws/endpoint.rb +0 -592
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
# Plugins under this namespace serve as interfaces to cloud providers and
|
|
17
|
+
# other provisioning layers.
|
|
18
|
+
class Cloud
|
|
19
|
+
|
|
20
|
+
# In this file: generic class method wrappers for all resource types.
|
|
21
|
+
|
|
22
|
+
@@resource_types.keys.each { |name|
|
|
23
|
+
Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
|
|
24
|
+
|
|
25
|
+
def self.shortname
|
|
26
|
+
name.sub(/.*?::([^:]+)$/, '\1')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.cfg_plural
|
|
30
|
+
MU::Cloud.resource_types[shortname.to_sym][:cfg_plural]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.has_multiples
|
|
34
|
+
MU::Cloud.resource_types[shortname.to_sym][:has_multiples]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.cfg_name
|
|
38
|
+
MU::Cloud.resource_types[shortname.to_sym][:cfg_name]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.can_live_in_vpc
|
|
42
|
+
MU::Cloud.resource_types[shortname.to_sym][:can_live_in_vpc]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.waits_on_parent_completion
|
|
46
|
+
MU::Cloud.resource_types[shortname.to_sym][:waits_on_parent_completion]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.deps_wait_on_my_creation
|
|
50
|
+
MU::Cloud.resource_types[shortname.to_sym][:deps_wait_on_my_creation]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Defaults any resources that don't declare their release-readiness to
|
|
54
|
+
# ALPHA. That'll learn 'em.
|
|
55
|
+
def self.quality
|
|
56
|
+
MU::Cloud::ALPHA
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Return a list of "container" artifacts, by class, that apply to this
|
|
60
|
+
# resource type in a cloud provider. This is so methods that call find
|
|
61
|
+
# know whether to call +find+ with identifiers for parent resources.
|
|
62
|
+
# This is similar in purpose to the +isGlobal?+ resource class method,
|
|
63
|
+
# which tells our search functions whether or not a resource scopes to
|
|
64
|
+
# a region. In almost all cases this is one-entry list consisting of
|
|
65
|
+
# +:Habitat+. Notable exceptions include most implementations of
|
|
66
|
+
# +Habitat+, which either reside inside a +:Folder+ or nothing at all;
|
|
67
|
+
# whereas a +:Folder+ tends to not have any containing parent. Very few
|
|
68
|
+
# resource implementations will need to override this.
|
|
69
|
+
# A +nil+ entry in this list is interpreted as "this resource can be
|
|
70
|
+
# global."
|
|
71
|
+
# @return [Array<Symbol,nil>]
|
|
72
|
+
def self.canLiveIn
|
|
73
|
+
if self.shortname == "Folder"
|
|
74
|
+
[nil, :Folder]
|
|
75
|
+
elsif self.shortname == "Habitat"
|
|
76
|
+
[:Folder]
|
|
77
|
+
else
|
|
78
|
+
[:Habitat]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.find(*flags)
|
|
83
|
+
allfound = {}
|
|
84
|
+
|
|
85
|
+
MU::Cloud.availableClouds.each { |cloud|
|
|
86
|
+
begin
|
|
87
|
+
args = flags.first
|
|
88
|
+
next if args[:cloud] and args[:cloud] != cloud
|
|
89
|
+
# skip this cloud if we have a region argument that makes no
|
|
90
|
+
# sense there
|
|
91
|
+
cloudbase = MU::Cloud.cloudClass(cloud)
|
|
92
|
+
next if cloudbase.listCredentials.nil? or cloudbase.listCredentials.empty? or cloudbase.credConfig(args[:credentials]).nil?
|
|
93
|
+
if args[:region] and cloudbase.respond_to?(:listRegions)
|
|
94
|
+
if !cloudbase.listRegions(credentials: args[:credentials])
|
|
95
|
+
MU.log "Failed to get region list for credentials #{args[:credentials]} in cloud #{cloud}", MU::ERR, details: caller
|
|
96
|
+
else
|
|
97
|
+
next if !cloudbase.listRegions(credentials: args[:credentials]).include?(args[:region])
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
begin
|
|
101
|
+
cloudclass = MU::Cloud.resourceClass(cloud, shortname)
|
|
102
|
+
rescue MU::MuError
|
|
103
|
+
next
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
found = cloudclass.find(args)
|
|
107
|
+
if !found.nil?
|
|
108
|
+
if found.is_a?(Hash)
|
|
109
|
+
allfound.merge!(found)
|
|
110
|
+
else
|
|
111
|
+
raise MuError, "#{cloudclass}.find returned a non-Hash result"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
rescue MuCloudResourceNotImplemented
|
|
115
|
+
end
|
|
116
|
+
}
|
|
117
|
+
allfound
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Wrapper for the cleanup class method of underlying cloud object implementations.
|
|
121
|
+
def self.cleanup(*flags)
|
|
122
|
+
ok = true
|
|
123
|
+
params = flags.first
|
|
124
|
+
clouds = MU::Cloud.supportedClouds
|
|
125
|
+
if params[:cloud]
|
|
126
|
+
clouds = [params[:cloud]]
|
|
127
|
+
params.delete(:cloud)
|
|
128
|
+
end
|
|
129
|
+
params[:deploy_id] ||= MU.deploy_id
|
|
130
|
+
if !params[:deploy_id] or params[:deploy_id].empty?
|
|
131
|
+
raise MuError, "Can't call cleanup methods without a deploy id"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
clouds.each { |cloud|
|
|
135
|
+
begin
|
|
136
|
+
cloudclass = MU::Cloud.resourceClass(cloud, shortname)
|
|
137
|
+
|
|
138
|
+
if cloudclass.isGlobal?
|
|
139
|
+
params.delete(:region)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
raise MuCloudResourceNotImplemented if !cloudclass.respond_to?(:cleanup) or cloudclass.method(:cleanup).owner.to_s != "#<Class:#{cloudclass}>"
|
|
143
|
+
MU.log "Invoking #{cloudclass}.cleanup from #{shortname}", MU::DEBUG, details: flags
|
|
144
|
+
cloudclass.cleanup(params)
|
|
145
|
+
rescue MuCloudResourceNotImplemented
|
|
146
|
+
MU.log "No #{cloud} implementation of #{shortname}.cleanup, skipping", MU::DEBUG, details: flags
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
in_msg = cloud
|
|
149
|
+
if params and params[:region]
|
|
150
|
+
in_msg += " "+params[:region]
|
|
151
|
+
end
|
|
152
|
+
if params and params[:flags] and params[:flags]["project"] and !params[:flags]["project"].empty?
|
|
153
|
+
in_msg += " project "+params[:flags]["project"]
|
|
154
|
+
end
|
|
155
|
+
MU.log "Skipping #{shortname} cleanup method in #{in_msg} due to #{e.class.name}: #{e.message}", MU::WARN, details: e.backtrace
|
|
156
|
+
ok = false
|
|
157
|
+
end
|
|
158
|
+
}
|
|
159
|
+
MU::MommaCat.unlockAll
|
|
160
|
+
|
|
161
|
+
ok
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
} # end dynamic class generation block
|
|
165
|
+
} # end resource type iteration
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
data/modules/mu/config.rb
CHANGED
|
@@ -18,6 +18,10 @@ require 'erb'
|
|
|
18
18
|
require 'pp'
|
|
19
19
|
require 'json-schema'
|
|
20
20
|
require 'net/http'
|
|
21
|
+
require 'mu/config/schema_helpers'
|
|
22
|
+
require 'mu/config/tail'
|
|
23
|
+
require 'mu/config/ref'
|
|
24
|
+
require 'mu/config/doc_helpers'
|
|
21
25
|
autoload :GraphViz, 'graphviz'
|
|
22
26
|
autoload :ChronicDuration, 'chronic_duration'
|
|
23
27
|
|
|
@@ -35,35 +39,6 @@ module MU
|
|
|
35
39
|
class DeployParamError < MuError
|
|
36
40
|
end
|
|
37
41
|
|
|
38
|
-
# The default cloud provider for new resources. Must exist in MU.supportedClouds
|
|
39
|
-
# return [String]
|
|
40
|
-
def self.defaultCloud
|
|
41
|
-
configured = {}
|
|
42
|
-
MU::Cloud.supportedClouds.each { |cloud|
|
|
43
|
-
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
44
|
-
if $MU_CFG[cloud.downcase] and !$MU_CFG[cloud.downcase].empty?
|
|
45
|
-
configured[cloud] = $MU_CFG[cloud.downcase].size
|
|
46
|
-
configured[cloud] += 0.5 if cloudclass.hosted? # tiebreaker
|
|
47
|
-
end
|
|
48
|
-
}
|
|
49
|
-
if configured.size > 0
|
|
50
|
-
return configured.keys.sort { |a, b|
|
|
51
|
-
configured[b] <=> configured[a]
|
|
52
|
-
}.first
|
|
53
|
-
else
|
|
54
|
-
MU::Cloud.supportedClouds.each { |cloud|
|
|
55
|
-
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
56
|
-
return cloud if cloudclass.hosted?
|
|
57
|
-
}
|
|
58
|
-
return MU::Cloud.supportedClouds.first
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# The default grooming agent for new resources. Must exist in MU.supportedGroomers.
|
|
63
|
-
def self.defaultGroomer
|
|
64
|
-
MU.localOnly ? "Ansible" : "Chef"
|
|
65
|
-
end
|
|
66
|
-
|
|
67
42
|
attr_accessor :nat_routes
|
|
68
43
|
attr_reader :skipinitialupdates
|
|
69
44
|
|
|
@@ -75,120 +50,6 @@ module MU
|
|
|
75
50
|
@@config_path
|
|
76
51
|
end
|
|
77
52
|
|
|
78
|
-
# Accessor for our Basket of Kittens schema definition
|
|
79
|
-
def self.schema
|
|
80
|
-
@@schema
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Deep merge a configuration hash so we can meld different cloud providers'
|
|
84
|
-
# schemas together, while preserving documentation differences
|
|
85
|
-
def self.schemaMerge(orig, new, cloud)
|
|
86
|
-
if new.is_a?(Hash)
|
|
87
|
-
new.each_pair { |k, v|
|
|
88
|
-
if cloud and k == "description" and v.is_a?(String) and !v.match(/\b#{Regexp.quote(cloud.upcase)}\b/) and !v.empty?
|
|
89
|
-
new[k] = "+"+cloud.upcase+"+: "+v
|
|
90
|
-
end
|
|
91
|
-
if orig and orig.has_key?(k)
|
|
92
|
-
elsif orig
|
|
93
|
-
orig[k] = new[k]
|
|
94
|
-
else
|
|
95
|
-
orig = new
|
|
96
|
-
end
|
|
97
|
-
schemaMerge(orig[k], new[k], cloud)
|
|
98
|
-
}
|
|
99
|
-
elsif orig.is_a?(Array) and new
|
|
100
|
-
orig.concat(new)
|
|
101
|
-
orig.uniq!
|
|
102
|
-
elsif new.is_a?(String)
|
|
103
|
-
orig ||= ""
|
|
104
|
-
orig += "\n" if !orig.empty?
|
|
105
|
-
orig += "+#{cloud.upcase}+: "+new
|
|
106
|
-
else
|
|
107
|
-
# XXX I think this is a NOOP?
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Accessor for our Basket of Kittens schema definition, with various
|
|
112
|
-
# cloud-specific details merged so we can generate documentation for them.
|
|
113
|
-
def self.docSchema
|
|
114
|
-
docschema = Marshal.load(Marshal.dump(@@schema))
|
|
115
|
-
only_children = {}
|
|
116
|
-
MU::Cloud.resource_types.each_pair { |classname, attrs|
|
|
117
|
-
MU::Cloud.supportedClouds.each { |cloud|
|
|
118
|
-
begin
|
|
119
|
-
require "mu/clouds/#{cloud.downcase}/#{attrs[:cfg_name]}"
|
|
120
|
-
rescue LoadError => e
|
|
121
|
-
next
|
|
122
|
-
end
|
|
123
|
-
res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
|
|
124
|
-
required, res_schema = res_class.schema(self)
|
|
125
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["description"] ||= ""
|
|
126
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["description"] += "\n#\n# `#{cloud}`: "+res_class.quality
|
|
127
|
-
res_schema.each { |key, cfg|
|
|
128
|
-
if !docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
|
|
129
|
-
only_children[attrs[:cfg_plural]] ||= {}
|
|
130
|
-
only_children[attrs[:cfg_plural]][key] ||= {}
|
|
131
|
-
only_children[attrs[:cfg_plural]][key][cloud] = cfg
|
|
132
|
-
end
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# recursively chase down description fields in arrays and objects of our
|
|
138
|
-
# schema and prepend stuff to them for documentation
|
|
139
|
-
def self.prepend_descriptions(prefix, cfg)
|
|
140
|
-
cfg["prefix"] = prefix
|
|
141
|
-
if cfg["type"] == "array" and cfg["items"]
|
|
142
|
-
cfg["items"] = prepend_descriptions(prefix, cfg["items"])
|
|
143
|
-
elsif cfg["type"] == "object" and cfg["properties"]
|
|
144
|
-
cfg["properties"].each_pair { |key, subcfg|
|
|
145
|
-
cfg["properties"][key] = prepend_descriptions(prefix, cfg["properties"][key])
|
|
146
|
-
}
|
|
147
|
-
end
|
|
148
|
-
cfg
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
MU::Cloud.resource_types.each_pair { |classname, attrs|
|
|
152
|
-
MU::Cloud.supportedClouds.each { |cloud|
|
|
153
|
-
res_class = nil
|
|
154
|
-
begin
|
|
155
|
-
res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
|
|
156
|
-
rescue MU::Cloud::MuCloudResourceNotImplemented
|
|
157
|
-
next
|
|
158
|
-
end
|
|
159
|
-
required, res_schema = res_class.schema(self)
|
|
160
|
-
next if required.size == 0 and res_schema.size == 0
|
|
161
|
-
res_schema.each { |key, cfg|
|
|
162
|
-
cfg["description"] ||= ""
|
|
163
|
-
if !cfg["description"].empty?
|
|
164
|
-
cfg["description"] = "\n# +"+cloud.upcase+"+: "+cfg["description"]
|
|
165
|
-
end
|
|
166
|
-
if docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
|
|
167
|
-
schemaMerge(docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key], cfg, cloud)
|
|
168
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] ||= ""
|
|
169
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] += "\n"+(cfg["description"].match(/^#/) ? "" : "# ")+cfg["description"]
|
|
170
|
-
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]
|
|
171
|
-
else
|
|
172
|
-
if only_children[attrs[:cfg_plural]][key]
|
|
173
|
-
prefix = only_children[attrs[:cfg_plural]][key].keys.map{ |x| x.upcase }.join(" & ")+" ONLY"
|
|
174
|
-
cfg["description"].gsub!(/^\n#/, '') # so we don't leave the description blank in the "optional parameters" section
|
|
175
|
-
cfg = prepend_descriptions(prefix, cfg)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key] = cfg
|
|
179
|
-
end
|
|
180
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["clouds"] = {}
|
|
181
|
-
docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["clouds"][cloud] = cfg
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
docschema['required'].concat(required)
|
|
185
|
-
docschema['required'].uniq!
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
docschema
|
|
190
|
-
end
|
|
191
|
-
|
|
192
53
|
attr_reader :config
|
|
193
54
|
|
|
194
55
|
@@parameters = {}
|
|
@@ -216,7 +77,7 @@ module MU
|
|
|
216
77
|
if config.is_a?(Hash)
|
|
217
78
|
newhash = {}
|
|
218
79
|
config.each_pair { |key, val|
|
|
219
|
-
next if remove_runtime_keys and key.match(/^#MU_/)
|
|
80
|
+
next if remove_runtime_keys and (key.nil? or key.match(/^#MU_/))
|
|
220
81
|
next if val.is_a?(Array) and val.empty?
|
|
221
82
|
newhash[key] = self.manxify(val, remove_runtime_keys: remove_runtime_keys)
|
|
222
83
|
}
|
|
@@ -243,495 +104,16 @@ module MU
|
|
|
243
104
|
MU::Config.manxify(Marshal.load(Marshal.dump(MU.structToHash(config.dup))), remove_runtime_keys: true)
|
|
244
105
|
end
|
|
245
106
|
|
|
246
|
-
# A wrapper class for resources to refer to other resources, whether they
|
|
247
|
-
# be a sibling object in the current deploy, an object in another deploy,
|
|
248
|
-
# or a plain cloud id from outside of Mu.
|
|
249
|
-
class Ref
|
|
250
|
-
attr_reader :name
|
|
251
|
-
attr_reader :type
|
|
252
|
-
attr_reader :cloud
|
|
253
|
-
attr_reader :deploy_id
|
|
254
|
-
attr_reader :region
|
|
255
|
-
attr_reader :credentials
|
|
256
|
-
attr_reader :habitat
|
|
257
|
-
attr_reader :mommacat
|
|
258
|
-
attr_reader :tag_key
|
|
259
|
-
attr_reader :tag_value
|
|
260
|
-
attr_reader :obj
|
|
261
|
-
|
|
262
|
-
@@refs = []
|
|
263
|
-
@@ref_semaphore = Mutex.new
|
|
264
|
-
|
|
265
|
-
# 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.
|
|
266
|
-
# @param cfg [Hash]:
|
|
267
|
-
# @return [MU::Config::Ref]
|
|
268
|
-
def self.get(cfg)
|
|
269
|
-
return cfg if cfg.is_a?(MU::Config::Ref)
|
|
270
|
-
checkfields = cfg.keys.map { |k| k.to_sym }
|
|
271
|
-
required = [:id, :type]
|
|
272
|
-
|
|
273
|
-
@@ref_semaphore.synchronize {
|
|
274
|
-
match = nil
|
|
275
|
-
@@refs.each { |ref|
|
|
276
|
-
saw_mismatch = false
|
|
277
|
-
saw_match = false
|
|
278
|
-
needed_values = []
|
|
279
|
-
checkfields.each { |field|
|
|
280
|
-
next if !cfg[field]
|
|
281
|
-
ext_value = ref.instance_variable_get("@#{field.to_s}".to_sym)
|
|
282
|
-
if !ext_value
|
|
283
|
-
needed_values << field
|
|
284
|
-
next
|
|
285
|
-
end
|
|
286
|
-
if cfg[field] != ext_value
|
|
287
|
-
saw_mismatch = true
|
|
288
|
-
elsif required.include?(field) and cfg[field] == ext_value
|
|
289
|
-
saw_match = true
|
|
290
|
-
end
|
|
291
|
-
}
|
|
292
|
-
if saw_match and !saw_mismatch
|
|
293
|
-
# populate empty fields we got from this request
|
|
294
|
-
if needed_values.size > 0
|
|
295
|
-
newref = ref.dup
|
|
296
|
-
needed_values.each { |field|
|
|
297
|
-
newref.instance_variable_set("@#{field.to_s}".to_sym, cfg[field])
|
|
298
|
-
if !newref.respond_to?(field)
|
|
299
|
-
newref.singleton_class.instance_eval { attr_reader field.to_sym }
|
|
300
|
-
end
|
|
301
|
-
}
|
|
302
|
-
@@refs << newref
|
|
303
|
-
return newref
|
|
304
|
-
else
|
|
305
|
-
return ref
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
# if we get here, there was no match
|
|
313
|
-
newref = MU::Config::Ref.new(cfg)
|
|
314
|
-
@@ref_semaphore.synchronize {
|
|
315
|
-
@@refs << newref
|
|
316
|
-
return newref
|
|
317
|
-
}
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
# A way of dynamically defining +attr_reader+ without leaking memory
|
|
321
|
-
def self.define_reader(name)
|
|
322
|
-
define_method(name) {
|
|
323
|
-
instance_variable_get("@#{name.to_s}")
|
|
324
|
-
}
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
# @param cfg [Hash]: A Basket of Kittens configuration hash containing
|
|
328
|
-
# lookup information for a cloud object
|
|
329
|
-
def initialize(cfg)
|
|
330
|
-
cfg.keys.each { |field|
|
|
331
|
-
next if field == "tag"
|
|
332
|
-
if !cfg[field].nil?
|
|
333
|
-
self.instance_variable_set("@#{field}".to_sym, cfg[field])
|
|
334
|
-
elsif !cfg[field.to_sym].nil?
|
|
335
|
-
self.instance_variable_set("@#{field.to_s}".to_sym, cfg[field.to_sym])
|
|
336
|
-
end
|
|
337
|
-
MU::Config::Ref.define_reader(field)
|
|
338
|
-
}
|
|
339
|
-
if cfg['tag'] and cfg['tag']['key'] and
|
|
340
|
-
!cfg['tag']['key'].empty? and cfg['tag']['value']
|
|
341
|
-
@tag_key = cfg['tag']['key']
|
|
342
|
-
@tag_value = cfg['tag']['value']
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
if @deploy_id and !@mommacat
|
|
346
|
-
@mommacat = MU::MommaCat.getLitter(@deploy_id, set_context_to_me: false)
|
|
347
|
-
elsif @mommacat and !@deploy_id
|
|
348
|
-
@deploy_id = @mommacat.deploy_id
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
kitten(shallow: true) if @mommacat # try to populate the actual cloud object for this
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
# Comparison operator
|
|
355
|
-
def <=>(other)
|
|
356
|
-
return 1 if other.nil?
|
|
357
|
-
self.to_s <=> other.to_s
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
# 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}.
|
|
361
|
-
# @param aliases [Array<Hash>]: Key => value mappings to set backwards-compatibility aliases for attributes, such as the ubiquitous +vpc_id+ (+vpc_id+ => +id+).
|
|
362
|
-
# @return [Hash]
|
|
363
|
-
def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil, omit_fields: [])
|
|
364
|
-
parent_obj ||= caller[1].gsub(/.*?\/([^\.\/]+)\.rb:.*/, '\1')
|
|
365
|
-
desc ||= "Reference a #{type ? "'#{type}' resource" : "resource" } from this #{parent_obj ? "'#{parent_obj}'" : "" } resource"
|
|
366
|
-
schema = {
|
|
367
|
-
"type" => "object",
|
|
368
|
-
"#MU_REFERENCE" => true,
|
|
369
|
-
"minProperties" => 1,
|
|
370
|
-
"description" => desc,
|
|
371
|
-
"properties" => {
|
|
372
|
-
"id" => {
|
|
373
|
-
"type" => "string",
|
|
374
|
-
"description" => "Cloud identifier of a resource we want to reference, typically used when leveraging resources not managed by MU"
|
|
375
|
-
},
|
|
376
|
-
"name" => {
|
|
377
|
-
"type" => "string",
|
|
378
|
-
"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+."
|
|
379
|
-
},
|
|
380
|
-
"type" => {
|
|
381
|
-
"type" => "string",
|
|
382
|
-
"description" => "The resource type we're attempting to reference.",
|
|
383
|
-
"enum" => MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }
|
|
384
|
-
},
|
|
385
|
-
"deploy_id" => {
|
|
386
|
-
"type" => "string",
|
|
387
|
-
"description" => "Our target resource should be found in this Mu deploy."
|
|
388
|
-
},
|
|
389
|
-
"credentials" => MU::Config.credentials_primitive,
|
|
390
|
-
"region" => MU::Config.region_primitive,
|
|
391
|
-
"cloud" => MU::Config.cloud_primitive,
|
|
392
|
-
"tag" => {
|
|
393
|
-
"type" => "object",
|
|
394
|
-
"description" => "If the target resource supports tagging and our resource implementations +find+ method supports it, we can attempt to locate it by tag.",
|
|
395
|
-
"properties" => {
|
|
396
|
-
"key" => {
|
|
397
|
-
"type" => "string",
|
|
398
|
-
"description" => "The tag or label key to search against"
|
|
399
|
-
},
|
|
400
|
-
"value" => {
|
|
401
|
-
"type" => "string",
|
|
402
|
-
"description" => "The tag or label value to match"
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if !["folders", "habitats"].include?(type)
|
|
409
|
-
schema["properties"]["habitat"] = MU::Config::Habitat.reference
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
if omit_fields
|
|
413
|
-
omit_fields.each { |f|
|
|
414
|
-
schema["properties"].delete(f)
|
|
415
|
-
}
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
if !type.nil?
|
|
419
|
-
schema["required"] = ["type"]
|
|
420
|
-
schema["properties"]["type"]["default"] = type
|
|
421
|
-
schema["properties"]["type"]["enum"] = [type]
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
aliases.each { |a|
|
|
425
|
-
a.each_pair { |k, v|
|
|
426
|
-
if schema["properties"][v]
|
|
427
|
-
schema["properties"][k] = schema["properties"][v].dup
|
|
428
|
-
schema["properties"][k]["description"] = "Alias for <tt>#{v}</tt>"
|
|
429
|
-
else
|
|
430
|
-
MU.log "Reference schema alias #{k} wants to alias #{v}, but no such attribute exists", MU::WARN, details: caller[4]
|
|
431
|
-
end
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
schema
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
# Decompose into a plain-jane {MU::Config::BasketOfKittens} hash fragment,
|
|
439
|
-
# of the sort that would have been used to declare this reference in the
|
|
440
|
-
# first place.
|
|
441
|
-
def to_h
|
|
442
|
-
me = { }
|
|
443
|
-
|
|
444
|
-
self.instance_variables.each { |var|
|
|
445
|
-
next if [:@obj, :@mommacat, :@tag_key, :@tag_value].include?(var)
|
|
446
|
-
val = self.instance_variable_get(var)
|
|
447
|
-
next if val.nil?
|
|
448
|
-
val = val.to_h if val.is_a?(MU::Config::Ref)
|
|
449
|
-
me[var.to_s.sub(/^@/, '')] = val
|
|
450
|
-
}
|
|
451
|
-
if @tag_key and !@tag_key.empty?
|
|
452
|
-
me['tag'] = {
|
|
453
|
-
'key' => @tag_key,
|
|
454
|
-
'value' => @tag_value
|
|
455
|
-
}
|
|
456
|
-
end
|
|
457
|
-
me
|
|
458
|
-
end
|
|
459
|
-
|
|
460
|
-
# Getter for the #{id} instance variable that attempts to populate it if
|
|
461
|
-
# it's not set.
|
|
462
|
-
# @return [String,nil]
|
|
463
|
-
def id
|
|
464
|
-
return @id if @id
|
|
465
|
-
kitten # if it's not defined, attempt to define it
|
|
466
|
-
@id
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Alias for {id}
|
|
470
|
-
# @return [String,nil]
|
|
471
|
-
def cloud_id
|
|
472
|
-
id
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
# Return a {MU::Cloud} object for this reference. This is only meant to be
|
|
476
|
-
# called in a live deploy, which is to say that if called during initial
|
|
477
|
-
# configuration parsing, results may be incorrect.
|
|
478
|
-
# @param mommacat [MU::MommaCat]: A deploy object which will be searched for the referenced resource if provided, before restoring to broader, less efficient searches.
|
|
479
|
-
def kitten(mommacat = @mommacat, shallow: false)
|
|
480
|
-
return nil if !@cloud or !@type
|
|
481
|
-
|
|
482
|
-
if @obj
|
|
483
|
-
@deploy_id ||= @obj.deploy_id
|
|
484
|
-
@id ||= @obj.cloud_id
|
|
485
|
-
@name ||= @obj.config['name']
|
|
486
|
-
return @obj
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
if mommacat
|
|
490
|
-
@obj = mommacat.findLitterMate(type: @type, name: @name, cloud_id: @id, credentials: @credentials, debug: false)
|
|
491
|
-
if @obj # initialize missing attributes, if we can
|
|
492
|
-
@id ||= @obj.cloud_id
|
|
493
|
-
@mommacat ||= mommacat
|
|
494
|
-
@obj.intoDeploy(@mommacat) # make real sure these are set
|
|
495
|
-
@deploy_id ||= mommacat.deploy_id
|
|
496
|
-
if !@name
|
|
497
|
-
if @obj.config and @obj.config['name']
|
|
498
|
-
@name = @obj.config['name']
|
|
499
|
-
elsif @obj.mu_name
|
|
500
|
-
if @type == "folders"
|
|
501
|
-
MU.log "would assign name '#{@obj.mu_name}' in ref to this folder if I were feeling aggressive", MU::WARN, details: self.to_h
|
|
502
|
-
end
|
|
503
|
-
# @name = @obj.mu_name
|
|
504
|
-
end
|
|
505
|
-
end
|
|
506
|
-
return @obj
|
|
507
|
-
else
|
|
508
|
-
# MU.log "Failed to find a live '#{@type.to_s}' object named #{@name}#{@id ? " (#{@id})" : "" }#{ @habitat ? " in habitat #{@habitat}" : "" }", MU::WARN, details: self
|
|
509
|
-
end
|
|
510
|
-
end
|
|
511
|
-
|
|
512
|
-
if !@obj and !(@cloud == "Google" and @id and @type == "users" and MU::Cloud::Google::User.cannedServiceAcctName?(@id)) and !shallow
|
|
513
|
-
|
|
514
|
-
begin
|
|
515
|
-
hab_arg = if @habitat.nil?
|
|
516
|
-
[nil]
|
|
517
|
-
elsif @habitat.is_a?(MU::Config::Ref)
|
|
518
|
-
[@habitat.id]
|
|
519
|
-
elsif @habitat.is_a?(Hash)
|
|
520
|
-
[@habitat["id"]]
|
|
521
|
-
else
|
|
522
|
-
[@habitat.to_s]
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
found = MU::MommaCat.findStray(
|
|
526
|
-
@cloud,
|
|
527
|
-
@type,
|
|
528
|
-
name: @name,
|
|
529
|
-
cloud_id: @id,
|
|
530
|
-
deploy_id: @deploy_id,
|
|
531
|
-
region: @region,
|
|
532
|
-
habitats: hab_arg,
|
|
533
|
-
credentials: @credentials,
|
|
534
|
-
dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type))
|
|
535
|
-
)
|
|
536
|
-
@obj ||= found.first if found
|
|
537
|
-
rescue ThreadError => e
|
|
538
|
-
# Sometimes MommaCat calls us in a potential deadlock situation;
|
|
539
|
-
# don't be the cause of a fatal error if so, we don't need this
|
|
540
|
-
# object that badly.
|
|
541
|
-
raise e if !e.message.match(/recursive locking/)
|
|
542
|
-
rescue SystemExit => e
|
|
543
|
-
# XXX this is temporary, to cope with some debug stuff that's in findStray
|
|
544
|
-
# for the nonce
|
|
545
|
-
return
|
|
546
|
-
end
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
if @obj
|
|
550
|
-
@deploy_id ||= @obj.deploy_id
|
|
551
|
-
@id ||= @obj.cloud_id
|
|
552
|
-
@name ||= @obj.config['name']
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
@obj
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
# A wrapper for config leaves that came from ERB parameters instead of raw
|
|
561
|
-
# YAML or JSON. Will behave like a string for things that expect that
|
|
562
|
-
# sort of thing. Code that needs to know that this leaf was the result of
|
|
563
|
-
# a parameter will be able to tell by the object class being something
|
|
564
|
-
# other than a plain string, array, or hash.
|
|
565
|
-
class Tail
|
|
566
|
-
@value = nil
|
|
567
|
-
@name = nil
|
|
568
|
-
@prettyname = nil
|
|
569
|
-
@description = nil
|
|
570
|
-
@prefix = ""
|
|
571
|
-
@suffix = ""
|
|
572
|
-
@is_list_element = false
|
|
573
|
-
@pseudo = false
|
|
574
|
-
@runtimecode = nil
|
|
575
|
-
@valid_values = []
|
|
576
|
-
@index = 0
|
|
577
|
-
attr_reader :description
|
|
578
|
-
attr_reader :pseudo
|
|
579
|
-
attr_reader :index
|
|
580
|
-
attr_reader :value
|
|
581
|
-
attr_reader :runtimecode
|
|
582
|
-
attr_reader :valid_values
|
|
583
|
-
attr_reader :is_list_element
|
|
584
|
-
|
|
585
|
-
def initialize(name, value, prettyname = nil, cloudtype = "String", valid_values = [], description = "", is_list_element = false, prefix: "", suffix: "", pseudo: false, runtimecode: nil, index: 0)
|
|
586
|
-
@name = name
|
|
587
|
-
@bindings = {}
|
|
588
|
-
@value = value
|
|
589
|
-
@valid_values = valid_values
|
|
590
|
-
@pseudo = pseudo
|
|
591
|
-
@index = index
|
|
592
|
-
@runtimecode = runtimecode
|
|
593
|
-
@cloudtype = cloudtype
|
|
594
|
-
@is_list_element = is_list_element
|
|
595
|
-
@description ||=
|
|
596
|
-
if !description.nil?
|
|
597
|
-
description
|
|
598
|
-
else
|
|
599
|
-
""
|
|
600
|
-
end
|
|
601
|
-
@prettyname ||=
|
|
602
|
-
if !prettyname.nil?
|
|
603
|
-
prettyname
|
|
604
|
-
else
|
|
605
|
-
@name.capitalize.gsub(/[^a-z0-9]/i, "")
|
|
606
|
-
end
|
|
607
|
-
@prefix = prefix if !prefix.nil?
|
|
608
|
-
@suffix = suffix if !suffix.nil?
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
# Return the parameter name of this Tail
|
|
612
|
-
def getName
|
|
613
|
-
@name
|
|
614
|
-
end
|
|
615
|
-
# Return the platform-specific cloud type of this Tail
|
|
616
|
-
def getCloudType
|
|
617
|
-
@cloudtype
|
|
618
|
-
end
|
|
619
|
-
# Return the human-friendly name of this Tail
|
|
620
|
-
def getPrettyName
|
|
621
|
-
@prettyname
|
|
622
|
-
end
|
|
623
|
-
# Walk like a String
|
|
624
|
-
def to_s
|
|
625
|
-
@prefix.to_s+@value.to_s+@suffix.to_s
|
|
626
|
-
end
|
|
627
|
-
# Quack like a String
|
|
628
|
-
def to_str
|
|
629
|
-
to_s
|
|
630
|
-
end
|
|
631
|
-
# Upcase like a String
|
|
632
|
-
def upcase
|
|
633
|
-
to_s.upcase
|
|
634
|
-
end
|
|
635
|
-
# Downcase like a String
|
|
636
|
-
def downcase
|
|
637
|
-
to_s.downcase
|
|
638
|
-
end
|
|
639
|
-
# Check for emptiness like a String
|
|
640
|
-
def empty?
|
|
641
|
-
to_s.empty?
|
|
642
|
-
end
|
|
643
|
-
# Match like a String
|
|
644
|
-
def match(*args)
|
|
645
|
-
to_s.match(*args)
|
|
646
|
-
end
|
|
647
|
-
# Check for equality like a String
|
|
648
|
-
def ==(o)
|
|
649
|
-
(o.class == self.class or o.class == "String") && o.to_s == to_s
|
|
650
|
-
end
|
|
651
|
-
# Concatenate like a string
|
|
652
|
-
def +(o)
|
|
653
|
-
return to_s if o.nil?
|
|
654
|
-
to_s + o.to_s
|
|
655
|
-
end
|
|
656
|
-
# Perform global substitutions like a String
|
|
657
|
-
def gsub(*args)
|
|
658
|
-
to_s.gsub(*args)
|
|
659
|
-
end
|
|
660
|
-
end
|
|
661
|
-
|
|
662
|
-
# Wrapper method for creating a {MU::Config::Tail} object as a reference to
|
|
663
|
-
# a parameter that's valid in the loaded configuration.
|
|
664
|
-
# @param param [<String>]: The name of the parameter to which this should be tied.
|
|
665
|
-
# @param value [<String>]: The value of the parameter to return when asked
|
|
666
|
-
# @param prettyname [<String>]: A human-friendly parameter name to be used when generating CloudFormation templates and the like
|
|
667
|
-
# @param cloudtype [<String>]: A platform-specific identifier used by cloud layers to identify a parameter's type, e.g. AWS::EC2::VPC::Id
|
|
668
|
-
# @param valid_values [Array<String>]: A list of acceptable String values for the given parameter.
|
|
669
|
-
# @param description [<String>]: A long-form description of what the parameter does.
|
|
670
|
-
# @param list_of [<String>]: Indicates that the value should be treated as a member of a list (array) by the cloud layer.
|
|
671
|
-
# @param prefix [<String>]: A static String that should be prefixed to the stored value when queried
|
|
672
|
-
# @param suffix [<String>]: A static String that should be appended to the stored value when queried
|
|
673
|
-
# @param pseudo [<Boolean>]: This is a pseudo-parameter, automatically provided, and not available as user input.
|
|
674
|
-
# @param runtimecode [<String>]: Actual code to allow the cloud layer to interpret literally in its own idiom, e.g. '"Ref" : "AWS::StackName"' for CloudFormation
|
|
675
|
-
def getTail(param, value: nil, prettyname: nil, cloudtype: "String", valid_values: [], description: nil, list_of: nil, prefix: "", suffix: "", pseudo: false, runtimecode: nil)
|
|
676
|
-
if value.nil?
|
|
677
|
-
if @@parameters.nil? or !@@parameters.has_key?(param)
|
|
678
|
-
MU.log "Parameter '#{param}' (#{param.class.name}) referenced in config but not provided (#{caller[0]})", MU::DEBUG, details: @@parameters
|
|
679
|
-
return nil
|
|
680
|
-
# raise DeployParamError
|
|
681
|
-
else
|
|
682
|
-
value = @@parameters[param]
|
|
683
|
-
end
|
|
684
|
-
end
|
|
685
|
-
if !prettyname.nil?
|
|
686
|
-
prettyname.gsub!(/[^a-z0-9]/i, "") # comply with CloudFormation restrictions
|
|
687
|
-
end
|
|
688
|
-
if value.is_a?(MU::Config::Tail)
|
|
689
|
-
MU.log "Parameter #{param} is using a nested parameter as a value. This rarely works, depending on the target cloud. YMMV.", MU::WARN
|
|
690
|
-
tail = MU::Config::Tail.new(param, value, prettyname, cloudtype, valid_values, description, prefix: prefix, suffix: suffix, pseudo: pseudo, runtimecode: runtimecode)
|
|
691
|
-
elsif !list_of.nil? or (@@tails.has_key?(param) and @@tails[param].is_a?(Array))
|
|
692
|
-
tail = []
|
|
693
|
-
count = 0
|
|
694
|
-
value.split(/\s*,\s*/).each { |subval|
|
|
695
|
-
if @@tails.has_key?(param) and !@@tails[param][count].nil?
|
|
696
|
-
subval = @@tails[param][count].values.first.to_s if subval.nil?
|
|
697
|
-
list_of = @@tails[param][count].values.first.getName if list_of.nil?
|
|
698
|
-
prettyname = @@tails[param][count].values.first.getPrettyName if prettyname.nil?
|
|
699
|
-
description = @@tails[param][count].values.first.description if description.nil?
|
|
700
|
-
valid_values = @@tails[param][count].values.first.valid_values if valid_values.nil? or valid_values.empty?
|
|
701
|
-
cloudtype = @@tails[param][count].values.first.getCloudType if @@tails[param][count].values.first.getCloudType != "String"
|
|
702
|
-
end
|
|
703
|
-
prettyname = param.capitalize if prettyname.nil?
|
|
704
|
-
tail << { list_of => MU::Config::Tail.new(list_of, subval, prettyname, cloudtype, valid_values, description, true, pseudo: pseudo, index: count) }
|
|
705
|
-
count = count + 1
|
|
706
|
-
}
|
|
707
|
-
else
|
|
708
|
-
if @@tails.has_key?(param)
|
|
709
|
-
pseudo = @@tails[param].pseudo
|
|
710
|
-
value = @@tails[param].to_s if value.nil?
|
|
711
|
-
prettyname = @@tails[param].getPrettyName if prettyname.nil?
|
|
712
|
-
description = @@tails[param].description if description.nil?
|
|
713
|
-
valid_values = @@tails[param].valid_values if valid_values.nil? or valid_values.empty?
|
|
714
|
-
cloudtype = @@tails[param].getCloudType if @@tails[param].getCloudType != "String"
|
|
715
|
-
end
|
|
716
|
-
tail = MU::Config::Tail.new(param, value, prettyname, cloudtype, valid_values, description, prefix: prefix, suffix: suffix, pseudo: pseudo, runtimecode: runtimecode)
|
|
717
|
-
end
|
|
718
|
-
|
|
719
|
-
if valid_values and valid_values.size > 0 and value
|
|
720
|
-
if !valid_values.include?(value)
|
|
721
|
-
raise DeployParamError, "Invalid parameter value '#{value}' supplied for '#{param}'"
|
|
722
|
-
end
|
|
723
|
-
end
|
|
724
|
-
@@tails[param] = tail
|
|
725
|
-
|
|
726
|
-
tail
|
|
727
|
-
end
|
|
728
|
-
|
|
729
107
|
# Load up our YAML or JSON and parse it through ERB, optionally substituting
|
|
730
108
|
# externally-supplied parameters.
|
|
731
109
|
def resolveConfig(path: @@config_path, param_pass: false, cloud: nil)
|
|
732
110
|
config = nil
|
|
733
111
|
@param_pass = param_pass
|
|
734
112
|
|
|
113
|
+
if cloud
|
|
114
|
+
MU.log "Exposing cloud variable to ERB with value of #{cloud}", MU::DEBUG
|
|
115
|
+
end
|
|
116
|
+
|
|
735
117
|
# Catch calls to missing variables in Basket of Kittens files when being
|
|
736
118
|
# parsed by ERB, and replace with placeholders for parameters. This
|
|
737
119
|
# method_missing is only defined innside {MU::Config.resolveConfig}
|
|
@@ -798,8 +180,19 @@ return
|
|
|
798
180
|
$file_format = MU::Config.guessFormat(path)
|
|
799
181
|
$yaml_refs = {}
|
|
800
182
|
erb = ERB.new(File.read(path), nil, "<>")
|
|
183
|
+
erb.filename = path
|
|
801
184
|
|
|
802
|
-
|
|
185
|
+
begin
|
|
186
|
+
raw_text = erb.result(erb_binding)
|
|
187
|
+
rescue NameError => e
|
|
188
|
+
loc = e.backtrace[0].sub(/:(\d+):.*/, ':\1')
|
|
189
|
+
msg = if e.message.match(/wrong constant name Config.getTail PLACEHOLDER ([^\s]+) REDLOHECALP/)
|
|
190
|
+
"Variable '#{Regexp.last_match[1]}' referenced in config, but not defined. Missing required parameter?"
|
|
191
|
+
else
|
|
192
|
+
e.message
|
|
193
|
+
end
|
|
194
|
+
raise ValidationError, msg+" at "+loc
|
|
195
|
+
end
|
|
803
196
|
raw_json = nil
|
|
804
197
|
|
|
805
198
|
# If we're working in YAML, do some magic to make includes work better.
|
|
@@ -862,17 +255,15 @@ return
|
|
|
862
255
|
# @param cloud [String]: Sets a parameter named 'cloud', and insert it as the default cloud platform if not already declared
|
|
863
256
|
# @return [Hash]: The complete validated configuration for a deployment.
|
|
864
257
|
def initialize(path, skipinitialupdates = false, params: {}, updating: nil, default_credentials: nil, cloud: nil)
|
|
865
|
-
$myPublicIp
|
|
866
|
-
$myRoot
|
|
258
|
+
$myPublicIp ||= MU.mu_public_ip
|
|
259
|
+
$myRoot ||= MU.myRoot
|
|
867
260
|
$myRoot.freeze
|
|
868
261
|
|
|
869
|
-
$myAZ
|
|
262
|
+
$myAZ ||= MU.myAZ.freeze
|
|
870
263
|
$myAZ.freeze
|
|
871
|
-
$myRegion
|
|
264
|
+
$myRegion ||= MU.curRegion.freeze
|
|
872
265
|
$myRegion.freeze
|
|
873
266
|
|
|
874
|
-
$myAppName = nil
|
|
875
|
-
|
|
876
267
|
@kittens = {}
|
|
877
268
|
@kittencfg_semaphore = Mutex.new
|
|
878
269
|
@@config_path = path
|
|
@@ -920,7 +311,7 @@ return
|
|
|
920
311
|
# you can't specify parameters in an included file, because ERB is what's
|
|
921
312
|
# doing the including, and parameters need to already be resolved so that
|
|
922
313
|
# ERB can use them.
|
|
923
|
-
param_cfg,
|
|
314
|
+
param_cfg, _raw_erb_params_only = resolveConfig(path: @@config_path, param_pass: true, cloud: cloud)
|
|
924
315
|
if param_cfg.has_key?("parameters")
|
|
925
316
|
param_cfg["parameters"].each { |param|
|
|
926
317
|
if param.has_key?("default") and param["default"].nil?
|
|
@@ -976,7 +367,7 @@ return
|
|
|
976
367
|
$parameters = @@parameters.dup
|
|
977
368
|
$parameters.freeze
|
|
978
369
|
|
|
979
|
-
tmp_cfg,
|
|
370
|
+
tmp_cfg, _raw_erb = resolveConfig(path: @@config_path, cloud: cloud)
|
|
980
371
|
|
|
981
372
|
# Convert parameter entries that constitute whole config keys into
|
|
982
373
|
# {MU::Config::Tail} objects.
|
|
@@ -1022,8 +413,6 @@ return
|
|
|
1022
413
|
exit 1
|
|
1023
414
|
end
|
|
1024
415
|
|
|
1025
|
-
types = MU::Cloud.resource_types.values.map { |v| v[:cfg_plural] }
|
|
1026
|
-
|
|
1027
416
|
MU::Cloud.resource_types.values.map { |v| v[:cfg_plural] }.each { |type|
|
|
1028
417
|
if @config[type]
|
|
1029
418
|
@config[type].each { |k|
|
|
@@ -1041,162 +430,37 @@ return
|
|
|
1041
430
|
@config.freeze
|
|
1042
431
|
end
|
|
1043
432
|
|
|
1044
|
-
#
|
|
1045
|
-
#
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if config.has_key?(attrs[:cfg_plural]) and config[attrs[:cfg_plural]]
|
|
1064
|
-
config[attrs[:cfg_plural]].each { |resource|
|
|
1065
|
-
if resource.has_key?("dependencies")
|
|
1066
|
-
me = nodes[attrs[:cfg_name]][resource['name']]
|
|
1067
|
-
resource["dependencies"].each { |dep|
|
|
1068
|
-
parent = nodes[dep['type']][dep['name']]
|
|
1069
|
-
g.add_edges(me, parent)
|
|
1070
|
-
}
|
|
1071
|
-
end
|
|
1072
|
-
}
|
|
1073
|
-
end
|
|
1074
|
-
}
|
|
1075
|
-
# Spew some output?
|
|
1076
|
-
MU.log "Emitting dependency graph as /tmp/#{config['appname']}.jpg", MU::NOTICE
|
|
1077
|
-
g.output(:jpg => "/tmp/#{config['appname']}.jpg")
|
|
1078
|
-
rescue Exception => e
|
|
1079
|
-
MU.log "Failed to generate GraphViz dependency tree: #{e.inspect}. This should only matter to developers.", MU::WARN, details: e.backtrace
|
|
1080
|
-
end
|
|
1081
|
-
end
|
|
1082
|
-
|
|
1083
|
-
# Generate a documentation-friendly dummy Ruby class for our mu.yaml main
|
|
1084
|
-
# config.
|
|
1085
|
-
def self.emitConfigAsRuby
|
|
1086
|
-
example = %Q{---
|
|
1087
|
-
public_address: 1.2.3.4
|
|
1088
|
-
mu_admin_email: egtlabs@eglobaltech.com
|
|
1089
|
-
mu_admin_name: Joe Schmoe
|
|
1090
|
-
mommacat_port: 2260
|
|
1091
|
-
banner: My Example Mu Master
|
|
1092
|
-
mu_repository: git://github.com/cloudamatic/mu.git
|
|
1093
|
-
repos:
|
|
1094
|
-
- https://github.com/cloudamatic/mu_demo_platform
|
|
1095
|
-
allow_invade_foreign_vpcs: true
|
|
1096
|
-
ansible_dir:
|
|
1097
|
-
aws:
|
|
1098
|
-
egtdev:
|
|
1099
|
-
region: us-east-1
|
|
1100
|
-
log_bucket_name: egt-mu-log-bucket
|
|
1101
|
-
default: true
|
|
1102
|
-
name: egtdev
|
|
1103
|
-
personal:
|
|
1104
|
-
region: us-east-2
|
|
1105
|
-
log_bucket_name: my-mu-log-bucket
|
|
1106
|
-
name: personal
|
|
1107
|
-
google:
|
|
1108
|
-
egtlabs:
|
|
1109
|
-
project: egt-labs-admin
|
|
1110
|
-
credentials_file: /opt/mu/etc/google.json
|
|
1111
|
-
region: us-east4
|
|
1112
|
-
log_bucket_name: hexabucket-761234
|
|
1113
|
-
default: true
|
|
1114
|
-
}
|
|
1115
|
-
mu_yaml_schema = eval(%Q{
|
|
1116
|
-
$NOOP = true
|
|
1117
|
-
load "#{MU.myRoot}/bin/mu-configure"
|
|
1118
|
-
$CONFIGURABLES
|
|
1119
|
-
})
|
|
1120
|
-
return if mu_yaml_schema.nil? or !mu_yaml_schema.is_a?(Hash)
|
|
1121
|
-
muyamlpath = "#{MU.myRoot}/modules/mu/mu.yaml.rb"
|
|
1122
|
-
MU.log "Converting mu.yaml schema to Ruby objects in #{muyamlpath}"
|
|
1123
|
-
muyaml_rb = File.new(muyamlpath, File::CREAT|File::TRUNC|File::RDWR, 0644)
|
|
1124
|
-
muyaml_rb.puts "# Configuration schema for mu.yaml. See also {https://github.com/cloudamatic/mu/wiki/Configuration the Mu wiki}."
|
|
1125
|
-
muyaml_rb.puts "#"
|
|
1126
|
-
muyaml_rb.puts "# Example:"
|
|
1127
|
-
muyaml_rb.puts "#"
|
|
1128
|
-
muyaml_rb.puts "# <pre>"
|
|
1129
|
-
example.split(/\n/).each { |line|
|
|
1130
|
-
muyaml_rb.puts "# "+line+" " # markdooooown
|
|
1131
|
-
}
|
|
1132
|
-
muyaml_rb.puts "# </pre>"
|
|
1133
|
-
muyaml_rb.puts "module MuYAML"
|
|
1134
|
-
muyaml_rb.puts "\t# The configuration file format for Mu's main config file."
|
|
1135
|
-
self.printMuYamlSchema(muyaml_rb, [], { "subtree" => mu_yaml_schema })
|
|
1136
|
-
muyaml_rb.puts "end"
|
|
1137
|
-
muyaml_rb.close
|
|
1138
|
-
end
|
|
1139
|
-
|
|
1140
|
-
# Take the schema we've defined and create a dummy Ruby class tree out of
|
|
1141
|
-
# it, basically so we can leverage Yard to document it.
|
|
1142
|
-
def self.emitSchemaAsRuby
|
|
1143
|
-
kittenpath = "#{MU.myRoot}/modules/mu/kittens.rb"
|
|
1144
|
-
MU.log "Converting Basket of Kittens schema to Ruby objects in #{kittenpath}"
|
|
1145
|
-
kitten_rb = File.new(kittenpath, File::CREAT|File::TRUNC|File::RDWR, 0644)
|
|
1146
|
-
kitten_rb.puts "### THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT ###"
|
|
1147
|
-
kitten_rb.puts "#"
|
|
1148
|
-
kitten_rb.puts "#"
|
|
1149
|
-
kitten_rb.puts "#"
|
|
1150
|
-
kitten_rb.puts "module MU"
|
|
1151
|
-
kitten_rb.puts "class Config"
|
|
1152
|
-
kitten_rb.puts "\t# The configuration file format for Mu application stacks."
|
|
1153
|
-
self.printSchema(kitten_rb, ["BasketofKittens"], MU::Config.docSchema)
|
|
1154
|
-
kitten_rb.puts "end"
|
|
1155
|
-
kitten_rb.puts "end"
|
|
1156
|
-
kitten_rb.close
|
|
1157
|
-
|
|
1158
|
-
end
|
|
1159
|
-
|
|
1160
|
-
# Take an IP block and split it into a more-or-less arbitrary number of
|
|
1161
|
-
# subnets.
|
|
1162
|
-
# @param ip_block [String]: CIDR of the network to subdivide
|
|
1163
|
-
# @param subnets_desired [Integer]: Number of subnets we want back
|
|
1164
|
-
# @param max_mask [Integer]: The highest netmask we're allowed to use for a subnet (various by cloud provider)
|
|
1165
|
-
# @return [MU::Config::Tail]: Resulting subnet tails, or nil if an error occurred.
|
|
1166
|
-
def divideNetwork(ip_block, subnets_desired, max_mask = 28)
|
|
1167
|
-
cidr = NetAddr::IPv4Net.parse(ip_block.to_s)
|
|
1168
|
-
|
|
1169
|
-
# Ugly but reliable method of landing on the right subnet size
|
|
1170
|
-
subnet_bits = cidr.netmask.prefix_len
|
|
1171
|
-
begin
|
|
1172
|
-
subnet_bits += 1
|
|
1173
|
-
if subnet_bits > max_mask
|
|
1174
|
-
MU.log "Can't subdivide #{cidr.to_s} into #{subnets_desired.to_s}", MU::ERR
|
|
1175
|
-
raise MuError, "Subnets smaller than /#{max_mask} not permitted"
|
|
433
|
+
# Insert a dependency into the config hash of a resource, with sensible
|
|
434
|
+
# error checking and de-duplication.
|
|
435
|
+
# @param resource [Hash]
|
|
436
|
+
# @param name [String]
|
|
437
|
+
# @param type [String]
|
|
438
|
+
# @param phase [String]
|
|
439
|
+
# @param no_create_wait [Boolean]
|
|
440
|
+
def self.addDependency(resource, name, type, phase: "create", no_create_wait: false)
|
|
441
|
+
if ![nil, "create", "groom"].include?(phase)
|
|
442
|
+
raise MuError, "Invalid phase '#{phase}' while adding dependency #{type} #{name} to #{resource['name']}"
|
|
443
|
+
end
|
|
444
|
+
resource['dependencies'] ||= []
|
|
445
|
+
_shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type)
|
|
446
|
+
|
|
447
|
+
resource['dependencies'].each { |dep|
|
|
448
|
+
if dep['type'] == cfg_name and dep['name'].to_s == name.to_s
|
|
449
|
+
dep["no_create_wait"] = no_create_wait
|
|
450
|
+
dep["phase"] = phase if phase
|
|
451
|
+
return
|
|
1176
452
|
end
|
|
1177
|
-
|
|
453
|
+
}
|
|
1178
454
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
455
|
+
newdep = {
|
|
456
|
+
"type" => cfg_name,
|
|
457
|
+
"name" => name.to_s,
|
|
458
|
+
"no_create_wait" => no_create_wait
|
|
459
|
+
}
|
|
460
|
+
newdep["phase"] = phase if phase
|
|
1182
461
|
|
|
1183
|
-
|
|
1184
|
-
subnets = []
|
|
1185
|
-
(0..subnets_desired).each { |x|
|
|
1186
|
-
subnets << cidr.nth_subnet(subnet_bits, x).to_s
|
|
1187
|
-
}
|
|
1188
|
-
rescue RuntimeError => e
|
|
1189
|
-
if e.message.match(/exceeds subnets available for allocation/)
|
|
1190
|
-
MU.log e.message, MU::ERR
|
|
1191
|
-
MU.log "I'm attempting to create #{subnets_desired} subnets (one public and one private for each Availability Zone), of #{subnet_size} addresses each, but that's too many for a /#{cidr.netmask.prefix_len} network. Either declare a larger network, or explicitly declare a list of subnets with few enough entries to fit.", MU::ERR
|
|
1192
|
-
return nil
|
|
1193
|
-
else
|
|
1194
|
-
raise e
|
|
1195
|
-
end
|
|
1196
|
-
end
|
|
462
|
+
resource['dependencies'] << newdep
|
|
1197
463
|
|
|
1198
|
-
subnets = getTail("subnetblocks", value: subnets.join(","), cloudtype: "CommaDelimitedList", description: "IP Address ranges to be used for VPC subnets", prettyname: "SubnetIpBlocks", list_of: "ip_block").map { |tail| tail["ip_block"] }
|
|
1199
|
-
subnets
|
|
1200
464
|
end
|
|
1201
465
|
|
|
1202
466
|
# See if a given resource is configured in the current stack
|
|
@@ -1206,7 +470,7 @@ $CONFIGURABLES
|
|
|
1206
470
|
def haveLitterMate?(name, type, has_multiple: false)
|
|
1207
471
|
@kittencfg_semaphore.synchronize {
|
|
1208
472
|
matches = []
|
|
1209
|
-
|
|
473
|
+
_shortclass, _cfg_name, cfg_plural, _classname = MU::Cloud.getResourceNames(type)
|
|
1210
474
|
if @kittens[cfg_plural]
|
|
1211
475
|
@kittens[cfg_plural].each { |kitten|
|
|
1212
476
|
if kitten['name'].to_s == name.to_s or
|
|
@@ -1233,7 +497,7 @@ $CONFIGURABLES
|
|
|
1233
497
|
# @param type [String]: The type of resource being removed
|
|
1234
498
|
def removeKitten(name, type)
|
|
1235
499
|
@kittencfg_semaphore.synchronize {
|
|
1236
|
-
|
|
500
|
+
_shortclass, _cfg_name, cfg_plural, _classname = MU::Cloud.getResourceNames(type)
|
|
1237
501
|
deletia = nil
|
|
1238
502
|
if @kittens[cfg_plural]
|
|
1239
503
|
@kittens[cfg_plural].each { |kitten|
|
|
@@ -1247,42 +511,6 @@ $CONFIGURABLES
|
|
|
1247
511
|
}
|
|
1248
512
|
end
|
|
1249
513
|
|
|
1250
|
-
# FirewallRules can reference other FirewallRules, which means we need to do
|
|
1251
|
-
# an extra pass to make sure we get all intra-stack dependencies correct.
|
|
1252
|
-
# @param acl [Hash]: The configuration hash for the FirewallRule to check
|
|
1253
|
-
# @return [Hash]
|
|
1254
|
-
def resolveIntraStackFirewallRefs(acl, delay_validation = false)
|
|
1255
|
-
acl["rules"].each { |acl_include|
|
|
1256
|
-
if acl_include['sgs']
|
|
1257
|
-
acl_include['sgs'].each { |sg_ref|
|
|
1258
|
-
if haveLitterMate?(sg_ref, "firewall_rules")
|
|
1259
|
-
acl["dependencies"] ||= []
|
|
1260
|
-
found = false
|
|
1261
|
-
acl["dependencies"].each { |dep|
|
|
1262
|
-
if dep["type"] == "firewall_rule" and dep["name"] == sg_ref
|
|
1263
|
-
dep["no_create_wait"] = true
|
|
1264
|
-
found = true
|
|
1265
|
-
end
|
|
1266
|
-
}
|
|
1267
|
-
if !found
|
|
1268
|
-
acl["dependencies"] << {
|
|
1269
|
-
"type" => "firewall_rule",
|
|
1270
|
-
"name" => sg_ref,
|
|
1271
|
-
"no_create_wait" => true
|
|
1272
|
-
}
|
|
1273
|
-
end
|
|
1274
|
-
siblingfw = haveLitterMate?(sg_ref, "firewall_rules")
|
|
1275
|
-
if !siblingfw["#MU_VALIDATED"]
|
|
1276
|
-
# XXX raise failure somehow
|
|
1277
|
-
insertKitten(siblingfw, "firewall_rules", delay_validation: delay_validation)
|
|
1278
|
-
end
|
|
1279
|
-
end
|
|
1280
|
-
}
|
|
1281
|
-
end
|
|
1282
|
-
}
|
|
1283
|
-
acl
|
|
1284
|
-
end
|
|
1285
|
-
|
|
1286
514
|
# Insert a resource into the current stack
|
|
1287
515
|
# @param descriptor [Hash]: The configuration description, as from a Basket of Kittens
|
|
1288
516
|
# @param type [String]: The type of resource being added
|
|
@@ -1291,6 +519,7 @@ $CONFIGURABLES
|
|
|
1291
519
|
def insertKitten(descriptor, type, delay_validation = false, ignore_duplicates: false, overwrite: false)
|
|
1292
520
|
append = false
|
|
1293
521
|
start = Time.now
|
|
522
|
+
|
|
1294
523
|
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
1295
524
|
MU.log "insertKitten on #{cfg_name} #{descriptor['name']} (delay_validation: #{delay_validation.to_s})", MU::DEBUG, details: caller[0]
|
|
1296
525
|
|
|
@@ -1330,7 +559,7 @@ $CONFIGURABLES
|
|
|
1330
559
|
# cloud-specific schema.
|
|
1331
560
|
schemaclass = Object.const_get("MU").const_get("Config").const_get(shortclass)
|
|
1332
561
|
myschema = Marshal.load(Marshal.dump(MU::Config.schema["properties"][cfg_plural]["items"]))
|
|
1333
|
-
more_required, more_schema =
|
|
562
|
+
more_required, more_schema = MU::Cloud.resourceClass(descriptor["cloud"], type).schema(self)
|
|
1334
563
|
if more_schema
|
|
1335
564
|
MU::Config.schemaMerge(myschema["properties"], more_schema, descriptor["cloud"])
|
|
1336
565
|
end
|
|
@@ -1349,7 +578,7 @@ $CONFIGURABLES
|
|
|
1349
578
|
end
|
|
1350
579
|
|
|
1351
580
|
# Make sure a sensible region has been targeted, if applicable
|
|
1352
|
-
classobj =
|
|
581
|
+
classobj = MU::Cloud.cloudClass(descriptor["cloud"])
|
|
1353
582
|
if descriptor["region"]
|
|
1354
583
|
valid_regions = classobj.listRegions
|
|
1355
584
|
if !valid_regions.include?(descriptor["region"])
|
|
@@ -1362,11 +591,7 @@ $CONFIGURABLES
|
|
|
1362
591
|
if descriptor['project'].nil?
|
|
1363
592
|
descriptor.delete('project')
|
|
1364
593
|
elsif haveLitterMate?(descriptor['project'], "habitats")
|
|
1365
|
-
descriptor['
|
|
1366
|
-
descriptor['dependencies'] << {
|
|
1367
|
-
"type" => "habitat",
|
|
1368
|
-
"name" => descriptor['project']
|
|
1369
|
-
}
|
|
594
|
+
MU::Config.addDependency(descriptor, descriptor['project'], "habitat")
|
|
1370
595
|
end
|
|
1371
596
|
end
|
|
1372
597
|
|
|
@@ -1394,21 +619,16 @@ $CONFIGURABLES
|
|
|
1394
619
|
if !descriptor["vpc"]["name"].nil? and
|
|
1395
620
|
haveLitterMate?(descriptor["vpc"]["name"], "vpcs") and
|
|
1396
621
|
descriptor["vpc"]['deploy_id'].nil? and
|
|
1397
|
-
descriptor["vpc"]['id'].nil?
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
"name" => descriptor["vpc"]["name"],
|
|
1401
|
-
}
|
|
622
|
+
descriptor["vpc"]['id'].nil? and
|
|
623
|
+
!(cfg_name == "vpc" and descriptor['name'] == descriptor['vpc']['name'])
|
|
624
|
+
MU::Config.addDependency(descriptor, descriptor['vpc']['name'], "vpc")
|
|
1402
625
|
siblingvpc = haveLitterMate?(descriptor["vpc"]["name"], "vpcs")
|
|
1403
626
|
|
|
1404
627
|
if siblingvpc and siblingvpc['bastion'] and
|
|
1405
628
|
["server", "server_pool", "container_cluster"].include?(cfg_name) and
|
|
1406
629
|
!descriptor['bastion']
|
|
1407
|
-
if descriptor['name'] != siblingvpc['bastion']
|
|
1408
|
-
descriptor["
|
|
1409
|
-
"type" => "server",
|
|
1410
|
-
"name" => siblingvpc['bastion'].to_h['name']
|
|
1411
|
-
}
|
|
630
|
+
if descriptor['name'] != siblingvpc['bastion']['name']
|
|
631
|
+
MU::Config.addDependency(descriptor, siblingvpc['bastion']['name'], "server")
|
|
1412
632
|
end
|
|
1413
633
|
end
|
|
1414
634
|
|
|
@@ -1470,7 +690,6 @@ $CONFIGURABLES
|
|
|
1470
690
|
if (descriptor['ingress_rules'] or
|
|
1471
691
|
["server", "server_pool", "database", "cache_cluster"].include?(cfg_name))
|
|
1472
692
|
descriptor['ingress_rules'] ||= []
|
|
1473
|
-
fw_classobj = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get("FirewallRule")
|
|
1474
693
|
|
|
1475
694
|
acl = haveLitterMate?(fwname, "firewall_rules")
|
|
1476
695
|
already_exists = !acl.nil?
|
|
@@ -1481,7 +700,7 @@ $CONFIGURABLES
|
|
|
1481
700
|
"region" => descriptor['region'],
|
|
1482
701
|
"credentials" => descriptor["credentials"]
|
|
1483
702
|
}
|
|
1484
|
-
if !
|
|
703
|
+
if !MU::Cloud.resourceClass(descriptor["cloud"], "FirewallRule").isGlobal?
|
|
1485
704
|
acl['region'] = descriptor['region']
|
|
1486
705
|
acl['region'] ||= classobj.myRegion(acl['credentials'])
|
|
1487
706
|
else
|
|
@@ -1507,10 +726,7 @@ $CONFIGURABLES
|
|
|
1507
726
|
if !descriptor["loadbalancers"].nil?
|
|
1508
727
|
descriptor["loadbalancers"].each { |lb|
|
|
1509
728
|
if !lb["concurrent_load_balancer"].nil?
|
|
1510
|
-
descriptor["
|
|
1511
|
-
"type" => "loadbalancer",
|
|
1512
|
-
"name" => lb["concurrent_load_balancer"]
|
|
1513
|
-
}
|
|
729
|
+
MU::Config.addDependency(descriptor, lb["concurrent_load_balancer"], "loadbalancer")
|
|
1514
730
|
end
|
|
1515
731
|
}
|
|
1516
732
|
end
|
|
@@ -1519,10 +735,7 @@ $CONFIGURABLES
|
|
|
1519
735
|
if !descriptor["storage_pools"].nil?
|
|
1520
736
|
descriptor["storage_pools"].each { |sp|
|
|
1521
737
|
if sp["name"]
|
|
1522
|
-
descriptor["
|
|
1523
|
-
"type" => "storage_pool",
|
|
1524
|
-
"name" => sp["name"]
|
|
1525
|
-
}
|
|
738
|
+
MU::Config.addDependency(descriptor, sp["name"], "storage_pool")
|
|
1526
739
|
end
|
|
1527
740
|
}
|
|
1528
741
|
end
|
|
@@ -1533,10 +746,7 @@ $CONFIGURABLES
|
|
|
1533
746
|
next if !acl_include["name"] and !acl_include["rule_name"]
|
|
1534
747
|
acl_include["name"] ||= acl_include["rule_name"]
|
|
1535
748
|
if haveLitterMate?(acl_include["name"], "firewall_rules")
|
|
1536
|
-
descriptor["
|
|
1537
|
-
"type" => "firewall_rule",
|
|
1538
|
-
"name" => acl_include["name"]
|
|
1539
|
-
}
|
|
749
|
+
MU::Config.addDependency(descriptor, acl_include["name"], "firewall_rule", no_create_wait: (cfg_name == "vpc"))
|
|
1540
750
|
elsif acl_include["name"]
|
|
1541
751
|
MU.log shortclass.to_s+" #{descriptor['name']} depends on FirewallRule #{acl_include["name"]}, but no such rule declared.", MU::ERR
|
|
1542
752
|
ok = false
|
|
@@ -1617,7 +827,7 @@ $CONFIGURABLES
|
|
|
1617
827
|
plain_cfg.delete("parent_block") if cfg_plural == "vpcs"
|
|
1618
828
|
begin
|
|
1619
829
|
JSON::Validator.validate!(myschema, plain_cfg)
|
|
1620
|
-
rescue JSON::Schema::ValidationError
|
|
830
|
+
rescue JSON::Schema::ValidationError
|
|
1621
831
|
pp plain_cfg
|
|
1622
832
|
# Use fully_validate to get the complete error list, save some time
|
|
1623
833
|
errors = JSON::Validator.fully_validate(myschema, plain_cfg)
|
|
@@ -1636,7 +846,7 @@ $CONFIGURABLES
|
|
|
1636
846
|
# Run the cloud class's deeper validation, unless we've already failed
|
|
1637
847
|
# on stuff that will cause spurious alarms further in
|
|
1638
848
|
if ok
|
|
1639
|
-
parser =
|
|
849
|
+
parser = MU::Cloud.resourceClass(descriptor['cloud'], type)
|
|
1640
850
|
original_descriptor = MU::Config.stripConfig(descriptor)
|
|
1641
851
|
passed = parser.validateConfig(descriptor, self)
|
|
1642
852
|
|
|
@@ -1646,7 +856,7 @@ $CONFIGURABLES
|
|
|
1646
856
|
end
|
|
1647
857
|
|
|
1648
858
|
# Make sure we've been configured with the right credentials
|
|
1649
|
-
cloudbase =
|
|
859
|
+
cloudbase = MU::Cloud.cloudClass(descriptor['cloud'])
|
|
1650
860
|
credcfg = cloudbase.credConfig(descriptor['credentials'])
|
|
1651
861
|
if !credcfg or credcfg.empty?
|
|
1652
862
|
raise ValidationError, "#{descriptor['cloud']} #{cfg_name} #{descriptor['name']} declares credential set #{descriptor['credentials']}, but no such credentials exist for that cloud provider"
|
|
@@ -1662,168 +872,98 @@ $CONFIGURABLES
|
|
|
1662
872
|
@kittens[cfg_plural] << descriptor if append
|
|
1663
873
|
}
|
|
1664
874
|
|
|
875
|
+
MU.log "insertKitten completed #{cfg_name} #{descriptor['name']} in #{sprintf("%.2fs", Time.now-start)}", MU::DEBUG
|
|
876
|
+
|
|
1665
877
|
ok
|
|
1666
878
|
end
|
|
1667
879
|
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
@@allregions.concat(regions) if regions
|
|
1673
|
-
}
|
|
880
|
+
# For our resources which specify intra-stack dependencies, make sure those
|
|
881
|
+
# dependencies are actually declared.
|
|
882
|
+
def check_dependencies
|
|
883
|
+
ok = true
|
|
1674
884
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
885
|
+
@config.each_pair { |type, values|
|
|
886
|
+
next if !values.instance_of?(Array)
|
|
887
|
+
_shortclass, cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(type, false)
|
|
888
|
+
next if !cfg_name
|
|
889
|
+
values.each { |resource|
|
|
890
|
+
next if !resource.kind_of?(Hash) or resource["dependencies"].nil?
|
|
891
|
+
addme = []
|
|
892
|
+
deleteme = []
|
|
893
|
+
|
|
894
|
+
resource["dependencies"].each { |dependency|
|
|
895
|
+
# make sure the thing we depend on really exists
|
|
896
|
+
sibling = haveLitterMate?(dependency['name'], dependency['type'])
|
|
897
|
+
if !sibling
|
|
898
|
+
MU.log "Missing dependency: #{type}{#{resource['name']}} needs #{cfg_name}{#{dependency['name']}}", MU::ERR
|
|
899
|
+
ok = false
|
|
900
|
+
next
|
|
901
|
+
end
|
|
1691
902
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
903
|
+
# Fudge dependency declarations to quash virtual_names that we know
|
|
904
|
+
# are extraneous. Note that wee can't do all virtual names here; we
|
|
905
|
+
# have no way to guess which of a collection of resources is the
|
|
906
|
+
# real correct one.
|
|
907
|
+
if sibling['virtual_name'] == dependency['name']
|
|
908
|
+
real_resources = []
|
|
909
|
+
found_exact = false
|
|
910
|
+
resource["dependencies"].each { |dep_again|
|
|
911
|
+
if dep_again['type'] == dependency['type'] and sibling['name'] == dep_again['name']
|
|
912
|
+
dependency['name'] = sibling['name']
|
|
913
|
+
found_exact = true
|
|
914
|
+
break
|
|
915
|
+
end
|
|
916
|
+
}
|
|
917
|
+
if !found_exact
|
|
918
|
+
all_siblings = haveLitterMate?(dependency['name'], dependency['type'], has_multiple: true)
|
|
919
|
+
if all_siblings.size > 0
|
|
920
|
+
all_siblings.each { |s|
|
|
921
|
+
newguy = dependency.clone
|
|
922
|
+
newguy['name'] = s['name']
|
|
923
|
+
addme << newguy
|
|
924
|
+
}
|
|
925
|
+
deleteme << dependency
|
|
926
|
+
MU.log "Expanding dependency which maps to virtual resources to all matching real resources", MU::NOTICE, details: { sibling['virtual_name'] => addme }
|
|
927
|
+
next
|
|
928
|
+
end
|
|
929
|
+
end
|
|
930
|
+
end
|
|
1700
931
|
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
end
|
|
932
|
+
# Check for a circular relationship that will lead to a deadlock
|
|
933
|
+
# when creating resource. This only goes one layer deep, and does
|
|
934
|
+
# not consider groom-phase deadlocks.
|
|
935
|
+
if dependency['phase'] == "groom" or dependency['no_create_wait'] or (
|
|
936
|
+
!MU::Cloud.resourceClass(sibling['cloud'], type).deps_wait_on_my_creation and
|
|
937
|
+
!MU::Cloud.resourceClass(resource['cloud'], type).waits_on_parent_completion
|
|
938
|
+
)
|
|
939
|
+
next
|
|
940
|
+
end
|
|
1711
941
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
"title" => "tags",
|
|
1723
|
-
"required" => ["key", "value"],
|
|
1724
|
-
"additionalProperties" => false,
|
|
1725
|
-
"properties" => {
|
|
1726
|
-
"key" => {
|
|
1727
|
-
"type" => "string",
|
|
1728
|
-
},
|
|
1729
|
-
"value" => {
|
|
1730
|
-
"type" => "string",
|
|
1731
|
-
}
|
|
942
|
+
if sibling['dependencies']
|
|
943
|
+
sibling['dependencies'].each { |sib_dep|
|
|
944
|
+
next if sib_dep['type'] != cfg_name or sib_dep['no_create_wait']
|
|
945
|
+
cousin = haveLitterMate?(sib_dep['name'], sib_dep['type'])
|
|
946
|
+
if cousin and cousin['name'] == resource['name']
|
|
947
|
+
MU.log "Circular dependency between #{type} #{resource['name']} <=> #{dependency['type']} #{dependency['name']}", MU::ERR, details: [ resource['name'] => dependency, sibling['name'] => sib_dep ]
|
|
948
|
+
ok = false
|
|
949
|
+
end
|
|
950
|
+
}
|
|
951
|
+
end
|
|
1732
952
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
953
|
+
resource["dependencies"].reject! { |dep| deleteme.include?(dep) }
|
|
954
|
+
resource["dependencies"].concat(addme)
|
|
955
|
+
resource["dependencies"].uniq!
|
|
1736
956
|
|
|
1737
|
-
# Configuration chunk for choosing a cloud provider
|
|
1738
|
-
# @return [Hash]
|
|
1739
|
-
def self.cloud_primitive
|
|
1740
|
-
{
|
|
1741
|
-
"type" => "string",
|
|
1742
|
-
# "default" => MU::Config.defaultCloud, # applyInheritedDefaults does this better
|
|
1743
|
-
"enum" => MU::Cloud.supportedClouds
|
|
1744
|
-
}
|
|
1745
|
-
end
|
|
1746
|
-
|
|
1747
|
-
# Generate configuration for the general-pursose ADMIN firewall rulesets
|
|
1748
|
-
# (security groups in AWS). Note that these are unique to regions and
|
|
1749
|
-
# individual VPCs (as well as Classic, which is just a degenerate case of
|
|
1750
|
-
# a VPC for our purposes.
|
|
1751
|
-
# @param vpc [Hash]: A VPC reference as defined in our config schema. This originates with the calling resource, so we'll peel out just what we need (a name or cloud id of a VPC).
|
|
1752
|
-
# @param admin_ip [String]: Optional string of an extra IP address to allow blanket access to the calling resource.
|
|
1753
|
-
# @param cloud [String]: The parent resource's cloud plugin identifier
|
|
1754
|
-
# @param region [String]: Cloud provider region, if applicable.
|
|
1755
|
-
# @return [Hash<String>]: A dependency description that the calling resource can then add to itself.
|
|
1756
|
-
def adminFirewallRuleset(vpc: nil, admin_ip: nil, region: nil, cloud: nil, credentials: nil, rules_only: false)
|
|
1757
|
-
if !cloud or (cloud == "AWS" and !region)
|
|
1758
|
-
raise MuError, "Cannot call adminFirewallRuleset without specifying the parent's region and cloud provider"
|
|
1759
|
-
end
|
|
1760
|
-
hosts = Array.new
|
|
1761
|
-
hosts << "#{MU.my_public_ip}/32" if MU.my_public_ip
|
|
1762
|
-
hosts << "#{MU.my_private_ip}/32" if MU.my_private_ip
|
|
1763
|
-
hosts << "#{MU.mu_public_ip}/32" if MU.mu_public_ip
|
|
1764
|
-
hosts << "#{admin_ip}/32" if admin_ip
|
|
1765
|
-
hosts.uniq!
|
|
1766
|
-
|
|
1767
|
-
rules = []
|
|
1768
|
-
if cloud == "Google"
|
|
1769
|
-
rules = [
|
|
1770
|
-
{ "ingress" => true, "proto" => "all", "hosts" => hosts },
|
|
1771
|
-
{ "egress" => true, "proto" => "all", "hosts" => hosts }
|
|
1772
|
-
]
|
|
1773
|
-
else
|
|
1774
|
-
rules = [
|
|
1775
|
-
{ "proto" => "tcp", "port_range" => "0-65535", "hosts" => hosts },
|
|
1776
|
-
{ "proto" => "udp", "port_range" => "0-65535", "hosts" => hosts },
|
|
1777
|
-
{ "proto" => "icmp", "port_range" => "-1", "hosts" => hosts }
|
|
1778
|
-
]
|
|
1779
|
-
end
|
|
1780
|
-
|
|
1781
|
-
resclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get("FirewallRule")
|
|
1782
|
-
|
|
1783
|
-
if rules_only
|
|
1784
|
-
return rules
|
|
1785
|
-
end
|
|
1786
|
-
|
|
1787
|
-
name = "admin"
|
|
1788
|
-
name += credentials.to_s if credentials
|
|
1789
|
-
realvpc = nil
|
|
1790
|
-
if vpc
|
|
1791
|
-
realvpc = {}
|
|
1792
|
-
['vpc_name', 'vpc_id'].each { |p|
|
|
1793
|
-
if vpc[p]
|
|
1794
|
-
vpc[p.sub(/^vpc_/, '')] = vpc[p]
|
|
1795
|
-
vpc.delete(p)
|
|
1796
|
-
end
|
|
1797
957
|
}
|
|
1798
|
-
|
|
1799
|
-
realvpc[field] = vpc[field] if !vpc[field].nil?
|
|
1800
|
-
}
|
|
1801
|
-
if !realvpc['id'].nil? and !realvpc['id'].empty?
|
|
1802
|
-
# Stupid kludge for Google cloud_ids which are sometimes URLs and
|
|
1803
|
-
# sometimes not. Requirements are inconsistent from scenario to
|
|
1804
|
-
# scenario.
|
|
1805
|
-
name = name + "-" + realvpc['id'].gsub(/.*\//, "")
|
|
1806
|
-
realvpc['id'] = getTail("id", value: realvpc['id'], prettyname: "Admin Firewall Ruleset #{name} Target VPC", cloudtype: "AWS::EC2::VPC::Id") if realvpc["id"].is_a?(String)
|
|
1807
|
-
elsif !realvpc['name'].nil?
|
|
1808
|
-
name = name + "-" + realvpc['name']
|
|
1809
|
-
end
|
|
1810
|
-
end
|
|
1811
|
-
|
|
958
|
+
}
|
|
1812
959
|
|
|
1813
|
-
|
|
1814
|
-
if cloud == "Google" and acl["vpc"] and acl["vpc"]["habitat"]
|
|
1815
|
-
acl['project'] = acl["vpc"]["habitat"]["id"] || acl["vpc"]["habitat"]["name"]
|
|
1816
|
-
end
|
|
1817
|
-
acl.delete("vpc") if !acl["vpc"]
|
|
1818
|
-
if !resclass.isGlobal? and !region.nil? and !region.empty?
|
|
1819
|
-
acl["region"] = region
|
|
1820
|
-
end
|
|
1821
|
-
@admin_firewall_rules << acl if !@admin_firewall_rules.include?(acl)
|
|
1822
|
-
return {"type" => "firewall_rule", "name" => name}
|
|
960
|
+
ok
|
|
1823
961
|
end
|
|
1824
962
|
|
|
1825
|
-
|
|
1826
|
-
|
|
963
|
+
# Ugly text-manipulation to recursively resolve some placeholder strings
|
|
964
|
+
# we put in for ERB include() directives.
|
|
965
|
+
# @param lines [String]
|
|
966
|
+
# @return [String]
|
|
1827
967
|
def self.resolveYAMLAnchors(lines)
|
|
1828
968
|
new_text = ""
|
|
1829
969
|
lines.each_line { |line|
|
|
@@ -1843,7 +983,6 @@ $CONFIGURABLES
|
|
|
1843
983
|
return new_text
|
|
1844
984
|
end
|
|
1845
985
|
|
|
1846
|
-
|
|
1847
986
|
# Given a path to a config file, try to guess whether it's YAML or JSON.
|
|
1848
987
|
# @param path [String]: The path to the file to check.
|
|
1849
988
|
def self.guessFormat(path)
|
|
@@ -1852,10 +991,10 @@ $CONFIGURABLES
|
|
|
1852
991
|
stripped = raw.gsub(/<%.*?%>,?/, "").gsub(/,[\n\s]*([\]\}])/, '\1')
|
|
1853
992
|
begin
|
|
1854
993
|
JSON.parse(stripped)
|
|
1855
|
-
rescue JSON::ParserError
|
|
994
|
+
rescue JSON::ParserError
|
|
1856
995
|
begin
|
|
1857
996
|
YAML.load(raw.gsub(/<%.*?%>/, ""))
|
|
1858
|
-
rescue Psych::SyntaxError
|
|
997
|
+
rescue Psych::SyntaxError
|
|
1859
998
|
# Ok, well neither of those worked, let's assume that filenames are
|
|
1860
999
|
# meaningful.
|
|
1861
1000
|
if path.match(/\.(yaml|yml)$/i)
|
|
@@ -1935,7 +1074,7 @@ $CONFIGURABLES
|
|
|
1935
1074
|
end
|
|
1936
1075
|
begin
|
|
1937
1076
|
erb = ERB.new(File.read(file), nil, "<>")
|
|
1938
|
-
rescue Errno::ENOENT
|
|
1077
|
+
rescue Errno::ENOENT
|
|
1939
1078
|
retries = retries + 1
|
|
1940
1079
|
if retries == 1
|
|
1941
1080
|
file = File.dirname(MU::Config.config_path)+"/"+orig_filename
|
|
@@ -1960,12 +1099,12 @@ $CONFIGURABLES
|
|
|
1960
1099
|
parsed_cfg = nil
|
|
1961
1100
|
begin
|
|
1962
1101
|
parsed_cfg = JSON.parse(erb.result(binding))
|
|
1963
|
-
parsed_as = :json
|
|
1102
|
+
# parsed_as = :json
|
|
1964
1103
|
rescue JSON::ParserError => e
|
|
1965
1104
|
MU.log e.inspect, MU::DEBUG
|
|
1966
1105
|
begin
|
|
1967
1106
|
parsed_cfg = YAML.load(MU::Config.resolveYAMLAnchors(erb.result(binding)))
|
|
1968
|
-
parsed_as = :yaml
|
|
1107
|
+
# parsed_as = :yaml
|
|
1969
1108
|
rescue Psych::SyntaxError => e
|
|
1970
1109
|
MU.log e.inspect, MU::DEBUG
|
|
1971
1110
|
MU.log "#{file} parsed neither as JSON nor as YAML, including as raw text", MU::WARN if @param_pass
|
|
@@ -1980,16 +1119,11 @@ $CONFIGURABLES
|
|
|
1980
1119
|
$yaml_refs[file] = ""+YAML.dump(parsed_cfg).sub(/^---\n/, "")
|
|
1981
1120
|
return "# MU::Config.include PLACEHOLDER #{file} REDLOHECALP"
|
|
1982
1121
|
end
|
|
1983
|
-
rescue SyntaxError
|
|
1122
|
+
rescue SyntaxError
|
|
1984
1123
|
raise ValidationError, "ERB in #{file} threw a syntax error"
|
|
1985
1124
|
end
|
|
1986
1125
|
end
|
|
1987
1126
|
|
|
1988
|
-
# (see #include)
|
|
1989
|
-
def include(file)
|
|
1990
|
-
MU::Config.include(file, get_binding(@@tails.keys.sort), param_pass = @param_pass)
|
|
1991
|
-
end
|
|
1992
|
-
|
|
1993
1127
|
@@bindings = {}
|
|
1994
1128
|
# Keep a cache of bindings we've created as sandbox contexts for ERB
|
|
1995
1129
|
# processing, so we don't keep reloading the entire Mu library inside new
|
|
@@ -1998,6 +1132,13 @@ $CONFIGURABLES
|
|
|
1998
1132
|
@@bindings
|
|
1999
1133
|
end
|
|
2000
1134
|
|
|
1135
|
+
private
|
|
1136
|
+
|
|
1137
|
+
# (see #include)
|
|
1138
|
+
def include(file)
|
|
1139
|
+
MU::Config.include(file, get_binding(@@tails.keys.sort), @param_pass)
|
|
1140
|
+
end
|
|
1141
|
+
|
|
2001
1142
|
# Namespace magic to pass to ERB's result method.
|
|
2002
1143
|
def get_binding(keyset)
|
|
2003
1144
|
environment = $environment
|
|
@@ -2012,242 +1153,6 @@ $CONFIGURABLES
|
|
|
2012
1153
|
MU::Config.global_bindings[keyset]
|
|
2013
1154
|
end
|
|
2014
1155
|
|
|
2015
|
-
def applySchemaDefaults(conf_chunk = config, schema_chunk = schema, depth = 0, siblings = nil, type: nil)
|
|
2016
|
-
return if schema_chunk.nil?
|
|
2017
|
-
|
|
2018
|
-
if conf_chunk != nil and schema_chunk["properties"].kind_of?(Hash) and conf_chunk.is_a?(Hash)
|
|
2019
|
-
|
|
2020
|
-
if schema_chunk["properties"]["creation_style"].nil? or
|
|
2021
|
-
schema_chunk["properties"]["creation_style"] != "existing"
|
|
2022
|
-
schema_chunk["properties"].each_pair { |key, subschema|
|
|
2023
|
-
shortclass = if conf_chunk[key]
|
|
2024
|
-
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(key)
|
|
2025
|
-
shortclass
|
|
2026
|
-
else
|
|
2027
|
-
nil
|
|
2028
|
-
end
|
|
2029
|
-
|
|
2030
|
-
new_val = applySchemaDefaults(conf_chunk[key], subschema, depth+1, conf_chunk, type: shortclass).dup
|
|
2031
|
-
|
|
2032
|
-
conf_chunk[key] = Marshal.load(Marshal.dump(new_val)) if !new_val.nil?
|
|
2033
|
-
}
|
|
2034
|
-
end
|
|
2035
|
-
elsif schema_chunk["type"] == "array" and conf_chunk.kind_of?(Array)
|
|
2036
|
-
conf_chunk.map! { |item|
|
|
2037
|
-
# If we're working on a resource type, go get implementation-specific
|
|
2038
|
-
# schema information so that we set those defaults correctly.
|
|
2039
|
-
realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
|
|
2040
|
-
|
|
2041
|
-
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
|
|
2042
|
-
toplevel_required, cloudschema = cloudclass.schema(self)
|
|
2043
|
-
|
|
2044
|
-
newschema = schema_chunk["items"].dup
|
|
2045
|
-
newschema["properties"].merge!(cloudschema)
|
|
2046
|
-
newschema
|
|
2047
|
-
else
|
|
2048
|
-
schema_chunk["items"].dup
|
|
2049
|
-
end
|
|
2050
|
-
|
|
2051
|
-
applySchemaDefaults(item, realschema, depth+1, conf_chunk, type: type).dup
|
|
2052
|
-
}
|
|
2053
|
-
else
|
|
2054
|
-
if conf_chunk.nil? and !schema_chunk["default_if"].nil? and !siblings.nil?
|
|
2055
|
-
schema_chunk["default_if"].each { |cond|
|
|
2056
|
-
if siblings[cond["key_is"]] == cond["value_is"]
|
|
2057
|
-
return Marshal.load(Marshal.dump(cond["set"]))
|
|
2058
|
-
end
|
|
2059
|
-
}
|
|
2060
|
-
end
|
|
2061
|
-
if conf_chunk.nil? and schema_chunk["default"] != nil
|
|
2062
|
-
return Marshal.load(Marshal.dump(schema_chunk["default"]))
|
|
2063
|
-
end
|
|
2064
|
-
end
|
|
2065
|
-
|
|
2066
|
-
return conf_chunk
|
|
2067
|
-
end
|
|
2068
|
-
|
|
2069
|
-
# For our resources which specify intra-stack dependencies, make sure those
|
|
2070
|
-
# dependencies are actually declared.
|
|
2071
|
-
# TODO check for loops
|
|
2072
|
-
def self.check_dependencies(config)
|
|
2073
|
-
ok = true
|
|
2074
|
-
|
|
2075
|
-
config.each_pair { |type, values|
|
|
2076
|
-
if values.instance_of?(Array)
|
|
2077
|
-
values.each { |resource|
|
|
2078
|
-
if resource.kind_of?(Hash) and !resource["dependencies"].nil?
|
|
2079
|
-
append = []
|
|
2080
|
-
delete = []
|
|
2081
|
-
resource["dependencies"].each { |dependency|
|
|
2082
|
-
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(dependency["type"])
|
|
2083
|
-
found = false
|
|
2084
|
-
names_seen = []
|
|
2085
|
-
if !config[cfg_plural].nil?
|
|
2086
|
-
config[cfg_plural].each { |service|
|
|
2087
|
-
names_seen << service["name"].to_s
|
|
2088
|
-
found = true if service["name"].to_s == dependency["name"].to_s
|
|
2089
|
-
if service["virtual_name"]
|
|
2090
|
-
names_seen << service["virtual_name"].to_s
|
|
2091
|
-
if service["virtual_name"].to_s == dependency["name"].to_s
|
|
2092
|
-
found = true
|
|
2093
|
-
append_me = dependency.dup
|
|
2094
|
-
append_me['name'] = service['name']
|
|
2095
|
-
append << append_me
|
|
2096
|
-
delete << dependency
|
|
2097
|
-
end
|
|
2098
|
-
end
|
|
2099
|
-
}
|
|
2100
|
-
end
|
|
2101
|
-
if !found
|
|
2102
|
-
MU.log "Missing dependency: #{type}{#{resource['name']}} needs #{cfg_name}{#{dependency['name']}}", MU::ERR, details: names_seen
|
|
2103
|
-
ok = false
|
|
2104
|
-
end
|
|
2105
|
-
}
|
|
2106
|
-
if append.size > 0
|
|
2107
|
-
append.uniq!
|
|
2108
|
-
resource["dependencies"].concat(append)
|
|
2109
|
-
end
|
|
2110
|
-
if delete.size > 0
|
|
2111
|
-
delete.each { |delete_me|
|
|
2112
|
-
resource["dependencies"].delete(delete_me)
|
|
2113
|
-
}
|
|
2114
|
-
end
|
|
2115
|
-
end
|
|
2116
|
-
}
|
|
2117
|
-
end
|
|
2118
|
-
}
|
|
2119
|
-
return ok
|
|
2120
|
-
end
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
# Verify that a server or server_pool has a valid AD config referencing
|
|
2124
|
-
# valid Vaults for credentials.
|
|
2125
|
-
def self.check_vault_refs(server)
|
|
2126
|
-
ok = true
|
|
2127
|
-
server['vault_access'] = [] if server['vault_access'].nil?
|
|
2128
|
-
server['groomer'] ||= self.defaultGroomer
|
|
2129
|
-
groomclass = MU::Groomer.loadGroomer(server['groomer'])
|
|
2130
|
-
|
|
2131
|
-
begin
|
|
2132
|
-
if !server['active_directory'].nil?
|
|
2133
|
-
["domain_admin_vault", "domain_join_vault"].each { |vault_class|
|
|
2134
|
-
server['vault_access'] << {
|
|
2135
|
-
"vault" => server['active_directory'][vault_class]['vault'],
|
|
2136
|
-
"item" => server['active_directory'][vault_class]['item']
|
|
2137
|
-
}
|
|
2138
|
-
item = groomclass.getSecret(
|
|
2139
|
-
vault: server['active_directory'][vault_class]['vault'],
|
|
2140
|
-
item: server['active_directory'][vault_class]['item'],
|
|
2141
|
-
)
|
|
2142
|
-
["username_field", "password_field"].each { |field|
|
|
2143
|
-
if !item.has_key?(server['active_directory'][vault_class][field])
|
|
2144
|
-
ok = false
|
|
2145
|
-
MU.log "I don't see a value named #{field} in Chef Vault #{server['active_directory'][vault_class]['vault']}:#{server['active_directory'][vault_class]['item']}", MU::ERR
|
|
2146
|
-
end
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
end
|
|
2150
|
-
|
|
2151
|
-
if !server['windows_auth_vault'].nil?
|
|
2152
|
-
server['use_cloud_provider_windows_password'] = false
|
|
2153
|
-
|
|
2154
|
-
server['vault_access'] << {
|
|
2155
|
-
"vault" => server['windows_auth_vault']['vault'],
|
|
2156
|
-
"item" => server['windows_auth_vault']['item']
|
|
2157
|
-
}
|
|
2158
|
-
item = groomclass.getSecret(
|
|
2159
|
-
vault: server['windows_auth_vault']['vault'],
|
|
2160
|
-
item: server['windows_auth_vault']['item']
|
|
2161
|
-
)
|
|
2162
|
-
["password_field", "ec2config_password_field", "sshd_password_field"].each { |field|
|
|
2163
|
-
if !item.has_key?(server['windows_auth_vault'][field])
|
|
2164
|
-
MU.log "No value named #{field} in Chef Vault #{server['windows_auth_vault']['vault']}:#{server['windows_auth_vault']['item']}, will use a generated password.", MU::NOTICE
|
|
2165
|
-
server['windows_auth_vault'].delete(field)
|
|
2166
|
-
end
|
|
2167
|
-
}
|
|
2168
|
-
end
|
|
2169
|
-
# Check all of the non-special ones while we're at it
|
|
2170
|
-
server['vault_access'].each { |v|
|
|
2171
|
-
next if v['vault'] == "splunk" and v['item'] == "admin_user"
|
|
2172
|
-
item = groomclass.getSecret(vault: v['vault'], item: v['item'])
|
|
2173
|
-
}
|
|
2174
|
-
rescue MuError
|
|
2175
|
-
MU.log "Can't load a Chef Vault I was configured to use. Does it exist?", MU::ERR
|
|
2176
|
-
ok = false
|
|
2177
|
-
end
|
|
2178
|
-
return ok
|
|
2179
|
-
end
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
# Given a bare hash describing a resource, insert default values which can
|
|
2183
|
-
# be inherited from its parent or from the root of the BoK.
|
|
2184
|
-
# @param kitten [Hash]: A resource descriptor
|
|
2185
|
-
# @param type [String]: The type of resource this is ("servers" etc)
|
|
2186
|
-
def applyInheritedDefaults(kitten, type)
|
|
2187
|
-
return if !kitten.is_a?(Hash)
|
|
2188
|
-
kitten['cloud'] ||= @config['cloud']
|
|
2189
|
-
kitten['cloud'] ||= MU::Config.defaultCloud
|
|
2190
|
-
|
|
2191
|
-
if !MU::Cloud.supportedClouds.include?(kitten['cloud'])
|
|
2192
|
-
return
|
|
2193
|
-
end
|
|
2194
|
-
|
|
2195
|
-
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(kitten['cloud'])
|
|
2196
|
-
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
2197
|
-
resclass = Object.const_get("MU").const_get("Cloud").const_get(kitten['cloud']).const_get(shortclass)
|
|
2198
|
-
|
|
2199
|
-
schema_fields = ["us_only", "scrub_mu_isms", "credentials", "billing_acct"]
|
|
2200
|
-
if !resclass.isGlobal?
|
|
2201
|
-
kitten['region'] ||= @config['region']
|
|
2202
|
-
kitten['region'] ||= cloudclass.myRegion(kitten['credentials'])
|
|
2203
|
-
schema_fields << "region"
|
|
2204
|
-
end
|
|
2205
|
-
|
|
2206
|
-
kitten['credentials'] ||= @config['credentials']
|
|
2207
|
-
kitten['credentials'] ||= cloudclass.credConfig(name_only: true)
|
|
2208
|
-
|
|
2209
|
-
kitten['us_only'] ||= @config['us_only']
|
|
2210
|
-
kitten['us_only'] ||= false
|
|
2211
|
-
|
|
2212
|
-
kitten['scrub_mu_isms'] ||= @config['scrub_mu_isms']
|
|
2213
|
-
kitten['scrub_mu_isms'] ||= false
|
|
2214
|
-
|
|
2215
|
-
if kitten['cloud'] == "Google"
|
|
2216
|
-
# TODO this should be cloud-generic (handle AWS accounts, Azure subscriptions)
|
|
2217
|
-
if resclass.canLiveIn.include?(:Habitat)
|
|
2218
|
-
kitten["project"] ||= MU::Cloud::Google.defaultProject(kitten['credentials'])
|
|
2219
|
-
schema_fields << "project"
|
|
2220
|
-
end
|
|
2221
|
-
if kitten['region'].nil? and !kitten['#MU_CLOUDCLASS'].nil? and
|
|
2222
|
-
!resclass.isGlobal? and
|
|
2223
|
-
![MU::Cloud::VPC, MU::Cloud::FirewallRule].include?(kitten['#MU_CLOUDCLASS'])
|
|
2224
|
-
if MU::Cloud::Google.myRegion((kitten['credentials'])).nil?
|
|
2225
|
-
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"
|
|
2226
|
-
end
|
|
2227
|
-
kitten['region'] ||= MU::Cloud::Google.myRegion
|
|
2228
|
-
end
|
|
2229
|
-
elsif kitten["cloud"] == "AWS" and !resclass.isGlobal? and !kitten['region']
|
|
2230
|
-
if MU::Cloud::AWS.myRegion.nil?
|
|
2231
|
-
raise ValidationError, "AWS resource declared without a region, but no default AWS region found"
|
|
2232
|
-
end
|
|
2233
|
-
kitten['region'] ||= MU::Cloud::AWS.myRegion
|
|
2234
|
-
end
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
kitten['billing_acct'] ||= @config['billing_acct'] if @config['billing_acct']
|
|
2238
|
-
|
|
2239
|
-
kitten["dependencies"] ||= []
|
|
2240
|
-
|
|
2241
|
-
# Make sure the schema knows about these "new" fields, so that validation
|
|
2242
|
-
# doesn't trip over them.
|
|
2243
|
-
schema_fields.each { |field|
|
|
2244
|
-
if @@schema["properties"][field]
|
|
2245
|
-
MU.log "Adding #{field} to schema for #{type} #{kitten['cloud']}", MU::DEBUG, details: @@schema["properties"][field]
|
|
2246
|
-
@@schema["properties"][type]["items"]["properties"][field] ||= @@schema["properties"][field]
|
|
2247
|
-
end
|
|
2248
|
-
}
|
|
2249
|
-
end
|
|
2250
|
-
|
|
2251
1156
|
def validate(config = @config)
|
|
2252
1157
|
ok = true
|
|
2253
1158
|
|
|
@@ -2277,9 +1182,11 @@ $CONFIGURABLES
|
|
|
2277
1182
|
}
|
|
2278
1183
|
}
|
|
2279
1184
|
|
|
1185
|
+
newrules = []
|
|
2280
1186
|
@kittens["firewall_rules"].each { |acl|
|
|
2281
|
-
|
|
1187
|
+
newrules << resolveIntraStackFirewallRefs(acl)
|
|
2282
1188
|
}
|
|
1189
|
+
@kittens["firewall_rules"] = newrules
|
|
2283
1190
|
|
|
2284
1191
|
# VPCs do complex things in their cloud-layer validation that other
|
|
2285
1192
|
# resources tend to need, like subnet allocation, so hit them early.
|
|
@@ -2321,7 +1228,7 @@ $CONFIGURABLES
|
|
|
2321
1228
|
ruleset = haveLitterMate?("database"+db['name'], "firewall_rules")
|
|
2322
1229
|
if ruleset
|
|
2323
1230
|
["server_pools", "servers"].each { |type|
|
|
2324
|
-
|
|
1231
|
+
_shortclass, cfg_name, cfg_plural, _classname = MU::Cloud.getResourceNames(type)
|
|
2325
1232
|
@kittens[cfg_plural].each { |server|
|
|
2326
1233
|
server["dependencies"].each { |dep|
|
|
2327
1234
|
if dep["type"] == "database" and dep["name"] == db["name"]
|
|
@@ -2331,12 +1238,7 @@ $CONFIGURABLES
|
|
|
2331
1238
|
"port" => db["port"],
|
|
2332
1239
|
"sgs" => [cfg_name+server['name']]
|
|
2333
1240
|
}
|
|
2334
|
-
|
|
2335
|
-
ruleset["dependencies"] << {
|
|
2336
|
-
"name" => cfg_name+server['name'],
|
|
2337
|
-
"type" => "firewall_rule",
|
|
2338
|
-
"no_create_wait" => true
|
|
2339
|
-
}
|
|
1241
|
+
MU::Config.addDependency(ruleset, cfg_name+server['name'], "firewall_rule", no_create_wait: true)
|
|
2340
1242
|
end
|
|
2341
1243
|
}
|
|
2342
1244
|
}
|
|
@@ -2354,7 +1256,7 @@ $CONFIGURABLES
|
|
|
2354
1256
|
types.each { |type|
|
|
2355
1257
|
config[type] = @kittens[type] if @kittens[type].size > 0
|
|
2356
1258
|
}
|
|
2357
|
-
ok = false if !
|
|
1259
|
+
ok = false if !check_dependencies
|
|
2358
1260
|
|
|
2359
1261
|
# TODO enforce uniqueness of resource names
|
|
2360
1262
|
raise ValidationError if !ok
|
|
@@ -2378,510 +1280,13 @@ $CONFIGURABLES
|
|
|
2378
1280
|
# end
|
|
2379
1281
|
end
|
|
2380
1282
|
|
|
2381
|
-
# Emit our mu.yaml schema in a format that YARD can comprehend and turn into
|
|
2382
|
-
# documentation.
|
|
2383
|
-
def self.printMuYamlSchema(muyaml_rb, class_hierarchy, schema, in_array = false, required = false, prefix: nil)
|
|
2384
|
-
return if schema.nil?
|
|
2385
|
-
if schema["subtree"]
|
|
2386
|
-
printme = Array.new
|
|
2387
|
-
# order sub-elements by whether they're required, so we can use YARD's
|
|
2388
|
-
# grouping tags on them
|
|
2389
|
-
have_required = schema["subtree"].keys.any? { |k| schema["subtree"][k]["required"] }
|
|
2390
|
-
prop_list = schema["subtree"].keys.sort { |a, b|
|
|
2391
|
-
if schema["subtree"][a]["required"] and !schema["subtree"][b]["required"]
|
|
2392
|
-
-1
|
|
2393
|
-
elsif !schema["subtree"][a]["required"] and schema["subtree"][b]["required"]
|
|
2394
|
-
1
|
|
2395
|
-
else
|
|
2396
|
-
a <=> b
|
|
2397
|
-
end
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
req = false
|
|
2401
|
-
printme << "# @!group Optional parameters" if !have_required
|
|
2402
|
-
prop_list.each { |name|
|
|
2403
|
-
prop = schema["subtree"][name]
|
|
2404
|
-
if prop["required"]
|
|
2405
|
-
printme << "# @!group Required parameters" if !req
|
|
2406
|
-
req = true
|
|
2407
|
-
else
|
|
2408
|
-
if req
|
|
2409
|
-
printme << "# @!endgroup"
|
|
2410
|
-
printme << "# @!group Optional parameters"
|
|
2411
|
-
end
|
|
2412
|
-
req = false
|
|
2413
|
-
end
|
|
2414
|
-
|
|
2415
|
-
printme << self.printMuYamlSchema(muyaml_rb, class_hierarchy+ [name], prop, false, req)
|
|
2416
|
-
}
|
|
2417
|
-
printme << "# @!endgroup"
|
|
2418
|
-
|
|
2419
|
-
desc = (schema['desc'] || schema['title'])
|
|
2420
|
-
|
|
2421
|
-
tabs = 1
|
|
2422
|
-
class_hierarchy.each { |classname|
|
|
2423
|
-
if classname == class_hierarchy.last and desc
|
|
2424
|
-
muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "# #{desc}\n"
|
|
2425
|
-
end
|
|
2426
|
-
muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "class #{classname}"
|
|
2427
|
-
tabs = tabs + 1
|
|
2428
|
-
}
|
|
2429
|
-
printme.each { |lines|
|
|
2430
|
-
if !lines.nil? and lines.is_a?(String)
|
|
2431
|
-
lines.lines.each { |line|
|
|
2432
|
-
muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + line
|
|
2433
|
-
}
|
|
2434
|
-
end
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
class_hierarchy.each { |classname|
|
|
2438
|
-
tabs = tabs - 1
|
|
2439
|
-
muyaml_rb.puts ["\t"].cycle(tabs).to_a.join('') + "end"
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
# And now that we've dealt with our children, pass our own rendered
|
|
2443
|
-
# commentary back up to our caller.
|
|
2444
|
-
name = class_hierarchy.last
|
|
2445
|
-
if in_array
|
|
2446
|
-
type = "Array<#{class_hierarchy.join("::")}>"
|
|
2447
|
-
else
|
|
2448
|
-
type = class_hierarchy.join("::")
|
|
2449
|
-
end
|
|
2450
|
-
|
|
2451
|
-
docstring = "\n"
|
|
2452
|
-
docstring = docstring + "# **REQUIRED**\n" if required
|
|
2453
|
-
# docstring = docstring + "# **"+schema["prefix"]+"**\n" if schema["prefix"]
|
|
2454
|
-
docstring = docstring + "# #{desc.gsub(/\n/, "\n#")}\n" if desc
|
|
2455
|
-
docstring = docstring + "#\n"
|
|
2456
|
-
docstring = docstring + "# @return [#{type}]\n"
|
|
2457
|
-
docstring = docstring + "# @see #{class_hierarchy.join("::")}\n"
|
|
2458
|
-
docstring = docstring + "attr_accessor :#{name}"
|
|
2459
|
-
return docstring
|
|
2460
|
-
|
|
2461
|
-
else
|
|
2462
|
-
in_array = schema["array"]
|
|
2463
|
-
name = class_hierarchy.last
|
|
2464
|
-
type = if schema['boolean']
|
|
2465
|
-
"Boolean"
|
|
2466
|
-
else
|
|
2467
|
-
"String"
|
|
2468
|
-
end
|
|
2469
|
-
if in_array
|
|
2470
|
-
type = "Array<#{type}>"
|
|
2471
|
-
end
|
|
2472
|
-
docstring = "\n"
|
|
2473
|
-
|
|
2474
|
-
prefixes = []
|
|
2475
|
-
prefixes << "# **REQUIRED**" if schema["required"] and schema['default'].nil?
|
|
2476
|
-
# prefixes << "# **"+schema["prefix"]+"**" if schema["prefix"]
|
|
2477
|
-
prefixes << "# **Default: `#{schema['default']}`**" if !schema['default'].nil?
|
|
2478
|
-
if !schema['pattern'].nil?
|
|
2479
|
-
# XXX unquoted regex chars confuse the hell out of YARD. How do we
|
|
2480
|
-
# quote {}[] etc in YARD-speak?
|
|
2481
|
-
prefixes << "# **Must match pattern `#{schema['pattern'].to_s.gsub(/\n/, "\n#")}`**"
|
|
2482
|
-
end
|
|
2483
|
-
|
|
2484
|
-
desc = (schema['desc'] || schema['title'])
|
|
2485
|
-
if prefixes.size > 0
|
|
2486
|
-
docstring += prefixes.join(",\n")
|
|
2487
|
-
if desc and desc.size > 1
|
|
2488
|
-
docstring += " - "
|
|
2489
|
-
end
|
|
2490
|
-
docstring += "\n"
|
|
2491
|
-
end
|
|
2492
|
-
|
|
2493
|
-
docstring = docstring + "# #{desc.gsub(/\n/, "\n#")}\n" if !desc.nil?
|
|
2494
|
-
docstring = docstring + "#\n"
|
|
2495
|
-
docstring = docstring + "# @return [#{type}]\n"
|
|
2496
|
-
docstring = docstring + "attr_accessor :#{name}"
|
|
2497
|
-
|
|
2498
|
-
return docstring
|
|
2499
|
-
end
|
|
2500
|
-
|
|
2501
|
-
end
|
|
2502
|
-
|
|
2503
|
-
# Emit our Basket of Kittens schema in a format that YARD can comprehend
|
|
2504
|
-
# and turn into documentation.
|
|
2505
|
-
def self.printSchema(kitten_rb, class_hierarchy, schema, in_array = false, required = false, prefix: nil)
|
|
2506
|
-
return if schema.nil?
|
|
2507
|
-
|
|
2508
|
-
if schema["type"] == "object"
|
|
2509
|
-
printme = []
|
|
2510
|
-
|
|
2511
|
-
if !schema["properties"].nil?
|
|
2512
|
-
# order sub-elements by whether they're required, so we can use YARD's
|
|
2513
|
-
# grouping tags on them
|
|
2514
|
-
if !schema["required"].nil? and schema["required"].size > 0
|
|
2515
|
-
prop_list = schema["properties"].keys.sort_by { |name|
|
|
2516
|
-
schema["required"].include?(name) ? 0 : 1
|
|
2517
|
-
}
|
|
2518
|
-
else
|
|
2519
|
-
prop_list = schema["properties"].keys
|
|
2520
|
-
end
|
|
2521
|
-
req = false
|
|
2522
|
-
printme << "# @!group Optional parameters" if schema["required"].nil? or schema["required"].size == 0
|
|
2523
|
-
prop_list.each { |name|
|
|
2524
|
-
prop = schema["properties"][name]
|
|
2525
|
-
|
|
2526
|
-
if class_hierarchy.size == 1
|
|
2527
|
-
|
|
2528
|
-
_shortclass, cfg_name, cfg_plural, _classname = MU::Cloud.getResourceNames(name)
|
|
2529
|
-
if cfg_name
|
|
2530
|
-
example_path = MU.myRoot+"/modules/mu/config/"+cfg_name+".yml"
|
|
2531
|
-
if File.exist?(example_path)
|
|
2532
|
-
example = "#\n# Examples:\n#\n"
|
|
2533
|
-
# XXX these variables are all parameters from the BoKs in
|
|
2534
|
-
# modules/tests. A really clever implementation would read
|
|
2535
|
-
# and parse them to get default values, perhaps, instead of
|
|
2536
|
-
# hard-coding them here.
|
|
2537
|
-
instance_type = "t2.medium"
|
|
2538
|
-
db_size = "db.t2.medium"
|
|
2539
|
-
vpc_name = "some_vpc"
|
|
2540
|
-
logs_name = "some_loggroup"
|
|
2541
|
-
queues_name = "some_queue"
|
|
2542
|
-
server_pools_name = "some_server_pool"
|
|
2543
|
-
["simple", "complex"].each { |complexity|
|
|
2544
|
-
erb = ERB.new(File.read(example_path), nil, "<>")
|
|
2545
|
-
example += "# !!!yaml\n"
|
|
2546
|
-
example += "# ---\n"
|
|
2547
|
-
example += "# appname: #{complexity}\n"
|
|
2548
|
-
example += "# #{cfg_plural}:\n"
|
|
2549
|
-
firstline = true
|
|
2550
|
-
erb.result(binding).split(/\n/).each { |l|
|
|
2551
|
-
l.chomp!
|
|
2552
|
-
l.sub!(/#.*/, "") if !l.match(/#(?:INTERNET|NAT|DENY)/)
|
|
2553
|
-
next if l.empty? or l.match(/^\s+$/)
|
|
2554
|
-
if firstline
|
|
2555
|
-
l = "- "+l
|
|
2556
|
-
firstline = false
|
|
2557
|
-
else
|
|
2558
|
-
l = " "+l
|
|
2559
|
-
end
|
|
2560
|
-
example += "# "+l+" "+"\n"
|
|
2561
|
-
}
|
|
2562
|
-
example += "# \n#\n" if complexity == "simple"
|
|
2563
|
-
}
|
|
2564
|
-
schema["properties"][name]["items"]["description"] ||= ""
|
|
2565
|
-
if !schema["properties"][name]["items"]["description"].empty?
|
|
2566
|
-
schema["properties"][name]["items"]["description"] += "\n"
|
|
2567
|
-
end
|
|
2568
|
-
schema["properties"][name]["items"]["description"] += example
|
|
2569
|
-
end
|
|
2570
|
-
end
|
|
2571
|
-
end
|
|
2572
|
-
|
|
2573
|
-
if !schema["required"].nil? and schema["required"].include?(name)
|
|
2574
|
-
printme << "# @!group Required parameters" if !req
|
|
2575
|
-
req = true
|
|
2576
|
-
else
|
|
2577
|
-
if req
|
|
2578
|
-
printme << "# @!endgroup"
|
|
2579
|
-
printme << "# @!group Optional parameters"
|
|
2580
|
-
end
|
|
2581
|
-
req = false
|
|
2582
|
-
end
|
|
2583
|
-
|
|
2584
|
-
printme << self.printSchema(kitten_rb, class_hierarchy+ [name], prop, false, req, prefix: schema["prefix"])
|
|
2585
|
-
}
|
|
2586
|
-
printme << "# @!endgroup"
|
|
2587
|
-
end
|
|
2588
|
-
|
|
2589
|
-
tabs = 1
|
|
2590
|
-
class_hierarchy.each { |classname|
|
|
2591
|
-
if classname == class_hierarchy.last and !schema['description'].nil?
|
|
2592
|
-
kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + "# #{schema['description']}\n"
|
|
2593
|
-
end
|
|
2594
|
-
kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + "class #{classname}"
|
|
2595
|
-
tabs = tabs + 1
|
|
2596
|
-
}
|
|
2597
|
-
printme.each { |lines|
|
|
2598
|
-
if !lines.nil? and lines.is_a?(String)
|
|
2599
|
-
lines.lines.each { |line|
|
|
2600
|
-
kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + line
|
|
2601
|
-
}
|
|
2602
|
-
end
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
class_hierarchy.each { |classname|
|
|
2606
|
-
tabs = tabs - 1
|
|
2607
|
-
kitten_rb.puts ["\t"].cycle(tabs).to_a.join('') + "end"
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
# And now that we've dealt with our children, pass our own rendered
|
|
2611
|
-
# commentary back up to our caller.
|
|
2612
|
-
name = class_hierarchy.last
|
|
2613
|
-
if in_array
|
|
2614
|
-
type = "Array<#{class_hierarchy.join("::")}>"
|
|
2615
|
-
else
|
|
2616
|
-
type = class_hierarchy.join("::")
|
|
2617
|
-
end
|
|
2618
|
-
|
|
2619
|
-
docstring = "\n"
|
|
2620
|
-
docstring = docstring + "# **REQUIRED**\n" if required
|
|
2621
|
-
docstring = docstring + "# **"+schema["prefix"]+"**\n" if schema["prefix"]
|
|
2622
|
-
docstring = docstring + "# #{schema['description'].gsub(/\n/, "\n#")}\n" if !schema['description'].nil?
|
|
2623
|
-
docstring = docstring + "#\n"
|
|
2624
|
-
docstring = docstring + "# @return [#{type}]\n"
|
|
2625
|
-
docstring = docstring + "# @see #{class_hierarchy.join("::")}\n"
|
|
2626
|
-
docstring = docstring + "attr_accessor :#{name}"
|
|
2627
|
-
return docstring
|
|
2628
|
-
|
|
2629
|
-
elsif schema["type"] == "array"
|
|
2630
|
-
return self.printSchema(kitten_rb, class_hierarchy, schema['items'], true, required, prefix: prefix)
|
|
2631
|
-
else
|
|
2632
|
-
name = class_hierarchy.last
|
|
2633
|
-
if schema['type'].nil?
|
|
2634
|
-
MU.log "Couldn't determine schema type in #{class_hierarchy.join(" => ")}", MU::WARN, details: schema
|
|
2635
|
-
return nil
|
|
2636
|
-
end
|
|
2637
|
-
if in_array
|
|
2638
|
-
type = "Array<#{schema['type'].capitalize}>"
|
|
2639
|
-
else
|
|
2640
|
-
type = schema['type'].capitalize
|
|
2641
|
-
end
|
|
2642
|
-
docstring = "\n"
|
|
2643
|
-
|
|
2644
|
-
prefixes = []
|
|
2645
|
-
prefixes << "# **REQUIRED**" if required and schema['default'].nil?
|
|
2646
|
-
prefixes << "# **"+schema["prefix"]+"**" if schema["prefix"]
|
|
2647
|
-
prefixes << "# **Default: `#{schema['default']}`**" if !schema['default'].nil?
|
|
2648
|
-
if !schema['enum'].nil? and !schema["enum"].empty?
|
|
2649
|
-
prefixes << "# **Must be one of: `#{schema['enum'].join(', ')}`**"
|
|
2650
|
-
elsif !schema['pattern'].nil?
|
|
2651
|
-
# XXX unquoted regex chars confuse the hell out of YARD. How do we
|
|
2652
|
-
# quote {}[] etc in YARD-speak?
|
|
2653
|
-
prefixes << "# **Must match pattern `#{schema['pattern'].gsub(/\n/, "\n#")}`**"
|
|
2654
|
-
end
|
|
2655
|
-
|
|
2656
|
-
if prefixes.size > 0
|
|
2657
|
-
docstring += prefixes.join(",\n")
|
|
2658
|
-
if schema['description'] and schema['description'].size > 1
|
|
2659
|
-
docstring += " - "
|
|
2660
|
-
end
|
|
2661
|
-
docstring += "\n"
|
|
2662
|
-
end
|
|
2663
|
-
|
|
2664
|
-
docstring = docstring + "# #{schema['description'].gsub(/\n/, "\n#")}\n" if !schema['description'].nil?
|
|
2665
|
-
docstring = docstring + "#\n"
|
|
2666
|
-
docstring = docstring + "# @return [#{type}]\n"
|
|
2667
|
-
docstring = docstring + "attr_accessor :#{name}"
|
|
2668
|
-
|
|
2669
|
-
return docstring
|
|
2670
|
-
end
|
|
2671
|
-
|
|
2672
|
-
end
|
|
2673
|
-
|
|
2674
|
-
def self.dependencies_primitive
|
|
2675
|
-
{
|
|
2676
|
-
"type" => "array",
|
|
2677
|
-
"items" => {
|
|
2678
|
-
"type" => "object",
|
|
2679
|
-
"description" => "Declare other objects which this resource requires. This resource will wait until the others are available to create itself.",
|
|
2680
|
-
"required" => ["name", "type"],
|
|
2681
|
-
"additionalProperties" => false,
|
|
2682
|
-
"properties" => {
|
|
2683
|
-
"name" => {"type" => "string"},
|
|
2684
|
-
"type" => {
|
|
2685
|
-
"type" => "string",
|
|
2686
|
-
"enum" => MU::Cloud.resource_types.values.map { |v| v[:cfg_name] }
|
|
2687
|
-
},
|
|
2688
|
-
"phase" => {
|
|
2689
|
-
"type" => "string",
|
|
2690
|
-
"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.",
|
|
2691
|
-
"enum" => ["create", "groom"]
|
|
2692
|
-
},
|
|
2693
|
-
"no_create_wait" => {
|
|
2694
|
-
"type" => "boolean",
|
|
2695
|
-
"default" => false,
|
|
2696
|
-
"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. "
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
end
|
|
2702
|
-
|
|
2703
|
-
CIDR_PATTERN = "^\\d+\\.\\d+\\.\\d+\\.\\d+\/[0-9]{1,2}$"
|
|
2704
|
-
CIDR_DESCRIPTION = "CIDR-formatted IP block, e.g. 1.2.3.4/32"
|
|
2705
|
-
CIDR_PRIMITIVE = {
|
|
2706
|
-
"type" => "string",
|
|
2707
|
-
"pattern" => CIDR_PATTERN,
|
|
2708
|
-
"description" => CIDR_DESCRIPTION
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
|
-
# Have a default value available for config schema elements that take an
|
|
2712
|
-
# email address.
|
|
2713
|
-
# @return [String]
|
|
2714
|
-
def self.notification_email
|
|
2715
|
-
if MU.chef_user == "mu"
|
|
2716
|
-
ENV['MU_ADMIN_EMAIL']
|
|
2717
|
-
else
|
|
2718
|
-
MU.userEmail
|
|
2719
|
-
end
|
|
2720
|
-
end
|
|
2721
|
-
|
|
2722
|
-
# Load and validate the schema for an individual resource class, optionally
|
|
2723
|
-
# merging cloud-specific schema components.
|
|
2724
|
-
# @param type [String]: The resource type to load
|
|
2725
|
-
# @param cloud [String]: A specific cloud, whose implementation's schema of this resource we will merge
|
|
2726
|
-
# @return [Hash]
|
|
2727
|
-
def self.loadResourceSchema(type, cloud: nil)
|
|
2728
|
-
valid = true
|
|
2729
|
-
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
2730
|
-
schemaclass = Object.const_get("MU").const_get("Config").const_get(shortclass)
|
|
2731
|
-
|
|
2732
|
-
[:schema, :validate].each { |method|
|
|
2733
|
-
if !schemaclass.respond_to?(method)
|
|
2734
|
-
MU.log "MU::Config::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
|
|
2735
|
-
return [nil, false] if method == :schema
|
|
2736
|
-
valid = false
|
|
2737
|
-
end
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
schema = schemaclass.schema.dup
|
|
2741
|
-
|
|
2742
|
-
schema["properties"]["virtual_name"] = {
|
|
2743
|
-
"description" => "Internal use.",
|
|
2744
|
-
"type" => "string"
|
|
2745
|
-
}
|
|
2746
|
-
schema["properties"]["dependencies"] = MU::Config.dependencies_primitive
|
|
2747
|
-
schema["properties"]["cloud"] = MU::Config.cloud_primitive
|
|
2748
|
-
schema["properties"]["credentials"] = MU::Config.credentials_primitive
|
|
2749
|
-
schema["title"] = type.to_s
|
|
2750
|
-
|
|
2751
|
-
if cloud
|
|
2752
|
-
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
|
|
2753
|
-
|
|
2754
|
-
if cloudclass.respond_to?(:schema)
|
|
2755
|
-
reqd, cloudschema = cloudclass.schema
|
|
2756
|
-
cloudschema.each { |key, cfg|
|
|
2757
|
-
if schema["properties"][key]
|
|
2758
|
-
schemaMerge(schema["properties"][key], cfg, cloud)
|
|
2759
|
-
else
|
|
2760
|
-
schema["properties"][key] = cfg.dup
|
|
2761
|
-
end
|
|
2762
|
-
}
|
|
2763
|
-
else
|
|
2764
|
-
MU.log "MU::Cloud::#{cloud}::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
|
|
2765
|
-
valid = false
|
|
2766
|
-
end
|
|
2767
|
-
|
|
2768
|
-
end
|
|
2769
|
-
|
|
2770
|
-
return [schema, valid]
|
|
2771
|
-
end
|
|
2772
|
-
|
|
2773
|
-
@@schema = {
|
|
2774
|
-
"$schema" => "http://json-schema.org/draft-04/schema#",
|
|
2775
|
-
"title" => "MU Application",
|
|
2776
|
-
"type" => "object",
|
|
2777
|
-
"description" => "A MU application stack, consisting of at least one resource.",
|
|
2778
|
-
"required" => ["admins", "appname"],
|
|
2779
|
-
"properties" => {
|
|
2780
|
-
"appname" => {
|
|
2781
|
-
"type" => "string",
|
|
2782
|
-
"description" => "A name for your application stack. Should be short, but easy to differentiate from other applications.",
|
|
2783
|
-
},
|
|
2784
|
-
"scrub_mu_isms" => {
|
|
2785
|
-
"type" => "boolean",
|
|
2786
|
-
"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."
|
|
2787
|
-
},
|
|
2788
|
-
"project" => {
|
|
2789
|
-
"type" => "string",
|
|
2790
|
-
"description" => "**GOOGLE ONLY**: The project into which to deploy resources"
|
|
2791
|
-
},
|
|
2792
|
-
"billing_acct" => {
|
|
2793
|
-
"type" => "string",
|
|
2794
|
-
"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.",
|
|
2795
|
-
},
|
|
2796
|
-
"region" => MU::Config.region_primitive,
|
|
2797
|
-
"credentials" => MU::Config.credentials_primitive,
|
|
2798
|
-
"us_only" => {
|
|
2799
|
-
"type" => "boolean",
|
|
2800
|
-
"description" => "For resources which span regions, restrict to regions inside the United States",
|
|
2801
|
-
"default" => false
|
|
2802
|
-
},
|
|
2803
|
-
"conditions" => {
|
|
2804
|
-
"type" => "array",
|
|
2805
|
-
"items" => {
|
|
2806
|
-
"type" => "object",
|
|
2807
|
-
"required" => ["name", "cloudcode"],
|
|
2808
|
-
"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.",
|
|
2809
|
-
"properties" => {
|
|
2810
|
-
"name" => { "required" => true, "type" => "string" },
|
|
2811
|
-
"cloudcode" => { "required" => true, "type" => "string" },
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
},
|
|
2815
|
-
"parameters" => {
|
|
2816
|
-
"type" => "array",
|
|
2817
|
-
"items" => {
|
|
2818
|
-
"type" => "object",
|
|
2819
|
-
"title" => "parameter",
|
|
2820
|
-
"description" => "Parameters to be substituted elsewhere in this Basket of Kittens as ERB variables (<%= varname %>)",
|
|
2821
|
-
"additionalProperties" => false,
|
|
2822
|
-
"properties" => {
|
|
2823
|
-
"name" => { "required" => true, "type" => "string" },
|
|
2824
|
-
"default" => { "type" => "string" },
|
|
2825
|
-
"list_of" => {
|
|
2826
|
-
"type" => "string",
|
|
2827
|
-
"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."
|
|
2828
|
-
},
|
|
2829
|
-
"prettyname" => {
|
|
2830
|
-
"type" => "string",
|
|
2831
|
-
"description" => "An alternative name to use when generating parameter fields in, for example, CloudFormation templates"
|
|
2832
|
-
},
|
|
2833
|
-
"description" => {"type" => "string"},
|
|
2834
|
-
"cloudtype" => {
|
|
2835
|
-
"type" => "string",
|
|
2836
|
-
"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."
|
|
2837
|
-
},
|
|
2838
|
-
"required" => {
|
|
2839
|
-
"type" => "boolean",
|
|
2840
|
-
"default" => true
|
|
2841
|
-
},
|
|
2842
|
-
"valid_values" => {
|
|
2843
|
-
"type" => "array",
|
|
2844
|
-
"description" => "List of valid values for this parameter. Can only be a list of static strings, for now.",
|
|
2845
|
-
"items" => {
|
|
2846
|
-
"type" => "string"
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
},
|
|
2852
|
-
# TODO availability zones (or an array thereof)
|
|
2853
|
-
|
|
2854
|
-
"admins" => {
|
|
2855
|
-
"type" => "array",
|
|
2856
|
-
"items" => {
|
|
2857
|
-
"type" => "object",
|
|
2858
|
-
"title" => "admin",
|
|
2859
|
-
"description" => "Administrative contacts for this application stack. Will be automatically set to invoking Mu user, if not specified.",
|
|
2860
|
-
"required" => ["name", "email"],
|
|
2861
|
-
"additionalProperties" => false,
|
|
2862
|
-
"properties" => {
|
|
2863
|
-
"name" => {"type" => "string"},
|
|
2864
|
-
"email" => {"type" => "string"},
|
|
2865
|
-
"public_key" => {
|
|
2866
|
-
"type" => "string",
|
|
2867
|
-
"description" => "An OpenSSH-style public key string. This will be installed on all instances created in this deployment."
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
},
|
|
2871
|
-
"minItems" => 1,
|
|
2872
|
-
"uniqueItems" => true
|
|
2873
|
-
}
|
|
2874
|
-
},
|
|
2875
|
-
"additionalProperties" => false
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
1283
|
failed = []
|
|
2879
1284
|
|
|
2880
1285
|
# Load all of the config stub files at the Ruby level
|
|
2881
1286
|
MU::Cloud.resource_types.each_pair { |type, cfg|
|
|
2882
1287
|
begin
|
|
2883
1288
|
require "mu/config/#{cfg[:cfg_name]}"
|
|
2884
|
-
rescue LoadError
|
|
1289
|
+
rescue LoadError
|
|
2885
1290
|
# raise MuError, "MU::Config implemention of #{type} missing from modules/mu/config/#{cfg[:cfg_name]}.rb"
|
|
2886
1291
|
MU.log "MU::Config::#{type} stub class is missing", MU::ERR
|
|
2887
1292
|
failed << type
|
|
@@ -2889,7 +1294,6 @@ $CONFIGURABLES
|
|
|
2889
1294
|
end
|
|
2890
1295
|
}
|
|
2891
1296
|
|
|
2892
|
-
|
|
2893
1297
|
MU::Cloud.resource_types.each_pair { |type, cfg|
|
|
2894
1298
|
begin
|
|
2895
1299
|
schema, valid = loadResourceSchema(type)
|