cloud-mu 1.9.0.pre.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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,2294 @@
|
|
|
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
|
+
require 'net/ssh'
|
|
16
|
+
require 'net/ssh/multi'
|
|
17
|
+
require 'net/ssh/proxy/command'
|
|
18
|
+
autoload :OpenStruct, "ostruct"
|
|
19
|
+
autoload :Timeout, "timeout"
|
|
20
|
+
autoload :ERB, "erb"
|
|
21
|
+
autoload :Base64, "base64"
|
|
22
|
+
require 'open-uri'
|
|
23
|
+
|
|
24
|
+
module MU
|
|
25
|
+
class Cloud
|
|
26
|
+
class AWS
|
|
27
|
+
|
|
28
|
+
# A server as configured in {MU::Config::BasketofKittens::servers}
|
|
29
|
+
class Server < MU::Cloud::Server
|
|
30
|
+
|
|
31
|
+
# A list of block device names to use if we get a storage block that
|
|
32
|
+
# doesn't declare one explicitly.
|
|
33
|
+
# This probably fails on some AMIs. It's crude.
|
|
34
|
+
@disk_devices = [
|
|
35
|
+
"/dev/sdf",
|
|
36
|
+
"/dev/sdg",
|
|
37
|
+
"/dev/sdh",
|
|
38
|
+
"/dev/sdi",
|
|
39
|
+
"/dev/sdj",
|
|
40
|
+
"/dev/sdk",
|
|
41
|
+
"/dev/sdl",
|
|
42
|
+
"/dev/sdm",
|
|
43
|
+
"/dev/sdn"
|
|
44
|
+
]
|
|
45
|
+
# List of standard disk device names to present to instances.
|
|
46
|
+
# @return [Array<String>]
|
|
47
|
+
def self.disk_devices
|
|
48
|
+
@disk_devices
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# See that we get our ephemeral storage devices with AMIs that don't do it
|
|
52
|
+
# for us
|
|
53
|
+
@ephemeral_mappings = [
|
|
54
|
+
{
|
|
55
|
+
:device_name => "/dev/sdr",
|
|
56
|
+
:virtual_name => "ephemeral0"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
:device_name => "/dev/sds",
|
|
60
|
+
:virtual_name => "ephemeral1"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
:device_name => "/dev/sdt",
|
|
64
|
+
:virtual_name => "ephemeral2"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
:device_name => "/dev/sdu",
|
|
68
|
+
:virtual_name => "ephemeral3"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
# Ephemeral storage device mappings. Useful for AMIs that don't do this
|
|
72
|
+
# for us.
|
|
73
|
+
# @return [Hash]
|
|
74
|
+
def self.ephemeral_mappings
|
|
75
|
+
@ephemeral_mappings
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
attr_reader :mu_name
|
|
79
|
+
attr_reader :config
|
|
80
|
+
attr_reader :deploy
|
|
81
|
+
attr_reader :cloud_id
|
|
82
|
+
attr_reader :cloud_desc
|
|
83
|
+
attr_reader :groomer
|
|
84
|
+
attr_accessor :mu_windows_name
|
|
85
|
+
|
|
86
|
+
# @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
|
|
87
|
+
# @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::servers}
|
|
88
|
+
def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
|
|
89
|
+
@deploy = mommacat
|
|
90
|
+
@config = MU::Config.manxify(kitten_cfg)
|
|
91
|
+
@cloud_id = cloud_id
|
|
92
|
+
|
|
93
|
+
if @deploy
|
|
94
|
+
@userdata = MU::Cloud.fetchUserdata(
|
|
95
|
+
platform: @config["platform"],
|
|
96
|
+
cloud: "aws",
|
|
97
|
+
template_variables: {
|
|
98
|
+
"deployKey" => Base64.urlsafe_encode64(@deploy.public_key),
|
|
99
|
+
"deploySSHKey" => @deploy.ssh_public_key,
|
|
100
|
+
"muID" => MU.deploy_id,
|
|
101
|
+
"muUser" => MU.mu_user,
|
|
102
|
+
"publicIP" => MU.mu_public_ip,
|
|
103
|
+
"skipApplyUpdates" => @config['skipinitialupdates'],
|
|
104
|
+
"windowsAdminName" => @config['windows_admin_username'],
|
|
105
|
+
"resourceName" => @config["name"],
|
|
106
|
+
"resourceType" => "server",
|
|
107
|
+
"platform" => @config["platform"]
|
|
108
|
+
},
|
|
109
|
+
custom_append: @config['userdata_script']
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
@disk_devices = MU::Cloud::AWS::Server.disk_devices
|
|
114
|
+
@ephemeral_mappings = MU::Cloud::AWS::Server.ephemeral_mappings
|
|
115
|
+
|
|
116
|
+
if !mu_name.nil?
|
|
117
|
+
@mu_name = mu_name
|
|
118
|
+
@config['mu_name'] = @mu_name
|
|
119
|
+
# describe
|
|
120
|
+
@mu_windows_name = @deploydata['mu_windows_name'] if @mu_windows_name.nil? and @deploydata
|
|
121
|
+
else
|
|
122
|
+
if kitten_cfg.has_key?("basis")
|
|
123
|
+
@mu_name = @deploy.getResourceName(@config['name'], need_unique_string: true)
|
|
124
|
+
else
|
|
125
|
+
@mu_name = @deploy.getResourceName(@config['name'])
|
|
126
|
+
end
|
|
127
|
+
@config['mu_name'] = @mu_name
|
|
128
|
+
|
|
129
|
+
@config['instance_secret'] = Password.random(50)
|
|
130
|
+
end
|
|
131
|
+
@groomer = MU::Groomer.new(self)
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
@@userdata_semaphore = Mutex.new
|
|
136
|
+
|
|
137
|
+
# Fetch our baseline userdata argument (read: "script that runs on first
|
|
138
|
+
# boot") for a given platform.
|
|
139
|
+
# *XXX* both the eval() and the blind File.read() based on the platform
|
|
140
|
+
# variable are dangerous without cleaning. Clean them.
|
|
141
|
+
# @param platform [String]: The target OS.
|
|
142
|
+
# @param template_variables [Hash]: A list of variable substitutions to pass as globals to the ERB parser when loading the userdata script.
|
|
143
|
+
# @param custom_append [String]: Arbitrary extra code to append to our default userdata behavior.
|
|
144
|
+
# @return [String]
|
|
145
|
+
def self.fetchUserdata(platform: "linux", template_variables: {}, custom_append: nil, scrub_mu_isms: false)
|
|
146
|
+
return nil if platform.nil? or platform.empty?
|
|
147
|
+
@@userdata_semaphore.synchronize {
|
|
148
|
+
script = ""
|
|
149
|
+
if !scrub_mu_isms
|
|
150
|
+
if template_variables.nil? or !template_variables.is_a?(Hash)
|
|
151
|
+
raise MuError, "My second argument should be a hash of variables to pass into ERB templates"
|
|
152
|
+
end
|
|
153
|
+
$mu = OpenStruct.new(template_variables)
|
|
154
|
+
userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/aws/userdata")
|
|
155
|
+
platform = "linux" if %w{centos centos6 centos7 ubuntu ubuntu14 rhel rhel7 rhel71 amazon}.include? platform
|
|
156
|
+
platform = "windows" if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16}.include? platform
|
|
157
|
+
erbfile = "#{userdata_dir}/#{platform}.erb"
|
|
158
|
+
if !File.exist?(erbfile)
|
|
159
|
+
MU.log "No such userdata template '#{erbfile}'", MU::WARN, details: caller
|
|
160
|
+
return ""
|
|
161
|
+
end
|
|
162
|
+
userdata = File.read(erbfile)
|
|
163
|
+
begin
|
|
164
|
+
erb = ERB.new(userdata, nil, "<>")
|
|
165
|
+
script = erb.result
|
|
166
|
+
rescue NameError => e
|
|
167
|
+
raise MuError, "Error parsing userdata script #{erbfile} as an ERB template: #{e.inspect}"
|
|
168
|
+
end
|
|
169
|
+
MU.log "Parsed #{erbfile} as ERB", MU::DEBUG, details: script
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if !custom_append.nil?
|
|
173
|
+
if custom_append['path'].nil?
|
|
174
|
+
raise MuError, "Got a custom userdata script argument, but no ['path'] component"
|
|
175
|
+
end
|
|
176
|
+
erbfile = File.read(custom_append['path'])
|
|
177
|
+
MU.log "Loaded userdata script from #{custom_append['path']}"
|
|
178
|
+
if custom_append['use_erb']
|
|
179
|
+
begin
|
|
180
|
+
erb = ERB.new(erbfile, 1, "<>")
|
|
181
|
+
if custom_append['skip_std']
|
|
182
|
+
script = +erb.result
|
|
183
|
+
else
|
|
184
|
+
script = script+"\n"+erb.result
|
|
185
|
+
end
|
|
186
|
+
rescue NameError => e
|
|
187
|
+
raise MuError, "Error parsing userdata script #{erbfile} as an ERB template: #{e.inspect}"
|
|
188
|
+
end
|
|
189
|
+
MU.log "Parsed #{custom_append['path']} as ERB", MU::DEBUG, details: script
|
|
190
|
+
else
|
|
191
|
+
if custom_append['skip_std']
|
|
192
|
+
script = erbfile
|
|
193
|
+
else
|
|
194
|
+
script = script+"\n"+erbfile
|
|
195
|
+
end
|
|
196
|
+
MU.log "Parsed #{custom_append['path']} as flat file", MU::DEBUG, details: script
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
return script
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Find volumes attached to a given instance id and tag them. If no arguments
|
|
204
|
+
# besides the instance id are provided, it will add our special MU-ID
|
|
205
|
+
# tag. Can also be used to do things like set the resource's name, if you
|
|
206
|
+
# leverage the other arguments.
|
|
207
|
+
# @param instance_id [String]: The cloud provider's identifier for the parent instance of this volume.
|
|
208
|
+
# @param device [String]: The OS-level device name of the volume.
|
|
209
|
+
# @param tag_name [String]: The name of the tag to attach.
|
|
210
|
+
# @param tag_value [String]: The value of the tag to attach.
|
|
211
|
+
# @param region [String]: The cloud provider region
|
|
212
|
+
# @return [void]
|
|
213
|
+
def self.tagVolumes(instance_id, device: nil, tag_name: "MU-ID", tag_value: MU.deploy_id, region: MU.curRegion)
|
|
214
|
+
MU::Cloud::AWS.ec2(region).describe_volumes(filters: [name: "attachment.instance-id", values: [instance_id]]).each { |vol|
|
|
215
|
+
vol.volumes.each { |volume|
|
|
216
|
+
volume.attachments.each { |attachment|
|
|
217
|
+
vol_parent = attachment.instance_id
|
|
218
|
+
vol_id = attachment.volume_id
|
|
219
|
+
vol_dev = attachment.device
|
|
220
|
+
if vol_parent == instance_id and (vol_dev == device or device.nil?)
|
|
221
|
+
MU::MommaCat.createTag(vol_id, tag_name, tag_value, region: region)
|
|
222
|
+
break
|
|
223
|
+
end
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Called automatically by {MU::Deploy#createResources}
|
|
230
|
+
def create
|
|
231
|
+
begin
|
|
232
|
+
done = false
|
|
233
|
+
instance = createEc2Instance
|
|
234
|
+
|
|
235
|
+
@cloud_id = instance.instance_id
|
|
236
|
+
@deploy.saveNodeSecret(@cloud_id, @config['instance_secret'], "instance_secret")
|
|
237
|
+
@config.delete("instance_secret")
|
|
238
|
+
|
|
239
|
+
if !@config['async_groom']
|
|
240
|
+
sleep 5
|
|
241
|
+
MU::MommaCat.lock(instance.instance_id+"-create")
|
|
242
|
+
if !postBoot
|
|
243
|
+
MU.log "#{@config['name']} is already being groomed, skipping", MU::NOTICE
|
|
244
|
+
else
|
|
245
|
+
MU.log "Node creation complete for #{@config['name']}"
|
|
246
|
+
end
|
|
247
|
+
MU::MommaCat.unlock(instance.instance_id+"-create")
|
|
248
|
+
else
|
|
249
|
+
MU::MommaCat.createStandardTags(instance.instance_id, region: @config['region'])
|
|
250
|
+
MU::MommaCat.createTag(instance.instance_id, "Name", @mu_name, region: @config['region'])
|
|
251
|
+
end
|
|
252
|
+
done = true
|
|
253
|
+
rescue Exception => e
|
|
254
|
+
if !instance.nil? and !done
|
|
255
|
+
MU.log "Aborted before I could finish setting up #{@config['name']}, cleaning it up. Stack trace will print once cleanup is complete.", MU::WARN if !@deploy.nocleanup
|
|
256
|
+
MU::MommaCat.unlockAll
|
|
257
|
+
if !@deploy.nocleanup
|
|
258
|
+
parent_thread_id = Thread.current.object_id
|
|
259
|
+
Thread.new {
|
|
260
|
+
MU.dupGlobals(parent_thread_id)
|
|
261
|
+
MU::Cloud::AWS::Server.cleanup(noop: false, ignoremaster: false, skipsnapshots: true)
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
raise e
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
return @config
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Create an Amazon EC2 instance.
|
|
274
|
+
def createEc2Instance
|
|
275
|
+
name = @config["name"]
|
|
276
|
+
node = @config['mu_name']
|
|
277
|
+
|
|
278
|
+
instance_descriptor = {
|
|
279
|
+
:image_id => @config["ami_id"],
|
|
280
|
+
:key_name => @deploy.ssh_key_name,
|
|
281
|
+
:instance_type => @config["size"],
|
|
282
|
+
:disable_api_termination => true,
|
|
283
|
+
:min_count => 1,
|
|
284
|
+
:max_count => 1
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
arn = nil
|
|
288
|
+
if @config['generate_iam_role']
|
|
289
|
+
# @config['iam_role'], @cfm_role_name, @cfm_prof_name, arn = MU::Cloud::AWS::Server.createIAMProfile(@mu_name, base_profile: @config['iam_role'], extra_policies: @config['iam_policies'])
|
|
290
|
+
role = @deploy.findLitterMate(name: @config['name'], type: "roles")
|
|
291
|
+
s3_objs = ["#{@deploy.deploy_id}-secret", "#{role.mu_name}.pfx", "#{role.mu_name}.crt", "#{role.mu_name}.key", "#{role.mu_name}-winrm.crt", "#{role.mu_name}-winrm.key"].map { |file|
|
|
292
|
+
'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU.adminBucketName+'/'+file
|
|
293
|
+
}
|
|
294
|
+
role.cloudobj.injectPolicyTargets("MuSecrets", s3_objs)
|
|
295
|
+
|
|
296
|
+
@config['iam_role'] = role.mu_name
|
|
297
|
+
arn = role.cloudobj.createInstanceProfile
|
|
298
|
+
# @cfm_role_name, @cfm_prof_name
|
|
299
|
+
|
|
300
|
+
elsif @config['iam_role'].nil?
|
|
301
|
+
raise MuError, "#{@mu_name} has generate_iam_role set to false, but no iam_role assigned."
|
|
302
|
+
end
|
|
303
|
+
if !@config["iam_role"].nil?
|
|
304
|
+
if arn
|
|
305
|
+
instance_descriptor[:iam_instance_profile] = {arn: arn}
|
|
306
|
+
else
|
|
307
|
+
instance_descriptor[:iam_instance_profile] = {name: @config["iam_role"]}
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
security_groups = []
|
|
312
|
+
if @dependencies.has_key?("firewall_rule")
|
|
313
|
+
@dependencies['firewall_rule'].values.each { |sg|
|
|
314
|
+
security_groups << sg.cloud_id
|
|
315
|
+
}
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
if security_groups.size > 0
|
|
319
|
+
instance_descriptor[:security_group_ids] = security_groups
|
|
320
|
+
else
|
|
321
|
+
raise MuError, "Didn't get any security groups assigned to be in #{@mu_name}, that shouldn't happen"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
if !@config['private_ip'].nil?
|
|
325
|
+
instance_descriptor[:private_ip_address] = @config['private_ip']
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
vpc_id = subnet = nil
|
|
329
|
+
if !@vpc.nil? and @config.has_key?("vpc")
|
|
330
|
+
subnet_conf = @config['vpc']
|
|
331
|
+
subnet_conf = @config['vpc']['subnets'].first if @config['vpc'].has_key?("subnets") and !@config['vpc']['subnets'].empty?
|
|
332
|
+
tag_key, tag_value = subnet_conf['tag'].split(/=/, 2) if !subnet_conf['tag'].nil?
|
|
333
|
+
|
|
334
|
+
subnet = @vpc.getSubnet(
|
|
335
|
+
cloud_id: subnet_conf['subnet_id'],
|
|
336
|
+
name: subnet_conf['subnet_name'],
|
|
337
|
+
tag_key: tag_key,
|
|
338
|
+
tag_value: tag_value
|
|
339
|
+
)
|
|
340
|
+
if subnet.nil?
|
|
341
|
+
raise MuError, "Got null subnet id out of #{subnet_conf['vpc']}"
|
|
342
|
+
end
|
|
343
|
+
MU.log "Deploying #{node} into VPC #{@vpc.cloud_id} Subnet #{subnet.cloud_id}"
|
|
344
|
+
punchAdminNAT
|
|
345
|
+
instance_descriptor[:subnet_id] = subnet.cloud_id
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
if !@userdata.nil? and !@userdata.empty?
|
|
349
|
+
instance_descriptor[:user_data] = Base64.encode64(@userdata)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
MU::Cloud::AWS::Server.waitForAMI(@config["ami_id"], region: @config['region'])
|
|
353
|
+
|
|
354
|
+
# Figure out which devices are embedded in the AMI already.
|
|
355
|
+
image = MU::Cloud::AWS.ec2(@config['region']).describe_images(image_ids: [@config["ami_id"]]).images.first
|
|
356
|
+
ext_disks = {}
|
|
357
|
+
if !image.block_device_mappings.nil?
|
|
358
|
+
image.block_device_mappings.each { |disk|
|
|
359
|
+
if !disk.device_name.nil? and !disk.device_name.empty? and !disk.ebs.nil? and !disk.ebs.empty?
|
|
360
|
+
ext_disks[disk.device_name] = MU.structToHash(disk.ebs)
|
|
361
|
+
end
|
|
362
|
+
}
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
configured_storage = Array.new
|
|
366
|
+
cfm_volume_map = {}
|
|
367
|
+
if @config["storage"]
|
|
368
|
+
@config["storage"].each { |vol|
|
|
369
|
+
# Drop the "encrypted" flag if a snapshot for this device exists
|
|
370
|
+
# in the AMI, even if they both agree about the value of said
|
|
371
|
+
# flag. Apparently that's a thing now.
|
|
372
|
+
if ext_disks.has_key?(vol["device"])
|
|
373
|
+
if ext_disks[vol["device"]].has_key?(:snapshot_id)
|
|
374
|
+
vol.delete("encrypted")
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
mapping, cfm_mapping = MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)
|
|
378
|
+
configured_storage << mapping
|
|
379
|
+
}
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
instance_descriptor[:block_device_mappings] = configured_storage
|
|
383
|
+
instance_descriptor[:block_device_mappings].concat(@ephemeral_mappings)
|
|
384
|
+
instance_descriptor[:monitoring] = {enabled: @config['monitoring']}
|
|
385
|
+
|
|
386
|
+
MU.log "Creating EC2 instance #{node}"
|
|
387
|
+
MU.log "Instance details for #{node}: #{instance_descriptor}", MU::DEBUG
|
|
388
|
+
# if instance_descriptor[:block_device_mappings].empty?
|
|
389
|
+
# instance_descriptor.delete(:block_device_mappings)
|
|
390
|
+
# end
|
|
391
|
+
|
|
392
|
+
retries = 0
|
|
393
|
+
begin
|
|
394
|
+
response = MU::Cloud::AWS.ec2(@config['region']).run_instances(instance_descriptor)
|
|
395
|
+
rescue Aws::EC2::Errors::InvalidGroupNotFound, Aws::EC2::Errors::InvalidSubnetIDNotFound, Aws::EC2::Errors::InvalidParameterValue => e
|
|
396
|
+
if retries < 10
|
|
397
|
+
if retries > 7
|
|
398
|
+
MU.log "Seeing #{e.inspect} while trying to launch #{node}, retrying a few more times...", MU::WARN, details: instance_descriptor
|
|
399
|
+
end
|
|
400
|
+
sleep 10
|
|
401
|
+
retries = retries + 1
|
|
402
|
+
retry
|
|
403
|
+
else
|
|
404
|
+
raise MuError, e.inspect
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
instance = response.instances.first
|
|
409
|
+
MU.log "#{node} (#{instance.instance_id}) coming online"
|
|
410
|
+
|
|
411
|
+
return instance
|
|
412
|
+
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Ask the Amazon API to restart this node
|
|
416
|
+
def reboot(hard = false)
|
|
417
|
+
return if @cloud_id.nil?
|
|
418
|
+
|
|
419
|
+
if hard
|
|
420
|
+
groupname = nil
|
|
421
|
+
if !@config['basis'].nil?
|
|
422
|
+
resp = MU::Cloud::AWS.autoscale(@config['region']).describe_auto_scaling_instances(
|
|
423
|
+
instance_ids: [@cloud_id]
|
|
424
|
+
)
|
|
425
|
+
groupname = resp.auto_scaling_instances.first.auto_scaling_group_name
|
|
426
|
+
MU.log "Pausing Autoscale processes in #{groupname}", MU::NOTICE
|
|
427
|
+
MU::Cloud::AWS.autoscale(@config['region']).suspend_processes(
|
|
428
|
+
auto_scaling_group_name: groupname
|
|
429
|
+
)
|
|
430
|
+
end
|
|
431
|
+
begin
|
|
432
|
+
MU.log "Stopping #{@mu_name} (#{@cloud_id})", MU::NOTICE
|
|
433
|
+
MU::Cloud::AWS.ec2(@config['region']).stop_instances(
|
|
434
|
+
instance_ids: [@cloud_id]
|
|
435
|
+
)
|
|
436
|
+
MU::Cloud::AWS.ec2(@config['region']).wait_until(:instance_stopped, instance_ids: [@cloud_id]) do |waiter|
|
|
437
|
+
waiter.before_attempt do |attempts|
|
|
438
|
+
MU.log "Waiting for #{@mu_name} to stop for hard reboot"
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
MU.log "Starting #{@mu_name} (#{@cloud_id})"
|
|
442
|
+
MU::Cloud::AWS.ec2(@config['region']).start_instances(
|
|
443
|
+
instance_ids: [@cloud_id]
|
|
444
|
+
)
|
|
445
|
+
ensure
|
|
446
|
+
if !groupname.nil?
|
|
447
|
+
MU.log "Resuming Autoscale processes in #{groupname}", MU::NOTICE
|
|
448
|
+
MU::Cloud::AWS.autoscale(@config['region']).resume_processes(
|
|
449
|
+
auto_scaling_group_name: groupname
|
|
450
|
+
)
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
else
|
|
454
|
+
MU.log "Rebooting #{@mu_name} (#{@cloud_id})"
|
|
455
|
+
MU::Cloud::AWS.ec2(@config['region']).reboot_instances(
|
|
456
|
+
instance_ids: [@cloud_id]
|
|
457
|
+
)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Figure out what's needed to SSH into this server.
|
|
462
|
+
# @return [Array<String>]: nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name, alternate_names
|
|
463
|
+
def getSSHConfig
|
|
464
|
+
node, config, deploydata = describe(cloud_id: @cloud_id)
|
|
465
|
+
# XXX add some awesome alternate names from metadata and make sure they end
|
|
466
|
+
# up in MU::MommaCat's ssh config wangling
|
|
467
|
+
ssh_keydir = Etc.getpwuid(Process.uid).dir+"/.ssh"
|
|
468
|
+
return nil if @config.nil? or @deploy.nil?
|
|
469
|
+
|
|
470
|
+
nat_ssh_key = nat_ssh_user = nat_ssh_host = nil
|
|
471
|
+
if !@config["vpc"].nil? and !MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'])
|
|
472
|
+
if !@nat.nil?
|
|
473
|
+
if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
|
|
474
|
+
raise MuError, "Configured to use NAT Gateway, but I have no route to instance. Either use Bastion, or configure VPC peering"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
if @nat.cloud_desc.nil?
|
|
478
|
+
MU.log "NAT was missing cloud descriptor when called in #{@mu_name}'s getSSHConfig", MU::ERR
|
|
479
|
+
return nil
|
|
480
|
+
end
|
|
481
|
+
# XXX Yanking these things from the cloud descriptor will only work in AWS!
|
|
482
|
+
|
|
483
|
+
nat_ssh_key = @nat.cloud_desc.key_name
|
|
484
|
+
nat_ssh_key = @config["vpc"]["nat_ssh_key"] if !@config["vpc"]["nat_ssh_key"].nil?
|
|
485
|
+
nat_ssh_host = @nat.cloud_desc.public_ip_address
|
|
486
|
+
nat_ssh_user = @config["vpc"]["nat_ssh_user"]
|
|
487
|
+
if nat_ssh_user.nil? and !nat_ssh_host.nil?
|
|
488
|
+
MU.log "#{@config["name"]} (#{MU.deploy_id}) is configured to use #{@config['vpc']} NAT #{nat_ssh_host}, but username isn't specified. Guessing root.", MU::ERR, details: caller
|
|
489
|
+
nat_ssh_user = "root"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
if @config['ssh_user'].nil?
|
|
495
|
+
if windows?
|
|
496
|
+
@config['ssh_user'] = "Administrator"
|
|
497
|
+
else
|
|
498
|
+
@config['ssh_user'] = "root"
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
return [nat_ssh_key, nat_ssh_user, nat_ssh_host, canonicalIP, @config['ssh_user'], @deploy.ssh_key_name]
|
|
503
|
+
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Apply tags, bootstrap our configuration management, and other
|
|
507
|
+
# administravia for a new instance.
|
|
508
|
+
def postBoot(instance_id = nil)
|
|
509
|
+
if !instance_id.nil?
|
|
510
|
+
@cloud_id = instance_id
|
|
511
|
+
end
|
|
512
|
+
node, config, deploydata = describe(cloud_id: @cloud_id)
|
|
513
|
+
instance = cloud_desc
|
|
514
|
+
raise MuError, "Couldn't find instance #{@mu_name} (#{@cloud_id})" if !instance
|
|
515
|
+
@cloud_id = instance.instance_id
|
|
516
|
+
return false if !MU::MommaCat.lock(instance.instance_id+"-orchestrate", true)
|
|
517
|
+
return false if !MU::MommaCat.lock(instance.instance_id+"-groom", true)
|
|
518
|
+
|
|
519
|
+
MU::MommaCat.createStandardTags(instance.instance_id, region: @config['region'])
|
|
520
|
+
MU::MommaCat.createTag(instance.instance_id, "Name", node, region: @config['region'])
|
|
521
|
+
|
|
522
|
+
if @config['optional_tags']
|
|
523
|
+
MU::MommaCat.listOptionalTags.each { |key, value|
|
|
524
|
+
MU::MommaCat.createTag(instance.instance_id, key, value, region: @config['region'])
|
|
525
|
+
}
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
if !@config['tags'].nil?
|
|
529
|
+
@config['tags'].each { |tag|
|
|
530
|
+
MU::MommaCat.createTag(instance.instance_id, tag['key'], tag['value'], region: @config['region'])
|
|
531
|
+
}
|
|
532
|
+
end
|
|
533
|
+
MU.log "Tagged #{node} (#{instance.instance_id}) with MU-ID=#{MU.deploy_id}", MU::DEBUG
|
|
534
|
+
|
|
535
|
+
# Make double sure we don't lose a cached mu_windows_name value.
|
|
536
|
+
if windows? or !@config['active_directory'].nil?
|
|
537
|
+
if @mu_windows_name.nil?
|
|
538
|
+
@mu_windows_name = deploydata['mu_windows_name']
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
retries = -1
|
|
543
|
+
max_retries = 30
|
|
544
|
+
begin
|
|
545
|
+
if instance.nil? or instance.state.name != "running"
|
|
546
|
+
retries = retries + 1
|
|
547
|
+
if !instance.nil? and instance.state.name == "terminated"
|
|
548
|
+
raise MuError, "#{@cloud_id} appears to have been terminated mid-bootstrap!"
|
|
549
|
+
end
|
|
550
|
+
if retries % 3 == 0
|
|
551
|
+
MU.log "Waiting for EC2 instance #{node} (#{@cloud_id}) to be ready...", MU::NOTICE
|
|
552
|
+
end
|
|
553
|
+
sleep 40
|
|
554
|
+
# Get a fresh AWS descriptor
|
|
555
|
+
instance = MU::Cloud::Server.find(cloud_id: @cloud_id, region: @config['region']).values.first
|
|
556
|
+
if instance and instance.state.name == "terminated"
|
|
557
|
+
raise MuError, "EC2 instance #{node} (#{@cloud_id}) terminating during bootstrap!"
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
rescue Aws::EC2::Errors::ServiceError => e
|
|
561
|
+
if retries < max_retries
|
|
562
|
+
MU.log "Got #{e.inspect} during initial instance creation of #{@cloud_id}, retrying...", MU::NOTICE, details: instance
|
|
563
|
+
retries = retries + 1
|
|
564
|
+
retry
|
|
565
|
+
else
|
|
566
|
+
raise MuError, "Too many retries creating #{node} (#{e.inspect})"
|
|
567
|
+
end
|
|
568
|
+
end while instance.nil? or (instance.state.name != "running" and retries < max_retries)
|
|
569
|
+
|
|
570
|
+
punchAdminNAT
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# If we came up via AutoScale, the Alarm module won't have had our
|
|
574
|
+
# instance ID to associate us with itself. So invoke that here.
|
|
575
|
+
if !@config['basis'].nil? and @config["alarms"] and !@config["alarms"].empty?
|
|
576
|
+
@config["alarms"].each { |alarm|
|
|
577
|
+
alarm_obj = MU::MommaCat.findStray(
|
|
578
|
+
"AWS",
|
|
579
|
+
"alarms",
|
|
580
|
+
region: @config["region"],
|
|
581
|
+
deploy_id: @deploy.deploy_id,
|
|
582
|
+
name: alarm['name']
|
|
583
|
+
).first
|
|
584
|
+
alarm["dimensions"] = [{:name => "InstanceId", :value => @cloud_id}]
|
|
585
|
+
|
|
586
|
+
if alarm["enable_notifications"]
|
|
587
|
+
topic_arn = MU::Cloud::AWS::Notification.createTopic(alarm["notification_group"], region: @config["region"])
|
|
588
|
+
MU::Cloud::AWS::Notification.subscribe(arn: topic_arn, protocol: alarm["notification_type"], endpoint: alarm["notification_endpoint"], region: @config["region"])
|
|
589
|
+
alarm["alarm_actions"] = [topic_arn]
|
|
590
|
+
alarm["ok_actions"] = [topic_arn]
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
alarm_name = alarm_obj ? alarm_obj.cloud_id : "#{node}-#{alarm['name']}".upcase
|
|
594
|
+
|
|
595
|
+
MU::Cloud::AWS::Alarm.setAlarm(
|
|
596
|
+
name: alarm_name,
|
|
597
|
+
ok_actions: alarm["ok_actions"],
|
|
598
|
+
alarm_actions: alarm["alarm_actions"],
|
|
599
|
+
insufficient_data_actions: alarm["no_data_actions"],
|
|
600
|
+
metric_name: alarm["metric_name"],
|
|
601
|
+
namespace: alarm["namespace"],
|
|
602
|
+
statistic: alarm["statistic"],
|
|
603
|
+
dimensions: alarm["dimensions"],
|
|
604
|
+
period: alarm["period"],
|
|
605
|
+
unit: alarm["unit"],
|
|
606
|
+
evaluation_periods: alarm["evaluation_periods"],
|
|
607
|
+
threshold: alarm["threshold"],
|
|
608
|
+
comparison_operator: alarm["comparison_operator"],
|
|
609
|
+
region: @config["region"]
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# We have issues sometimes where our dns_records are pointing at the wrong node name and IP address.
|
|
615
|
+
# Make sure that doesn't happen. Happens with server pools only
|
|
616
|
+
if @config['dns_records'] && !@config['dns_records'].empty?
|
|
617
|
+
@config['dns_records'].each { |dnsrec|
|
|
618
|
+
if dnsrec.has_key?("name")
|
|
619
|
+
if dnsrec['name'].start_with?(MU.deploy_id.downcase) && !dnsrec['name'].start_with?(node.downcase)
|
|
620
|
+
MU.log "DNS records for #{node} seem to be wrong, deleting from current config", MU::WARN, details: dnsrec
|
|
621
|
+
dnsrec.delete('name')
|
|
622
|
+
dnsrec.delete('target')
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
}
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# Unless we're planning on associating a different IP later, set up a
|
|
629
|
+
# DNS entry for this thing and let it sync in the background. We'll come
|
|
630
|
+
# back to it later.
|
|
631
|
+
if @config['static_ip'].nil? && !@named
|
|
632
|
+
MU::MommaCat.nameKitten(self)
|
|
633
|
+
@named = true
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
if !@config['src_dst_check'] and !@config["vpc"].nil?
|
|
637
|
+
MU.log "Disabling source_dest_check #{node} (making it NAT-worthy)"
|
|
638
|
+
MU::Cloud::AWS.ec2(@config['region']).modify_instance_attribute(
|
|
639
|
+
instance_id: @cloud_id,
|
|
640
|
+
source_dest_check: {:value => false}
|
|
641
|
+
)
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
# Set console termination protection. Autoscale nodes won't set this
|
|
645
|
+
# by default.
|
|
646
|
+
MU::Cloud::AWS.ec2(@config['region']).modify_instance_attribute(
|
|
647
|
+
instance_id: @cloud_id,
|
|
648
|
+
disable_api_termination: {:value => true}
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
has_elastic_ip = false
|
|
652
|
+
if !instance.public_ip_address.nil?
|
|
653
|
+
begin
|
|
654
|
+
resp = MU::Cloud::AWS.ec2((@config['region'])).describe_addresses(public_ips: [instance.public_ip_address])
|
|
655
|
+
if resp.addresses.size > 0 and resp.addresses.first.instance_id == @cloud_id
|
|
656
|
+
has_elastic_ip = true
|
|
657
|
+
end
|
|
658
|
+
rescue Aws::EC2::Errors::InvalidAddressNotFound => e
|
|
659
|
+
# XXX this is ok to ignore, it means the public IP isn't Elastic
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
win_admin_password = nil
|
|
664
|
+
ec2config_password = nil
|
|
665
|
+
sshd_password = nil
|
|
666
|
+
if windows?
|
|
667
|
+
ssh_keydir = "#{Etc.getpwuid(Process.uid).dir}/.ssh"
|
|
668
|
+
ssh_key_name = @deploy.ssh_key_name
|
|
669
|
+
|
|
670
|
+
if @config['use_cloud_provider_windows_password']
|
|
671
|
+
win_admin_password = getWindowsAdminPassword
|
|
672
|
+
elsif @config['windows_auth_vault'] && !@config['windows_auth_vault'].empty?
|
|
673
|
+
if @config["windows_auth_vault"].has_key?("password_field")
|
|
674
|
+
win_admin_password = @groomer.getSecret(
|
|
675
|
+
vault: @config['windows_auth_vault']['vault'],
|
|
676
|
+
item: @config['windows_auth_vault']['item'],
|
|
677
|
+
field: @config["windows_auth_vault"]["password_field"]
|
|
678
|
+
)
|
|
679
|
+
else
|
|
680
|
+
win_admin_password = getWindowsAdminPassword
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
if @config["windows_auth_vault"].has_key?("ec2config_password_field")
|
|
684
|
+
ec2config_password = @groomer.getSecret(
|
|
685
|
+
vault: @config['windows_auth_vault']['vault'],
|
|
686
|
+
item: @config['windows_auth_vault']['item'],
|
|
687
|
+
field: @config["windows_auth_vault"]["ec2config_password_field"]
|
|
688
|
+
)
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
if @config["windows_auth_vault"].has_key?("sshd_password_field")
|
|
692
|
+
sshd_password = @groomer.getSecret(
|
|
693
|
+
vault: @config['windows_auth_vault']['vault'],
|
|
694
|
+
item: @config['windows_auth_vault']['item'],
|
|
695
|
+
field: @config["windows_auth_vault"]["sshd_password_field"]
|
|
696
|
+
)
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
win_admin_password = MU.generateWindowsPassword if win_admin_password.nil?
|
|
701
|
+
ec2config_password = MU.generateWindowsPassword if ec2config_password.nil?
|
|
702
|
+
sshd_password = MU.generateWindowsPassword if sshd_password.nil?
|
|
703
|
+
|
|
704
|
+
# We're creating the vault here so when we run
|
|
705
|
+
# MU::Cloud::Server.initialSSHTasks and we need to set the Windows
|
|
706
|
+
# Admin password we can grab it from said vault.
|
|
707
|
+
creds = {
|
|
708
|
+
"username" => @config['windows_admin_username'],
|
|
709
|
+
"password" => win_admin_password,
|
|
710
|
+
"ec2config_username" => "ec2config",
|
|
711
|
+
"ec2config_password" => ec2config_password,
|
|
712
|
+
"sshd_username" => "sshd_service",
|
|
713
|
+
"sshd_password" => sshd_password
|
|
714
|
+
}
|
|
715
|
+
@groomer.saveSecret(vault: @mu_name, item: "windows_credentials", data: creds, permissions: "name:#{@mu_name}")
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
subnet = nil
|
|
719
|
+
if !@vpc.nil? and @config.has_key?("vpc") and !instance.subnet_id.nil?
|
|
720
|
+
subnet = @vpc.getSubnet(
|
|
721
|
+
cloud_id: instance.subnet_id
|
|
722
|
+
)
|
|
723
|
+
if subnet.nil?
|
|
724
|
+
raise MuError, "Got null subnet id out of #{@config['vpc']} when asking for #{instance.subnet_id}"
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
if !subnet.nil?
|
|
729
|
+
if !subnet.private? or (!@config['static_ip'].nil? and !@config['static_ip']['assign_ip'].nil?)
|
|
730
|
+
if !@config['static_ip'].nil?
|
|
731
|
+
if !@config['static_ip']['ip'].nil?
|
|
732
|
+
public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: false, ip: @config['static_ip']['ip'])
|
|
733
|
+
elsif !has_elastic_ip
|
|
734
|
+
public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id)
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name = getSSHConfig
|
|
740
|
+
if subnet.private? and !nat_ssh_host and !MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'])
|
|
741
|
+
raise MuError, "#{node} is in a private subnet (#{subnet}), but has no NAT host configured, and I have no other route to it"
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# If we've asked for additional subnets (and this @config is not a
|
|
745
|
+
# member of a Server Pool, which has different semantics), create
|
|
746
|
+
# extra interfaces to accomodate.
|
|
747
|
+
if !@config['vpc']['subnets'].nil? and @config['basis'].nil?
|
|
748
|
+
device_index = 1
|
|
749
|
+
@vpc.subnets { |subnet|
|
|
750
|
+
subnet_id = subnet.cloud_id
|
|
751
|
+
MU.log "Adding network interface on subnet #{subnet_id} for #{node}"
|
|
752
|
+
iface = MU::Cloud::AWS.ec2(@config['region']).create_network_interface(subnet_id: subnet_id).network_interface
|
|
753
|
+
MU::MommaCat.createStandardTags(iface.network_interface_id, region: @config['region'])
|
|
754
|
+
MU::MommaCat.createTag(iface.network_interface_id, "Name", node+"-ETH"+device_index.to_s, region: @config['region'])
|
|
755
|
+
|
|
756
|
+
if @config['optional_tags']
|
|
757
|
+
MU::MommaCat.listOptionalTags.each { |key, value|
|
|
758
|
+
MU::MommaCat.createTag(iface.network_interface_id, key, value, region: @config['region'])
|
|
759
|
+
}
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
if !@config['tags'].nil?
|
|
763
|
+
@config['tags'].each { |tag|
|
|
764
|
+
MU::MommaCat.createTag(iface.network_interface_id, tag['key'], tag['value'], region: @config['region'])
|
|
765
|
+
}
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
MU::Cloud::AWS.ec2(@config['region']).attach_network_interface(
|
|
769
|
+
network_interface_id: iface.network_interface_id,
|
|
770
|
+
instance_id: instance.instance_id,
|
|
771
|
+
device_index: device_index
|
|
772
|
+
)
|
|
773
|
+
device_index = device_index + 1
|
|
774
|
+
}
|
|
775
|
+
end
|
|
776
|
+
elsif !@config['static_ip'].nil?
|
|
777
|
+
if !@config['static_ip']['ip'].nil?
|
|
778
|
+
public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: true, ip: @config['static_ip']['ip'])
|
|
779
|
+
elsif !has_elastic_ip
|
|
780
|
+
public_ip = MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: true)
|
|
781
|
+
end
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
if !@config['image_then_destroy']
|
|
786
|
+
notify
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
MU.log "EC2 instance #{node} has id #{instance.instance_id}", MU::DEBUG
|
|
790
|
+
|
|
791
|
+
@config["private_dns_name"] = instance.private_dns_name
|
|
792
|
+
@config["public_dns_name"] = instance.public_dns_name
|
|
793
|
+
@config["private_ip_address"] = instance.private_ip_address
|
|
794
|
+
@config["public_ip_address"] = instance.public_ip_address
|
|
795
|
+
|
|
796
|
+
ext_mappings = MU.structToHash(instance.block_device_mappings)
|
|
797
|
+
|
|
798
|
+
# Root disk on standard CentOS AMI
|
|
799
|
+
# tagVolumes(instance.instance_id, "/dev/sda", "Name", "ROOT-"+MU.deploy_id+"-"+@config["name"].upcase)
|
|
800
|
+
# Root disk on standard Ubuntu AMI
|
|
801
|
+
# tagVolumes(instance.instance_id, "/dev/sda1", "Name", "ROOT-"+MU.deploy_id+"-"+@config["name"].upcase)
|
|
802
|
+
|
|
803
|
+
# Generic deploy ID tag
|
|
804
|
+
# tagVolumes(instance.instance_id)
|
|
805
|
+
|
|
806
|
+
# Tag volumes with all our standard tags.
|
|
807
|
+
# Maybe replace tagVolumes with this? There is one more place tagVolumes is called from
|
|
808
|
+
volumes = MU::Cloud::AWS.ec2(@config['region']).describe_volumes(filters: [name: "attachment.instance-id", values: [instance.instance_id]])
|
|
809
|
+
volumes.each { |vol|
|
|
810
|
+
vol.volumes.each { |volume|
|
|
811
|
+
volume.attachments.each { |attachment|
|
|
812
|
+
MU::MommaCat.listStandardTags.each_pair { |key, value|
|
|
813
|
+
MU::MommaCat.createTag(attachment.volume_id, key, value, region: @config['region'])
|
|
814
|
+
|
|
815
|
+
if attachment.device == "/dev/sda" or attachment.device == "/dev/sda1"
|
|
816
|
+
MU::MommaCat.createTag(attachment.volume_id, "Name", "ROOT-#{MU.deploy_id}-#{@config["name"].upcase}", region: @config['region'])
|
|
817
|
+
else
|
|
818
|
+
MU::MommaCat.createTag(attachment.volume_id, "Name", "#{MU.deploy_id}-#{@config["name"].upcase}-#{attachment.device.upcase}", region: @config['region'])
|
|
819
|
+
end
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if @config['optional_tags']
|
|
823
|
+
MU::MommaCat.listOptionalTags.each { |key, value|
|
|
824
|
+
MU::MommaCat.createTag(attachment.volume_id, key, value, region: @config['region'])
|
|
825
|
+
}
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
if @config['tags']
|
|
829
|
+
@config['tags'].each { |tag|
|
|
830
|
+
MU::MommaCat.createTag(attachment.volume_id, tag['key'], tag['value'], region: @config['region'])
|
|
831
|
+
}
|
|
832
|
+
end
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
canonical_name = instance.public_dns_name
|
|
838
|
+
canonical_name = instance.private_dns_name if !canonical_name or nat_ssh_host != nil
|
|
839
|
+
@config['canonical_name'] = canonical_name
|
|
840
|
+
|
|
841
|
+
if !@config['add_private_ips'].nil?
|
|
842
|
+
instance.network_interfaces.each { |int|
|
|
843
|
+
if int.private_ip_address == instance.private_ip_address and int.private_ip_addresses.size < (@config['add_private_ips'] + 1)
|
|
844
|
+
MU.log "Adding #{@config['add_private_ips']} extra private IP addresses to #{instance.instance_id}"
|
|
845
|
+
MU::Cloud::AWS.ec2(@config['region']).assign_private_ip_addresses(
|
|
846
|
+
network_interface_id: int.network_interface_id,
|
|
847
|
+
secondary_private_ip_address_count: @config['add_private_ips'],
|
|
848
|
+
allow_reassignment: false
|
|
849
|
+
)
|
|
850
|
+
end
|
|
851
|
+
}
|
|
852
|
+
notify
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
begin
|
|
856
|
+
if windows?
|
|
857
|
+
# kick off certificate generation early; WinRM will need it
|
|
858
|
+
cert, key = @deploy.nodeSSLCerts(self)
|
|
859
|
+
if @config.has_key?("basis")
|
|
860
|
+
@deploy.nodeSSLCerts(self, true)
|
|
861
|
+
end
|
|
862
|
+
if !@groomer.haveBootstrapped?
|
|
863
|
+
session = getWinRMSession(50, 60, reboot_on_problems: true)
|
|
864
|
+
initialWinRMTasks(session)
|
|
865
|
+
begin
|
|
866
|
+
session.close
|
|
867
|
+
rescue Exception
|
|
868
|
+
# this is allowed to fail- we're probably rebooting anyway
|
|
869
|
+
end
|
|
870
|
+
else # for an existing Windows node: WinRM, then SSH if it fails
|
|
871
|
+
begin
|
|
872
|
+
session = getWinRMSession(1, 60)
|
|
873
|
+
rescue Exception # yeah, yeah
|
|
874
|
+
session = getSSHSession(1, 60)
|
|
875
|
+
# XXX maybe loop at least once if this also fails?
|
|
876
|
+
end
|
|
877
|
+
end
|
|
878
|
+
else
|
|
879
|
+
session = getSSHSession(40, 30)
|
|
880
|
+
initialSSHTasks(session)
|
|
881
|
+
end
|
|
882
|
+
rescue BootstrapTempFail
|
|
883
|
+
sleep 45
|
|
884
|
+
retry
|
|
885
|
+
ensure
|
|
886
|
+
session.close if !session.nil? and !windows?
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
if @config["existing_deploys"] && !@config["existing_deploys"].empty?
|
|
890
|
+
@config["existing_deploys"].each { |ext_deploy|
|
|
891
|
+
if ext_deploy["cloud_id"]
|
|
892
|
+
found = MU::MommaCat.findStray(
|
|
893
|
+
@config['cloud'],
|
|
894
|
+
ext_deploy["cloud_type"],
|
|
895
|
+
cloud_id: ext_deploy["cloud_id"],
|
|
896
|
+
region: @config['region'],
|
|
897
|
+
dummy_ok: false
|
|
898
|
+
).first
|
|
899
|
+
|
|
900
|
+
MU.log "Couldn't find existing resource #{ext_deploy["cloud_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
|
|
901
|
+
@deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: found.mu_name, triggering_node: @mu_name)
|
|
902
|
+
elsif ext_deploy["mu_name"] && ext_deploy["deploy_id"]
|
|
903
|
+
MU.log "#{ext_deploy["mu_name"]} / #{ext_deploy["deploy_id"]}"
|
|
904
|
+
found = MU::MommaCat.findStray(
|
|
905
|
+
@config['cloud'],
|
|
906
|
+
ext_deploy["cloud_type"],
|
|
907
|
+
deploy_id: ext_deploy["deploy_id"],
|
|
908
|
+
mu_name: ext_deploy["mu_name"],
|
|
909
|
+
region: @config['region'],
|
|
910
|
+
dummy_ok: false
|
|
911
|
+
).first
|
|
912
|
+
|
|
913
|
+
MU.log "Couldn't find existing resource #{ext_deploy["mu_name"]}/#{ext_deploy["deploy_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
|
|
914
|
+
@deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: ext_deploy["mu_name"], triggering_node: @mu_name)
|
|
915
|
+
else
|
|
916
|
+
MU.log "Trying to find existing deploy, but either the cloud_id is not valid or no mu_name and deploy_id where provided", MU::ERR
|
|
917
|
+
end
|
|
918
|
+
}
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# See if this node already exists in our config management. If it does,
|
|
922
|
+
# we're done.
|
|
923
|
+
if @groomer.haveBootstrapped?
|
|
924
|
+
MU.log "Node #{node} has already been bootstrapped, skipping groomer setup.", MU::NOTICE
|
|
925
|
+
@groomer.saveDeployData
|
|
926
|
+
MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
|
|
927
|
+
MU::MommaCat.unlock(instance.instance_id+"-groom")
|
|
928
|
+
return true
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
begin
|
|
932
|
+
@groomer.bootstrap
|
|
933
|
+
rescue MU::Groomer::RunError
|
|
934
|
+
MU::MommaCat.unlock(instance.instance_id+"-groom")
|
|
935
|
+
MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
|
|
936
|
+
return false
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# Make sure we got our name written everywhere applicable
|
|
940
|
+
if !@named
|
|
941
|
+
MU::MommaCat.nameKitten(self)
|
|
942
|
+
@named = true
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
MU::MommaCat.unlock(instance.instance_id+"-groom")
|
|
946
|
+
MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
|
|
947
|
+
return true
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
# postBoot
|
|
951
|
+
|
|
952
|
+
# Locate an existing instance or instances and return an array containing matching AWS resource descriptors for those that match.
|
|
953
|
+
# @param cloud_id [String]: The cloud provider's identifier for this resource.
|
|
954
|
+
# @param region [String]: The cloud provider region
|
|
955
|
+
# @param tag_key [String]: A tag key to search.
|
|
956
|
+
# @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
|
|
957
|
+
# @param ip [String]: An IP address associated with the instance
|
|
958
|
+
# @param flags [Hash]: Optional flags
|
|
959
|
+
# @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching instances
|
|
960
|
+
def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, ip: nil, flags: {})
|
|
961
|
+
# XXX put that 'ip' value into opts
|
|
962
|
+
instance = nil
|
|
963
|
+
if !region.nil?
|
|
964
|
+
regions = [region]
|
|
965
|
+
else
|
|
966
|
+
regions = MU::Cloud::AWS.listRegions
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
found_instances = {}
|
|
970
|
+
search_semaphore = Mutex.new
|
|
971
|
+
search_threads = []
|
|
972
|
+
|
|
973
|
+
# If we got an instance id, go get it
|
|
974
|
+
if !cloud_id.nil? and !cloud_id.empty?
|
|
975
|
+
regions.each { |region|
|
|
976
|
+
search_threads << Thread.new {
|
|
977
|
+
MU.log "Hunting for instance with cloud id '#{cloud_id}' in #{region}", MU::DEBUG
|
|
978
|
+
retries = 0
|
|
979
|
+
begin
|
|
980
|
+
MU::Cloud::AWS.ec2(region).describe_instances(
|
|
981
|
+
instance_ids: [cloud_id],
|
|
982
|
+
filters: [
|
|
983
|
+
{name: "instance-state-name", values: ["running", "pending"]}
|
|
984
|
+
]
|
|
985
|
+
).reservations.each { |resp|
|
|
986
|
+
if !resp.nil? and !resp.instances.nil?
|
|
987
|
+
resp.instances.each { |instance|
|
|
988
|
+
search_semaphore.synchronize {
|
|
989
|
+
found_instances[instance.instance_id] = instance
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
end
|
|
993
|
+
}
|
|
994
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
|
|
995
|
+
if retries < 5
|
|
996
|
+
retries = retries + 1
|
|
997
|
+
sleep 5
|
|
998
|
+
else
|
|
999
|
+
raise MuError, "#{e.inspect} in region #{region}"
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
done_threads = []
|
|
1005
|
+
begin
|
|
1006
|
+
search_threads.each { |t|
|
|
1007
|
+
joined = t.join(2)
|
|
1008
|
+
done_threads << joined if !joined.nil?
|
|
1009
|
+
}
|
|
1010
|
+
end while found_instances.size < 1 and done_threads.size != search_threads.size
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
if found_instances.size > 0
|
|
1014
|
+
return found_instances
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
# Ok, well, let's try looking it up by IP then
|
|
1018
|
+
if instance.nil? and !ip.nil?
|
|
1019
|
+
MU.log "Hunting for instance by IP '#{ip}'", MU::DEBUG
|
|
1020
|
+
["ip-address", "private-ip-address"].each { |filter|
|
|
1021
|
+
response = MU::Cloud::AWS.ec2(region).describe_instances(
|
|
1022
|
+
filters: [
|
|
1023
|
+
{name: filter, values: [ip]},
|
|
1024
|
+
{name: "instance-state-name", values: ["running", "pending"]}
|
|
1025
|
+
]
|
|
1026
|
+
).reservations.first
|
|
1027
|
+
instance = response.instances.first if !response.nil?
|
|
1028
|
+
}
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
if !instance.nil?
|
|
1032
|
+
return {instance.instance_id => instance} if !instance.nil?
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
# Fine, let's try it by tag.
|
|
1036
|
+
if !tag_value.nil?
|
|
1037
|
+
MU.log "Searching for instance by tag '#{tag_key}=#{tag_value}'", MU::DEBUG
|
|
1038
|
+
MU::Cloud::AWS.ec2(region).describe_instances(
|
|
1039
|
+
filters: [
|
|
1040
|
+
{name: "tag:#{tag_key}", values: [tag_value]},
|
|
1041
|
+
{name: "instance-state-name", values: ["running", "pending"]}
|
|
1042
|
+
]
|
|
1043
|
+
).reservations.each { |resp|
|
|
1044
|
+
if !resp.nil? and resp.instances.size > 0
|
|
1045
|
+
resp.instances.each { |instance|
|
|
1046
|
+
found_instances[instance.instance_id] = instance
|
|
1047
|
+
}
|
|
1048
|
+
end
|
|
1049
|
+
}
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
return found_instances
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
# Return a description of this resource appropriate for deployment
|
|
1056
|
+
# metadata. Arguments reflect the return values of the MU::Cloud::[Resource].describe method
|
|
1057
|
+
def notify
|
|
1058
|
+
node, config, deploydata = describe(cloud_id: @cloud_id, update_cache: true)
|
|
1059
|
+
deploydata = {} if deploydata.nil?
|
|
1060
|
+
|
|
1061
|
+
if cloud_desc.nil?
|
|
1062
|
+
raise MuError, "Failed to load instance metadata for #{@mu_name}/#{@cloud_id}"
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
interfaces = []
|
|
1066
|
+
private_ips = []
|
|
1067
|
+
|
|
1068
|
+
cloud_desc.network_interfaces.each { |iface|
|
|
1069
|
+
iface.private_ip_addresses.each { |priv_ip|
|
|
1070
|
+
private_ips << priv_ip.private_ip_address
|
|
1071
|
+
}
|
|
1072
|
+
interfaces << {
|
|
1073
|
+
"network_interface_id" => iface.network_interface_id,
|
|
1074
|
+
"subnet_id" => iface.subnet_id,
|
|
1075
|
+
"vpc_id" => iface.vpc_id
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
deploydata = {
|
|
1080
|
+
"nodename" => @mu_name,
|
|
1081
|
+
"run_list" => @config['run_list'],
|
|
1082
|
+
"image_created" => @config['image_created'],
|
|
1083
|
+
"iam_role" => @config['iam_role'],
|
|
1084
|
+
"cloud_desc_id" => @cloud_id,
|
|
1085
|
+
"private_dns_name" => cloud_desc.private_dns_name,
|
|
1086
|
+
"public_dns_name" => cloud_desc.public_dns_name,
|
|
1087
|
+
"private_ip_address" => cloud_desc.private_ip_address,
|
|
1088
|
+
"public_ip_address" => cloud_desc.public_ip_address,
|
|
1089
|
+
"private_ip_list" => private_ips,
|
|
1090
|
+
"key_name" => cloud_desc.key_name,
|
|
1091
|
+
"subnet_id" => cloud_desc.subnet_id,
|
|
1092
|
+
"cloud_desc_type" => cloud_desc.instance_type #,
|
|
1093
|
+
# "network_interfaces" => interfaces,
|
|
1094
|
+
# "config" => server
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if !@mu_windows_name.nil?
|
|
1098
|
+
deploydata["mu_windows_name"] = @mu_windows_name
|
|
1099
|
+
end
|
|
1100
|
+
if !@config['chef_data'].nil?
|
|
1101
|
+
deploydata.merge!(@config['chef_data'])
|
|
1102
|
+
end
|
|
1103
|
+
deploydata["region"] = @config['region'] if !@config['region'].nil?
|
|
1104
|
+
if !@named
|
|
1105
|
+
MU::MommaCat.nameKitten(self)
|
|
1106
|
+
@named = true
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
return deploydata
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
# If the specified server is in a VPC, and has a NAT, make sure we'll
|
|
1113
|
+
# be letting ssh traffic in from said NAT.
|
|
1114
|
+
def punchAdminNAT
|
|
1115
|
+
if @config['vpc'].nil? or
|
|
1116
|
+
(
|
|
1117
|
+
!@config['vpc'].has_key?("nat_host_id") and
|
|
1118
|
+
!@config['vpc'].has_key?("nat_host_tag") and
|
|
1119
|
+
!@config['vpc'].has_key?("nat_host_ip") and
|
|
1120
|
+
!@config['vpc'].has_key?("nat_host_name")
|
|
1121
|
+
)
|
|
1122
|
+
return nil
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
return nil if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
|
|
1126
|
+
|
|
1127
|
+
dependencies if @nat.nil?
|
|
1128
|
+
if @nat.nil? or @nat.cloud_desc.nil?
|
|
1129
|
+
raise MuError, "#{@mu_name} (#{MU.deploy_id}) is configured to use #{@config['vpc']} but I can't find the cloud descriptor for a matching NAT instance"
|
|
1130
|
+
end
|
|
1131
|
+
MU.log "Adding administrative holes for NAT host #{@nat.cloud_desc.private_ip_address} to #{@mu_name}"
|
|
1132
|
+
if !@deploy.kittens['firewall_rules'].nil?
|
|
1133
|
+
@deploy.kittens['firewall_rules'].each_pair { |name, acl|
|
|
1134
|
+
if acl.config["admin"]
|
|
1135
|
+
acl.addRule([@nat.cloud_desc.private_ip_address], proto: "tcp")
|
|
1136
|
+
acl.addRule([@nat.cloud_desc.private_ip_address], proto: "udp")
|
|
1137
|
+
acl.addRule([@nat.cloud_desc.private_ip_address], proto: "icmp")
|
|
1138
|
+
end
|
|
1139
|
+
}
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
# Called automatically by {MU::Deploy#createResources}
|
|
1144
|
+
def groom
|
|
1145
|
+
MU::MommaCat.lock(@cloud_id+"-groom")
|
|
1146
|
+
node, config, deploydata = describe(cloud_id: @cloud_id)
|
|
1147
|
+
|
|
1148
|
+
if node.nil? or node.empty?
|
|
1149
|
+
raise MuError, "MU::Cloud::AWS::Server.groom was called without a mu_name"
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
# Make double sure we don't lose a cached mu_windows_name value.
|
|
1153
|
+
if windows? or !@config['active_directory'].nil?
|
|
1154
|
+
if @mu_windows_name.nil?
|
|
1155
|
+
@mu_windows_name = deploydata['mu_windows_name']
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
punchAdminNAT
|
|
1160
|
+
|
|
1161
|
+
MU::Cloud::AWS::Server.tagVolumes(@cloud_id)
|
|
1162
|
+
|
|
1163
|
+
# If we have a loadbalancer configured, attach us to it
|
|
1164
|
+
if !@config['loadbalancers'].nil?
|
|
1165
|
+
if @loadbalancers.nil?
|
|
1166
|
+
raise MuError, "#{@mu_name} is configured to use LoadBalancers, but none have been loaded by dependencies()"
|
|
1167
|
+
end
|
|
1168
|
+
@loadbalancers.each { |lb|
|
|
1169
|
+
lb.registerNode(@cloud_id)
|
|
1170
|
+
}
|
|
1171
|
+
end
|
|
1172
|
+
MU.log %Q{Server #{@config['name']} private IP is #{@deploydata["private_ip_address"]}#{@deploydata["public_ip_address"] ? ", public IP is "+@deploydata["public_ip_address"] : ""}}, MU::SUMMARY
|
|
1173
|
+
|
|
1174
|
+
# Let us into any databases we depend on.
|
|
1175
|
+
# This is probelmtic with autscaling - old ips are not removed, and access to the database can easily be given at the BoK level
|
|
1176
|
+
# if @dependencies.has_key?("database")
|
|
1177
|
+
# @dependencies['database'].values.each { |db|
|
|
1178
|
+
# db.allowHost(@deploydata["private_ip_address"]+"/32")
|
|
1179
|
+
# if @deploydata["public_ip_address"]
|
|
1180
|
+
# db.allowHost(@deploydata["public_ip_address"]+"/32")
|
|
1181
|
+
# end
|
|
1182
|
+
# }
|
|
1183
|
+
# end
|
|
1184
|
+
|
|
1185
|
+
@groomer.saveDeployData
|
|
1186
|
+
|
|
1187
|
+
begin
|
|
1188
|
+
@groomer.run(purpose: "Full Initial Run", max_retries: 15, reboot_first_fail: windows?)
|
|
1189
|
+
rescue MU::Groomer::RunError => e
|
|
1190
|
+
MU.log "Proceeding after failed initial Groomer run, but #{node} may not behave as expected!", MU::WARN, details: e.message
|
|
1191
|
+
rescue Exception => e
|
|
1192
|
+
MU.log "Caught #{e.inspect} on #{node} in an unexpected place (after @groomer.run on Full Initial Run)", MU::ERR
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
if !@config['create_image'].nil? and !@config['image_created']
|
|
1196
|
+
img_cfg = @config['create_image']
|
|
1197
|
+
# Scrub things that don't belong on an AMI
|
|
1198
|
+
session = getSSHSession
|
|
1199
|
+
sudo = purgecmd = ""
|
|
1200
|
+
sudo = "sudo" if @config['ssh_user'] != "root"
|
|
1201
|
+
if windows?
|
|
1202
|
+
purgecmd = "rm -rf /cygdrive/c/mu_installed_chef"
|
|
1203
|
+
else
|
|
1204
|
+
purgecmd = "rm -rf /opt/mu_installed_chef"
|
|
1205
|
+
end
|
|
1206
|
+
if img_cfg['image_then_destroy']
|
|
1207
|
+
if windows?
|
|
1208
|
+
purgecmd = "rm -rf /cygdrive/c/chef/ /home/#{@config['windows_admin_username']}/.ssh/authorized_keys /home/Administrator/.ssh/authorized_keys /cygdrive/c/mu-installer-ran-updates /cygdrive/c/mu_installed_chef"
|
|
1209
|
+
# session.exec!("powershell -Command \"& {(Get-WmiObject -Class Win32_Product -Filter \"Name='UniversalForwarder'\").Uninstall()}\"")
|
|
1210
|
+
else
|
|
1211
|
+
purgecmd = "#{sudo} rm -rf /root/.ssh/authorized_keys /etc/ssh/ssh_host_*key* /etc/chef /etc/opscode/* /.mu-installer-ran-updates /var/chef /opt/mu_installed_chef /opt/chef ; #{sudo} sed -i 's/^HOSTNAME=.*//' /etc/sysconfig/network"
|
|
1212
|
+
end
|
|
1213
|
+
end
|
|
1214
|
+
session.exec!(purgecmd)
|
|
1215
|
+
session.close
|
|
1216
|
+
ami_id = MU::Cloud::AWS::Server.createImage(
|
|
1217
|
+
name: @mu_name,
|
|
1218
|
+
instance_id: @cloud_id,
|
|
1219
|
+
storage: @config['storage'],
|
|
1220
|
+
exclude_storage: img_cfg['image_exclude_storage'],
|
|
1221
|
+
copy_to_regions: img_cfg['copy_to_regions'],
|
|
1222
|
+
make_public: img_cfg['public'],
|
|
1223
|
+
region: @config['region'],
|
|
1224
|
+
tags: @config['tags'])
|
|
1225
|
+
@deploy.notify("images", @config['name'], {"image_id" => ami_id})
|
|
1226
|
+
@config['image_created'] = true
|
|
1227
|
+
if img_cfg['image_then_destroy']
|
|
1228
|
+
MU::Cloud::AWS::Server.waitForAMI(ami_id, region: @config['region'])
|
|
1229
|
+
MU.log "AMI #{ami_id} ready, removing source node #{node}"
|
|
1230
|
+
MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @config['region'], deploy_id: @deploy.deploy_id, mu_name: @mu_name)
|
|
1231
|
+
destroy
|
|
1232
|
+
end
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
MU::MommaCat.unlock(@cloud_id+"-groom")
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
# Canonical Amazon Resource Number for this resource
|
|
1239
|
+
# @return [String]
|
|
1240
|
+
def arn
|
|
1241
|
+
"arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":ec2:"+@config['region']+":"+MU.account_number+":instance/"+@cloud_id
|
|
1242
|
+
end
|
|
1243
|
+
|
|
1244
|
+
def cloud_desc
|
|
1245
|
+
max_retries = 5
|
|
1246
|
+
retries = 0
|
|
1247
|
+
if !@cloud_id.nil?
|
|
1248
|
+
begin
|
|
1249
|
+
return MU::Cloud::AWS.ec2(@config['region']).describe_instances(instance_ids: [@cloud_id]).reservations.first.instances.first
|
|
1250
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
|
|
1251
|
+
return nil
|
|
1252
|
+
rescue NoMethodError => e
|
|
1253
|
+
if retries >= max_retries
|
|
1254
|
+
raise MuError, "Couldn't get a cloud descriptor for #{@mu_name} (#{@cloud_id})"
|
|
1255
|
+
else
|
|
1256
|
+
retries = retries + 1
|
|
1257
|
+
sleep 10
|
|
1258
|
+
retry
|
|
1259
|
+
end
|
|
1260
|
+
end
|
|
1261
|
+
end
|
|
1262
|
+
nil
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
# Return the IP address that we, the Mu server, should be using to access
|
|
1266
|
+
# this host via the network. Note that this does not factor in SSH
|
|
1267
|
+
# bastion hosts that may be in the path, see getSSHConfig if that's what
|
|
1268
|
+
# you need.
|
|
1269
|
+
def canonicalIP
|
|
1270
|
+
mu_name, config, deploydata = describe(cloud_id: @cloud_id)
|
|
1271
|
+
|
|
1272
|
+
instance = cloud_desc
|
|
1273
|
+
|
|
1274
|
+
if !instance
|
|
1275
|
+
raise MuError, "Couldn't retrieve cloud descriptor for server #{self}"
|
|
1276
|
+
end
|
|
1277
|
+
|
|
1278
|
+
if deploydata.nil? or
|
|
1279
|
+
(!deploydata.has_key?("private_ip_address") and
|
|
1280
|
+
!deploydata.has_key?("public_ip_address"))
|
|
1281
|
+
return nil if instance.nil?
|
|
1282
|
+
@deploydata = {} if @deploydata.nil?
|
|
1283
|
+
@deploydata["public_ip_address"] = instance.public_ip_address
|
|
1284
|
+
@deploydata["public_dns_name"] = instance.public_dns_name
|
|
1285
|
+
@deploydata["private_ip_address"] = instance.private_ip_address
|
|
1286
|
+
@deploydata["private_dns_name"] = instance.private_dns_name
|
|
1287
|
+
|
|
1288
|
+
notify
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
# Our deploydata gets corrupted often with server pools, this will cause us to use the wrong IP to identify a node
|
|
1292
|
+
# which will cause us to create certificates, DNS records and other artifacts with incorrect information which will cause our deploy to fail.
|
|
1293
|
+
# The cloud_id is always correct so lets use 'cloud_desc' to get the correct IPs
|
|
1294
|
+
if MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region']) or @deploydata["public_ip_address"].nil?
|
|
1295
|
+
@config['canonical_ip'] = instance.private_ip_address
|
|
1296
|
+
@deploydata["private_ip_address"] = instance.private_ip_address
|
|
1297
|
+
return instance.private_ip_address
|
|
1298
|
+
else
|
|
1299
|
+
@config['canonical_ip'] = instance.public_ip_address
|
|
1300
|
+
@deploydata["public_ip_address"] = instance.public_ip_address
|
|
1301
|
+
return instance.public_ip_address
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
# Create an AMI out of a running server. Requires either the name of a MU resource in the current deployment, or the cloud provider id of a running instance.
|
|
1306
|
+
# @param name [String]: The MU resource name of the server to use as the basis for this image.
|
|
1307
|
+
# @param instance_id [String]: The cloud provider resource identifier of the server to use as the basis for this image.
|
|
1308
|
+
# @param storage [Hash]: The storage devices to include in this image.
|
|
1309
|
+
# @param exclude_storage [Boolean]: Do not include the storage device profile of the running instance when creating this image.
|
|
1310
|
+
# @param region [String]: The cloud provider region
|
|
1311
|
+
# @param copy_to_regions [Array<String>]: Copy the resulting AMI into the listed regions.
|
|
1312
|
+
# @param tags [Array<String>]: Extra/override tags to apply to the image.
|
|
1313
|
+
# @return [String]: The cloud provider identifier of the new machine image.
|
|
1314
|
+
def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, make_public: false, region: MU.curRegion, copy_to_regions: [], tags: [])
|
|
1315
|
+
ami_descriptor = {
|
|
1316
|
+
:instance_id => instance_id,
|
|
1317
|
+
:name => name,
|
|
1318
|
+
:description => "Image automatically generated by Mu from #{name}"
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
storage_list = Array.new
|
|
1322
|
+
if exclude_storage
|
|
1323
|
+
instance = MU::Cloud::Server.find(cloud_id: instance_id, region: region)
|
|
1324
|
+
instance.block_device_mappings.each { |vol|
|
|
1325
|
+
if vol.device_name != instance.root_device_name
|
|
1326
|
+
|
|
1327
|
+
storage_list << MU::Cloud::AWS::Server.convertBlockDeviceMapping(
|
|
1328
|
+
{
|
|
1329
|
+
"device" => vol.device_name,
|
|
1330
|
+
"no-device" => ""
|
|
1331
|
+
}
|
|
1332
|
+
)[0]
|
|
1333
|
+
end
|
|
1334
|
+
}
|
|
1335
|
+
elsif !storage.nil?
|
|
1336
|
+
storage.each { |vol|
|
|
1337
|
+
storage_list << MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)[0]
|
|
1338
|
+
}
|
|
1339
|
+
end
|
|
1340
|
+
ami_descriptor[:block_device_mappings] = storage_list
|
|
1341
|
+
if !exclude_storage
|
|
1342
|
+
ami_descriptor[:block_device_mappings].concat(@ephemeral_mappings)
|
|
1343
|
+
end
|
|
1344
|
+
MU.log "Creating AMI from #{name}", details: ami_descriptor
|
|
1345
|
+
resp = nil
|
|
1346
|
+
begin
|
|
1347
|
+
resp = MU::Cloud::AWS.ec2(region).create_image(ami_descriptor)
|
|
1348
|
+
rescue Aws::EC2::Errors::InvalidAMINameDuplicate => e
|
|
1349
|
+
MU.log "AMI #{name} already exists, skipping", MU::WARN
|
|
1350
|
+
return nil
|
|
1351
|
+
end
|
|
1352
|
+
ami = resp.image_id
|
|
1353
|
+
MU::MommaCat.createStandardTags(ami, region: region)
|
|
1354
|
+
MU::MommaCat.createTag(ami, "Name", name, region: region)
|
|
1355
|
+
MU.log "AMI of #{name} in region #{region}: #{ami}"
|
|
1356
|
+
if make_public
|
|
1357
|
+
MU::Cloud::AWS::Server.waitForAMI(ami, region: region)
|
|
1358
|
+
MU::Cloud::AWS.ec2(region).modify_image_attribute(
|
|
1359
|
+
image_id: ami,
|
|
1360
|
+
launch_permission: {add: [{group: "all"}]},
|
|
1361
|
+
attribute: "launchPermission"
|
|
1362
|
+
)
|
|
1363
|
+
end
|
|
1364
|
+
copythreads = []
|
|
1365
|
+
if !copy_to_regions.nil? and copy_to_regions.size > 0
|
|
1366
|
+
parent_thread_id = Thread.current.object_id
|
|
1367
|
+
MU::Cloud::AWS::Server.waitForAMI(ami, region: region) if !make_public
|
|
1368
|
+
copy_to_regions.each { |r|
|
|
1369
|
+
next if r == region
|
|
1370
|
+
copythreads << Thread.new {
|
|
1371
|
+
MU.dupGlobals(parent_thread_id)
|
|
1372
|
+
copy = MU::Cloud::AWS.ec2(r).copy_image(
|
|
1373
|
+
source_region: region,
|
|
1374
|
+
source_image_id: ami,
|
|
1375
|
+
name: name,
|
|
1376
|
+
description: "Image automatically generated by Mu from #{name}"
|
|
1377
|
+
)
|
|
1378
|
+
MU.log "Initiated copy of #{ami} from #{region} to #{r}: #{copy.image_id}"
|
|
1379
|
+
|
|
1380
|
+
MU::MommaCat.createStandardTags(copy.image_id, region: r)
|
|
1381
|
+
MU::MommaCat.createTag(copy.image_id, "Name", name, region: r)
|
|
1382
|
+
if !tags.nil?
|
|
1383
|
+
tags.each { |tag|
|
|
1384
|
+
MU::MommaCat.createTag(instance.instance_id, tag['key'], tag['value'], region: r)
|
|
1385
|
+
}
|
|
1386
|
+
end
|
|
1387
|
+
MU::Cloud::AWS::Server.waitForAMI(copy.image_id, region: r)
|
|
1388
|
+
if make_public
|
|
1389
|
+
MU::Cloud::AWS.ec2(r).modify_image_attribute(
|
|
1390
|
+
image_id: copy.image_id,
|
|
1391
|
+
launch_permission: {add: [{group: "all"}]},
|
|
1392
|
+
attribute: "launchPermission"
|
|
1393
|
+
)
|
|
1394
|
+
end
|
|
1395
|
+
MU.log "AMI of #{name} in region #{r}: #{copy.image_id}"
|
|
1396
|
+
} # Thread
|
|
1397
|
+
}
|
|
1398
|
+
end
|
|
1399
|
+
|
|
1400
|
+
copythreads.each { |t|
|
|
1401
|
+
t.join
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
return resp.image_id
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
# Given a cloud platform identifier for a machine image, wait until it's
|
|
1408
|
+
# flagged as ready.
|
|
1409
|
+
# @param image_id [String]: The machine image to wait for.
|
|
1410
|
+
# @param region [String]: The cloud provider region
|
|
1411
|
+
def self.waitForAMI(image_id, region: MU.curRegion)
|
|
1412
|
+
MU.log "Checking to see if AMI #{image_id} is available", MU::DEBUG
|
|
1413
|
+
|
|
1414
|
+
retries = 0
|
|
1415
|
+
begin
|
|
1416
|
+
images = MU::Cloud::AWS.ec2(region).describe_images(image_ids: [image_id]).images
|
|
1417
|
+
if images.nil? or images.size == 0
|
|
1418
|
+
raise MuError, "No such AMI #{image_id} found"
|
|
1419
|
+
end
|
|
1420
|
+
state = images.first.state
|
|
1421
|
+
if state == "failed"
|
|
1422
|
+
raise MuError, "#{image_id} is marked as failed! I can't use this."
|
|
1423
|
+
end
|
|
1424
|
+
if state != "available"
|
|
1425
|
+
loglevel = MU::DEBUG
|
|
1426
|
+
loglevel = MU::NOTICE if retries % 3 == 0
|
|
1427
|
+
MU.log "Waiting for AMI #{image_id} in #{region} (#{state})", loglevel
|
|
1428
|
+
sleep 60
|
|
1429
|
+
end
|
|
1430
|
+
rescue Aws::EC2::Errors::InvalidAMIIDNotFound => e
|
|
1431
|
+
retries = retries + 1
|
|
1432
|
+
if retries >= 10
|
|
1433
|
+
raise e
|
|
1434
|
+
end
|
|
1435
|
+
sleep 5
|
|
1436
|
+
retry
|
|
1437
|
+
end while state != "available"
|
|
1438
|
+
MU.log "AMI #{image_id} is ready", MU::DEBUG
|
|
1439
|
+
end
|
|
1440
|
+
|
|
1441
|
+
# Maps our configuration language's 'storage' primitive to an Amazon-style
|
|
1442
|
+
# block_device_mapping.
|
|
1443
|
+
# @param storage [Hash]: The {MU::Config}-style storage description.
|
|
1444
|
+
# @return [Hash]: The Amazon-style storage description.
|
|
1445
|
+
def self.convertBlockDeviceMapping(storage)
|
|
1446
|
+
vol_struct = {}
|
|
1447
|
+
cfm_mapping = {}
|
|
1448
|
+
if storage["no_device"]
|
|
1449
|
+
vol_struct[:no_device] = storage["no_device"]
|
|
1450
|
+
cfm_mapping["NoDevice"] = storage["no_device"]
|
|
1451
|
+
end
|
|
1452
|
+
|
|
1453
|
+
if storage["device"]
|
|
1454
|
+
vol_struct[:device_name] = storage["device"]
|
|
1455
|
+
cfm_mapping["DeviceName"] = storage["device"]
|
|
1456
|
+
elsif storage["no_device"].nil?
|
|
1457
|
+
vol_struct[:device_name] = @disk_devices.shift
|
|
1458
|
+
cfm_mapping["DeviceName"] = @disk_devices.shift
|
|
1459
|
+
end
|
|
1460
|
+
|
|
1461
|
+
vol_struct[:virtual_name] = storage["virtual_name"] if storage["virtual_name"]
|
|
1462
|
+
|
|
1463
|
+
storage["volume_size"] = storage["size"]
|
|
1464
|
+
if storage["snapshot_id"] or storage["size"]
|
|
1465
|
+
vol_struct[:ebs] = {}
|
|
1466
|
+
cfm_mapping["Ebs"] = {}
|
|
1467
|
+
[:delete_on_termination, :snapshot_id, :volume_size, :volume_type, :encrypted].each { |arg|
|
|
1468
|
+
if storage.has_key?(arg.to_s) and !storage[arg.to_s].nil?
|
|
1469
|
+
vol_struct[:ebs][arg] = storage[arg.to_s]
|
|
1470
|
+
key = ""
|
|
1471
|
+
arg.to_s.split(/_/).each { |chunk| key = key + chunk.capitalize }
|
|
1472
|
+
cfm_mapping["Ebs"][key] = storage[arg.to_s]
|
|
1473
|
+
end
|
|
1474
|
+
}
|
|
1475
|
+
cfm_mapping["Ebs"].delete("Encrypted") if !cfm_mapping["Ebs"]["Encrypted"]
|
|
1476
|
+
|
|
1477
|
+
if storage["iops"] and storage["volume_type"] == "io1"
|
|
1478
|
+
vol_struct[:ebs][:iops] = storage["iops"]
|
|
1479
|
+
cfm_mapping["Ebs"]["Iops"] = storage["iops"]
|
|
1480
|
+
end
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
return [vol_struct, cfm_mapping]
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
# Retrieves the Cloud provider's randomly generated Windows password
|
|
1487
|
+
# Will only work on stock Amazon Windows AMIs or custom AMIs that where created with Administrator Password set to random in EC2Config
|
|
1488
|
+
# return [String]: A password string.
|
|
1489
|
+
def getWindowsAdminPassword
|
|
1490
|
+
if @cloud_id.nil?
|
|
1491
|
+
node, config, deploydata = describe
|
|
1492
|
+
@cloud_id = cloud_desc.instance_id
|
|
1493
|
+
end
|
|
1494
|
+
ssh_keydir = "#{Etc.getpwuid(Process.uid).dir}/.ssh"
|
|
1495
|
+
ssh_key_name = @deploy.ssh_key_name
|
|
1496
|
+
|
|
1497
|
+
retries = 0
|
|
1498
|
+
MU.log "Waiting for Windows instance password to be set by Amazon and flagged as available from the API. Note- if you're using a source AMI that already has its password set, this may fail. You'll want to set use_cloud_provider_windows_password to false if this is the case.", MU::NOTICE
|
|
1499
|
+
begin
|
|
1500
|
+
MU::Cloud::AWS.ec2(@config['region']).wait_until(:password_data_available, instance_id: @cloud_id) do |waiter|
|
|
1501
|
+
waiter.max_attempts = 60
|
|
1502
|
+
waiter.before_attempt do |attempts|
|
|
1503
|
+
MU.log "Waiting for Windows password data to be available for node #{@mu_name}", MU::NOTICE if attempts % 5 == 0
|
|
1504
|
+
end
|
|
1505
|
+
# waiter.before_wait do |attempts, resp|
|
|
1506
|
+
# throw :success if resp.data.password_data and !resp.data.password_data.empty?
|
|
1507
|
+
# end
|
|
1508
|
+
end
|
|
1509
|
+
rescue Aws::Waiters::Errors::TooManyAttemptsError => e
|
|
1510
|
+
if retries < 2
|
|
1511
|
+
retries = retries + 1
|
|
1512
|
+
MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@config['region']} never got a good response, retrying (#{retries}/2)", MU::WARN, details: e.inspect
|
|
1513
|
+
retry
|
|
1514
|
+
else
|
|
1515
|
+
MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@config['region']} never returned- this image may not be configured to have its password set by AWS.", MU::ERR
|
|
1516
|
+
return nil
|
|
1517
|
+
end
|
|
1518
|
+
end
|
|
1519
|
+
|
|
1520
|
+
resp = MU::Cloud::AWS.ec2(@config['region']).get_password_data(instance_id: @cloud_id)
|
|
1521
|
+
encrypted_password = resp.password_data
|
|
1522
|
+
|
|
1523
|
+
# Note: This is already implemented in the decrypt_windows_password API call
|
|
1524
|
+
decoded = Base64.decode64(encrypted_password)
|
|
1525
|
+
pem_bytes = File.open("#{ssh_keydir}/#{ssh_key_name}", 'rb') { |f| f.read }
|
|
1526
|
+
private_key = OpenSSL::PKey::RSA.new(pem_bytes)
|
|
1527
|
+
decrypted_password = private_key.private_decrypt(decoded)
|
|
1528
|
+
return decrypted_password
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
@eips_used = Array.new
|
|
1532
|
+
# Find a free AWS Elastic IP.
|
|
1533
|
+
# @param classic [Boolean]: Toggle whether to allocate an IP in EC2 Classic
|
|
1534
|
+
# instead of VPC.
|
|
1535
|
+
# @param ip [String]: Request a specific IP address.
|
|
1536
|
+
# @param region [String]: The cloud provider region
|
|
1537
|
+
def self.findFreeElasticIp(classic: false, ip: nil, region: MU.curRegion)
|
|
1538
|
+
filters = Array.new
|
|
1539
|
+
if !classic
|
|
1540
|
+
filters << {name: "domain", values: ["vpc"]}
|
|
1541
|
+
else
|
|
1542
|
+
filters << {name: "domain", values: ["standard"]}
|
|
1543
|
+
end
|
|
1544
|
+
filters << {name: "public-ip", values: [ip]} if ip != nil
|
|
1545
|
+
|
|
1546
|
+
if filters.size > 0
|
|
1547
|
+
resp = MU::Cloud::AWS.ec2(region).describe_addresses(filters: filters)
|
|
1548
|
+
else
|
|
1549
|
+
resp = MU::Cloud::AWS.ec2(region).describe_addresses()
|
|
1550
|
+
end
|
|
1551
|
+
resp.addresses.each { |address|
|
|
1552
|
+
return address if (address.network_interface_id.nil? || address.network_interface_id.empty?) && !@eips_used.include?(address.public_ip)
|
|
1553
|
+
}
|
|
1554
|
+
if ip != nil
|
|
1555
|
+
if !classic
|
|
1556
|
+
raise MuError, "Requested EIP #{ip}, but no such IP exists or is avaulable in VPC"
|
|
1557
|
+
else
|
|
1558
|
+
raise MuError, "Requested EIP #{ip}, but no such IP exists or is available in EC2 Classic"
|
|
1559
|
+
end
|
|
1560
|
+
end
|
|
1561
|
+
if !classic
|
|
1562
|
+
resp = MU::Cloud::AWS.ec2(region).allocate_address(domain: "vpc")
|
|
1563
|
+
new_ip = resp.public_ip
|
|
1564
|
+
else
|
|
1565
|
+
new_ip = MU::Cloud::AWS.ec2(region).allocate_address().public_ip
|
|
1566
|
+
end
|
|
1567
|
+
filters = [{name: "public-ip", values: [new_ip]}]
|
|
1568
|
+
if resp.domain
|
|
1569
|
+
filters << {name: "domain", values: [resp.domain]}
|
|
1570
|
+
end rescue NoMethodError
|
|
1571
|
+
if new_ip.nil?
|
|
1572
|
+
MU.log "Unable to allocate new Elastic IP. Are we at quota?", MU::ERR
|
|
1573
|
+
raise MuError, "Unable to allocate new Elastic IP. Are we at quota?"
|
|
1574
|
+
end
|
|
1575
|
+
MU.log "Allocated new EIP #{new_ip}, fetching full description"
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
begin
|
|
1579
|
+
begin
|
|
1580
|
+
sleep 5
|
|
1581
|
+
resp = MU::Cloud::AWS.ec2(region).describe_addresses(
|
|
1582
|
+
filters: filters
|
|
1583
|
+
)
|
|
1584
|
+
addr = resp.addresses.first
|
|
1585
|
+
end while resp.addresses.size < 1 or addr.public_ip.nil?
|
|
1586
|
+
rescue NoMethodError
|
|
1587
|
+
MU.log "EIP descriptor came back without a public_ip attribute for #{new_ip}, retrying", MU::WARN
|
|
1588
|
+
sleep 5
|
|
1589
|
+
retry
|
|
1590
|
+
end
|
|
1591
|
+
|
|
1592
|
+
return addr
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
# Add a volume to this instance
|
|
1596
|
+
# @param dev [String]: Device name to use when attaching to instance
|
|
1597
|
+
# @param size [String]: Size (in gb) of the new volume
|
|
1598
|
+
# @param type [String]: Cloud storage type of the volume, if applicable
|
|
1599
|
+
def addVolume(dev, size, type: "gp2")
|
|
1600
|
+
if @cloud_id.nil? or @cloud_id.empty?
|
|
1601
|
+
MU.log "#{self} didn't have a cloud id, couldn't determine 'active?' status", MU::ERR
|
|
1602
|
+
return true
|
|
1603
|
+
end
|
|
1604
|
+
az = nil
|
|
1605
|
+
MU::Cloud::AWS.ec2(@config['region']).describe_instances(
|
|
1606
|
+
instance_ids: [@cloud_id]
|
|
1607
|
+
).reservations.each { |resp|
|
|
1608
|
+
if !resp.nil? and !resp.instances.nil?
|
|
1609
|
+
resp.instances.each { |instance|
|
|
1610
|
+
az = instance.placement.availability_zone
|
|
1611
|
+
instance.block_device_mappings.each { |vol|
|
|
1612
|
+
if vol.device_name == dev
|
|
1613
|
+
MU.log "A volume #{dev} already attached to #{self}, skipping", MU::NOTICE
|
|
1614
|
+
return
|
|
1615
|
+
end
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
end
|
|
1619
|
+
}
|
|
1620
|
+
MU.log "Creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
|
|
1621
|
+
creation = MU::Cloud::AWS.ec2(@config['region']).create_volume(
|
|
1622
|
+
availability_zone: az,
|
|
1623
|
+
size: size,
|
|
1624
|
+
volume_type: type
|
|
1625
|
+
)
|
|
1626
|
+
begin
|
|
1627
|
+
sleep 3
|
|
1628
|
+
creation = MU::Cloud::AWS.ec2(@config['region']).describe_volumes(volume_ids: [creation.volume_id]).volumes.first
|
|
1629
|
+
if !["creating", "available"].include?(creation.state)
|
|
1630
|
+
raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
|
|
1631
|
+
end
|
|
1632
|
+
end while creation.state != "available"
|
|
1633
|
+
|
|
1634
|
+
if @deploy
|
|
1635
|
+
MU::MommaCat.listStandardTags.each_pair { |key, value|
|
|
1636
|
+
MU::MommaCat.createTag(creation.volume_id, key, value, region: @config['region'])
|
|
1637
|
+
}
|
|
1638
|
+
MU::MommaCat.createTag(creation.volume_id, "Name", "#{MU.deploy_id}-#{@config["name"].upcase}-#{dev.upcase}", region: @config['region'])
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
attachment = MU::Cloud::AWS.ec2(@config['region']).attach_volume(
|
|
1642
|
+
device: dev,
|
|
1643
|
+
instance_id: @cloud_id,
|
|
1644
|
+
volume_id: creation.volume_id
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
begin
|
|
1648
|
+
sleep 3
|
|
1649
|
+
attachment = MU::Cloud::AWS.ec2(@config['region']).describe_volumes(volume_ids: [attachment.volume_id]).volumes.first.attachments.first
|
|
1650
|
+
if !["attaching", "attached"].include?(attachment.state)
|
|
1651
|
+
raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
|
|
1652
|
+
end
|
|
1653
|
+
end while attachment.state != "attached"
|
|
1654
|
+
end
|
|
1655
|
+
|
|
1656
|
+
# Determine whether the node in question exists at the Cloud provider
|
|
1657
|
+
# layer.
|
|
1658
|
+
# @return [Boolean]
|
|
1659
|
+
def active?
|
|
1660
|
+
if @cloud_id.nil? or @cloud_id.empty?
|
|
1661
|
+
MU.log "#{self} didn't have a #{@cloud_id}, couldn't determine 'active?' status", MU::ERR
|
|
1662
|
+
return true
|
|
1663
|
+
end
|
|
1664
|
+
begin
|
|
1665
|
+
MU::Cloud::AWS.ec2(@config['region']).describe_instances(
|
|
1666
|
+
instance_ids: [@cloud_id]
|
|
1667
|
+
).reservations.each { |resp|
|
|
1668
|
+
if !resp.nil? and !resp.instances.nil?
|
|
1669
|
+
resp.instances.each { |instance|
|
|
1670
|
+
if instance.state.name == "terminated" or
|
|
1671
|
+
instance.state.name == "terminating"
|
|
1672
|
+
return false
|
|
1673
|
+
end
|
|
1674
|
+
return true
|
|
1675
|
+
}
|
|
1676
|
+
end
|
|
1677
|
+
}
|
|
1678
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
|
|
1679
|
+
return false
|
|
1680
|
+
end
|
|
1681
|
+
return false
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1684
|
+
@eip_semaphore = Mutex.new
|
|
1685
|
+
# Associate an Amazon Elastic IP with an instance.
|
|
1686
|
+
# @param instance_id [String]: The cloud provider identifier of the instance.
|
|
1687
|
+
# @param classic [Boolean]: Whether to assume we're using an IP in EC2 Classic instead of VPC.
|
|
1688
|
+
# @param ip [String]: Request a specific IP address.
|
|
1689
|
+
# @param region [String]: The cloud provider region
|
|
1690
|
+
# @return [void]
|
|
1691
|
+
def self.associateElasticIp(instance_id, classic: false, ip: nil, region: MU.curRegion)
|
|
1692
|
+
MU.log "associateElasticIp called: #{instance_id}, classic: #{classic}, ip: #{ip}, region: #{region}", MU::DEBUG
|
|
1693
|
+
elastic_ip = nil
|
|
1694
|
+
@eip_semaphore.synchronize {
|
|
1695
|
+
if !ip.nil?
|
|
1696
|
+
filters = [{name: "public-ip", values: [ip]}]
|
|
1697
|
+
resp = MU::Cloud::AWS.ec2(region).describe_addresses(filters: filters)
|
|
1698
|
+
if @eips_used.include?(ip)
|
|
1699
|
+
is_free = false
|
|
1700
|
+
resp.addresses.each { |address|
|
|
1701
|
+
if address.public_ip == ip and (address.instance_id.nil? and address.network_interface_id.nil?) or address.instance_id == instance_id
|
|
1702
|
+
@eips_used.delete(ip)
|
|
1703
|
+
is_free = true
|
|
1704
|
+
end
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
raise MuError, "Requested EIP #{ip}, but we've already assigned this IP to someone else" if !is_free
|
|
1708
|
+
else
|
|
1709
|
+
resp.addresses.each { |address|
|
|
1710
|
+
if address.public_ip == ip and address.instance_id == instance_id
|
|
1711
|
+
return ip
|
|
1712
|
+
end
|
|
1713
|
+
}
|
|
1714
|
+
end
|
|
1715
|
+
end
|
|
1716
|
+
elastic_ip = findFreeElasticIp(classic: classic, ip: ip)
|
|
1717
|
+
if !ip.nil? and (elastic_ip.nil? or ip != elastic_ip.public_ip)
|
|
1718
|
+
raise MuError, "Requested EIP #{ip}, but this IP does not exist or is not available"
|
|
1719
|
+
end
|
|
1720
|
+
if elastic_ip.nil?
|
|
1721
|
+
raise MuError, "Couldn't find an Elastic IP to associate with #{instance_id}"
|
|
1722
|
+
end
|
|
1723
|
+
@eips_used << elastic_ip.public_ip
|
|
1724
|
+
MU.log "Associating Elastic IP #{elastic_ip.public_ip} with #{instance_id}", details: elastic_ip
|
|
1725
|
+
}
|
|
1726
|
+
attempts = 0
|
|
1727
|
+
begin
|
|
1728
|
+
if classic
|
|
1729
|
+
resp = MU::Cloud::AWS.ec2(region).associate_address(
|
|
1730
|
+
instance_id: instance_id,
|
|
1731
|
+
public_ip: elastic_ip.public_ip
|
|
1732
|
+
)
|
|
1733
|
+
else
|
|
1734
|
+
resp = MU::Cloud::AWS.ec2(region).associate_address(
|
|
1735
|
+
instance_id: instance_id,
|
|
1736
|
+
allocation_id: elastic_ip.allocation_id,
|
|
1737
|
+
allow_reassociation: false
|
|
1738
|
+
)
|
|
1739
|
+
end
|
|
1740
|
+
rescue Aws::EC2::Errors::IncorrectInstanceState => e
|
|
1741
|
+
attempts = attempts + 1
|
|
1742
|
+
if attempts < 6
|
|
1743
|
+
MU.log "Got #{e.message} associating #{elastic_ip.allocation_id} with #{instance_id}, retrying", MU::WARN
|
|
1744
|
+
sleep 5
|
|
1745
|
+
retry
|
|
1746
|
+
end
|
|
1747
|
+
raise MuError "#{e.message} associating #{elastic_ip.allocation_id} with #{instance_id}"
|
|
1748
|
+
rescue Aws::EC2::Errors::ResourceAlreadyAssociated => e
|
|
1749
|
+
# A previous association attempt may have succeeded, albeit slowly.
|
|
1750
|
+
resp = MU::Cloud::AWS.ec2(region).describe_addresses(
|
|
1751
|
+
allocation_ids: [elastic_ip.allocation_id]
|
|
1752
|
+
)
|
|
1753
|
+
first_addr = resp.addresses.first
|
|
1754
|
+
if !first_addr.nil? and first_addr.instance_id == instance_id
|
|
1755
|
+
MU.log "#{elastic_ip.public_ip} already associated with #{instance_id}", MU::WARN
|
|
1756
|
+
else
|
|
1757
|
+
MU.log "#{elastic_ip.public_ip} shows as already associated!", MU::ERR, details: resp
|
|
1758
|
+
raise MuError, "#{elastic_ip.public_ip} shows as already associated with #{first_addr.instance_id}!"
|
|
1759
|
+
end
|
|
1760
|
+
end
|
|
1761
|
+
|
|
1762
|
+
instance = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [instance_id]).reservations.first.instances.first
|
|
1763
|
+
waited = false
|
|
1764
|
+
if instance.public_ip_address != elastic_ip.public_ip
|
|
1765
|
+
waited = true
|
|
1766
|
+
begin
|
|
1767
|
+
sleep 10
|
|
1768
|
+
MU.log "Waiting for Elastic IP association of #{elastic_ip.public_ip} to #{instance_id} to take effect", MU::NOTICE
|
|
1769
|
+
instance = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [instance_id]).reservations.first.instances.first
|
|
1770
|
+
end while instance.public_ip_address != elastic_ip.public_ip
|
|
1771
|
+
end
|
|
1772
|
+
|
|
1773
|
+
MU.log "Elastic IP #{elastic_ip.public_ip} now associated with #{instance_id}" if waited
|
|
1774
|
+
|
|
1775
|
+
return elastic_ip.public_ip
|
|
1776
|
+
end
|
|
1777
|
+
|
|
1778
|
+
# Remove all instances associated with the currently loaded deployment. Also cleans up associated volumes, droppings in the MU master's /etc/hosts and ~/.ssh, and in whatever Groomer was used.
|
|
1779
|
+
# @param noop [Boolean]: If true, will only print what would be done
|
|
1780
|
+
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
1781
|
+
# @param region [String]: The cloud provider region
|
|
1782
|
+
# @return [void]
|
|
1783
|
+
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, skipsnapshots: false, onlycloud: false, flags: {})
|
|
1784
|
+
tagfilters = [
|
|
1785
|
+
{name: "tag:MU-ID", values: [MU.deploy_id]}
|
|
1786
|
+
]
|
|
1787
|
+
if !ignoremaster
|
|
1788
|
+
tagfilters << {name: "tag:MU-MASTER-IP", values: [MU.mu_public_ip]}
|
|
1789
|
+
end
|
|
1790
|
+
instances = Array.new
|
|
1791
|
+
unterminated = Array.new
|
|
1792
|
+
name_tags = Array.new
|
|
1793
|
+
|
|
1794
|
+
# Build a list of instances we need to clean up. We guard against
|
|
1795
|
+
# accidental deletion here by requiring someone to have hand-terminated
|
|
1796
|
+
# these, by default.
|
|
1797
|
+
resp = MU::Cloud::AWS.ec2(region).describe_instances(
|
|
1798
|
+
filters: tagfilters
|
|
1799
|
+
)
|
|
1800
|
+
|
|
1801
|
+
return if resp.data.reservations.nil?
|
|
1802
|
+
resp.data.reservations.each { |reservation|
|
|
1803
|
+
reservation.instances.each { |instance|
|
|
1804
|
+
if instance.state.name != "terminated"
|
|
1805
|
+
unterminated << instance
|
|
1806
|
+
instance.tags.each { |tag|
|
|
1807
|
+
name_tags << tag.value if tag.key == "Name"
|
|
1808
|
+
}
|
|
1809
|
+
end
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
parent_thread_id = Thread.current.object_id
|
|
1814
|
+
|
|
1815
|
+
threads = []
|
|
1816
|
+
unterminated.each { |instance|
|
|
1817
|
+
threads << Thread.new(instance) { |myinstance|
|
|
1818
|
+
MU.dupGlobals(parent_thread_id)
|
|
1819
|
+
Thread.abort_on_exception = true
|
|
1820
|
+
MU::Cloud::AWS::Server.terminateInstance(id: myinstance.instance_id, noop: noop, onlycloud: onlycloud, region: region, deploy_id: MU.deploy_id)
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
resp = MU::Cloud::AWS.ec2(region).describe_volumes(
|
|
1825
|
+
filters: tagfilters
|
|
1826
|
+
)
|
|
1827
|
+
resp.data.volumes.each { |volume|
|
|
1828
|
+
threads << Thread.new(volume) { |myvolume|
|
|
1829
|
+
MU.dupGlobals(parent_thread_id)
|
|
1830
|
+
Thread.abort_on_exception = true
|
|
1831
|
+
MU::Cloud::AWS::Server.delete_volume(myvolume, noop, skipsnapshots)
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
# Wait for all of the instances to finish cleanup before proceeding
|
|
1836
|
+
threads.each { |t|
|
|
1837
|
+
t.join
|
|
1838
|
+
}
|
|
1839
|
+
end
|
|
1840
|
+
|
|
1841
|
+
# Terminate an instance.
|
|
1842
|
+
# @param instance [OpenStruct]: The cloud provider's description of the instance.
|
|
1843
|
+
# @param id [String]: The cloud provider's identifier for the instance, to use if the full description is not available.
|
|
1844
|
+
# @param region [String]: The cloud provider region
|
|
1845
|
+
# @return [void]
|
|
1846
|
+
def self.terminateInstance(instance: nil, noop: false, id: nil, onlycloud: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil)
|
|
1847
|
+
ips = Array.new
|
|
1848
|
+
if !instance
|
|
1849
|
+
if id
|
|
1850
|
+
begin
|
|
1851
|
+
resp = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [id])
|
|
1852
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
|
|
1853
|
+
MU.log "Instance #{id} no longer exists", MU::WARN
|
|
1854
|
+
end
|
|
1855
|
+
if !resp.nil? and !resp.reservations.nil? and !resp.reservations.first.nil?
|
|
1856
|
+
instance = resp.reservations.first.instances.first
|
|
1857
|
+
ips << instance.public_ip_address if !instance.public_ip_address.nil?
|
|
1858
|
+
ips << instance.private_ip_address if !instance.private_ip_address.nil?
|
|
1859
|
+
end
|
|
1860
|
+
else
|
|
1861
|
+
MU.log "You must supply an instance handle or id to terminateInstance", MU::ERR
|
|
1862
|
+
end
|
|
1863
|
+
else
|
|
1864
|
+
id = instance.instance_id
|
|
1865
|
+
end
|
|
1866
|
+
if !MU.deploy_id.empty?
|
|
1867
|
+
deploy_dir = File.expand_path("#{MU.dataDir}/deployments/"+MU.deploy_id)
|
|
1868
|
+
if Dir.exist?(deploy_dir) and !noop
|
|
1869
|
+
FileUtils.touch("#{deploy_dir}/.cleanup-"+id)
|
|
1870
|
+
end
|
|
1871
|
+
end
|
|
1872
|
+
|
|
1873
|
+
server_obj = MU::MommaCat.findStray(
|
|
1874
|
+
"AWS",
|
|
1875
|
+
"servers",
|
|
1876
|
+
region: region,
|
|
1877
|
+
deploy_id: deploy_id,
|
|
1878
|
+
cloud_id: id,
|
|
1879
|
+
mu_name: mu_name
|
|
1880
|
+
).first
|
|
1881
|
+
|
|
1882
|
+
begin
|
|
1883
|
+
MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [id])
|
|
1884
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
|
|
1885
|
+
MU.log "Instance #{id} no longer exists", MU::DEBUG
|
|
1886
|
+
end
|
|
1887
|
+
|
|
1888
|
+
if !server_obj.nil? and MU::Cloud::AWS.hosted and !MU::Cloud::AWS.isGovCloud?
|
|
1889
|
+
# DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
|
|
1890
|
+
cleaned_dns = false
|
|
1891
|
+
mu_name = server_obj.mu_name
|
|
1892
|
+
mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu").values.first
|
|
1893
|
+
if !mu_zone.nil?
|
|
1894
|
+
zone_rrsets = []
|
|
1895
|
+
rrsets = MU::Cloud::AWS.route53(region).list_resource_record_sets(hosted_zone_id: mu_zone.id)
|
|
1896
|
+
rrsets.resource_record_sets.each{ |record|
|
|
1897
|
+
zone_rrsets << record
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
# AWS API returns a maximum of 100 results. DNS zones are likely to have more than 100 records, lets page and make sure we grab all records in a given zone
|
|
1901
|
+
while rrsets.next_record_name && rrsets.next_record_type
|
|
1902
|
+
rrsets = MU::Cloud::AWS.route53(region).list_resource_record_sets(hosted_zone_id: mu_zone.id, start_record_name: rrsets.next_record_name, start_record_type: rrsets.next_record_type)
|
|
1903
|
+
rrsets.resource_record_sets.each{ |record|
|
|
1904
|
+
zone_rrsets << record
|
|
1905
|
+
}
|
|
1906
|
+
end
|
|
1907
|
+
end
|
|
1908
|
+
if !onlycloud and !mu_name.nil?
|
|
1909
|
+
# DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
|
|
1910
|
+
if !zone_rrsets.empty?
|
|
1911
|
+
zone_rrsets.each { |rrset|
|
|
1912
|
+
if rrset.name.match(/^#{mu_name.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
|
|
1913
|
+
rrset.resource_records.each { |record|
|
|
1914
|
+
MU::Cloud::DNSZone.genericMuDNSEntry(name: mu_name, target: record.value, cloudclass: MU::Cloud::Server, delete: true)
|
|
1915
|
+
cleaned_dns = true
|
|
1916
|
+
}
|
|
1917
|
+
end
|
|
1918
|
+
}
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
# Expunge traces left in Chef, Puppet or what have you
|
|
1922
|
+
MU::Groomer.supportedGroomers.each { |groomer|
|
|
1923
|
+
groomclass = MU::Groomer.loadGroomer(groomer)
|
|
1924
|
+
if !server_obj.nil? and !server_obj.config.nil? and !server_obj.config['vault_access'].nil?
|
|
1925
|
+
groomclass.cleanup(mu_name, server_obj.config['vault_access'], noop)
|
|
1926
|
+
else
|
|
1927
|
+
groomclass.cleanup(mu_name, [], noop)
|
|
1928
|
+
end
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
if !noop
|
|
1932
|
+
if !server_obj.nil? and !server_obj.config.nil?
|
|
1933
|
+
MU.mommacat.notify(MU::Cloud::Server.cfg_plural, server_obj.config['name'], {}, mu_name: server_obj.mu_name, remove: true) if MU.mommacat
|
|
1934
|
+
end
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1937
|
+
# If we didn't manage to find this instance's Route53 entry by sifting
|
|
1938
|
+
# deployment metadata, see if we can get it with the Name tag.
|
|
1939
|
+
if !mu_zone.nil? and !cleaned_dns and !instance.nil?
|
|
1940
|
+
instance.tags.each { |tag|
|
|
1941
|
+
if tag.key == "Name"
|
|
1942
|
+
zone_rrsets.each { |rrset|
|
|
1943
|
+
if rrset.name.match(/^#{tag.value.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
|
|
1944
|
+
rrset.resource_records.each { |record|
|
|
1945
|
+
MU::Cloud::DNSZone.genericMuDNSEntry(name: tag.value, target: record.value, cloudclass: MU::Cloud::Server, delete: true) if !noop
|
|
1946
|
+
}
|
|
1947
|
+
end
|
|
1948
|
+
}
|
|
1949
|
+
end
|
|
1950
|
+
}
|
|
1951
|
+
end
|
|
1952
|
+
end
|
|
1953
|
+
end
|
|
1954
|
+
|
|
1955
|
+
if ips.size > 0 and !onlycloud
|
|
1956
|
+
known_hosts_files = [Etc.getpwuid(Process.uid).dir+"/.ssh/known_hosts"]
|
|
1957
|
+
if Etc.getpwuid(Process.uid).name == "root"
|
|
1958
|
+
known_hosts_files << Etc.getpwnam("nagios").dir+"/.ssh/known_hosts"
|
|
1959
|
+
end
|
|
1960
|
+
known_hosts_files.each { |known_hosts|
|
|
1961
|
+
next if !File.exists?(known_hosts)
|
|
1962
|
+
MU.log "Cleaning up #{ips} from #{known_hosts}"
|
|
1963
|
+
if !noop
|
|
1964
|
+
File.open(known_hosts, File::CREAT|File::RDWR, 0644) { |f|
|
|
1965
|
+
f.flock(File::LOCK_EX)
|
|
1966
|
+
newlines = Array.new
|
|
1967
|
+
f.readlines.each { |line|
|
|
1968
|
+
ip_match = false
|
|
1969
|
+
ips.each { |ip|
|
|
1970
|
+
if line.match(/(^|,| )#{ip}( |,)/)
|
|
1971
|
+
MU.log "Expunging #{ip} from #{known_hosts}"
|
|
1972
|
+
ip_match = true
|
|
1973
|
+
end
|
|
1974
|
+
}
|
|
1975
|
+
newlines << line if !ip_match
|
|
1976
|
+
}
|
|
1977
|
+
f.rewind
|
|
1978
|
+
f.truncate(0)
|
|
1979
|
+
f.puts(newlines)
|
|
1980
|
+
f.flush
|
|
1981
|
+
f.flock(File::LOCK_UN)
|
|
1982
|
+
}
|
|
1983
|
+
end
|
|
1984
|
+
}
|
|
1985
|
+
end
|
|
1986
|
+
|
|
1987
|
+
return if instance.nil?
|
|
1988
|
+
|
|
1989
|
+
name = ""
|
|
1990
|
+
instance.tags.each { |tag|
|
|
1991
|
+
name = tag.value if tag.key == "Name"
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
if instance.state.name == "terminated"
|
|
1995
|
+
MU.log "#{instance.instance_id} (#{name}) has already been terminated, skipping"
|
|
1996
|
+
else
|
|
1997
|
+
if instance.state.name == "terminating"
|
|
1998
|
+
MU.log "#{instance.instance_id} (#{name}) already terminating, waiting"
|
|
1999
|
+
elsif instance.state.name != "running" and instance.state.name != "pending" and instance.state.name != "stopping" and instance.state.name != "stopped"
|
|
2000
|
+
MU.log "#{instance.instance_id} (#{name}) is in state #{instance.state.name}, waiting"
|
|
2001
|
+
else
|
|
2002
|
+
MU.log "Terminating #{instance.instance_id} (#{name}) #{noop}"
|
|
2003
|
+
if !noop
|
|
2004
|
+
begin
|
|
2005
|
+
MU::Cloud::AWS.ec2(region).modify_instance_attribute(
|
|
2006
|
+
instance_id: instance.instance_id,
|
|
2007
|
+
disable_api_termination: {value: false}
|
|
2008
|
+
)
|
|
2009
|
+
MU::Cloud::AWS.ec2(region).terminate_instances(instance_ids: [instance.instance_id])
|
|
2010
|
+
# Small race window here with the state changing from under us
|
|
2011
|
+
rescue Aws::EC2::Errors::IncorrectInstanceState => e
|
|
2012
|
+
resp = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [id])
|
|
2013
|
+
if !resp.nil? and !resp.reservations.nil? and !resp.reservations.first.nil?
|
|
2014
|
+
instance = resp.reservations.first.instances.first
|
|
2015
|
+
if !instance.nil? and instance.state.name != "terminated" and instance.state.name != "terminating"
|
|
2016
|
+
sleep 5
|
|
2017
|
+
retry
|
|
2018
|
+
end
|
|
2019
|
+
end
|
|
2020
|
+
rescue Aws::EC2::Errors::InternalError => e
|
|
2021
|
+
MU.log "Error #{e.inspect} while Terminating instance #{instance.instance_id} (#{name}), retrying", MU::WARN, details: e.inspect
|
|
2022
|
+
sleep 5
|
|
2023
|
+
retry
|
|
2024
|
+
end
|
|
2025
|
+
end
|
|
2026
|
+
end
|
|
2027
|
+
while instance.state.name != "terminated" and !noop
|
|
2028
|
+
sleep 30
|
|
2029
|
+
instance_response = MU::Cloud::AWS.ec2(region).describe_instances(instance_ids: [instance.instance_id])
|
|
2030
|
+
instance = instance_response.reservations.first.instances.first
|
|
2031
|
+
end
|
|
2032
|
+
MU.log "#{instance.instance_id} (#{name}) terminated" if !noop
|
|
2033
|
+
end
|
|
2034
|
+
end
|
|
2035
|
+
|
|
2036
|
+
# Cloud-specific configuration properties.
|
|
2037
|
+
# @param config [MU::Config]: The calling MU::Config object
|
|
2038
|
+
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
|
2039
|
+
def self.schema(config)
|
|
2040
|
+
toplevel_required = []
|
|
2041
|
+
schema = {
|
|
2042
|
+
"ami_id" => {
|
|
2043
|
+
"type" => "string",
|
|
2044
|
+
"description" => "The Amazon EC2 AMI on which to base this instance. Will use the default appropriate for the platform, if not specified."
|
|
2045
|
+
},
|
|
2046
|
+
"image_id" => {
|
|
2047
|
+
"type" => "string",
|
|
2048
|
+
"description" => "Synonymous with ami_id"
|
|
2049
|
+
},
|
|
2050
|
+
"generate_iam_role" => {
|
|
2051
|
+
"type" => "boolean",
|
|
2052
|
+
"default" => true,
|
|
2053
|
+
"description" => "Generate a unique IAM profile for this Server or ServerPool.",
|
|
2054
|
+
},
|
|
2055
|
+
"iam_role" => {
|
|
2056
|
+
"type" => "string",
|
|
2057
|
+
"description" => "An Amazon IAM instance profile, from which to harvest role policies to merge into this node's own instance profile. If generate_iam_role is false, will simple use this profile.",
|
|
2058
|
+
},
|
|
2059
|
+
"canned_iam_policies" => {
|
|
2060
|
+
"type" => "array",
|
|
2061
|
+
"items" => {
|
|
2062
|
+
"description" => "IAM policies to attach, pre-defined by Amazon (e.g. AmazonEKSWorkerNodePolicy)",
|
|
2063
|
+
"type" => "string"
|
|
2064
|
+
}
|
|
2065
|
+
},
|
|
2066
|
+
"iam_policies" => {
|
|
2067
|
+
"type" => "array",
|
|
2068
|
+
"items" => {
|
|
2069
|
+
"description" => "Amazon-compatible role policies which will be merged into this node's own instance profile. Not valid with generate_iam_role set to false. Our parser expects the role policy document to me embedded under a named container, e.g. { 'name_of_policy':'{ <policy document> } }",
|
|
2070
|
+
"type" => "object"
|
|
2071
|
+
}
|
|
2072
|
+
},
|
|
2073
|
+
"ingress_rules" => {
|
|
2074
|
+
"items" => {
|
|
2075
|
+
"properties" => {
|
|
2076
|
+
"sgs" => {
|
|
2077
|
+
"type" => "array",
|
|
2078
|
+
"items" => {
|
|
2079
|
+
"description" => "Other AWS Security Groups; resources that are associated with this group will have this rule applied to their traffic",
|
|
2080
|
+
"type" => "string"
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
"lbs" => {
|
|
2084
|
+
"type" => "array",
|
|
2085
|
+
"items" => {
|
|
2086
|
+
"description" => "AWS Load Balancers which will have this rule applied to their traffic",
|
|
2087
|
+
"type" => "string"
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
[toplevel_required, schema]
|
|
2095
|
+
end
|
|
2096
|
+
|
|
2097
|
+
# Confirm that the given instance size is valid for the given region.
|
|
2098
|
+
# If someone accidentally specified an equivalent size from some other cloud provider, return something that makes sense. If nothing makes sense, return nil.
|
|
2099
|
+
# @param size [String]: Instance type to check
|
|
2100
|
+
# @param region [String]: Region to check against
|
|
2101
|
+
# @return [String,nil]
|
|
2102
|
+
def self.validateInstanceType(size, region)
|
|
2103
|
+
begin
|
|
2104
|
+
types = (MU::Cloud::AWS.listInstanceTypes(region))[region]
|
|
2105
|
+
rescue Aws::Pricing::Errors::UnrecognizedClientException
|
|
2106
|
+
MU.log "Saw authentication error communicating with Pricing API, going to assume our instance type is correct", MU::WARN
|
|
2107
|
+
return size
|
|
2108
|
+
end
|
|
2109
|
+
if size.nil? or !types.has_key?(size)
|
|
2110
|
+
# See if it's a type we can approximate from one of the other clouds
|
|
2111
|
+
gtypes = (MU::Cloud::Google.listInstanceTypes)[MU::Cloud::Google.myRegion]
|
|
2112
|
+
foundmatch = false
|
|
2113
|
+
if gtypes and gtypes.size > 0 and gtypes.has_key?(size)
|
|
2114
|
+
vcpu = gtypes[size]["vcpu"]
|
|
2115
|
+
mem = gtypes[size]["memory"]
|
|
2116
|
+
ecu = gtypes[size]["ecu"]
|
|
2117
|
+
types.keys.sort.reverse.each { |type|
|
|
2118
|
+
features = types[type]
|
|
2119
|
+
next if ecu == "Variable" and ecu != features["ecu"]
|
|
2120
|
+
next if features["vcpu"] != vcpu
|
|
2121
|
+
if (features["memory"] - mem.to_f).abs < 0.10*mem
|
|
2122
|
+
foundmatch = true
|
|
2123
|
+
MU.log "You specified a Google Compute instance type '#{size}.' Approximating with Amazon EC2 type '#{type}.'", MU::WARN
|
|
2124
|
+
size = type
|
|
2125
|
+
break
|
|
2126
|
+
end
|
|
2127
|
+
}
|
|
2128
|
+
end
|
|
2129
|
+
if !foundmatch
|
|
2130
|
+
MU.log "Invalid size '#{size}' for AWS EC2 instance in #{region}. Supported types:", MU::ERR, details: types.keys.sort.join(", ")
|
|
2131
|
+
return nil
|
|
2132
|
+
end
|
|
2133
|
+
end
|
|
2134
|
+
size
|
|
2135
|
+
end
|
|
2136
|
+
|
|
2137
|
+
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
|
|
2138
|
+
# @param server [Hash]: The resource to process and validate
|
|
2139
|
+
# @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
|
2140
|
+
# @return [Boolean]: True if validation succeeded, False otherwise
|
|
2141
|
+
def self.validateConfig(server, configurator)
|
|
2142
|
+
ok = true
|
|
2143
|
+
|
|
2144
|
+
server['size'] = validateInstanceType(server["size"], server["region"])
|
|
2145
|
+
ok = false if server['size'].nil?
|
|
2146
|
+
|
|
2147
|
+
if !server['generate_iam_role']
|
|
2148
|
+
if !server['iam_role'] and server['cloud'] != "CloudFormation"
|
|
2149
|
+
MU.log "Must set iam_role if generate_iam_role set to false", MU::ERR
|
|
2150
|
+
ok = false
|
|
2151
|
+
end
|
|
2152
|
+
if !server['iam_policies'].nil? and server['iam_policies'].size > 0
|
|
2153
|
+
MU.log "Cannot mix iam_policies with generate_iam_role set to false", MU::ERR
|
|
2154
|
+
ok = false
|
|
2155
|
+
end
|
|
2156
|
+
else
|
|
2157
|
+
role = {
|
|
2158
|
+
"name" => server["name"],
|
|
2159
|
+
"can_assume" => [
|
|
2160
|
+
{
|
|
2161
|
+
"entity_id" => "ec2.amazonaws.com",
|
|
2162
|
+
"entity_type" => "service"
|
|
2163
|
+
}
|
|
2164
|
+
],
|
|
2165
|
+
"policies" => [
|
|
2166
|
+
{
|
|
2167
|
+
"name" => "MuSecrets",
|
|
2168
|
+
"permissions" => ["s3:GetObject"],
|
|
2169
|
+
"targets" => [
|
|
2170
|
+
{
|
|
2171
|
+
"identifier" => 'arn:'+(MU::Cloud::AWS.isGovCloud?(server['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU.adminBucketName+'/Mu_CA.pem'
|
|
2172
|
+
}
|
|
2173
|
+
]
|
|
2174
|
+
}
|
|
2175
|
+
]
|
|
2176
|
+
}
|
|
2177
|
+
if server['iam_policies']
|
|
2178
|
+
role['iam_policies'] = server['iam_policies'].dup
|
|
2179
|
+
end
|
|
2180
|
+
if server['canned_policies']
|
|
2181
|
+
role['import'] = server['canned_policies'].dup
|
|
2182
|
+
end
|
|
2183
|
+
if server['iam_role']
|
|
2184
|
+
# XXX maybe break this down into policies and add those?
|
|
2185
|
+
end
|
|
2186
|
+
|
|
2187
|
+
configurator.insertKitten(role, "roles")
|
|
2188
|
+
server["dependencies"] ||= []
|
|
2189
|
+
server["dependencies"] << {
|
|
2190
|
+
"type" => "role",
|
|
2191
|
+
"name" => server["name"]
|
|
2192
|
+
}
|
|
2193
|
+
end
|
|
2194
|
+
if !server['create_image'].nil?
|
|
2195
|
+
if server['create_image'].has_key?('copy_to_regions') and
|
|
2196
|
+
(server['create_image']['copy_to_regions'].nil? or
|
|
2197
|
+
server['create_image']['copy_to_regions'].include?("#ALL") or
|
|
2198
|
+
server['create_image']['copy_to_regions'].size == 0
|
|
2199
|
+
)
|
|
2200
|
+
server['create_image']['copy_to_regions'] = MU::Cloud::AWS.listRegions(server['us_only'])
|
|
2201
|
+
end
|
|
2202
|
+
end
|
|
2203
|
+
|
|
2204
|
+
server['ami_id'] ||= server['image_id']
|
|
2205
|
+
|
|
2206
|
+
if server['ami_id'].nil?
|
|
2207
|
+
if MU::Config.amazon_images.has_key?(server['platform']) and
|
|
2208
|
+
MU::Config.amazon_images[server['platform']].has_key?(server['region'])
|
|
2209
|
+
server['ami_id'] = configurator.getTail("server"+server['name']+"AMI", value: MU::Config.amazon_images[server['platform']][server['region']], prettyname: "server"+server['name']+"AMI", cloudtype: "AWS::EC2::Image::Id")
|
|
2210
|
+
else
|
|
2211
|
+
MU.log "No AMI specified for #{server['name']} and no default available for platform #{server['platform']} in region #{server['region']}", MU::ERR, details: server
|
|
2212
|
+
ok = false
|
|
2213
|
+
end
|
|
2214
|
+
end
|
|
2215
|
+
|
|
2216
|
+
if !server["loadbalancers"].nil?
|
|
2217
|
+
server["loadbalancers"].each { |lb|
|
|
2218
|
+
if lb["concurrent_load_balancer"] != nil
|
|
2219
|
+
server["dependencies"] << {
|
|
2220
|
+
"type" => "loadbalancer",
|
|
2221
|
+
"name" => lb["concurrent_load_balancer"]
|
|
2222
|
+
}
|
|
2223
|
+
end
|
|
2224
|
+
}
|
|
2225
|
+
end
|
|
2226
|
+
|
|
2227
|
+
if !server["vpc"].nil?
|
|
2228
|
+
if server["vpc"]["subnet_name"].nil? and server["vpc"]["subnet_id"].nil? and server["vpc"]["subnet_pref"].nil?
|
|
2229
|
+
MU.log "A server VPC block must specify a target subnet", MU::ERR
|
|
2230
|
+
ok = false
|
|
2231
|
+
end
|
|
2232
|
+
end
|
|
2233
|
+
|
|
2234
|
+
ok
|
|
2235
|
+
end
|
|
2236
|
+
|
|
2237
|
+
private
|
|
2238
|
+
|
|
2239
|
+
# Destroy a volume.
|
|
2240
|
+
# @param volume [OpenStruct]: The cloud provider's description of the volume.
|
|
2241
|
+
# @param id [String]: The cloud provider's identifier for the volume, to use if the full description is not available.
|
|
2242
|
+
# @param region [String]: The cloud provider region
|
|
2243
|
+
# @return [void]
|
|
2244
|
+
def self.delete_volume(volume, noop, skipsnapshots, id: nil, region: MU.curRegion)
|
|
2245
|
+
if !volume.nil?
|
|
2246
|
+
resp = MU::Cloud::AWS.ec2(region).describe_volumes(volume_ids: [volume.volume_id])
|
|
2247
|
+
volume = resp.data.volumes.first
|
|
2248
|
+
end
|
|
2249
|
+
name = ""
|
|
2250
|
+
volume.tags.each { |tag|
|
|
2251
|
+
name = tag.value if tag.key == "Name"
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
MU.log("Deleting volume #{volume.volume_id} (#{name})")
|
|
2255
|
+
if !noop
|
|
2256
|
+
if !skipsnapshots
|
|
2257
|
+
if !name.nil? and !name.empty?
|
|
2258
|
+
desc = "#{MU.deploy_id}-MUfinal (#{name})"
|
|
2259
|
+
else
|
|
2260
|
+
desc = "#{MU.deploy_id}-MUfinal"
|
|
2261
|
+
end
|
|
2262
|
+
|
|
2263
|
+
MU::Cloud::AWS.ec2(region).create_snapshot(
|
|
2264
|
+
volume_id: volume.volume_id,
|
|
2265
|
+
description: desc
|
|
2266
|
+
)
|
|
2267
|
+
end
|
|
2268
|
+
|
|
2269
|
+
retries = 0
|
|
2270
|
+
begin
|
|
2271
|
+
MU::Cloud::AWS.ec2(region).delete_volume(volume_id: volume.volume_id)
|
|
2272
|
+
rescue Aws::EC2::Errors::InvalidVolumeNotFound
|
|
2273
|
+
MU.log "Volume #{volume.volume_id} (#{name}) disappeared before I could remove it!", MU::WARN
|
|
2274
|
+
rescue Aws::EC2::Errors::VolumeInUse
|
|
2275
|
+
if retries < 10
|
|
2276
|
+
volume.attachments.each { |attachment|
|
|
2277
|
+
MU.log "#{volume.volume_id} is attached to #{attachment.instance_id} as #{attachment.device}", MU::NOTICE
|
|
2278
|
+
}
|
|
2279
|
+
MU.log "Volume '#{name}' is still attached, waiting...", MU::NOTICE
|
|
2280
|
+
sleep 30
|
|
2281
|
+
retries = retries + 1
|
|
2282
|
+
retry
|
|
2283
|
+
else
|
|
2284
|
+
MU.log "Failed to delete #{name}", MU::ERR
|
|
2285
|
+
end
|
|
2286
|
+
end
|
|
2287
|
+
end
|
|
2288
|
+
end
|
|
2289
|
+
|
|
2290
|
+
|
|
2291
|
+
end #class
|
|
2292
|
+
end #class
|
|
2293
|
+
end
|
|
2294
|
+
end #module
|