cloud-mu 1.9.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Berksfile +56 -0
- data/Berksfile.lock +250 -0
- data/Jenkinsfile +184 -0
- data/LICENSE.md +37 -0
- data/README.md +26 -0
- data/bin/mu-aws-setup +376 -0
- data/bin/mu-cleanup +68 -0
- data/bin/mu-configure +1133 -0
- data/bin/mu-deploy +166 -0
- data/bin/mu-firewall-allow-clients +30 -0
- data/bin/mu-gcp-setup +200 -0
- data/bin/mu-gen-docs +34 -0
- data/bin/mu-gen-env +42 -0
- data/bin/mu-load-config.rb +158 -0
- data/bin/mu-node-manage +683 -0
- data/bin/mu-self-update +228 -0
- data/bin/mu-ssh +23 -0
- data/bin/mu-tunnel-nagios +144 -0
- data/bin/mu-upload-chef-artifacts +757 -0
- data/bin/mu-user-manage +275 -0
- data/cookbooks/awscli/LICENSE +37 -0
- data/cookbooks/awscli/README.md +58 -0
- data/cookbooks/awscli/attributes/default.rb +1 -0
- data/cookbooks/awscli/libraries/instance_metadata.rb +21 -0
- data/cookbooks/awscli/metadata.rb +20 -0
- data/cookbooks/awscli/recipes/default.rb +56 -0
- data/cookbooks/awscli/templates/default/config.erb +18 -0
- data/cookbooks/mu-activedirectory/CHANGELOG.md +13 -0
- data/cookbooks/mu-activedirectory/LICENSE +37 -0
- data/cookbooks/mu-activedirectory/README.md +6 -0
- data/cookbooks/mu-activedirectory/attributes/default.rb +98 -0
- data/cookbooks/mu-activedirectory/files/default/password-auth +32 -0
- data/cookbooks/mu-activedirectory/files/default/sshd_pol.pp +0 -0
- data/cookbooks/mu-activedirectory/files/default/sshd_pol.te +32 -0
- data/cookbooks/mu-activedirectory/files/default/syslogd_oddjobd.pp +0 -0
- data/cookbooks/mu-activedirectory/files/default/syslogd_oddjobd.te +10 -0
- data/cookbooks/mu-activedirectory/files/default/system-auth +34 -0
- data/cookbooks/mu-activedirectory/files/default/winbindpol.pp +0 -0
- data/cookbooks/mu-activedirectory/files/default/winbindpol.te +37 -0
- data/cookbooks/mu-activedirectory/libraries/config.rb +106 -0
- data/cookbooks/mu-activedirectory/libraries/helper.rb +86 -0
- data/cookbooks/mu-activedirectory/metadata.rb +17 -0
- data/cookbooks/mu-activedirectory/providers/domain.rb +152 -0
- data/cookbooks/mu-activedirectory/providers/domain_controller.rb +89 -0
- data/cookbooks/mu-activedirectory/providers/domain_node.rb +275 -0
- data/cookbooks/mu-activedirectory/recipes/default.rb +8 -0
- data/cookbooks/mu-activedirectory/recipes/domain-controller.rb +44 -0
- data/cookbooks/mu-activedirectory/recipes/domain-node.rb +50 -0
- data/cookbooks/mu-activedirectory/recipes/domain.rb +43 -0
- data/cookbooks/mu-activedirectory/recipes/sssd.rb +185 -0
- data/cookbooks/mu-activedirectory/resources/domain.rb +25 -0
- data/cookbooks/mu-activedirectory/resources/domain_controller.rb +25 -0
- data/cookbooks/mu-activedirectory/resources/domain_node.rb +20 -0
- data/cookbooks/mu-activedirectory/templates/default/dhclient-eth0.conf.erb +4 -0
- data/cookbooks/mu-activedirectory/templates/default/interface +0 -0
- data/cookbooks/mu-activedirectory/templates/default/krb5.conf.erb +23 -0
- data/cookbooks/mu-activedirectory/templates/default/ntp.conf.erb +56 -0
- data/cookbooks/mu-activedirectory/templates/default/smb.conf.erb +33 -0
- data/cookbooks/mu-activedirectory/templates/default/sssd.conf.erb +60 -0
- data/cookbooks/mu-activedirectory/templates/windows/Backup.xml.erb +20 -0
- data/cookbooks/mu-activedirectory/templates/windows/bkupInfo.xml.erb +1 -0
- data/cookbooks/mu-activedirectory/templates/windows/gpreprt.xml.erb +198 -0
- data/cookbooks/mu-activedirectory/templates/windows/gptmpl.inf.erb +12 -0
- data/cookbooks/mu-activedirectory/templates/windows/manifest.xml.erb +1 -0
- data/cookbooks/mu-firewall/CHANGELOG.md +11 -0
- data/cookbooks/mu-firewall/LICENSE +37 -0
- data/cookbooks/mu-firewall/README.md +5 -0
- data/cookbooks/mu-firewall/attributes/default.rb +3 -0
- data/cookbooks/mu-firewall/metadata.rb +16 -0
- data/cookbooks/mu-firewall/recipes/default.rb +10 -0
- data/cookbooks/mu-glusterfs/CHANGELOG.md +13 -0
- data/cookbooks/mu-glusterfs/LICENSE +37 -0
- data/cookbooks/mu-glusterfs/README.md +5 -0
- data/cookbooks/mu-glusterfs/attributes/default.rb +34 -0
- data/cookbooks/mu-glusterfs/metadata.rb +17 -0
- data/cookbooks/mu-glusterfs/recipes/client.rb +62 -0
- data/cookbooks/mu-glusterfs/recipes/default.rb +16 -0
- data/cookbooks/mu-glusterfs/recipes/samba.rb +57 -0
- data/cookbooks/mu-glusterfs/recipes/server.rb +200 -0
- data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +71 -0
- data/cookbooks/mu-glusterfs/templates/default/smb.conf.erb +14 -0
- data/cookbooks/mu-jenkins/CHANGELOG.md +13 -0
- data/cookbooks/mu-jenkins/LICENSE +37 -0
- data/cookbooks/mu-jenkins/README.md +105 -0
- data/cookbooks/mu-jenkins/attributes/default.rb +42 -0
- data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +73 -0
- data/cookbooks/mu-jenkins/files/default/deploy_config.xml +44 -0
- data/cookbooks/mu-jenkins/metadata.rb +21 -0
- data/cookbooks/mu-jenkins/recipes/default.rb +195 -0
- data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +54 -0
- data/cookbooks/mu-jenkins/recipes/public_key.rb +24 -0
- data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +24 -0
- data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +14 -0
- data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +6 -0
- data/cookbooks/mu-master/CHANGELOG.md +13 -0
- data/cookbooks/mu-master/LICENSE +37 -0
- data/cookbooks/mu-master/README.md +6 -0
- data/cookbooks/mu-master/attributes/default.rb +95 -0
- data/cookbooks/mu-master/files/default/0-mu-log-server.conf +19 -0
- data/cookbooks/mu-master/files/default/addRSA.ldif +8 -0
- data/cookbooks/mu-master/files/default/check_mem.pl +197 -0
- data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
- data/cookbooks/mu-master/files/default/dirsrv_admin.pp +0 -0
- data/cookbooks/mu-master/files/default/dirsrv_admin.te +13 -0
- data/cookbooks/mu-master/files/default/nagios_selinux.pp +0 -0
- data/cookbooks/mu-master/files/default/nagios_selinux.te +51 -0
- data/cookbooks/mu-master/files/default/nagios_selinux_7.pp +0 -0
- data/cookbooks/mu-master/files/default/nagios_selinux_7.te +17 -0
- data/cookbooks/mu-master/files/default/pam_sshd +18 -0
- data/cookbooks/mu-master/files/default/ssl_enable.ldif +18 -0
- data/cookbooks/mu-master/files/default/syslogd_oddjobd.pp +0 -0
- data/cookbooks/mu-master/files/default/syslogd_oddjobd.te +10 -0
- data/cookbooks/mu-master/files/default/vimrc +19 -0
- data/cookbooks/mu-master/libraries/mu.rb +29 -0
- data/cookbooks/mu-master/metadata.rb +30 -0
- data/cookbooks/mu-master/providers/user.rb +41 -0
- data/cookbooks/mu-master/recipes/389ds.rb +164 -0
- data/cookbooks/mu-master/recipes/basepackages.rb +58 -0
- data/cookbooks/mu-master/recipes/caching_nameserver.rb +37 -0
- data/cookbooks/mu-master/recipes/default.rb +451 -0
- data/cookbooks/mu-master/recipes/eks-kubectl.rb +41 -0
- data/cookbooks/mu-master/recipes/firewall-holes.rb +70 -0
- data/cookbooks/mu-master/recipes/init.rb +542 -0
- data/cookbooks/mu-master/recipes/ssl-certs.rb +109 -0
- data/cookbooks/mu-master/recipes/sssd.rb +89 -0
- data/cookbooks/mu-master/recipes/update_nagios_only.rb +242 -0
- data/cookbooks/mu-master/recipes/vault.rb +111 -0
- data/cookbooks/mu-master/resources/user.rb +19 -0
- data/cookbooks/mu-master/templates/default/389-directory-setup.inf.erb +28 -0
- data/cookbooks/mu-master/templates/default/chef-server.rb.erb +18 -0
- data/cookbooks/mu-master/templates/default/dhclient-eth0.conf.erb +9 -0
- data/cookbooks/mu-master/templates/default/mu-momma-cat.erb +149 -0
- data/cookbooks/mu-master/templates/default/mu.rc.erb +9 -0
- data/cookbooks/mu-master/templates/default/openssl.cnf.erb +354 -0
- data/cookbooks/mu-master/templates/default/sssd.conf.erb +44 -0
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +90 -0
- data/cookbooks/mu-mongo/CHANGELOG.md +13 -0
- data/cookbooks/mu-mongo/LICENSE +37 -0
- data/cookbooks/mu-mongo/README.md +5 -0
- data/cookbooks/mu-mongo/attributes/default.rb +22 -0
- data/cookbooks/mu-mongo/files/default/keyfile +16 -0
- data/cookbooks/mu-mongo/files/default/remove_nodes.js +5 -0
- data/cookbooks/mu-mongo/metadata.rb +17 -0
- data/cookbooks/mu-mongo/recipes/default.rb +149 -0
- data/cookbooks/mu-mongo/recipes/yum-update-rule.rb +18 -0
- data/cookbooks/mu-mongo/templates/default/mongo_create_openfema_db.js.erb +2 -0
- data/cookbooks/mu-mongo/templates/default/mongo_init.js.erb +1 -0
- data/cookbooks/mu-mongo/templates/default/mongo_logrotate.erb +14 -0
- data/cookbooks/mu-mongo/templates/default/mongo_replset_addnodes.js.erb +6 -0
- data/cookbooks/mu-mongo/templates/default/replset_init.js.erb +2 -0
- data/cookbooks/mu-openvpn/CHANGELOG.md +13 -0
- data/cookbooks/mu-openvpn/LICENSE +37 -0
- data/cookbooks/mu-openvpn/README.md +6 -0
- data/cookbooks/mu-openvpn/attributes/default.rb +119 -0
- data/cookbooks/mu-openvpn/metadata.rb +18 -0
- data/cookbooks/mu-openvpn/recipes/default.rb +108 -0
- data/cookbooks/mu-openvpn/templates/default/users.json.erb +42 -0
- data/cookbooks/mu-php54/CHANGELOG.md +12 -0
- data/cookbooks/mu-php54/LICENSE +37 -0
- data/cookbooks/mu-php54/README.md +0 -0
- data/cookbooks/mu-php54/files/centos/php.ini +1802 -0
- data/cookbooks/mu-php54/files/ubuntu/php.ini +1870 -0
- data/cookbooks/mu-php54/metadata.rb +21 -0
- data/cookbooks/mu-php54/recipes/default.rb +97 -0
- data/cookbooks/mu-splunk/CHANGELOG.md +37 -0
- data/cookbooks/mu-splunk/LICENSE +37 -0
- data/cookbooks/mu-splunk/README.md +451 -0
- data/cookbooks/mu-splunk/attributes/default.rb +95 -0
- data/cookbooks/mu-splunk/attributes/upgrade.rb +49 -0
- data/cookbooks/mu-splunk/definitions/splunk_installer.rb +103 -0
- data/cookbooks/mu-splunk/files/default/splunk-nocheck +10 -0
- data/cookbooks/mu-splunk/libraries/helpers.rb +72 -0
- data/cookbooks/mu-splunk/libraries/splunk_app_provider.rb +156 -0
- data/cookbooks/mu-splunk/libraries/splunk_app_resource.rb +43 -0
- data/cookbooks/mu-splunk/metadata.json +30 -0
- data/cookbooks/mu-splunk/metadata.rb +17 -0
- data/cookbooks/mu-splunk/recipes/client.rb +143 -0
- data/cookbooks/mu-splunk/recipes/default.rb +31 -0
- data/cookbooks/mu-splunk/recipes/disabled.rb +41 -0
- data/cookbooks/mu-splunk/recipes/install_forwarder.rb +23 -0
- data/cookbooks/mu-splunk/recipes/install_server.rb +23 -0
- data/cookbooks/mu-splunk/recipes/server.rb +53 -0
- data/cookbooks/mu-splunk/recipes/service.rb +95 -0
- data/cookbooks/mu-splunk/recipes/setup_auth.rb +49 -0
- data/cookbooks/mu-splunk/recipes/setup_ssl.rb +63 -0
- data/cookbooks/mu-splunk/recipes/upgrade.rb +94 -0
- data/cookbooks/mu-splunk/recipes/user.rb +34 -0
- data/cookbooks/mu-splunk/templates/default/base_logs_unix_inputs.conf.erb +26 -0
- data/cookbooks/mu-splunk/templates/default/inputs.conf.erb +13 -0
- data/cookbooks/mu-splunk/templates/default/outputs.conf.erb +9 -0
- data/cookbooks/mu-splunk/templates/default/splunk-init.erb +74 -0
- data/cookbooks/mu-splunk/templates/default/system-web.conf.erb +7 -0
- data/cookbooks/mu-tools/CHANGELOG.md +12 -0
- data/cookbooks/mu-tools/LICENSE +37 -0
- data/cookbooks/mu-tools/README.md +188 -0
- data/cookbooks/mu-tools/attributes/default.rb +142 -0
- data/cookbooks/mu-tools/attributes/ebs_rolling_snapshots.rb +3 -0
- data/cookbooks/mu-tools/files/amazon/etc/freshclam.conf +235 -0
- data/cookbooks/mu-tools/files/centos/CentOS-Base.repo +52 -0
- data/cookbooks/mu-tools/files/centos/etc/bashrc +93 -0
- data/cookbooks/mu-tools/files/centos/etc/freshclam.conf +235 -0
- data/cookbooks/mu-tools/files/centos/etc/login.defs +72 -0
- data/cookbooks/mu-tools/files/centos/etc/profile +77 -0
- data/cookbooks/mu-tools/files/centos/etc/security/limits.conf +57 -0
- data/cookbooks/mu-tools/files/centos/etc/sysconfig/init +19 -0
- data/cookbooks/mu-tools/files/centos/etc/sysctl.conf +82 -0
- data/cookbooks/mu-tools/files/centos-6/README_MU +0 -0
- data/cookbooks/mu-tools/files/centos-6/etc/audit/stig.rules +173 -0
- data/cookbooks/mu-tools/files/centos-6/etc/bashrc +90 -0
- data/cookbooks/mu-tools/files/centos-6/etc/login.defs +70 -0
- data/cookbooks/mu-tools/files/centos-6/etc/pam.d/su +12 -0
- data/cookbooks/mu-tools/files/centos-6/etc/profile +83 -0
- data/cookbooks/mu-tools/files/centos-6/etc/securetty +12 -0
- data/cookbooks/mu-tools/files/centos-6/etc/sysconfig/init +30 -0
- data/cookbooks/mu-tools/files/centos-6/etc/sysctl.conf +40 -0
- data/cookbooks/mu-tools/files/default/Mu_CA.pem +34 -0
- data/cookbooks/mu-tools/files/default/PSWindowsUpdate.zip +0 -0
- data/cookbooks/mu-tools/files/default/ebs_snapshots.py +123 -0
- data/cookbooks/mu-tools/files/default/etc/BANNER +0 -0
- data/cookbooks/mu-tools/files/default/etc/BANNER-FEDERAL +19 -0
- data/cookbooks/mu-tools/files/default/gpo_no_uac.zip +0 -0
- data/cookbooks/mu-tools/files/default/mypol.pp +0 -0
- data/cookbooks/mu-tools/files/default/mypol.te +37 -0
- data/cookbooks/mu-tools/files/default/nrpe_c7.pp +0 -0
- data/cookbooks/mu-tools/files/default/nrpe_c7.te +31 -0
- data/cookbooks/mu-tools/files/default/nrpe_check_disk.pp +0 -0
- data/cookbooks/mu-tools/files/default/nrpe_check_disk.te +11 -0
- data/cookbooks/mu-tools/files/default/nrpe_disk.pp +0 -0
- data/cookbooks/mu-tools/files/default/nrpe_disk.te +10 -0
- data/cookbooks/mu-tools/files/default/nrpe_file.pp +0 -0
- data/cookbooks/mu-tools/files/default/nrpe_file.te +31 -0
- data/cookbooks/mu-tools/files/default/ntrights +0 -0
- data/cookbooks/mu-tools/files/default/serverclass.conf +18 -0
- data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_unix/local/app.conf +1 -0
- data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_unix/local/inputs.conf +13 -0
- data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_windows/local/app.conf +1 -0
- data/cookbooks/mu-tools/files/default/splunk-apps/base_logs_windows/local/inputs.conf +8 -0
- data/cookbooks/mu-tools/files/default/sshd_pol.pp +0 -0
- data/cookbooks/mu-tools/files/default/sshd_pol.te +32 -0
- data/cookbooks/mu-tools/files/redhat/etc/bashrc +93 -0
- data/cookbooks/mu-tools/files/redhat/etc/freshclam.conf +235 -0
- data/cookbooks/mu-tools/files/redhat/etc/login.defs +72 -0
- data/cookbooks/mu-tools/files/redhat/etc/profile +77 -0
- data/cookbooks/mu-tools/files/redhat/etc/security/limits.conf +57 -0
- data/cookbooks/mu-tools/files/redhat/etc/sysconfig/init +19 -0
- data/cookbooks/mu-tools/files/redhat/etc/sysctl.conf +82 -0
- data/cookbooks/mu-tools/files/redhat-6/README_MU +0 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/audit/stig.rules +173 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/bashrc +90 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/login.defs +70 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/pam.d/su +12 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/profile +83 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/securetty +12 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/sysconfig/init +30 -0
- data/cookbooks/mu-tools/files/redhat-6/etc/sysctl.conf +40 -0
- data/cookbooks/mu-tools/files/redhat-7.1/etc/freshclam.conf +235 -0
- data/cookbooks/mu-tools/files/ubuntu-12.04/etc/bash.bashrc +64 -0
- data/cookbooks/mu-tools/files/ubuntu-12.04/etc/common-session +30 -0
- data/cookbooks/mu-tools/files/ubuntu-12.04/etc/login.defs +338 -0
- data/cookbooks/mu-tools/files/ubuntu-12.04/etc/profile +30 -0
- data/cookbooks/mu-tools/files/ubuntu-12.04/etc/security/limits.conf +56 -0
- data/cookbooks/mu-tools/files/ubuntu-12.04/etc/sysctl.conf +60 -0
- data/cookbooks/mu-tools/libraries/helper.rb +292 -0
- data/cookbooks/mu-tools/metadata.rb +28 -0
- data/cookbooks/mu-tools/recipes/add_admin_ssh_keys.rb +35 -0
- data/cookbooks/mu-tools/recipes/apply_security.rb +440 -0
- data/cookbooks/mu-tools/recipes/aws_api.rb +23 -0
- data/cookbooks/mu-tools/recipes/base_repositories.rb +31 -0
- data/cookbooks/mu-tools/recipes/cisbenchmark.rb +59 -0
- data/cookbooks/mu-tools/recipes/clamav.rb +53 -0
- data/cookbooks/mu-tools/recipes/cloudinit.rb +58 -0
- data/cookbooks/mu-tools/recipes/configure_oracle_tools.rb +81 -0
- data/cookbooks/mu-tools/recipes/disable-requiretty.rb +22 -0
- data/cookbooks/mu-tools/recipes/ebs_rolling_snapshots.rb +75 -0
- data/cookbooks/mu-tools/recipes/efs.rb +70 -0
- data/cookbooks/mu-tools/recipes/eks.rb +160 -0
- data/cookbooks/mu-tools/recipes/gcloud.rb +98 -0
- data/cookbooks/mu-tools/recipes/google_api.rb +25 -0
- data/cookbooks/mu-tools/recipes/maldet.rb +67 -0
- data/cookbooks/mu-tools/recipes/nagios.rb +19 -0
- data/cookbooks/mu-tools/recipes/newclient.rb +23 -0
- data/cookbooks/mu-tools/recipes/nrpe.rb +115 -0
- data/cookbooks/mu-tools/recipes/python_pip.rb +35 -0
- data/cookbooks/mu-tools/recipes/retrieve_application.rb +51 -0
- data/cookbooks/mu-tools/recipes/rsyslog.rb +65 -0
- data/cookbooks/mu-tools/recipes/set_local_fw.rb +57 -0
- data/cookbooks/mu-tools/recipes/set_mu_hostname.rb +81 -0
- data/cookbooks/mu-tools/recipes/split_var_partitions.rb +86 -0
- data/cookbooks/mu-tools/recipes/splunk-client.rb +69 -0
- data/cookbooks/mu-tools/recipes/splunk-server.rb +104 -0
- data/cookbooks/mu-tools/recipes/store_inspec_attr.rb +8 -0
- data/cookbooks/mu-tools/recipes/updates.rb +96 -0
- data/cookbooks/mu-tools/recipes/windows-client.rb +202 -0
- data/cookbooks/mu-tools/resources/aws_windows.rb +33 -0
- data/cookbooks/mu-tools/resources/disk.rb +88 -0
- data/cookbooks/mu-tools/resources/mommacat_request.rb +11 -0
- data/cookbooks/mu-tools/resources/scheduled_tasks.rb +29 -0
- data/cookbooks/mu-tools/resources/sshd_service.rb +45 -0
- data/cookbooks/mu-tools/resources/windows_users.rb +242 -0
- data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +168 -0
- data/cookbooks/mu-tools/templates/centos-6/sshd_config.erb +212 -0
- data/cookbooks/mu-tools/templates/centos-7/sshd_config.erb +215 -0
- data/cookbooks/mu-tools/templates/default/0-mu-log-client.conf.erb +13 -0
- data/cookbooks/mu-tools/templates/default/conf.maldet.erb +137 -0
- data/cookbooks/mu-tools/templates/default/etc_hosts.erb +30 -0
- data/cookbooks/mu-tools/templates/default/etc_pamd_password-auth.erb +14 -0
- data/cookbooks/mu-tools/templates/default/etc_pamd_system-auth.erb +14 -0
- data/cookbooks/mu-tools/templates/default/etc_sysconfig_network.erb +12 -0
- data/cookbooks/mu-tools/templates/default/kubeconfig.erb +29 -0
- data/cookbooks/mu-tools/templates/default/kubelet.service.erb +35 -0
- data/cookbooks/mu-tools/templates/default/maldet_scanall.sh.erb +15 -0
- data/cookbooks/mu-tools/templates/default/nrpe.cfg.erb +233 -0
- data/cookbooks/mu-tools/templates/redhat-6/sshd_config.erb +213 -0
- data/cookbooks/mu-tools/templates/redhat-7/sshd_config.erb +215 -0
- data/cookbooks/mu-tools/templates/ubuntu-12.04/sshd_config.erb +146 -0
- data/cookbooks/mu-tools/templates/ubuntu-14.04/sshd_config.erb +145 -0
- data/cookbooks/mu-tools/templates/windows/Backup.xml.erb +20 -0
- data/cookbooks/mu-tools/templates/windows/bkupInfo.xml.erb +1 -0
- data/cookbooks/mu-tools/templates/windows/gpreprt.xml.erb +214 -0
- data/cookbooks/mu-tools/templates/windows/gptmpl.inf.erb +12 -0
- data/cookbooks/mu-tools/templates/windows/manifest.xml.erb +1 -0
- data/cookbooks/mu-tools/templates/windows/set_ad_dns_scheduled_task.ps1.erb +6 -0
- data/cookbooks/mu-tools/templates/windows/sshd_config.erb +136 -0
- data/cookbooks/mu-utility/CHANGELOG.md +12 -0
- data/cookbooks/mu-utility/LICENSE +37 -0
- data/cookbooks/mu-utility/README.md +6 -0
- data/cookbooks/mu-utility/attributes/default.rb +1 -0
- data/cookbooks/mu-utility/libraries/matchers.rb +21 -0
- data/cookbooks/mu-utility/metadata.rb +16 -0
- data/cookbooks/mu-utility/recipes/apt.rb +23 -0
- data/cookbooks/mu-utility/recipes/cleanup_image_helper.rb +118 -0
- data/cookbooks/mu-utility/recipes/iptables.rb +26 -0
- data/cookbooks/mu-utility/recipes/luks.rb +18 -0
- data/cookbooks/mu-utility/recipes/nat.rb +104 -0
- data/cookbooks/mu-utility/recipes/php.rb +33 -0
- data/cookbooks/mu-utility/recipes/rdp_gateway.rb +83 -0
- data/cookbooks/mu-utility/recipes/remi.rb +44 -0
- data/cookbooks/mu-utility/recipes/vim.rb +26 -0
- data/cookbooks/mu-utility/recipes/windows_basics.rb +37 -0
- data/cookbooks/mu-utility/recipes/zip.rb +26 -0
- data/cookbooks/mu-utility/templates/default/BundleConfig.xml.erb +34 -0
- data/cookbooks/mu-utility/templates/default/config.xml.erb +60 -0
- data/cookbooks/nagios/Berksfile +8 -0
- data/cookbooks/nagios/CHANGELOG.md +589 -0
- data/cookbooks/nagios/CONTRIBUTING.md +11 -0
- data/cookbooks/nagios/LICENSE +37 -0
- data/cookbooks/nagios/README.md +328 -0
- data/cookbooks/nagios/TESTING.md +2 -0
- data/cookbooks/nagios/attributes/config.rb +171 -0
- data/cookbooks/nagios/attributes/default.rb +228 -0
- data/cookbooks/nagios/chefignore +102 -0
- data/cookbooks/nagios/definitions/command.rb +33 -0
- data/cookbooks/nagios/definitions/contact.rb +33 -0
- data/cookbooks/nagios/definitions/contactgroup.rb +33 -0
- data/cookbooks/nagios/definitions/host.rb +33 -0
- data/cookbooks/nagios/definitions/hostdependency.rb +33 -0
- data/cookbooks/nagios/definitions/hostescalation.rb +34 -0
- data/cookbooks/nagios/definitions/hostgroup.rb +33 -0
- data/cookbooks/nagios/definitions/nagios_conf.rb +38 -0
- data/cookbooks/nagios/definitions/resource.rb +33 -0
- data/cookbooks/nagios/definitions/service.rb +33 -0
- data/cookbooks/nagios/definitions/servicedependency.rb +33 -0
- data/cookbooks/nagios/definitions/serviceescalation.rb +34 -0
- data/cookbooks/nagios/definitions/servicegroup.rb +33 -0
- data/cookbooks/nagios/definitions/timeperiod.rb +33 -0
- data/cookbooks/nagios/libraries/base.rb +314 -0
- data/cookbooks/nagios/libraries/command.rb +91 -0
- data/cookbooks/nagios/libraries/contact.rb +230 -0
- data/cookbooks/nagios/libraries/contactgroup.rb +112 -0
- data/cookbooks/nagios/libraries/custom_option.rb +36 -0
- data/cookbooks/nagios/libraries/data_bag_helper.rb +23 -0
- data/cookbooks/nagios/libraries/default.rb +90 -0
- data/cookbooks/nagios/libraries/host.rb +412 -0
- data/cookbooks/nagios/libraries/hostdependency.rb +181 -0
- data/cookbooks/nagios/libraries/hostescalation.rb +173 -0
- data/cookbooks/nagios/libraries/hostgroup.rb +119 -0
- data/cookbooks/nagios/libraries/nagios.rb +282 -0
- data/cookbooks/nagios/libraries/resource.rb +59 -0
- data/cookbooks/nagios/libraries/service.rb +455 -0
- data/cookbooks/nagios/libraries/servicedependency.rb +215 -0
- data/cookbooks/nagios/libraries/serviceescalation.rb +195 -0
- data/cookbooks/nagios/libraries/servicegroup.rb +144 -0
- data/cookbooks/nagios/libraries/timeperiod.rb +160 -0
- data/cookbooks/nagios/libraries/users_helper.rb +54 -0
- data/cookbooks/nagios/metadata.rb +25 -0
- data/cookbooks/nagios/recipes/_load_databag_config.rb +153 -0
- data/cookbooks/nagios/recipes/_load_default_config.rb +241 -0
- data/cookbooks/nagios/recipes/apache.rb +48 -0
- data/cookbooks/nagios/recipes/default.rb +204 -0
- data/cookbooks/nagios/recipes/nginx.rb +82 -0
- data/cookbooks/nagios/recipes/pagerduty.rb +143 -0
- data/cookbooks/nagios/recipes/server_package.rb +40 -0
- data/cookbooks/nagios/recipes/server_source.rb +164 -0
- data/cookbooks/nagios/templates/default/apache2.conf.erb +96 -0
- data/cookbooks/nagios/templates/default/cgi.cfg.erb +266 -0
- data/cookbooks/nagios/templates/default/commands.cfg.erb +13 -0
- data/cookbooks/nagios/templates/default/contacts.cfg.erb +37 -0
- data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +25 -0
- data/cookbooks/nagios/templates/default/hosts.cfg.erb +15 -0
- data/cookbooks/nagios/templates/default/htpasswd.users.erb +6 -0
- data/cookbooks/nagios/templates/default/nagios.cfg.erb +22 -0
- data/cookbooks/nagios/templates/default/nginx.conf.erb +62 -0
- data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +185 -0
- data/cookbooks/nagios/templates/default/resource.cfg.erb +27 -0
- data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +15 -0
- data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +14 -0
- data/cookbooks/nagios/templates/default/services.cfg.erb +14 -0
- data/cookbooks/nagios/templates/default/templates.cfg.erb +31 -0
- data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +13 -0
- data/cookbooks/s3fs/CHANGELOG.md +13 -0
- data/cookbooks/s3fs/LICENSE +37 -0
- data/cookbooks/s3fs/README.md +6 -0
- data/cookbooks/s3fs/attributes/default.rb +15 -0
- data/cookbooks/s3fs/files/default/fuse-2.9.3.zip +0 -0
- data/cookbooks/s3fs/metadata.rb +16 -0
- data/cookbooks/s3fs/recipes/default.rb +91 -0
- data/data_bags/demo/app.json +7 -0
- data/data_bags/nagios_services/chef.json +6 -0
- data/data_bags/nagios_services/linux_diskspace.json +5 -0
- data/data_bags/nagios_services/momma_cat.json +6 -0
- data/data_bags/nagios_services/mu-master-memory.json +5 -0
- data/data_bags/nagios_services/nagios_ui.json +6 -0
- data/data_bags/nagios_services/node_ssh.json +6 -0
- data/data_bags/nagios_services/ssh.json +6 -0
- data/demo/lambda_test.yaml +29 -0
- data/environments/DEV.json +8 -0
- data/environments/PROD.json +8 -0
- data/environments/dev.json +8 -0
- data/environments/development.json +8 -0
- data/environments/prod.json +8 -0
- data/extras/README.md +1 -0
- data/extras/admin-role-binding.yaml +16 -0
- data/extras/admin-user.yaml +6 -0
- data/extras/aws-auth-cm.yaml.erb +12 -0
- data/extras/clean-stock-amis +48 -0
- data/extras/git-fix-permissions-hook +12 -0
- data/extras/gitlab-eks-helper.sh.erb +20 -0
- data/extras/image-generators/README.md +2 -0
- data/extras/image-generators/aws/centos6.yaml +18 -0
- data/extras/image-generators/aws/centos7-govcloud.yaml +24 -0
- data/extras/image-generators/aws/centos7.yaml +17 -0
- data/extras/image-generators/aws/rhel7.yaml +17 -0
- data/extras/image-generators/aws/win2k12.yaml +16 -0
- data/extras/image-generators/aws/win2k16.yaml +16 -0
- data/extras/image-generators/aws/windows.yaml +18 -0
- data/extras/image-generators/gcp/centos6.yaml +17 -0
- data/extras/lambda_waf_domain_blacklist.py +103 -0
- data/extras/platform_berksfile_base +50 -0
- data/extras/ruby_rpm/build.sh +17 -0
- data/extras/ruby_rpm/muby.spec +44 -0
- data/extras/vault_tools/README.md +6 -0
- data/extras/vault_tools/export_vaults.sh +3 -0
- data/extras/vault_tools/recreate_vaults.sh +5 -0
- data/extras/vault_tools/test_vaults.sh +5 -0
- data/install/README.md +8 -0
- data/install/cfn_create_mu_master.json +1034 -0
- data/install/chef-server.rb.erb +19 -0
- data/install/deprecated-bash-library.sh +1891 -0
- data/install/images/Usage.png +0 -0
- data/install/installer +71 -0
- data/install/jenkinskeys.rb +8 -0
- data/install/user-dot-murc.erb +14 -0
- data/modules/html.erb +19 -0
- data/modules/mommacat.ru +426 -0
- data/modules/mu/cleanup.rb +339 -0
- data/modules/mu/cloud.rb +1446 -0
- data/modules/mu/clouds/README.md +201 -0
- data/modules/mu/clouds/aws/alarm.rb +319 -0
- data/modules/mu/clouds/aws/cache_cluster.rb +1010 -0
- data/modules/mu/clouds/aws/collection.rb +373 -0
- data/modules/mu/clouds/aws/container_cluster.rb +667 -0
- data/modules/mu/clouds/aws/database.rb +1836 -0
- data/modules/mu/clouds/aws/dnszone.rb +911 -0
- data/modules/mu/clouds/aws/firewall_rule.rb +641 -0
- data/modules/mu/clouds/aws/folder.rb +92 -0
- data/modules/mu/clouds/aws/function.rb +349 -0
- data/modules/mu/clouds/aws/group.rb +251 -0
- data/modules/mu/clouds/aws/loadbalancer.rb +888 -0
- data/modules/mu/clouds/aws/log.rb +363 -0
- data/modules/mu/clouds/aws/msg_queue.rb +480 -0
- data/modules/mu/clouds/aws/notification.rb +139 -0
- data/modules/mu/clouds/aws/role.rb +656 -0
- data/modules/mu/clouds/aws/search_domain.rb +646 -0
- data/modules/mu/clouds/aws/server.rb +2294 -0
- data/modules/mu/clouds/aws/server_pool.rb +1388 -0
- data/modules/mu/clouds/aws/storage_pool.rb +495 -0
- data/modules/mu/clouds/aws/user.rb +382 -0
- data/modules/mu/clouds/aws/userdata/README.md +4 -0
- data/modules/mu/clouds/aws/userdata/linux.erb +179 -0
- data/modules/mu/clouds/aws/userdata/windows.erb +278 -0
- data/modules/mu/clouds/aws/vpc.rb +1943 -0
- data/modules/mu/clouds/aws.rb +1009 -0
- data/modules/mu/clouds/cloudformation/alarm.rb +146 -0
- data/modules/mu/clouds/cloudformation/cache_cluster.rb +167 -0
- data/modules/mu/clouds/cloudformation/collection.rb +117 -0
- data/modules/mu/clouds/cloudformation/database.rb +278 -0
- data/modules/mu/clouds/cloudformation/dnszone.rb +274 -0
- data/modules/mu/clouds/cloudformation/firewall_rule.rb +308 -0
- data/modules/mu/clouds/cloudformation/loadbalancer.rb +193 -0
- data/modules/mu/clouds/cloudformation/log.rb +170 -0
- data/modules/mu/clouds/cloudformation/server.rb +370 -0
- data/modules/mu/clouds/cloudformation/server_pool.rb +279 -0
- data/modules/mu/clouds/cloudformation/vpc.rb +322 -0
- data/modules/mu/clouds/cloudformation.rb +733 -0
- data/modules/mu/clouds/docker.rb +30 -0
- data/modules/mu/clouds/google/container_cluster.rb +290 -0
- data/modules/mu/clouds/google/database.rb +152 -0
- data/modules/mu/clouds/google/firewall_rule.rb +267 -0
- data/modules/mu/clouds/google/group.rb +164 -0
- data/modules/mu/clouds/google/loadbalancer.rb +479 -0
- data/modules/mu/clouds/google/server.rb +1510 -0
- data/modules/mu/clouds/google/server_pool.rb +274 -0
- data/modules/mu/clouds/google/user.rb +266 -0
- data/modules/mu/clouds/google/userdata/README.md +4 -0
- data/modules/mu/clouds/google/userdata/linux.erb +137 -0
- data/modules/mu/clouds/google/userdata/windows.erb +275 -0
- data/modules/mu/clouds/google/vpc.rb +890 -0
- data/modules/mu/clouds/google.rb +811 -0
- data/modules/mu/config/README.md +11 -0
- data/modules/mu/config/alarm.rb +271 -0
- data/modules/mu/config/cache_cluster.rb +172 -0
- data/modules/mu/config/collection.rb +87 -0
- data/modules/mu/config/container_cluster.rb +103 -0
- data/modules/mu/config/container_cluster.yml +36 -0
- data/modules/mu/config/database.rb +458 -0
- data/modules/mu/config/database.yml +26 -0
- data/modules/mu/config/dnszone.rb +327 -0
- data/modules/mu/config/firewall_rule.rb +118 -0
- data/modules/mu/config/folder.rb +70 -0
- data/modules/mu/config/function.rb +140 -0
- data/modules/mu/config/group.rb +64 -0
- data/modules/mu/config/loadbalancer.rb +482 -0
- data/modules/mu/config/log.rb +47 -0
- data/modules/mu/config/log.yml +6 -0
- data/modules/mu/config/msg_queue.rb +47 -0
- data/modules/mu/config/msg_queue.yml +9 -0
- data/modules/mu/config/notification.rb +44 -0
- data/modules/mu/config/project.rb +71 -0
- data/modules/mu/config/role.rb +102 -0
- data/modules/mu/config/search_domain.rb +61 -0
- data/modules/mu/config/search_domain.yml +25 -0
- data/modules/mu/config/server.rb +587 -0
- data/modules/mu/config/server.yml +8 -0
- data/modules/mu/config/server_pool.rb +216 -0
- data/modules/mu/config/server_pool.yml +71 -0
- data/modules/mu/config/storage_pool.rb +145 -0
- data/modules/mu/config/user.rb +78 -0
- data/modules/mu/config/vpc.rb +743 -0
- data/modules/mu/config/vpc.yml +6 -0
- data/modules/mu/config.rb +2000 -0
- data/modules/mu/defaults/README.md +2 -0
- data/modules/mu/defaults/amazon_images.yaml +121 -0
- data/modules/mu/defaults/google_images.yaml +16 -0
- data/modules/mu/deploy.rb +686 -0
- data/modules/mu/groomer.rb +123 -0
- data/modules/mu/groomers/README.md +58 -0
- data/modules/mu/groomers/chef.rb +1024 -0
- data/modules/mu/kittens.rb +11319 -0
- data/modules/mu/logger.rb +208 -0
- data/modules/mu/master/README.md +27 -0
- data/modules/mu/master/chef.rb +471 -0
- data/modules/mu/master/ldap.rb +1005 -0
- data/modules/mu/master.rb +415 -0
- data/modules/mu/mommacat.rb +2703 -0
- data/modules/mu-load-config.rb +1 -0
- data/modules/mu.rb +724 -0
- data/modules/scratchpad.erb +1 -0
- data/modules/tests/super_complex_bok.yml +41 -0
- data/modules/tests/super_simple_bok.yml +40 -0
- data/mu.gemspec +62 -0
- data/roles/demo-dbservice-configure.json +19 -0
- data/roles/demo-portal-configure.json +19 -0
- data/roles/mu-master-jenkins.json +24 -0
- data/roles/mu-master-nagios-only.json +13 -0
- data/roles/mu-master.json +12 -0
- data/roles/mu-node.json +19 -0
- data/roles/mu-splunk-server.json +13 -0
- data/roles/mu-splunk.json +13 -0
- data/test/clean_up.py +25 -0
- data/test/demo-test-profile/README.md +3 -0
- data/test/demo-test-profile/controls/flask.rb +84 -0
- data/test/demo-test-profile/inspec.lock +7 -0
- data/test/demo-test-profile/inspec.yml +11 -0
- data/test/etco-test-profile/README.md +3 -0
- data/test/etco-test-profile/controls/all-in-one.rb +182 -0
- data/test/etco-test-profile/inspec.lock +7 -0
- data/test/etco-test-profile/inspec.yml +11 -0
- data/test/exec_inspec.py +246 -0
- data/test/exec_mu_install.py +241 -0
- data/test/exec_retry.py +44 -0
- data/test/mu-master-test/README.md +3 -0
- data/test/mu-master-test/controls/all_in_one.rb +557 -0
- data/test/mu-master-test/inspec.lock +3 -0
- data/test/mu-master-test/inspec.yml +11 -0
- data/test/mu-tools-test/README.md +3 -0
- data/test/mu-tools-test/controls/base.rb +265 -0
- data/test/mu-tools-test/inspec.lock +3 -0
- data/test/mu-tools-test/inspec.yml +8 -0
- data/test/simple-server-php-test/README.md +3 -0
- data/test/simple-server-php-test/controls/apachephp.rb +25 -0
- data/test/simple-server-php-test/controls/example.rb +19 -0
- data/test/simple-server-php-test/inspec.lock +7 -0
- data/test/simple-server-php-test/inspec.yml +12 -0
- data/test/simple-server-rails-test/README.md +3 -0
- data/test/simple-server-rails-test/controls/rails.rb +188 -0
- data/test/simple-server-rails-test/inspec.lock +7 -0
- data/test/simple-server-rails-test/inspec.yml +11 -0
- data/test/simple-windows-test/README.md +3 -0
- data/test/simple-windows-test/controls/windows.rb +20 -0
- data/test/simple-windows-test/inspec.lock +7 -0
- data/test/simple-windows-test/inspec.yml +11 -0
- data/test/smoke_test.rb +75 -0
- data/test/wordpress-test/README.md +3 -0
- data/test/wordpress-test/controls/wordpress.rb +97 -0
- data/test/wordpress-test/inspec.lock +7 -0
- data/test/wordpress-test/inspec.yml +11 -0
- metadata +979 -0
@@ -0,0 +1,2703 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
|
2
|
+
#
|
3
|
+
# Licensed under the BSD-3 license (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License in the root of the project or at
|
6
|
+
#
|
7
|
+
# http://egt-labs.com/mu/LICENSE.html
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
autoload :Net, 'net/ssh'
|
16
|
+
require 'fileutils'
|
17
|
+
require 'json'
|
18
|
+
require 'stringio'
|
19
|
+
require 'securerandom'
|
20
|
+
gem "chef"
|
21
|
+
autoload :Chef, 'chef'
|
22
|
+
gem "chef-vault"
|
23
|
+
autoload :ChefVault, 'chef-vault'
|
24
|
+
require 'timeout'
|
25
|
+
|
26
|
+
module MU
|
27
|
+
|
28
|
+
# MommaCat is in charge of managing metadata about resources we've created,
|
29
|
+
# as well as orchestrating amongst them and bootstrapping nodes outside of
|
30
|
+
# the normal synchronous deploy sequence invoked by *mu-deploy*.
|
31
|
+
class MommaCat
|
32
|
+
|
33
|
+
# An exception denoting a failure in MommaCat#fetchSecret and related methods
|
34
|
+
class SecretError < MuError;
|
35
|
+
end
|
36
|
+
|
37
|
+
# Failure to load or create a deploy
|
38
|
+
class DeployInitializeError < MuError;
|
39
|
+
end
|
40
|
+
|
41
|
+
# Failure to groom a node
|
42
|
+
class GroomError < MuError;
|
43
|
+
end
|
44
|
+
|
45
|
+
@@litters = {}
|
46
|
+
@@litter_semaphore = Mutex.new
|
47
|
+
|
48
|
+
# Return a {MU::MommaCat} instance for an existing deploy. Use this instead
|
49
|
+
# of using #initialize directly to avoid loading deploys multiple times or
|
50
|
+
# stepping on the global context for the deployment you're really working
|
51
|
+
# on..
|
52
|
+
# @param deploy_id [String]: The deploy ID of the deploy to load.
|
53
|
+
# @param set_context_to_me [Boolean]: Whether new MommaCat objects should overwrite any existing per-thread global deploy variables.
|
54
|
+
# @param use_cache [Boolean]: If we have an existing object for this deploy, use that
|
55
|
+
# @return [MU::MommaCat]
|
56
|
+
def self.getLitter(deploy_id, set_context_to_me: false, use_cache: true)
|
57
|
+
if deploy_id.nil? or deploy_id.empty?
|
58
|
+
raise MuError, "Cannot fetch a deployment without a deploy_id"
|
59
|
+
end
|
60
|
+
# XXX this caching may be harmful, causing stale resource objects to stick
|
61
|
+
# around. Have we fixed this? Sort of. Bad entries seem to have no kittens,
|
62
|
+
# so force a reload if we see that. That's probably not the root problem.
|
63
|
+
@@litter_semaphore.synchronize {
|
64
|
+
|
65
|
+
if !use_cache or !@@litters.has_key?(deploy_id) or @@litters[deploy_id].kittens.nil? or @@litters[deploy_id].kittens.size == 0
|
66
|
+
@@litters[deploy_id] = MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
|
67
|
+
elsif set_context_to_me
|
68
|
+
MU::MommaCat.setThreadContext(@@litters[deploy_id])
|
69
|
+
end
|
70
|
+
return @@litters[deploy_id]
|
71
|
+
}
|
72
|
+
# MU::MommaCat.new(deploy_id, set_context_to_me: set_context_to_me)
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :public_key
|
76
|
+
attr_reader :deploy_secret
|
77
|
+
attr_reader :deployment
|
78
|
+
attr_reader :original_config
|
79
|
+
attr_reader :environment
|
80
|
+
attr_reader :ssh_key_name
|
81
|
+
attr_reader :ssh_public_key
|
82
|
+
attr_reader :nocleanup
|
83
|
+
attr_reader :deploy_id
|
84
|
+
attr_reader :timestamp
|
85
|
+
attr_reader :appname
|
86
|
+
attr_reader :handle
|
87
|
+
attr_reader :seed
|
88
|
+
attr_reader :mu_user
|
89
|
+
attr_reader :clouds
|
90
|
+
attr_reader :chef_user
|
91
|
+
attr_reader :no_artifacts
|
92
|
+
attr_accessor :kittens # really want a method only available to :Deploy
|
93
|
+
@myhome = Etc.getpwuid(Process.uid).dir
|
94
|
+
@nagios_home = "/opt/mu/var/nagios_user_home"
|
95
|
+
@locks = Hash.new
|
96
|
+
@deploy_cache = Hash.new
|
97
|
+
@nocleanup = false
|
98
|
+
# List the currently held flock() locks.
|
99
|
+
def self.trapSafeLocks;
|
100
|
+
@locks
|
101
|
+
end
|
102
|
+
# List the currently held flock() locks.
|
103
|
+
def self.locks;
|
104
|
+
@lock_semaphore.synchronize {
|
105
|
+
@locks
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
@@deploy_struct_semaphore = Mutex.new
|
110
|
+
# Don't let things that modify the deploy struct Hash step on each other.
|
111
|
+
# @return [Mutex]
|
112
|
+
def self.deploy_struct_semaphore;
|
113
|
+
@@deploy_struct_semaphore
|
114
|
+
end
|
115
|
+
|
116
|
+
# Set the current threads' context (some knucklehead global variables) to
|
117
|
+
# values pertinent to the given deployment object.
|
118
|
+
# @param deploy [MU::MommaCat]: A deployment object
|
119
|
+
def self.setThreadContext(deploy)
|
120
|
+
raise MuError, "Didn't get a MU::MommaCat object in setThreadContext" if !deploy.is_a?(MU::MommaCat)
|
121
|
+
if !deploy.mu_user.nil?
|
122
|
+
MU.setVar("chef_user", deploy.chef_user)
|
123
|
+
if deploy.mu_user != "mu" and deploy.mu_user != "root"
|
124
|
+
MU.setVar("dataDir", Etc.getpwnam(deploy.mu_user).dir+"/.mu/var")
|
125
|
+
MU.setVar("mu_user", deploy.mu_user)
|
126
|
+
else
|
127
|
+
MU.setVar("dataDir", MU.mainDataDir)
|
128
|
+
MU.setVar("mu_user", "root")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
MU.setVar("mommacat", deploy)
|
132
|
+
MU.setVar("deploy_id", deploy.deploy_id)
|
133
|
+
MU.setVar("appname", deploy.appname)
|
134
|
+
MU.setVar("environment", deploy.environment)
|
135
|
+
MU.setVar("timestamp", deploy.timestamp)
|
136
|
+
MU.setVar("seed", deploy.seed)
|
137
|
+
MU.setVar("handle", deploy.handle)
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param deploy_id [String]: The MU identifier of the deployment to load or create.
|
141
|
+
# @param create [Boolean]: Create a new deployment instead of searching for an existing one.
|
142
|
+
# @param deploy_secret [String]: A secret encrypted by the private key of a deployment we're loading. Used to validate remote requests to bootstrap into this deployment.
|
143
|
+
# @param config [Hash]: The full configuration, parsed by {MU::Config}, of this deployment. Required when creating a new deployment.
|
144
|
+
# @param environment [String]: The environment of a deployment to create.
|
145
|
+
# @param ssh_key_name [String]: Required when creating a new deployment.
|
146
|
+
# @param ssh_private_key [String]: Required when creating a new deployment.
|
147
|
+
# @param ssh_public_key [String]: SSH public key for authorized_hosts on clients.
|
148
|
+
# @param skip_resource_objects [Boolean]: Whether preload the cloud resource objects from this deploy. Can save load time for simple MommaCat tasks.
|
149
|
+
# @param nocleanup [Boolean]: Skip automatic cleanup of failed resources
|
150
|
+
# @param no_artifacts [Boolean]: Do not save deploy metadata
|
151
|
+
# @param deployment_data [Hash]: Known deployment data.
|
152
|
+
# @return [void]
|
153
|
+
def initialize(deploy_id,
|
154
|
+
create: false,
|
155
|
+
deploy_secret: nil,
|
156
|
+
config: nil,
|
157
|
+
environment: "dev",
|
158
|
+
ssh_key_name: nil,
|
159
|
+
ssh_private_key: nil,
|
160
|
+
ssh_public_key: nil,
|
161
|
+
nocleanup: false,
|
162
|
+
set_context_to_me: true,
|
163
|
+
skip_resource_objects: false,
|
164
|
+
no_artifacts: false,
|
165
|
+
deployment_data: {},
|
166
|
+
mu_user: Etc.getpwuid(Process.uid).name
|
167
|
+
)
|
168
|
+
if deploy_id.nil? or deploy_id.empty?
|
169
|
+
raise DeployInitializeError, "MommaCat objects must specify a deploy_id"
|
170
|
+
end
|
171
|
+
set_context_to_me = true if create
|
172
|
+
|
173
|
+
@deploy_id = deploy_id
|
174
|
+
@mu_user = mu_user.dup
|
175
|
+
@no_artifacts = no_artifacts
|
176
|
+
|
177
|
+
# Make sure mu_user and chef_user are sane.
|
178
|
+
if @mu_user == "root"
|
179
|
+
@chef_user = "mu"
|
180
|
+
else
|
181
|
+
@chef_user = @mu_user.dup.gsub(/\./, "")
|
182
|
+
@mu_user = "root" if @mu_user == "mu"
|
183
|
+
end
|
184
|
+
@kitten_semaphore = Mutex.new
|
185
|
+
@kittens = {}
|
186
|
+
@original_config = config
|
187
|
+
@nocleanup = nocleanup
|
188
|
+
@secret_semaphore = Mutex.new
|
189
|
+
@notify_semaphore = Mutex.new
|
190
|
+
@node_cert_semaphore = Mutex.new
|
191
|
+
@deployment = deployment_data
|
192
|
+
@deployment['mu_public_ip'] = MU.mu_public_ip
|
193
|
+
@private_key = nil
|
194
|
+
@public_key = nil
|
195
|
+
@secrets = Hash.new
|
196
|
+
@secrets['instance_secret'] = Hash.new
|
197
|
+
@environment = environment
|
198
|
+
@ssh_key_name = ssh_key_name
|
199
|
+
@ssh_private_key = ssh_private_key
|
200
|
+
@ssh_public_key = ssh_public_key
|
201
|
+
@clouds = {}
|
202
|
+
@seed = MU.seed # pass this in
|
203
|
+
@handle = MU.handle # pass this in
|
204
|
+
if set_context_to_me
|
205
|
+
MU::MommaCat.setThreadContext(self)
|
206
|
+
end
|
207
|
+
if create and !@no_artifacts
|
208
|
+
if !Dir.exist?(MU.dataDir+"/deployments")
|
209
|
+
MU.log "Creating #{MU.dataDir}/deployments", MU::DEBUG
|
210
|
+
Dir.mkdir(MU.dataDir+"/deployments", 0700)
|
211
|
+
end
|
212
|
+
path = File.expand_path(MU.dataDir+"/deployments")+"/"+@deploy_id
|
213
|
+
if !Dir.exist?(path)
|
214
|
+
MU.log "Creating #{path}", MU::DEBUG
|
215
|
+
Dir.mkdir(path, 0700)
|
216
|
+
end
|
217
|
+
if @original_config.nil? or !@original_config.is_a?(Hash)
|
218
|
+
raise DeployInitializeError, "New MommaCat repository requires config hash"
|
219
|
+
end
|
220
|
+
@appname = @original_config['name']
|
221
|
+
MU::Cloud.resource_types.each { |cloudclass, data|
|
222
|
+
if !@original_config[data[:cfg_plural]].nil? and @original_config[data[:cfg_plural]].size > 0
|
223
|
+
@original_config[data[:cfg_plural]].each { |resource|
|
224
|
+
@clouds[resource['cloud']] = 0 if !@clouds.has_key?(resource['cloud'])
|
225
|
+
@clouds[resource['cloud']] = @clouds[resource['cloud']] + 1
|
226
|
+
}
|
227
|
+
end
|
228
|
+
}
|
229
|
+
@ssh_key_name, @ssh_private_key, @ssh_public_key = self.SSHKey
|
230
|
+
if !File.exist?(deploy_dir+"/private_key")
|
231
|
+
@private_key, @public_key = createDeployKey
|
232
|
+
end
|
233
|
+
MU.log "Creating deploy secret for #{MU.deploy_id}"
|
234
|
+
@deploy_secret = Password.random(256)
|
235
|
+
if !@original_config['scrub_mu_isms']
|
236
|
+
# TODO there's a nicer way to do this than hardcoding strings
|
237
|
+
if @clouds["AWS"] and @clouds["AWS"] > 0
|
238
|
+
MU::Cloud::AWS.writeDeploySecret(@deploy_id, @deploy_secret)
|
239
|
+
end
|
240
|
+
if @clouds["Google"] and @clouds["Google"] > 0
|
241
|
+
MU::Cloud::Google.writeDeploySecret(@deploy_id, @deploy_secret)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
if set_context_to_me
|
245
|
+
MU::MommaCat.setThreadContext(self)
|
246
|
+
end
|
247
|
+
save!
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
loadDeploy(set_context_to_me: set_context_to_me)
|
252
|
+
if !deploy_secret.nil?
|
253
|
+
if !authKey(deploy_secret)
|
254
|
+
raise DeployInitializeError, "Invalid or incorrect deploy key."
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# Initialize a MU::Cloud object for each resource belonging to this
|
260
|
+
# deploy, IF it already exists, which is to say if we're loading an
|
261
|
+
# existing deploy instead of creating a new one.
|
262
|
+
if !create and @deployment and @original_config and !skip_resource_objects
|
263
|
+
MU::Cloud.resource_types.each_pair { |res_type, attrs|
|
264
|
+
type = attrs[:cfg_plural]
|
265
|
+
if @deployment.has_key?(type)
|
266
|
+
@deployment[type].each_pair { |res_name, data|
|
267
|
+
orig_cfg = nil
|
268
|
+
if @original_config.has_key?(type)
|
269
|
+
@original_config[type].each { |resource|
|
270
|
+
if resource["name"] == res_name
|
271
|
+
orig_cfg = resource
|
272
|
+
break
|
273
|
+
end
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
# Some Server objects originated from ServerPools, get their
|
278
|
+
# configs from there
|
279
|
+
if type == "servers" and orig_cfg.nil? and
|
280
|
+
@original_config.has_key?("server_pools")
|
281
|
+
@original_config["server_pools"].each { |resource|
|
282
|
+
if resource["name"] == res_name
|
283
|
+
orig_cfg = resource
|
284
|
+
break
|
285
|
+
end
|
286
|
+
}
|
287
|
+
end
|
288
|
+
if orig_cfg.nil?
|
289
|
+
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
|
290
|
+
next
|
291
|
+
end
|
292
|
+
begin
|
293
|
+
# Load up MU::Cloud objects for all our kittens in this deploy
|
294
|
+
orig_cfg['environment'] = @environment # not always set in old deploys
|
295
|
+
if attrs[:has_multiples]
|
296
|
+
data.each_pair { |mu_name, actual_data|
|
297
|
+
attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: mu_name)
|
298
|
+
}
|
299
|
+
else
|
300
|
+
# XXX hack for old deployments, this can go away some day
|
301
|
+
if data['mu_name'].nil? or data['mu_name'].empty?
|
302
|
+
if res_type.to_s == "LoadBalancer" and !data['awsname'].nil?
|
303
|
+
data['mu_name'] = data['awsname'].dup
|
304
|
+
elsif res_type.to_s == "FirewallRule" and !data['group_name'].nil?
|
305
|
+
data['mu_name'] = data['group_name'].dup
|
306
|
+
elsif res_type.to_s == "Database" and !data['identifier'].nil?
|
307
|
+
data['mu_name'] = data['identifier'].dup.upcase
|
308
|
+
elsif res_type.to_s == "VPC"
|
309
|
+
# VPC names are deterministic, just generate the things
|
310
|
+
data['mu_name'] = getResourceName(data['name'])
|
311
|
+
end
|
312
|
+
end
|
313
|
+
if data['mu_name'].nil?
|
314
|
+
raise MuError, "Unable to find or guess a Mu name for #{res_type}: #{res_name} in #{@deploy_id}"
|
315
|
+
end
|
316
|
+
attrs[:interface].new(mommacat: self, kitten_cfg: orig_cfg, mu_name: data['mu_name'], cloud_id: data['cloud_id'])
|
317
|
+
end
|
318
|
+
rescue Exception => e
|
319
|
+
MU.log "Failed to load an existing resource of type '#{type}' in #{@deploy_id}: #{e.inspect}", MU::WARN, details: e.backtrace
|
320
|
+
end
|
321
|
+
}
|
322
|
+
end
|
323
|
+
}
|
324
|
+
end
|
325
|
+
|
326
|
+
# XXX this .owned? method may get changed by the Ruby maintainers
|
327
|
+
# if !@@litter_semaphore.owned?
|
328
|
+
# @@litter_semaphore.synchronize {
|
329
|
+
# @@litters[@deploy_id] = self
|
330
|
+
# }
|
331
|
+
# end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Tell us the number of first-class resources we've configured, optionally
|
335
|
+
# filtering results to only include a given type and/or in a given cloud
|
336
|
+
# environment.
|
337
|
+
# @param clouds [Array<String>]: The cloud environment(s) to check for. If unspecified, will match all environments in this deployment.
|
338
|
+
# @param types [Array<String>]: The type of resource(s) to check for. If unspecified, will match all resources in this deployment.
|
339
|
+
# @param negate [Boolean]: Invert logic of the other filters if they are specified, e.g. search for all cloud resources that are *not* AWS.
|
340
|
+
def numKittens(clouds: [], types: [], negate: false)
|
341
|
+
realtypes = []
|
342
|
+
return 0 if @original_config.nil?
|
343
|
+
if !types.nil? and types.size > 0
|
344
|
+
types.each { |type|
|
345
|
+
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
346
|
+
realtypes << cfg_plural
|
347
|
+
}
|
348
|
+
end
|
349
|
+
|
350
|
+
count = 0
|
351
|
+
MU::Cloud.resource_types.each { |cloudclass, data|
|
352
|
+
next if @original_config[data[:cfg_plural]].nil?
|
353
|
+
next if realtypes.size > 0 and (!negate and !realtypes.include?(data[:cfg_plural]))
|
354
|
+
@original_config[data[:cfg_plural]].each { |resource|
|
355
|
+
if clouds.nil? or clouds.size == 0 or (!negate and clouds.include?(resource["cloud"])) or (negate and !clouds.include?(resource["cloud"]))
|
356
|
+
count = count + 1
|
357
|
+
end
|
358
|
+
}
|
359
|
+
}
|
360
|
+
count
|
361
|
+
end
|
362
|
+
|
363
|
+
# @param object [MU::Cloud]:
|
364
|
+
def removeKitten(object)
|
365
|
+
if !object
|
366
|
+
raise MuError, "Nil arguments to removeKitten are not allowed"
|
367
|
+
end
|
368
|
+
@kitten_semaphore.synchronize {
|
369
|
+
MU::Cloud.resource_types.each_pair { |name, attrs|
|
370
|
+
type = attrs[:cfg_plural]
|
371
|
+
next if !@kittens.has_key?(type)
|
372
|
+
tmplitter = @kittens[type].values.dup
|
373
|
+
tmplitter.each { |nodeclass, data|
|
374
|
+
if data.is_a?(Hash)
|
375
|
+
data.each_pair { |mu_name, obj|
|
376
|
+
if data == object
|
377
|
+
@kittens[type][nodeclass].delete(mu_name)
|
378
|
+
return
|
379
|
+
end
|
380
|
+
}
|
381
|
+
else
|
382
|
+
if data == object
|
383
|
+
@kittens[type].delete(nodeclass)
|
384
|
+
return
|
385
|
+
end
|
386
|
+
end
|
387
|
+
}
|
388
|
+
}
|
389
|
+
}
|
390
|
+
@kittens
|
391
|
+
end
|
392
|
+
|
393
|
+
# Overwrite this deployment's configuration with a new version. Save the
|
394
|
+
# previous version as well.
|
395
|
+
# @param new_conf [Hash]: A new configuration, fully resolved by {MU::Config}
|
396
|
+
def updateBasketofKittens(new_conf)
|
397
|
+
loadDeploy
|
398
|
+
if new_conf == @original_config
|
399
|
+
MU.log "#{@deploy_id}", MU::WARN
|
400
|
+
return
|
401
|
+
end
|
402
|
+
|
403
|
+
backup = "#{deploy_dir}/basket_of_kittens.json.#{Time.now.to_i.to_s}"
|
404
|
+
MU.log "Saving previous config of #{@deploy_id} to #{backup}"
|
405
|
+
config = File.new(backup, File::CREAT|File::TRUNC|File::RDWR, 0600)
|
406
|
+
config.flock(File::LOCK_EX)
|
407
|
+
config.puts JSON.pretty_generate(@original_config)
|
408
|
+
config.flock(File::LOCK_UN)
|
409
|
+
config.close
|
410
|
+
|
411
|
+
@original_config = new_conf
|
412
|
+
# save! # XXX this will happen later, more sensibly
|
413
|
+
MU.log "New config saved to #{deploy_dir}/basket_of_kittens.json"
|
414
|
+
end
|
415
|
+
|
416
|
+
# Keep tabs on a {MU::Cloud} object so that it can be found easily by
|
417
|
+
# #findLitterMate.
|
418
|
+
# @param type [String]:
|
419
|
+
# @param name [String]:
|
420
|
+
# @param object [MU::Cloud]:
|
421
|
+
def addKitten(type, name, object)
|
422
|
+
if !type or !name or !object or !object.mu_name
|
423
|
+
raise MuError, "Nil arguments to addKitten are not allowed (got type: #{type}, name: #{name}, and '#{object}' to add)"
|
424
|
+
end
|
425
|
+
shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
|
426
|
+
type = cfg_plural
|
427
|
+
has_multiples = attrs[:has_multiples]
|
428
|
+
|
429
|
+
@kitten_semaphore.synchronize {
|
430
|
+
@kittens[type] ||= {}
|
431
|
+
if has_multiples
|
432
|
+
@kittens[type][name] ||= {}
|
433
|
+
@kittens[type][name][object.mu_name] = object
|
434
|
+
else
|
435
|
+
@kittens[type][name] = object
|
436
|
+
end
|
437
|
+
}
|
438
|
+
end
|
439
|
+
|
440
|
+
# Check a provided deploy key against our stored version. The instance has
|
441
|
+
# in theory accessed a secret via S3 and encrypted it with the deploy's
|
442
|
+
# public key. If it decrypts correctly, we assume this instance is indeed
|
443
|
+
# one of ours.
|
444
|
+
# @param ciphertext [String]: The text to decrypt.
|
445
|
+
# return [Boolean]: Whether the provided text was encrypted with the correct key
|
446
|
+
def authKey(ciphertext)
|
447
|
+
if @private_key.nil? or @deploy_secret.nil?
|
448
|
+
MU.log "Missing auth metadata, can't authorize node in authKey", MU::ERR
|
449
|
+
return false
|
450
|
+
end
|
451
|
+
my_key = OpenSSL::PKey::RSA.new(@private_key)
|
452
|
+
|
453
|
+
begin
|
454
|
+
if my_key.private_decrypt(ciphertext).force_encoding("UTF-8") == @deploy_secret.force_encoding("UTF-8")
|
455
|
+
MU.log "Matched ciphertext for #{MU.deploy_id}", MU::INFO
|
456
|
+
return true
|
457
|
+
else
|
458
|
+
MU.log "Mis-matched ciphertext for #{MU.deploy_id}", MU::ERR
|
459
|
+
return false
|
460
|
+
end
|
461
|
+
rescue OpenSSL::PKey::RSAError => e
|
462
|
+
MU.log e.inspect, MU::ERR
|
463
|
+
return false
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
# Generate a three-character string which can be used to unique-ify the
|
468
|
+
# names of resources which might potentially collide, e.g. Windows local
|
469
|
+
# hostnames, Amazon Elastic Load Balancers, or server pool instances.
|
470
|
+
# @return [String]: A three-character string consisting of two alphnumeric
|
471
|
+
# characters (uppercase) and one number.
|
472
|
+
def self.genUniquenessString
|
473
|
+
begin
|
474
|
+
candidate = SecureRandom.base64(2).slice(0..1) + SecureRandom.random_number(9).to_s
|
475
|
+
candidate.upcase!
|
476
|
+
end while candidate.match(/[^A-Z0-9]/)
|
477
|
+
return candidate
|
478
|
+
end
|
479
|
+
|
480
|
+
@unique_map_semaphore = Mutex.new
|
481
|
+
@name_unique_str_map = {}
|
482
|
+
# Keep a map of the uniqueness strings we assign to various full names, in
|
483
|
+
# case we want to reuse them later.
|
484
|
+
# @return [Hash<String>]
|
485
|
+
def self.name_unique_str_map
|
486
|
+
@name_unique_str_map
|
487
|
+
end
|
488
|
+
|
489
|
+
# Keep a map of the uniqueness strings we assign to various full names, in
|
490
|
+
# case we want to reuse them later.
|
491
|
+
# @return [Mutex]
|
492
|
+
def self.unique_map_semaphore
|
493
|
+
@unique_map_semaphore
|
494
|
+
end
|
495
|
+
|
496
|
+
# Generate a name string for a resource, incorporate the MU identifier
|
497
|
+
# for this deployment. Will dynamically shorten the name to fit for
|
498
|
+
# restrictive uses (e.g. Windows local hostnames, Amazon Elastic Load
|
499
|
+
# Balancers).
|
500
|
+
# @param name [String]: The shorthand name of the resource, usually the value of the "name" field in an Mu resource declaration.
|
501
|
+
# @param max_length [Integer]: The maximum length of the resulting resource name.
|
502
|
+
# @param need_unique_string [Boolean]: Whether to forcibly append a random three-character string to the name to ensure it's unique. Note that this behavior will be automatically invoked if the name must be truncated.
|
503
|
+
# @param scrub_mu_isms [Boolean]: Don't bother with generating names specific to this deployment. Used to generate generic CloudFormation templates, amongst other purposes.
|
504
|
+
# @return [String]: A full name string for this resource
|
505
|
+
def getResourceName(name, max_length: 255, need_unique_string: false, use_unique_string: nil, reuse_unique_string: false, scrub_mu_isms: @original_config['scrub_mu_isms'])
|
506
|
+
if name.nil?
|
507
|
+
raise MuError, "Got no argument to MU::MommaCat.getResourceName"
|
508
|
+
end
|
509
|
+
if @appname.nil? or @environment.nil? or @timestamp.nil? or @seed.nil?
|
510
|
+
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
|
511
|
+
return name
|
512
|
+
end
|
513
|
+
need_unique_string = false if scrub_mu_isms
|
514
|
+
|
515
|
+
muname = nil
|
516
|
+
if need_unique_string
|
517
|
+
reserved = 4
|
518
|
+
else
|
519
|
+
reserved = 0
|
520
|
+
end
|
521
|
+
|
522
|
+
# First, pare down the base name string until it will fit
|
523
|
+
basename = @appname.upcase + "-" + @environment.upcase + "-" + @timestamp + "-" + @seed.upcase + "-" + name.upcase
|
524
|
+
if scrub_mu_isms
|
525
|
+
basename = @appname.upcase + "-" + @environment.upcase + name.upcase
|
526
|
+
end
|
527
|
+
|
528
|
+
begin
|
529
|
+
if (basename.length + reserved) > max_length
|
530
|
+
MU.log "Stripping name down from #{basename}[#{basename.length.to_s}] (reserved: #{reserved.to_s}, max_length: #{max_length.to_s})", MU::DEBUG
|
531
|
+
if basename == @appname.upcase + "-" + @seed.upcase + "-" + name.upcase
|
532
|
+
# If we've run out of stuff to strip, truncate what's left and
|
533
|
+
# just leave room for the deploy seed and uniqueness string. This
|
534
|
+
# is the bare minimum, and probably what you'll see for most Windows
|
535
|
+
# hostnames.
|
536
|
+
basename = name.upcase + "-" + @appname.upcase
|
537
|
+
basename.slice!((max_length-(reserved+3))..basename.length)
|
538
|
+
basename.sub!(/-$/, "")
|
539
|
+
basename = basename + "-" + @seed.upcase
|
540
|
+
else
|
541
|
+
# If we have to strip anything, assume we've lost uniqueness and
|
542
|
+
# will have to compensate with #genUniquenessString.
|
543
|
+
need_unique_string = true
|
544
|
+
reserved = 4
|
545
|
+
basename.sub!(/-[^-]+-#{@seed.upcase}-#{Regexp.escape(name.upcase)}$/, "")
|
546
|
+
basename = basename + "-" + @seed.upcase + "-" + name.upcase
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end while (basename.length + reserved) > max_length
|
550
|
+
|
551
|
+
# Finally, apply our short random differentiator, if it's needed.
|
552
|
+
if need_unique_string
|
553
|
+
# Preferentially use a requested one, if it's not already in use.
|
554
|
+
if !use_unique_string.nil?
|
555
|
+
muname = basename + "-" + use_unique_string
|
556
|
+
if !allocateUniqueResourceName(muname) and !reuse_unique_string
|
557
|
+
MU.log "Requested to use #{use_unique_string} as differentiator when naming #{name}, but the name #{muname} is unavailable.", MU::WARN
|
558
|
+
muname = nil
|
559
|
+
end
|
560
|
+
end
|
561
|
+
if !muname
|
562
|
+
begin
|
563
|
+
unique_string = MU::MommaCat.genUniquenessString
|
564
|
+
muname = basename + "-" + unique_string
|
565
|
+
end while !allocateUniqueResourceName(muname)
|
566
|
+
MU::MommaCat.unique_map_semaphore.synchronize {
|
567
|
+
MU::MommaCat.name_unique_str_map[muname] = unique_string
|
568
|
+
}
|
569
|
+
end
|
570
|
+
else
|
571
|
+
muname = basename
|
572
|
+
end
|
573
|
+
|
574
|
+
return muname
|
575
|
+
end
|
576
|
+
|
577
|
+
|
578
|
+
# Encrypt a string with the deployment's public key.
|
579
|
+
# @param ciphertext [String]: The string to encrypt
|
580
|
+
def encryptWithDeployKey(ciphertext)
|
581
|
+
my_public_key = OpenSSL::PKey::RSA.new(@public_key)
|
582
|
+
return my_public_key.public_encrypt(ciphertext)
|
583
|
+
end
|
584
|
+
|
585
|
+
# Decrypt a string with the deployment's private key.
|
586
|
+
# @param ciphertext [String]: The string to decrypt
|
587
|
+
def decryptWithDeployKey(ciphertext)
|
588
|
+
my_private_key = OpenSSL::PKey::RSA.new(@private_key)
|
589
|
+
return my_private_key.private_decrypt(ciphertext)
|
590
|
+
end
|
591
|
+
|
592
|
+
|
593
|
+
# Save a string into deployment metadata for the current deployment,
|
594
|
+
# encrypting it with our deploy key.
|
595
|
+
# @param instance_id [String]: The cloud instance identifier with which this secret is associated.
|
596
|
+
# @param raw_secret [String]: The unencrypted string to store.
|
597
|
+
# @param type [String]: The type of secret, used to identify for retrieval.
|
598
|
+
def saveNodeSecret(instance_id, raw_secret, type)
|
599
|
+
return if @no_artifacts
|
600
|
+
if instance_id.nil? or instance_id.empty? or raw_secret.nil? or raw_secret.empty? or type.nil? or type.empty?
|
601
|
+
raise SecretError, "saveNodeSecret requires instance_id, raw_secret, and type args"
|
602
|
+
end
|
603
|
+
MU::MommaCat.lock("deployment-notification")
|
604
|
+
loadDeploy(true) # make sure we're not trampling deployment data
|
605
|
+
@secret_semaphore.synchronize {
|
606
|
+
if @secrets[type].nil?
|
607
|
+
raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
|
608
|
+
end
|
609
|
+
@secrets[type][instance_id] = encryptWithDeployKey(raw_secret)
|
610
|
+
}
|
611
|
+
save!
|
612
|
+
MU::MommaCat.unlock("deployment-notification")
|
613
|
+
end
|
614
|
+
|
615
|
+
# Retrieve an encrypted secret from metadata for the current deployment.
|
616
|
+
# @param instance_id [String]: The cloud instance identifier with which this secret is associated.
|
617
|
+
# @param type [String]: The type of secret, used to identify for retrieval.
|
618
|
+
# @param quiet [Boolean]: Do not log errors for non-existent secrets
|
619
|
+
def fetchSecret(instance_id, type, quiet: false)
|
620
|
+
@secret_semaphore.synchronize {
|
621
|
+
if @secrets[type].nil?
|
622
|
+
return nil if quiet
|
623
|
+
raise SecretError, "'#{type}' is not a valid secret type (valid types: #{@secrets.keys.to_s})"
|
624
|
+
end
|
625
|
+
if @secrets[type][instance_id].nil?
|
626
|
+
return nil if quiet
|
627
|
+
raise SecretError, "No '#{type}' secret known for instance #{instance_id}"
|
628
|
+
end
|
629
|
+
}
|
630
|
+
return decryptWithDeployKey(@secrets[type][instance_id])
|
631
|
+
end
|
632
|
+
|
633
|
+
|
634
|
+
# Run {MU::Cloud::Server#postBoot} and {MU::Cloud::Server#groom} on a node.
|
635
|
+
# @param cloud_id [OpenStruct]: The cloud provider's identifier for this node.
|
636
|
+
# @param name [String]: The MU resource name of the node being created.
|
637
|
+
# @param mu_name [String]: The full #{MU::MommaCat.getResourceName} name of the server we're grooming, if it's been initialized already.
|
638
|
+
# @param type [String]: The type of resource that created this node (either *server* or *serverpool*).
|
639
|
+
def groomNode(cloud_id, name, type, mu_name: nil, reraise_fail: false, sync_wait: true)
|
640
|
+
if cloud_id.nil?
|
641
|
+
raise GroomError, "MU::MommaCat.groomNode requires a {MU::Cloud::Server} object"
|
642
|
+
end
|
643
|
+
if name.nil? or name.empty?
|
644
|
+
raise GroomError, "MU::MommaCat.groomNode requires a resource name"
|
645
|
+
end
|
646
|
+
if type.nil? or type.empty?
|
647
|
+
raise GroomError, "MU::MommaCat.groomNode requires a resource type"
|
648
|
+
end
|
649
|
+
|
650
|
+
if !MU::MommaCat.lock(cloud_id+"-mommagroom", true)
|
651
|
+
MU.log "Instance #{cloud_id} on #{MU.deploy_id} (#{type}: #{name}) is already being groomed, ignoring this extra request.", MU::NOTICE
|
652
|
+
MU::MommaCat.unlockAll
|
653
|
+
if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
|
654
|
+
puts "------------------------------"
|
655
|
+
puts "Open flock() locks:"
|
656
|
+
pp MU::MommaCat.locks
|
657
|
+
puts "------------------------------"
|
658
|
+
end
|
659
|
+
return
|
660
|
+
end
|
661
|
+
loadDeploy
|
662
|
+
|
663
|
+
# XXX this is to stop Net::SSH from killing our entire stack when it
|
664
|
+
# throws an exception. See ECAP-139 in JIRA. Far as we can tell, it's
|
665
|
+
# just not entirely thread safe.
|
666
|
+
Thread.handle_interrupt(Net::SSH::Disconnect => :never) {
|
667
|
+
begin
|
668
|
+
Thread.handle_interrupt(Net::SSH::Disconnect => :immediate) {
|
669
|
+
MU.log "(Probably harmless) Caught a Net::SSH::Disconnect in #{Thread.current.inspect}", MU::DEBUG, details: Thread.current.backtrace
|
670
|
+
}
|
671
|
+
ensure
|
672
|
+
end
|
673
|
+
}
|
674
|
+
|
675
|
+
if @original_config[type+"s"].nil?
|
676
|
+
raise GroomError, "I see no configured resources of type #{type} (bootstrap request for #{name} on #{@deploy_id})"
|
677
|
+
end
|
678
|
+
kitten = nil
|
679
|
+
|
680
|
+
kitten = findLitterMate(type: "server", name: name, mu_name: mu_name, cloud_id: cloud_id)
|
681
|
+
if !kitten.nil?
|
682
|
+
MU.log "Re-grooming #{mu_name}", details: kitten.deploydata
|
683
|
+
else
|
684
|
+
first_groom = true
|
685
|
+
@original_config[type+"s"].each { |svr|
|
686
|
+
if svr['name'] == name
|
687
|
+
svr["instance_id"] = cloud_id
|
688
|
+
|
689
|
+
# This will almost always be true in server pools, but lets be safe. Somewhat problematic because we are only
|
690
|
+
# looking at deploy_id, but we still know this is our DNS record and not a custom one.
|
691
|
+
if svr['dns_records'] && !svr['dns_records'].empty?
|
692
|
+
svr['dns_records'].each { |dnsrec|
|
693
|
+
if dnsrec.has_key?("name") && dnsrec['name'].start_with?(MU.deploy_id.downcase)
|
694
|
+
MU.log "DNS record for #{MU.deploy_id.downcase}, #{name} is probably wrong, deleting", MU::WARN, details: dnsrec
|
695
|
+
dnsrec.delete('name')
|
696
|
+
dnsrec.delete('target')
|
697
|
+
end
|
698
|
+
}
|
699
|
+
end
|
700
|
+
|
701
|
+
kitten = MU::Cloud::Server.new(mommacat: self, kitten_cfg: svr, cloud_id: cloud_id)
|
702
|
+
mu_name = kitten.mu_name if mu_name.nil?
|
703
|
+
MU.log "Grooming #{mu_name} for the first time", details: svr
|
704
|
+
break
|
705
|
+
end
|
706
|
+
}
|
707
|
+
end
|
708
|
+
|
709
|
+
begin
|
710
|
+
# This is a shared lock with MU::Cloud::AWS::Server.create, to keep from
|
711
|
+
# stomping on synchronous deploys that are still running. This
|
712
|
+
# means we're going to wait here if this instance is still being
|
713
|
+
# bootstrapped by "regular" means.
|
714
|
+
if !MU::MommaCat.lock(cloud_id+"-create", true)
|
715
|
+
MU.log "#{mu_name} is still in mid-creation, skipping", MU::NOTICE
|
716
|
+
MU::MommaCat.unlockAll
|
717
|
+
if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
|
718
|
+
puts "------------------------------"
|
719
|
+
puts "Open flock() locks:"
|
720
|
+
pp MU::MommaCat.locks
|
721
|
+
puts "------------------------------"
|
722
|
+
end
|
723
|
+
return
|
724
|
+
end
|
725
|
+
MU::MommaCat.unlock(cloud_id+"-create")
|
726
|
+
|
727
|
+
if !kitten.postBoot(cloud_id)
|
728
|
+
MU.log "#{mu_name} is already being groomed, skipping", MU::NOTICE
|
729
|
+
MU::MommaCat.unlockAll
|
730
|
+
if !MU::MommaCat.locks.nil? and MU::MommaCat.locks.size > 0
|
731
|
+
puts "------------------------------"
|
732
|
+
puts "Open flock() locks:"
|
733
|
+
pp MU::MommaCat.locks
|
734
|
+
puts "------------------------------"
|
735
|
+
end
|
736
|
+
return
|
737
|
+
end
|
738
|
+
|
739
|
+
# This is a shared lock with MU::Deploy.createResources, simulating the
|
740
|
+
# thread logic that tells MU::Cloud::AWS::Server.deploy to wait until
|
741
|
+
# its dependencies are ready. We don't, for example, want to start
|
742
|
+
# deploying if we rely on an RDS instance that isn't ready yet. We can
|
743
|
+
# release this immediately, once we successfully grab it.
|
744
|
+
MU::MommaCat.lock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies")
|
745
|
+
MU::MommaCat.unlock("#{kitten.cloudclass.name}_#{kitten.config["name"]}-dependencies")
|
746
|
+
|
747
|
+
kitten.groom
|
748
|
+
rescue Exception => e
|
749
|
+
MU::MommaCat.unlockAll
|
750
|
+
if e.class.name != "MU::Cloud::AWS::Server::BootstrapTempFail" and !File.exists?(deploy_dir+"/.cleanup."+cloud_id) and !File.exists?(deploy_dir+"/.cleanup")
|
751
|
+
MU.log "Grooming FAILED for #{kitten.mu_name} (#{e.inspect})", MU::ERR, details: e.backtrace
|
752
|
+
# sendAdminMail("Grooming FAILED for #{kitten.mu_name} on #{MU.appname} \"#{MU.handle}\" (#{MU.deploy_id})",
|
753
|
+
# msg: e.inspect,
|
754
|
+
# kitten: kitten,
|
755
|
+
# data: e.backtrace,
|
756
|
+
# debug: true
|
757
|
+
# )
|
758
|
+
raise e if reraise_fail
|
759
|
+
else
|
760
|
+
MU.log "Grooming of #{kitten.mu_name} interrupted by cleanup or planned reboot"
|
761
|
+
end
|
762
|
+
return
|
763
|
+
end
|
764
|
+
|
765
|
+
if !@deployment['servers'].nil?
|
766
|
+
syncLitter(@deployment["servers"].keys, triggering_node: kitten)
|
767
|
+
end
|
768
|
+
MU::MommaCat.unlock(cloud_id+"-mommagroom")
|
769
|
+
if MU.myCloud == "AWS"
|
770
|
+
MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
|
771
|
+
end
|
772
|
+
MU::MommaCat.getLitter(MU.deploy_id, use_cache: false)
|
773
|
+
MU::MommaCat.syncMonitoringConfig(false)
|
774
|
+
MU::MommaCat.createStandardTags(cloud_id, region: kitten.config["region"])
|
775
|
+
MU.log "Grooming complete for '#{name}' mu_name on \"#{MU.handle}\" (#{MU.deploy_id})"
|
776
|
+
FileUtils.touch("/opt/mu/var/deployments/#{MU.deploy_id}/#{name}_done.txt")
|
777
|
+
MU::MommaCat.unlockAll
|
778
|
+
if first_groom
|
779
|
+
sendAdminMail("Grooming complete for '#{name}' (#{mu_name}) on deploy \"#{MU.handle}\" (#{MU.deploy_id})", kitten: kitten)
|
780
|
+
end
|
781
|
+
return
|
782
|
+
end
|
783
|
+
|
784
|
+
# Return the parts and pieces of this deploy's node ssh key set. Generate
|
785
|
+
# or load if that hasn't been done already.
|
786
|
+
def SSHKey
|
787
|
+
return [@ssh_key_name, @ssh_private_key, @ssh_public_key] if !@ssh_key_name.nil?
|
788
|
+
if numKittens(types: ["Server", "ServerPool", "ContainerCluster"]) == 0
|
789
|
+
return []
|
790
|
+
end
|
791
|
+
@ssh_key_name="deploy-#{MU.deploy_id}"
|
792
|
+
ssh_dir = Etc.getpwnam(@mu_user).dir+"/.ssh"
|
793
|
+
|
794
|
+
if !File.directory?(ssh_dir) then
|
795
|
+
MU.log "Creating #{ssh_dir}", MU::DEBUG
|
796
|
+
Dir.mkdir(ssh_dir, 0700)
|
797
|
+
if Process.uid == 0 and @mu_user != "mu"
|
798
|
+
ssh_dir.chown(Etc.getpwnam(@mu_user).uid, Etc.getpwnam(@mu_user).gid)
|
799
|
+
end
|
800
|
+
end
|
801
|
+
if !File.exists?("#{ssh_dir}/#{@ssh_key_name}")
|
802
|
+
MU.log "Generating SSH key #{@ssh_key_name}"
|
803
|
+
%x{/usr/bin/ssh-keygen -N "" -f #{ssh_dir}/#{@ssh_key_name}}
|
804
|
+
end
|
805
|
+
@ssh_public_key = File.read("#{ssh_dir}/#{@ssh_key_name}.pub")
|
806
|
+
@ssh_public_key.chomp!
|
807
|
+
@ssh_private_key = File.read("#{ssh_dir}/#{@ssh_key_name}")
|
808
|
+
@ssh_private_key.chomp!
|
809
|
+
|
810
|
+
if numKittens(clouds: ["AWS"], types: ["Server", "ServerPool"]) > 0
|
811
|
+
MU::Cloud::AWS.createEc2SSHKey(@ssh_key_name, @ssh_public_key)
|
812
|
+
end
|
813
|
+
|
814
|
+
return [@ssh_key_name, @ssh_private_key, @ssh_public_key]
|
815
|
+
end
|
816
|
+
|
817
|
+
@lock_semaphore = Mutex.new
|
818
|
+
# Release all flock() locks held by the current thread.
|
819
|
+
def self.unlockAll
|
820
|
+
if !@locks.nil? and !@locks[Thread.current.object_id].nil?
|
821
|
+
# Work from a copy so we can iterate without worrying about contention
|
822
|
+
# in lock() or unlock(). We can't just wrap our iterator block in a
|
823
|
+
# semaphore here, because we're calling another method that uses the
|
824
|
+
# same semaphore.
|
825
|
+
lock_copy = nil
|
826
|
+
@lock_semaphore.synchronize {
|
827
|
+
delete_list = []
|
828
|
+
@locks[Thread.current.object_id].each_pair { |id, fh|
|
829
|
+
MU.log "Releasing lock on #{deploy_dir(MU.deploy_id)}/locks/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
|
830
|
+
begin
|
831
|
+
@locks[Thread.current.object_id][id].flock(File::LOCK_UN)
|
832
|
+
@locks[Thread.current.object_id][id].close
|
833
|
+
rescue IOError => e
|
834
|
+
MU.log "Got #{e.inspect} unlocking #{id} on #{Thread.current.object_id}", MU::WARN
|
835
|
+
end
|
836
|
+
delete_list << id
|
837
|
+
}
|
838
|
+
# We do this here because we can't mangle a Hash while we're iterating
|
839
|
+
# over it.
|
840
|
+
delete_list.each { |id|
|
841
|
+
@locks[Thread.current.object_id].delete(id)
|
842
|
+
}
|
843
|
+
if @locks[Thread.current.object_id].size == 0
|
844
|
+
@locks.delete(Thread.current.object_id)
|
845
|
+
end
|
846
|
+
}
|
847
|
+
end
|
848
|
+
end
|
849
|
+
|
850
|
+
# Create/hold a flock() lock.
|
851
|
+
# @param id [String]: The lock identifier to release.
|
852
|
+
# @param nonblock [Boolean]: Whether to block while waiting for the lock. In non-blocking mode, we simply return false if the lock is not available.
|
853
|
+
# return [false, nil]
|
854
|
+
def self.lock(id, nonblock = false, global = false)
|
855
|
+
raise MuError, "Can't pass a nil id to MU::MommaCat.lock" if id.nil?
|
856
|
+
|
857
|
+
if !global
|
858
|
+
lockdir = "#{deploy_dir(MU.deploy_id)}/locks"
|
859
|
+
else
|
860
|
+
lockdir = File.expand_path(MU.dataDir+"/locks")
|
861
|
+
end
|
862
|
+
|
863
|
+
if !Dir.exist?(lockdir)
|
864
|
+
MU.log "Creating #{lockdir}", MU::DEBUG
|
865
|
+
Dir.mkdir(lockdir, 0700)
|
866
|
+
end
|
867
|
+
|
868
|
+
@lock_semaphore.synchronize {
|
869
|
+
if @locks[Thread.current.object_id].nil?
|
870
|
+
@locks[Thread.current.object_id] = Hash.new
|
871
|
+
end
|
872
|
+
|
873
|
+
@locks[Thread.current.object_id][id] = File.open("#{lockdir}/#{id}.lock", File::CREAT|File::RDWR, 0600)
|
874
|
+
}
|
875
|
+
MU.log "Getting a lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})...", MU::DEBUG
|
876
|
+
begin
|
877
|
+
if nonblock
|
878
|
+
if !@locks[Thread.current.object_id][id].flock(File::LOCK_EX|File::LOCK_NB)
|
879
|
+
return false
|
880
|
+
end
|
881
|
+
else
|
882
|
+
@locks[Thread.current.object_id][id].flock(File::LOCK_EX)
|
883
|
+
end
|
884
|
+
rescue IOError => e
|
885
|
+
raise MU::BootstrapTempFail, "Interrupted waiting for lock on thread #{Thread.current.object_id}, probably just a node rebooting as part of a synchronous install"
|
886
|
+
end
|
887
|
+
MU.log "Lock on #{lockdir}/#{id}.lock on thread #{Thread.current.object_id} acquired", MU::DEBUG
|
888
|
+
return true
|
889
|
+
end
|
890
|
+
|
891
|
+
# Release a flock() lock.
|
892
|
+
# @param id [String]: The lock identifier to release.
|
893
|
+
def self.unlock(id, global = false)
|
894
|
+
raise MuError, "Can't pass a nil id to MU::MommaCat.unlock" if id.nil?
|
895
|
+
lockdir = nil
|
896
|
+
if !global
|
897
|
+
lockdir = "#{deploy_dir(MU.deploy_id)}/locks"
|
898
|
+
else
|
899
|
+
lockdir = File.expand_path(MU.dataDir+"/locks")
|
900
|
+
end
|
901
|
+
@lock_semaphore.synchronize {
|
902
|
+
return if @locks.nil? or @locks[Thread.current.object_id].nil? or @locks[Thread.current.object_id][id].nil?
|
903
|
+
}
|
904
|
+
MU.log "Releasing lock on #{lockdir}/#{id}.lock (thread #{Thread.current.object_id})", MU::DEBUG
|
905
|
+
begin
|
906
|
+
@locks[Thread.current.object_id][id].flock(File::LOCK_UN)
|
907
|
+
@locks[Thread.current.object_id][id].close
|
908
|
+
if !@locks[Thread.current.object_id].nil?
|
909
|
+
@locks[Thread.current.object_id].delete(id)
|
910
|
+
end
|
911
|
+
if @locks[Thread.current.object_id].size == 0
|
912
|
+
@locks.delete(Thread.current.object_id)
|
913
|
+
end
|
914
|
+
rescue IOError => e
|
915
|
+
MU.log "Got #{e.inspect} unlocking #{id} on #{Thread.current.object_id}", MU::WARN
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
# Remove a deployment's metadata.
|
920
|
+
# @param deploy_id [String]: The deployment identifier to remove.
|
921
|
+
def self.purge(deploy_id)
|
922
|
+
if deploy_id.nil? or deploy_id.empty?
|
923
|
+
raise MuError, "Got nil deploy_id in MU::MommaCat.purge"
|
924
|
+
end
|
925
|
+
# XXX archiving is better than annihilating
|
926
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
927
|
+
if Dir.exist?(path+"/"+deploy_id)
|
928
|
+
unlockAll
|
929
|
+
MU.log "Purging #{path}/#{deploy_id}" if File.exists?(path+"/"+deploy_id+"/deployment.json")
|
930
|
+
|
931
|
+
FileUtils.rm_rf(path+"/"+deploy_id, :secure => true)
|
932
|
+
end
|
933
|
+
if File.exists?(path+"/unique_ids")
|
934
|
+
File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
|
935
|
+
newlines = []
|
936
|
+
f.flock(File::LOCK_EX)
|
937
|
+
f.readlines.each { |line|
|
938
|
+
newlines << line if !line.match(/:#{deploy_id}$/)
|
939
|
+
}
|
940
|
+
f.rewind
|
941
|
+
f.truncate(0)
|
942
|
+
f.puts(newlines)
|
943
|
+
f.flush
|
944
|
+
f.flock(File::LOCK_UN)
|
945
|
+
}
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
# Remove the metadata of the currently loaded deployment.
|
950
|
+
def purge!
|
951
|
+
MU::MommaCat.purge(MU.deploy_id)
|
952
|
+
end
|
953
|
+
|
954
|
+
@cleanup_threads = []
|
955
|
+
|
956
|
+
# Iterate over all known deployments and look for instances that have been
|
957
|
+
# terminated, but not yet cleaned up, then clean them up.
|
958
|
+
def self.cleanTerminatedInstances
|
959
|
+
MU::MommaCat.lock("clean-terminated-instances", false, true)
|
960
|
+
MU.log "Checking for harvested instances in need of cleanup", MU::DEBUG
|
961
|
+
parent_thread_id = Thread.current.object_id
|
962
|
+
cleanup_threads = []
|
963
|
+
purged = 0
|
964
|
+
MU::MommaCat.listDeploys.each { |deploy_id|
|
965
|
+
next if File.exists?(deploy_dir(deploy_id)+"/.cleanup")
|
966
|
+
MU.log "Checking for dead wood in #{deploy_id}", MU::DEBUG
|
967
|
+
@cleanup_threads << Thread.new {
|
968
|
+
MU.dupGlobals(parent_thread_id)
|
969
|
+
# We can't use cached litter information because we will then try to delete the same node over and over again until we restart the service
|
970
|
+
deploy = MU::MommaCat.getLitter(deploy_id, set_context_to_me: true, use_cache: false)
|
971
|
+
purged_this_deploy = 0
|
972
|
+
if deploy.kittens.has_key?("servers")
|
973
|
+
deploy.kittens["servers"].each_pair { |nodeclass, servers|
|
974
|
+
deletia = []
|
975
|
+
servers.each_pair { |mu_name, server|
|
976
|
+
server.describe
|
977
|
+
if !server.cloud_id
|
978
|
+
MU.log "Checking for deletion of #{mu_name}, but unable to fetch its cloud_id", MU::WARN, details: server
|
979
|
+
elsif !server.active?
|
980
|
+
next if File.exists?(deploy_dir(deploy_id)+"/.cleanup-"+server.cloud_id)
|
981
|
+
deletia << mu_name
|
982
|
+
MU.log "Deleting #{server} (#{nodeclass}), formerly #{server.cloud_id}", MU::NOTICE
|
983
|
+
begin
|
984
|
+
server.destroy
|
985
|
+
deploy.sendAdminMail("Retired terminated node #{mu_name}", kitten: server)
|
986
|
+
rescue Exception => e
|
987
|
+
MU.log "Saw #{e.message} while retiring #{mu_name}", MU::ERR, details: e.backtrace
|
988
|
+
next
|
989
|
+
end
|
990
|
+
MU.log "Deletion of #{server} (#{nodeclass}), formerly #{server.cloud_id} complete", MU::NOTICE
|
991
|
+
purged = purged + 1
|
992
|
+
purged_this_deploy = purged_this_deploy + 1
|
993
|
+
end
|
994
|
+
}
|
995
|
+
if purged_this_deploy > 0
|
996
|
+
# XXX some kind of filter (obey sync_siblings on nodes' configs)
|
997
|
+
deploy.syncLitter(servers.keys)
|
998
|
+
end
|
999
|
+
}
|
1000
|
+
end
|
1001
|
+
MU.purgeGlobals
|
1002
|
+
}
|
1003
|
+
}
|
1004
|
+
@cleanup_threads.each { |t|
|
1005
|
+
t.join
|
1006
|
+
}
|
1007
|
+
@cleanup_threads = []
|
1008
|
+
|
1009
|
+
if purged > 0
|
1010
|
+
if MU.myCloud == "AWS"
|
1011
|
+
MU::Cloud::AWS.openFirewallForClients # XXX add the other clouds, or abstract
|
1012
|
+
end
|
1013
|
+
MU::MommaCat.syncMonitoringConfig
|
1014
|
+
end
|
1015
|
+
MU::MommaCat.unlock("clean-terminated-instances", true)
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
|
1019
|
+
# Locate a resource that's either a member of another deployment, or of no
|
1020
|
+
# deployment at all, and return a {MU::Cloud} object for it.
|
1021
|
+
# @param cloud [String]: The Cloud provider to use.
|
1022
|
+
# @param type [String]: The resource type. Can be the full class name, symbolic name, or Basket of Kittens configuration shorthand for the resource type.
|
1023
|
+
# @param deploy_id [String]: The identifier of an outside deploy to search.
|
1024
|
+
# @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field, typically used in conjunction with deploy_id.
|
1025
|
+
# @param mu_name [String]: The fully-resolved and deployed name of the resource, typically used in conjunction with deploy_id.
|
1026
|
+
# @param cloud_id [String]: A cloud provider identifier for this resource.
|
1027
|
+
# @param region [String]: The cloud provider region
|
1028
|
+
# @param tag_key [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_value.
|
1029
|
+
# @param tag_value [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_key.
|
1030
|
+
# @param allow_multi [Boolean]: Permit an array of matching resources to be returned (if applicable) instead of just one.
|
1031
|
+
# @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.
|
1032
|
+
# @param flags [Hash]: Other cloud or resource type specific options to pass to that resource's find() method
|
1033
|
+
# @return [Array<MU::Cloud>]
|
1034
|
+
def self.findStray(cloud,
|
1035
|
+
type,
|
1036
|
+
deploy_id: nil,
|
1037
|
+
name: nil,
|
1038
|
+
mu_name: nil,
|
1039
|
+
cloud_id: nil,
|
1040
|
+
region: nil,
|
1041
|
+
tag_key: nil,
|
1042
|
+
tag_value: nil,
|
1043
|
+
allow_multi: false,
|
1044
|
+
calling_deploy: MU.mommacat,
|
1045
|
+
flags: {},
|
1046
|
+
dummy_ok: false
|
1047
|
+
)
|
1048
|
+
return nil if cloud == "CloudFormation" and !cloud_id.nil?
|
1049
|
+
begin
|
1050
|
+
deploy_id = deploy_id.to_s if deploy_id.class.to_s == "MU::Config::Tail"
|
1051
|
+
name = name.to_s if name.class.to_s == "MU::Config::Tail"
|
1052
|
+
cloud_id = cloud_id.to_s if !cloud_id.nil?
|
1053
|
+
mu_name = mu_name.to_s if mu_name.class.to_s == "MU::Config::Tail"
|
1054
|
+
tag_key = tag_key.to_s if tag_key.class.to_s == "MU::Config::Tail"
|
1055
|
+
tag_value = tag_value.to_s if tag_value.class.to_s == "MU::Config::Tail"
|
1056
|
+
shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
|
1057
|
+
resourceclass = MU::Cloud.loadCloudType(cloud, shortclass)
|
1058
|
+
cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
1059
|
+
if (tag_key and !tag_value) or (!tag_key and tag_value)
|
1060
|
+
raise MuError, "Can't call findStray with only one of tag_key and tag_value set, must be both or neither"
|
1061
|
+
end
|
1062
|
+
# Help ourselves by making more refined parameters out of mu_name, if
|
1063
|
+
# they weren't passed explicitly
|
1064
|
+
if mu_name
|
1065
|
+
if !tag_key and !tag_value
|
1066
|
+
# XXX "Name" is an AWS-ism, perhaps those plugins should do this bit?
|
1067
|
+
tag_key="Name"
|
1068
|
+
tag_value=mu_name
|
1069
|
+
end
|
1070
|
+
# We can extract a deploy_id from mu_name if we don't have one already
|
1071
|
+
if !deploy_id and mu_name
|
1072
|
+
deploy_id = mu_name.sub(/^(\w+-\w+-\d{10}-[A-Z]{2})-/, '\1')
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
MU.log "Called findStray with 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}", MU::DEBUG, details: flags
|
1076
|
+
|
1077
|
+
# See if the thing we're looking for is a member of the deploy that's
|
1078
|
+
# asking after it.
|
1079
|
+
if !deploy_id.nil? and !calling_deploy.nil? and flags.empty? and
|
1080
|
+
calling_deploy.deploy_id == deploy_id and (!name.nil? or !mu_name.nil?)
|
1081
|
+
handle = calling_deploy.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id)
|
1082
|
+
return [handle] if !handle.nil?
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
kittens = {}
|
1086
|
+
# Search our other deploys for matching resources
|
1087
|
+
if (deploy_id or name or mu_name or cloud_id)# and flags.empty?
|
1088
|
+
mu_descs = MU::MommaCat.getResourceMetadata(cfg_plural, name: name, deploy_id: deploy_id, mu_name: mu_name)
|
1089
|
+
|
1090
|
+
mu_descs.each_pair { |deploy_id, matches|
|
1091
|
+
next if matches.nil? or matches.size == 0
|
1092
|
+
momma = MU::MommaCat.getLitter(deploy_id)
|
1093
|
+
straykitten = nil
|
1094
|
+
# If we found exactly one match in this deploy, use its metadata to
|
1095
|
+
# guess at resource names we weren't told.
|
1096
|
+
if matches.size == 1 and name.nil? and mu_name.nil?
|
1097
|
+
if cloud_id.nil?
|
1098
|
+
straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: matches.first["cloud_id"])
|
1099
|
+
else
|
1100
|
+
straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: cloud_id)
|
1101
|
+
end
|
1102
|
+
# elsif !flags.nil? and !flags.empty? # XXX eh, maybe later
|
1103
|
+
# # see if we can narrow it down further with some flags
|
1104
|
+
# filtered = []
|
1105
|
+
# matches.each { |m|
|
1106
|
+
# f = resourceclass.find(cloud_id: m['mu_name'], flags: flags)
|
1107
|
+
# filtered << m if !f.nil? and f.size > 0
|
1108
|
+
# MU.log "RESULT FROM find(cloud_id: #{m['mu_name']}, flags: #{flags})", MU::WARN, details: f
|
1109
|
+
# }
|
1110
|
+
# if filtered.size == 1
|
1111
|
+
# straykitten = momma.findLitterMate(type: type, name: matches.first["name"], cloud_id: filtered.first['cloud_id'])
|
1112
|
+
# end
|
1113
|
+
else
|
1114
|
+
straykitten = momma.findLitterMate(type: type, name: name, mu_name: mu_name, cloud_id: cloud_id)
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
next if straykitten.nil?
|
1118
|
+
|
1119
|
+
kittens[straykitten.cloud_id] = straykitten
|
1120
|
+
# Peace out if we found the exact resource we want
|
1121
|
+
if cloud_id and straykitten.cloud_id == cloud_id
|
1122
|
+
return [straykitten]
|
1123
|
+
elsif !cloud_id and mu_descs.size == 1 and matches.size == 1
|
1124
|
+
return [straykitten]
|
1125
|
+
end
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
# if !mu_descs.nil? and mu_descs.size > 0 and !deploy_id.nil? and !deploy_id.empty? and !mu_descs.first.empty?
|
1129
|
+
# MU.log "I found descriptions that might match #{resourceclass.cfg_plural} name: #{name}, deploy_id: #{deploy_id}, mu_name: #{mu_name}, but couldn't isolate my target kitten", MU::WARN, details: caller
|
1130
|
+
# puts File.read(deploy_dir(deploy_id)+"/deployment.json")
|
1131
|
+
# end
|
1132
|
+
|
1133
|
+
# We can't refine any further by asking the cloud provider...
|
1134
|
+
if !cloud_id and !tag_key and !tag_value and kittens.size > 1
|
1135
|
+
if !allow_multi
|
1136
|
+
raise MuError, "Multiple matches in MU::MommaCat.findStray where none allowed from deploy_id: '#{deploy_id}', name: '#{name}', mu_name: '#{mu_name}' (#{caller[0]})"
|
1137
|
+
else
|
1138
|
+
return kittens.values
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
matches = []
|
1144
|
+
|
1145
|
+
if cloud_id or (tag_key and tag_value) or !flags.empty?
|
1146
|
+
regions = []
|
1147
|
+
begin
|
1148
|
+
if region
|
1149
|
+
regions << region
|
1150
|
+
else
|
1151
|
+
regions = cloudclass.listRegions
|
1152
|
+
end
|
1153
|
+
rescue NoMethodError # Not all cloud providers have regions
|
1154
|
+
regions = [""]
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
if cloud == "Google" and ["vpcs", "firewall_rules"].include?(cfg_plural)
|
1158
|
+
regions = [nil]
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
cloud_descs = {}
|
1162
|
+
regions.each { |r|
|
1163
|
+
cloud_descs[r] = resourceclass.find(cloud_id: cloud_id, region: r, tag_key: tag_key, tag_value: tag_value, flags: flags)
|
1164
|
+
# Stop if you found the thing
|
1165
|
+
if cloud_id and cloud_descs[r] and !cloud_descs[r].empty?
|
1166
|
+
break
|
1167
|
+
end
|
1168
|
+
}
|
1169
|
+
regions.each { |r|
|
1170
|
+
next if cloud_descs[r].nil?
|
1171
|
+
cloud_descs[r].each_pair { |kitten_cloud_id, descriptor|
|
1172
|
+
# We already have a MU::Cloud object for this guy, use it
|
1173
|
+
if kittens.has_key?(kitten_cloud_id)
|
1174
|
+
matches << kittens[kitten_cloud_id]
|
1175
|
+
elsif kittens.size == 0
|
1176
|
+
if !dummy_ok
|
1177
|
+
next
|
1178
|
+
end
|
1179
|
+
# If we don't have a MU::Cloud object, manufacture a dummy one.
|
1180
|
+
# Give it a fake name if we have to and have decided that's ok.
|
1181
|
+
if (name.nil? or name.empty?)
|
1182
|
+
if !dummy_ok
|
1183
|
+
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", MU::DEBUG, details: caller
|
1184
|
+
next
|
1185
|
+
else
|
1186
|
+
if !mu_name.nil?
|
1187
|
+
name = mu_name
|
1188
|
+
elsif !tag_value.nil?
|
1189
|
+
name = tag_value
|
1190
|
+
else
|
1191
|
+
name = kitten_cloud_id
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
cfg = {"name" => name, "cloud" => cloud, "region" => r}
|
1196
|
+
# If we can at least find the config from the deploy this will
|
1197
|
+
# belong with, use that, even if it's an ungroomed resource.
|
1198
|
+
if !calling_deploy.nil? and
|
1199
|
+
!calling_deploy.original_config.nil? and
|
1200
|
+
!calling_deploy.original_config[type+"s"].nil?
|
1201
|
+
calling_deploy.original_config[type+"s"].each { |s|
|
1202
|
+
if s["name"] == name
|
1203
|
+
cfg = s.dup
|
1204
|
+
break
|
1205
|
+
end
|
1206
|
+
}
|
1207
|
+
|
1208
|
+
matches << resourceclass.new(mommacat: calling_deploy, kitten_cfg: cfg, cloud_id: kitten_cloud_id)
|
1209
|
+
else
|
1210
|
+
matches << resourceclass.new(mu_name: name, kitten_cfg: cfg, cloud_id: kitten_cloud_id.to_s)
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
}
|
1214
|
+
}
|
1215
|
+
end
|
1216
|
+
rescue Exception => e
|
1217
|
+
MU.log e.inspect, MU::ERR, details: e.backtrace
|
1218
|
+
end
|
1219
|
+
matches
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# Return the resource object of another member of this deployment
|
1223
|
+
# @param type [String,Symbol]: The type of resource
|
1224
|
+
# @param name [String]: The name of the resource as defined in its 'name' Basket of Kittens field
|
1225
|
+
# @param mu_name [String]: The fully-resolved and deployed name of the resource
|
1226
|
+
# @param cloud_id [String]: The cloud provider's unique identifier for this resource
|
1227
|
+
# @param created_only [Boolean]: Only return the littermate if its cloud_id method returns a value
|
1228
|
+
# @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.
|
1229
|
+
# @return [MU::Cloud]
|
1230
|
+
def findLitterMate(type: nil, name: nil, mu_name: nil, cloud_id: nil, created_only: false, return_all: false)
|
1231
|
+
shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
|
1232
|
+
type = cfg_plural
|
1233
|
+
has_multiples = attrs[:has_multiples]
|
1234
|
+
|
1235
|
+
@kitten_semaphore.synchronize {
|
1236
|
+
if !@kittens.has_key?(type)
|
1237
|
+
return nil
|
1238
|
+
end
|
1239
|
+
MU.log "findLitterMate(type: #{type}, name: #{name}, mu_name: #{mu_name}, cloud_id: #{cloud_id}, created_only: #{created_only}). Caller: #{caller[2]}", MU::DEBUG, details: @kittens.keys.map { |k| k.to_s+": "+@kittens[k].keys.join(", ") }
|
1240
|
+
@kittens[type].each { |sib_class, data|
|
1241
|
+
next if !name.nil? and name != sib_class
|
1242
|
+
if has_multiples
|
1243
|
+
if !name.nil?
|
1244
|
+
if return_all
|
1245
|
+
return data.dup
|
1246
|
+
end
|
1247
|
+
if data.size == 1 and (cloud_id.nil? or data.values.first.cloud_id == cloud_id)
|
1248
|
+
obj = data.values.first
|
1249
|
+
return obj
|
1250
|
+
elsif mu_name.nil? and cloud_id.nil?
|
1251
|
+
obj = data.values.first
|
1252
|
+
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
|
1253
|
+
return data.values.first
|
1254
|
+
end
|
1255
|
+
end
|
1256
|
+
data.each_pair { |sib_mu_name, obj|
|
1257
|
+
if (!mu_name.nil? and mu_name == sib_mu_name) or
|
1258
|
+
(!cloud_id.nil? and cloud_id == obj.cloud_id)
|
1259
|
+
if !created_only or !obj.cloud_id.nil?
|
1260
|
+
if return_all
|
1261
|
+
return data.dup
|
1262
|
+
else
|
1263
|
+
return obj
|
1264
|
+
end
|
1265
|
+
end
|
1266
|
+
end
|
1267
|
+
}
|
1268
|
+
else
|
1269
|
+
if (name.nil? or sib_class == name) and
|
1270
|
+
(cloud_id.nil? or cloud_id == data.cloud_id)
|
1271
|
+
return data if !created_only or !data.cloud_id.nil?
|
1272
|
+
end
|
1273
|
+
end
|
1274
|
+
}
|
1275
|
+
}
|
1276
|
+
return nil
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
# Add or remove a resource's metadata to this deployment's structure and
|
1280
|
+
# flush it to disk.
|
1281
|
+
# @param type [String]: The type of resource (e.g. *server*, *database*).
|
1282
|
+
# @param key [String]: The name field of this resource.
|
1283
|
+
# @param data [Hash]: The resource's metadata.
|
1284
|
+
# @param remove [Boolean]: Remove this resource from the deploy structure, instead of adding it.
|
1285
|
+
# @return [void]
|
1286
|
+
def notify(type, key, data, mu_name: nil, remove: false, triggering_node: nil, delayed_save: false)
|
1287
|
+
return if @no_artifacts
|
1288
|
+
MU::MommaCat.lock("deployment-notification")
|
1289
|
+
loadDeploy(true) # make sure we're saving the latest and greatest
|
1290
|
+
have_deploy = true
|
1291
|
+
shortclass, cfg_name, cfg_plural, classname, attrs = MU::Cloud.getResourceNames(type)
|
1292
|
+
type = cfg_plural
|
1293
|
+
has_multiples = attrs[:has_multiples]
|
1294
|
+
|
1295
|
+
if mu_name.nil?
|
1296
|
+
if !data.nil? and !data["mu_name"].nil?
|
1297
|
+
mu_name = data["mu_name"]
|
1298
|
+
elsif !triggering_node.nil? and !triggering_node.mu_name.nil?
|
1299
|
+
mu_name = triggering_node.mu_name
|
1300
|
+
end
|
1301
|
+
if mu_name.nil? and has_multiples
|
1302
|
+
MU.log "MU::MommaCat.notify called to modify deployment struct for a type (#{type}) with :has_multiples, but no mu_name available to look under #{key}. Call was #{caller[0]}", MU::WARN, details: data
|
1303
|
+
MU::MommaCat.unlock("deployment-notification")
|
1304
|
+
return
|
1305
|
+
end
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
if !remove
|
1309
|
+
if data.nil?
|
1310
|
+
MU.log "MU::MommaCat.notify called to modify deployment struct, but no data provided", MU::WARN
|
1311
|
+
MU::MommaCat.unlock("deployment-notification")
|
1312
|
+
return
|
1313
|
+
end
|
1314
|
+
@deployment[type] = {} if @deployment[type].nil?
|
1315
|
+
if has_multiples
|
1316
|
+
@deployment[type][key] = {} if @deployment[type][key].nil?
|
1317
|
+
# fix has_multiples classes that weren't tiered correctly
|
1318
|
+
if @deployment[type][key].is_a?(Hash) and @deployment[type][key].has_key?("mu_name")
|
1319
|
+
olddata = @deployment[type][key].dup
|
1320
|
+
@deployment[type][key][olddata["mu_name"]] = olddata
|
1321
|
+
end
|
1322
|
+
@deployment[type][key][mu_name] = data
|
1323
|
+
MU.log "Adding to @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: data
|
1324
|
+
else
|
1325
|
+
@deployment[type][key] = data
|
1326
|
+
MU.log "Adding to @deployment[#{type}][#{key}]", MU::DEBUG, details: data
|
1327
|
+
end
|
1328
|
+
save!(key) if !delayed_save
|
1329
|
+
else
|
1330
|
+
have_deploy = true
|
1331
|
+
if @deployment[type].nil? or @deployment[type][key].nil?
|
1332
|
+
|
1333
|
+
if has_multiples
|
1334
|
+
MU.log "MU::MommaCat.notify called to remove #{type} #{key} #{mu_name} deployment struct, but no such data exist", MU::DEBUG
|
1335
|
+
else
|
1336
|
+
MU.log "MU::MommaCat.notify called to remove #{type} #{key} deployment struct, but no such data exist", MU::DEBUG
|
1337
|
+
end
|
1338
|
+
MU::MommaCat.unlock("deployment-notification")
|
1339
|
+
|
1340
|
+
return
|
1341
|
+
end
|
1342
|
+
|
1343
|
+
if have_deploy
|
1344
|
+
if has_multiples
|
1345
|
+
MU.log "Removing @deployment[#{type}][#{key}][#{mu_name}]", MU::DEBUG, details: @deployment[type][key][mu_name]
|
1346
|
+
@deployment[type][key].delete(mu_name)
|
1347
|
+
if @deployment[type][key].size == 0
|
1348
|
+
@deployment[type].delete(key)
|
1349
|
+
end
|
1350
|
+
else
|
1351
|
+
MU.log "Removing @deployment[#{type}][#{key}]", MU::DEBUG, details: @deployment[type][key]
|
1352
|
+
@deployment[type].delete(key)
|
1353
|
+
end
|
1354
|
+
if @deployment[type].size == 0
|
1355
|
+
@deployment.delete(type)
|
1356
|
+
end
|
1357
|
+
end
|
1358
|
+
save! if !delayed_save
|
1359
|
+
|
1360
|
+
end
|
1361
|
+
MU::MommaCat.unlock("deployment-notification")
|
1362
|
+
end
|
1363
|
+
|
1364
|
+
# Tag a resource. Defaults to applying our MU deployment identifier, if no
|
1365
|
+
# arguments other than the resource identifier are given.
|
1366
|
+
# XXX this belongs in the cloud layer(s)
|
1367
|
+
#
|
1368
|
+
# @param resource [String]: The cloud provider identifier of the resource to tag
|
1369
|
+
# @param tag_name [String]: The name of the tag to create
|
1370
|
+
# @param tag_value [String]: The value of the tag
|
1371
|
+
# @param region [String]: The cloud provider region
|
1372
|
+
# @return [void]
|
1373
|
+
def self.createTag(resource = nil,
|
1374
|
+
tag_name="MU-ID",
|
1375
|
+
tag_value=MU.deploy_id,
|
1376
|
+
region: MU.curRegion)
|
1377
|
+
attempts = 0
|
1378
|
+
|
1379
|
+
if !MU::Cloud::CloudFormation.emitCloudFormation
|
1380
|
+
begin
|
1381
|
+
MU::Cloud::AWS.ec2(region).create_tags(
|
1382
|
+
resources: [resource],
|
1383
|
+
tags: [
|
1384
|
+
{
|
1385
|
+
key: tag_name,
|
1386
|
+
value: tag_value
|
1387
|
+
}
|
1388
|
+
]
|
1389
|
+
)
|
1390
|
+
rescue Aws::EC2::Errors::ServiceError => e
|
1391
|
+
MU.log "Got #{e.inspect} tagging #{resource} with #{tag_name}=#{tag_value}", MU::WARN if attempts > 1
|
1392
|
+
if attempts < 5
|
1393
|
+
attempts = attempts + 1
|
1394
|
+
sleep 15
|
1395
|
+
retry
|
1396
|
+
else
|
1397
|
+
raise e
|
1398
|
+
end
|
1399
|
+
end
|
1400
|
+
MU.log "Created tag #{tag_name} with value #{tag_value} for resource #{resource}", MU::DEBUG
|
1401
|
+
else
|
1402
|
+
return {
|
1403
|
+
"Key" => tag_name,
|
1404
|
+
"Value" => tag_value
|
1405
|
+
}
|
1406
|
+
end
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
# XXX this belongs in MU::Cloud::AWS
|
1410
|
+
# Tag a resource with all of our standard identifying tags.
|
1411
|
+
#
|
1412
|
+
# @param resource [String]: The cloud provider identifier of the resource to tag
|
1413
|
+
# @param region [String]: The cloud provider region
|
1414
|
+
# @return [void]
|
1415
|
+
def self.createStandardTags(resource = nil, region: MU.curRegion)
|
1416
|
+
tags = []
|
1417
|
+
listStandardTags.each_pair { |name, value|
|
1418
|
+
if !value.nil?
|
1419
|
+
tags << {key: name, value: value}
|
1420
|
+
end
|
1421
|
+
}
|
1422
|
+
if MU::Cloud::CloudFormation.emitCloudFormation
|
1423
|
+
return tags
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
attempts = 0
|
1427
|
+
begin
|
1428
|
+
MU::Cloud::AWS.ec2(region).create_tags(
|
1429
|
+
resources: [resource],
|
1430
|
+
tags: tags
|
1431
|
+
)
|
1432
|
+
rescue Aws::EC2::Errors::ServiceError => e
|
1433
|
+
MU.log "Got #{e.inspect} tagging #{resource} in #{region}, will retry", MU::WARN, details: caller.concat(tags) if attempts > 1
|
1434
|
+
if attempts < 5
|
1435
|
+
attempts = attempts + 1
|
1436
|
+
sleep 15
|
1437
|
+
retry
|
1438
|
+
else
|
1439
|
+
raise e
|
1440
|
+
end
|
1441
|
+
end
|
1442
|
+
MU.log "Created standard tags for resource #{resource}", MU::DEBUG, details: caller
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
# List the name/value pairs for our mandatory standard set of resource tags, which
|
1446
|
+
# should be applied to all taggable cloud provider resources.
|
1447
|
+
# @return [Hash<String,String>]
|
1448
|
+
def self.listStandardTags
|
1449
|
+
return {
|
1450
|
+
"MU-ID" => MU.deploy_id,
|
1451
|
+
"MU-APP" => MU.appname,
|
1452
|
+
"MU-ENV" => MU.environment,
|
1453
|
+
"MU-MASTER-IP" => MU.mu_public_ip
|
1454
|
+
}
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
# List the name/value pairs of our optional set of resource tags which
|
1458
|
+
# should be applied to all taggable cloud provider resources.
|
1459
|
+
# @return [Hash<String,String>]
|
1460
|
+
def self.listOptionalTags
|
1461
|
+
return {
|
1462
|
+
"MU-HANDLE" => MU.handle,
|
1463
|
+
"MU-MASTER-NAME" => Socket.gethostname,
|
1464
|
+
"MU-OWNER" => MU.mu_user
|
1465
|
+
}
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
# Clean a node's entries out of ~/.ssh/config
|
1469
|
+
# @param node [String]: The node's name
|
1470
|
+
# @return [void]
|
1471
|
+
def self.removeHostFromSSHConfig(node)
|
1472
|
+
sshdir = "#{@myhome}/.ssh"
|
1473
|
+
sshconf = "#{sshdir}/config"
|
1474
|
+
|
1475
|
+
if File.exists?(sshconf) and File.open(sshconf).read.match(/ #{node} /)
|
1476
|
+
MU.log "Expunging old #{node} entry from #{sshconf}", MU::DEBUG
|
1477
|
+
if !@noop
|
1478
|
+
File.open(sshconf, File::CREAT|File::RDWR, 0600) { |f|
|
1479
|
+
f.flock(File::LOCK_EX)
|
1480
|
+
newlines = Array.new
|
1481
|
+
delete_block = false
|
1482
|
+
f.readlines.each { |line|
|
1483
|
+
if line.match(/^Host #{node}(\s|$)/)
|
1484
|
+
delete_block = true
|
1485
|
+
elsif line.match(/^Host /)
|
1486
|
+
delete_block = false
|
1487
|
+
end
|
1488
|
+
newlines << line if !delete_block
|
1489
|
+
}
|
1490
|
+
f.rewind
|
1491
|
+
f.truncate(0)
|
1492
|
+
f.puts(newlines)
|
1493
|
+
f.flush
|
1494
|
+
f.flock(File::LOCK_UN)
|
1495
|
+
}
|
1496
|
+
end
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
# Make sure the given node has proper DNS entries, /etc/hosts entries,
|
1502
|
+
# SSH config entries, etc.
|
1503
|
+
# @param server [MU::Cloud::Server]: The {MU::Cloud::Server} we'll be setting up.
|
1504
|
+
# @param sync_wait [Boolean]: Whether to wait for DNS to fully synchronize before returning.
|
1505
|
+
def self.nameKitten(server, sync_wait: false)
|
1506
|
+
node, config, deploydata = server.describe
|
1507
|
+
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_addr, ssh_user, ssh_key_name = server.getSSHConfig
|
1508
|
+
|
1509
|
+
mu_zone = nil
|
1510
|
+
# XXX GCP!
|
1511
|
+
if MU::Cloud::AWS.hosted and !MU::Cloud::AWS.isGovCloud?
|
1512
|
+
zones = MU::Cloud::DNSZone.find(cloud_id: "platform-mu")
|
1513
|
+
mu_zone = zones.values.first if !zones.nil?
|
1514
|
+
end
|
1515
|
+
if !mu_zone.nil?
|
1516
|
+
MU::Cloud::DNSZone.genericMuDNSEntry(name: node, target: server.canonicalIP, cloudclass: MU::Cloud::Server, sync_wait: sync_wait)
|
1517
|
+
else
|
1518
|
+
MU::MommaCat.addInstanceToEtcHosts(server.canonicalIP, node)
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
## TO DO: Do DNS registration of "real" records as the last stage after the groomer completes
|
1522
|
+
if config && config['dns_records'] && !config['dns_records'].empty?
|
1523
|
+
dnscfg = config['dns_records'].dup
|
1524
|
+
dnscfg.each { |dnsrec|
|
1525
|
+
if !dnsrec.has_key?('name')
|
1526
|
+
dnsrec['name'] = node.downcase
|
1527
|
+
dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/)
|
1528
|
+
end
|
1529
|
+
|
1530
|
+
if !dnsrec.has_key?("target")
|
1531
|
+
# Default to register public endpoint
|
1532
|
+
public = true
|
1533
|
+
|
1534
|
+
if dnsrec.has_key?("target_type")
|
1535
|
+
# See if we have a preference for pubic/private endpoint
|
1536
|
+
public = dnsrec["target_type"] == "private" ? false : true
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
dnsrec["target"] =
|
1540
|
+
if dnsrec["type"] == "CNAME"
|
1541
|
+
if public
|
1542
|
+
# Make sure we have a public canonical name to register. Use the private one if we don't
|
1543
|
+
server.cloud_desc.public_dns_name.empty? ? server.cloud_desc.private_dns_name : server.cloud_desc.public_dns_name
|
1544
|
+
else
|
1545
|
+
# If we specifically requested to register the private canonical name lets use that
|
1546
|
+
server.cloud_desc.private_dns_name
|
1547
|
+
end
|
1548
|
+
elsif dnsrec["type"] == "A"
|
1549
|
+
if public
|
1550
|
+
# Make sure we have a public IP address to register. Use the private one if we don't
|
1551
|
+
server.cloud_desc.public_ip_address ? server.cloud_desc.public_ip_address : server.cloud_desc.private_ip_address
|
1552
|
+
else
|
1553
|
+
# If we specifically requested to register the private IP lets use that
|
1554
|
+
server.cloud_desc.private_ip_address
|
1555
|
+
end
|
1556
|
+
end
|
1557
|
+
end
|
1558
|
+
}
|
1559
|
+
if !MU::Cloud::AWS.isGovCloud?
|
1560
|
+
MU::Cloud::DNSZone.createRecordsFromConfig(dnscfg)
|
1561
|
+
end
|
1562
|
+
end
|
1563
|
+
|
1564
|
+
MU::MommaCat.removeHostFromSSHConfig(node)
|
1565
|
+
# XXX add names paramater with useful stuff
|
1566
|
+
MU::MommaCat.addHostToSSHConfig(
|
1567
|
+
server,
|
1568
|
+
ssh_owner: server.deploy.mu_user,
|
1569
|
+
ssh_dir: Etc.getpwnam(server.deploy.mu_user).dir+"/.ssh"
|
1570
|
+
)
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
@ssh_semaphore = Mutex.new
|
1574
|
+
# Insert a definition for a node into our SSH config.
|
1575
|
+
# @param server [MU::Cloud::Server]: The name of the node.
|
1576
|
+
# @param names [Array<String>]: Other names that we'd like this host to be known by for SSH purposes
|
1577
|
+
# @param ssh_dir [String]: The configuration directory of the SSH config to emit.
|
1578
|
+
# @param ssh_conf [String]: A specific SSH configuration file to write entries into.
|
1579
|
+
# @param ssh_owner [String]: The preferred owner of the SSH configuration files.
|
1580
|
+
# @param timeout [Integer]: An alternate timeout value for connections to this server.
|
1581
|
+
# @return [void]
|
1582
|
+
def self.addHostToSSHConfig(server,
|
1583
|
+
ssh_dir: "#{@myhome}/.ssh",
|
1584
|
+
ssh_conf: "#{@myhome}/.ssh/config",
|
1585
|
+
ssh_owner: Etc.getpwuid(Process.uid).name,
|
1586
|
+
names: [],
|
1587
|
+
timeout: 0
|
1588
|
+
)
|
1589
|
+
if server.nil?
|
1590
|
+
MU.log "Called addHostToSSHConfig without a MU::Cloud::Server object", MU::ERR, details: caller
|
1591
|
+
return nil
|
1592
|
+
end
|
1593
|
+
begin
|
1594
|
+
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = server.getSSHConfig
|
1595
|
+
rescue MU::MuError => e
|
1596
|
+
return
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
if ssh_user.nil? or ssh_user.empty?
|
1600
|
+
MU.log "Failed to extract ssh_user for #{server.mu_name} addHostToSSHConfig", MU::ERR
|
1601
|
+
return
|
1602
|
+
end
|
1603
|
+
if canonical_ip.nil? or canonical_ip.empty?
|
1604
|
+
MU.log "Failed to extract canonical_ip for #{server.mu_name} addHostToSSHConfig", MU::ERR
|
1605
|
+
return
|
1606
|
+
end
|
1607
|
+
if ssh_key_name.nil? or ssh_key_name.empty?
|
1608
|
+
MU.log "Failed to extract canonical_ip for #{ssh_key_name.mu_name} in addHostToSSHConfig", MU::ERR
|
1609
|
+
return
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
@ssh_semaphore.synchronize {
|
1613
|
+
|
1614
|
+
if File.exists?(ssh_conf)
|
1615
|
+
File.readlines(ssh_conf).each { |line|
|
1616
|
+
if line.match(/^Host #{server.mu_name} /)
|
1617
|
+
MU.log("Attempt to add duplicate #{ssh_conf} entry for #{server.mu_name}", MU::WARN)
|
1618
|
+
return
|
1619
|
+
end
|
1620
|
+
}
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
File.open(ssh_conf, 'a', 0600) { |ssh_config|
|
1624
|
+
ssh_config.flock(File::LOCK_EX)
|
1625
|
+
host_str = "Host #{server.mu_name} #{server.canonicalIP}"
|
1626
|
+
if !names.nil? and names.size > 0
|
1627
|
+
host_str = host_str+" "+names.join(" ")
|
1628
|
+
end
|
1629
|
+
ssh_config.puts host_str
|
1630
|
+
ssh_config.puts " Hostname #{server.canonicalIP}"
|
1631
|
+
if !nat_ssh_host.nil? and server.canonicalIP != nat_ssh_host
|
1632
|
+
ssh_config.puts " ProxyCommand ssh -W %h:%p #{nat_ssh_user}@#{nat_ssh_host}"
|
1633
|
+
end
|
1634
|
+
if timeout > 0
|
1635
|
+
ssh_config.puts " ConnectTimeout #{timeout}"
|
1636
|
+
end
|
1637
|
+
|
1638
|
+
ssh_config.puts " User #{ssh_user}"
|
1639
|
+
# XXX I'd rather add the host key to known_hosts, but Net::SSH is a little dumb
|
1640
|
+
ssh_config.puts " StrictHostKeyChecking no"
|
1641
|
+
ssh_config.puts " ServerAliveInterval 60"
|
1642
|
+
|
1643
|
+
ssh_config.puts " IdentityFile #{ssh_dir}/#{ssh_key_name}"
|
1644
|
+
if !File.exist?("#{ssh_dir}/#{ssh_key_name}")
|
1645
|
+
MU.log "#{server.mu_name} - ssh private key #{ssh_dir}/#{ssh_key_name} does not exist", MU::WARN
|
1646
|
+
end
|
1647
|
+
|
1648
|
+
ssh_config.flock(File::LOCK_UN)
|
1649
|
+
ssh_config.chown(Etc.getpwnam(ssh_owner).uid, Etc.getpwnam(ssh_owner).gid)
|
1650
|
+
}
|
1651
|
+
MU.log "Wrote #{server.mu_name} ssh key to #{ssh_dir}/config", MU::DEBUG
|
1652
|
+
return "#{ssh_dir}/#{ssh_key_name}"
|
1653
|
+
}
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
# Clean a node's entries out of /etc/hosts
|
1657
|
+
# @param node [String]: The node's name
|
1658
|
+
# @return [void]
|
1659
|
+
def self.removeInstanceFromEtcHosts(node)
|
1660
|
+
return if MU.mu_user != "mu"
|
1661
|
+
hostsfile = "/etc/hosts"
|
1662
|
+
FileUtils.copy(hostsfile, "#{hostsfile}.bak-#{MU.deploy_id}")
|
1663
|
+
File.open(hostsfile, File::CREAT|File::RDWR, 0644) { |f|
|
1664
|
+
f.flock(File::LOCK_EX)
|
1665
|
+
newlines = Array.new
|
1666
|
+
f.readlines.each { |line|
|
1667
|
+
newlines << line if !line.match(/ #{node}(\s|$)/)
|
1668
|
+
}
|
1669
|
+
f.rewind
|
1670
|
+
f.truncate(0)
|
1671
|
+
f.puts(newlines)
|
1672
|
+
f.flush
|
1673
|
+
|
1674
|
+
f.flock(File::LOCK_UN)
|
1675
|
+
}
|
1676
|
+
end
|
1677
|
+
|
1678
|
+
|
1679
|
+
# Insert node names associated with a new instance into /etc/hosts so we
|
1680
|
+
# can treat them as if they were real DNS entries. Especially helpful when
|
1681
|
+
# Chef/Ohai mistake the proper hostname, e.g. when bootstrapping Windows.
|
1682
|
+
# @param public_ip [String]: The node's IP address
|
1683
|
+
# @param chef_name [String]: The node's Chef node name
|
1684
|
+
# @param system_name [String]: The node's local system name
|
1685
|
+
# @return [void]
|
1686
|
+
def self.addInstanceToEtcHosts(public_ip, chef_name = nil, system_name = nil)
|
1687
|
+
return if !["mu", "root"].include?(MU.mu_user)
|
1688
|
+
|
1689
|
+
# XXX cover ipv6 case
|
1690
|
+
if public_ip.nil? or !public_ip.match(/^\d+\.\d+\.\d+\.\d+$/) or (chef_name.nil? and system_name.nil?)
|
1691
|
+
raise MuError, "addInstanceToEtcHosts requires public_ip and one or both of chef_name and system_name!"
|
1692
|
+
end
|
1693
|
+
if chef_name == "localhost" or system_name == "localhost"
|
1694
|
+
raise MuError, "Can't set localhost as a name in addInstanceToEtcHosts"
|
1695
|
+
end
|
1696
|
+
File.readlines("/etc/hosts").each { |line|
|
1697
|
+
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|$)/))
|
1698
|
+
MU.log "Ignoring attempt to add duplicate /etc/hosts entry: #{public_ip} #{chef_name} #{system_name}", MU::DEBUG
|
1699
|
+
return
|
1700
|
+
end
|
1701
|
+
}
|
1702
|
+
File.open("/etc/hosts", 'a') { |etc_hosts|
|
1703
|
+
etc_hosts.flock(File::LOCK_EX)
|
1704
|
+
etc_hosts.puts("#{public_ip} #{chef_name} #{system_name}")
|
1705
|
+
etc_hosts.flock(File::LOCK_UN)
|
1706
|
+
}
|
1707
|
+
MU.log("Added to /etc/hosts: #{public_ip} #{chef_name} #{system_name}")
|
1708
|
+
end
|
1709
|
+
|
1710
|
+
# Send a notification to a deployment's administrators.
|
1711
|
+
# @param subject [String]: The subject line of the message.
|
1712
|
+
# @param msg [String]: The message body.
|
1713
|
+
# @param data [Array]: Supplemental data to add to the message body.
|
1714
|
+
# @param debug [Boolean]: If set, will include the full deployment structure and original {MU::Config}-parsed configuration.
|
1715
|
+
# @return [void]
|
1716
|
+
def sendAdminMail(subject, msg: msg = "", kitten: nil, data: nil, debug: debug = false)
|
1717
|
+
require 'net/smtp'
|
1718
|
+
if @deployment.nil?
|
1719
|
+
MU.log "Can't send admin mail without a loaded deployment", MU::ERR
|
1720
|
+
return
|
1721
|
+
end
|
1722
|
+
to = Array.new
|
1723
|
+
if !@original_config.nil?
|
1724
|
+
@original_config['admins'].each { |admin|
|
1725
|
+
to << "#{admin['name']} <#{admin['email']}>"
|
1726
|
+
}
|
1727
|
+
end
|
1728
|
+
message = <<MESSAGE_END
|
1729
|
+
From: #{MU.handle} <root@localhost>
|
1730
|
+
To: #{to.join(",")}
|
1731
|
+
Subject: #{subject}
|
1732
|
+
|
1733
|
+
#{msg}
|
1734
|
+
MESSAGE_END
|
1735
|
+
if !kitten.nil? and kitten.kind_of?(MU::Cloud)
|
1736
|
+
message = message + "\n\n**** #{kitten}:\n"
|
1737
|
+
if !kitten.report.nil?
|
1738
|
+
kitten.report.each { |line|
|
1739
|
+
message = message + line
|
1740
|
+
}
|
1741
|
+
end
|
1742
|
+
end
|
1743
|
+
if !data.nil?
|
1744
|
+
message = message + "\n\n" + PP.pp(data, "")
|
1745
|
+
end
|
1746
|
+
if debug
|
1747
|
+
message = message + "\n\n**** Stack configuration:\n" + PP.pp(@original_config, "")
|
1748
|
+
message = message + "\n\n**** Deployment structure:\n" + PP.pp(@deployment, "")
|
1749
|
+
end
|
1750
|
+
begin
|
1751
|
+
Net::SMTP.start('localhost') do |smtp|
|
1752
|
+
smtp.send_message message, "root@localhost", to
|
1753
|
+
end
|
1754
|
+
rescue Net::SMTPFatalError, Errno::ECONNREFUSED => e
|
1755
|
+
MU.log e.inspect, MU::WARN
|
1756
|
+
end
|
1757
|
+
end
|
1758
|
+
|
1759
|
+
# Manufactures a human-readable deployment name from the random
|
1760
|
+
# two-character seed in MU-ID. Cat-themed when possible.
|
1761
|
+
# @param seed [String]: A two-character seed from which we'll generate a name.
|
1762
|
+
# @return [String]: Two words
|
1763
|
+
def self.generateHandle(seed)
|
1764
|
+
word_one=word_two=nil
|
1765
|
+
|
1766
|
+
# Unless we've got two letters that don't have corresponding cat-themed
|
1767
|
+
# words, we'll insist that our generated handle have at least one cat
|
1768
|
+
# element to it.
|
1769
|
+
require_cat_words = true
|
1770
|
+
if @catwords.select { |word| word.match(/^#{seed[0]}/i) }.size == 0 and
|
1771
|
+
@catwords.select { |word| word.match(/^#{seed[1]}/i) }.size == 0
|
1772
|
+
require_cat_words = false
|
1773
|
+
MU.log "Got an annoying pair of letters #{seed}, not forcing cat-theming", MU::DEBUG
|
1774
|
+
end
|
1775
|
+
allnouns = @catnouns + @jaegernouns
|
1776
|
+
alladjs = @catadjs + @jaegeradjs
|
1777
|
+
|
1778
|
+
tries = 0
|
1779
|
+
begin
|
1780
|
+
# Try to avoid picking something "nouny" for the first word
|
1781
|
+
source = @catadjs + @catmixed + @jaegeradjs + @jaegermixed
|
1782
|
+
first_ltr = source.select { |word| word.match(/^#{seed[0]}/i) }
|
1783
|
+
if !first_ltr or first_ltr.size == 0
|
1784
|
+
first_ltr = @words.select { |word| word.match(/^#{seed[0]}/i) }
|
1785
|
+
end
|
1786
|
+
word_one = first_ltr.shuffle.first
|
1787
|
+
|
1788
|
+
# If we got a paired set that happen to match our letters, go with it
|
1789
|
+
if !word_one.nil? and word_one.match(/-#{seed[1]}/i)
|
1790
|
+
word_one, word_two = word_one.split(/-/)
|
1791
|
+
else
|
1792
|
+
source = @words
|
1793
|
+
if @catwords.include?(word_one)
|
1794
|
+
source = @jaegerwords
|
1795
|
+
elsif require_cat_words
|
1796
|
+
source = @catwords
|
1797
|
+
end
|
1798
|
+
second_ltr = source.select { |word| word.match(/^#{seed[1]}/i) and !word.match(/-/i) }
|
1799
|
+
word_two = second_ltr.shuffle.first
|
1800
|
+
end
|
1801
|
+
tries = tries + 1
|
1802
|
+
end while tries < 50 and (word_one.nil? or word_two.nil? or word_one.match(/-/) or word_one == word_two or (allnouns.include?(word_one) and allnouns.include?(word_two)) or (alladjs.include?(word_one) and alladjs.include?(word_two)) or (require_cat_words and !@catwords.include?(word_one) and !@catwords.include?(word_two)))
|
1803
|
+
|
1804
|
+
if tries >= 50 and (word_one.nil? or word_two.nil?)
|
1805
|
+
MU.log "I failed to generated a valid handle, faking it", MU::ERR
|
1806
|
+
return "#{seed[0].capitalize} #{seed[1].capitalize}"
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
return "#{word_one.capitalize} #{word_two.capitalize}"
|
1810
|
+
end
|
1811
|
+
|
1812
|
+
# Ensure that the Nagios configuration local to the MU master has been
|
1813
|
+
# updated, and make sure Nagios has all of the ssh keys it needs to tunnel
|
1814
|
+
# to client nodes.
|
1815
|
+
# @return [void]
|
1816
|
+
def self.syncMonitoringConfig(blocking = true)
|
1817
|
+
return if Etc.getpwuid(Process.uid).name != "root" or (MU.mu_user != "mu" and MU.mu_user != "root")
|
1818
|
+
parent_thread_id = Thread.current.object_id
|
1819
|
+
nagios_threads = []
|
1820
|
+
nagios_threads << Thread.new {
|
1821
|
+
MU.dupGlobals(parent_thread_id)
|
1822
|
+
realhome = Etc.getpwnam("nagios").dir
|
1823
|
+
[@nagios_home, "#{@nagios_home}/.ssh"].each { |dir|
|
1824
|
+
Dir.mkdir(dir, 0711) if !Dir.exists?(dir)
|
1825
|
+
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, dir)
|
1826
|
+
}
|
1827
|
+
if realhome != @nagios_home and Dir.exists?(realhome) and !File.symlink?("#{realhome}/.ssh")
|
1828
|
+
File.rename("#{realhome}/.ssh", "#{realhome}/.ssh.#{$$}") if Dir.exists?("#{realhome}/.ssh")
|
1829
|
+
File.symlink("#{@nagios_home}/.ssh", Etc.getpwnam("nagios").dir+"/.ssh")
|
1830
|
+
end
|
1831
|
+
MU.log "Updating #{@nagios_home}/.ssh/config..."
|
1832
|
+
ssh_lock = File.new("#{@nagios_home}/.ssh/config.mu.lock", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
1833
|
+
ssh_lock.flock(File::LOCK_EX)
|
1834
|
+
ssh_conf = File.new("#{@nagios_home}/.ssh/config.tmp", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
1835
|
+
ssh_conf.puts "Host MU-MASTER localhost"
|
1836
|
+
ssh_conf.puts " Hostname localhost"
|
1837
|
+
ssh_conf.puts " User root"
|
1838
|
+
ssh_conf.puts " IdentityFile #{@nagios_home}/.ssh/id_rsa"
|
1839
|
+
ssh_conf.puts " StrictHostKeyChecking no"
|
1840
|
+
ssh_conf.close
|
1841
|
+
FileUtils.cp("#{@myhome}/.ssh/id_rsa", "#{@nagios_home}/.ssh/id_rsa")
|
1842
|
+
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/id_rsa")
|
1843
|
+
threads = []
|
1844
|
+
if !MU::Cloud::AWS.isGovCloud?
|
1845
|
+
mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
|
1846
|
+
end
|
1847
|
+
# XXX what if we're in GCP?
|
1848
|
+
# XXX need a MU::Cloud::DNSZone.lookup for bulk lookups
|
1849
|
+
# XXX also grab things like mu_windows_name out of deploy data if we can
|
1850
|
+
|
1851
|
+
parent_thread_id = Thread.current.object_id
|
1852
|
+
MU::MommaCat.listDeploys.sort.each { |deploy_id|
|
1853
|
+
begin
|
1854
|
+
# We don't want to use cached litter information here because this is also called by cleanTerminatedInstances.
|
1855
|
+
deploy = MU::MommaCat.getLitter(deploy_id, use_cache: false)
|
1856
|
+
if deploy.ssh_key_name.nil? or deploy.ssh_key_name.empty?
|
1857
|
+
MU.log "Failed to extract ssh key name from #{deploy_id} in syncMonitoringConfig", MU::ERR if deploy.kittens.has_key?("servers")
|
1858
|
+
next
|
1859
|
+
end
|
1860
|
+
FileUtils.cp("#{@myhome}/.ssh/#{deploy.ssh_key_name}", "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
|
1861
|
+
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/#{deploy.ssh_key_name}")
|
1862
|
+
if deploy.kittens.has_key?("servers")
|
1863
|
+
deploy.kittens["servers"].each_pair { |nodeclass, nodes|
|
1864
|
+
nodes.each_pair { |mu_name, server|
|
1865
|
+
MU.dupGlobals(parent_thread_id)
|
1866
|
+
threads << Thread.new {
|
1867
|
+
MU::MommaCat.setThreadContext(deploy)
|
1868
|
+
MU.log "Adding #{server.mu_name} to #{@nagios_home}/.ssh/config", MU::DEBUG
|
1869
|
+
MU::MommaCat.addHostToSSHConfig(
|
1870
|
+
server,
|
1871
|
+
ssh_dir: "#{@nagios_home}/.ssh",
|
1872
|
+
ssh_conf: "#{@nagios_home}/.ssh/config.tmp",
|
1873
|
+
ssh_owner: "nagios"
|
1874
|
+
)
|
1875
|
+
MU.purgeGlobals
|
1876
|
+
}
|
1877
|
+
}
|
1878
|
+
}
|
1879
|
+
end
|
1880
|
+
rescue Exception => e
|
1881
|
+
MU.log "#{e.inspect} while generating Nagios SSH config in #{deploy_id}", MU::ERR, details: e.backtrace
|
1882
|
+
end
|
1883
|
+
}
|
1884
|
+
threads.each { |t|
|
1885
|
+
t.join
|
1886
|
+
}
|
1887
|
+
ssh_lock.flock(File::LOCK_UN)
|
1888
|
+
ssh_lock.close
|
1889
|
+
File.chown(Etc.getpwnam("nagios").uid, Etc.getpwnam("nagios").gid, "#{@nagios_home}/.ssh/config.tmp")
|
1890
|
+
File.rename("#{@nagios_home}/.ssh/config.tmp", "#{@nagios_home}/.ssh/config")
|
1891
|
+
|
1892
|
+
MU.log "Updating Nagios monitoring config, this may take a while..."
|
1893
|
+
output = nil
|
1894
|
+
if $MU_CFG and !$MU_CFG['master_runlist_extras'].nil?
|
1895
|
+
output = %x{#{MU::Groomer::Chef.chefclient} -o 'role[mu-master-nagios-only],#{$MU_CFG['master_runlist_extras'].join(",")}' 2>&1}
|
1896
|
+
else
|
1897
|
+
output = %x{#{MU::Groomer::Chef.chefclient} -o 'role[mu-master-nagios-only]' 2>&1}
|
1898
|
+
end
|
1899
|
+
|
1900
|
+
if $?.exitstatus != 0
|
1901
|
+
MU.log "Nagios monitoring config update returned a non-zero exit code!", MU::ERR, details: output
|
1902
|
+
else
|
1903
|
+
MU.log "Nagios monitoring config update complete."
|
1904
|
+
end
|
1905
|
+
}
|
1906
|
+
|
1907
|
+
if blocking
|
1908
|
+
nagios_threads.each { |t|
|
1909
|
+
t.join
|
1910
|
+
}
|
1911
|
+
end
|
1912
|
+
end
|
1913
|
+
|
1914
|
+
# Return a list of all currently active deploy identifiers.
|
1915
|
+
# @return [Array<String>]
|
1916
|
+
def self.listDeploys
|
1917
|
+
return [] if !Dir.exists?("#{MU.dataDir}/deployments")
|
1918
|
+
deploys = []
|
1919
|
+
Dir.entries("#{MU.dataDir}/deployments").reverse_each { |muid|
|
1920
|
+
next if !Dir.exists?("#{MU.dataDir}/deployments/#{muid}") or muid == "." or muid == ".."
|
1921
|
+
deploys << muid
|
1922
|
+
}
|
1923
|
+
return deploys
|
1924
|
+
end
|
1925
|
+
|
1926
|
+
# Return a list of all nodes in all deployments. Does so without loading
|
1927
|
+
# deployments fully.
|
1928
|
+
# @return [Hash]
|
1929
|
+
def self.listAllNodes
|
1930
|
+
nodes = Hash.new
|
1931
|
+
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
1932
|
+
MU::MommaCat.listDeploys.each { |deploy|
|
1933
|
+
if !Dir.exists?(MU::MommaCat.deploy_dir(deploy)) or
|
1934
|
+
!File.size?("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json")
|
1935
|
+
MU.log "Didn't see deployment metadata for '#{deploy}'", MU::WARN
|
1936
|
+
next
|
1937
|
+
end
|
1938
|
+
data = File.open("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json", File::RDONLY)
|
1939
|
+
MU.log "Getting lock to read #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::DEBUG
|
1940
|
+
data.flock(File::LOCK_EX)
|
1941
|
+
begin
|
1942
|
+
deployment = JSON.parse(File.read("#{MU::MommaCat.deploy_dir(deploy)}/deployment.json"))
|
1943
|
+
deployment["deploy_id"] = deploy
|
1944
|
+
if deployment.has_key?("servers")
|
1945
|
+
deployment["servers"].each_key { |nodeclass|
|
1946
|
+
deployment["servers"][nodeclass].each_pair { |mu_name, metadata|
|
1947
|
+
nodes[mu_name] = metadata
|
1948
|
+
}
|
1949
|
+
}
|
1950
|
+
end
|
1951
|
+
rescue JSON::ParserError => e
|
1952
|
+
MU.log "JSON parse failed on #{MU::MommaCat.deploy_dir(deploy)}/deployment.json", MU::ERR
|
1953
|
+
end
|
1954
|
+
data.flock(File::LOCK_UN)
|
1955
|
+
data.close
|
1956
|
+
}
|
1957
|
+
}
|
1958
|
+
return nodes
|
1959
|
+
end
|
1960
|
+
|
1961
|
+
# Return a list of all nodes associated with the current deployment.
|
1962
|
+
# @return [Hash]
|
1963
|
+
def listNodes
|
1964
|
+
nodes = Hash.new
|
1965
|
+
if !@deployment['servers'].nil?
|
1966
|
+
@deployment['servers'].each_pair { |nodetype, node|
|
1967
|
+
node.each_pair { |name, metadata|
|
1968
|
+
if name.nil? or metadata.nil? or !metadata.is_a?(Hash)
|
1969
|
+
MU.log "Original config of deploy #{MU.deploy_id} looks funny. It's probably very old.", MU::WARN
|
1970
|
+
next
|
1971
|
+
end
|
1972
|
+
metadata['deploy_id'] = MU.deploy_id
|
1973
|
+
nodes[name] = metadata
|
1974
|
+
['servers', 'server_pools'].each { |res_type|
|
1975
|
+
if !@original_config[res_type].nil?
|
1976
|
+
@original_config[res_type].each { |srv_conf|
|
1977
|
+
if srv_conf['name'] == nodetype
|
1978
|
+
nodes[name]['conf'] = srv_conf.dup
|
1979
|
+
end
|
1980
|
+
}
|
1981
|
+
end
|
1982
|
+
}
|
1983
|
+
}
|
1984
|
+
}
|
1985
|
+
end
|
1986
|
+
|
1987
|
+
return nodes
|
1988
|
+
end
|
1989
|
+
|
1990
|
+
# For a given (Windows) server, return it's administrator user and password.
|
1991
|
+
# This is generally for requests made to MommaCat from said server, which
|
1992
|
+
# we can assume have been authenticated with the deploy secret.
|
1993
|
+
# @param server [MU::Cloud::Server]: The Server object whose credentials we're fetching.
|
1994
|
+
def retrieveWindowsAdminCreds(server)
|
1995
|
+
if server.nil?
|
1996
|
+
raise MuError, "retrieveWindowsAdminCreds must be called with a Server object"
|
1997
|
+
elsif !server.is_a?(MU::Cloud::Server)
|
1998
|
+
raise MuError, "retrieveWindowsAdminCreds must be called with a Server object (got #{server.class.name})"
|
1999
|
+
end
|
2000
|
+
if server.config['use_cloud_provider_windows_password']
|
2001
|
+
return [server.config["windows_admin_username"], server.getWindowsAdminPassword]
|
2002
|
+
elsif server.config['windows_auth_vault'] && !server.config['windows_auth_vault'].empty?
|
2003
|
+
if server.config["windows_auth_vault"].has_key?("password_field")
|
2004
|
+
return [server.config["windows_admin_username"],
|
2005
|
+
server.groomer.getSecret(
|
2006
|
+
vault: server.config['windows_auth_vault']['vault'],
|
2007
|
+
item: server.config['windows_auth_vault']['item'],
|
2008
|
+
field: server.config["windows_auth_vault"]["password_field"]
|
2009
|
+
)]
|
2010
|
+
else
|
2011
|
+
return [server.config["windows_admin_username"], server.getWindowsAdminPassword]
|
2012
|
+
end
|
2013
|
+
end
|
2014
|
+
[]
|
2015
|
+
end
|
2016
|
+
|
2017
|
+
# Given a Certificate Signing Request, sign it with our internal CA and
|
2018
|
+
# writers the resulting signed certificate. Only works on local files.
|
2019
|
+
# @param csr_path [String]: The CSR to sign, as a file.
|
2020
|
+
def signSSLCert(csr_path, sans = [])
|
2021
|
+
# XXX more sanity here, this feels unsafe
|
2022
|
+
certdir = File.dirname(csr_path)
|
2023
|
+
certname = File.basename(csr_path, ".csr")
|
2024
|
+
if File.exists?("#{certdir}/#{certname}.crt")
|
2025
|
+
MU.log "Not re-signing SSL certificate request #{csr_path}, #{certdir}/#{certname}.crt already exists", MU::WARN
|
2026
|
+
return
|
2027
|
+
end
|
2028
|
+
MU.log "Signing SSL certificate request #{csr_path} with #{MU.mySSLDir}/Mu_CA.pem"
|
2029
|
+
|
2030
|
+
begin
|
2031
|
+
csr = OpenSSL::X509::Request.new File.read csr_path
|
2032
|
+
rescue Exception => e
|
2033
|
+
MU.log e.message, MU::ERR, details: File.read(csr_path)
|
2034
|
+
raise e
|
2035
|
+
end
|
2036
|
+
key = OpenSSL::PKey::RSA.new File.read "#{certdir}/#{certname}.key"
|
2037
|
+
|
2038
|
+
# Load up the Mu Certificate Authority
|
2039
|
+
cakey = OpenSSL::PKey::RSA.new File.read "#{MU.mySSLDir}/Mu_CA.key"
|
2040
|
+
cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
|
2041
|
+
cur_serial = 0
|
2042
|
+
File.open("#{MU.mySSLDir}/serial", File::CREAT|File::RDWR, 0600) { |f|
|
2043
|
+
f.flock(File::LOCK_EX)
|
2044
|
+
cur_serial = f.read.chomp!.to_i
|
2045
|
+
cur_serial = cur_serial + 1
|
2046
|
+
f.rewind
|
2047
|
+
f.truncate(0)
|
2048
|
+
f.puts cur_serial
|
2049
|
+
f.flush
|
2050
|
+
f.flock(File::LOCK_UN)
|
2051
|
+
}
|
2052
|
+
|
2053
|
+
# Create a certificate from our CSR, signed by the Mu CA
|
2054
|
+
cert = OpenSSL::X509::Certificate.new
|
2055
|
+
cert.serial = cur_serial
|
2056
|
+
cert.version = 3
|
2057
|
+
cert.not_before = Time.now
|
2058
|
+
cert.not_after = Time.now + 180000000
|
2059
|
+
cert.subject = csr.subject
|
2060
|
+
cert.public_key = csr.public_key
|
2061
|
+
cert.issuer = cacert.subject
|
2062
|
+
if !sans.nil? and sans.size > 0
|
2063
|
+
MU.log "Incorporting Subject Alternative Names: #{sans.join(",")}"
|
2064
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
2065
|
+
ef.issuer_certificate = cacert
|
2066
|
+
#v3_req_client
|
2067
|
+
ef.subject_certificate = cert
|
2068
|
+
ef.subject_request = csr
|
2069
|
+
cert.add_extension(ef.create_extension("keyUsage","nonRepudiation,digitalSignature,keyEncipherment", false))
|
2070
|
+
cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false))
|
2071
|
+
# XXX only do this if we see the otherName thinger in the san list
|
2072
|
+
cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth,serverAuth,codeSigning,emailProtection",false))
|
2073
|
+
end
|
2074
|
+
cert.sign cakey, OpenSSL::Digest::SHA256.new
|
2075
|
+
|
2076
|
+
open("#{certdir}/#{certname}.crt", 'w', 0644) { |io|
|
2077
|
+
io.write cert.to_pem
|
2078
|
+
}
|
2079
|
+
if MU.mu_user != "mu"
|
2080
|
+
owner_uid = Etc.getpwnam(MU.mu_user).uid
|
2081
|
+
File.chown(owner_uid, nil, "#{certdir}/#{certname}.crt")
|
2082
|
+
end
|
2083
|
+
|
2084
|
+
end
|
2085
|
+
|
2086
|
+
# Make sure deployment data is synchronized to/from each node in the
|
2087
|
+
# currently-loaded deployment.
|
2088
|
+
def syncLitter(nodeclasses = [], triggering_node: nil, save_all_only: false)
|
2089
|
+
# XXX take some config logic to decide what nodeclasses to hit
|
2090
|
+
# XXX don't run on triggering node, duh
|
2091
|
+
return if MU.syncLitterThread
|
2092
|
+
return if !Dir.exists?(deploy_dir)
|
2093
|
+
svrs = MU::Cloud.resource_types[:Server][:cfg_plural] # legibility shorthand
|
2094
|
+
|
2095
|
+
@kitten_semaphore.synchronize {
|
2096
|
+
if @kittens.nil? or
|
2097
|
+
@kittens[svrs].nil?
|
2098
|
+
MU.log "No #{svrs} as yet available in #{@deploy_id}", MU::DEBUG, details: @kittens
|
2099
|
+
return
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
MU.log "Updating these siblings in #{@deploy_id}: #{nodeclasses.join(', ')}", MU::DEBUG, details: @kittens[svrs].map { |nodeclass, instance| instance.keys }
|
2103
|
+
}
|
2104
|
+
|
2105
|
+
update_servers = []
|
2106
|
+
if nodeclasses.nil? or nodeclasses.size == 0
|
2107
|
+
litter = findLitterMate(type: "server", return_all: true)
|
2108
|
+
litter.each_pair { |mu_name, node|
|
2109
|
+
next if !triggering_node.nil? and mu_name == triggering_node.mu_name
|
2110
|
+
if !node.groomer.nil?
|
2111
|
+
update_servers << node
|
2112
|
+
end
|
2113
|
+
}
|
2114
|
+
else
|
2115
|
+
litter = {}
|
2116
|
+
nodeclasses.each { |nodeclass|
|
2117
|
+
mates = findLitterMate(type: "server", name: nodeclass, return_all: true)
|
2118
|
+
litter.merge!(mates) if mates
|
2119
|
+
}
|
2120
|
+
litter.each_pair { |mu_name, node|
|
2121
|
+
next if !triggering_node.nil? and mu_name == triggering_node.mu_name
|
2122
|
+
if !node.deploydata or !node.deploydata.keys.include?('nodename')
|
2123
|
+
details = node.deploydata ? node.deploydata.keys : nil
|
2124
|
+
MU.log "#{mu_name} deploy data is missing (possibly retired), not syncing it", MU::WARN, details: details
|
2125
|
+
else
|
2126
|
+
update_servers << node
|
2127
|
+
end
|
2128
|
+
}
|
2129
|
+
end
|
2130
|
+
return if update_servers.size == 0
|
2131
|
+
|
2132
|
+
update_servers.each { |node|
|
2133
|
+
# Not clear where this pollution comes from, but let's stick a temp
|
2134
|
+
# fix in here.
|
2135
|
+
if node.deploydata['nodename'] != node.mu_name
|
2136
|
+
MU.log "Node #{node.mu_name} had wrong or missing nodename (#{node.deploydata['nodename']}), correcting", MU::WARN
|
2137
|
+
node.deploydata['nodename'] = node.mu_name
|
2138
|
+
@deployment[svrs][node.config['name']][node.mu_name]['nodename'] = node.mu_name
|
2139
|
+
save!
|
2140
|
+
end
|
2141
|
+
}
|
2142
|
+
|
2143
|
+
# Merge everyone's deploydata together
|
2144
|
+
if !save_all_only
|
2145
|
+
skip = []
|
2146
|
+
update_servers.each { |node|
|
2147
|
+
if node.mu_name.nil? or node.deploydata.nil? or node.config.nil?
|
2148
|
+
MU.log "Missing mu_name #{node.mu_name}, deploydata, or config from #{node} in syncLitter", MU::ERR, details: node.deploydata
|
2149
|
+
next
|
2150
|
+
end
|
2151
|
+
|
2152
|
+
if !@deployment[svrs][node.config['name']].has_key?(node.mu_name) or @deployment[svrs][node.config['name']][node.mu_name] != node.deploydata
|
2153
|
+
@deployment[svrs][node.config['name']][node.mu_name] = node.deploydata
|
2154
|
+
else
|
2155
|
+
skip << node
|
2156
|
+
end
|
2157
|
+
}
|
2158
|
+
update_servers = update_servers - skip
|
2159
|
+
end
|
2160
|
+
|
2161
|
+
return if update_servers.size < 1
|
2162
|
+
threads = []
|
2163
|
+
parent_thread_id = Thread.current.object_id
|
2164
|
+
update_servers.each { |sibling|
|
2165
|
+
threads << Thread.new {
|
2166
|
+
Thread.abort_on_exception = true
|
2167
|
+
MU.dupGlobals(parent_thread_id)
|
2168
|
+
Thread.current.thread_variable_set("name", "sync-"+sibling.mu_name.downcase)
|
2169
|
+
MU.setVar("syncLitterThread", true)
|
2170
|
+
begin
|
2171
|
+
sibling.groomer.saveDeployData
|
2172
|
+
sibling.groomer.run(purpose: "Synchronizing sibling kittens") if !save_all_only
|
2173
|
+
rescue MU::Groomer::RunError => e
|
2174
|
+
MU.log "Sync of #{sibling.mu_name} failed: #{e.inspect}", MU::WARN
|
2175
|
+
end
|
2176
|
+
MU.purgeGlobals
|
2177
|
+
}
|
2178
|
+
}
|
2179
|
+
|
2180
|
+
threads.each { |t|
|
2181
|
+
t.join
|
2182
|
+
}
|
2183
|
+
|
2184
|
+
MU.log "Synchronization of #{@deploy_id} complete", MU::DEBUG, details: update_servers
|
2185
|
+
end
|
2186
|
+
|
2187
|
+
@node_cert_semaphore = nil
|
2188
|
+
# Given a MU::Cloud object, return the generic self-signed SSL
|
2189
|
+
# certficate we made for it. If one doesn't exist yet, generate it first.
|
2190
|
+
# If it's a Windows node, also generate a certificate for WinRM client auth.
|
2191
|
+
# @param resource [MU::Cloud]: The server or other MU::Cloud resource object for which to generate or return the cert
|
2192
|
+
# @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
|
2193
|
+
# @param keysize [Integer]: The size of the private key to use when generating this certificate
|
2194
|
+
def nodeSSLCerts(resource, poolname = false, keysize = 4096)
|
2195
|
+
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = resource.getSSHConfig
|
2196
|
+
|
2197
|
+
deploy_id = resource.deploy_id || resource.deploy.deploy_id
|
2198
|
+
|
2199
|
+
cert_cn = poolname ? deploy_id + "-" + resource.config['name'].upcase : resource.mu_name
|
2200
|
+
|
2201
|
+
certs = {}
|
2202
|
+
results = {}
|
2203
|
+
|
2204
|
+
@node_cert_semaphore.synchronize {
|
2205
|
+
if File.exists?("#{MU.mySSLDir}/#{cert_cn}.crt") and
|
2206
|
+
File.exists?("#{MU.mySSLDir}/#{cert_cn}.key")
|
2207
|
+
ext_cert = OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt"))
|
2208
|
+
if ext_cert.not_after < Time.now
|
2209
|
+
MU.log "Node certificate for #{cert_cn} is expired, regenerating", MU::WARN
|
2210
|
+
["crt", "key", "csr"].each { |suffix|
|
2211
|
+
if File.exists?("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
|
2212
|
+
File.unlink("#{MU.mySSLDir}/#{cert_cn}.#{suffix}")
|
2213
|
+
end
|
2214
|
+
}
|
2215
|
+
else
|
2216
|
+
results[cert_cn] = [
|
2217
|
+
OpenSSL::X509::Certificate.new(File.read("#{MU.mySSLDir}/#{cert_cn}.crt")),
|
2218
|
+
OpenSSL::PKey::RSA.new(File.read("#{MU.mySSLDir}/#{cert_cn}.key"))
|
2219
|
+
]
|
2220
|
+
end
|
2221
|
+
end
|
2222
|
+
|
2223
|
+
if results.size == 0
|
2224
|
+
certs[cert_cn] = {
|
2225
|
+
"sans" => ["IP:#{canonical_ip}"],
|
2226
|
+
"cn" => cert_cn
|
2227
|
+
}
|
2228
|
+
if canonical_ip
|
2229
|
+
certs[cert_cn]["sans"] = ["IP:#{canonical_ip}"]
|
2230
|
+
end
|
2231
|
+
end
|
2232
|
+
|
2233
|
+
if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
|
2234
|
+
if File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.crt") and
|
2235
|
+
File.exists?("#{MU.mySSLDir}/#{cert_cn}-winrm.key")
|
2236
|
+
results[cert_cn+"-winrm"] = [File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.crt"), File.read("#{MU.mySSLDir}/#{cert_cn}-winrm.key")]
|
2237
|
+
else
|
2238
|
+
certs[cert_cn+"-winrm"] = {
|
2239
|
+
"sans" => ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:#{resource.config['windows_admin_username']}@localhost"],
|
2240
|
+
"cn" => resource.config['windows_admin_username']
|
2241
|
+
}
|
2242
|
+
end
|
2243
|
+
end
|
2244
|
+
|
2245
|
+
certs.each { |certname, data|
|
2246
|
+
MU.log "Generating SSL certificate #{certname} for #{resource}"
|
2247
|
+
|
2248
|
+
# Create and save a key
|
2249
|
+
key = OpenSSL::PKey::RSA.new keysize
|
2250
|
+
if !Dir.exist?(MU.mySSLDir)
|
2251
|
+
Dir.mkdir(MU.mySSLDir, 0700)
|
2252
|
+
end
|
2253
|
+
|
2254
|
+
open("#{MU.mySSLDir}/#{certname}.key", 'w', 0600) { |io|
|
2255
|
+
io.write key.to_pem
|
2256
|
+
}
|
2257
|
+
# Create a certificate request for this node
|
2258
|
+
csr = OpenSSL::X509::Request.new
|
2259
|
+
csr.version = 3
|
2260
|
+
csr.subject = OpenSSL::X509::Name.parse "CN=#{data['cn']}/O=Mu/C=US"
|
2261
|
+
csr.public_key = key.public_key
|
2262
|
+
csr.sign key, OpenSSL::Digest::SHA256.new
|
2263
|
+
open("#{MU.mySSLDir}/#{certname}.csr", 'w', 0644) { |io|
|
2264
|
+
io.write csr.to_pem
|
2265
|
+
}
|
2266
|
+
if MU.chef_user == "mu"
|
2267
|
+
signSSLCert("#{MU.mySSLDir}/#{certname}.csr", data['sans'])
|
2268
|
+
else
|
2269
|
+
deploykey = OpenSSL::PKey::RSA.new(public_key)
|
2270
|
+
deploysecret = Base64.urlsafe_encode64(deploykey.public_encrypt(deploy_secret))
|
2271
|
+
# XXX things that aren't servers
|
2272
|
+
res_type = "server"
|
2273
|
+
res_type = "server_pool" if !resource.config['basis'].nil?
|
2274
|
+
uri = URI("https://#{MU.mu_public_addr}:2260/")
|
2275
|
+
req = Net::HTTP::Post.new(uri)
|
2276
|
+
req.set_form_data(
|
2277
|
+
"mu_id" => MU.deploy_id,
|
2278
|
+
"mu_resource_name" => resource.config['name'],
|
2279
|
+
"mu_resource_type" => res_type,
|
2280
|
+
"mu_ssl_sign" => "#{MU.mySSLDir}/#{certname}.csr",
|
2281
|
+
"mu_ssl_sans" => data["sans"].join(","),
|
2282
|
+
"mu_user" => MU.mu_user,
|
2283
|
+
"mu_deploy_secret" => deploysecret
|
2284
|
+
)
|
2285
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
2286
|
+
http.ca_file = "/etc/pki/Mu_CA.pem" # XXX why no worky?
|
2287
|
+
http.use_ssl = true
|
2288
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX this sucks
|
2289
|
+
response = http.request(req)
|
2290
|
+
MU.log "Got error back on signing request for #{MU.mySSLDir}/#{certname}.csr", MU::ERR if response.code != "200"
|
2291
|
+
end
|
2292
|
+
|
2293
|
+
pfx = nil
|
2294
|
+
cert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/#{certname}.crt"
|
2295
|
+
if [MU::Cloud::Server, MU::Cloud::AWS::Server, MU::Cloud::Google::Server].include?(resource.class) and resource.windows?
|
2296
|
+
cacert = OpenSSL::X509::Certificate.new File.read "#{MU.mySSLDir}/Mu_CA.pem"
|
2297
|
+
pfx = OpenSSL::PKCS12.create(nil, nil, key, cert, [cacert], nil, nil, nil, nil)
|
2298
|
+
open("#{MU.mySSLDir}/#{certname}.pfx", 'w', 0644) { |io|
|
2299
|
+
io.write pfx.to_der
|
2300
|
+
}
|
2301
|
+
end
|
2302
|
+
|
2303
|
+
results[certname] = [cert, key]
|
2304
|
+
|
2305
|
+
if resource.config['cloud'] == "AWS"
|
2306
|
+
MU::Cloud::AWS.writeDeploySecret(@deploy_id, cert.to_pem, certname+".crt")
|
2307
|
+
MU::Cloud::AWS.writeDeploySecret(@deploy_id, key.to_pem, certname+".key")
|
2308
|
+
if pfx
|
2309
|
+
MU::Cloud::AWS.writeDeploySecret(@deploy_id, pfx.to_der, certname+".pfx")
|
2310
|
+
end
|
2311
|
+
# XXX add google logic, or better yet abstract this method
|
2312
|
+
end
|
2313
|
+
}
|
2314
|
+
}
|
2315
|
+
|
2316
|
+
results[cert_cn]
|
2317
|
+
end
|
2318
|
+
|
2319
|
+
# @return [String]: The Mu Master filesystem directory holding metadata for the current deployment
|
2320
|
+
def deploy_dir
|
2321
|
+
MU::MommaCat.deploy_dir(@deploy_id)
|
2322
|
+
end
|
2323
|
+
|
2324
|
+
private
|
2325
|
+
|
2326
|
+
# Check to see whether a given resource name is unique across all
|
2327
|
+
# deployments on this Mu server. We only enforce this for certain classes
|
2328
|
+
# of names. If the name in question is available, add it to our cache of
|
2329
|
+
# said names. See #{MU::MommaCat.getResourceName}
|
2330
|
+
# @param name [String]: The name to attempt to allocate.
|
2331
|
+
# @return [Boolean]: True if allocation was successful.
|
2332
|
+
def allocateUniqueResourceName(name)
|
2333
|
+
raise MuError, "Cannot call allocateUniqueResourceName without an active deployment" if @deploy_id.nil?
|
2334
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
2335
|
+
File.open(path+"/unique_ids", File::CREAT|File::RDWR, 0600) { |f|
|
2336
|
+
existing = []
|
2337
|
+
f.flock(File::LOCK_EX)
|
2338
|
+
f.readlines.each { |line|
|
2339
|
+
existing << line.chomp
|
2340
|
+
}
|
2341
|
+
begin
|
2342
|
+
existing.each { |used|
|
2343
|
+
if used.match(/^#{name}:/)
|
2344
|
+
if !used.match(/^#{name}:#{@deploy_id}$/)
|
2345
|
+
MU.log "#{name} is already reserved by another resource on this Mu server.", MU::WARN, details: caller
|
2346
|
+
return false
|
2347
|
+
else
|
2348
|
+
return true
|
2349
|
+
end
|
2350
|
+
end
|
2351
|
+
}
|
2352
|
+
f.puts name+":"+@deploy_id
|
2353
|
+
return true
|
2354
|
+
ensure
|
2355
|
+
f.flock(File::LOCK_UN)
|
2356
|
+
end
|
2357
|
+
}
|
2358
|
+
end
|
2359
|
+
|
2360
|
+
###########################################################################
|
2361
|
+
###########################################################################
|
2362
|
+
def self.deploy_dir(deploy_id)
|
2363
|
+
raise MuError, "deploy_dir must get a deploy_id if called as class method (from #{caller[0]}; #{caller[1]})" if deploy_id.nil?
|
2364
|
+
# XXX this will blow up if someone sticks MU in /
|
2365
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
2366
|
+
if !Dir.exist?(path)
|
2367
|
+
MU.log "Creating #{path}", MU::DEBUG
|
2368
|
+
Dir.mkdir(path, 0700)
|
2369
|
+
end
|
2370
|
+
path = path+"/"+deploy_id
|
2371
|
+
return path
|
2372
|
+
end
|
2373
|
+
|
2374
|
+
def self.deploy_exists?(deploy_id)
|
2375
|
+
if deploy_id.nil? or deploy_id.empty?
|
2376
|
+
MU.log "Got nil deploy_id in MU::MommaCat.deploy_exists?", MU::WARN
|
2377
|
+
return
|
2378
|
+
end
|
2379
|
+
path = File.expand_path(MU.dataDir+"/deployments")
|
2380
|
+
if !Dir.exists?(path)
|
2381
|
+
Dir.mkdir(path, 0700)
|
2382
|
+
end
|
2383
|
+
deploy_path = File.expand_path(path+"/"+deploy_id)
|
2384
|
+
return Dir.exist?(deploy_path)
|
2385
|
+
end
|
2386
|
+
|
2387
|
+
|
2388
|
+
def createDeployKey
|
2389
|
+
key = OpenSSL::PKey::RSA.generate(4096)
|
2390
|
+
MU.log "Generated deploy key for #{MU.deploy_id}", MU::DEBUG, details: key.public_key.export
|
2391
|
+
return [key.export, key.public_key.export]
|
2392
|
+
end
|
2393
|
+
|
2394
|
+
# Synchronize all in-memory information related to this to deployment to
|
2395
|
+
# disk.
|
2396
|
+
def save!(triggering_node = nil)
|
2397
|
+
return if @no_artifacts
|
2398
|
+
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
2399
|
+
MU.log "Saving deployment #{MU.deploy_id}", MU::DEBUG
|
2400
|
+
|
2401
|
+
if !Dir.exist?(deploy_dir)
|
2402
|
+
MU.log "Creating #{deploy_dir}", MU::DEBUG
|
2403
|
+
Dir.mkdir(deploy_dir, 0700)
|
2404
|
+
end
|
2405
|
+
|
2406
|
+
if !@private_key.nil?
|
2407
|
+
privkey = File.new("#{deploy_dir}/private_key", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2408
|
+
privkey.puts @private_key
|
2409
|
+
privkey.close
|
2410
|
+
end
|
2411
|
+
|
2412
|
+
if !@public_key.nil?
|
2413
|
+
pubkey = File.new("#{deploy_dir}/public_key", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2414
|
+
pubkey.puts @public_key
|
2415
|
+
pubkey.close
|
2416
|
+
end
|
2417
|
+
|
2418
|
+
if !@deployment.nil? and @deployment.size > 0
|
2419
|
+
@deployment['handle'] = MU.handle if @deployment['handle'].nil? and !MU.handle.nil?
|
2420
|
+
@deployment['public_key'] = @public_key
|
2421
|
+
begin
|
2422
|
+
# XXX doing this to trigger JSON errors before stomping the stored
|
2423
|
+
# file...
|
2424
|
+
JSON.pretty_generate(@deployment, max_nesting: false)
|
2425
|
+
deploy = File.new("#{deploy_dir}/deployment.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2426
|
+
MU.log "Getting lock to write #{deploy_dir}/deployment.json", MU::DEBUG
|
2427
|
+
deploy.flock(File::LOCK_EX)
|
2428
|
+
deploy.puts JSON.pretty_generate(@deployment, max_nesting: false)
|
2429
|
+
rescue JSON::NestingError => e
|
2430
|
+
raise MuError, e.inspect+"\n\n"+@deployment.to_s
|
2431
|
+
end
|
2432
|
+
deploy.flock(File::LOCK_UN)
|
2433
|
+
deploy.close
|
2434
|
+
end
|
2435
|
+
|
2436
|
+
if !@original_config.nil? and @original_config.is_a?(Hash)
|
2437
|
+
config = File.new("#{deploy_dir}/basket_of_kittens.json", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2438
|
+
config.puts JSON.pretty_generate(@original_config)
|
2439
|
+
config.close
|
2440
|
+
end
|
2441
|
+
|
2442
|
+
if !@ssh_private_key.nil?
|
2443
|
+
key = File.new("#{deploy_dir}/node_ssh.key", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2444
|
+
key.puts @ssh_private_key
|
2445
|
+
key.close
|
2446
|
+
end
|
2447
|
+
if !@ssh_public_key.nil?
|
2448
|
+
key = File.new("#{deploy_dir}/node_ssh.pub", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2449
|
+
key.puts @ssh_public_key
|
2450
|
+
key.close
|
2451
|
+
end
|
2452
|
+
if !@ssh_key_name.nil?
|
2453
|
+
key = File.new("#{deploy_dir}/ssh_key_name", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2454
|
+
key.puts @ssh_key_name
|
2455
|
+
key.close
|
2456
|
+
end
|
2457
|
+
if !@environment.nil?
|
2458
|
+
env = File.new("#{deploy_dir}/environment_name", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2459
|
+
env.puts @environment
|
2460
|
+
env.close
|
2461
|
+
end
|
2462
|
+
if !@deploy_secret.nil?
|
2463
|
+
secret = File.new("#{deploy_dir}/deploy_secret", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2464
|
+
secret.print @deploy_secret
|
2465
|
+
secret.close
|
2466
|
+
end
|
2467
|
+
if !@secrets.nil?
|
2468
|
+
secretdir = "#{deploy_dir}/secrets"
|
2469
|
+
if !Dir.exist?(secretdir)
|
2470
|
+
MU.log "Creating #{secretdir}", MU::DEBUG
|
2471
|
+
Dir.mkdir(secretdir, 0700)
|
2472
|
+
end
|
2473
|
+
@secrets.each_pair { |type, server|
|
2474
|
+
server.each_pair { |server, secret|
|
2475
|
+
key = File.new("#{secretdir}/#{type}.#{server}", File::CREAT|File::TRUNC|File::RDWR, 0600)
|
2476
|
+
key.puts secret
|
2477
|
+
key.close
|
2478
|
+
}
|
2479
|
+
}
|
2480
|
+
end
|
2481
|
+
}
|
2482
|
+
|
2483
|
+
# Update groomer copies of this metadata
|
2484
|
+
syncLitter(@deployment['servers'].keys, save_all_only: true) if @deployment.has_key?("servers")
|
2485
|
+
end
|
2486
|
+
|
2487
|
+
# Find one or more resources by their Mu resource name, and return
|
2488
|
+
# MommaCat objects for their containing deploys, their BoK config data,
|
2489
|
+
# and their deployment data.
|
2490
|
+
#
|
2491
|
+
# @param type [String]: The type of resource, e.g. "vpc" or "server."
|
2492
|
+
# @param name [String]: The Mu resource class, typically the name field of a Basket of Kittens resource declaration.
|
2493
|
+
# @param mu_name [String]: The fully-expanded Mu resource name, e.g. MGMT-PROD-2015040115-FR-ADMGMT2
|
2494
|
+
# @param deploy_id [String]: The deployment to search. Will search all deployments if not specified.
|
2495
|
+
# @return [Hash,Array<Hash>]
|
2496
|
+
def self.getResourceMetadata(type, name: nil, deploy_id: nil, use_cache: true, mu_name: nil)
|
2497
|
+
if type.nil?
|
2498
|
+
raise MuError, "Can't call getResourceMetadata without a type argument"
|
2499
|
+
end
|
2500
|
+
shortclass, cfg_name, cfg_plural, classname = MU::Cloud.getResourceNames(type)
|
2501
|
+
type = cfg_plural
|
2502
|
+
|
2503
|
+
deploy_root = File.expand_path(MU.dataDir+"/deployments")
|
2504
|
+
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
2505
|
+
if Dir.exists?(deploy_root)
|
2506
|
+
Dir.entries(deploy_root).each { |deploy|
|
2507
|
+
this_deploy_dir = deploy_root+"/"+deploy
|
2508
|
+
next if deploy == "." or deploy == ".." or !Dir.exists?(this_deploy_dir)
|
2509
|
+
next if deploy_id and deploy_id != deploy
|
2510
|
+
|
2511
|
+
if !File.size?(this_deploy_dir+"/deployment.json")
|
2512
|
+
MU.log "#{this_deploy_dir}/deployment.json doesn't exist, skipping when loading cache", MU::DEBUG
|
2513
|
+
next
|
2514
|
+
end
|
2515
|
+
if @deploy_cache[deploy].nil? or !use_cache
|
2516
|
+
@deploy_cache[deploy] = Hash.new
|
2517
|
+
elsif @deploy_cache[deploy]['mtime'] == File.mtime("#{this_deploy_dir}/deployment.json")
|
2518
|
+
MU.log "Using cached copy of deploy #{deploy} from #{@deploy_cache[deploy]['mtime']}", MU::DEBUG
|
2519
|
+
|
2520
|
+
next
|
2521
|
+
end
|
2522
|
+
|
2523
|
+
@deploy_cache[deploy] = Hash.new if !@deploy_cache.has_key?(deploy)
|
2524
|
+
MU.log "Caching deploy #{deploy}", MU::DEBUG
|
2525
|
+
lock = File.open("#{this_deploy_dir}/deployment.json", File::RDONLY)
|
2526
|
+
lock.flock(File::LOCK_EX)
|
2527
|
+
@deploy_cache[deploy]['mtime'] = File.mtime("#{this_deploy_dir}/deployment.json")
|
2528
|
+
|
2529
|
+
begin
|
2530
|
+
@deploy_cache[deploy]['data'] = JSON.parse(File.read("#{this_deploy_dir}/deployment.json"))
|
2531
|
+
lock.flock(File::LOCK_UN)
|
2532
|
+
|
2533
|
+
next if @deploy_cache[deploy].nil? or @deploy_cache[deploy]['data'].nil?
|
2534
|
+
# Populate some generable entries that should be in the deploy
|
2535
|
+
# data. Also, bounce out if we realize we've found exactly what
|
2536
|
+
# we needed already.
|
2537
|
+
MU::Cloud.resource_types.each_pair { |res_type, attrs|
|
2538
|
+
|
2539
|
+
next if @deploy_cache[deploy]['data'][attrs[:cfg_plural]].nil?
|
2540
|
+
if !attrs[:has_multiples]
|
2541
|
+
@deploy_cache[deploy]['data'][attrs[:cfg_plural]].each_pair { |nodename, data|
|
2542
|
+
# XXX we don't actually store node names for some resources, need to farm them
|
2543
|
+
# and fix metadata
|
2544
|
+
# if !mu_name.nil? and nodename == mu_name
|
2545
|
+
# return { deploy => [data] }
|
2546
|
+
# end
|
2547
|
+
}
|
2548
|
+
else
|
2549
|
+
@deploy_cache[deploy]['data'][attrs[:cfg_plural]].each_pair { |node_class, nodes|
|
2550
|
+
next if nodes.nil? or !nodes.is_a?(Hash)
|
2551
|
+
nodes.each_pair { |nodename, data|
|
2552
|
+
next if !data.is_a?(Hash)
|
2553
|
+
data['#MU_NODE_CLASS'] = node_class
|
2554
|
+
if !data.has_key?("cloud") # XXX kludge until old metadata gets fixed
|
2555
|
+
data["cloud"] = MU::Config.defaultCloud
|
2556
|
+
end
|
2557
|
+
data['#MU_NAME'] = nodename
|
2558
|
+
if !mu_name.nil? and nodename == mu_name
|
2559
|
+
return {deploy => [data]} if deploy_id && deploy == deploy_id
|
2560
|
+
end
|
2561
|
+
}
|
2562
|
+
}
|
2563
|
+
end
|
2564
|
+
}
|
2565
|
+
rescue JSON::ParserError => e
|
2566
|
+
raise MuError, "JSON parse failed on #{this_deploy_dir}/deployment.json\n\n"+File.read("#{this_deploy_dir}/deployment.json")
|
2567
|
+
end
|
2568
|
+
lock.flock(File::LOCK_UN)
|
2569
|
+
lock.close
|
2570
|
+
}
|
2571
|
+
end
|
2572
|
+
}
|
2573
|
+
|
2574
|
+
matches = {}
|
2575
|
+
|
2576
|
+
if deploy_id.nil?
|
2577
|
+
@deploy_cache.each_key { |deploy|
|
2578
|
+
next if !@deploy_cache[deploy].has_key?('data')
|
2579
|
+
next if !@deploy_cache[deploy]['data'].has_key?(type)
|
2580
|
+
if !name.nil?
|
2581
|
+
next if @deploy_cache[deploy]['data'][type][name].nil?
|
2582
|
+
matches[deploy] = [] if !matches.has_key?(deploy)
|
2583
|
+
matches[deploy] << @deploy_cache[deploy]['data'][type][name].dup
|
2584
|
+
else
|
2585
|
+
matches[deploy] = [] if !matches.has_key?(deploy)
|
2586
|
+
matches[deploy].concat(@deploy_cache[deploy]['data'][type].values)
|
2587
|
+
end
|
2588
|
+
}
|
2589
|
+
return matches
|
2590
|
+
elsif !@deploy_cache[deploy_id].nil?
|
2591
|
+
if !@deploy_cache[deploy_id]['data'].nil? and
|
2592
|
+
!@deploy_cache[deploy_id]['data'][type].nil?
|
2593
|
+
if !name.nil?
|
2594
|
+
if !@deploy_cache[deploy_id]['data'][type][name].nil?
|
2595
|
+
matches[deploy_id] = [] if !matches.has_key?(deploy_id)
|
2596
|
+
matches[deploy_id] << @deploy_cache[deploy_id]['data'][type][name].dup
|
2597
|
+
else
|
2598
|
+
return matches # nothing, actually
|
2599
|
+
end
|
2600
|
+
else
|
2601
|
+
matches[deploy_id] = @deploy_cache[deploy_id]['data'][type].values
|
2602
|
+
end
|
2603
|
+
end
|
2604
|
+
end
|
2605
|
+
|
2606
|
+
return matches
|
2607
|
+
end
|
2608
|
+
|
2609
|
+
###########################################################################
|
2610
|
+
###########################################################################
|
2611
|
+
def loadDeploy(deployment_json_only = false, set_context_to_me: true)
|
2612
|
+
MU::MommaCat.deploy_struct_semaphore.synchronize {
|
2613
|
+
if File.size?(deploy_dir+"/deployment.json")
|
2614
|
+
deploy = File.open("#{deploy_dir}/deployment.json", File::RDONLY)
|
2615
|
+
MU.log "Getting lock to read #{deploy_dir}/deployment.json", MU::DEBUG
|
2616
|
+
# deploy.flock(File::LOCK_EX)
|
2617
|
+
begin
|
2618
|
+
Timeout::timeout(90) {deploy.flock(File::LOCK_EX)}
|
2619
|
+
rescue Timeout::Error
|
2620
|
+
raise MuError, "Timed out trying to get an exclusive lock on #{deploy_dir}/deployment.json"
|
2621
|
+
end
|
2622
|
+
|
2623
|
+
begin
|
2624
|
+
@deployment = JSON.parse(File.read("#{deploy_dir}/deployment.json"))
|
2625
|
+
rescue JSON::ParserError => e
|
2626
|
+
MU.log "JSON parse failed on #{deploy_dir}/deployment.json", MU::ERR
|
2627
|
+
end
|
2628
|
+
|
2629
|
+
deploy.flock(File::LOCK_UN)
|
2630
|
+
deploy.close
|
2631
|
+
if set_context_to_me
|
2632
|
+
["appname", "environment", "timestamp", "seed", "handle"].each { |var|
|
2633
|
+
if @deployment[var]
|
2634
|
+
if var != "handle"
|
2635
|
+
MU.setVar(var, @deployment[var].upcase)
|
2636
|
+
else
|
2637
|
+
MU.setVar(var, @deployment[var])
|
2638
|
+
end
|
2639
|
+
else
|
2640
|
+
MU.log "Missing global variable #{var} for #{MU.deploy_id}", MU::ERR
|
2641
|
+
end
|
2642
|
+
}
|
2643
|
+
end
|
2644
|
+
@timestamp = @deployment['timestamp']
|
2645
|
+
@seed = @deployment['seed']
|
2646
|
+
@appname = @deployment['appname']
|
2647
|
+
@handle = @deployment['handle']
|
2648
|
+
|
2649
|
+
return if deployment_json_only
|
2650
|
+
end
|
2651
|
+
if File.exist?(deploy_dir+"/private_key")
|
2652
|
+
@private_key = File.read("#{deploy_dir}/private_key")
|
2653
|
+
@public_key = File.read("#{deploy_dir}/public_key")
|
2654
|
+
end
|
2655
|
+
if File.exist?(deploy_dir+"/basket_of_kittens.json")
|
2656
|
+
begin
|
2657
|
+
@original_config = JSON.parse(File.read("#{deploy_dir}/basket_of_kittens.json"))
|
2658
|
+
rescue JSON::ParserError => e
|
2659
|
+
MU.log "JSON parse failed on #{deploy_dir}/basket_of_kittens.json", MU::ERR
|
2660
|
+
end
|
2661
|
+
end
|
2662
|
+
if File.exist?(deploy_dir+"/ssh_key_name")
|
2663
|
+
@ssh_key_name = File.read("#{deploy_dir}/ssh_key_name").chomp!
|
2664
|
+
end
|
2665
|
+
if File.exist?(deploy_dir+"/node_ssh.key")
|
2666
|
+
@ssh_private_key = File.read("#{deploy_dir}/node_ssh.key")
|
2667
|
+
end
|
2668
|
+
if File.exist?(deploy_dir+"/node_ssh.pub")
|
2669
|
+
@ssh_public_key = File.read("#{deploy_dir}/node_ssh.pub")
|
2670
|
+
end
|
2671
|
+
if File.exist?(deploy_dir+"/environment_name")
|
2672
|
+
@environment = File.read("#{deploy_dir}/environment_name").chomp!
|
2673
|
+
end
|
2674
|
+
if File.exist?(deploy_dir+"/deploy_secret")
|
2675
|
+
@deploy_secret = File.read("#{deploy_dir}/deploy_secret")
|
2676
|
+
end
|
2677
|
+
if Dir.exist?("#{deploy_dir}/secrets")
|
2678
|
+
@secrets.each_key { |type|
|
2679
|
+
Dir.glob("#{deploy_dir}/secrets/#{type}.*") { |filename|
|
2680
|
+
base, server = File.basename(filename).split(/\./)
|
2681
|
+
|
2682
|
+
@secrets[type][server] = File.read(filename).chomp!
|
2683
|
+
}
|
2684
|
+
}
|
2685
|
+
end
|
2686
|
+
}
|
2687
|
+
end
|
2688
|
+
|
2689
|
+
@catadjs = %w{fuzzy ginger lilac chocolate xanthic wiggly itty}
|
2690
|
+
@catnouns = %w{bastet biscuits bobcat catnip cheetah chonk dot felix 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 mew quat eek ziggy}
|
2691
|
+
@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}
|
2692
|
+
@catwords = @catadjs + @catnouns + @catmixed
|
2693
|
+
|
2694
|
+
@jaegeradjs = %w{azure fearless lucky olive vivid electric grey yarely violet ivory jade cinnamon crimson tacit umber mammoth ultra iron zodiac}
|
2695
|
+
@jaegernouns = %w{horizon hulk ultimatum yardarm watchman whilrwind wright rhythm ocean enigma eruption typhoon jaeger brawler blaze vandal excalibur paladin juliet kaleidoscope romeo}
|
2696
|
+
@jaegermixed = %w{alpha ajax amber avenger brave bravo charlie chocolate chrome corinthian dancer danger dash delta duet echo edge elite eureka foxtrot guardian gold hyperion illusion imperative india intercept kilo lancer night nova november oscar omega pacer quickstrike rogue ronin striker tango titan valor victor vulcan warder xenomorph xenon xray xylem yankee yell yukon zeal zero zoner zodiac}
|
2697
|
+
@jaegerwords = @jaegeradjs + @jaegernouns + @jaegermixed
|
2698
|
+
|
2699
|
+
@words = @catwords + @jaegerwords
|
2700
|
+
|
2701
|
+
end #class
|
2702
|
+
end #module
|
2703
|
+
|