cloud-mu 2.1.0beta → 3.0.0beta
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 +5 -5
- data/Berksfile +4 -5
- data/Berksfile.lock +179 -0
- data/README.md +1 -6
- data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +0 -0
- data/ansible/roles/mu-installer/README.md +33 -0
- data/ansible/roles/mu-installer/defaults/main.yml +2 -0
- data/ansible/roles/mu-installer/handlers/main.yml +2 -0
- data/ansible/roles/mu-installer/meta/main.yml +60 -0
- data/ansible/roles/mu-installer/tasks/main.yml +13 -0
- data/ansible/roles/mu-installer/tests/inventory +2 -0
- data/ansible/roles/mu-installer/tests/test.yml +5 -0
- data/ansible/roles/mu-installer/vars/main.yml +2 -0
- data/bin/mu-adopt +125 -0
- data/bin/mu-aws-setup +4 -4
- data/bin/mu-azure-setup +265 -0
- data/bin/mu-azure-tests +43 -0
- data/bin/mu-cleanup +20 -8
- data/bin/mu-configure +224 -98
- data/bin/mu-deploy +8 -3
- data/bin/mu-gcp-setup +16 -8
- data/bin/mu-gen-docs +92 -8
- data/bin/mu-load-config.rb +52 -12
- data/bin/mu-momma-cat +36 -0
- data/bin/mu-node-manage +34 -27
- data/bin/mu-self-update +2 -2
- data/bin/mu-ssh +12 -8
- data/bin/mu-upload-chef-artifacts +11 -4
- data/bin/mu-user-manage +3 -0
- data/cloud-mu.gemspec +8 -11
- data/cookbooks/firewall/libraries/helpers_iptables.rb +2 -2
- data/cookbooks/firewall/metadata.json +1 -1
- data/cookbooks/firewall/recipes/default.rb +5 -9
- data/cookbooks/mu-firewall/attributes/default.rb +2 -0
- data/cookbooks/mu-firewall/metadata.rb +1 -1
- data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +0 -0
- data/cookbooks/mu-master/Berksfile +2 -2
- data/cookbooks/mu-master/files/default/check_mem.pl +0 -0
- data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
- data/cookbooks/mu-master/metadata.rb +5 -4
- data/cookbooks/mu-master/recipes/389ds.rb +1 -1
- data/cookbooks/mu-master/recipes/basepackages.rb +30 -10
- data/cookbooks/mu-master/recipes/default.rb +59 -7
- data/cookbooks/mu-master/recipes/firewall-holes.rb +1 -1
- data/cookbooks/mu-master/recipes/init.rb +65 -47
- data/cookbooks/mu-master/recipes/{eks-kubectl.rb → kubectl.rb} +4 -10
- data/cookbooks/mu-master/recipes/sssd.rb +2 -1
- data/cookbooks/mu-master/recipes/update_nagios_only.rb +6 -6
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +2 -2
- data/cookbooks/mu-master/templates/mods/ldap.conf.erb +4 -0
- data/cookbooks/mu-php54/Berksfile +1 -2
- data/cookbooks/mu-php54/metadata.rb +4 -5
- data/cookbooks/mu-php54/recipes/default.rb +1 -1
- data/cookbooks/mu-splunk/templates/default/splunk-init.erb +0 -0
- data/cookbooks/mu-tools/Berksfile +3 -2
- data/cookbooks/mu-tools/files/default/Mu_CA.pem +33 -0
- data/cookbooks/mu-tools/libraries/helper.rb +20 -8
- data/cookbooks/mu-tools/metadata.rb +5 -2
- data/cookbooks/mu-tools/recipes/apply_security.rb +2 -3
- data/cookbooks/mu-tools/recipes/eks.rb +1 -1
- data/cookbooks/mu-tools/recipes/gcloud.rb +5 -30
- data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
- data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -0
- data/cookbooks/mu-tools/recipes/selinux.rb +19 -0
- data/cookbooks/mu-tools/recipes/split_var_partitions.rb +0 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +256 -122
- data/cookbooks/mu-tools/resources/disk.rb +3 -1
- data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +1 -1
- data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
- data/cookbooks/mu-tools/templates/default/{kubeconfig.erb → kubeconfig-eks.erb} +0 -0
- data/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb +27 -0
- data/cookbooks/mu-tools/templates/windows-10/sshd_config.erb +137 -0
- data/cookbooks/mu-utility/recipes/nat.rb +4 -0
- data/extras/alpha.png +0 -0
- data/extras/beta.png +0 -0
- data/extras/clean-stock-amis +2 -2
- data/extras/generate-stock-images +131 -0
- data/extras/git-fix-permissions-hook +0 -0
- data/extras/image-generators/AWS/centos6.yaml +17 -0
- data/extras/image-generators/{aws → AWS}/centos7-govcloud.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/centos7.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/rhel7.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/win2k12.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/win2k16.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/windows.yaml +0 -0
- data/extras/image-generators/{gcp → Google}/centos6.yaml +1 -0
- data/extras/image-generators/Google/centos7.yaml +18 -0
- data/extras/python_rpm/build.sh +0 -0
- data/extras/release.png +0 -0
- data/extras/ruby_rpm/build.sh +0 -0
- data/extras/ruby_rpm/muby.spec +1 -1
- data/install/README.md +43 -5
- data/install/deprecated-bash-library.sh +0 -0
- data/install/installer +1 -1
- data/install/jenkinskeys.rb +0 -0
- data/install/mu-master.yaml +55 -0
- data/modules/mommacat.ru +41 -7
- data/modules/mu.rb +444 -149
- data/modules/mu/adoption.rb +500 -0
- data/modules/mu/cleanup.rb +235 -158
- data/modules/mu/cloud.rb +675 -138
- data/modules/mu/clouds/aws.rb +156 -24
- data/modules/mu/clouds/aws/alarm.rb +4 -14
- data/modules/mu/clouds/aws/bucket.rb +60 -18
- data/modules/mu/clouds/aws/cache_cluster.rb +8 -20
- data/modules/mu/clouds/aws/collection.rb +12 -22
- data/modules/mu/clouds/aws/container_cluster.rb +209 -118
- data/modules/mu/clouds/aws/database.rb +120 -45
- data/modules/mu/clouds/aws/dnszone.rb +7 -18
- data/modules/mu/clouds/aws/endpoint.rb +5 -15
- data/modules/mu/clouds/aws/firewall_rule.rb +144 -72
- data/modules/mu/clouds/aws/folder.rb +4 -11
- data/modules/mu/clouds/aws/function.rb +6 -16
- data/modules/mu/clouds/aws/group.rb +4 -12
- data/modules/mu/clouds/aws/habitat.rb +11 -13
- data/modules/mu/clouds/aws/loadbalancer.rb +40 -28
- data/modules/mu/clouds/aws/log.rb +5 -13
- data/modules/mu/clouds/aws/msg_queue.rb +9 -24
- data/modules/mu/clouds/aws/nosqldb.rb +4 -12
- data/modules/mu/clouds/aws/notifier.rb +6 -13
- data/modules/mu/clouds/aws/role.rb +69 -40
- data/modules/mu/clouds/aws/search_domain.rb +17 -20
- data/modules/mu/clouds/aws/server.rb +184 -94
- data/modules/mu/clouds/aws/server_pool.rb +33 -38
- data/modules/mu/clouds/aws/storage_pool.rb +5 -12
- data/modules/mu/clouds/aws/user.rb +59 -33
- data/modules/mu/clouds/aws/userdata/linux.erb +18 -30
- data/modules/mu/clouds/aws/userdata/windows.erb +9 -9
- data/modules/mu/clouds/aws/vpc.rb +214 -145
- data/modules/mu/clouds/azure.rb +978 -44
- data/modules/mu/clouds/azure/container_cluster.rb +413 -0
- data/modules/mu/clouds/azure/firewall_rule.rb +500 -0
- data/modules/mu/clouds/azure/habitat.rb +167 -0
- data/modules/mu/clouds/azure/loadbalancer.rb +205 -0
- data/modules/mu/clouds/azure/role.rb +211 -0
- data/modules/mu/clouds/azure/server.rb +810 -0
- data/modules/mu/clouds/azure/user.rb +257 -0
- data/modules/mu/clouds/azure/userdata/README.md +4 -0
- data/modules/mu/clouds/azure/userdata/linux.erb +137 -0
- data/modules/mu/clouds/azure/userdata/windows.erb +275 -0
- data/modules/mu/clouds/azure/vpc.rb +782 -0
- data/modules/mu/clouds/cloudformation.rb +12 -9
- data/modules/mu/clouds/cloudformation/firewall_rule.rb +5 -13
- data/modules/mu/clouds/cloudformation/server.rb +10 -1
- data/modules/mu/clouds/cloudformation/server_pool.rb +1 -0
- data/modules/mu/clouds/cloudformation/vpc.rb +0 -2
- data/modules/mu/clouds/google.rb +554 -117
- data/modules/mu/clouds/google/bucket.rb +173 -32
- data/modules/mu/clouds/google/container_cluster.rb +1112 -157
- data/modules/mu/clouds/google/database.rb +24 -47
- data/modules/mu/clouds/google/firewall_rule.rb +344 -89
- data/modules/mu/clouds/google/folder.rb +156 -79
- data/modules/mu/clouds/google/group.rb +272 -82
- data/modules/mu/clouds/google/habitat.rb +177 -52
- data/modules/mu/clouds/google/loadbalancer.rb +9 -34
- data/modules/mu/clouds/google/role.rb +1211 -0
- data/modules/mu/clouds/google/server.rb +491 -227
- data/modules/mu/clouds/google/server_pool.rb +233 -48
- data/modules/mu/clouds/google/user.rb +479 -125
- data/modules/mu/clouds/google/userdata/linux.erb +3 -3
- data/modules/mu/clouds/google/userdata/windows.erb +9 -9
- data/modules/mu/clouds/google/vpc.rb +381 -223
- data/modules/mu/config.rb +689 -214
- data/modules/mu/config/bucket.rb +1 -1
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/cache_cluster.yml +0 -4
- data/modules/mu/config/container_cluster.rb +18 -9
- data/modules/mu/config/database.rb +6 -23
- data/modules/mu/config/firewall_rule.rb +9 -15
- data/modules/mu/config/folder.rb +22 -21
- data/modules/mu/config/habitat.rb +22 -21
- data/modules/mu/config/loadbalancer.rb +2 -2
- data/modules/mu/config/role.rb +9 -40
- data/modules/mu/config/server.rb +26 -5
- data/modules/mu/config/server_pool.rb +1 -1
- data/modules/mu/config/storage_pool.rb +2 -2
- data/modules/mu/config/user.rb +4 -0
- data/modules/mu/config/vpc.rb +350 -110
- data/modules/mu/defaults/{amazon_images.yaml → AWS.yaml} +37 -39
- data/modules/mu/defaults/Azure.yaml +17 -0
- data/modules/mu/defaults/Google.yaml +24 -0
- data/modules/mu/defaults/README.md +1 -1
- data/modules/mu/deploy.rb +168 -125
- data/modules/mu/groomer.rb +2 -1
- data/modules/mu/groomers/ansible.rb +104 -32
- data/modules/mu/groomers/chef.rb +96 -44
- data/modules/mu/kittens.rb +20602 -0
- data/modules/mu/logger.rb +38 -11
- data/modules/mu/master.rb +90 -8
- data/modules/mu/master/chef.rb +2 -3
- data/modules/mu/master/ldap.rb +0 -1
- data/modules/mu/master/ssl.rb +250 -0
- data/modules/mu/mommacat.rb +917 -513
- data/modules/scratchpad.erb +1 -1
- data/modules/tests/super_complex_bok.yml +0 -0
- data/modules/tests/super_simple_bok.yml +0 -0
- data/roles/mu-master.json +2 -1
- data/spec/azure_creds +5 -0
- data/spec/mu.yaml +56 -0
- data/spec/mu/clouds/azure_spec.rb +164 -27
- data/spec/spec_helper.rb +5 -0
- data/test/clean_up.py +0 -0
- data/test/exec_inspec.py +0 -0
- data/test/exec_mu_install.py +0 -0
- data/test/exec_retry.py +0 -0
- data/test/smoke_test.rb +0 -0
- metadata +90 -118
- data/cookbooks/mu-jenkins/Berksfile +0 -14
- data/cookbooks/mu-jenkins/CHANGELOG.md +0 -13
- data/cookbooks/mu-jenkins/LICENSE +0 -37
- data/cookbooks/mu-jenkins/README.md +0 -105
- data/cookbooks/mu-jenkins/attributes/default.rb +0 -42
- data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +0 -73
- data/cookbooks/mu-jenkins/files/default/deploy_config.xml +0 -44
- data/cookbooks/mu-jenkins/metadata.rb +0 -21
- data/cookbooks/mu-jenkins/recipes/default.rb +0 -195
- data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +0 -54
- data/cookbooks/mu-jenkins/recipes/public_key.rb +0 -24
- data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +0 -24
- data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +0 -14
- data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +0 -6
- data/cookbooks/nagios/Berksfile +0 -11
- data/cookbooks/nagios/CHANGELOG.md +0 -589
- data/cookbooks/nagios/CONTRIBUTING.md +0 -11
- data/cookbooks/nagios/LICENSE +0 -37
- data/cookbooks/nagios/README.md +0 -328
- data/cookbooks/nagios/TESTING.md +0 -2
- data/cookbooks/nagios/attributes/config.rb +0 -171
- data/cookbooks/nagios/attributes/default.rb +0 -228
- data/cookbooks/nagios/chefignore +0 -102
- data/cookbooks/nagios/definitions/command.rb +0 -33
- data/cookbooks/nagios/definitions/contact.rb +0 -33
- data/cookbooks/nagios/definitions/contactgroup.rb +0 -33
- data/cookbooks/nagios/definitions/host.rb +0 -33
- data/cookbooks/nagios/definitions/hostdependency.rb +0 -33
- data/cookbooks/nagios/definitions/hostescalation.rb +0 -34
- data/cookbooks/nagios/definitions/hostgroup.rb +0 -33
- data/cookbooks/nagios/definitions/nagios_conf.rb +0 -38
- data/cookbooks/nagios/definitions/resource.rb +0 -33
- data/cookbooks/nagios/definitions/service.rb +0 -33
- data/cookbooks/nagios/definitions/servicedependency.rb +0 -33
- data/cookbooks/nagios/definitions/serviceescalation.rb +0 -34
- data/cookbooks/nagios/definitions/servicegroup.rb +0 -33
- data/cookbooks/nagios/definitions/timeperiod.rb +0 -33
- data/cookbooks/nagios/libraries/base.rb +0 -314
- data/cookbooks/nagios/libraries/command.rb +0 -91
- data/cookbooks/nagios/libraries/contact.rb +0 -230
- data/cookbooks/nagios/libraries/contactgroup.rb +0 -112
- data/cookbooks/nagios/libraries/custom_option.rb +0 -36
- data/cookbooks/nagios/libraries/data_bag_helper.rb +0 -23
- data/cookbooks/nagios/libraries/default.rb +0 -90
- data/cookbooks/nagios/libraries/host.rb +0 -412
- data/cookbooks/nagios/libraries/hostdependency.rb +0 -181
- data/cookbooks/nagios/libraries/hostescalation.rb +0 -173
- data/cookbooks/nagios/libraries/hostgroup.rb +0 -119
- data/cookbooks/nagios/libraries/nagios.rb +0 -282
- data/cookbooks/nagios/libraries/resource.rb +0 -59
- data/cookbooks/nagios/libraries/service.rb +0 -455
- data/cookbooks/nagios/libraries/servicedependency.rb +0 -215
- data/cookbooks/nagios/libraries/serviceescalation.rb +0 -195
- data/cookbooks/nagios/libraries/servicegroup.rb +0 -144
- data/cookbooks/nagios/libraries/timeperiod.rb +0 -160
- data/cookbooks/nagios/libraries/users_helper.rb +0 -54
- data/cookbooks/nagios/metadata.rb +0 -25
- data/cookbooks/nagios/recipes/_load_databag_config.rb +0 -153
- data/cookbooks/nagios/recipes/_load_default_config.rb +0 -241
- data/cookbooks/nagios/recipes/apache.rb +0 -48
- data/cookbooks/nagios/recipes/default.rb +0 -204
- data/cookbooks/nagios/recipes/nginx.rb +0 -82
- data/cookbooks/nagios/recipes/pagerduty.rb +0 -143
- data/cookbooks/nagios/recipes/server_package.rb +0 -40
- data/cookbooks/nagios/recipes/server_source.rb +0 -164
- data/cookbooks/nagios/templates/default/apache2.conf.erb +0 -96
- data/cookbooks/nagios/templates/default/cgi.cfg.erb +0 -266
- data/cookbooks/nagios/templates/default/commands.cfg.erb +0 -13
- data/cookbooks/nagios/templates/default/contacts.cfg.erb +0 -37
- data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +0 -25
- data/cookbooks/nagios/templates/default/hosts.cfg.erb +0 -15
- data/cookbooks/nagios/templates/default/htpasswd.users.erb +0 -6
- data/cookbooks/nagios/templates/default/nagios.cfg.erb +0 -22
- data/cookbooks/nagios/templates/default/nginx.conf.erb +0 -62
- data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +0 -185
- data/cookbooks/nagios/templates/default/resource.cfg.erb +0 -27
- data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +0 -15
- data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +0 -14
- data/cookbooks/nagios/templates/default/services.cfg.erb +0 -14
- data/cookbooks/nagios/templates/default/templates.cfg.erb +0 -31
- data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +0 -13
- data/extras/image-generators/aws/centos6.yaml +0 -18
- data/modules/mu/defaults/google_images.yaml +0 -16
- data/roles/mu-master-jenkins.json +0 -24
data/modules/mu/config.rb
CHANGED
|
@@ -28,6 +28,9 @@ module MU
|
|
|
28
28
|
# Exception class for BoK parse or validation errors
|
|
29
29
|
class ValidationError < MU::MuError
|
|
30
30
|
end
|
|
31
|
+
# Exception class for duplicate resource names
|
|
32
|
+
class DuplicateNameError < MU::MuError
|
|
33
|
+
end
|
|
31
34
|
# Exception class for deploy parameter (mu-deploy -p foo=bar) errors
|
|
32
35
|
class DeployParamError < MuError
|
|
33
36
|
end
|
|
@@ -41,8 +44,6 @@ module MU
|
|
|
41
44
|
if $MU_CFG[cloud.downcase] and !$MU_CFG[cloud.downcase].empty?
|
|
42
45
|
configured[cloud] = $MU_CFG[cloud.downcase].size
|
|
43
46
|
configured[cloud] += 0.5 if cloudclass.hosted? # tiebreaker
|
|
44
|
-
elsif cloudclass.hosted?
|
|
45
|
-
configured[cloud] = 1
|
|
46
47
|
end
|
|
47
48
|
}
|
|
48
49
|
if configured.size > 0
|
|
@@ -50,61 +51,22 @@ module MU
|
|
|
50
51
|
configured[b] <=> configured[a]
|
|
51
52
|
}.first
|
|
52
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
|
+
}
|
|
53
58
|
return MU::Cloud.supportedClouds.first
|
|
54
59
|
end
|
|
55
60
|
end
|
|
56
61
|
|
|
57
62
|
# The default grooming agent for new resources. Must exist in MU.supportedGroomers.
|
|
58
63
|
def self.defaultGroomer
|
|
59
|
-
"Chef"
|
|
64
|
+
MU.localOnly ? "Ansible" : "Chef"
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
attr_accessor :nat_routes
|
|
63
68
|
attr_reader :skipinitialupdates
|
|
64
69
|
|
|
65
|
-
attr_reader :google_images
|
|
66
|
-
@@google_images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/google_images.yaml"))
|
|
67
|
-
if File.exists?("#{MU.etcDir}/google_images.yaml")
|
|
68
|
-
custom = YAML.load(File.read("#{MU.etcDir}/google_images.yaml"))
|
|
69
|
-
@@google_images.merge!(custom) { |key, oldval, newval|
|
|
70
|
-
if !oldval.is_a?(Hash) and !newval.nil?
|
|
71
|
-
if !newval.nil?
|
|
72
|
-
newval
|
|
73
|
-
else
|
|
74
|
-
oldval
|
|
75
|
-
end
|
|
76
|
-
else
|
|
77
|
-
oldval.merge(newval)
|
|
78
|
-
end
|
|
79
|
-
}
|
|
80
|
-
end
|
|
81
|
-
# The list of known Google Images which we can use for a given platform
|
|
82
|
-
def self.google_images
|
|
83
|
-
@@google_images
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
attr_reader :amazon_images
|
|
87
|
-
@@amazon_images = YAML.load(File.read("#{MU.myRoot}/modules/mu/defaults/amazon_images.yaml"))
|
|
88
|
-
if File.exists?("#{MU.etcDir}/amazon_images.yaml")
|
|
89
|
-
custom = YAML.load(File.read("#{MU.etcDir}/amazon_images.yaml"))
|
|
90
|
-
@@amazon_images.merge!(custom) { |key, oldval, newval|
|
|
91
|
-
if !oldval.is_a?(Hash) and !newval.nil?
|
|
92
|
-
if !newval.nil?
|
|
93
|
-
newval
|
|
94
|
-
else
|
|
95
|
-
oldval
|
|
96
|
-
end
|
|
97
|
-
else
|
|
98
|
-
oldval.merge(newval)
|
|
99
|
-
end
|
|
100
|
-
}
|
|
101
|
-
end
|
|
102
|
-
# The list of known Amazon AMIs, by region, which we can use for a given
|
|
103
|
-
# platform.
|
|
104
|
-
def self.amazon_images
|
|
105
|
-
@@amazon_images
|
|
106
|
-
end
|
|
107
|
-
|
|
108
70
|
@@config_path = nil
|
|
109
71
|
# The path to the most recently loaded configuration file
|
|
110
72
|
attr_reader :config_path
|
|
@@ -123,20 +85,24 @@ module MU
|
|
|
123
85
|
def self.schemaMerge(orig, new, cloud)
|
|
124
86
|
if new.is_a?(Hash)
|
|
125
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
|
|
126
91
|
if orig and orig.has_key?(k)
|
|
127
|
-
schemaMerge(orig[k], new[k], cloud)
|
|
128
92
|
elsif orig
|
|
129
93
|
orig[k] = new[k]
|
|
130
94
|
else
|
|
131
95
|
orig = new
|
|
132
96
|
end
|
|
97
|
+
schemaMerge(orig[k], new[k], cloud)
|
|
133
98
|
}
|
|
134
99
|
elsif orig.is_a?(Array) and new
|
|
135
100
|
orig.concat(new)
|
|
136
101
|
orig.uniq!
|
|
137
102
|
elsif new.is_a?(String)
|
|
138
103
|
orig ||= ""
|
|
139
|
-
orig += "\n
|
|
104
|
+
orig += "\n" if !orig.empty?
|
|
105
|
+
orig += "+#{cloud.upcase}+: "+new
|
|
140
106
|
else
|
|
141
107
|
# XXX I think this is a NOOP?
|
|
142
108
|
end
|
|
@@ -171,8 +137,6 @@ module MU
|
|
|
171
137
|
# recursively chase down description fields in arrays and objects of our
|
|
172
138
|
# schema and prepend stuff to them for documentation
|
|
173
139
|
def self.prepend_descriptions(prefix, cfg)
|
|
174
|
-
# cfg["description"] ||= ""
|
|
175
|
-
# cfg["description"] = prefix+cfg["description"]
|
|
176
140
|
cfg["prefix"] = prefix
|
|
177
141
|
if cfg["type"] == "array" and cfg["items"]
|
|
178
142
|
cfg["items"] = prepend_descriptions(prefix, cfg["items"])
|
|
@@ -196,7 +160,9 @@ module MU
|
|
|
196
160
|
next if required.size == 0 and res_schema.size == 0
|
|
197
161
|
res_schema.each { |key, cfg|
|
|
198
162
|
cfg["description"] ||= ""
|
|
199
|
-
cfg["description"]
|
|
163
|
+
if !cfg["description"].empty?
|
|
164
|
+
cfg["description"] = "\n# +"+cloud.upcase+"+: "+cfg["description"]
|
|
165
|
+
end
|
|
200
166
|
if docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
|
|
201
167
|
schemaMerge(docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key], cfg, cloud)
|
|
202
168
|
docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]["description"] ||= ""
|
|
@@ -205,6 +171,7 @@ module MU
|
|
|
205
171
|
else
|
|
206
172
|
if only_children[attrs[:cfg_plural]][key]
|
|
207
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
|
|
208
175
|
cfg = prepend_descriptions(prefix, cfg)
|
|
209
176
|
end
|
|
210
177
|
|
|
@@ -247,19 +214,334 @@ module MU
|
|
|
247
214
|
# @return [Hash]: The modified configuration
|
|
248
215
|
def self.manxify(config)
|
|
249
216
|
if config.is_a?(Hash)
|
|
217
|
+
newhash = {}
|
|
250
218
|
config.each_pair { |key, val|
|
|
251
|
-
|
|
219
|
+
newhash[key] = self.manxify(val)
|
|
252
220
|
}
|
|
221
|
+
config = newhash
|
|
253
222
|
elsif config.is_a?(Array)
|
|
223
|
+
newarray = []
|
|
254
224
|
config.each { |val|
|
|
255
|
-
|
|
225
|
+
newarray << self.manxify(val)
|
|
256
226
|
}
|
|
227
|
+
config = newarray
|
|
257
228
|
elsif config.is_a?(MU::Config::Tail)
|
|
258
229
|
return config.to_s
|
|
230
|
+
elsif config.is_a?(MU::Config::Ref)
|
|
231
|
+
return config.to_h
|
|
259
232
|
end
|
|
260
233
|
return config
|
|
261
234
|
end
|
|
262
235
|
|
|
236
|
+
# Make a deep copy of a config hash and pare it down to only primitive
|
|
237
|
+
# types, even at the leaves.
|
|
238
|
+
# @param config [Hash]
|
|
239
|
+
# @return [Hash]
|
|
240
|
+
def self.stripConfig(config)
|
|
241
|
+
MU::Config.manxify(Marshal.load(Marshal.dump(MU.structToHash(config.dup))))
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# A wrapper class for resources to refer to other resources, whether they
|
|
245
|
+
# be a sibling object in the current deploy, an object in another deploy,
|
|
246
|
+
# or a plain cloud id from outside of Mu.
|
|
247
|
+
class Ref
|
|
248
|
+
attr_reader :name
|
|
249
|
+
attr_reader :type
|
|
250
|
+
attr_reader :cloud
|
|
251
|
+
attr_reader :deploy_id
|
|
252
|
+
attr_reader :region
|
|
253
|
+
attr_reader :credentials
|
|
254
|
+
attr_reader :habitat
|
|
255
|
+
attr_reader :mommacat
|
|
256
|
+
attr_reader :tag_key
|
|
257
|
+
attr_reader :tag_value
|
|
258
|
+
attr_reader :obj
|
|
259
|
+
|
|
260
|
+
@@refs = []
|
|
261
|
+
@@ref_semaphore = Mutex.new
|
|
262
|
+
|
|
263
|
+
# 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.
|
|
264
|
+
# @param cfg [Hash]:
|
|
265
|
+
# @return [MU::Config::Ref]
|
|
266
|
+
def self.get(cfg)
|
|
267
|
+
return cfg if cfg.is_a?(MU::Config::Ref)
|
|
268
|
+
checkfields = cfg.keys.map { |k| k.to_sym }
|
|
269
|
+
required = [:id, :type]
|
|
270
|
+
|
|
271
|
+
@@ref_semaphore.synchronize {
|
|
272
|
+
match = nil
|
|
273
|
+
@@refs.each { |ref|
|
|
274
|
+
saw_mismatch = false
|
|
275
|
+
saw_match = false
|
|
276
|
+
needed_values = []
|
|
277
|
+
checkfields.each { |field|
|
|
278
|
+
next if !cfg[field]
|
|
279
|
+
ext_value = ref.instance_variable_get("@#{field.to_s}".to_sym)
|
|
280
|
+
if !ext_value
|
|
281
|
+
needed_values << field
|
|
282
|
+
next
|
|
283
|
+
end
|
|
284
|
+
if cfg[field] != ext_value
|
|
285
|
+
saw_mismatch = true
|
|
286
|
+
elsif required.include?(field) and cfg[field] == ext_value
|
|
287
|
+
saw_match = true
|
|
288
|
+
end
|
|
289
|
+
}
|
|
290
|
+
if saw_match and !saw_mismatch
|
|
291
|
+
# populate empty fields we got from this request
|
|
292
|
+
if needed_values.size > 0
|
|
293
|
+
newref = ref.dup
|
|
294
|
+
needed_values.each { |field|
|
|
295
|
+
newref.instance_variable_set("@#{field.to_s}".to_sym, cfg[field])
|
|
296
|
+
if !newref.respond_to?(field)
|
|
297
|
+
newref.singleton_class.instance_eval { attr_reader field.to_sym }
|
|
298
|
+
end
|
|
299
|
+
}
|
|
300
|
+
@@refs << newref
|
|
301
|
+
return newref
|
|
302
|
+
else
|
|
303
|
+
return ref
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
# if we get here, there was no match
|
|
311
|
+
newref = MU::Config::Ref.new(cfg)
|
|
312
|
+
@@ref_semaphore.synchronize {
|
|
313
|
+
@@refs << newref
|
|
314
|
+
return newref
|
|
315
|
+
}
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# @param cfg [Hash]: A Basket of Kittens configuration hash containing
|
|
319
|
+
# lookup information for a cloud object
|
|
320
|
+
def initialize(cfg)
|
|
321
|
+
cfg.keys.each { |field|
|
|
322
|
+
next if field == "tag"
|
|
323
|
+
if !cfg[field].nil?
|
|
324
|
+
self.instance_variable_set("@#{field}".to_sym, cfg[field])
|
|
325
|
+
elsif !cfg[field.to_sym].nil?
|
|
326
|
+
self.instance_variable_set("@#{field.to_s}".to_sym, cfg[field.to_sym])
|
|
327
|
+
end
|
|
328
|
+
self.singleton_class.instance_eval { attr_reader field.to_sym }
|
|
329
|
+
}
|
|
330
|
+
if cfg['tag'] and cfg['tag']['key'] and
|
|
331
|
+
!cfg['tag']['key'].empty? and cfg['tag']['value']
|
|
332
|
+
@tag_key = cfg['tag']['key']
|
|
333
|
+
@tag_value = cfg['tag']['value']
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
if @deploy_id and !@mommacat
|
|
337
|
+
@mommacat = MU::MommaCat.new(@deploy_id, set_context_to_me: false, create: false)
|
|
338
|
+
elsif @mommacat and !@deploy_id
|
|
339
|
+
@deploy_id = @mommacat.deploy_id
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
kitten if @mommacat # try to populate the actual cloud object for this
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Comparison operator
|
|
346
|
+
def <=>(other)
|
|
347
|
+
return 1 if other.nil?
|
|
348
|
+
self.to_s <=> other.to_s
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# 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}.
|
|
352
|
+
# @param aliases [Array<Hash>]: Key => value mappings to set backwards-compatibility aliases for attributes, such as the ubiquitous +vpc_id+ (+vpc_id+ => +id+).
|
|
353
|
+
# @return [Hash]
|
|
354
|
+
def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil)
|
|
355
|
+
parent_obj ||= caller[1].gsub(/.*?\/([^\.\/]+)\.rb:.*/, '\1')
|
|
356
|
+
desc ||= "Reference a #{type ? "'#{type}' resource" : "resource" } from this #{parent_obj ? "'#{parent_obj}'" : "" } resource"
|
|
357
|
+
schema = {
|
|
358
|
+
"type" => "object",
|
|
359
|
+
"#MU_REFERENCE" => true,
|
|
360
|
+
"minProperties" => 1,
|
|
361
|
+
"description" => desc,
|
|
362
|
+
"properties" => {
|
|
363
|
+
"id" => {
|
|
364
|
+
"type" => "string",
|
|
365
|
+
"description" => "Cloud identifier of a resource we want to reference, typically used when leveraging resources not managed by MU"
|
|
366
|
+
},
|
|
367
|
+
"name" => {
|
|
368
|
+
"type" => "string",
|
|
369
|
+
"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+."
|
|
370
|
+
},
|
|
371
|
+
"type" => {
|
|
372
|
+
"type" => "string",
|
|
373
|
+
"description" => "The resource type we're attempting to reference.",
|
|
374
|
+
"enum" => MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }
|
|
375
|
+
},
|
|
376
|
+
"deploy_id" => {
|
|
377
|
+
"type" => "string",
|
|
378
|
+
"description" => "Our target resource should be found in this Mu deploy."
|
|
379
|
+
},
|
|
380
|
+
"credentials" => MU::Config.credentials_primitive,
|
|
381
|
+
"region" => MU::Config.region_primitive,
|
|
382
|
+
"cloud" => MU::Config.cloud_primitive,
|
|
383
|
+
"tag" => {
|
|
384
|
+
"type" => "object",
|
|
385
|
+
"description" => "If the target resource supports tagging and our resource implementations +find+ method supports it, we can attempt to locate it by tag.",
|
|
386
|
+
"properties" => {
|
|
387
|
+
"key" => {
|
|
388
|
+
"type" => "string",
|
|
389
|
+
"description" => "The tag or label key to search against"
|
|
390
|
+
},
|
|
391
|
+
"value" => {
|
|
392
|
+
"type" => "string",
|
|
393
|
+
"description" => "The tag or label value to match"
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if !["folders", "habitats"].include?(type)
|
|
400
|
+
schema["properties"]["habitat"] = MU::Config::Habitat.reference
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if !type.nil?
|
|
404
|
+
schema["required"] = ["type"]
|
|
405
|
+
schema["properties"]["type"]["default"] = type
|
|
406
|
+
schema["properties"]["type"]["enum"] = [type]
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
aliases.each { |a|
|
|
410
|
+
a.each_pair { |k, v|
|
|
411
|
+
if schema["properties"][v]
|
|
412
|
+
schema["properties"][k] = schema["properties"][v].dup
|
|
413
|
+
schema["properties"][k]["description"] = "Alias for <tt>#{v}</tt>"
|
|
414
|
+
else
|
|
415
|
+
MU.log "Reference schema alias #{k} wants to alias #{v}, but no such attribute exists", MU::WARN, details: caller[4]
|
|
416
|
+
end
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
schema
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Decompose into a plain-jane {MU::Config::BasketOfKittens} hash fragment,
|
|
424
|
+
# of the sort that would have been used to declare this reference in the
|
|
425
|
+
# first place.
|
|
426
|
+
def to_h
|
|
427
|
+
me = { }
|
|
428
|
+
|
|
429
|
+
self.instance_variables.each { |var|
|
|
430
|
+
next if [:@obj, :@mommacat, :@tag_key, :@tag_value].include?(var)
|
|
431
|
+
val = self.instance_variable_get(var)
|
|
432
|
+
next if val.nil?
|
|
433
|
+
val = val.to_h if val.is_a?(MU::Config::Ref)
|
|
434
|
+
me[var.to_s.sub(/^@/, '')] = val
|
|
435
|
+
}
|
|
436
|
+
if @tag_key and !@tag_key.empty?
|
|
437
|
+
me['tag'] = {
|
|
438
|
+
'key' => @tag_key,
|
|
439
|
+
'value' => @tag_value
|
|
440
|
+
}
|
|
441
|
+
end
|
|
442
|
+
me
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Getter for the #{id} instance variable that attempts to populate it if
|
|
446
|
+
# it's not set.
|
|
447
|
+
# @return [String,nil]
|
|
448
|
+
def id
|
|
449
|
+
return @id if @id
|
|
450
|
+
kitten # if it's not defined, attempt to define it
|
|
451
|
+
@id
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Alias for {id}
|
|
455
|
+
# @return [String,nil]
|
|
456
|
+
def cloud_id
|
|
457
|
+
id
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Return a {MU::Cloud} object for this reference. This is only meant to be
|
|
461
|
+
# called in a live deploy, which is to say that if called during initial
|
|
462
|
+
# configuration parsing, results may be incorrect.
|
|
463
|
+
# @param mommacat [MU::MommaCat]: A deploy object which will be searched for the referenced resource if provided, before restoring to broader, less efficient searches.
|
|
464
|
+
def kitten(mommacat = @mommacat)
|
|
465
|
+
return nil if !@cloud or !@type
|
|
466
|
+
|
|
467
|
+
if @obj
|
|
468
|
+
@deploy_id ||= @obj.deploy_id
|
|
469
|
+
@id ||= @obj.cloud_id
|
|
470
|
+
@name ||= @obj.config['name']
|
|
471
|
+
return @obj
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
if mommacat
|
|
475
|
+
@obj = mommacat.findLitterMate(type: @type, name: @name, cloud_id: @id, credentials: @credentials, debug: false)
|
|
476
|
+
if @obj # initialize missing attributes, if we can
|
|
477
|
+
@id ||= @obj.cloud_id
|
|
478
|
+
@mommacat ||= mommacat
|
|
479
|
+
@obj.intoDeploy(@mommacat) # make real sure these are set
|
|
480
|
+
@deploy_id ||= mommacat.deploy_id
|
|
481
|
+
if !@name
|
|
482
|
+
if @obj.config and @obj.config['name']
|
|
483
|
+
@name = @obj.config['name']
|
|
484
|
+
elsif @obj.mu_name
|
|
485
|
+
if @type == "folders"
|
|
486
|
+
MU.log "would assign name '#{@obj.mu_name}' in ref to this folder if I were feeling aggressive", MU::WARN, details: self.to_h
|
|
487
|
+
end
|
|
488
|
+
# @name = @obj.mu_name
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
return @obj
|
|
492
|
+
else
|
|
493
|
+
# MU.log "Failed to find a live '#{@type.to_s}' object named #{@name}#{@id ? " (#{@id})" : "" }#{ @habitat ? " in habitat #{@habitat}" : "" }", MU::WARN, details: self
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if !@obj and !(@cloud == "Google" and @id and @type == "users" and MU::Cloud::Google::User.cannedServiceAcctName?(@id))
|
|
498
|
+
|
|
499
|
+
begin
|
|
500
|
+
hab_arg = if @habitat.nil?
|
|
501
|
+
[nil]
|
|
502
|
+
elsif @habitat.is_a?(MU::Config::Ref)
|
|
503
|
+
[@habitat.id]
|
|
504
|
+
elsif @habitat.is_a?(Hash)
|
|
505
|
+
[@habitat["id"]]
|
|
506
|
+
else
|
|
507
|
+
[@habitat.to_s]
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
found = MU::MommaCat.findStray(
|
|
511
|
+
@cloud,
|
|
512
|
+
@type,
|
|
513
|
+
name: @name,
|
|
514
|
+
cloud_id: @id,
|
|
515
|
+
deploy_id: @deploy_id,
|
|
516
|
+
region: @region,
|
|
517
|
+
habitats: hab_arg,
|
|
518
|
+
credentials: @credentials,
|
|
519
|
+
dummy_ok: (["habitats", "folders", "users", "groups"].include?(@type))
|
|
520
|
+
)
|
|
521
|
+
@obj ||= found.first if found
|
|
522
|
+
rescue ThreadError => e
|
|
523
|
+
# Sometimes MommaCat calls us in a potential deadlock situation;
|
|
524
|
+
# don't be the cause of a fatal error if so, we don't need this
|
|
525
|
+
# object that badly.
|
|
526
|
+
raise e if !e.message.match(/recursive locking/)
|
|
527
|
+
rescue SystemExit => e
|
|
528
|
+
# XXX this is temporary, to cope with some debug stuff that's in findStray
|
|
529
|
+
# for the nonce
|
|
530
|
+
return
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
if @obj
|
|
535
|
+
@deploy_id ||= @obj.deploy_id
|
|
536
|
+
@id ||= @obj.cloud_id
|
|
537
|
+
@name ||= @obj.config['name']
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
@obj
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
end
|
|
544
|
+
|
|
263
545
|
# A wrapper for config leaves that came from ERB parameters instead of raw
|
|
264
546
|
# YAML or JSON. Will behave like a string for things that expect that
|
|
265
547
|
# sort of thing. Code that needs to know that this leaf was the result of
|
|
@@ -324,7 +606,7 @@ module MU
|
|
|
324
606
|
end
|
|
325
607
|
# Walk like a String
|
|
326
608
|
def to_s
|
|
327
|
-
@prefix+@value+@suffix
|
|
609
|
+
@prefix.to_s+@value.to_s+@suffix.to_s
|
|
328
610
|
end
|
|
329
611
|
# Quack like a String
|
|
330
612
|
def to_str
|
|
@@ -350,6 +632,11 @@ module MU
|
|
|
350
632
|
def ==(o)
|
|
351
633
|
(o.class == self.class or o.class == "String") && o.to_s == to_s
|
|
352
634
|
end
|
|
635
|
+
# Concatenate like a string
|
|
636
|
+
def +(o)
|
|
637
|
+
return to_s if o.nil?
|
|
638
|
+
to_s + o.to_s
|
|
639
|
+
end
|
|
353
640
|
# Perform global substitutions like a String
|
|
354
641
|
def gsub(*args)
|
|
355
642
|
to_s.gsub(*args)
|
|
@@ -437,6 +724,7 @@ module MU
|
|
|
437
724
|
"MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
|
|
438
725
|
else
|
|
439
726
|
tail = getTail(var_name.to_s)
|
|
727
|
+
|
|
440
728
|
if tail.is_a?(Array)
|
|
441
729
|
if @param_pass
|
|
442
730
|
return tail.map {|f| f.values.first.to_s }.join(",")
|
|
@@ -446,7 +734,11 @@ module MU
|
|
|
446
734
|
return "MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
|
|
447
735
|
end
|
|
448
736
|
else
|
|
449
|
-
|
|
737
|
+
if @param_pass
|
|
738
|
+
tail.to_s
|
|
739
|
+
else
|
|
740
|
+
return "MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
|
|
741
|
+
end
|
|
450
742
|
end
|
|
451
743
|
end
|
|
452
744
|
end
|
|
@@ -470,13 +762,28 @@ module MU
|
|
|
470
762
|
"MU::Config.getTail PLACEHOLDER #{var_name} REDLOHECALP"
|
|
471
763
|
end
|
|
472
764
|
|
|
765
|
+
# Make sure our parameter values are all available in the local namespace
|
|
766
|
+
# that ERB will be using, minus any that conflict with existing variables
|
|
767
|
+
erb_binding = get_binding
|
|
768
|
+
@@tails.each_pair { |key, tail|
|
|
769
|
+
next if !tail.is_a?(MU::Config::Tail) or tail.is_list_element
|
|
770
|
+
# XXX figure out what to do with lists
|
|
771
|
+
begin
|
|
772
|
+
erb_binding.local_variable_set(key.to_sym, tail.to_s)
|
|
773
|
+
rescue NameError
|
|
774
|
+
MU.log "Binding #{key} = #{tail.to_s}", MU::DEBUG
|
|
775
|
+
erb_binding.local_variable_set(key.to_sym, tail.to_s)
|
|
776
|
+
end
|
|
777
|
+
}
|
|
778
|
+
|
|
473
779
|
# Figure out what kind of file we're loading. We handle includes
|
|
474
780
|
# differently if YAML is involved. These globals get used inside
|
|
475
781
|
# templates. They're globals on purpose. Stop whining.
|
|
476
782
|
$file_format = MU::Config.guessFormat(path)
|
|
477
783
|
$yaml_refs = {}
|
|
478
784
|
erb = ERB.new(File.read(path), nil, "<>")
|
|
479
|
-
|
|
785
|
+
|
|
786
|
+
raw_text = erb.result(erb_binding)
|
|
480
787
|
raw_json = nil
|
|
481
788
|
|
|
482
789
|
# If we're working in YAML, do some magic to make includes work better.
|
|
@@ -533,7 +840,7 @@ module MU
|
|
|
533
840
|
# @param skipinitialupdates [Boolean]: Whether to forcibly apply the *skipinitialupdates* flag to nodes created by this configuration.
|
|
534
841
|
# @param params [Hash]: Optional name-value parameter pairs, which will be passed to our configuration files as ERB variables.
|
|
535
842
|
# @return [Hash]: The complete validated configuration for a deployment.
|
|
536
|
-
def initialize(path, skipinitialupdates = false, params:
|
|
843
|
+
def initialize(path, skipinitialupdates = false, params: {}, updating: nil, default_credentials: nil)
|
|
537
844
|
$myPublicIp = MU::Cloud::AWS.getAWSMetaData("public-ipv4")
|
|
538
845
|
$myRoot = MU.myRoot
|
|
539
846
|
$myRoot.freeze
|
|
@@ -551,6 +858,7 @@ module MU
|
|
|
551
858
|
@admin_firewall_rules = []
|
|
552
859
|
@skipinitialupdates = skipinitialupdates
|
|
553
860
|
@updating = updating
|
|
861
|
+
@default_credentials = default_credentials
|
|
554
862
|
|
|
555
863
|
ok = true
|
|
556
864
|
params.each_pair { |name, value|
|
|
@@ -601,15 +909,19 @@ module MU
|
|
|
601
909
|
elsif param["required"] or !param.has_key?("required")
|
|
602
910
|
MU.log "Required parameter '#{param['name']}' not supplied", MU::ERR
|
|
603
911
|
ok = false
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
else
|
|
608
|
-
getTail(param['name'], value: @@parameters[param['name']], valid_values: param['valid_values'], description: param['description'], prettyname: param['prettyname'], list_of: param['list_of'])
|
|
912
|
+
next
|
|
913
|
+
else # not required, no default
|
|
914
|
+
next
|
|
609
915
|
end
|
|
610
916
|
end
|
|
917
|
+
if param.has_key?("cloudtype")
|
|
918
|
+
getTail(param['name'], value: @@parameters[param['name']], cloudtype: param["cloudtype"], valid_values: param['valid_values'], description: param['description'], prettyname: param['prettyname'], list_of: param['list_of'])
|
|
919
|
+
else
|
|
920
|
+
getTail(param['name'], value: @@parameters[param['name']], valid_values: param['valid_values'], description: param['description'], prettyname: param['prettyname'], list_of: param['list_of'])
|
|
921
|
+
end
|
|
611
922
|
}
|
|
612
923
|
end
|
|
924
|
+
|
|
613
925
|
raise ValidationError if !ok
|
|
614
926
|
@@parameters.each_pair { |name, val|
|
|
615
927
|
next if @@tails.has_key?(name) and @@tails[name].is_a?(MU::Config::Tail) and @@tails[name].pseudo
|
|
@@ -626,13 +938,13 @@ module MU
|
|
|
626
938
|
MU.log "Passing variable '#{name}' into #{path} with value '#{val}'"
|
|
627
939
|
}
|
|
628
940
|
raise DeployParamError, "One or more invalid parameters specified" if !ok
|
|
629
|
-
$parameters = @@parameters
|
|
941
|
+
$parameters = @@parameters.dup
|
|
630
942
|
$parameters.freeze
|
|
631
943
|
|
|
632
944
|
tmp_cfg, raw_erb = resolveConfig(path: @@config_path)
|
|
633
945
|
|
|
634
946
|
# Convert parameter entries that constitute whole config keys into
|
|
635
|
-
# MU::Config::Tail objects.
|
|
947
|
+
# {MU::Config::Tail} objects.
|
|
636
948
|
def resolveTails(tree, indent= "")
|
|
637
949
|
if tree.is_a?(Hash)
|
|
638
950
|
tree.each_pair { |key, val|
|
|
@@ -663,19 +975,32 @@ module MU
|
|
|
663
975
|
}
|
|
664
976
|
]
|
|
665
977
|
end
|
|
666
|
-
|
|
978
|
+
|
|
979
|
+
@config['credentials'] ||= @default_credentials
|
|
980
|
+
|
|
981
|
+
types = MU::Cloud.resource_types.values.map { |v| v[:cfg_plural] }
|
|
982
|
+
|
|
983
|
+
MU::Cloud.resource_types.values.map { |v| v[:cfg_plural] }.each { |type|
|
|
984
|
+
if @config[type]
|
|
985
|
+
@config[type].each { |k|
|
|
986
|
+
applyInheritedDefaults(k, type)
|
|
987
|
+
}
|
|
988
|
+
end
|
|
989
|
+
}
|
|
990
|
+
applySchemaDefaults(@config, MU::Config.schema)
|
|
991
|
+
|
|
667
992
|
validate # individual resources validate when added now, necessary because the schema can change depending on what cloud they're targeting
|
|
668
993
|
# XXX but now we're not validating top-level keys, argh
|
|
669
994
|
#pp @config
|
|
670
995
|
#raise "DERP"
|
|
671
|
-
|
|
996
|
+
@config.freeze
|
|
672
997
|
end
|
|
673
998
|
|
|
674
999
|
# Output the dependencies of this BoK stack as a directed acyclic graph.
|
|
675
1000
|
# Very useful for debugging.
|
|
676
1001
|
def visualizeDependencies
|
|
677
1002
|
# GraphViz won't like MU::Config::Tail, pare down to plain Strings
|
|
678
|
-
config = MU::Config.
|
|
1003
|
+
config = MU::Config.stripConfig(@config)
|
|
679
1004
|
begin
|
|
680
1005
|
g = GraphViz.new(:G, :type => :digraph)
|
|
681
1006
|
# Generate a GraphViz node for each resource in this stack
|
|
@@ -781,7 +1106,9 @@ module MU
|
|
|
781
1106
|
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
782
1107
|
if @kittens[cfg_plural]
|
|
783
1108
|
@kittens[cfg_plural].each { |kitten|
|
|
784
|
-
if kitten['name'] == name.to_s or
|
|
1109
|
+
if kitten['name'].to_s == name.to_s or
|
|
1110
|
+
kitten['virtual_name'].to_s == name.to_s or
|
|
1111
|
+
(has_multiple and name.nil?)
|
|
785
1112
|
if has_multiple
|
|
786
1113
|
matches << kitten
|
|
787
1114
|
else
|
|
@@ -857,11 +1184,18 @@ module MU
|
|
|
857
1184
|
# @param descriptor [Hash]: The configuration description, as from a Basket of Kittens
|
|
858
1185
|
# @param type [String]: The type of resource being added
|
|
859
1186
|
# @param delay_validation [Boolean]: Whether to hold off on calling the resource's validateConfig method
|
|
860
|
-
|
|
1187
|
+
# @param ignore_duplicates [Boolean]: Do not raise an exception if we attempt to insert a resource with a +name+ field that's already in use
|
|
1188
|
+
def insertKitten(descriptor, type, delay_validation = false, ignore_duplicates: false)
|
|
861
1189
|
append = false
|
|
1190
|
+
start = Time.now
|
|
1191
|
+
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
1192
|
+
|
|
1193
|
+
if !ignore_duplicates and haveLitterMate?(descriptor['name'], cfg_name)
|
|
1194
|
+
# raise DuplicateNameError, "A #{shortclass} named #{descriptor['name']} has already been inserted into this configuration"
|
|
1195
|
+
end
|
|
862
1196
|
|
|
863
1197
|
@kittencfg_semaphore.synchronize {
|
|
864
|
-
append = !@kittens[
|
|
1198
|
+
append = !@kittens[cfg_plural].include?(descriptor)
|
|
865
1199
|
|
|
866
1200
|
# Skip if this kitten has already been validated and appended
|
|
867
1201
|
if !append and descriptor["#MU_VALIDATED"]
|
|
@@ -870,10 +1204,26 @@ module MU
|
|
|
870
1204
|
}
|
|
871
1205
|
ok = true
|
|
872
1206
|
|
|
873
|
-
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
874
1207
|
descriptor["#MU_CLOUDCLASS"] = classname
|
|
875
|
-
|
|
1208
|
+
|
|
1209
|
+
applyInheritedDefaults(descriptor, cfg_plural)
|
|
1210
|
+
|
|
1211
|
+
# Meld defaults from our global schema and, if applicable, from our
|
|
1212
|
+
# cloud-specific schema.
|
|
876
1213
|
schemaclass = Object.const_get("MU").const_get("Config").const_get(shortclass)
|
|
1214
|
+
myschema = Marshal.load(Marshal.dump(MU::Config.schema["properties"][cfg_plural]["items"]))
|
|
1215
|
+
more_required, more_schema = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get(shortclass.to_s).schema(self)
|
|
1216
|
+
if more_schema
|
|
1217
|
+
MU::Config.schemaMerge(myschema["properties"], more_schema, descriptor["cloud"])
|
|
1218
|
+
end
|
|
1219
|
+
myschema["required"] ||= []
|
|
1220
|
+
if more_required
|
|
1221
|
+
myschema["required"].concat(more_required)
|
|
1222
|
+
myschema["required"].uniq!
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
descriptor = applySchemaDefaults(descriptor, myschema, type: shortclass)
|
|
1226
|
+
MU.log "Schema check on #{descriptor['cloud']} #{cfg_name} #{descriptor['name']}", MU::DEBUG, details: myschema
|
|
877
1227
|
|
|
878
1228
|
if (descriptor["region"] and descriptor["region"].empty?) or
|
|
879
1229
|
(descriptor['cloud'] == "Google" and ["firewall_rule", "vpc"].include?(cfg_name))
|
|
@@ -881,8 +1231,8 @@ module MU
|
|
|
881
1231
|
end
|
|
882
1232
|
|
|
883
1233
|
# Make sure a sensible region has been targeted, if applicable
|
|
1234
|
+
classobj = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"])
|
|
884
1235
|
if descriptor["region"]
|
|
885
|
-
classobj = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"])
|
|
886
1236
|
valid_regions = classobj.listRegions
|
|
887
1237
|
if !valid_regions.include?(descriptor["region"])
|
|
888
1238
|
MU.log "Known regions for cloud '#{descriptor['cloud']}' do not include '#{descriptor["region"]}'", MU::ERR, details: valid_regions
|
|
@@ -890,8 +1240,10 @@ module MU
|
|
|
890
1240
|
end
|
|
891
1241
|
end
|
|
892
1242
|
|
|
893
|
-
if descriptor
|
|
894
|
-
if
|
|
1243
|
+
if descriptor.has_key?('project')
|
|
1244
|
+
if descriptor['project'].nil?
|
|
1245
|
+
descriptor.delete('project')
|
|
1246
|
+
elsif haveLitterMate?(descriptor['project'], "habitats")
|
|
895
1247
|
descriptor['dependencies'] ||= []
|
|
896
1248
|
descriptor['dependencies'] << {
|
|
897
1249
|
"type" => "habitat",
|
|
@@ -902,6 +1254,16 @@ module MU
|
|
|
902
1254
|
|
|
903
1255
|
# Does this resource go in a VPC?
|
|
904
1256
|
if !descriptor["vpc"].nil? and !delay_validation
|
|
1257
|
+
# Quietly fix old vpc reference style
|
|
1258
|
+
if descriptor['vpc']['vpc_id']
|
|
1259
|
+
descriptor['vpc']['id'] ||= descriptor['vpc']['vpc_id']
|
|
1260
|
+
descriptor['vpc'].delete('vpc_id')
|
|
1261
|
+
end
|
|
1262
|
+
if descriptor['vpc']['vpc_name']
|
|
1263
|
+
descriptor['vpc']['name'] = descriptor['vpc']['vpc_name']
|
|
1264
|
+
descriptor['vpc'].delete('vpc_name')
|
|
1265
|
+
end
|
|
1266
|
+
|
|
905
1267
|
descriptor['vpc']['cloud'] = descriptor['cloud']
|
|
906
1268
|
if descriptor['credentials']
|
|
907
1269
|
descriptor['vpc']['credentials'] ||= descriptor['credentials']
|
|
@@ -911,16 +1273,27 @@ module MU
|
|
|
911
1273
|
end
|
|
912
1274
|
|
|
913
1275
|
# If we're using a VPC in this deploy, set it as a dependency
|
|
914
|
-
if !descriptor["vpc"]["
|
|
915
|
-
haveLitterMate?(descriptor["vpc"]["
|
|
1276
|
+
if !descriptor["vpc"]["name"].nil? and
|
|
1277
|
+
haveLitterMate?(descriptor["vpc"]["name"], "vpcs") and
|
|
916
1278
|
descriptor["vpc"]['deploy_id'].nil? and
|
|
917
|
-
descriptor["vpc"]['
|
|
1279
|
+
descriptor["vpc"]['id'].nil?
|
|
918
1280
|
descriptor["dependencies"] << {
|
|
919
1281
|
"type" => "vpc",
|
|
920
|
-
"name" => descriptor["vpc"]["
|
|
1282
|
+
"name" => descriptor["vpc"]["name"],
|
|
921
1283
|
}
|
|
1284
|
+
siblingvpc = haveLitterMate?(descriptor["vpc"]["name"], "vpcs")
|
|
1285
|
+
|
|
1286
|
+
if siblingvpc and siblingvpc['bastion'] and
|
|
1287
|
+
["server", "server_pool"].include?(cfg_name) and
|
|
1288
|
+
!descriptor['bastion']
|
|
1289
|
+
if descriptor['name'] != siblingvpc['bastion'].to_h['name']
|
|
1290
|
+
descriptor["dependencies"] << {
|
|
1291
|
+
"type" => "server",
|
|
1292
|
+
"name" => siblingvpc['bastion'].to_h['name']
|
|
1293
|
+
}
|
|
1294
|
+
end
|
|
1295
|
+
end
|
|
922
1296
|
|
|
923
|
-
siblingvpc = haveLitterMate?(descriptor["vpc"]["vpc_name"], "vpcs")
|
|
924
1297
|
# things that live in subnets need their VPCs to be fully
|
|
925
1298
|
# resolved before we can proceed
|
|
926
1299
|
if ["server", "server_pool", "loadbalancer", "database", "cache_cluster", "container_cluster", "storage_pool"].include?(cfg_name)
|
|
@@ -930,11 +1303,11 @@ module MU
|
|
|
930
1303
|
end
|
|
931
1304
|
if !MU::Config::VPC.processReference(descriptor['vpc'],
|
|
932
1305
|
cfg_plural,
|
|
933
|
-
|
|
1306
|
+
descriptor,
|
|
934
1307
|
self,
|
|
935
1308
|
dflt_region: descriptor['region'],
|
|
936
|
-
is_sibling: true,
|
|
937
1309
|
credentials: descriptor['credentials'],
|
|
1310
|
+
dflt_project: descriptor['project'],
|
|
938
1311
|
sibling_vpcs: @kittens['vpcs'])
|
|
939
1312
|
ok = false
|
|
940
1313
|
end
|
|
@@ -943,12 +1316,13 @@ module MU
|
|
|
943
1316
|
# thing exists, and also fetch its id now so later search routines
|
|
944
1317
|
# don't have to work so hard.
|
|
945
1318
|
else
|
|
946
|
-
if !MU::Config::VPC.processReference(descriptor["vpc"],
|
|
947
|
-
|
|
1319
|
+
if !MU::Config::VPC.processReference(descriptor["vpc"],
|
|
1320
|
+
cfg_plural,
|
|
1321
|
+
descriptor,
|
|
948
1322
|
self,
|
|
949
1323
|
credentials: descriptor['credentials'],
|
|
1324
|
+
dflt_project: descriptor['project'],
|
|
950
1325
|
dflt_region: descriptor['region'])
|
|
951
|
-
MU.log "insertKitten was called from #{caller[0]}", MU::ERR
|
|
952
1326
|
ok = false
|
|
953
1327
|
end
|
|
954
1328
|
end
|
|
@@ -979,6 +1353,7 @@ module MU
|
|
|
979
1353
|
(descriptor['ingress_rules'] or
|
|
980
1354
|
["server", "server_pool", "database"].include?(cfg_name))
|
|
981
1355
|
descriptor['ingress_rules'] ||= []
|
|
1356
|
+
fw_classobj = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get("FirewallRule")
|
|
982
1357
|
|
|
983
1358
|
acl = {
|
|
984
1359
|
"name" => fwname,
|
|
@@ -986,14 +1361,24 @@ module MU
|
|
|
986
1361
|
"region" => descriptor['region'],
|
|
987
1362
|
"credentials" => descriptor["credentials"]
|
|
988
1363
|
}
|
|
989
|
-
|
|
1364
|
+
if !fw_classobj.isGlobal?
|
|
1365
|
+
acl['region'] = descriptor['region']
|
|
1366
|
+
acl['region'] ||= classobj.myRegion(acl['credentials'])
|
|
1367
|
+
else
|
|
1368
|
+
acl.delete("region")
|
|
1369
|
+
end
|
|
1370
|
+
if descriptor["vpc"]
|
|
1371
|
+
acl["vpc"] = descriptor['vpc'].dup
|
|
1372
|
+
acl["vpc"].delete("subnet_pref")
|
|
1373
|
+
end
|
|
1374
|
+
|
|
990
1375
|
["optional_tags", "tags", "cloud", "project"].each { |param|
|
|
991
1376
|
acl[param] = descriptor[param] if descriptor[param]
|
|
992
1377
|
}
|
|
993
1378
|
descriptor["add_firewall_rules"] = [] if descriptor["add_firewall_rules"].nil?
|
|
994
|
-
descriptor["add_firewall_rules"] << {"rule_name" => fwname}
|
|
1379
|
+
descriptor["add_firewall_rules"] << {"rule_name" => fwname, "type" => "firewall_rules" } # XXX why the duck is there a type argument required here?
|
|
995
1380
|
acl = resolveIntraStackFirewallRefs(acl)
|
|
996
|
-
ok = false if !insertKitten(acl, "firewall_rules")
|
|
1381
|
+
ok = false if !insertKitten(acl, "firewall_rules", delay_validation)
|
|
997
1382
|
end
|
|
998
1383
|
|
|
999
1384
|
# Does it declare association with any sibling LoadBalancers?
|
|
@@ -1030,7 +1415,7 @@ module MU
|
|
|
1030
1415
|
}
|
|
1031
1416
|
siblingfw = haveLitterMate?(acl_include["rule_name"], "firewall_rules")
|
|
1032
1417
|
if !siblingfw["#MU_VALIDATED"]
|
|
1033
|
-
ok = false if !insertKitten(siblingfw, "firewall_rules")
|
|
1418
|
+
ok = false if !insertKitten(siblingfw, "firewall_rules", delay_validation)
|
|
1034
1419
|
end
|
|
1035
1420
|
elsif acl_include["rule_name"]
|
|
1036
1421
|
MU.log shortclass.to_s+" #{descriptor['name']} depends on FirewallRule #{acl_include["rule_name"]}, but no such rule declared.", MU::ERR
|
|
@@ -1103,21 +1488,9 @@ module MU
|
|
|
1103
1488
|
# here
|
|
1104
1489
|
ok = false if !schemaclass.validate(descriptor, self)
|
|
1105
1490
|
|
|
1106
|
-
|
|
1107
|
-
myschema = Marshal.load(Marshal.dump(MU::Config.schema["properties"][cfg_plural]["items"]))
|
|
1108
|
-
more_required, more_schema = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get(shortclass.to_s).schema(self)
|
|
1109
|
-
|
|
1110
|
-
if more_schema
|
|
1111
|
-
MU::Config.schemaMerge(myschema["properties"], more_schema, descriptor["cloud"])
|
|
1112
|
-
MU::Config.set_defaults(descriptor, myschema)
|
|
1113
|
-
end
|
|
1114
|
-
myschema["required"] ||= []
|
|
1115
|
-
myschema["required"].concat(more_required)
|
|
1116
|
-
myschema["required"].uniq!
|
|
1117
|
-
MU.log "Schema check on #{descriptor['cloud']} #{cfg_name} #{descriptor['name']}", MU::DEBUG, details: myschema
|
|
1118
|
-
|
|
1119
|
-
plain_cfg = MU::Config.manxify(Marshal.load(Marshal.dump(descriptor)))
|
|
1491
|
+
plain_cfg = MU::Config.stripConfig(descriptor)
|
|
1120
1492
|
plain_cfg.delete("#MU_CLOUDCLASS")
|
|
1493
|
+
plain_cfg.delete("#MU_VALIDATION_ATTEMPTED")
|
|
1121
1494
|
plain_cfg.delete("#TARGETCLASS")
|
|
1122
1495
|
plain_cfg.delete("#TARGETNAME")
|
|
1123
1496
|
plain_cfg.delete("parent_block") if cfg_plural == "vpcs"
|
|
@@ -1143,17 +1516,23 @@ module MU
|
|
|
1143
1516
|
# on stuff that will cause spurious alarms further in
|
|
1144
1517
|
if ok
|
|
1145
1518
|
parser = Object.const_get("MU").const_get("Cloud").const_get(descriptor["cloud"]).const_get(shortclass.to_s)
|
|
1146
|
-
|
|
1147
|
-
passed = parser.validateConfig(
|
|
1519
|
+
original_descriptor = MU::Config.stripConfig(descriptor)
|
|
1520
|
+
passed = parser.validateConfig(descriptor, self)
|
|
1148
1521
|
|
|
1149
|
-
if passed
|
|
1150
|
-
descriptor
|
|
1151
|
-
else
|
|
1522
|
+
if !passed
|
|
1523
|
+
descriptor = original_descriptor
|
|
1152
1524
|
ok = false
|
|
1153
1525
|
end
|
|
1526
|
+
|
|
1527
|
+
# Make sure we've been configured with the right credentials
|
|
1528
|
+
cloudbase = Object.const_get("MU").const_get("Cloud").const_get(descriptor['cloud'])
|
|
1529
|
+
credcfg = cloudbase.credConfig(descriptor['credentials'])
|
|
1530
|
+
if !credcfg or credcfg.empty?
|
|
1531
|
+
raise ValidationError, "#{descriptor['cloud']} #{cfg_name} #{descriptor['name']} declares credential set #{descriptor['credentials']}, but no such credentials exist for that cloud provider"
|
|
1532
|
+
end
|
|
1533
|
+
|
|
1154
1534
|
descriptor['#MU_VALIDATED'] = true
|
|
1155
1535
|
end
|
|
1156
|
-
|
|
1157
1536
|
end
|
|
1158
1537
|
|
|
1159
1538
|
descriptor["dependencies"].uniq!
|
|
@@ -1161,13 +1540,15 @@ module MU
|
|
|
1161
1540
|
@kittencfg_semaphore.synchronize {
|
|
1162
1541
|
@kittens[cfg_plural] << descriptor if append
|
|
1163
1542
|
}
|
|
1543
|
+
|
|
1164
1544
|
ok
|
|
1165
1545
|
end
|
|
1166
1546
|
|
|
1167
1547
|
@@allregions = []
|
|
1168
|
-
MU::Cloud.
|
|
1548
|
+
MU::Cloud.availableClouds.each { |cloud|
|
|
1169
1549
|
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
1170
|
-
|
|
1550
|
+
regions = cloudclass.listRegions()
|
|
1551
|
+
@@allregions.concat(regions) if regions
|
|
1171
1552
|
}
|
|
1172
1553
|
|
|
1173
1554
|
# Configuration chunk for choosing a provider region
|
|
@@ -1175,8 +1556,9 @@ module MU
|
|
|
1175
1556
|
def self.region_primitive
|
|
1176
1557
|
if !@@allregions or @@allregions.empty?
|
|
1177
1558
|
@@allregions = []
|
|
1178
|
-
MU::Cloud.
|
|
1559
|
+
MU::Cloud.availableClouds.each { |cloud|
|
|
1179
1560
|
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
|
1561
|
+
return @allregions if !cloudclass.listRegions()
|
|
1180
1562
|
@@allregions.concat(cloudclass.listRegions())
|
|
1181
1563
|
}
|
|
1182
1564
|
end
|
|
@@ -1236,7 +1618,7 @@ module MU
|
|
|
1236
1618
|
def self.cloud_primitive
|
|
1237
1619
|
{
|
|
1238
1620
|
"type" => "string",
|
|
1239
|
-
"default" => MU::Config.defaultCloud,
|
|
1621
|
+
# "default" => MU::Config.defaultCloud, # applyInheritedDefaults does this better
|
|
1240
1622
|
"enum" => MU::Cloud.supportedClouds
|
|
1241
1623
|
}
|
|
1242
1624
|
end
|
|
@@ -1250,7 +1632,7 @@ module MU
|
|
|
1250
1632
|
# @param cloud [String]: The parent resource's cloud plugin identifier
|
|
1251
1633
|
# @param region [String]: Cloud provider region, if applicable.
|
|
1252
1634
|
# @return [Hash<String>]: A dependency description that the calling resource can then add to itself.
|
|
1253
|
-
def adminFirewallRuleset(vpc: nil, admin_ip: nil, region: nil, cloud: nil, credentials: nil)
|
|
1635
|
+
def adminFirewallRuleset(vpc: nil, admin_ip: nil, region: nil, cloud: nil, credentials: nil, rules_only: false)
|
|
1254
1636
|
if !cloud or (cloud == "AWS" and !region)
|
|
1255
1637
|
raise MuError, "Cannot call adminFirewallRuleset without specifying the parent's region and cloud provider"
|
|
1256
1638
|
end
|
|
@@ -1259,27 +1641,6 @@ module MU
|
|
|
1259
1641
|
hosts << "#{MU.my_private_ip}/32" if MU.my_private_ip
|
|
1260
1642
|
hosts << "#{MU.mu_public_ip}/32" if MU.mu_public_ip
|
|
1261
1643
|
hosts << "#{admin_ip}/32" if admin_ip
|
|
1262
|
-
hosts.uniq!
|
|
1263
|
-
name = "admin"
|
|
1264
|
-
name += credentials.to_s if credentials
|
|
1265
|
-
realvpc = nil
|
|
1266
|
-
|
|
1267
|
-
if vpc
|
|
1268
|
-
realvpc = {}
|
|
1269
|
-
realvpc['vpc_id'] = vpc['vpc_id'] if !vpc['vpc_id'].nil?
|
|
1270
|
-
realvpc['vpc_name'] = vpc['vpc_name'] if !vpc['vpc_name'].nil?
|
|
1271
|
-
realvpc['deploy_id'] = vpc['deploy_id'] if !vpc['deploy_id'].nil?
|
|
1272
|
-
if !realvpc['vpc_id'].nil? and !realvpc['vpc_id'].empty?
|
|
1273
|
-
# Stupid kludge for Google cloud_ids which are sometimes URLs and
|
|
1274
|
-
# sometimes not. Requirements are inconsistent from scenario to
|
|
1275
|
-
# scenario.
|
|
1276
|
-
name = name + "-" + realvpc['vpc_id'].gsub(/.*\//, "")
|
|
1277
|
-
realvpc['vpc_id'] = getTail("vpc_id", value: realvpc['vpc_id'], prettyname: "Admin Firewall Ruleset #{name} Target VPC", cloudtype: "AWS::EC2::VPC::Id") if realvpc["vpc_id"].is_a?(String)
|
|
1278
|
-
elsif !realvpc['vpc_name'].nil?
|
|
1279
|
-
name = name + "-" + realvpc['vpc_name']
|
|
1280
|
-
end
|
|
1281
|
-
end
|
|
1282
|
-
|
|
1283
1644
|
hosts.uniq!
|
|
1284
1645
|
|
|
1285
1646
|
rules = []
|
|
@@ -1296,9 +1657,43 @@ module MU
|
|
|
1296
1657
|
]
|
|
1297
1658
|
end
|
|
1298
1659
|
|
|
1660
|
+
resclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get("FirewallRule")
|
|
1661
|
+
|
|
1662
|
+
if rules_only
|
|
1663
|
+
return rules
|
|
1664
|
+
end
|
|
1665
|
+
|
|
1666
|
+
name = "admin"
|
|
1667
|
+
name += credentials.to_s if credentials
|
|
1668
|
+
realvpc = nil
|
|
1669
|
+
if vpc
|
|
1670
|
+
realvpc = {}
|
|
1671
|
+
['vpc_name', 'vpc_id'].each { |p|
|
|
1672
|
+
if vpc[p]
|
|
1673
|
+
vpc[p.sub(/^vpc_/, '')] = vpc[p]
|
|
1674
|
+
vpc.delete(p)
|
|
1675
|
+
end
|
|
1676
|
+
}
|
|
1677
|
+
['cloud', 'id', 'name', 'deploy_id', 'habitat', 'credentials'].each { |field|
|
|
1678
|
+
realvpc[field] = vpc[field] if !vpc[field].nil?
|
|
1679
|
+
}
|
|
1680
|
+
if !realvpc['id'].nil? and !realvpc['id'].empty?
|
|
1681
|
+
# Stupid kludge for Google cloud_ids which are sometimes URLs and
|
|
1682
|
+
# sometimes not. Requirements are inconsistent from scenario to
|
|
1683
|
+
# scenario.
|
|
1684
|
+
name = name + "-" + realvpc['id'].gsub(/.*\//, "")
|
|
1685
|
+
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)
|
|
1686
|
+
elsif !realvpc['name'].nil?
|
|
1687
|
+
name = name + "-" + realvpc['name']
|
|
1688
|
+
end
|
|
1689
|
+
end
|
|
1690
|
+
|
|
1691
|
+
|
|
1299
1692
|
acl = {"name" => name, "rules" => rules, "vpc" => realvpc, "cloud" => cloud, "admin" => true, "credentials" => credentials }
|
|
1300
1693
|
acl.delete("vpc") if !acl["vpc"]
|
|
1301
|
-
|
|
1694
|
+
if !resclass.isGlobal? and !region.nil? and !region.empty?
|
|
1695
|
+
acl["region"] = region
|
|
1696
|
+
end
|
|
1302
1697
|
@admin_firewall_rules << acl if !@admin_firewall_rules.include?(acl)
|
|
1303
1698
|
return {"type" => "firewall_rule", "name" => name}
|
|
1304
1699
|
end
|
|
@@ -1476,33 +1871,57 @@ module MU
|
|
|
1476
1871
|
binding
|
|
1477
1872
|
end
|
|
1478
1873
|
|
|
1479
|
-
def
|
|
1874
|
+
def applySchemaDefaults(conf_chunk = config, schema_chunk = schema, depth = 0, siblings = nil, type: nil)
|
|
1480
1875
|
return if schema_chunk.nil?
|
|
1481
1876
|
|
|
1482
1877
|
if conf_chunk != nil and schema_chunk["properties"].kind_of?(Hash) and conf_chunk.is_a?(Hash)
|
|
1878
|
+
|
|
1483
1879
|
if schema_chunk["properties"]["creation_style"].nil? or
|
|
1484
1880
|
schema_chunk["properties"]["creation_style"] != "existing"
|
|
1485
1881
|
schema_chunk["properties"].each_pair { |key, subschema|
|
|
1486
|
-
|
|
1487
|
-
|
|
1882
|
+
shortclass = if conf_chunk[key]
|
|
1883
|
+
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(key)
|
|
1884
|
+
shortclass
|
|
1885
|
+
else
|
|
1886
|
+
nil
|
|
1887
|
+
end
|
|
1888
|
+
|
|
1889
|
+
new_val = applySchemaDefaults(conf_chunk[key], subschema, depth+1, conf_chunk, type: shortclass).dup
|
|
1890
|
+
|
|
1891
|
+
conf_chunk[key] = Marshal.load(Marshal.dump(new_val)) if !new_val.nil?
|
|
1488
1892
|
}
|
|
1489
1893
|
end
|
|
1490
1894
|
elsif schema_chunk["type"] == "array" and conf_chunk.kind_of?(Array)
|
|
1491
1895
|
conf_chunk.map! { |item|
|
|
1492
|
-
|
|
1896
|
+
# If we're working on a resource type, go get implementation-specific
|
|
1897
|
+
# schema information so that we set those defaults correctly.
|
|
1898
|
+
realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"]
|
|
1899
|
+
|
|
1900
|
+
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
|
|
1901
|
+
toplevel_required, cloudschema = cloudclass.schema(self)
|
|
1902
|
+
|
|
1903
|
+
newschema = schema_chunk["items"].dup
|
|
1904
|
+
newschema["properties"].merge!(cloudschema)
|
|
1905
|
+
newschema
|
|
1906
|
+
else
|
|
1907
|
+
schema_chunk["items"].dup
|
|
1908
|
+
end
|
|
1909
|
+
|
|
1910
|
+
applySchemaDefaults(item, realschema, depth+1, conf_chunk, type: type).dup
|
|
1493
1911
|
}
|
|
1494
1912
|
else
|
|
1495
1913
|
if conf_chunk.nil? and !schema_chunk["default_if"].nil? and !siblings.nil?
|
|
1496
1914
|
schema_chunk["default_if"].each { |cond|
|
|
1497
1915
|
if siblings[cond["key_is"]] == cond["value_is"]
|
|
1498
|
-
return cond["set"]
|
|
1916
|
+
return Marshal.load(Marshal.dump(cond["set"]))
|
|
1499
1917
|
end
|
|
1500
1918
|
}
|
|
1501
1919
|
end
|
|
1502
1920
|
if conf_chunk.nil? and schema_chunk["default"] != nil
|
|
1503
|
-
return schema_chunk["default"]
|
|
1921
|
+
return Marshal.load(Marshal.dump(schema_chunk["default"]))
|
|
1504
1922
|
end
|
|
1505
1923
|
end
|
|
1924
|
+
|
|
1506
1925
|
return conf_chunk
|
|
1507
1926
|
end
|
|
1508
1927
|
|
|
@@ -1512,48 +1931,46 @@ module MU
|
|
|
1512
1931
|
def self.check_dependencies(config)
|
|
1513
1932
|
ok = true
|
|
1514
1933
|
|
|
1515
|
-
config.
|
|
1516
|
-
if
|
|
1517
|
-
|
|
1518
|
-
if
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
end
|
|
1539
|
-
}
|
|
1540
|
-
end
|
|
1541
|
-
if !found
|
|
1542
|
-
MU.log "Missing dependency: #{type[0]}{#{resource['name']}} needs #{collection}{#{dependency['name']}}", MU::ERR, details: names_seen
|
|
1543
|
-
ok = false
|
|
1934
|
+
config.each_pair { |type, values|
|
|
1935
|
+
if values.instance_of?(Array)
|
|
1936
|
+
values.each { |resource|
|
|
1937
|
+
if resource.kind_of?(Hash) and !resource["dependencies"].nil?
|
|
1938
|
+
append = []
|
|
1939
|
+
delete = []
|
|
1940
|
+
resource["dependencies"].each { |dependency|
|
|
1941
|
+
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(dependency["type"])
|
|
1942
|
+
found = false
|
|
1943
|
+
names_seen = []
|
|
1944
|
+
if !config[cfg_plural].nil?
|
|
1945
|
+
config[cfg_plural].each { |service|
|
|
1946
|
+
names_seen << service["name"].to_s
|
|
1947
|
+
found = true if service["name"].to_s == dependency["name"].to_s
|
|
1948
|
+
if service["virtual_name"]
|
|
1949
|
+
names_seen << service["virtual_name"].to_s
|
|
1950
|
+
if service["virtual_name"].to_s == dependency["name"].to_s
|
|
1951
|
+
found = true
|
|
1952
|
+
append_me = dependency.dup
|
|
1953
|
+
append_me['name'] = service['name']
|
|
1954
|
+
append << append_me
|
|
1955
|
+
delete << dependency
|
|
1956
|
+
end
|
|
1544
1957
|
end
|
|
1545
1958
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
if delete.size > 0
|
|
1551
|
-
delete.each { |delete_me|
|
|
1552
|
-
resource["dependencies"].delete(delete_me)
|
|
1553
|
-
}
|
|
1554
|
-
end
|
|
1959
|
+
end
|
|
1960
|
+
if !found
|
|
1961
|
+
MU.log "Missing dependency: #{type}{#{resource['name']}} needs #{cfg_name}{#{dependency['name']}}", MU::ERR, details: names_seen
|
|
1962
|
+
ok = false
|
|
1555
1963
|
end
|
|
1556
1964
|
}
|
|
1965
|
+
if append.size > 0
|
|
1966
|
+
append.uniq!
|
|
1967
|
+
resource["dependencies"].concat(append)
|
|
1968
|
+
end
|
|
1969
|
+
if delete.size > 0
|
|
1970
|
+
delete.each { |delete_me|
|
|
1971
|
+
resource["dependencies"].delete(delete_me)
|
|
1972
|
+
}
|
|
1973
|
+
end
|
|
1557
1974
|
end
|
|
1558
1975
|
}
|
|
1559
1976
|
end
|
|
@@ -1622,23 +2039,39 @@ module MU
|
|
|
1622
2039
|
|
|
1623
2040
|
|
|
1624
2041
|
# Given a bare hash describing a resource, insert default values which can
|
|
1625
|
-
# be inherited from the
|
|
2042
|
+
# be inherited from its parent or from the root of the BoK.
|
|
1626
2043
|
# @param kitten [Hash]: A resource descriptor
|
|
1627
2044
|
# @param type [String]: The type of resource this is ("servers" etc)
|
|
1628
|
-
def
|
|
2045
|
+
def applyInheritedDefaults(kitten, type)
|
|
2046
|
+
kitten['cloud'] ||= @config['cloud']
|
|
1629
2047
|
kitten['cloud'] ||= MU::Config.defaultCloud
|
|
2048
|
+
|
|
1630
2049
|
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(kitten['cloud'])
|
|
1631
2050
|
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
1632
2051
|
resclass = Object.const_get("MU").const_get("Cloud").const_get(kitten['cloud']).const_get(shortclass)
|
|
1633
2052
|
|
|
1634
|
-
schema_fields = ["us_only", "scrub_mu_isms", "credentials"]
|
|
2053
|
+
schema_fields = ["us_only", "scrub_mu_isms", "credentials", "billing_acct"]
|
|
1635
2054
|
if !resclass.isGlobal?
|
|
2055
|
+
kitten['region'] ||= @config['region']
|
|
2056
|
+
kitten['region'] ||= cloudclass.myRegion(kitten['credentials'])
|
|
1636
2057
|
schema_fields << "region"
|
|
1637
2058
|
end
|
|
1638
2059
|
|
|
2060
|
+
kitten['credentials'] ||= @config['credentials']
|
|
2061
|
+
kitten['credentials'] ||= cloudclass.credConfig(name_only: true)
|
|
2062
|
+
|
|
2063
|
+
kitten['us_only'] ||= @config['us_only']
|
|
2064
|
+
kitten['us_only'] ||= false
|
|
2065
|
+
|
|
2066
|
+
kitten['scrub_mu_isms'] ||= @config['scrub_mu_isms']
|
|
2067
|
+
kitten['scrub_mu_isms'] ||= false
|
|
2068
|
+
|
|
1639
2069
|
if kitten['cloud'] == "Google"
|
|
1640
|
-
|
|
1641
|
-
|
|
2070
|
+
# TODO this should be cloud-generic (handle AWS accounts, Azure subscriptions)
|
|
2071
|
+
if resclass.canLiveIn.include?(:Habitat)
|
|
2072
|
+
kitten["project"] ||= MU::Cloud::Google.defaultProject(kitten['credentials'])
|
|
2073
|
+
schema_fields << "project"
|
|
2074
|
+
end
|
|
1642
2075
|
if kitten['region'].nil? and !kitten['#MU_CLOUDCLASS'].nil? and
|
|
1643
2076
|
!resclass.isGlobal? and
|
|
1644
2077
|
![MU::Cloud::VPC, MU::Cloud::FirewallRule].include?(kitten['#MU_CLOUDCLASS'])
|
|
@@ -1647,21 +2080,15 @@ module MU
|
|
|
1647
2080
|
end
|
|
1648
2081
|
kitten['region'] ||= MU::Cloud::Google.myRegion
|
|
1649
2082
|
end
|
|
1650
|
-
elsif kitten["cloud"] == "AWS" and !resclass.isGlobal?
|
|
2083
|
+
elsif kitten["cloud"] == "AWS" and !resclass.isGlobal? and !kitten['region']
|
|
1651
2084
|
if MU::Cloud::AWS.myRegion.nil?
|
|
1652
2085
|
raise ValidationError, "AWS resource declared without a region, but no default AWS region found"
|
|
1653
2086
|
end
|
|
1654
2087
|
kitten['region'] ||= MU::Cloud::AWS.myRegion
|
|
1655
2088
|
end
|
|
1656
2089
|
|
|
1657
|
-
kitten['us_only'] ||= @config['us_only']
|
|
1658
|
-
kitten['us_only'] ||= false
|
|
1659
2090
|
|
|
1660
|
-
kitten['
|
|
1661
|
-
kitten['scrub_mu_isms'] ||= false
|
|
1662
|
-
|
|
1663
|
-
kitten['credentials'] ||= @config['credentials']
|
|
1664
|
-
kitten['credentials'] ||= cloudclass.credConfig(name_only: true)
|
|
2091
|
+
kitten['billing_acct'] ||= @config['billing_acct'] if @config['billing_acct']
|
|
1665
2092
|
|
|
1666
2093
|
kitten["dependencies"] ||= []
|
|
1667
2094
|
|
|
@@ -1677,7 +2104,6 @@ module MU
|
|
|
1677
2104
|
|
|
1678
2105
|
def validate(config = @config)
|
|
1679
2106
|
ok = true
|
|
1680
|
-
plain_cfg = MU::Config.manxify(Marshal.load(Marshal.dump(config)))
|
|
1681
2107
|
|
|
1682
2108
|
count = 0
|
|
1683
2109
|
@kittens ||= {}
|
|
@@ -1687,7 +2113,7 @@ module MU
|
|
|
1687
2113
|
@kittens[type] = config[type]
|
|
1688
2114
|
@kittens[type] ||= []
|
|
1689
2115
|
@kittens[type].each { |k|
|
|
1690
|
-
|
|
2116
|
+
applyInheritedDefaults(k, type)
|
|
1691
2117
|
}
|
|
1692
2118
|
count = count + @kittens[type].size
|
|
1693
2119
|
}
|
|
@@ -1708,6 +2134,12 @@ module MU
|
|
|
1708
2134
|
acl = resolveIntraStackFirewallRefs(acl)
|
|
1709
2135
|
}
|
|
1710
2136
|
|
|
2137
|
+
# VPCs do complex things in their cloud-layer validation that other
|
|
2138
|
+
# resources tend to need, like subnet allocation, so hit them early.
|
|
2139
|
+
@kittens["vpcs"].each { |vpc|
|
|
2140
|
+
ok = false if !insertKitten(vpc, "vpcs")
|
|
2141
|
+
}
|
|
2142
|
+
|
|
1711
2143
|
# Make sure validation has been called for all on-the-fly generated
|
|
1712
2144
|
# resources.
|
|
1713
2145
|
validated_something_new = false
|
|
@@ -1715,9 +2147,10 @@ module MU
|
|
|
1715
2147
|
validated_something_new = false
|
|
1716
2148
|
types.each { |type|
|
|
1717
2149
|
@kittens[type].each { |descriptor|
|
|
1718
|
-
if !descriptor["#
|
|
2150
|
+
if !descriptor["#MU_VALIDATION_ATTEMPTED"]
|
|
1719
2151
|
validated_something_new = true
|
|
1720
2152
|
ok = false if !insertKitten(descriptor, type)
|
|
2153
|
+
descriptor["#MU_VALIDATION_ATTEMPTED"] = true
|
|
1721
2154
|
end
|
|
1722
2155
|
}
|
|
1723
2156
|
}
|
|
@@ -1918,7 +2351,6 @@ module MU
|
|
|
1918
2351
|
return docstring
|
|
1919
2352
|
end
|
|
1920
2353
|
|
|
1921
|
-
return nil
|
|
1922
2354
|
end
|
|
1923
2355
|
|
|
1924
2356
|
def self.dependencies_primitive
|
|
@@ -1969,6 +2401,57 @@ module MU
|
|
|
1969
2401
|
end
|
|
1970
2402
|
end
|
|
1971
2403
|
|
|
2404
|
+
# Load and validate the schema for an individual resource class, optionally
|
|
2405
|
+
# merging cloud-specific schema components.
|
|
2406
|
+
# @param type [String]: The resource type to load
|
|
2407
|
+
# @param cloud [String]: A specific cloud, whose implementation's schema of this resource we will merge
|
|
2408
|
+
# @return [Hash]
|
|
2409
|
+
def self.loadResourceSchema(type, cloud: nil)
|
|
2410
|
+
valid = true
|
|
2411
|
+
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
|
2412
|
+
schemaclass = Object.const_get("MU").const_get("Config").const_get(shortclass)
|
|
2413
|
+
|
|
2414
|
+
[:schema, :validate].each { |method|
|
|
2415
|
+
if !schemaclass.respond_to?(method)
|
|
2416
|
+
MU.log "MU::Config::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
|
|
2417
|
+
return [nil, false] if method == :schema
|
|
2418
|
+
valid = false
|
|
2419
|
+
end
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
schema = schemaclass.schema.dup
|
|
2423
|
+
|
|
2424
|
+
schema["properties"]["virtual_name"] = {
|
|
2425
|
+
"description" => "Internal use.",
|
|
2426
|
+
"type" => "string"
|
|
2427
|
+
}
|
|
2428
|
+
schema["properties"]["dependencies"] = MU::Config.dependencies_primitive
|
|
2429
|
+
schema["properties"]["cloud"] = MU::Config.cloud_primitive
|
|
2430
|
+
schema["properties"]["credentials"] = MU::Config.credentials_primitive
|
|
2431
|
+
schema["title"] = type.to_s
|
|
2432
|
+
|
|
2433
|
+
if cloud
|
|
2434
|
+
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
|
|
2435
|
+
|
|
2436
|
+
if cloudclass.respond_to?(:schema)
|
|
2437
|
+
reqd, cloudschema = cloudclass.schema
|
|
2438
|
+
cloudschema.each { |key, cfg|
|
|
2439
|
+
if schema["properties"][key]
|
|
2440
|
+
schemaMerge(schema["properties"][key], cfg, cloud)
|
|
2441
|
+
else
|
|
2442
|
+
schema["properties"][key] = cfg.dup
|
|
2443
|
+
end
|
|
2444
|
+
}
|
|
2445
|
+
else
|
|
2446
|
+
MU.log "MU::Cloud::#{cloud}::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
|
|
2447
|
+
valid = false
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
end
|
|
2451
|
+
|
|
2452
|
+
return [schema, valid]
|
|
2453
|
+
end
|
|
2454
|
+
|
|
1972
2455
|
@@schema = {
|
|
1973
2456
|
"$schema" => "http://json-schema.org/draft-04/schema#",
|
|
1974
2457
|
"title" => "MU Application",
|
|
@@ -1986,7 +2469,11 @@ module MU
|
|
|
1986
2469
|
},
|
|
1987
2470
|
"project" => {
|
|
1988
2471
|
"type" => "string",
|
|
1989
|
-
"description" => "GOOGLE
|
|
2472
|
+
"description" => "**GOOGLE ONLY**: The project into which to deploy resources"
|
|
2473
|
+
},
|
|
2474
|
+
"billing_acct" => {
|
|
2475
|
+
"type" => "string",
|
|
2476
|
+
"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.",
|
|
1990
2477
|
},
|
|
1991
2478
|
"region" => MU::Config.region_primitive,
|
|
1992
2479
|
"credentials" => MU::Config.credentials_primitive,
|
|
@@ -2084,28 +2571,16 @@ module MU
|
|
|
2084
2571
|
end
|
|
2085
2572
|
}
|
|
2086
2573
|
|
|
2574
|
+
|
|
2087
2575
|
MU::Cloud.resource_types.each_pair { |type, cfg|
|
|
2088
2576
|
begin
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
if !schemaclass.respond_to?(method)
|
|
2092
|
-
MU.log "MU::Config::#{type}.#{method.to_s} doesn't seem to be implemented", MU::ERR
|
|
2093
|
-
failed << type
|
|
2094
|
-
end
|
|
2095
|
-
}
|
|
2577
|
+
schema, valid = loadResourceSchema(type)
|
|
2578
|
+
failed << type if !valid
|
|
2096
2579
|
next if failed.include?(type)
|
|
2097
2580
|
@@schema["properties"][cfg[:cfg_plural]] = {
|
|
2098
2581
|
"type" => "array",
|
|
2099
|
-
"items" =>
|
|
2100
|
-
}
|
|
2101
|
-
@@schema["properties"][cfg[:cfg_plural]]["items"]["properties"]["virtual_name"] = {
|
|
2102
|
-
"description" => "Internal use.",
|
|
2103
|
-
"type" => "string"
|
|
2582
|
+
"items" => schema
|
|
2104
2583
|
}
|
|
2105
|
-
@@schema["properties"][cfg[:cfg_plural]]["items"]["properties"]["dependencies"] = MU::Config.dependencies_primitive
|
|
2106
|
-
@@schema["properties"][cfg[:cfg_plural]]["items"]["properties"]["cloud"] = MU::Config.cloud_primitive
|
|
2107
|
-
@@schema["properties"][cfg[:cfg_plural]]["items"]["properties"]["credentials"] = MU::Config.credentials_primitive
|
|
2108
|
-
@@schema["properties"][cfg[:cfg_plural]]["items"]["title"] = type.to_s
|
|
2109
2584
|
rescue NameError => e
|
|
2110
2585
|
failed << type
|
|
2111
2586
|
MU.log "Error loading #{type} schema from mu/config/#{cfg[:cfg_name]}", MU::ERR, details: "\t"+e.inspect+"\n\t"+e.backtrace[0]
|