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/mommacat.rb
CHANGED
@@ -53,21 +53,34 @@ module MU
|
|
53
53
|
if deploy_id.nil? or deploy_id.empty?
|
54
54
|
raise MuError, "Cannot fetch a deployment without a deploy_id"
|
55
55
|
end
|
56
|
+
|
56
57
|
# XXX this caching may be harmful, causing stale resource objects to stick
|
57
58
|
# around. Have we fixed this? Sort of. Bad entries seem to have no kittens,
|
58
59
|
# so force a reload if we see that. That's probably not the root problem.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
60
|
+
littercache = nil
|
61
|
+
begin
|
62
|
+
@@litter_semaphore.synchronize {
|
63
|
+
littercache = @@litters.dup
|
64
|
+
}
|
65
|
+
rescue ThreadError => e
|
66
|
+
# already locked by a parent caller and this is a read op, so this is ok
|
67
|
+
raise e if !e.message.match(/recursive locking/)
|
68
|
+
littercache = @@litters.dup
|
69
|
+
end
|
70
|
+
if !use_cache or littercache[deploy_id].nil?
|
71
|
+
newlitter = MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
|
72
|
+
# This, we have to synchronize, as it's a write
|
73
|
+
@@litter_semaphore.synchronize {
|
74
|
+
@@litters[deploy_id] ||= newlitter
|
75
|
+
}
|
76
|
+
elsif set_context_to_me
|
77
|
+
MU::MommaCat.setThreadContext(@@litters[deploy_id])
|
78
|
+
end
|
79
|
+
return @@litters[deploy_id]
|
68
80
|
# MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
|
69
81
|
end
|
70
82
|
|
83
|
+
attr_reader :initializing
|
71
84
|
attr_reader :public_key
|
72
85
|
attr_reader :deploy_secret
|
73
86
|
attr_reader :deployment
|
@@ -155,16 +168,20 @@ module MU
|
|
155
168
|
ssh_private_key: nil,
|
156
169
|
ssh_public_key: nil,
|
157
170
|
nocleanup: false,
|
171
|
+
appname: nil,
|
172
|
+
timestamp: nil,
|
158
173
|
set_context_to_me: true,
|
159
174
|
skip_resource_objects: false,
|
160
175
|
no_artifacts: false,
|
161
176
|
deployment_data: {},
|
177
|
+
delay_descriptor_load: false,
|
162
178
|
mu_user: Etc.getpwuid(Process.uid).name
|
163
179
|
)
|
164
180
|
if deploy_id.nil? or deploy_id.empty?
|
165
181
|
raise DeployInitializeError, "MommaCat objects must specify a deploy_id"
|
166
182
|
end
|
167
183
|
set_context_to_me = true if create
|
184
|
+
@initializing = true
|
168
185
|
|
169
186
|
@deploy_id = deploy_id
|
170
187
|
@mu_user = mu_user.dup
|
@@ -179,10 +196,11 @@ module MU
|
|
179
196
|
end
|
180
197
|
@kitten_semaphore = Mutex.new
|
181
198
|
@kittens = {}
|
182
|
-
@original_config = config
|
199
|
+
@original_config = MU::Config.manxify(config)
|
183
200
|
@nocleanup = nocleanup
|
184
201
|
@secret_semaphore = Mutex.new
|
185
202
|
@notify_semaphore = Mutex.new
|
203
|
+
@need_deploy_flush = false
|
186
204
|
@node_cert_semaphore = Mutex.new
|
187
205
|
@deployment = deployment_data
|
188
206
|
@deployment['mu_public_ip'] = MU.mu_public_ip
|
@@ -190,16 +208,21 @@ module MU
|
|
190
208
|
@public_key = nil
|
191
209
|
@secrets = Hash.new
|
192
210
|
@secrets['instance_secret'] = Hash.new
|
193
|
-
@environment = environment
|
194
211
|
@ssh_key_name = ssh_key_name
|
195
212
|
@ssh_private_key = ssh_private_key
|
196
213
|
@ssh_public_key = ssh_public_key
|
197
214
|
@clouds = {}
|
198
215
|
@seed = MU.seed # pass this in
|
199
216
|
@handle = MU.handle # pass this in
|
217
|
+
@appname = appname
|
218
|
+
@appname ||= @original_config['name'] if @original_config
|
219
|
+
@timestamp = timestamp
|
220
|
+
@environment = environment
|
221
|
+
|
200
222
|
if set_context_to_me
|
201
223
|
MU::MommaCat.setThreadContext(self)
|
202
224
|
end
|
225
|
+
|
203
226
|
if create and !@no_artifacts
|
204
227
|
if !Dir.exist?(MU.dataDir+"/deployments")
|
205
228
|
MU.log "Creating #{MU.dataDir}/deployments", MU::DEBUG
|
@@ -214,23 +237,27 @@ module MU
|
|
214
237
|
raise DeployInitializeError, "New MommaCat repository requires config hash"
|
215
238
|
end
|
216
239
|
credsets = {}
|
217
|
-
|
218
|
-
MU::Cloud.resource_types.
|
240
|
+
|
241
|
+
MU::Cloud.resource_types.values { |data|
|
219
242
|
if !@original_config[data[:cfg_plural]].nil? and @original_config[data[:cfg_plural]].size > 0
|
220
243
|
@original_config[data[:cfg_plural]].each { |resource|
|
244
|
+
|
221
245
|
credsets[resource['cloud']] ||= []
|
222
246
|
credsets[resource['cloud']] << resource['credentials']
|
223
247
|
@clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
|
224
248
|
@clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
|
249
|
+
|
225
250
|
}
|
226
251
|
end
|
227
252
|
}
|
253
|
+
|
228
254
|
@ssh_key_name, @ssh_private_key, @ssh_public_key = self.SSHKey
|
229
255
|
if !File.exist?(deploy_dir+"/private_key")
|
230
256
|
@private_key, @public_key = createDeployKey
|
231
257
|
end
|
232
258
|
MU.log "Creating deploy secret for #{MU.deploy_id}"
|
233
259
|
@deploy_secret = Password.random(256)
|
260
|
+
|
234
261
|
if !@original_config['scrub_mu_isms']
|
235
262
|
credsets.each_pair { |cloud, creds|
|
236
263
|
creds.uniq!
|
@@ -243,9 +270,14 @@ module MU
|
|
243
270
|
if set_context_to_me
|
244
271
|
MU::MommaCat.setThreadContext(self)
|
245
272
|
end
|
273
|
+
|
246
274
|
save!
|
275
|
+
|
247
276
|
end
|
248
277
|
|
278
|
+
@appname ||= MU.appname
|
279
|
+
@timestamp ||= MU.timestamp
|
280
|
+
@environment ||= MU.environment
|
249
281
|
|
250
282
|
loadDeploy(set_context_to_me: set_context_to_me)
|
251
283
|
if !deploy_secret.nil?
|
@@ -255,13 +287,19 @@ module MU
|
|
255
287
|
end
|
256
288
|
|
257
289
|
|
290
|
+
@@litter_semaphore.synchronize {
|
291
|
+
@@litters[@deploy_id] ||= self
|
292
|
+
}
|
293
|
+
|
258
294
|
# Initialize a MU::Cloud object for each resource belonging to this
|
259
295
|
# deploy, IF it already exists, which is to say if we're loading an
|
260
296
|
# existing deploy instead of creating a new one.
|
261
297
|
if !create and @deployment and @original_config and !skip_resource_objects
|
298
|
+
|
262
299
|
MU::Cloud.resource_types.each_pair { |res_type, attrs|
|
263
300
|
type = attrs[:cfg_plural]
|
264
301
|
if @deployment.has_key?(type)
|
302
|
+
|
265
303
|
@deployment[type].each_pair { |res_name, data|
|
266
304
|
orig_cfg = nil
|
267
305
|
if @original_config.has_key?(type)
|
@@ -284,16 +322,23 @@ module MU
|
|
284
322
|
end
|
285
323
|
}
|
286
324
|
end
|
325
|
+
|
287
326
|
if orig_cfg.nil?
|
288
327
|
MU.log "Failed to locate original config for #{attrs[:cfg_name]} #{res_name} in #{@deploy_id}", MU::WARN if !["firewall_rules", "databases", "storage_pools", "cache_clusters", "alarms"].include?(type) # XXX shaddap
|
289
328
|
next
|
290
329
|
end
|
330
|
+
|
331
|
+
if orig_cfg['vpc']
|
332
|
+
ref = MU::Config::Ref.get(orig_cfg['vpc'])
|
333
|
+
orig_cfg['vpc']['id'] = ref if ref.kitten
|
334
|
+
end
|
335
|
+
|
291
336
|
begin
|
292
337
|
# Load up MU::Cloud objects for all our kittens in this deploy
|
293
338
|
orig_cfg['environment'] = @environment # not always set in old deploys
|
294
339
|
if attrs[:has_multiples]
|
295
|
-
data.
|
296
|
-
attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name)
|
340
|
+
data.keys.each { |mu_name|
|
341
|
+
attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name, delay_descriptor_load: delay_descriptor_load)
|
297
342
|
}
|
298
343
|
else
|
299
344
|
# XXX hack for old deployments, this can go away some day
|
@@ -315,19 +360,99 @@ module MU
|
|
315
360
|
attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id'])
|
316
361
|
end
|
317
362
|
rescue Exception => e
|
318
|
-
|
363
|
+
if e.class != MU::Cloud::MuCloudResourceNotImplemented
|
364
|
+
MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
|
365
|
+
end
|
319
366
|
end
|
320
367
|
}
|
368
|
+
|
321
369
|
end
|
322
370
|
}
|
323
371
|
end
|
324
372
|
|
373
|
+
@initializing = false
|
374
|
+
|
325
375
|
# XXX this .owned? method may get changed by the Ruby maintainers
|
326
376
|
# if !@@litter_semaphore.owned?
|
327
|
-
#
|
328
|
-
|
329
|
-
#
|
330
|
-
|
377
|
+
end # end of initialize()
|
378
|
+
|
379
|
+
# List all the cloud providers declared by resources in our deploy.
|
380
|
+
def cloudsUsed
|
381
|
+
seen = []
|
382
|
+
seen << @original_config['cloud'] if @original_config['cloud']
|
383
|
+
MU::Cloud.resource_types.values.each { |attrs|
|
384
|
+
type = attrs[:cfg_plural]
|
385
|
+
if @original_config.has_key?(type)
|
386
|
+
@original_config[type].each { |resource|
|
387
|
+
seen << resource['cloud'] if resource['cloud']
|
388
|
+
}
|
389
|
+
end
|
390
|
+
}
|
391
|
+
seen.uniq
|
392
|
+
end
|
393
|
+
|
394
|
+
# Assay this deployment for a list of credentials (from mu.yaml) which are
|
395
|
+
# used. Our Cleanup module can leverage this to skip unnecessary checks.
|
396
|
+
# @return [Array<String>]
|
397
|
+
def credsUsed
|
398
|
+
return [] if !@original_config
|
399
|
+
seen = []
|
400
|
+
# clouds = []
|
401
|
+
seen << @original_config['credentials'] if @original_config['credentials']
|
402
|
+
# defaultcloud = @original_config['cloud']
|
403
|
+
MU::Cloud.resource_types.values.each { |attrs|
|
404
|
+
type = attrs[:cfg_plural]
|
405
|
+
if @original_config.has_key?(type)
|
406
|
+
@original_config[type].each { |resource|
|
407
|
+
if resource['credentials']
|
408
|
+
seen << resource['credentials']
|
409
|
+
else
|
410
|
+
cloudclass = if @original_config['cloud']
|
411
|
+
Object.const_get("MU").const_get("Cloud").const_get(@original_config['cloud'])
|
412
|
+
else
|
413
|
+
Object.const_get("MU").const_get("Cloud").const_get(MU::Config.defaultCloud)
|
414
|
+
end
|
415
|
+
seen << cloudclass.credConfig(name_only: true)
|
416
|
+
end
|
417
|
+
}
|
418
|
+
end
|
419
|
+
}
|
420
|
+
# XXX insert default for each cloud provider if not explicitly seen
|
421
|
+
seen.uniq
|
422
|
+
end
|
423
|
+
|
424
|
+
# List the regions used by each resource in our deploy. This will just be
|
425
|
+
# a flat list of strings with no regard to which region belongs with what
|
426
|
+
# cloud provider- things mostly use this as a lookup table so they can
|
427
|
+
# safely skip unnecessary regions when creating/cleaning deploy artifacts.
|
428
|
+
# @return [Array<String>]
|
429
|
+
def regionsUsed
|
430
|
+
return [] if !@original_config
|
431
|
+
regions = []
|
432
|
+
regions << @original_config['region'] if @original_config['region']
|
433
|
+
MU::Cloud.resource_types.each_pair { |res_type, attrs|
|
434
|
+
type = attrs[:cfg_plural]
|
435
|
+
if @original_config.has_key?(type)
|
436
|
+
@original_config[type].each { |resource|
|
437
|
+
if resource['cloud']
|
438
|
+
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud'])
|
439
|
+
resclass = Object.const_get("MU").const_get("Cloud").const_get(resource['cloud']).const_get(res_type.to_s)
|
440
|
+
if resclass.isGlobal?
|
441
|
+
regions.concat(cloudclass.listRegions)
|
442
|
+
next
|
443
|
+
elsif !resource['region']
|
444
|
+
regions << cloudclass.myRegion
|
445
|
+
end
|
446
|
+
end
|
447
|
+
if resource['region']
|
448
|
+
regions << resource['region'] if resource['region']
|
449
|
+
else
|
450
|
+
end
|
451
|
+
}
|
452
|
+
end
|
453
|
+
}
|
454
|
+
|
455
|
+
regions.uniq
|
331
456
|
end
|
332
457
|
|
333
458
|
# Tell us the number of first-class resources we've configured, optionally
|
@@ -341,13 +466,13 @@ module MU
|
|
341
466
|
return 0 if @original_config.nil?
|
342
467
|
if !types.nil? and types.size > 0
|
343
468
|
types.each { |type|
|
344
|
-
|
469
|
+
cfg_plural = MU::Cloud.getResourceNames(type)[2]
|
345
470
|
realtypes << cfg_plural
|
346
471
|
}
|
347
472
|
end
|
348
473
|
|
349
474
|
count = 0
|
350
|
-
MU::Cloud.resource_types.each { |
|
475
|
+
MU::Cloud.resource_types.values.each { |data|
|
351
476
|
next if @original_config[data[:cfg_plural]].nil?
|
352
477
|
next if realtypes.size > 0 and (!negate and !realtypes.include?(data[:cfg_plural]))
|
353
478
|
@original_config[data[:cfg_plural]].each { |resource|
|
@@ -365,13 +490,13 @@ module MU
|
|
365
490
|
raise MuError, "Nil arguments to removeKitten are not allowed"
|
366
491
|
end
|
367
492
|
@kitten_semaphore.synchronize {
|
368
|
-
MU::Cloud.resource_types.
|
493
|
+
MU::Cloud.resource_types.values.each { |attrs|
|
369
494
|
type = attrs[:cfg_plural]
|
370
495
|
next if !@kittens.has_key?(type)
|
371
496
|
tmplitter = @kittens[type].values.dup
|
372
497
|
tmplitter.each { |nodeclass, data|
|
373
498
|
if data.is_a?(Hash)
|
374
|
-
data.
|
499
|
+
data.keys.each { |mu_name|
|
375
500
|
if data == object
|
376
501
|
@kittens[type][nodeclass].delete(mu_name)
|
377
502
|
return
|
@@ -421,17 +546,19 @@ module MU
|
|
421
546
|
if !type or !name or !object or !object.mu_name
|
422
547
|
raise MuError, "Nil arguments to addKitten are not allowed (got type: #{type}, name: #{name}, and '#{object}' to add)"
|
423
548
|
end
|
424
|
-
|
425
|
-
type =
|
549
|
+
|
550
|
+
_shortclass, _cfg_name, type, _classname, attrs = MU::Cloud.getResourceNames(type)
|
426
551
|
has_multiples = attrs[:has_multiples]
|
552
|
+
object.intoDeploy(self)
|
427
553
|
|
428
554
|
@kitten_semaphore.synchronize {
|
429
555
|
@kittens[type] ||= {}
|
556
|
+
@kittens[type][object.habitat] ||= {}
|
430
557
|
if has_multiples
|
431
|
-
@kittens[type][name] ||= {}
|
432
|
-
@kittens[type][name][object.mu_name] = object
|
558
|
+
@kittens[type][object.habitat][name] ||= {}
|
559
|
+
@kittens[type][object.habitat][name][object.mu_name] = object
|
433
560
|
else
|
434
|
-
@kittens[type][name] = object
|
561
|
+
@kittens[type][object.habitat][name] = object
|
435
562
|
end
|
436
563
|
}
|
437
564
|
end
|
@@ -458,7 +585,7 @@ module MU
|
|
458
585
|
return false
|
459
586
|
end
|
460
587
|
rescue OpenSSL::PKey::RSAError => e
|
461
|
-
MU.log e.
|
588
|
+
MU.log "Error decrypting provided ciphertext using private key from #{deploy_dir}/private_key: #{e.message}", MU::ERR, details: ciphertext
|
462
589
|
return false
|
463
590
|
end
|
464
591
|
end
|
@@ -506,7 +633,7 @@ module MU
|
|
506
633
|
raise MuError, "Got no argument to MU::MommaCat.getResourceName"
|
507
634
|
end
|
508
635
|
if @appname.nil? or @environment.nil? or @timestamp.nil? or @seed.nil?
|
509
|
-
MU.log "Missing global deploy variables in thread #{Thread.current.object_id}, using bare name '#{name}' (appname: #{@appname}, environment: #{@environment}, timestamp: #{@timestamp}, seed: #{@seed}", MU::WARN, details: caller
|
636
|
+
MU.log "getResourceName: Missing global deploy variables in thread #{Thread.current.object_id}, using bare name '#{name}' (appname: #{@appname}, environment: #{@environment}, timestamp: #{@timestamp}, seed: #{@seed}, deploy_id: #{@deploy_id}", MU::WARN, details: caller
|
510
637
|
return name
|
511
638
|
end
|
512
639
|
need_unique_string = false if scrub_mu_isms
|
@@ -597,7 +724,7 @@ module MU
|
|
597
724
|
def saveNodeSecret(instance_id, raw_secret, type)
|
598
725
|
return if @no_artifacts
|
599
726
|
if instance_id.nil? or instance_id.empty? or raw_secret.nil? or raw_secret.empty? or type.nil? or type.empty?
|
600
|
-
raise SecretError, "saveNodeSecret requires instance_id, raw_secret, and type args"
|
727
|
+
raise SecretError, "saveNodeSecret requires instance_id (#{instance_id}), raw_secret (#{raw_secret}), and type (#{type}) args"
|
601
728
|
end
|
602
729
|
MU::MommaCat.lock("deployment-notification")
|
603
730
|
loadDeploy(true) # make sure we're not trampling deployment data
|
@@ -746,14 +873,14 @@ module MU
|
|
746
873
|
kitten.groom
|
747
874
|
rescue Exception => e
|
748
875
|
MU::MommaCat.unlockAll
|
749
|
-
if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.
|
876
|
+
if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exist?(deploy_dir+"/.cleanup."+cloud_id) and !File.exist?(deploy_dir+"/.cleanup")
|
750
877
|
MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
|
751
|
-
|
752
|
-
#
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
878
|
+
sendAdminSlack("Grooming FAILED for `#{kitten.mu_name}` with `#{e.message}` :crying_cat_face:", msg: e.backtrace.join("\n"))
|
879
|
+
sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
|
880
|
+
msg: e.inspect,
|
881
|
+
data: e.backtrace,
|
882
|
+
debug: true
|
883
|
+
)
|
757
884
|
raise e if reraise_fail
|
758
885
|
else
|
759
886
|
MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
|
@@ -761,7 +888,7 @@ module MU
|
|
761
888
|
return
|
762
889
|
end
|
763
890
|
|
764
|
-
if !@deployment['servers'].nil?
|
891
|
+
if !@deployment['servers'].nil? and !sync_wait
|
765
892
|
syncLitter(@deployment["servers"].keys, triggering_node: kitten)
|
766
893
|
end
|
767
894
|
MU::MommaCat.unlock(cloud_id+"-mommagroom")
|
@@ -770,12 +897,12 @@ module MU
|
|
770
897
|
end
|
771
898
|
MU::MommaCat.getLitter(MU.deploy_id, use_cache: false)
|
772
899
|
MU::MommaCat.syncMonitoringConfig(false)
|
773
|
-
MU::MommaCat.createStandardTags(cloud_id, region: kitten.config["region"])
|
774
900
|
MU.log "Grooming complete for '#{name}' mu_name on \"#{MU.handle}\" (#{MU.deploy_id})"
|
775
|
-
FileUtils.touch("/
|
901
|
+
FileUtils.touch(MU.dataDir+"/deployments/#{MU.deploy_id}/#{name}_done.txt")
|
776
902
|
MU::MommaCat.unlockAll
|
777
903
|
if first_groom
|
778
|
-
|
904
|
+
sendAdminSlack("Grooming complete for #{mu_name} :heart_eyes_cat:")
|
905
|
+
sendAdminMail("Grooming complete for '#{name}' (#{mu_name}) on deploy \"#{MU.handle}\" (#{MU.deploy_id})")
|
779
906
|
end
|
780
907
|
return
|
781
908
|
end
|
@@ -797,7 +924,7 @@ module MU
|
|
797
924
|
ssh_dir.chown(Etc.getpwnam(@mu_user).uid, Etc.getpwnam(@mu_user).gid)
|
798
925
|
end
|
799
926
|
end
|
800
|
-
if !File.
|
927
|
+
if !File.exist?("#{ssh_dir}/#{@ssh_key_name}")
|
801
928
|
MU.log "Generating SSH key #{@ssh_key_name}"
|
802
929
|
%x{/usr/bin/ssh-keygen -N "" -f #{ssh_dir}/#{@ssh_key_name}}
|
803
930
|
end
|
@@ -811,6 +938,7 @@ module MU
|
|
811
938
|
["servers", "server_pools", "container_clusters"].each { |type|
|
812
939
|
next if @original_config[type].nil?
|
813
940
|
@original_config[type].each { |descriptor|
|
941
|
+
next if descriptor['cloud'] != "AWS"
|
814
942
|
if descriptor['credentials']
|
815
943
|
creds_used << descriptor['credentials']
|
816
944
|
else
|
@@ -836,10 +964,9 @@ module MU
|
|
836
964
|
# in lock() or unlock(). We can't just wrap our iterator block in a
|
837
965
|
# semaphore here, because we're calling another method that uses the
|
838
966
|
# same semaphore.
|
839
|
-
lock_copy = nil
|
840
967
|
@lock_semaphore.synchronize {
|
841
968
|
delete_list = []
|
842
|
-
@locks[Thread.current.object_id].
|
969
|
+
@locks[Thread.current.object_id].keys.each { |id|
|
843
970
|
MU.log "Releasing lock on #{deploy_dir(MU.deploy_id)}/locks/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
|
844
971
|
begin
|
845
972
|
@locks[Thread.current.object_id][id].flock(File::LOCK_UN)
|
@@ -895,7 +1022,7 @@ module MU
|
|
895
1022
|
else
|
896
1023
|
@locks[Thread.current.object_id][id].flock(File::LOCK_EX)
|
897
1024
|
end
|
898
|
-
rescue IOError
|
1025
|
+
rescue IOError
|
899
1026
|
raise MU::BootstrapTempFail, "Interrupted waiting for lock on thread #{Thread.current.object_id}, probably just a node rebooting as part of a synchronous install"
|
900
1027
|
end
|
901
1028
|
MU.log "Lock on #{lockdir}/#{id}.lock on thread #{Thread.current.object_id} acquired", MU::DEBUG
|
@@ -940,11 +1067,11 @@ module MU
|
|
940
1067
|
path = File.expand_path(MU.dataDir+"/deployments")
|
941
1068
|
if Dir.exist?(path+"/"+deploy_id)
|
942
1069
|
unlockAll
|
943
|
-
MU.log "Purging #{path}/#{deploy_id}" if File.
|
1070
|
+
MU.log "Purging #{path}/#{deploy_id}" if File.exist?(path+"/"+deploy_id+"/deployment.json")
|
944
1071
|
|
945
1072
|
FileUtils.rm_rf(path+"/"+deploy_id, :secure => true)
|
946
1073
|
end
|
947
|
-
if File.
|
1074
|
+
if File.exist?(path+"/unique_ids")
|
948
1075
|
File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
|
949
1076
|
newlines = []
|
950
1077
|
f.flock(File::LOCK_EX)
|
@@ -973,10 +1100,9 @@ module MU
|
|
973
1100
|
MU::MommaCat.lock("clean-terminated-instances", false, true)
|
974
1101
|
MU.log "Checking for harvested instances in need of cleanup", MU::DEBUG
|
975
1102
|
parent_thread_id = Thread.current.object_id
|
976
|
-
cleanup_threads = []
|
977
1103
|
purged = 0
|
978
1104
|
MU::MommaCat.listDeploys.each { |deploy_id|
|
979
|
-
next if File.
|
1105
|
+
next if File.exist?(deploy_dir(deploy_id)+"/.cleanup")
|
980
1106
|
MU.log "Checking for dead wood in #{deploy_id}", MU::DEBUG
|
981
1107
|
@cleanup_threads << Thread.new {
|
982
1108
|
MU.dupGlobals(parent_thread_id)
|
@@ -984,32 +1110,35 @@ module MU
|
|
984
1110
|
deploy = MU::MommaCat.getLitter(deploy_id, set_context_to_me: true, use_cache: false)
|
985
1111
|
purged_this_deploy = 0
|
986
1112
|
if deploy.kittens.has_key?("servers")
|
987
|
-
deploy.kittens["servers"].
|
988
|
-
|
989
|
-
|
990
|
-
server
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1113
|
+
deploy.kittens["servers"].values.each { |nodeclasses|
|
1114
|
+
nodeclasses.each_pair { |nodeclass, servers|
|
1115
|
+
deletia = []
|
1116
|
+
servers.each_pair { |mu_name, server|
|
1117
|
+
server.describe
|
1118
|
+
if !server.cloud_id
|
1119
|
+
MU.log "Checking for presence of #{mu_name}, but unable to fetch its cloud_id", MU::WARN, details: server
|
1120
|
+
elsif !server.active?
|
1121
|
+
next if File.exist?(deploy_dir(deploy_id)+"/.cleanup-"+server.cloud_id)
|
1122
|
+
deletia << mu_name
|
1123
|
+
MU.log "Cleaning up metadata for #{server} (#{nodeclass}), formerly #{server.cloud_id}, which appears to have been terminated", MU::NOTICE
|
1124
|
+
begin
|
1125
|
+
server.destroy
|
1126
|
+
deploy.sendAdminMail("Retired metadata for terminated node #{mu_name}")
|
1127
|
+
deploy.sendAdminSlack("Retired metadata for terminated node `#{mu_name}`")
|
1128
|
+
rescue Exception => e
|
1129
|
+
MU.log "Saw #{e.message} while retiring #{mu_name}", MU::ERR, details: e.backtrace
|
1130
|
+
next
|
1131
|
+
end
|
1132
|
+
MU.log "Cleanup of metadata for #{server} (#{nodeclass}), formerly #{server.cloud_id} complete", MU::NOTICE
|
1133
|
+
purged = purged + 1
|
1134
|
+
purged_this_deploy = purged_this_deploy + 1
|
1003
1135
|
end
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1136
|
+
}
|
1137
|
+
if purged_this_deploy > 0
|
1138
|
+
# XXX some kind of filter (obey sync_siblings on nodes' configs)
|
1139
|
+
deploy.syncLitter(servers.keys)
|
1007
1140
|
end
|
1008
1141
|
}
|
1009
|
-
if purged_this_deploy > 0
|
1010
|
-
# XXX some kind of filter (obey sync_siblings on nodes' configs)
|
1011
|
-
deploy.syncLitter(servers.keys)
|
1012
|
-
end
|
1013
1142
|
}
|
1014
1143
|
end
|
1015
1144
|
MU.purgeGlobals
|
@@ -1029,6 +1158,7 @@ module MU
|
|
1029
1158
|
MU::MommaCat.unlock("clean-terminated-instances", true)
|
1030
1159
|
end
|
1031
1160
|
|
1161
|
+
@@dummy_cache = {}
|
1032
1162
|
|
1033
1163
|
# Locate a resource that's either a member of another deployment, or of no
|
1034
1164
|
# deployment at all, and return a {MU::Cloud} object for it.
|
@@ -1045,7 +1175,8 @@ module MU
|
|
1045
1175
|
# @param dummy_ok [Boolean]: Permit return of a faked {MU::Cloud} object if we don't have enough information to identify a real live one.
|
1046
1176
|
# @param flags [Hash]: Other cloud or resource type specific options to pass to that resource's find() method
|
1047
1177
|
# @return [Array<MU::Cloud>]
|
1048
|
-
def self.findStray(
|
1178
|
+
def self.findStray(
|
1179
|
+
cloud,
|
1049
1180
|
type,
|
1050
1181
|
deploy_id: nil,
|
1051
1182
|
name: nil,
|
@@ -1058,18 +1189,31 @@ module MU
|
|
1058
1189
|
allow_multi: false,
|
1059
1190
|
calling_deploy: MU.mommacat,
|
1060
1191
|
flags: {},
|
1192
|
+
habitats: [],
|
1061
1193
|
dummy_ok: false,
|
1062
1194
|
debug: false
|
1063
|
-
)
|
1195
|
+
)
|
1196
|
+
start = Time.now
|
1197
|
+
callstr = "findStray(cloud: #{cloud}, type: #{type}, deploy_id: #{deploy_id}, calling_deploy: #{calling_deploy.deploy_id if !calling_deploy.nil?}, name: #{name}, cloud_id: #{cloud_id}, tag_key: #{tag_key}, tag_value: #{tag_value}, credentials: #{credentials}, habitats: #{habitats ? habitats.to_s : "[]"}, dummy_ok: #{dummy_ok.to_s}, flags: #{flags.to_s}) from #{caller[0]}"
|
1198
|
+
callstack = caller.dup
|
1199
|
+
|
1064
1200
|
return nil if cloud == "CloudFormation" and !cloud_id.nil?
|
1201
|
+
shortclass, _cfg_name, cfg_plural, classname, _attrs = MU::Cloud.getResourceNames(type)
|
1202
|
+
if !MU::Cloud.supportedClouds.include?(cloud) or shortclass.nil?
|
1203
|
+
MU.log "findStray was called with bogus cloud argument '#{cloud}'", MU::WARN, details: callstr
|
1204
|
+
return nil
|
1205
|
+
end
|
1206
|
+
|
1065
1207
|
begin
|
1208
|
+
# TODO this is dumb as hell, clean this up.. and while we're at it
|
1209
|
+
# .dup everything so we don't mangle referenced values from the caller
|
1066
1210
|
deploy_id = deploy_id.to_s if deploy_id.class.to_s == "MU::Config::Tail"
|
1067
1211
|
name = name.to_s if name.class.to_s == "MU::Config::Tail"
|
1068
1212
|
cloud_id = cloud_id.to_s if !cloud_id.nil?
|
1069
1213
|
mu_name = mu_name.to_s if mu_name.class.to_s == "MU::Config::Tail"
|
1070
1214
|
tag_key = tag_key.to_s if tag_key.class.to_s == "MU::Config::Tail"
|
1071
1215
|
tag_value = tag_value.to_s if tag_value.class.to_s == "MU::Config::Tail"
|
1072
|
-
|
1216
|
+
type = cfg_plural
|
1073
1217
|
resourceclass = MU::Cloud.loadCloudType(cloud, shortclass)
|
1074
1218
|
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
1075
1219
|
|
@@ -1097,11 +1241,11 @@ module MU
|
|
1097
1241
|
end
|
1098
1242
|
loglevel = debug ? MU::NOTICE : MU::DEBUG
|
1099
1243
|
|
1100
|
-
MU.log
|
1244
|
+
MU.log callstr, loglevel, details: caller
|
1101
1245
|
|
1102
1246
|
# See if the thing we're looking for is a member of the deploy that's
|
1103
1247
|
# asking after it.
|
1104
|
-
if !deploy_id.nil? and !calling_deploy.nil? and
|
1248
|
+
if !deploy_id.nil? and !calling_deploy.nil? and
|
1105
1249
|
calling_deploy.deploy_id == deploy_id and (!name.nil? or !mu_name.nil?)
|
1106
1250
|
handle = calling_deploy.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id, credentials: credentials)
|
1107
1251
|
return [handle] if !handle.nil?
|
@@ -1109,23 +1253,61 @@ module MU
|
|
1109
1253
|
|
1110
1254
|
kittens = {}
|
1111
1255
|
# Search our other deploys for matching resources
|
1112
|
-
if (deploy_id or name or mu_name or cloud_id)
|
1256
|
+
if (deploy_id or name or mu_name or cloud_id)
|
1257
|
+
MU.log "findStray: searching my deployments (#{cfg_plural}, name: #{name}, deploy_id: #{deploy_id}, mu_name: #{mu_name}) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1258
|
+
|
1259
|
+
# Check our in-memory cache of live deploys before resorting to
|
1260
|
+
# metadata
|
1261
|
+
littercache = nil
|
1262
|
+
# Sometimes we're called inside a locked thread, sometimes not. Deal
|
1263
|
+
# with locking gracefully.
|
1264
|
+
begin
|
1265
|
+
@@litter_semaphore.synchronize {
|
1266
|
+
littercache = @@litters.dup
|
1267
|
+
}
|
1268
|
+
rescue ThreadError => e
|
1269
|
+
raise e if !e.message.match(/recursive locking/)
|
1270
|
+
littercache = @@litters.dup
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
littercache.each_pair { |cur_deploy, momma|
|
1274
|
+
next if deploy_id and deploy_id != cur_deploy
|
1275
|
+
|
1276
|
+
straykitten = momma.findLitterMate(type: type, cloud_id: cloud_id, name: name, mu_name: mu_name, credentials: credentials, created_only: true)
|
1277
|
+
if straykitten
|
1278
|
+
MU.log "Found matching kitten #{straykitten.mu_name} in-memory - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1279
|
+
# Peace out if we found the exact resource we want
|
1280
|
+
if cloud_id and straykitten.cloud_id.to_s == cloud_id.to_s
|
1281
|
+
return [straykitten]
|
1282
|
+
elsif mu_name and straykitten.mu_name == mu_name
|
1283
|
+
return [straykitten]
|
1284
|
+
else
|
1285
|
+
kittens[straykitten.cloud_id] ||= straykitten
|
1286
|
+
end
|
1287
|
+
end
|
1288
|
+
}
|
1289
|
+
|
1113
1290
|
mu_descs = MU::MommaCat.getResourceMetadata(cfg_plural, name: name, deploy_id: deploy_id, mu_name: mu_name)
|
1291
|
+
MU.log "findStray: #{mu_descs.size.to_s} deploys had matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1114
1292
|
|
1115
|
-
mu_descs.each_pair { |
|
1116
|
-
MU.log "findStray: #{
|
1293
|
+
mu_descs.each_pair { |cur_deploy_id, matches|
|
1294
|
+
MU.log "findStray: #{cur_deploy_id} had #{matches.size.to_s} initial matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1117
1295
|
next if matches.nil? or matches.size == 0
|
1118
|
-
momma = MU::MommaCat.getLitter(deploy_id)
|
1119
|
-
straykitten = nil
|
1120
1296
|
|
1297
|
+
momma = MU::MommaCat.getLitter(cur_deploy_id)
|
1298
|
+
|
1299
|
+
straykitten = nil
|
1121
1300
|
|
1122
1301
|
# If we found exactly one match in this deploy, use its metadata to
|
1123
1302
|
# guess at resource names we weren't told.
|
1124
|
-
if matches.size
|
1303
|
+
if matches.size > 1 and cloud_id
|
1304
|
+
MU.log "findStray: attempting to narrow down multiple matches with cloud_id #{cloud_id} - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1305
|
+
straykitten = momma.findLitterMate(type: type, cloud_id: cloud_id, credentials: credentials, created_only: true)
|
1306
|
+
elsif matches.size == 1 and name.nil? and mu_name.nil?
|
1125
1307
|
if cloud_id.nil?
|
1126
1308
|
straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: matches.first["cloud_id"], credentials: credentials)
|
1127
1309
|
else
|
1128
|
-
MU.log "findStray:
|
1310
|
+
MU.log "findStray: fetching single match with cloud_id #{cloud_id} - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1129
1311
|
straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: cloud_id, credentials: credentials)
|
1130
1312
|
end
|
1131
1313
|
# elsif !flags.nil? and !flags.empty? # XXX eh, maybe later
|
@@ -1146,16 +1328,17 @@ module MU
|
|
1146
1328
|
end
|
1147
1329
|
|
1148
1330
|
next if straykitten.nil?
|
1331
|
+
straykitten.intoDeploy(momma)
|
1149
1332
|
|
1150
1333
|
if straykitten.cloud_id.nil?
|
1151
1334
|
MU.log "findStray: kitten #{straykitten.mu_name} came back with nil cloud_id", MU::WARN
|
1152
1335
|
next
|
1153
1336
|
end
|
1154
1337
|
|
1155
|
-
kittens[straykitten.cloud_id]
|
1338
|
+
kittens[straykitten.cloud_id] ||= straykitten
|
1156
1339
|
|
1157
1340
|
# Peace out if we found the exact resource we want
|
1158
|
-
if cloud_id and straykitten.cloud_id == cloud_id
|
1341
|
+
if cloud_id and straykitten.cloud_id.to_s == cloud_id.to_s
|
1159
1342
|
return [straykitten]
|
1160
1343
|
# ...or if we've validated our one possible match
|
1161
1344
|
elsif !cloud_id and mu_descs.size == 1 and matches.size == 1
|
@@ -1186,87 +1369,208 @@ module MU
|
|
1186
1369
|
found_the_thing = false
|
1187
1370
|
credlist.each { |creds|
|
1188
1371
|
break if found_the_thing
|
1189
|
-
if cloud_id or (tag_key and tag_value) or !flags.empty?
|
1190
|
-
|
1191
|
-
begin
|
1192
|
-
|
1193
|
-
regions << region
|
1194
|
-
else
|
1195
|
-
regions = cloudclass.listRegions(credentials: creds)
|
1196
|
-
end
|
1372
|
+
if cloud_id or (tag_key and tag_value) or !flags.empty? or allow_multi
|
1373
|
+
|
1374
|
+
regions = begin
|
1375
|
+
region ? [region] : cloudclass.listRegions(credentials: creds)
|
1197
1376
|
rescue NoMethodError # Not all cloud providers have regions
|
1198
|
-
|
1377
|
+
[nil]
|
1199
1378
|
end
|
1200
1379
|
|
1201
|
-
|
1380
|
+
# ..not all resource types care about regions either
|
1381
|
+
if resourceclass.isGlobal?
|
1202
1382
|
regions = [nil]
|
1203
1383
|
end
|
1204
1384
|
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
if
|
1210
|
-
|
1211
|
-
break
|
1385
|
+
# Decide what habitats (accounts/projects/subscriptions) we'll
|
1386
|
+
# search, if applicable for this resource type.
|
1387
|
+
habitats ||= []
|
1388
|
+
begin
|
1389
|
+
if flags["project"] # backwards-compat
|
1390
|
+
habitats << flags["project"]
|
1212
1391
|
end
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1392
|
+
if habitats.empty?
|
1393
|
+
if resourceclass.canLiveIn.include?(nil)
|
1394
|
+
habitats << nil
|
1395
|
+
end
|
1396
|
+
if resourceclass.canLiveIn.include?(:Habitat)
|
1397
|
+
habitats.concat(cloudclass.listProjects(creds))
|
1398
|
+
end
|
1399
|
+
end
|
1400
|
+
rescue NoMethodError # we only expect this to work on Google atm
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
if habitats.empty?
|
1404
|
+
habitats << nil
|
1405
|
+
end
|
1406
|
+
habitats.uniq!
|
1407
|
+
|
1408
|
+
habitat_threads = []
|
1409
|
+
desc_semaphore = Mutex.new
|
1410
|
+
|
1411
|
+
cloud_descs = {}
|
1412
|
+
habitats.each { |hab|
|
1413
|
+
begin
|
1414
|
+
habitat_threads.each { |t| t.join(0.1) }
|
1415
|
+
habitat_threads.reject! { |t| t.nil? or !t.status }
|
1416
|
+
sleep 1 if habitat_threads.size > 5
|
1417
|
+
end while habitat_threads.size > 5
|
1418
|
+
habitat_threads << Thread.new(hab) { |p|
|
1419
|
+
MU.log "findStray: Searching #{p} (#{habitat_threads.size.to_s} habitat threads running) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1420
|
+
cloud_descs[p] = {}
|
1421
|
+
region_threads = []
|
1422
|
+
regions.each { |reg| region_threads << Thread.new(reg) { |r|
|
1423
|
+
MU.log "findStray: Searching #{r} in #{p} (#{region_threads.size.to_s} region threads running) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1424
|
+
MU.log "findStray: calling #{classname}.find(cloud_id: #{cloud_id}, region: #{r}, tag_key: #{tag_key}, tag_value: #{tag_value}, flags: #{flags}, credentials: #{creds}, project: #{p}) - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1425
|
+
begin
|
1426
|
+
found = resourceclass.find(cloud_id: cloud_id, region: r, tag_key: tag_key, tag_value: tag_value, flags: flags, credentials: creds, habitat: p)
|
1427
|
+
MU.log "findStray: #{found ? found.size.to_s : "nil"} results - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1428
|
+
rescue Exception => e
|
1429
|
+
MU.log "#{e.class.name} THREW A FIND EXCEPTION "+e.message, MU::WARN, details: caller
|
1430
|
+
pp e.backtrace
|
1431
|
+
MU.log "#{callstr}", MU::WARN, details: callstack
|
1432
|
+
exit
|
1433
|
+
end
|
1434
|
+
if found
|
1435
|
+
desc_semaphore.synchronize {
|
1436
|
+
cloud_descs[p][r] = found
|
1437
|
+
}
|
1438
|
+
end
|
1439
|
+
# Stop if you found the thing by a specific cloud_id
|
1440
|
+
if cloud_id and found and !found.empty?
|
1441
|
+
found_the_thing = true
|
1442
|
+
Thread.exit
|
1443
|
+
end
|
1444
|
+
} }
|
1445
|
+
begin
|
1446
|
+
region_threads.each { |t| t.join(0.1) }
|
1447
|
+
region_threads.reject! { |t| t.nil? or !t.status }
|
1448
|
+
if region_threads.size > 0
|
1449
|
+
MU.log "#{region_threads.size.to_s} regions still running in #{p}", loglevel
|
1450
|
+
sleep 3
|
1223
1451
|
end
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1452
|
+
end while region_threads.size > 0
|
1453
|
+
}
|
1454
|
+
}
|
1455
|
+
begin
|
1456
|
+
habitat_threads.each { |t| t.join(0.1) }
|
1457
|
+
habitat_threads.reject! { |t| t.nil? or !t.status }
|
1458
|
+
if habitat_threads.size > 0
|
1459
|
+
MU.log "#{habitat_threads.size.to_s} habitats still running", loglevel
|
1460
|
+
sleep 3
|
1461
|
+
end
|
1462
|
+
end while habitat_threads.size > 0
|
1463
|
+
|
1464
|
+
habitat_threads = []
|
1465
|
+
habitats.each { |hab| habitat_threads << Thread.new(hab) { |p|
|
1466
|
+
region_threads = []
|
1467
|
+
regions.each { |reg| region_threads << Thread.new(reg) { |r|
|
1468
|
+
next if cloud_descs[p][r].nil?
|
1469
|
+
cloud_descs[p][r].each_pair { |kitten_cloud_id, descriptor|
|
1470
|
+
|
1471
|
+
# We already have a MU::Cloud object for this guy, use it
|
1472
|
+
if kittens.has_key?(kitten_cloud_id)
|
1473
|
+
desc_semaphore.synchronize {
|
1474
|
+
matches << kittens[kitten_cloud_id]
|
1475
|
+
}
|
1476
|
+
elsif kittens.size == 0
|
1227
1477
|
if !dummy_ok
|
1228
|
-
MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return", loglevel, details: caller
|
1229
1478
|
next
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
# If we don't have a MU::Cloud object, manufacture a dummy
|
1482
|
+
# one. Give it a fake name if we have to and have decided
|
1483
|
+
# that's ok. Wild inferences from the cloud descriptor are
|
1484
|
+
# ok to try here.
|
1485
|
+
use_name = if (name.nil? or name.empty?)
|
1486
|
+
if !dummy_ok
|
1487
|
+
nil
|
1488
|
+
elsif !mu_name.nil?
|
1489
|
+
mu_name
|
1235
1490
|
else
|
1236
|
-
|
1491
|
+
try = nil
|
1492
|
+
[:display_name, :name, (resourceclass.cfg_name+"_name").to_sym].each { |field|
|
1493
|
+
if descriptor.respond_to?(field) and descriptor.send(field).is_a?(String)
|
1494
|
+
try = descriptor.send(field)
|
1495
|
+
break
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
}
|
1499
|
+
try ||= if !tag_value.nil?
|
1500
|
+
tag_value
|
1501
|
+
else
|
1502
|
+
kitten_cloud_id
|
1503
|
+
end
|
1504
|
+
try
|
1237
1505
|
end
|
1506
|
+
else
|
1507
|
+
name
|
1238
1508
|
end
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
# belong with, use that, even if it's an ungroomed resource.
|
1248
|
-
if !calling_deploy.nil? and
|
1249
|
-
!calling_deploy.original_config.nil? and
|
1250
|
-
!calling_deploy.original_config[type+"s"].nil?
|
1251
|
-
calling_deploy.original_config[type+"s"].each { |s|
|
1252
|
-
if s["name"] == name
|
1253
|
-
cfg = s.dup
|
1254
|
-
break
|
1255
|
-
end
|
1509
|
+
if use_name.nil?
|
1510
|
+
MU.log "Found cloud provider data for #{cloud} #{type} #{kitten_cloud_id}, but without a name I can't manufacture a proper #{type} object to return - #{sprintf("%.2fs", (Time.now-start))}", loglevel, details: caller
|
1511
|
+
next
|
1512
|
+
end
|
1513
|
+
cfg = {
|
1514
|
+
"name" => use_name,
|
1515
|
+
"cloud" => cloud,
|
1516
|
+
"credentials" => creds
|
1256
1517
|
}
|
1518
|
+
if !r.nil? and !resourceclass.isGlobal?
|
1519
|
+
cfg["region"] = r
|
1520
|
+
end
|
1257
1521
|
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1522
|
+
if !p.nil? and resourceclass.canLiveIn.include?(:Habitat)
|
1523
|
+
cfg["project"] = p
|
1524
|
+
end
|
1525
|
+
# If we can at least find the config from the deploy this will
|
1526
|
+
# belong with, use that, even if it's an ungroomed resource.
|
1527
|
+
if !calling_deploy.nil? and
|
1528
|
+
!calling_deploy.original_config.nil? and
|
1529
|
+
!calling_deploy.original_config[type+"s"].nil?
|
1530
|
+
calling_deploy.original_config[type+"s"].each { |s|
|
1531
|
+
if s["name"] == use_name
|
1532
|
+
cfg = s.dup
|
1533
|
+
break
|
1534
|
+
end
|
1535
|
+
}
|
1536
|
+
|
1537
|
+
newkitten = resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
|
1538
|
+
desc_semaphore.synchronize {
|
1539
|
+
matches << newkitten
|
1540
|
+
}
|
1541
|
+
else
|
1542
|
+
if !@@dummy_cache[cfg_plural] or !@@dummy_cache[cfg_plural][cfg.to_s]
|
1543
|
+
MU.log "findStray: Generating dummy '#{resourceclass.to_s}' cloudobj with name: #{use_name}, cloud_id: #{kitten_cloud_id.to_s} - #{sprintf("%.2fs", (Time.now-start))}", loglevel, details: cfg
|
1544
|
+
resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
|
1545
|
+
desc_semaphore.synchronize {
|
1546
|
+
@@dummy_cache[cfg_plural] ||= {}
|
1547
|
+
@@dummy_cache[cfg_plural][cfg.to_s] = resourceclass.new(mu_name: use_name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s, from_cloud_desc: descriptor)
|
1548
|
+
MU.log "findStray: Finished generating dummy '#{resourceclass.to_s}' cloudobj - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1549
|
+
}
|
1550
|
+
end
|
1551
|
+
desc_semaphore.synchronize {
|
1552
|
+
matches << @@dummy_cache[cfg_plural][cfg.to_s]
|
1553
|
+
}
|
1554
|
+
end
|
1261
1555
|
end
|
1262
|
-
|
1556
|
+
}
|
1557
|
+
} }
|
1558
|
+
MU.log "findStray: tying up #{region_threads.size.to_s} region threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1559
|
+
region_threads.each { |t|
|
1560
|
+
t.join
|
1263
1561
|
}
|
1562
|
+
} }
|
1563
|
+
MU.log "findStray: tying up #{habitat_threads.size.to_s} habitat threads - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1564
|
+
habitat_threads.each { |t|
|
1565
|
+
t.join
|
1264
1566
|
}
|
1265
1567
|
end
|
1266
1568
|
}
|
1267
1569
|
rescue Exception => e
|
1268
1570
|
MU.log e.inspect, MU::ERR, details: e.backtrace
|
1269
1571
|
end
|
1572
|
+
MU.log "findStray: returning #{matches.size.to_s} matches - #{sprintf("%.2fs", (Time.now-start))}", loglevel
|
1573
|
+
|
1270
1574
|
matches
|
1271
1575
|
end
|
1272
1576
|
|
@@ -1278,19 +1582,47 @@ module MU
|
|
1278
1582
|
# @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
|
1279
1583
|
# @param return_all [Boolean]: Return a Hash of matching objects indexed by their mu_name, instead of a single match. Only valid for resource types where has_multiples is true.
|
1280
1584
|
# @return [MU::Cloud]
|
1281
|
-
def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil)
|
1585
|
+
def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false, credentials: nil, habitat: nil, debug: false, indent: "")
|
1282
1586
|
shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
|
1283
1587
|
type = cfg_plural
|
1284
1588
|
has_multiples = attrs[:has_multiples]
|
1285
1589
|
|
1590
|
+
loglevel = debug ? MU::NOTICE : MU::DEBUG
|
1591
|
+
|
1592
|
+
argstring = [:type, :name, :mu_name, :cloud_id, :created_only, :credentials, :habitat, :has_multiples].reject { |a|
|
1593
|
+
binding.local_variable_get(a).nil?
|
1594
|
+
}.map { |v|
|
1595
|
+
v.to_s+": "+binding.local_variable_get(v).to_s
|
1596
|
+
}.join(", ")
|
1597
|
+
|
1598
|
+
# Fun times: if we specified a habitat, which we may also have done by
|
1599
|
+
# its shorthand sibling name, let's... call ourselves first to make sure
|
1600
|
+
# we're fishing for the right thing.
|
1601
|
+
if habitat
|
1602
|
+
MU.log indent+"findLitterMate(#{argstring}): Attempting to resolve habitat name #{habitat}", loglevel
|
1603
|
+
realhabitat = findLitterMate(type: "habitat", name: habitat, debug: debug, credentials: credentials, indent: indent+" ")
|
1604
|
+
if realhabitat and realhabitat.mu_name
|
1605
|
+
MU.log indent+"findLitterMate: Resolved habitat name #{habitat} to #{realhabitat.mu_name}", loglevel, details: [realhabitat.mu_name, realhabitat.cloud_id, realhabitat.config.keys]
|
1606
|
+
habitat = realhabitat.cloud_id
|
1607
|
+
elsif debug
|
1608
|
+
MU.log indent+"findLitterMate(#{argstring}): Failed to resolve habitat name #{habitat}", MU::WARN
|
1609
|
+
end
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
|
1286
1613
|
@kitten_semaphore.synchronize {
|
1287
1614
|
if !@kittens.has_key?(type)
|
1615
|
+
if debug
|
1616
|
+
MU.log indent+"NO SUCH KEY #{type} findLitterMate(#{argstring})", MU::WARN, details: @kittens.keys
|
1617
|
+
end
|
1288
1618
|
return nil
|
1289
1619
|
end
|
1290
|
-
MU.log "findLitterMate(
|
1620
|
+
MU.log indent+"START findLitterMate(#{argstring}), caller: #{caller[2]}", loglevel, details: @kittens[type].keys.map { |hab| hab.to_s+": "+@kittens[type][hab].keys.join(", ") }
|
1291
1621
|
matches = []
|
1292
1622
|
|
1293
|
-
@kittens[type].each { |
|
1623
|
+
@kittens[type].each { |habitat_group, sib_classes|
|
1624
|
+
next if habitat and habitat_group != habitat
|
1625
|
+
sib_classes.each_pair { |sib_class, data|
|
1294
1626
|
virtual_name = nil
|
1295
1627
|
|
1296
1628
|
if !has_multiples and data and !data.is_a?(Hash) and data.config and data.config.is_a?(Hash) and data.config['virtual_name'] and name == data.config['virtual_name']
|
@@ -1301,14 +1633,13 @@ module MU
|
|
1301
1633
|
if has_multiples
|
1302
1634
|
if !name.nil?
|
1303
1635
|
if return_all
|
1636
|
+
MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
|
1304
1637
|
return data.dup
|
1305
1638
|
end
|
1306
1639
|
if data.size == 1 and (cloud_id.nil? or data.values.first.cloud_id == cloud_id)
|
1307
|
-
|
1308
|
-
return obj
|
1640
|
+
return data.values.first
|
1309
1641
|
elsif mu_name.nil? and cloud_id.nil?
|
1310
|
-
|
1311
|
-
MU.log "#{@deploy_id}: Found multiple matches in findLitterMate based on #{type}: #{name}, and not enough info to narrow down further. Returning an arbitrary result. Caller: #{caller[2]}", MU::WARN, details: data.keys
|
1642
|
+
MU.log indent+"#{@deploy_id}: Found multiple matches in findLitterMate based on #{type}: #{name}, and not enough info to narrow down further. Returning an arbitrary result. Caller: #{caller[2]}", MU::WARN, details: data.keys
|
1312
1643
|
return data.values.first
|
1313
1644
|
end
|
1314
1645
|
end
|
@@ -1318,20 +1649,35 @@ module MU
|
|
1318
1649
|
(!credentials.nil? and credentials == obj.credentials)
|
1319
1650
|
if !created_only or !obj.cloud_id.nil?
|
1320
1651
|
if return_all
|
1652
|
+
MU.log indent+"MULTI-MATCH RETURN_ALL findLitterMate(#{argstring})", loglevel, details: data.keys
|
1321
1653
|
return data.dup
|
1322
1654
|
else
|
1655
|
+
MU.log indent+"MULTI-MATCH findLitterMate(#{argstring})", loglevel, details: data.keys
|
1323
1656
|
return obj
|
1324
1657
|
end
|
1325
1658
|
end
|
1326
1659
|
end
|
1327
1660
|
}
|
1328
1661
|
else
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1662
|
+
|
1663
|
+
MU.log indent+"CHECKING AGAINST findLitterMate #{habitat_group}/#{type}/#{sib_class} data.cloud_id: #{data.cloud_id}, data.credentials: #{data.credentials}, sib_class: #{sib_class}, virtual_name: #{virtual_name}", loglevel, details: argstring
|
1664
|
+
|
1665
|
+
data_cloud_id = data.cloud_id.nil? ? nil : data.cloud_id.to_s
|
1666
|
+
|
1667
|
+
MU.log indent+"(name.nil? or sib_class == name or virtual_name == name)", loglevel, details: (name.nil? or sib_class == name or virtual_name == name).to_s
|
1668
|
+
MU.log indent+"(cloud_id.nil? or cloud_id[#{cloud_id.class.name}:#{cloud_id.to_s}] == data_cloud_id[#{data_cloud_id.class.name}:#{data_cloud_id}])", loglevel, details: (cloud_id.nil? or cloud_id == data_cloud_id).to_s
|
1669
|
+
MU.log indent+"(credentials.nil? or data.credentials.nil? or credentials[#{credentials.class.name}:#{credentials}] == data.credentials[#{data.credentials.class.name}:#{data.credentials}])", loglevel, details: (credentials.nil? or data.credentials.nil? or credentials == data.credentials).to_s
|
1670
|
+
|
1671
|
+
if (name.nil? or sib_class == name.to_s or virtual_name == name.to_s) and
|
1672
|
+
(cloud_id.nil? or cloud_id.to_s == data_cloud_id) and
|
1673
|
+
(credentials.nil? or data.credentials.nil? or credentials.to_s == data.credentials.to_s)
|
1674
|
+
if !created_only or !data_cloud_id.nil?
|
1675
|
+
MU.log indent+"SINGLE MATCH findLitterMate(#{argstring})", loglevel, details: [data.mu_name, data_cloud_id, data.config.keys]
|
1676
|
+
matches << data
|
1677
|
+
end
|
1333
1678
|
end
|
1334
1679
|
end
|
1680
|
+
}
|
1335
1681
|
}
|
1336
1682
|
|
1337
1683
|
return matches.first if matches.size == 1
|
@@ -1340,6 +1686,7 @@ module MU
|
|
1340
1686
|
end
|
1341
1687
|
}
|
1342
1688
|
|
1689
|
+
MU.log indent+"NO MATCH findLitterMate(#{argstring})", loglevel
|
1343
1690
|
|
1344
1691
|
return nil
|
1345
1692
|
end
|
@@ -1354,11 +1701,20 @@ module MU
|
|
1354
1701
|
def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
|
1355
1702
|
return if @no_artifacts
|
1356
1703
|
MU::MommaCat.lock("deployment-notification")
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1704
|
+
|
1705
|
+
if !@need_deploy_flush or @deployment.nil? or @deployment.empty?
|
1706
|
+
loadDeploy(true) # make sure we're saving the latest and greatest
|
1707
|
+
end
|
1708
|
+
|
1709
|
+
_shortclass, _cfg_name, cfg_plural, _classname, attrs = MU::Cloud.getResourceNames(type)
|
1710
|
+
has_multiples = false
|
1711
|
+
|
1712
|
+
# it's not always the case that we're logging data for a legal resource
|
1713
|
+
# type, though that's what we're usually for
|
1714
|
+
if cfg_plural
|
1715
|
+
type = cfg_plural
|
1716
|
+
has_multiples = attrs[:has_multiples]
|
1717
|
+
end
|
1362
1718
|
|
1363
1719
|
if mu_name.nil?
|
1364
1720
|
if !data.nil? and !data["mu_name"].nil?
|
@@ -1373,15 +1729,21 @@ module MU
|
|
1373
1729
|
end
|
1374
1730
|
end
|
1375
1731
|
|
1732
|
+
@need_deploy_flush = true
|
1733
|
+
|
1376
1734
|
if !remove
|
1377
1735
|
if data.nil?
|
1378
1736
|
MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
|
1379
1737
|
MU::MommaCat.unlock("deployment-notification")
|
1380
1738
|
return
|
1381
1739
|
end
|
1382
|
-
@
|
1740
|
+
@notify_semaphore.synchronize {
|
1741
|
+
@deployment[type] ||= {}
|
1742
|
+
}
|
1383
1743
|
if has_multiples
|
1384
|
-
@
|
1744
|
+
@notify_semaphore.synchronize {
|
1745
|
+
@deployment[type][key] ||= {}
|
1746
|
+
}
|
1385
1747
|
# fix has_multiples classes that weren't tiered correctly
|
1386
1748
|
if @deployment[type][key].is_a?(Hash) and @deployment[type][key].has_key?("mu_name")
|
1387
1749
|
olddata = @deployment[type][key].dup
|
@@ -1409,23 +1771,26 @@ module MU
|
|
1409
1771
|
end
|
1410
1772
|
|
1411
1773
|
if have_deploy
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1774
|
+
@notify_semaphore.synchronize {
|
1775
|
+
if has_multiples
|
1776
|
+
MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
|
1777
|
+
@deployment[type][key].delete(mu_name)
|
1778
|
+
if @deployment[type][key].size == 0
|
1779
|
+
@deployment[type].delete(key)
|
1780
|
+
end
|
1781
|
+
else
|
1782
|
+
MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
|
1416
1783
|
@deployment[type].delete(key)
|
1417
1784
|
end
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
if @deployment[type].size == 0
|
1423
|
-
@deployment.delete(type)
|
1424
|
-
end
|
1785
|
+
if @deployment[type].size == 0
|
1786
|
+
@deployment.delete(type)
|
1787
|
+
end
|
1788
|
+
}
|
1425
1789
|
end
|
1426
1790
|
save! if !delayed_save
|
1427
1791
|
|
1428
1792
|
end
|
1793
|
+
|
1429
1794
|
MU::MommaCat.unlock("deployment-notification")
|
1430
1795
|
end
|
1431
1796
|
|
@@ -1475,51 +1840,27 @@ module MU
|
|
1475
1840
|
end
|
1476
1841
|
end
|
1477
1842
|
|
1478
|
-
# XXX this belongs in MU::Cloud::AWS
|
1479
|
-
# Tag a resource with all of our standard identifying tags.
|
1480
|
-
#
|
1481
|
-
# @param resource [String]: The cloud provider identifier of the resource to tag
|
1482
|
-
# @param region [String]: The cloud provider region
|
1483
|
-
# @return [void]
|
1484
|
-
def self.createStandardTags(resource = nil, region: MU.curRegion, credentials: nil)
|
1485
|
-
tags = []
|
1486
|
-
listStandardTags.each_pair { |name, value|
|
1487
|
-
if !value.nil?
|
1488
|
-
tags << {key: name, value: value}
|
1489
|
-
end
|
1490
|
-
}
|
1491
|
-
if MU::Cloud::CloudFormation.emitCloudFormation
|
1492
|
-
return tags
|
1493
|
-
end
|
1494
|
-
|
1495
|
-
attempts = 0
|
1496
|
-
begin
|
1497
|
-
MU::Cloud::AWS.ec2(region: region, credentials: credentials).create_tags(
|
1498
|
-
resources: [resource],
|
1499
|
-
tags: tags
|
1500
|
-
)
|
1501
|
-
rescue Aws::EC2::Errors::ServiceError => e
|
1502
|
-
MU.log "Got #{e.inspect} tagging #{resource} in #{region}, will retry", MU::WARN, details: caller.concat(tags) if attempts > 1
|
1503
|
-
if attempts < 5
|
1504
|
-
attempts = attempts + 1
|
1505
|
-
sleep 15
|
1506
|
-
retry
|
1507
|
-
else
|
1508
|
-
raise e
|
1509
|
-
end
|
1510
|
-
end
|
1511
|
-
MU.log "Created standard tags for resource #{resource}", MU::DEBUG, details: caller
|
1512
|
-
end
|
1513
|
-
|
1514
1843
|
# List the name/value pairs for our mandatory standard set of resource tags, which
|
1515
1844
|
# should be applied to all taggable cloud provider resources.
|
1516
1845
|
# @return [Hash<String,String>]
|
1517
1846
|
def self.listStandardTags
|
1518
|
-
return {
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1847
|
+
return {} if !MU.deploy_id
|
1848
|
+
{
|
1849
|
+
"MU-ID" => MU.deploy_id,
|
1850
|
+
"MU-APP" => MU.appname,
|
1851
|
+
"MU-ENV" => MU.environment,
|
1852
|
+
"MU-MASTER-IP" => MU.mu_public_ip
|
1853
|
+
}
|
1854
|
+
end
|
1855
|
+
# List the name/value pairs for our mandatory standard set of resource tags
|
1856
|
+
# for this deploy.
|
1857
|
+
# @return [Hash<String,String>]
|
1858
|
+
def listStandardTags
|
1859
|
+
{
|
1860
|
+
"MU-ID" => @deploy_id,
|
1861
|
+
"MU-APP" => @appname,
|
1862
|
+
"MU-ENV" => @environment,
|
1863
|
+
"MU-MASTER-IP" => MU.mu_public_ip
|
1523
1864
|
}
|
1524
1865
|
end
|
1525
1866
|
|
@@ -1541,7 +1882,7 @@ module MU
|
|
1541
1882
|
sshdir = "#{@myhome}/.ssh"
|
1542
1883
|
sshconf = "#{sshdir}/config"
|
1543
1884
|
|
1544
|
-
if File.
|
1885
|
+
if File.exist?(sshconf) and File.open(sshconf).read.match(/ #{node} /)
|
1545
1886
|
MU.log "Expunging old #{node} entry from #{sshconf}", MU::DEBUG
|
1546
1887
|
if !@noop
|
1547
1888
|
File.open(sshconf, File::CREAT|File::RDWR, 0600) { |f|
|
@@ -1572,8 +1913,7 @@ module MU
|
|
1572
1913
|
# @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
|
1573
1914
|
# @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
|
1574
1915
|
def self.nameKitten(server, sync_wait: false)
|
1575
|
-
node, config,
|
1576
|
-
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_addr, ssh_user, ssh_key_name = server.getSSHConfig
|
1916
|
+
node, config, _deploydata = server.describe
|
1577
1917
|
|
1578
1918
|
mu_zone = nil
|
1579
1919
|
# XXX GCP!
|
@@ -1659,9 +1999,10 @@ module MU
|
|
1659
1999
|
MU.log "Called addHostToSSHConfig without a MU::Cloud::Server object", MU::ERR, details: caller
|
1660
2000
|
return nil
|
1661
2001
|
end
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
2002
|
+
|
2003
|
+
_nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = begin
|
2004
|
+
server.getSSHConfig
|
2005
|
+
rescue MU::MuError
|
1665
2006
|
return
|
1666
2007
|
end
|
1667
2008
|
|
@@ -1680,7 +2021,7 @@ module MU
|
|
1680
2021
|
|
1681
2022
|
@ssh_semaphore.synchronize {
|
1682
2023
|
|
1683
|
-
if File.
|
2024
|
+
if File.exist?(ssh_conf)
|
1684
2025
|
File.readlines(ssh_conf).each { |line|
|
1685
2026
|
if line.match(/^Host #{server.mu_name} /)
|
1686
2027
|
MU.log("Attempt to add duplicate #{ssh_conf} entry for #{server.mu_name}", MU::WARN)
|
@@ -1753,7 +2094,6 @@ module MU
|
|
1753
2094
|
# @param system_name [String]: The node's local system name
|
1754
2095
|
# @return [void]
|
1755
2096
|
def self.addInstanceToEtcHosts(public_ip, chef_name = nil, system_name = nil)
|
1756
|
-
return if !["mu", "root"].include?(MU.mu_user)
|
1757
2097
|
|
1758
2098
|
# XXX cover ipv6 case
|
1759
2099
|
if public_ip.nil? or !public_ip.match(/^\d+\.\d+\.\d+\.\d+$/) or (chef_name.nil? and system_name.nil?)
|
@@ -1762,6 +2102,19 @@ module MU
|
|
1762
2102
|
if chef_name == "localhost" or system_name == "localhost"
|
1763
2103
|
raise MuError, "Can't set localhost as a name in addInstanceToEtcHosts"
|
1764
2104
|
end
|
2105
|
+
|
2106
|
+
if !["mu", "root"].include?(MU.mu_user)
|
2107
|
+
response = nil
|
2108
|
+
begin
|
2109
|
+
response = open("https://127.0.0.1:#{MU.mommaCatPort.to_s}/rest/hosts_add/#{chef_name}/#{public_ip}").read
|
2110
|
+
rescue Errno::ECONNRESET, Errno::ECONNREFUSED
|
2111
|
+
end
|
2112
|
+
if response != "ok"
|
2113
|
+
MU.log "Error adding #{public_ip} to /etc/hosts via MommaCat request", MU::ERR
|
2114
|
+
end
|
2115
|
+
return
|
2116
|
+
end
|
2117
|
+
|
1765
2118
|
File.readlines("/etc/hosts").each { |line|
|
1766
2119
|
if line.match(/^#{public_ip} /) or (chef_name != nil and line.match(/ #{chef_name}(\s|$)/)) or (system_name != nil and line.match(/ #{system_name}(\s|$)/))
|
1767
2120
|
MU.log "Ignoring attempt to add duplicate /etc/hosts entry: #{public_ip} #{chef_name} #{system_name}", MU::DEBUG
|
@@ -1776,13 +2129,32 @@ module MU
|
|
1776
2129
|
MU.log("Added to /etc/hosts: #{public_ip} #{chef_name} #{system_name}")
|
1777
2130
|
end
|
1778
2131
|
|
1779
|
-
|
2132
|
+
|
2133
|
+
# Send a Slack notification to a deployment's administrators.
|
2134
|
+
# @param subject [String]: The subject line of the message.
|
2135
|
+
# @param msg [String]: The message body.
|
2136
|
+
# @return [void]
|
2137
|
+
def sendAdminSlack(subject, msg: "")
|
2138
|
+
if $MU_CFG['slack'] and $MU_CFG['slack']['webhook'] and
|
2139
|
+
(!$MU_CFG['slack']['skip_environments'] or !$MU_CFG['slack']['skip_environments'].any?{ |s| s.casecmp(MU.environment)==0 })
|
2140
|
+
require 'slack-notifier'
|
2141
|
+
slack = Slack::Notifier.new $MU_CFG['slack']['webhook']
|
2142
|
+
|
2143
|
+
if msg and !msg.empty?
|
2144
|
+
slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}:\n\n```#{msg}\n```", channel: $MU_CFG['slack']['channel']
|
2145
|
+
else
|
2146
|
+
slack.ping "#{MU.appname} \*\"#{MU.handle}\"\* (`#{MU.deploy_id}`) - #{subject}", channel: $MU_CFG['slack']['channel']
|
2147
|
+
end
|
2148
|
+
end
|
2149
|
+
end
|
2150
|
+
|
2151
|
+
# Send an email notification to a deployment's administrators.
|
1780
2152
|
# @param subject [String]: The subject line of the message.
|
1781
2153
|
# @param msg [String]: The message body.
|
1782
2154
|
# @param data [Array]: Supplemental data to add to the message body.
|
1783
2155
|
# @param debug [Boolean]: If set, will include the full deployment structure and original {MU::Config}-parsed configuration.
|
1784
2156
|
# @return [void]
|
1785
|
-
def sendAdminMail(subject, msg:
|
2157
|
+
def sendAdminMail(subject, msg: "", kitten: nil, data: nil, debug: false)
|
1786
2158
|
require 'net/smtp'
|
1787
2159
|
if @deployment.nil?
|
1788
2160
|
MU.log "Can't send admin mail without a loaded deployment", MU::ERR
|
@@ -1890,11 +2262,11 @@ MESSAGE_END
|
|
1890
2262
|
MU.dupGlobals(parent_thread_id)
|
1891
2263
|
realhome = Etc.getpwnam("nagios").dir
|
1892
2264
|
[@nagios_home, "#{@nagios_home}/.ssh"].each { |dir|
|
1893
|
-
Dir.mkdir(dir, 0711) if !Dir.
|
2265
|
+
Dir.mkdir(dir, 0711) if !Dir.exist?(dir)
|
1894
2266
|
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, dir)
|
1895
2267
|
}
|
1896
|
-
if realhome != @nagios_home and Dir.
|
1897
|
-
File.rename("#{realhome}/.ssh", "#{realhome}/.ssh.#{$$}") if Dir.
|
2268
|
+
if realhome != @nagios_home and Dir.exist?(realhome) and !File.symlink?("#{realhome}/.ssh")
|
2269
|
+
File.rename("#{realhome}/.ssh", "#{realhome}/.ssh.#{$$}") if Dir.exist?("#{realhome}/.ssh")
|
1898
2270
|
File.symlink("#{@nagios_home}/.ssh", Etc.getpwnam("nagios").dir+"/.ssh")
|
1899
2271
|
end
|
1900
2272
|
MU.log "Updating #{@nagios_home}/.ssh/config..."
|
@@ -1910,12 +2282,6 @@ MESSAGE_END
|
|
1910
2282
|
FileUtils.cp("#{@myhome}/.ssh/id_rsa", "#{@nagios_home}/.ssh/id_rsa")
|
1911
2283
|
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/id_rsa")
|
1912
2284
|
threads = []
|
1913
|
-
if !MU::Cloud::AWS.isGovCloud?
|
1914
|
-
mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
|
1915
|
-
end
|
1916
|
-
# XXX what if we're in GCP?
|
1917
|
-
# XXX need a MU::Cloud::DNSZone.lookup for bulk lookups
|
1918
|
-
# XXX also grab things like mu_windows_name out of deploy data if we can
|
1919
2285
|
|
1920
2286
|
parent_thread_id = Thread.current.object_id
|
1921
2287
|
MU::MommaCat.listDeploys.sort.each { |deploy_id|
|
@@ -1929,19 +2295,21 @@ MESSAGE_END
|
|
1929
2295
|
FileUtils.cp("#{@myhome}/.ssh/#{deploy.ssh_key_name}", "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
|
1930
2296
|
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
|
1931
2297
|
if deploy.kittens.has_key?("servers")
|
1932
|
-
deploy.kittens["servers"].
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1939
|
-
|
1940
|
-
|
1941
|
-
|
1942
|
-
|
1943
|
-
|
1944
|
-
|
2298
|
+
deploy.kittens["servers"].values.each { |nodeclasses|
|
2299
|
+
nodeclasses.values.each { |nodes|
|
2300
|
+
nodes.values.each { |server|
|
2301
|
+
MU.dupGlobals(parent_thread_id)
|
2302
|
+
threads << Thread.new {
|
2303
|
+
MU::MommaCat.setThreadContext(deploy)
|
2304
|
+
MU.log "Adding #{server.mu_name} to #{@nagios_home}/.ssh/config", MU::DEBUG
|
2305
|
+
MU::MommaCat.addHostToSSHConfig(
|
2306
|
+
server,
|
2307
|
+
ssh_dir: "#{@nagios_home}/.ssh",
|
2308
|
+
ssh_conf: "#{@nagios_home}/.ssh/config.tmp",
|
2309
|
+
ssh_owner: "nagios"
|
2310
|
+
)
|
2311
|
+
MU.purgeGlobals
|
2312
|
+
}
|
1945
2313
|
}
|
1946
2314
|
}
|
1947
2315
|
}
|
@@ -1983,10 +2351,10 @@ MESSAGE_END
|
|
1983
2351
|
# Return a list of all currently active deploy identifiers.
|
1984
2352
|
# @return [Array<String>]
|
1985
2353
|
def self.listDeploys
|
1986
|
-
return [] if !Dir.
|
2354
|
+
return [] if !Dir.exist?("#{MU.dataDir}/deployments")
|
1987
2355
|
deploys = []
|
1988
2356
|
Dir.entries("#{MU.dataDir}/deployments").reverse_each { |muid|
|
1989
|
-
next if !Dir.
|
2357
|
+
next if !Dir.exist?("#{MU.dataDir}/deployments/#{muid}") or muid == "." or muid == ".."
|
1990
2358
|
deploys << muid
|
1991
2359
|
}
|
1992
2360
|
return deploys
|
@@ -1999,7 +2367,7 @@ MESSAGE_END
|
|
1999
2367
|
nodes = Hash.new
|
2000
2368
|
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
2001
2369
|
MU::MommaCat.listDeploys.each { |deploy|
|
2002
|
-
if !Dir.
|
2370
|
+
if !Dir.exist?(MU::MommaCat.deploy_dir(deploy)) or
|
2003
2371
|
!File.size?("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json")
|
2004
2372
|
MU.log "Didn't see deployment metadata for '#{deploy}'", MU::WARN
|
2005
2373
|
next
|
@@ -2018,7 +2386,7 @@ MESSAGE_END
|
|
2018
2386
|
}
|
2019
2387
|
end
|
2020
2388
|
rescue JSON::ParserError => e
|
2021
|
-
MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR
|
2389
|
+
MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR, details: e.message
|
2022
2390
|
end
|
2023
2391
|
data.flock(File::LOCK_UN)
|
2024
2392
|
data.close
|
@@ -2084,82 +2452,25 @@ MESSAGE_END
|
|
2084
2452
|
end
|
2085
2453
|
|
2086
2454
|
# Given a Certificate Signing Request, sign it with our internal CA and
|
2087
|
-
#
|
2455
|
+
# write the resulting signed certificate. Only works on local files.
|
2088
2456
|
# @param csr_path [String]: The CSR to sign, as a file.
|
2089
2457
|
def signSSLCert(csr_path, sans = [])
|
2090
|
-
|
2091
|
-
certdir = File.dirname(csr_path)
|
2092
|
-
certname = File.basename(csr_path, ".csr")
|
2093
|
-
if File.exists?("#{certdir}/#{certname}.crt")
|
2094
|
-
MU.log "Not re-signing SSL certificate request #{csr_path}, #{certdir}/#{certname}.crt already exists", MU::WARN
|
2095
|
-
return
|
2096
|
-
end
|
2097
|
-
MU.log "Signing SSL certificate request #{csr_path} with #{MU.mySSLDir}/Mu_CA.pem"
|
2098
|
-
|
2099
|
-
begin
|
2100
|
-
csr = OpenSSL::X509::Request.new File.read csr_path
|
2101
|
-
rescue Exception => e
|
2102
|
-
MU.log e.message, MU::ERR, details: File.read(csr_path)
|
2103
|
-
raise e
|
2104
|
-
end
|
2105
|
-
key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key"
|
2106
|
-
|
2107
|
-
# Load up the Mu Certificate Authority
|
2108
|
-
cakey = OpenSSL::PKey::RSA.new File.read "#{MU.mySSLDir}/Mu_CA.key"
|
2109
|
-
cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
|
2110
|
-
cur_serial = 0
|
2111
|
-
File.open("#{MU.mySSLDir}/serial", File::CREAT|File::RDWR, 0600) { |f|
|
2112
|
-
f.flock(File::LOCK_EX)
|
2113
|
-
cur_serial = f.read.chomp!.to_i
|
2114
|
-
cur_serial = cur_serial + 1
|
2115
|
-
f.rewind
|
2116
|
-
f.truncate(0)
|
2117
|
-
f.puts cur_serial
|
2118
|
-
f.flush
|
2119
|
-
f.flock(File::LOCK_UN)
|
2120
|
-
}
|
2121
|
-
|
2122
|
-
# Create a certificate from our CSR, signed by the Mu CA
|
2123
|
-
cert = OpenSSL::X509::Certificate.new
|
2124
|
-
cert.serial = cur_serial
|
2125
|
-
cert.version = 3
|
2126
|
-
cert.not_before = Time.now
|
2127
|
-
cert.not_after = Time.now + 180000000
|
2128
|
-
cert.subject = csr.subject
|
2129
|
-
cert.public_key = csr.public_key
|
2130
|
-
cert.issuer = cacert.subject
|
2131
|
-
if !sans.nil? and sans.size > 0
|
2132
|
-
MU.log "Incorporting Subject Alternative Names: #{sans.join(",")}"
|
2133
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
2134
|
-
ef.issuer_certificate = cacert
|
2135
|
-
#v3_req_client
|
2136
|
-
ef.subject_certificate = cert
|
2137
|
-
ef.subject_request = csr
|
2138
|
-
cert.add_extension(ef.create_extension("keyUsage","nonRepudiation,digitalSignature,keyEncipherment", false))
|
2139
|
-
cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false))
|
2140
|
-
# XXX only do this if we see the otherName thinger in the san list
|
2141
|
-
cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth,serverAuth,codeSigning,emailProtection",false))
|
2142
|
-
end
|
2143
|
-
cert.sign cakey, OpenSSL::Digest::SHA256.new
|
2144
|
-
|
2145
|
-
open("#{certdir}/#{certname}.crt", 'w', 0644) { |io|
|
2146
|
-
io.write cert.to_pem
|
2147
|
-
}
|
2148
|
-
if MU.mu_user != "mu"
|
2149
|
-
owner_uid = Etc.getpwnam(MU.mu_user).uid
|
2150
|
-
File.chown(owner_uid, nil, "#{certdir}/#{certname}.crt")
|
2151
|
-
end
|
2152
|
-
|
2458
|
+
MU::Master::SSL.sign(csr_path, sans, for_user: MU.mu_user)
|
2153
2459
|
end
|
2154
2460
|
|
2155
2461
|
# Make sure deployment data is synchronized to/from each node in the
|
2156
2462
|
# currently-loaded deployment.
|
2157
|
-
def syncLitter(nodeclasses = [], triggering_node: nil,
|
2158
|
-
# XXX take some config logic to decide what nodeclasses to hit
|
2159
|
-
#
|
2463
|
+
def syncLitter(nodeclasses = [], triggering_node: nil, save_only: false)
|
2464
|
+
# XXX take some config logic to decide what nodeclasses to hit? like, make
|
2465
|
+
# inferences from dependencies or something?
|
2466
|
+
|
2160
2467
|
return if MU.syncLitterThread
|
2161
|
-
return if !Dir.
|
2468
|
+
return if !Dir.exist?(deploy_dir)
|
2162
2469
|
svrs = MU::Cloud.resource_types[:Server][:cfg_plural] # legibility shorthand
|
2470
|
+
if !triggering_node.nil? and nodeclasses.size > 0
|
2471
|
+
nodeclasses.reject! { |n| n == triggering_node.to_s }
|
2472
|
+
return if nodeclasses.size == 0
|
2473
|
+
end
|
2163
2474
|
|
2164
2475
|
@kitten_semaphore.synchronize {
|
2165
2476
|
if @kittens.nil? or
|
@@ -2168,14 +2479,22 @@ MESSAGE_END
|
|
2168
2479
|
return
|
2169
2480
|
end
|
2170
2481
|
|
2171
|
-
|
2482
|
+
|
2483
|
+
MU.log "Updating these node classes in #{@deploy_id}", MU::DEBUG, details: nodeclasses
|
2172
2484
|
}
|
2173
2485
|
|
2174
2486
|
update_servers = []
|
2175
2487
|
if nodeclasses.nil? or nodeclasses.size == 0
|
2176
2488
|
litter = findLitterMate(type: "server", return_all: true)
|
2489
|
+
return if litter.nil?
|
2177
2490
|
litter.each_pair { |mu_name, node|
|
2178
|
-
|
2491
|
+
if !triggering_node.nil? and (
|
2492
|
+
(triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
|
2493
|
+
(triggering_node.is_a?(String) and mu_name == triggering_node)
|
2494
|
+
)
|
2495
|
+
next
|
2496
|
+
end
|
2497
|
+
|
2179
2498
|
if !node.groomer.nil?
|
2180
2499
|
update_servers << node
|
2181
2500
|
end
|
@@ -2187,10 +2506,16 @@ MESSAGE_END
|
|
2187
2506
|
litter.merge!(mates) if mates
|
2188
2507
|
}
|
2189
2508
|
litter.each_pair { |mu_name, node|
|
2190
|
-
|
2509
|
+
if !triggering_node.nil? and (
|
2510
|
+
(triggering_node.is_a?(MU::Cloud::Server) and mu_name == triggering_node.mu_name) or
|
2511
|
+
(triggering_node.is_a?(String) and mu_name == triggering_node)
|
2512
|
+
)
|
2513
|
+
next
|
2514
|
+
end
|
2515
|
+
|
2191
2516
|
if !node.deploydata or !node.deploydata.keys.include?('nodename')
|
2192
2517
|
details = node.deploydata ? node.deploydata.keys : nil
|
2193
|
-
MU.log "#{mu_name} deploy data is missing (possibly retired), not syncing it", MU::WARN, details: details
|
2518
|
+
MU.log "#{mu_name} deploy data is missing (possibly retired or mid-bootstrap), so not syncing it", MU::WARN, details: details
|
2194
2519
|
else
|
2195
2520
|
update_servers << node
|
2196
2521
|
end
|
@@ -2198,6 +2523,8 @@ MESSAGE_END
|
|
2198
2523
|
end
|
2199
2524
|
return if update_servers.size == 0
|
2200
2525
|
|
2526
|
+
MU.log "Updating these nodes in #{@deploy_id}", MU::DEBUG, details: update_servers.map { |n| n.mu_name }
|
2527
|
+
|
2201
2528
|
update_servers.each { |node|
|
2202
2529
|
# Not clear where this pollution comes from, but let's stick a temp
|
2203
2530
|
# fix in here.
|
@@ -2210,7 +2537,7 @@ MESSAGE_END
|
|
2210
2537
|
}
|
2211
2538
|
|
2212
2539
|
# Merge everyone's deploydata together
|
2213
|
-
if !
|
2540
|
+
if !save_only
|
2214
2541
|
skip = []
|
2215
2542
|
update_servers.each { |node|
|
2216
2543
|
if node.mu_name.nil? or node.deploydata.nil? or node.config.nil?
|
@@ -2239,7 +2566,7 @@ MESSAGE_END
|
|
2239
2566
|
begin
|
2240
2567
|
if sibling.config['groom'].nil? or sibling.config['groom']
|
2241
2568
|
sibling.groomer.saveDeployData
|
2242
|
-
sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !
|
2569
|
+
sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_only
|
2243
2570
|
end
|
2244
2571
|
rescue MU::Groomer::RunError => e
|
2245
2572
|
MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
|
@@ -2263,125 +2590,45 @@ MESSAGE_END
|
|
2263
2590
|
# @param poolname [Boolean]: If true, generate certificates for the base name of the server pool of which this node is a member, rather than for the individual node
|
2264
2591
|
# @param keysize [Integer]: The size of the private key to use when generating this certificate
|
2265
2592
|
def nodeSSLCerts(resource, poolname = false, keysize = 4096)
|
2266
|
-
|
2593
|
+
_nat_ssh_key, _nat_ssh_user, _nat_ssh_host, canonical_ip, _ssh_user, _ssh_key_name = resource.getSSHConfig if resource.respond_to?(:getSSHConfig)
|
2267
2594
|
|
2268
2595
|
deploy_id = resource.deploy_id || @deploy_id || resource.deploy.deploy_id
|
2269
2596
|
|
2270
2597
|
cert_cn = poolname ? deploy_id + "-" + resource.config['name'].upcase : resource.mu_name
|
2271
2598
|
|
2272
|
-
certs = {}
|
2273
2599
|
results = {}
|
2274
2600
|
|
2275
|
-
|
2276
|
-
if File.exists?("#{MU.mySSLDir}/#{cert_cn}.crt") and
|
2277
|
-
File.exists?("#{MU.mySSLDir}/#{cert_cn}.key")
|
2278
|
-
ext_cert = OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt"))
|
2279
|
-
if ext_cert.not_after < Time.now
|
2280
|
-
MU.log "Node certificate for #{cert_cn} is expired, regenerating", MU::WARN
|
2281
|
-
["crt", "key", "csr"].each { |suffix|
|
2282
|
-
if File.exists?("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
|
2283
|
-
File.unlink("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
|
2284
|
-
end
|
2285
|
-
}
|
2286
|
-
else
|
2287
|
-
results[cert_cn] = [
|
2288
|
-
OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt")),
|
2289
|
-
OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{cert_cn}.key"))
|
2290
|
-
]
|
2291
|
-
end
|
2292
|
-
end
|
2601
|
+
is_windows = (resource.respond_to?(:windows?) and resource.windows?)
|
2293
2602
|
|
2294
|
-
|
2295
|
-
|
2296
|
-
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2300
|
-
|
2603
|
+
@node_cert_semaphore.synchronize {
|
2604
|
+
MU::Master::SSL.bootstrap
|
2605
|
+
sans = []
|
2606
|
+
sans << canonical_ip if canonical_ip
|
2607
|
+
# XXX were there other names we wanted to include?
|
2608
|
+
key = MU::Master::SSL.getKey(cert_cn, keysize: keysize)
|
2609
|
+
cert, pfx_cert = MU::Master::SSL.getCert(cert_cn, "/CN=#{cert_cn}/O=Mu/C=US", sans: sans, pfx: is_windows)
|
2610
|
+
results[cert_cn] = [key, cert]
|
2611
|
+
|
2612
|
+
winrm_cert = nil
|
2613
|
+
if is_windows
|
2614
|
+
winrm_key = MU::Master::SSL.getKey(cert_cn+"-winrm", keysize: keysize)
|
2615
|
+
winrm_cert = MU::Master::SSL.getCert(cert_cn+"-winrm", "/CN=#{resource.config['windows_admin_username']}/O=Mu/C=US", sans: ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{resource.config['windows_admin_username']}@localhost"], pfx: true)[0]
|
2616
|
+
results[cert_cn+"-winrm"] = [winrm_key, winrm_cert]
|
2617
|
+
end
|
2618
|
+
|
2619
|
+
if resource and resource.config and resource.config['cloud']
|
2620
|
+
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(resource.config['cloud'])
|
2621
|
+
|
2622
|
+
cloudclass.writeDeploySecret(@deploy_id, cert.to_pem, cert_cn+".crt", credentials: resource.config['credentials'])
|
2623
|
+
cloudclass.writeDeploySecret(@deploy_id, key.to_pem, cert_cn+".key", credentials: resource.config['credentials'])
|
2624
|
+
if pfx_cert
|
2625
|
+
cloudclass.writeDeploySecret(@deploy_id, pfx_cert.to_der, cert_cn+".pfx", credentials: resource.config['credentials'])
|
2301
2626
|
end
|
2302
|
-
|
2303
|
-
|
2304
|
-
if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
|
2305
|
-
if File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.crt") and
|
2306
|
-
File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.key")
|
2307
|
-
results[cert_cn+"-winrm"] = [File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.crt"), File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.key")]
|
2308
|
-
else
|
2309
|
-
certs[cert_cn+"-winrm"] = {
|
2310
|
-
"sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{resource.config['windows_admin_username']}@localhost"],
|
2311
|
-
"cn" => resource.config['windows_admin_username']
|
2312
|
-
}
|
2627
|
+
if winrm_cert
|
2628
|
+
cloudclass.writeDeploySecret(@deploy_id, winrm_cert.to_pem, cert_cn+"-winrm.crt", credentials: resource.config['credentials'])
|
2313
2629
|
end
|
2314
2630
|
end
|
2315
2631
|
|
2316
|
-
certs.each { |certname, data|
|
2317
|
-
MU.log "Generating SSL certificate #{certname} for #{resource} with key size #{keysize.to_s}"
|
2318
|
-
|
2319
|
-
# Create and save a key
|
2320
|
-
key = OpenSSL::PKey::RSA.new keysize
|
2321
|
-
if !Dir.exist?(MU.mySSLDir)
|
2322
|
-
Dir.mkdir(MU.mySSLDir, 0700)
|
2323
|
-
end
|
2324
|
-
|
2325
|
-
open("#{MU.mySSLDir}/#{certname}.key", 'w', 0600) { |io|
|
2326
|
-
io.write key.to_pem
|
2327
|
-
}
|
2328
|
-
# Create a certificate request for this node
|
2329
|
-
csr = OpenSSL::X509::Request.new
|
2330
|
-
csr.version = 3
|
2331
|
-
csr.subject = OpenSSL::X509::Name.parse "/CN=#{data['cn']}/O=Mu/C=US"
|
2332
|
-
csr.public_key = key.public_key
|
2333
|
-
csr.sign key, OpenSSL::Digest::SHA256.new
|
2334
|
-
open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io|
|
2335
|
-
io.write csr.to_pem
|
2336
|
-
}
|
2337
|
-
if MU.chef_user == "mu"
|
2338
|
-
signSSLCert("#{MU.mySSLDir}/#{certname}.csr", data['sans'])
|
2339
|
-
else
|
2340
|
-
deploykey = OpenSSL::PKey::RSA.new(public_key)
|
2341
|
-
deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(deploy_secret))
|
2342
|
-
# XXX things that aren't servers
|
2343
|
-
res_type = "server"
|
2344
|
-
res_type = "server_pool" if !resource.config['basis'].nil?
|
2345
|
-
uri = URI("https://#{MU.mu_public_addr}:2260/")
|
2346
|
-
req = Net::HTTP::Post.new(uri)
|
2347
|
-
req.set_form_data(
|
2348
|
-
"mu_id" => MU.deploy_id,
|
2349
|
-
"mu_resource_name" => resource.config['name'],
|
2350
|
-
"mu_resource_type" => res_type,
|
2351
|
-
"mu_ssl_sign" => "#{MU.mySSLDir}/#{certname}.csr",
|
2352
|
-
"mu_ssl_sans" => data["sans"].join(","),
|
2353
|
-
"mu_user" => MU.mu_user,
|
2354
|
-
"mu_deploy_secret" => deploysecret
|
2355
|
-
)
|
2356
|
-
http = Net::HTTP.new(uri.hostname, uri.port)
|
2357
|
-
http.ca_file = "/etc/pki/Mu_CA.pem" # XXX why no worky?
|
2358
|
-
http.use_ssl = true
|
2359
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX this sucks
|
2360
|
-
response = http.request(req)
|
2361
|
-
MU.log "Got error back on signing request for #{MU.mySSLDir}/#{certname}.csr", MU::ERR if response.code != "200"
|
2362
|
-
end
|
2363
|
-
|
2364
|
-
pfx = nil
|
2365
|
-
cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{certname}.crt"
|
2366
|
-
if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
|
2367
|
-
cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
|
2368
|
-
pfx = OpenSSL::PKCS12.create(nil, nil, key, cert, [cacert], nil, nil, nil, nil)
|
2369
|
-
open("#{MU.mySSLDir}/#{certname}.pfx", 'w', 0644) { |io|
|
2370
|
-
io.write pfx.to_der
|
2371
|
-
}
|
2372
|
-
end
|
2373
|
-
|
2374
|
-
results[certname] = [cert, key]
|
2375
|
-
|
2376
|
-
if resource.config['cloud'] == "AWS"
|
2377
|
-
MU::Cloud::AWS.writeDeploySecret(@deploy_id, cert.to_pem, certname+".crt")
|
2378
|
-
MU::Cloud::AWS.writeDeploySecret(@deploy_id, key.to_pem, certname+".key")
|
2379
|
-
if pfx
|
2380
|
-
MU::Cloud::AWS.writeDeploySecret(@deploy_id, pfx.to_der, certname+".pfx")
|
2381
|
-
end
|
2382
|
-
# XXX add google logic, or better yet abstract this method
|
2383
|
-
end
|
2384
|
-
}
|
2385
2632
|
}
|
2386
2633
|
|
2387
2634
|
results[cert_cn]
|
@@ -2392,80 +2639,129 @@ MESSAGE_END
|
|
2392
2639
|
MU::MommaCat.deploy_dir(@deploy_id)
|
2393
2640
|
end
|
2394
2641
|
|
2395
|
-
|
2642
|
+
# Path to the log file used by the Momma Cat daemon
|
2643
|
+
# @return [String]
|
2644
|
+
def self.daemonLogFile
|
2645
|
+
base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
|
2646
|
+
"#{base}/log/mu-momma-cat.log"
|
2647
|
+
end
|
2396
2648
|
|
2397
|
-
#
|
2398
|
-
#
|
2399
|
-
|
2400
|
-
|
2401
|
-
|
2402
|
-
# @return [Boolean]: True if allocation was successful.
|
2403
|
-
def allocateUniqueResourceName(name)
|
2404
|
-
raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
|
2405
|
-
path = File.expand_path(MU.dataDir+"/deployments")
|
2406
|
-
File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
|
2407
|
-
existing = []
|
2408
|
-
f.flock(File::LOCK_EX)
|
2409
|
-
f.readlines.each { |line|
|
2410
|
-
existing << line.chomp
|
2411
|
-
}
|
2412
|
-
begin
|
2413
|
-
existing.each { |used|
|
2414
|
-
if used.match(/^#{name}:/)
|
2415
|
-
if !used.match(/^#{name}:#{@deploy_id}$/)
|
2416
|
-
MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
|
2417
|
-
return false
|
2418
|
-
else
|
2419
|
-
return true
|
2420
|
-
end
|
2421
|
-
end
|
2422
|
-
}
|
2423
|
-
f.puts name+":"+@deploy_id
|
2424
|
-
return true
|
2425
|
-
ensure
|
2426
|
-
f.flock(File::LOCK_UN)
|
2427
|
-
end
|
2428
|
-
}
|
2649
|
+
# Path to the PID file used by the Momma Cat daemon
|
2650
|
+
# @return [String]
|
2651
|
+
def self.daemonPidFile
|
2652
|
+
base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
|
2653
|
+
"#{base}/run/mommacat.pid"
|
2429
2654
|
end
|
2430
2655
|
|
2431
|
-
|
2432
|
-
|
2433
|
-
def self.
|
2434
|
-
|
2435
|
-
#
|
2436
|
-
|
2437
|
-
|
2438
|
-
|
2439
|
-
|
2656
|
+
# Start the Momma Cat daemon and return the exit status of the command used
|
2657
|
+
# @return [Integer]
|
2658
|
+
def self.start
|
2659
|
+
base = (Process.uid == 0 and !MU.localOnly) ? "/var" : MU.dataDir
|
2660
|
+
[base, "#{base}/log", "#{base}/run"].each { |dir|
|
2661
|
+
if !Dir.exist?(dir)
|
2662
|
+
MU.log "Creating #{dir}"
|
2663
|
+
Dir.mkdir(dir)
|
2664
|
+
end
|
2665
|
+
}
|
2666
|
+
return 0 if status
|
2667
|
+
|
2668
|
+
MU.log "Starting Momma Cat on port #{MU.mommaCatPort}, logging to #{daemonLogFile}, PID file #{daemonPidFile}"
|
2669
|
+
origdir = Dir.getwd
|
2670
|
+
Dir.chdir(MU.myRoot+"/modules")
|
2671
|
+
|
2672
|
+
# XXX what's the safest way to find the 'bundle' executable in both gem and non-gem installs?
|
2673
|
+
cmd = %Q{bundle exec thin --threaded --daemonize --port #{MU.mommaCatPort} --pid #{daemonPidFile} --log #{daemonLogFile} --ssl --ssl-key-file #{MU.mySSLDir}/mommacat.key --ssl-cert-file #{MU.mySSLDir}/mommacat.pem --ssl-disable-verify --tag mu-momma-cat -R mommacat.ru start}
|
2674
|
+
MU.log cmd, MU::NOTICE
|
2675
|
+
output = %x{#{cmd}}
|
2676
|
+
Dir.chdir(origdir)
|
2677
|
+
|
2678
|
+
retries = 0
|
2679
|
+
begin
|
2680
|
+
sleep 1
|
2681
|
+
retries += 1
|
2682
|
+
if retries >= 10
|
2683
|
+
MU.log "MommaCat failed to start (command was #{cmd})", MU::WARN, details: output
|
2684
|
+
pp caller
|
2685
|
+
return $?.exitstatus
|
2686
|
+
end
|
2687
|
+
end while !status
|
2688
|
+
|
2689
|
+
if $?.exitstatus != 0
|
2690
|
+
exit 1
|
2440
2691
|
end
|
2441
|
-
|
2442
|
-
return
|
2692
|
+
|
2693
|
+
return $?.exitstatus
|
2443
2694
|
end
|
2444
2695
|
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2696
|
+
# Return true if the Momma Cat daemon appears to be running
|
2697
|
+
# @return [Boolean]
|
2698
|
+
def self.status
|
2699
|
+
if File.exist?(daemonPidFile)
|
2700
|
+
pid = File.read(daemonPidFile).chomp.to_i
|
2701
|
+
begin
|
2702
|
+
Process.getpgid(pid)
|
2703
|
+
MU.log "Momma Cat running with pid #{pid.to_s}"
|
2704
|
+
return true
|
2705
|
+
rescue Errno::ESRCH
|
2706
|
+
end
|
2449
2707
|
end
|
2450
|
-
|
2451
|
-
|
2452
|
-
|
2708
|
+
MU.log "Momma Cat daemon not running", MU::NOTICE, details: daemonPidFile
|
2709
|
+
false
|
2710
|
+
end
|
2711
|
+
|
2712
|
+
# Stop the Momma Cat daemon, if it's running
|
2713
|
+
def self.stop
|
2714
|
+
if File.exist?(daemonPidFile)
|
2715
|
+
pid = File.read(daemonPidFile).chomp.to_i
|
2716
|
+
MU.log "Stopping Momma Cat with pid #{pid.to_s}"
|
2717
|
+
Process.kill("INT", pid)
|
2718
|
+
killed = false
|
2719
|
+
begin
|
2720
|
+
Process.getpgid(pid)
|
2721
|
+
sleep 1
|
2722
|
+
rescue Errno::ESRCH
|
2723
|
+
killed = true
|
2724
|
+
end while killed
|
2725
|
+
MU.log "Momma Cat with pid #{pid.to_s} stopped", MU::DEBUG, details: daemonPidFile
|
2726
|
+
|
2727
|
+
begin
|
2728
|
+
File.unlink(daemonPidFile)
|
2729
|
+
rescue Errno::ENOENT
|
2730
|
+
end
|
2453
2731
|
end
|
2454
|
-
deploy_path = File.expand_path(path+"/"+deploy_id)
|
2455
|
-
return Dir.exist?(deploy_path)
|
2456
2732
|
end
|
2457
2733
|
|
2734
|
+
# (Re)start the Momma Cat daemon and return the exit status of the start command
|
2735
|
+
# @return [Integer]
|
2736
|
+
def self.restart
|
2737
|
+
stop
|
2738
|
+
start
|
2739
|
+
end
|
2458
2740
|
|
2459
|
-
|
2460
|
-
|
2461
|
-
|
2462
|
-
|
2741
|
+
# Locate and return the deploy, if any, which matches the provided origin
|
2742
|
+
# description
|
2743
|
+
# @param origin [Hash]
|
2744
|
+
def self.findMatchingDeploy(origin)
|
2745
|
+
MU::MommaCat.listDeploys.each { |deploy_id|
|
2746
|
+
o_path = deploy_dir(deploy_id)+"/origin.json"
|
2747
|
+
next if !File.exist?(o_path)
|
2748
|
+
this_origin = JSON.parse(File.read(o_path))
|
2749
|
+
if origin == this_origin
|
2750
|
+
MU.log "Deploy #{deploy_id} matches origin hash, loading", details: origin
|
2751
|
+
return MU::MommaCat.new(deploy_id)
|
2752
|
+
end
|
2753
|
+
}
|
2754
|
+
nil
|
2463
2755
|
end
|
2464
2756
|
|
2465
2757
|
# Synchronize all in-memory information related to this to deployment to
|
2466
2758
|
# disk.
|
2467
|
-
|
2468
|
-
|
2759
|
+
# @param triggering_node [MU::Cloud::Server]: If we're being triggered by the addition/removal/update of a node, this allows us to notify any sibling or dependent nodes of changes
|
2760
|
+
# @param force [Boolean]: Save even if +no_artifacts+ is set
|
2761
|
+
# @param origin [Hash]: Optional blob of data indicating how this deploy was created
|
2762
|
+
def save!(triggering_node = nil, force: false, origin: nil)
|
2763
|
+
|
2764
|
+
return if @no_artifacts and !force
|
2469
2765
|
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
2470
2766
|
MU.log "Saving deployment #{MU.deploy_id}", MU::DEBUG
|
2471
2767
|
|
@@ -2474,6 +2770,12 @@ MESSAGE_END
|
|
2474
2770
|
Dir.mkdir(deploy_dir, 0700)
|
2475
2771
|
end
|
2476
2772
|
|
2773
|
+
if !origin.nil?
|
2774
|
+
o_file = File.new("#{deploy_dir}/origin.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2775
|
+
o_file.puts JSON.pretty_generate(origin)
|
2776
|
+
o_file.close
|
2777
|
+
end
|
2778
|
+
|
2477
2779
|
if !@private_key.nil?
|
2478
2780
|
privkey = File.new("#{deploy_dir}/private_key", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2479
2781
|
privkey.puts @private_key
|
@@ -2489,6 +2791,11 @@ MESSAGE_END
|
|
2489
2791
|
if !@deployment.nil? and @deployment.size > 0
|
2490
2792
|
@deployment['handle'] = MU.handle if @deployment['handle'].nil? and !MU.handle.nil?
|
2491
2793
|
@deployment['public_key'] = @public_key
|
2794
|
+
@deployment['timestamp'] ||= @timestamp
|
2795
|
+
@deployment['seed'] ||= @seed
|
2796
|
+
@deployment['appname'] ||= @appname
|
2797
|
+
@deployment['handle'] ||= @handle
|
2798
|
+
@deployment['ssh_public_key'] ||= @ssh_public_key if @ssh_public_key
|
2492
2799
|
begin
|
2493
2800
|
# XXX doing this to trigger JSON errors before stomping the stored
|
2494
2801
|
# file...
|
@@ -2498,15 +2805,20 @@ MESSAGE_END
|
|
2498
2805
|
deploy.flock(File::LOCK_EX)
|
2499
2806
|
deploy.puts JSON.pretty_generate(@deployment, max_nesting: false)
|
2500
2807
|
rescue JSON::NestingError => e
|
2501
|
-
|
2808
|
+
MU.log e.inspect, MU::ERR, details: @deployment
|
2809
|
+
raise MuError, "Got #{e.message} trying to save deployment"
|
2810
|
+
rescue Encoding::UndefinedConversionError => e
|
2811
|
+
MU.log e.inspect, MU::ERR, details: @deployment
|
2812
|
+
raise MuError, "Got #{e.message} at #{e.error_char.dump} (#{e.source_encoding_name} => #{e.destination_encoding_name}) trying to save deployment"
|
2502
2813
|
end
|
2503
2814
|
deploy.flock(File::LOCK_UN)
|
2504
2815
|
deploy.close
|
2816
|
+
@need_deploy_flush = false
|
2505
2817
|
end
|
2506
2818
|
|
2507
2819
|
if !@original_config.nil? and @original_config.is_a?(Hash)
|
2508
2820
|
config = File.new("#{deploy_dir}/basket_of_kittens.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2509
|
-
config.puts JSON.pretty_generate(@original_config)
|
2821
|
+
config.puts JSON.pretty_generate(MU::Config.manxify(@original_config))
|
2510
2822
|
config.close
|
2511
2823
|
end
|
2512
2824
|
|
@@ -2541,10 +2853,10 @@ MESSAGE_END
|
|
2541
2853
|
MU.log "Creating #{secretdir}", MU::DEBUG
|
2542
2854
|
Dir.mkdir(secretdir, 0700)
|
2543
2855
|
end
|
2544
|
-
@secrets.each_pair { |type,
|
2545
|
-
|
2856
|
+
@secrets.each_pair { |type, servers|
|
2857
|
+
servers.each_pair { |server, svr_secret|
|
2546
2858
|
key = File.new("#{secretdir}/#{type}.#{server}", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2547
|
-
key.puts
|
2859
|
+
key.puts svr_secret
|
2548
2860
|
key.close
|
2549
2861
|
}
|
2550
2862
|
}
|
@@ -2552,7 +2864,7 @@ MESSAGE_END
|
|
2552
2864
|
}
|
2553
2865
|
|
2554
2866
|
# Update groomer copies of this metadata
|
2555
|
-
syncLitter(@deployment['servers'].keys,
|
2867
|
+
syncLitter(@deployment['servers'].keys, triggering_node: triggering_node, save_only: true) if @deployment.has_key?("servers")
|
2556
2868
|
end
|
2557
2869
|
|
2558
2870
|
# Find one or more resources by their Mu resource name, and return
|
@@ -2562,21 +2874,112 @@ MESSAGE_END
|
|
2562
2874
|
# @param type [String]: The type of resource, e.g. "vpc" or "server."
|
2563
2875
|
# @param name [String]: The Mu resource class, typically the name field of a Basket of Kittens resource declaration.
|
2564
2876
|
# @param mu_name [String]: The fully-expanded Mu resource name, e.g. MGMT-PROD-2015040115-FR-ADMGMT2
|
2877
|
+
|
2878
|
+
private
|
2879
|
+
|
2880
|
+
# Check to see whether a given resource name is unique across all
|
2881
|
+
# deployments on this Mu server. We only enforce this for certain classes
|
2882
|
+
# of names. If the name in question is available, add it to our cache of
|
2883
|
+
# said names. See #{MU::MommaCat.getResourceName}
|
2884
|
+
# @param name [String]: The name to attempt to allocate.
|
2885
|
+
# @return [Boolean]: True if allocation was successful.
|
2886
|
+
def allocateUniqueResourceName(name)
|
2887
|
+
raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
|
2888
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
2889
|
+
File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
|
2890
|
+
existing = []
|
2891
|
+
f.flock(File::LOCK_EX)
|
2892
|
+
f.readlines.each { |line|
|
2893
|
+
existing << line.chomp
|
2894
|
+
}
|
2895
|
+
begin
|
2896
|
+
existing.each { |used|
|
2897
|
+
if used.match(/^#{name}:/)
|
2898
|
+
if !used.match(/^#{name}:#{@deploy_id}$/)
|
2899
|
+
MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
|
2900
|
+
return false
|
2901
|
+
else
|
2902
|
+
return true
|
2903
|
+
end
|
2904
|
+
end
|
2905
|
+
}
|
2906
|
+
f.puts name+":"+@deploy_id
|
2907
|
+
return true
|
2908
|
+
ensure
|
2909
|
+
f.flock(File::LOCK_UN)
|
2910
|
+
end
|
2911
|
+
}
|
2912
|
+
end
|
2913
|
+
|
2914
|
+
###########################################################################
|
2915
|
+
###########################################################################
|
2916
|
+
def self.deploy_dir(deploy_id)
|
2917
|
+
raise MuError, "deploy_dir must get a deploy_id if called as class method (from #{caller[0]}; #{caller[1]})" if deploy_id.nil?
|
2918
|
+
# XXX this will blow up if someone sticks MU in /
|
2919
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
2920
|
+
if !Dir.exist?(path)
|
2921
|
+
MU.log "Creating #{path}", MU::DEBUG
|
2922
|
+
Dir.mkdir(path, 0700)
|
2923
|
+
end
|
2924
|
+
path = path+"/"+deploy_id
|
2925
|
+
return path
|
2926
|
+
end
|
2927
|
+
|
2928
|
+
def self.deploy_exists?(deploy_id)
|
2929
|
+
if deploy_id.nil? or deploy_id.empty?
|
2930
|
+
MU.log "Got nil deploy_id in MU::MommaCat.deploy_exists?", MU::WARN
|
2931
|
+
return
|
2932
|
+
end
|
2933
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
2934
|
+
if !Dir.exist?(path)
|
2935
|
+
Dir.mkdir(path, 0700)
|
2936
|
+
end
|
2937
|
+
deploy_path = File.expand_path(path+"/"+deploy_id)
|
2938
|
+
return Dir.exist?(deploy_path)
|
2939
|
+
end
|
2940
|
+
|
2941
|
+
|
2942
|
+
def createDeployKey
|
2943
|
+
key = OpenSSL::PKey::RSA.generate(4096)
|
2944
|
+
MU.log "Generated deploy key for #{MU.deploy_id}", MU::DEBUG, details: key.public_key.export
|
2945
|
+
return [key.export, key.public_key.export]
|
2946
|
+
end
|
2947
|
+
|
2565
2948
|
# @param deploy_id [String]: The deployment to search. Will search all deployments if not specified.
|
2566
2949
|
# @return [Hash,Array<Hash>]
|
2567
2950
|
def self.getResourceMetadata(type, name: nil, deploy_id: nil, use_cache: true, mu_name: nil)
|
2568
2951
|
if type.nil?
|
2569
2952
|
raise MuError, "Can't call getResourceMetadata without a type argument"
|
2570
2953
|
end
|
2571
|
-
|
2572
|
-
|
2954
|
+
_shortclass, _cfg_name, type, _classname = MU::Cloud.getResourceNames(type)
|
2955
|
+
|
2956
|
+
# first, check our in-memory deploys, which may or may not have been
|
2957
|
+
# written to disk yet.
|
2958
|
+
littercache = nil
|
2959
|
+
begin
|
2960
|
+
@@litter_semaphore.synchronize {
|
2961
|
+
littercache = @@litters.dup
|
2962
|
+
}
|
2963
|
+
rescue ThreadError => e
|
2964
|
+
# already locked by a parent caller and this is a read op, so this is ok
|
2965
|
+
raise e if !e.message.match(/recursive locking/)
|
2966
|
+
littercache = @@litters.dup
|
2967
|
+
end
|
2968
|
+
littercache.each_pair { |deploy, momma|
|
2969
|
+
@@deploy_struct_semaphore.synchronize {
|
2970
|
+
@deploy_cache[deploy] = {
|
2971
|
+
"mtime" => Time.now,
|
2972
|
+
"data" => momma.deployment
|
2973
|
+
}
|
2974
|
+
}
|
2975
|
+
}
|
2573
2976
|
|
2574
2977
|
deploy_root = File.expand_path(MU.dataDir+"/deployments")
|
2575
2978
|
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
2576
|
-
if Dir.
|
2979
|
+
if Dir.exist?(deploy_root)
|
2577
2980
|
Dir.entries(deploy_root).each { |deploy|
|
2578
2981
|
this_deploy_dir = deploy_root+"/"+deploy
|
2579
|
-
next if deploy == "." or deploy == ".." or !Dir.
|
2982
|
+
next if deploy == "." or deploy == ".." or !Dir.exist?(this_deploy_dir)
|
2580
2983
|
next if deploy_id and deploy_id != deploy
|
2581
2984
|
|
2582
2985
|
if !File.size?(this_deploy_dir+"/deployment.json")
|
@@ -2605,7 +3008,7 @@ MESSAGE_END
|
|
2605
3008
|
# Populate some generable entries that should be in the deploy
|
2606
3009
|
# data. Also, bounce out if we realize we've found exactly what
|
2607
3010
|
# we needed already.
|
2608
|
-
MU::Cloud.resource_types.
|
3011
|
+
MU::Cloud.resource_types.values.each { |attrs|
|
2609
3012
|
|
2610
3013
|
next if @deploy_cache[deploy]['data'][attrs[:cfg_plural]].nil?
|
2611
3014
|
if !attrs[:has_multiples]
|
@@ -2650,10 +3053,10 @@ MESSAGE_END
|
|
2650
3053
|
next if !@deploy_cache[deploy]['data'].has_key?(type)
|
2651
3054
|
if !name.nil?
|
2652
3055
|
next if @deploy_cache[deploy]['data'][type][name].nil?
|
2653
|
-
matches[deploy]
|
3056
|
+
matches[deploy] ||= []
|
2654
3057
|
matches[deploy] << @deploy_cache[deploy]['data'][type][name].dup
|
2655
3058
|
else
|
2656
|
-
matches[deploy]
|
3059
|
+
matches[deploy] ||= []
|
2657
3060
|
matches[deploy].concat(@deploy_cache[deploy]['data'][type].values)
|
2658
3061
|
end
|
2659
3062
|
}
|
@@ -2663,7 +3066,7 @@ MESSAGE_END
|
|
2663
3066
|
!@deploy_cache[deploy_id]['data'][type].nil?
|
2664
3067
|
if !name.nil?
|
2665
3068
|
if !@deploy_cache[deploy_id]['data'][type][name].nil?
|
2666
|
-
matches[deploy_id]
|
3069
|
+
matches[deploy_id] ||= []
|
2667
3070
|
matches[deploy_id] << @deploy_cache[deploy_id]['data'][type][name].dup
|
2668
3071
|
else
|
2669
3072
|
return matches # nothing, actually
|
@@ -2694,13 +3097,14 @@ MESSAGE_END
|
|
2694
3097
|
begin
|
2695
3098
|
@deployment = JSON.parse(File.read("#{deploy_dir}/deployment.json"))
|
2696
3099
|
rescue JSON::ParserError => e
|
2697
|
-
MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR
|
3100
|
+
MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR, details: e.message
|
2698
3101
|
end
|
2699
3102
|
|
2700
3103
|
deploy.flock(File::LOCK_UN)
|
2701
3104
|
deploy.close
|
2702
3105
|
if set_context_to_me
|
2703
3106
|
["appname", "environment", "timestamp", "seed", "handle"].each { |var|
|
3107
|
+
@deployment[var] ||= instance_variable_get("@#{var}".to_sym)
|
2704
3108
|
if @deployment[var]
|
2705
3109
|
if var != "handle"
|
2706
3110
|
MU.setVar(var, @deployment[var].upcase)
|
@@ -2727,7 +3131,7 @@ MESSAGE_END
|
|
2727
3131
|
begin
|
2728
3132
|
@original_config = JSON.parse(File.read("#{deploy_dir}/basket_of_kittens.json"))
|
2729
3133
|
rescue JSON::ParserError => e
|
2730
|
-
MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR
|
3134
|
+
MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR, details: e.message
|
2731
3135
|
end
|
2732
3136
|
end
|
2733
3137
|
if File.exist?(deploy_dir+"/ssh_key_name")
|
@@ -2748,7 +3152,7 @@ MESSAGE_END
|
|
2748
3152
|
if Dir.exist?("#{deploy_dir}/secrets")
|
2749
3153
|
@secrets.each_key { |type|
|
2750
3154
|
Dir.glob("#{deploy_dir}/secrets/#{type}.*") { |filename|
|
2751
|
-
|
3155
|
+
server = File.basename(filename).split(/\./)[1]
|
2752
3156
|
|
2753
3157
|
@secrets[type][server] = File.read(filename).chomp!
|
2754
3158
|
}
|
@@ -2757,9 +3161,10 @@ MESSAGE_END
|
|
2757
3161
|
}
|
2758
3162
|
end
|
2759
3163
|
|
2760
|
-
|
2761
|
-
@
|
2762
|
-
@
|
3164
|
+
# 2019-06-03 adding things from https://aiweirdness.com/post/185339301987/once-again-a-neural-net-tries-to-name-cats
|
3165
|
+
@catadjs = %w{fuzzy ginger lilac chocolate xanthic wiggly itty chonky norty slonky floofy}
|
3166
|
+
@catnouns = %w{bastet biscuits bobcat catnip cheetah chonk dot felix hamb jaguar kitty leopard lion lynx maru mittens moggy neko nip ocelot panther patches paws phoebe purr queen roar saber sekhmet skogkatt socks sphinx spot tail tiger tom whiskers wildcat yowl floof beans ailurophile dander dewclaw grimalkin kibble quick tuft misty simba slonk mew quat eek ziggy whiskeridoo cromch monch screm}
|
3167
|
+
@catmixed = %w{abyssinian angora bengal birman bobtail bombay burmese calico chartreux cheshire cornish-rex curl devon egyptian-mau feline furever fumbs havana himilayan japanese-bobtail javanese khao-manee maine-coon manx marmalade mau munchkin norwegian pallas persian peterbald polydactyl ragdoll russian-blue savannah scottish-fold serengeti shorthair siamese siberian singapura snowshoe stray tabby tonkinese tortoiseshell turkish-van tuxedo uncia caterwaul lilac-point chocolate-point mackerel maltese knead whitenose vorpal chewie-bean chicken-whiskey fish-especially thelonious-monsieur tom-glitter serendipitous-kill sparky-buttons}
|
2763
3168
|
@catwords = @catadjs + @catnouns + @catmixed
|
2764
3169
|
|
2765
3170
|
@jaegeradjs = %w{azure fearless lucky olive vivid electric grey yarely violet ivory jade cinnamon crimson tacit umber mammoth ultra iron zodiac}
|
@@ -2771,4 +3176,3 @@ MESSAGE_END
|
|
2771
3176
|
|
2772
3177
|
end #class
|
2773
3178
|
end #module
|
2774
|
-
|