cloud-mu 2.1.0beta → 3.0.0beta
Sign up to get free protection for your applications and to get access to all the features.
- 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]
|