cloud-mu 2.1.0beta → 3.0.0beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Berksfile +4 -5
- data/Berksfile.lock +179 -0
- data/README.md +1 -6
- data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +0 -0
- data/ansible/roles/mu-installer/README.md +33 -0
- data/ansible/roles/mu-installer/defaults/main.yml +2 -0
- data/ansible/roles/mu-installer/handlers/main.yml +2 -0
- data/ansible/roles/mu-installer/meta/main.yml +60 -0
- data/ansible/roles/mu-installer/tasks/main.yml +13 -0
- data/ansible/roles/mu-installer/tests/inventory +2 -0
- data/ansible/roles/mu-installer/tests/test.yml +5 -0
- data/ansible/roles/mu-installer/vars/main.yml +2 -0
- data/bin/mu-adopt +125 -0
- data/bin/mu-aws-setup +4 -4
- data/bin/mu-azure-setup +265 -0
- data/bin/mu-azure-tests +43 -0
- data/bin/mu-cleanup +20 -8
- data/bin/mu-configure +224 -98
- data/bin/mu-deploy +8 -3
- data/bin/mu-gcp-setup +16 -8
- data/bin/mu-gen-docs +92 -8
- data/bin/mu-load-config.rb +52 -12
- data/bin/mu-momma-cat +36 -0
- data/bin/mu-node-manage +34 -27
- data/bin/mu-self-update +2 -2
- data/bin/mu-ssh +12 -8
- data/bin/mu-upload-chef-artifacts +11 -4
- data/bin/mu-user-manage +3 -0
- data/cloud-mu.gemspec +8 -11
- data/cookbooks/firewall/libraries/helpers_iptables.rb +2 -2
- data/cookbooks/firewall/metadata.json +1 -1
- data/cookbooks/firewall/recipes/default.rb +5 -9
- data/cookbooks/mu-firewall/attributes/default.rb +2 -0
- data/cookbooks/mu-firewall/metadata.rb +1 -1
- data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +0 -0
- data/cookbooks/mu-master/Berksfile +2 -2
- data/cookbooks/mu-master/files/default/check_mem.pl +0 -0
- data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
- data/cookbooks/mu-master/metadata.rb +5 -4
- data/cookbooks/mu-master/recipes/389ds.rb +1 -1
- data/cookbooks/mu-master/recipes/basepackages.rb +30 -10
- data/cookbooks/mu-master/recipes/default.rb +59 -7
- data/cookbooks/mu-master/recipes/firewall-holes.rb +1 -1
- data/cookbooks/mu-master/recipes/init.rb +65 -47
- data/cookbooks/mu-master/recipes/{eks-kubectl.rb → kubectl.rb} +4 -10
- data/cookbooks/mu-master/recipes/sssd.rb +2 -1
- data/cookbooks/mu-master/recipes/update_nagios_only.rb +6 -6
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +2 -2
- data/cookbooks/mu-master/templates/mods/ldap.conf.erb +4 -0
- data/cookbooks/mu-php54/Berksfile +1 -2
- data/cookbooks/mu-php54/metadata.rb +4 -5
- data/cookbooks/mu-php54/recipes/default.rb +1 -1
- data/cookbooks/mu-splunk/templates/default/splunk-init.erb +0 -0
- data/cookbooks/mu-tools/Berksfile +3 -2
- data/cookbooks/mu-tools/files/default/Mu_CA.pem +33 -0
- data/cookbooks/mu-tools/libraries/helper.rb +20 -8
- data/cookbooks/mu-tools/metadata.rb +5 -2
- data/cookbooks/mu-tools/recipes/apply_security.rb +2 -3
- data/cookbooks/mu-tools/recipes/eks.rb +1 -1
- data/cookbooks/mu-tools/recipes/gcloud.rb +5 -30
- data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
- data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -0
- data/cookbooks/mu-tools/recipes/selinux.rb +19 -0
- data/cookbooks/mu-tools/recipes/split_var_partitions.rb +0 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +256 -122
- data/cookbooks/mu-tools/resources/disk.rb +3 -1
- data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +1 -1
- data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
- data/cookbooks/mu-tools/templates/default/{kubeconfig.erb → kubeconfig-eks.erb} +0 -0
- data/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb +27 -0
- data/cookbooks/mu-tools/templates/windows-10/sshd_config.erb +137 -0
- data/cookbooks/mu-utility/recipes/nat.rb +4 -0
- data/extras/alpha.png +0 -0
- data/extras/beta.png +0 -0
- data/extras/clean-stock-amis +2 -2
- data/extras/generate-stock-images +131 -0
- data/extras/git-fix-permissions-hook +0 -0
- data/extras/image-generators/AWS/centos6.yaml +17 -0
- data/extras/image-generators/{aws → AWS}/centos7-govcloud.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/centos7.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/rhel7.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/win2k12.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/win2k16.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/windows.yaml +0 -0
- data/extras/image-generators/{gcp → Google}/centos6.yaml +1 -0
- data/extras/image-generators/Google/centos7.yaml +18 -0
- data/extras/python_rpm/build.sh +0 -0
- data/extras/release.png +0 -0
- data/extras/ruby_rpm/build.sh +0 -0
- data/extras/ruby_rpm/muby.spec +1 -1
- data/install/README.md +43 -5
- data/install/deprecated-bash-library.sh +0 -0
- data/install/installer +1 -1
- data/install/jenkinskeys.rb +0 -0
- data/install/mu-master.yaml +55 -0
- data/modules/mommacat.ru +41 -7
- data/modules/mu.rb +444 -149
- data/modules/mu/adoption.rb +500 -0
- data/modules/mu/cleanup.rb +235 -158
- data/modules/mu/cloud.rb +675 -138
- data/modules/mu/clouds/aws.rb +156 -24
- data/modules/mu/clouds/aws/alarm.rb +4 -14
- data/modules/mu/clouds/aws/bucket.rb +60 -18
- data/modules/mu/clouds/aws/cache_cluster.rb +8 -20
- data/modules/mu/clouds/aws/collection.rb +12 -22
- data/modules/mu/clouds/aws/container_cluster.rb +209 -118
- data/modules/mu/clouds/aws/database.rb +120 -45
- data/modules/mu/clouds/aws/dnszone.rb +7 -18
- data/modules/mu/clouds/aws/endpoint.rb +5 -15
- data/modules/mu/clouds/aws/firewall_rule.rb +144 -72
- data/modules/mu/clouds/aws/folder.rb +4 -11
- data/modules/mu/clouds/aws/function.rb +6 -16
- data/modules/mu/clouds/aws/group.rb +4 -12
- data/modules/mu/clouds/aws/habitat.rb +11 -13
- data/modules/mu/clouds/aws/loadbalancer.rb +40 -28
- data/modules/mu/clouds/aws/log.rb +5 -13
- data/modules/mu/clouds/aws/msg_queue.rb +9 -24
- data/modules/mu/clouds/aws/nosqldb.rb +4 -12
- data/modules/mu/clouds/aws/notifier.rb +6 -13
- data/modules/mu/clouds/aws/role.rb +69 -40
- data/modules/mu/clouds/aws/search_domain.rb +17 -20
- data/modules/mu/clouds/aws/server.rb +184 -94
- data/modules/mu/clouds/aws/server_pool.rb +33 -38
- data/modules/mu/clouds/aws/storage_pool.rb +5 -12
- data/modules/mu/clouds/aws/user.rb +59 -33
- data/modules/mu/clouds/aws/userdata/linux.erb +18 -30
- data/modules/mu/clouds/aws/userdata/windows.erb +9 -9
- data/modules/mu/clouds/aws/vpc.rb +214 -145
- data/modules/mu/clouds/azure.rb +978 -44
- data/modules/mu/clouds/azure/container_cluster.rb +413 -0
- data/modules/mu/clouds/azure/firewall_rule.rb +500 -0
- data/modules/mu/clouds/azure/habitat.rb +167 -0
- data/modules/mu/clouds/azure/loadbalancer.rb +205 -0
- data/modules/mu/clouds/azure/role.rb +211 -0
- data/modules/mu/clouds/azure/server.rb +810 -0
- data/modules/mu/clouds/azure/user.rb +257 -0
- data/modules/mu/clouds/azure/userdata/README.md +4 -0
- data/modules/mu/clouds/azure/userdata/linux.erb +137 -0
- data/modules/mu/clouds/azure/userdata/windows.erb +275 -0
- data/modules/mu/clouds/azure/vpc.rb +782 -0
- data/modules/mu/clouds/cloudformation.rb +12 -9
- data/modules/mu/clouds/cloudformation/firewall_rule.rb +5 -13
- data/modules/mu/clouds/cloudformation/server.rb +10 -1
- data/modules/mu/clouds/cloudformation/server_pool.rb +1 -0
- data/modules/mu/clouds/cloudformation/vpc.rb +0 -2
- data/modules/mu/clouds/google.rb +554 -117
- data/modules/mu/clouds/google/bucket.rb +173 -32
- data/modules/mu/clouds/google/container_cluster.rb +1112 -157
- data/modules/mu/clouds/google/database.rb +24 -47
- data/modules/mu/clouds/google/firewall_rule.rb +344 -89
- data/modules/mu/clouds/google/folder.rb +156 -79
- data/modules/mu/clouds/google/group.rb +272 -82
- data/modules/mu/clouds/google/habitat.rb +177 -52
- data/modules/mu/clouds/google/loadbalancer.rb +9 -34
- data/modules/mu/clouds/google/role.rb +1211 -0
- data/modules/mu/clouds/google/server.rb +491 -227
- data/modules/mu/clouds/google/server_pool.rb +233 -48
- data/modules/mu/clouds/google/user.rb +479 -125
- data/modules/mu/clouds/google/userdata/linux.erb +3 -3
- data/modules/mu/clouds/google/userdata/windows.erb +9 -9
- data/modules/mu/clouds/google/vpc.rb +381 -223
- data/modules/mu/config.rb +689 -214
- data/modules/mu/config/bucket.rb +1 -1
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/cache_cluster.yml +0 -4
- data/modules/mu/config/container_cluster.rb +18 -9
- data/modules/mu/config/database.rb +6 -23
- data/modules/mu/config/firewall_rule.rb +9 -15
- data/modules/mu/config/folder.rb +22 -21
- data/modules/mu/config/habitat.rb +22 -21
- data/modules/mu/config/loadbalancer.rb +2 -2
- data/modules/mu/config/role.rb +9 -40
- data/modules/mu/config/server.rb +26 -5
- data/modules/mu/config/server_pool.rb +1 -1
- data/modules/mu/config/storage_pool.rb +2 -2
- data/modules/mu/config/user.rb +4 -0
- data/modules/mu/config/vpc.rb +350 -110
- data/modules/mu/defaults/{amazon_images.yaml → AWS.yaml} +37 -39
- data/modules/mu/defaults/Azure.yaml +17 -0
- data/modules/mu/defaults/Google.yaml +24 -0
- data/modules/mu/defaults/README.md +1 -1
- data/modules/mu/deploy.rb +168 -125
- data/modules/mu/groomer.rb +2 -1
- data/modules/mu/groomers/ansible.rb +104 -32
- data/modules/mu/groomers/chef.rb +96 -44
- data/modules/mu/kittens.rb +20602 -0
- data/modules/mu/logger.rb +38 -11
- data/modules/mu/master.rb +90 -8
- data/modules/mu/master/chef.rb +2 -3
- data/modules/mu/master/ldap.rb +0 -1
- data/modules/mu/master/ssl.rb +250 -0
- data/modules/mu/mommacat.rb +917 -513
- data/modules/scratchpad.erb +1 -1
- data/modules/tests/super_complex_bok.yml +0 -0
- data/modules/tests/super_simple_bok.yml +0 -0
- data/roles/mu-master.json +2 -1
- data/spec/azure_creds +5 -0
- data/spec/mu.yaml +56 -0
- data/spec/mu/clouds/azure_spec.rb +164 -27
- data/spec/spec_helper.rb +5 -0
- data/test/clean_up.py +0 -0
- data/test/exec_inspec.py +0 -0
- data/test/exec_mu_install.py +0 -0
- data/test/exec_retry.py +0 -0
- data/test/smoke_test.rb +0 -0
- metadata +90 -118
- data/cookbooks/mu-jenkins/Berksfile +0 -14
- data/cookbooks/mu-jenkins/CHANGELOG.md +0 -13
- data/cookbooks/mu-jenkins/LICENSE +0 -37
- data/cookbooks/mu-jenkins/README.md +0 -105
- data/cookbooks/mu-jenkins/attributes/default.rb +0 -42
- data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +0 -73
- data/cookbooks/mu-jenkins/files/default/deploy_config.xml +0 -44
- data/cookbooks/mu-jenkins/metadata.rb +0 -21
- data/cookbooks/mu-jenkins/recipes/default.rb +0 -195
- data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +0 -54
- data/cookbooks/mu-jenkins/recipes/public_key.rb +0 -24
- data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +0 -24
- data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +0 -14
- data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +0 -6
- data/cookbooks/nagios/Berksfile +0 -11
- data/cookbooks/nagios/CHANGELOG.md +0 -589
- data/cookbooks/nagios/CONTRIBUTING.md +0 -11
- data/cookbooks/nagios/LICENSE +0 -37
- data/cookbooks/nagios/README.md +0 -328
- data/cookbooks/nagios/TESTING.md +0 -2
- data/cookbooks/nagios/attributes/config.rb +0 -171
- data/cookbooks/nagios/attributes/default.rb +0 -228
- data/cookbooks/nagios/chefignore +0 -102
- data/cookbooks/nagios/definitions/command.rb +0 -33
- data/cookbooks/nagios/definitions/contact.rb +0 -33
- data/cookbooks/nagios/definitions/contactgroup.rb +0 -33
- data/cookbooks/nagios/definitions/host.rb +0 -33
- data/cookbooks/nagios/definitions/hostdependency.rb +0 -33
- data/cookbooks/nagios/definitions/hostescalation.rb +0 -34
- data/cookbooks/nagios/definitions/hostgroup.rb +0 -33
- data/cookbooks/nagios/definitions/nagios_conf.rb +0 -38
- data/cookbooks/nagios/definitions/resource.rb +0 -33
- data/cookbooks/nagios/definitions/service.rb +0 -33
- data/cookbooks/nagios/definitions/servicedependency.rb +0 -33
- data/cookbooks/nagios/definitions/serviceescalation.rb +0 -34
- data/cookbooks/nagios/definitions/servicegroup.rb +0 -33
- data/cookbooks/nagios/definitions/timeperiod.rb +0 -33
- data/cookbooks/nagios/libraries/base.rb +0 -314
- data/cookbooks/nagios/libraries/command.rb +0 -91
- data/cookbooks/nagios/libraries/contact.rb +0 -230
- data/cookbooks/nagios/libraries/contactgroup.rb +0 -112
- data/cookbooks/nagios/libraries/custom_option.rb +0 -36
- data/cookbooks/nagios/libraries/data_bag_helper.rb +0 -23
- data/cookbooks/nagios/libraries/default.rb +0 -90
- data/cookbooks/nagios/libraries/host.rb +0 -412
- data/cookbooks/nagios/libraries/hostdependency.rb +0 -181
- data/cookbooks/nagios/libraries/hostescalation.rb +0 -173
- data/cookbooks/nagios/libraries/hostgroup.rb +0 -119
- data/cookbooks/nagios/libraries/nagios.rb +0 -282
- data/cookbooks/nagios/libraries/resource.rb +0 -59
- data/cookbooks/nagios/libraries/service.rb +0 -455
- data/cookbooks/nagios/libraries/servicedependency.rb +0 -215
- data/cookbooks/nagios/libraries/serviceescalation.rb +0 -195
- data/cookbooks/nagios/libraries/servicegroup.rb +0 -144
- data/cookbooks/nagios/libraries/timeperiod.rb +0 -160
- data/cookbooks/nagios/libraries/users_helper.rb +0 -54
- data/cookbooks/nagios/metadata.rb +0 -25
- data/cookbooks/nagios/recipes/_load_databag_config.rb +0 -153
- data/cookbooks/nagios/recipes/_load_default_config.rb +0 -241
- data/cookbooks/nagios/recipes/apache.rb +0 -48
- data/cookbooks/nagios/recipes/default.rb +0 -204
- data/cookbooks/nagios/recipes/nginx.rb +0 -82
- data/cookbooks/nagios/recipes/pagerduty.rb +0 -143
- data/cookbooks/nagios/recipes/server_package.rb +0 -40
- data/cookbooks/nagios/recipes/server_source.rb +0 -164
- data/cookbooks/nagios/templates/default/apache2.conf.erb +0 -96
- data/cookbooks/nagios/templates/default/cgi.cfg.erb +0 -266
- data/cookbooks/nagios/templates/default/commands.cfg.erb +0 -13
- data/cookbooks/nagios/templates/default/contacts.cfg.erb +0 -37
- data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +0 -25
- data/cookbooks/nagios/templates/default/hosts.cfg.erb +0 -15
- data/cookbooks/nagios/templates/default/htpasswd.users.erb +0 -6
- data/cookbooks/nagios/templates/default/nagios.cfg.erb +0 -22
- data/cookbooks/nagios/templates/default/nginx.conf.erb +0 -62
- data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +0 -185
- data/cookbooks/nagios/templates/default/resource.cfg.erb +0 -27
- data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +0 -15
- data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +0 -14
- data/cookbooks/nagios/templates/default/services.cfg.erb +0 -14
- data/cookbooks/nagios/templates/default/templates.cfg.erb +0 -31
- data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +0 -13
- data/extras/image-generators/aws/centos6.yaml +0 -18
- data/modules/mu/defaults/google_images.yaml +0 -16
- data/roles/mu-master-jenkins.json +0 -24
@@ -28,27 +28,19 @@ module MU
|
|
28
28
|
# Google Cloud, this amounts to a single Instance in an Unmanaged
|
29
29
|
# Instance Group.
|
30
30
|
class Server < MU::Cloud::Server
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
# @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
|
42
|
-
# @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::servers}
|
43
|
-
def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
|
44
|
-
@deploy = mommacat
|
45
|
-
@config = MU::Config.manxify(kitten_cfg)
|
46
|
-
@cloud_id = cloud_id
|
47
|
-
|
48
|
-
if @deploy
|
49
|
-
@userdata = MU::Cloud.fetchUserdata(
|
31
|
+
|
32
|
+
# Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like <tt>@vpc</tt>, for us.
|
33
|
+
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
34
|
+
def initialize(**args)
|
35
|
+
super
|
36
|
+
|
37
|
+
@userdata = if @config['userdata_script']
|
38
|
+
@config['userdata_script']
|
39
|
+
elsif @deploy and !@config['scrub_mu_isms']
|
40
|
+
MU::Cloud.fetchUserdata(
|
50
41
|
platform: @config["platform"],
|
51
|
-
cloud: "
|
42
|
+
cloud: "Google",
|
43
|
+
credentials: @config['credentials'],
|
52
44
|
template_variables: {
|
53
45
|
"deployKey" => Base64.urlsafe_encode64(@deploy.public_key),
|
54
46
|
"deploySSHKey" => @deploy.ssh_public_key,
|
@@ -57,6 +49,9 @@ module MU
|
|
57
49
|
"publicIP" => MU.mu_public_ip,
|
58
50
|
"skipApplyUpdates" => @config['skipinitialupdates'],
|
59
51
|
"windowsAdminName" => @config['windows_admin_username'],
|
52
|
+
"adminBucketName" => MU::Cloud::Google.adminBucketName(@credentials),
|
53
|
+
"chefVersion" => MU.chefVersion,
|
54
|
+
"mommaCatPort" => MU.mommaCatPort,
|
60
55
|
"resourceName" => @config["name"],
|
61
56
|
"resourceType" => "server",
|
62
57
|
"platform" => @config["platform"]
|
@@ -64,17 +59,11 @@ module MU
|
|
64
59
|
custom_append: @config['userdata_script']
|
65
60
|
)
|
66
61
|
end
|
67
|
-
|
68
|
-
if
|
69
|
-
@mu_name = mu_name
|
70
|
-
@config['mu_name'] = @mu_name
|
62
|
+
# XXX writing things into @config at runtime is a bad habit and we should stop
|
63
|
+
if !@mu_name.nil?
|
64
|
+
@config['mu_name'] = @mu_name # XXX whyyyy
|
71
65
|
# describe
|
72
66
|
@mu_windows_name = @deploydata['mu_windows_name'] if @mu_windows_name.nil? and @deploydata
|
73
|
-
@config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
|
74
|
-
if !@project_id
|
75
|
-
project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
|
76
|
-
@project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
|
77
|
-
end
|
78
67
|
else
|
79
68
|
if kitten_cfg.has_key?("basis")
|
80
69
|
@mu_name = @deploy.getResourceName(@config['name'], need_unique_string: true)
|
@@ -83,60 +72,88 @@ module MU
|
|
83
72
|
end
|
84
73
|
@config['mu_name'] = @mu_name
|
85
74
|
|
86
|
-
@config['instance_secret'] = Password.random(50)
|
87
75
|
end
|
88
|
-
@config['
|
89
|
-
@
|
76
|
+
@config['instance_secret'] ||= Password.random(50)
|
77
|
+
@config['ssh_user'] ||= "muadmin"
|
90
78
|
|
91
79
|
end
|
92
80
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# @param
|
96
|
-
# @
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
saobj = MU::Cloud::Google.iam(:CreateServiceAccountRequest).new(
|
106
|
-
account_id: name.gsub(/[^a-z]/, ""), # XXX this mangling isn't required in the console, so why is it here?
|
107
|
-
service_account: MU::Cloud::Google.iam(:ServiceAccount).new(
|
108
|
-
display_name: rolename,
|
109
|
-
# do NOT specify project_id or name, we know that much
|
110
|
-
)
|
111
|
-
)
|
112
|
-
|
113
|
-
resp = MU::Cloud::Google.iam(credentials: credentials).create_service_account(
|
114
|
-
"projects/#{project}",
|
115
|
-
saobj
|
116
|
-
)
|
81
|
+
# Return the date/time a machine image was created.
|
82
|
+
# @param image_id [String]: URL to a Google disk image
|
83
|
+
# @param credentials [String]
|
84
|
+
# @return [DateTime]
|
85
|
+
def self.imageTimeStamp(image_id, credentials: nil)
|
86
|
+
begin
|
87
|
+
img = fetchImage(image_id, credentials: credentials)
|
88
|
+
return DateTime.new if img.nil?
|
89
|
+
return DateTime.parse(img.creation_timestamp)
|
90
|
+
rescue ::Google::Apis::ClientError => e
|
91
|
+
end
|
117
92
|
|
118
|
-
|
119
|
-
email: resp.email,
|
120
|
-
scopes: scopes
|
121
|
-
)
|
93
|
+
return DateTime.new
|
122
94
|
end
|
123
95
|
|
96
|
+
@@image_id_map = {}
|
97
|
+
|
124
98
|
# Retrieve the cloud descriptor for this machine image, which can be
|
125
99
|
# a whole or partial URL. Will follow deprecation notices and retrieve
|
126
100
|
# the latest version, if applicable.
|
127
101
|
# @param image_id [String]: URL to a Google disk image
|
128
|
-
# @
|
102
|
+
# @param credentials [String]
|
103
|
+
# @return [Google::Apis::ComputeV1::Image]
|
129
104
|
def self.fetchImage(image_id, credentials: nil)
|
105
|
+
return @@image_id_map[image_id] if @@image_id_map[image_id]
|
106
|
+
|
130
107
|
img_proj = img_name = nil
|
131
|
-
|
132
|
-
img_proj = image_id.gsub(
|
108
|
+
if image_id.match(/\//)
|
109
|
+
img_proj = image_id.gsub(/(?:https?:\/\/.*?\.googleapis\.com\/compute\/.*?\/)?.*?\/?(?:projects\/)?([^\/]+)\/.*/, '\1')
|
133
110
|
img_name = image_id.gsub(/.*?([^\/]+)$/, '\1')
|
111
|
+
else
|
112
|
+
img_name = image_id
|
113
|
+
end
|
114
|
+
|
115
|
+
begin
|
116
|
+
@@image_id_map[image_id] = MU::Cloud::Google.compute(credentials: credentials).get_image_from_family(img_proj, img_name)
|
117
|
+
return @@image_id_map[image_id]
|
118
|
+
rescue ::Google::Apis::ClientError
|
119
|
+
# This is fine- we don't know that what we asked for is really an
|
120
|
+
# image family name, instead of just an image.
|
121
|
+
end
|
122
|
+
|
123
|
+
begin
|
134
124
|
img = MU::Cloud::Google.compute(credentials: credentials).get_image(img_proj, img_name)
|
135
125
|
if !img.deprecated.nil? and !img.deprecated.replacement.nil?
|
136
126
|
image_id = img.deprecated.replacement
|
127
|
+
img_proj = image_id.gsub(/(?:https?:\/\/.*?\.googleapis\.com\/compute\/.*?\/)?.*?\/?(?:projects\/)?([^\/]+)\/.*/, '\1')
|
128
|
+
img_name = image_id.gsub(/.*?([^\/]+)$/, '\1')
|
137
129
|
end
|
130
|
+
rescue ::Google::Apis::ClientError => e
|
131
|
+
# SOME people *cough* don't use deprecation or image family names
|
132
|
+
# and just spew out images with a version appended to the name, so
|
133
|
+
# let's try some crude semantic versioning list.
|
134
|
+
if e.message.match(/^notFound: /) and img_name.match(/-[^\-]+$/)
|
135
|
+
list = MU::Cloud::Google.compute(credentials: credentials).list_images(img_proj, filter: "name eq #{img_name.sub(/-[^\-]+$/, '')}-.*")
|
136
|
+
if list and list.items
|
137
|
+
latest = nil
|
138
|
+
list.items.each { |candidate|
|
139
|
+
created = DateTime.parse(candidate.creation_timestamp)
|
140
|
+
if latest.nil? or created > latest
|
141
|
+
latest = created
|
142
|
+
img = candidate
|
143
|
+
end
|
144
|
+
}
|
145
|
+
if latest
|
146
|
+
MU.log "Mapped #{image_id} to #{img.name} with semantic versioning guesswork", MU::WARN
|
147
|
+
@@image_id_map[image_id] = img
|
148
|
+
return @@image_id_map[image_id]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
raise e # if our little semantic versioning party trick failed
|
138
153
|
end while !img.deprecated.nil? and img.deprecated.state == "DEPRECATED" and !img.deprecated.replacement.nil?
|
139
|
-
MU::Cloud::Google.compute(credentials: credentials).get_image(img_proj, img_name)
|
154
|
+
final = MU::Cloud::Google.compute(credentials: credentials).get_image(img_proj, img_name)
|
155
|
+
@@image_id_map[image_id] = final
|
156
|
+
@@image_id_map[image_id]
|
140
157
|
end
|
141
158
|
|
142
159
|
# Generator for disk configuration parameters for a Compute instance
|
@@ -230,7 +247,7 @@ next if !create
|
|
230
247
|
end
|
231
248
|
subnet = vpc.getSubnet(name: subnet_cfg['subnet_name'], cloud_id: subnet_cfg['subnet_id'])
|
232
249
|
if subnet.nil?
|
233
|
-
raise MuError, "Couldn't find subnet details while configuring Server #{config['name']} (VPC: #{vpc.mu_name})"
|
250
|
+
raise MuError, "Couldn't find subnet details for #{subnet_cfg['subnet_name'] || subnet_cfg['subnet_id']} while configuring Server #{config['name']} (VPC: #{vpc.mu_name})"
|
234
251
|
end
|
235
252
|
base_iface_obj = {
|
236
253
|
:network => vpc.url,
|
@@ -249,15 +266,21 @@ next if !create
|
|
249
266
|
|
250
267
|
# Called automatically by {MU::Deploy#createResources}
|
251
268
|
def create
|
252
|
-
@project_id = MU::Cloud::Google.projectLookup(@config['
|
269
|
+
@project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloud_id
|
270
|
+
|
271
|
+
sa = MU::Config::Ref.get(@config['service_account'])
|
253
272
|
|
254
|
-
|
255
|
-
@
|
256
|
-
|
257
|
-
|
258
|
-
|
273
|
+
if !sa or !sa.kitten or !sa.kitten.cloud_desc
|
274
|
+
raise MuError, "Failed to get service account cloud id from #{@config['service_account'].to_s}"
|
275
|
+
end
|
276
|
+
|
277
|
+
@service_acct = MU::Cloud::Google.compute(:ServiceAccount).new(
|
278
|
+
email: sa.kitten.cloud_desc.email,
|
279
|
+
scopes: @config['scopes']
|
259
280
|
)
|
260
|
-
|
281
|
+
if !@config['scrub_mu_isms']
|
282
|
+
MU::Cloud::Google.grantDeploySecretAccess(@service_acct.email, credentials: @config['credentials'])
|
283
|
+
end
|
261
284
|
|
262
285
|
begin
|
263
286
|
disks = MU::Cloud::Google::Server.diskConfig(@config, credentials: @config['credentials'])
|
@@ -273,25 +296,36 @@ next if !create
|
|
273
296
|
:name => MU::Cloud::Google.nameStr(@mu_name),
|
274
297
|
:can_ip_forward => !@config['src_dst_check'],
|
275
298
|
:description => @deploy.deploy_id,
|
276
|
-
:service_accounts => [service_acct],
|
299
|
+
:service_accounts => [@service_acct],
|
277
300
|
:network_interfaces => interfaces,
|
278
301
|
:machine_type => "zones/"+@config['availability_zone']+"/machineTypes/"+@config['size'],
|
279
|
-
:metadata => {
|
280
|
-
:items => [
|
281
|
-
{
|
282
|
-
:key => "ssh-keys",
|
283
|
-
:value => @config['ssh_user']+":"+@deploy.ssh_public_key
|
284
|
-
},
|
285
|
-
{
|
286
|
-
:key => "startup-script",
|
287
|
-
:value => @userdata
|
288
|
-
}
|
289
|
-
]
|
290
|
-
},
|
291
302
|
:tags => MU::Cloud::Google.compute(:Tags).new(items: [MU::Cloud::Google.nameStr(@mu_name)])
|
292
303
|
}
|
293
304
|
desc[:disks] = disks if disks.size > 0
|
294
305
|
|
306
|
+
metadata = {}
|
307
|
+
if @config['metadata']
|
308
|
+
metadata = Hash[@config['metadata'].map { |m|
|
309
|
+
[m["key"], m["value"]]
|
310
|
+
}]
|
311
|
+
end
|
312
|
+
metadata["startup-script"] = @userdata if @userdata and !@userdata.empty?
|
313
|
+
|
314
|
+
deploykey = @config['ssh_user']+":"+@deploy.ssh_public_key
|
315
|
+
if metadata["ssh-keys"]
|
316
|
+
metadata["ssh-keys"] += "\n"+deploykey
|
317
|
+
else
|
318
|
+
metadata["ssh-keys"] = deploykey
|
319
|
+
end
|
320
|
+
desc[:metadata] = MU::Cloud::Google.compute(:Metadata).new(
|
321
|
+
:items => metadata.keys.map { |k|
|
322
|
+
MU::Cloud::Google.compute(:Metadata)::Item.new(
|
323
|
+
key: k,
|
324
|
+
value: metadata[k]
|
325
|
+
)
|
326
|
+
}
|
327
|
+
)
|
328
|
+
|
295
329
|
# Tags in GCP means something other than what we think of;
|
296
330
|
# labels are the thing you think you mean
|
297
331
|
desc[:labels] = {}
|
@@ -302,10 +336,16 @@ next if !create
|
|
302
336
|
}
|
303
337
|
desc[:labels]["name"] = @mu_name.downcase
|
304
338
|
|
339
|
+
if @config['network_tags'] and @config['network_tags'].size > 0
|
340
|
+
desc[:tags] = U::Cloud::Google.compute(:Tags).new(
|
341
|
+
items: @config['network_tags']
|
342
|
+
)
|
343
|
+
end
|
305
344
|
|
306
345
|
instanceobj = MU::Cloud::Google.compute(:Instance).new(desc)
|
307
346
|
|
308
|
-
MU.log "Creating instance #{@mu_name}"
|
347
|
+
MU.log "Creating instance #{@mu_name}", MU::NOTICE, details: instanceobj
|
348
|
+
|
309
349
|
begin
|
310
350
|
instance = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_instance(
|
311
351
|
@project_id,
|
@@ -351,7 +391,7 @@ next if !create
|
|
351
391
|
parent_thread_id = Thread.current.object_id
|
352
392
|
Thread.new {
|
353
393
|
MU.dupGlobals(parent_thread_id)
|
354
|
-
MU::Cloud::Google::Server.cleanup(noop: false, ignoremaster: false, flags: { "skipsnapshots" => true } )
|
394
|
+
MU::Cloud::Google::Server.cleanup(noop: false, ignoremaster: false, flags: { "skipsnapshots" => true }, region: @config['region'] )
|
355
395
|
}
|
356
396
|
end
|
357
397
|
end
|
@@ -467,7 +507,7 @@ next if !create
|
|
467
507
|
return false if !MU::MommaCat.lock(@cloud_id+"-orchestrate", true)
|
468
508
|
return false if !MU::MommaCat.lock(@cloud_id+"-groom", true)
|
469
509
|
|
470
|
-
# MU::
|
510
|
+
# MU::Cloud::AWS.createStandardTags(@cloud_id, region: @config['region'])
|
471
511
|
# MU::MommaCat.createTag(@cloud_id, "Name", node, region: @config['region'])
|
472
512
|
#
|
473
513
|
# if @config['optional_tags']
|
@@ -586,80 +626,82 @@ next if !create
|
|
586
626
|
end #postBoot
|
587
627
|
|
588
628
|
# Locate an existing instance or instances and return an array containing matching AWS resource descriptors for those that match.
|
589
|
-
# @param cloud_id [String]: The cloud provider's identifier for this resource.
|
590
|
-
# @param region [String]: The cloud provider region
|
591
|
-
# @param tag_key [String]: A tag key to search.
|
592
|
-
# @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
|
593
|
-
# @param ip [String]: An IP address associated with the instance
|
594
|
-
# @param flags [Hash]: Optional flags
|
595
629
|
# @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching instances
|
596
|
-
def self.find(
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
regions = [region]
|
630
|
+
def self.find(**args)
|
631
|
+
args[:project] ||= args[:habitat]
|
632
|
+
args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
|
633
|
+
if !args[:region].nil? and MU::Cloud::Google.listRegions.include?(args[:region])
|
634
|
+
regions = [args[:region]]
|
602
635
|
else
|
603
636
|
regions = MU::Cloud::Google.listRegions
|
604
637
|
end
|
605
638
|
|
606
|
-
|
639
|
+
found = {}
|
607
640
|
search_semaphore = Mutex.new
|
608
641
|
search_threads = []
|
609
642
|
|
610
643
|
# If we got an instance id, go get it
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
flags["project"],
|
644
|
+
parent_thread_id = Thread.current.object_id
|
645
|
+
regions.each { |r|
|
646
|
+
search_threads << Thread.new(r) { |region|
|
647
|
+
Thread.abort_on_exception = false
|
648
|
+
MU.dupGlobals(parent_thread_id)
|
649
|
+
MU.log "Hunting for instance with cloud id '#{args[:cloud_id]}' in #{region}", MU::DEBUG
|
650
|
+
MU::Cloud::Google.listAZs(region).each { |az|
|
651
|
+
begin
|
652
|
+
if !args[:cloud_id].nil? and !args[:cloud_id].empty?
|
653
|
+
resp = MU::Cloud::Google.compute(credentials: args[:credentials]).get_instance(
|
654
|
+
args[:project],
|
623
655
|
az,
|
624
|
-
cloud_id
|
656
|
+
args[:cloud_id]
|
657
|
+
)
|
658
|
+
search_semaphore.synchronize {
|
659
|
+
found[args[:cloud_id]] = resp if !resp.nil?
|
660
|
+
}
|
661
|
+
else
|
662
|
+
resp = MU::Cloud::Google.compute(credentials: args[:credentials]).list_instances(
|
663
|
+
args[:project],
|
664
|
+
az
|
625
665
|
)
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
666
|
+
if resp and resp.items
|
667
|
+
resp.items.each { |instance|
|
668
|
+
search_semaphore.synchronize {
|
669
|
+
found[instance.name] = instance
|
670
|
+
}
|
671
|
+
}
|
672
|
+
end
|
630
673
|
end
|
631
|
-
|
632
|
-
|
674
|
+
rescue ::OpenSSL::SSL::SSLError => e
|
675
|
+
MU.log "Got #{e.message} looking for instance #{args[:cloud_id]} in project #{args[:project]} (#{az}). Usually this means we've tried to query a non-functional region.", MU::DEBUG
|
676
|
+
rescue ::Google::Apis::ClientError => e
|
677
|
+
raise e if !e.message.match(/^(?:notFound|forbidden): /)
|
678
|
+
end
|
633
679
|
}
|
634
680
|
}
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
if found_instances.size > 0
|
645
|
-
return found_instances
|
646
|
-
end
|
647
|
-
|
681
|
+
}
|
682
|
+
done_threads = []
|
683
|
+
begin
|
684
|
+
search_threads.reject! { |t| t.nil? }
|
685
|
+
search_threads.each { |t|
|
686
|
+
joined = t.join(2)
|
687
|
+
done_threads << joined if !joined.nil?
|
688
|
+
}
|
689
|
+
end while found.size < 1 and done_threads.size != search_threads.size
|
648
690
|
# Ok, well, let's try looking it up by IP then
|
649
|
-
if instance.nil? and !ip.nil?
|
650
|
-
MU.log "Hunting for instance by IP '#{ip}'", MU::DEBUG
|
651
|
-
end
|
691
|
+
# if instance.nil? and !args[:ip].nil?
|
692
|
+
# MU.log "Hunting for instance by IP '#{args[:ip]}'", MU::DEBUG
|
693
|
+
# end
|
652
694
|
|
653
|
-
if !instance.nil?
|
654
|
-
return {instance.name => instance} if !instance.nil?
|
655
|
-
end
|
695
|
+
# if !instance.nil?
|
696
|
+
# return {instance.name => instance} if !instance.nil?
|
697
|
+
# end
|
656
698
|
|
657
699
|
# Fine, let's try it by tag.
|
658
|
-
if !tag_value.nil?
|
659
|
-
MU.log "Searching for instance by tag '#{tag_key}=#{tag_value}'", MU::DEBUG
|
660
|
-
end
|
700
|
+
# if !args[:tag_value].nil?
|
701
|
+
# MU.log "Searching for instance by tag '#{args[:tag_key]}=#{args[:tag_value]}'", MU::DEBUG
|
702
|
+
# end
|
661
703
|
|
662
|
-
return
|
704
|
+
return found
|
663
705
|
end
|
664
706
|
|
665
707
|
# Return a description of this resource appropriate for deployment
|
@@ -724,7 +766,7 @@ next if !create
|
|
724
766
|
|
725
767
|
# Called automatically by {MU::Deploy#createResources}
|
726
768
|
def groom
|
727
|
-
@project_id = MU::Cloud::Google.projectLookup(@config['
|
769
|
+
@project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloud_id
|
728
770
|
|
729
771
|
MU::MommaCat.lock(@cloud_id+"-groom")
|
730
772
|
|
@@ -801,12 +843,12 @@ next if !create
|
|
801
843
|
instance_id: @cloud_id,
|
802
844
|
region: @config['region'],
|
803
845
|
storage: @config['storage'],
|
804
|
-
family: ("mu-"+@config['platform']+"-"+MU.environment).downcase,
|
805
846
|
project: @project_id,
|
806
847
|
exclude_storage: img_cfg['image_exclude_storage'],
|
807
848
|
make_public: img_cfg['public'],
|
808
849
|
tags: @config['tags'],
|
809
850
|
zone: @config['availability_zone'],
|
851
|
+
family: @config['family'],
|
810
852
|
credentials: @config['credentials']
|
811
853
|
)
|
812
854
|
@deploy.notify("images", @config['name'], {"image_id" => image_id})
|
@@ -835,7 +877,7 @@ next if !create
|
|
835
877
|
# @param region [String]: The cloud provider region
|
836
878
|
# @param tags [Array<String>]: Extra/override tags to apply to the image.
|
837
879
|
# @return [String]: The cloud provider identifier of the new machine image.
|
838
|
-
def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, project: nil, make_public: false, tags: [], region: nil, family:
|
880
|
+
def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, project: nil, make_public: false, tags: [], region: nil, family: nil, zone: MU::Cloud::Google.listAZs.sample, credentials: nil)
|
839
881
|
project ||= MU::Cloud::Google.defaultProject(credentials)
|
840
882
|
instance = MU::Cloud::Server.find(cloud_id: instance_id, region: region)
|
841
883
|
if instance.nil?
|
@@ -891,46 +933,21 @@ next if !create
|
|
891
933
|
end
|
892
934
|
|
893
935
|
labels["name"] = instance_id.downcase
|
894
|
-
|
895
|
-
name
|
896
|
-
source_disk
|
897
|
-
description
|
898
|
-
labels
|
899
|
-
|
900
|
-
|
936
|
+
image_desc = {
|
937
|
+
:name => name,
|
938
|
+
:source_disk => bootdisk,
|
939
|
+
:description => "Mu image created from #{name}",
|
940
|
+
:labels => labels
|
941
|
+
}
|
942
|
+
image_desc[:family] = family if family
|
901
943
|
|
902
944
|
newimage = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_image(
|
903
945
|
project,
|
904
|
-
|
946
|
+
MU::Cloud::Google.compute(:Image).new(image_desc)
|
905
947
|
)
|
906
948
|
newimage.name
|
907
949
|
end
|
908
950
|
|
909
|
-
# def cloud_desc
|
910
|
-
# max_retries = 5
|
911
|
-
# retries = 0
|
912
|
-
# if !@cloud_id.nil?
|
913
|
-
# begin
|
914
|
-
# return MU::Cloud::Google.compute(credentials: @config['credentials']).get_instance(
|
915
|
-
# @project_id,
|
916
|
-
# @config['availability_zone'],
|
917
|
-
# @cloud_id
|
918
|
-
# )
|
919
|
-
# rescue ::Google::Apis::ClientError => e
|
920
|
-
# if e.message.match(/^notFound: /)
|
921
|
-
# return nil
|
922
|
-
# else
|
923
|
-
# raise e
|
924
|
-
# end
|
925
|
-
# end
|
926
|
-
# end
|
927
|
-
# nil
|
928
|
-
# end
|
929
|
-
|
930
|
-
def cloud_desc
|
931
|
-
MU::Cloud::Google::Server.find(cloud_id: @cloud_id, credentials: @config['credentials']).values.first
|
932
|
-
end
|
933
|
-
|
934
951
|
# Return the IP address that we, the Mu server, should be using to access
|
935
952
|
# this host via the network. Note that this does not factor in SSH
|
936
953
|
# bastion hosts that may be in the path, see getSSHConfig if that's what
|
@@ -974,7 +991,8 @@ next if !create
|
|
974
991
|
# @param dev [String]: Device name to use when attaching to instance
|
975
992
|
# @param size [String]: Size (in gb) of the new volume
|
976
993
|
# @param type [String]: Cloud storage type of the volume, if applicable
|
977
|
-
|
994
|
+
# @param delete_on_termination [Boolean]: Value of delete_on_termination flag to set
|
995
|
+
def addVolume(dev, size, type: "pd-standard", delete_on_termination: false)
|
978
996
|
devname = dev.gsub(/.*?\/([^\/]+)$/, '\1')
|
979
997
|
resname = MU::Cloud::Google.nameStr(@mu_name+"-"+devname)
|
980
998
|
MU.log "Creating disk #{resname}"
|
@@ -1007,11 +1025,13 @@ next if !create
|
|
1007
1025
|
end
|
1008
1026
|
|
1009
1027
|
attachobj = MU::Cloud::Google.compute(:AttachedDisk).new(
|
1010
|
-
auto_delete: true,
|
1011
1028
|
device_name: devname,
|
1012
1029
|
source: newdisk.self_link,
|
1013
|
-
type: "PERSISTENT"
|
1030
|
+
type: "PERSISTENT",
|
1031
|
+
auto_delete: delete_on_termination
|
1014
1032
|
)
|
1033
|
+
|
1034
|
+
MU.log "Attaching disk #{resname} to #{@cloud_id} at #{devname}"
|
1015
1035
|
attachment = MU::Cloud::Google.compute(credentials: @config['credentials']).attach_disk(
|
1016
1036
|
@project_id,
|
1017
1037
|
@config['availability_zone'],
|
@@ -1028,6 +1048,113 @@ next if !create
|
|
1028
1048
|
true
|
1029
1049
|
end
|
1030
1050
|
|
1051
|
+
# Reverse-map our cloud description into a runnable config hash.
|
1052
|
+
# We assume that any values we have in +@config+ are placeholders, and
|
1053
|
+
# calculate our own accordingly based on what's live in the cloud.
|
1054
|
+
def toKitten(rootparent: nil, billing: nil, habitats: nil)
|
1055
|
+
bok = {
|
1056
|
+
"cloud" => "Google",
|
1057
|
+
"credentials" => @config['credentials'],
|
1058
|
+
"cloud_id" => @cloud_id,
|
1059
|
+
"project" => @project_id
|
1060
|
+
}
|
1061
|
+
if !cloud_desc
|
1062
|
+
MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
|
1063
|
+
return nil
|
1064
|
+
end
|
1065
|
+
bok['name'] = cloud_desc.name
|
1066
|
+
|
1067
|
+
# XXX we can have multiple network interfaces, and often do; need
|
1068
|
+
# language to account for this
|
1069
|
+
iface = cloud_desc.network_interfaces.first
|
1070
|
+
iface.network.match(/(?:^|\/)projects\/(.*?)\/.*?\/networks\/([^\/]+)(?:$|\/)/)
|
1071
|
+
vpc_proj = Regexp.last_match[1]
|
1072
|
+
vpc_id = Regexp.last_match[2]
|
1073
|
+
|
1074
|
+
bok['vpc'] = MU::Config::Ref.get(
|
1075
|
+
id: vpc_id,
|
1076
|
+
cloud: "Google",
|
1077
|
+
habitat: MU::Config::Ref.get(
|
1078
|
+
id: vpc_proj,
|
1079
|
+
cloud: "Google",
|
1080
|
+
credentials: @credentials,
|
1081
|
+
type: "habitats"
|
1082
|
+
),
|
1083
|
+
credentials: @credentials,
|
1084
|
+
type: "vpcs",
|
1085
|
+
subnet_id: iface.subnetwork.sub(/.*?\/([^\/]+)$/, '\1')
|
1086
|
+
)
|
1087
|
+
|
1088
|
+
cloud_desc.disks.each { |disk|
|
1089
|
+
next if !disk.source
|
1090
|
+
disk.source.match(/\/projects\/([^\/]+)\/zones\/([^\/]+)\/disks\/(.*)/)
|
1091
|
+
proj = Regexp.last_match[1]
|
1092
|
+
az = Regexp.last_match[2]
|
1093
|
+
name = Regexp.last_match[3]
|
1094
|
+
begin
|
1095
|
+
disk_desc = MU::Cloud::Google.compute(credentials: @credentials).get_disk(proj, az, name)
|
1096
|
+
if disk_desc.source_image and disk.boot
|
1097
|
+
bok['image_id'] ||= disk_desc.source_image.sub(/^https:\/\/www\.googleapis\.com\/compute\/[^\/]+\//, '')
|
1098
|
+
else
|
1099
|
+
bok['storage'] ||= []
|
1100
|
+
storage_blob = {
|
1101
|
+
"size" => disk_desc.size_gb,
|
1102
|
+
"device" => "/dev/xvd"+(disk.index+97).chr.downcase
|
1103
|
+
}
|
1104
|
+
bok['storage'] << storage_blob
|
1105
|
+
end
|
1106
|
+
rescue ::Google::Apis::ClientError => e
|
1107
|
+
MU.log "Failed to retrieve disk #{name} attached to server #{@cloud_id} in #{proj}/#{az}", MU::WARN, details: e.message
|
1108
|
+
next
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
}
|
1112
|
+
|
1113
|
+
if cloud_desc.labels
|
1114
|
+
bok['tags'] = cloud_desc.labels.keys.map { |k| { "key" => k, "value" => cloud_desc.labels[k] } }
|
1115
|
+
end
|
1116
|
+
if cloud_desc.tags and cloud_desc.tags.items and cloud_desc.tags.items.size > 0
|
1117
|
+
bok['network_tags'] = cloud_desc.tags.items
|
1118
|
+
end
|
1119
|
+
bok['src_dst_check'] = !cloud_desc.can_ip_forward
|
1120
|
+
bok['size'] = cloud_desc.machine_type.sub(/.*?\/([^\/]+)$/, '\1')
|
1121
|
+
bok['project'] = @project_id
|
1122
|
+
if cloud_desc.service_accounts
|
1123
|
+
bok['scopes'] = cloud_desc.service_accounts.map { |sa| sa.scopes }.flatten.uniq
|
1124
|
+
end
|
1125
|
+
if cloud_desc.metadata and cloud_desc.metadata.items
|
1126
|
+
bok['metadata'] = cloud_desc.metadata.items.map { |m| MU.structToHash(m) }
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
# Skip nodes that are just members of GKE clusters
|
1130
|
+
if bok['name'].match(/^gke-.*?-[a-f0-9]+-[a-z0-9]+$/) and
|
1131
|
+
bok['image_id'].match(/(:?^|\/)projects\/gke-node-images\//)
|
1132
|
+
found_gke_tag = false
|
1133
|
+
bok['network_tags'].each { |tag|
|
1134
|
+
if tag.match(/^gke-/)
|
1135
|
+
found_gke_tag = true
|
1136
|
+
break
|
1137
|
+
end
|
1138
|
+
}
|
1139
|
+
if found_gke_tag
|
1140
|
+
MU.log "Server #{bok['name']} appears to belong to a ContainerCluster, skipping adoption", MU::DEBUG
|
1141
|
+
return nil
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
if bok['metadata']
|
1146
|
+
bok['metadata'].each { |item|
|
1147
|
+
if item[:key] == "created-by" and item[:value].match(/\/instanceGroupManagers\//)
|
1148
|
+
MU.log "Server #{bok['name']} appears to belong to a ServerPool, skipping adoption", MU::DEBUG, details: item[:value]
|
1149
|
+
return nil
|
1150
|
+
end
|
1151
|
+
}
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
|
1155
|
+
bok
|
1156
|
+
end
|
1157
|
+
|
1031
1158
|
# Does this resource type exist as a global (cloud-wide) artifact, or
|
1032
1159
|
# is it localized to a region/zone?
|
1033
1160
|
# @return [Boolean]
|
@@ -1048,6 +1175,7 @@ next if !create
|
|
1048
1175
|
# @return [void]
|
1049
1176
|
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
1050
1177
|
flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
|
1178
|
+
return if !MU::Cloud::Google::Habitat.isLive?(flags["project"], credentials)
|
1051
1179
|
skipsnapshots = flags["skipsnapshots"]
|
1052
1180
|
onlycloud = flags["onlycloud"]
|
1053
1181
|
# XXX make damn sure MU.deploy_id is set
|
@@ -1105,51 +1233,140 @@ next if !create
|
|
1105
1233
|
def self.schema(config)
|
1106
1234
|
toplevel_required = []
|
1107
1235
|
schema = {
|
1108
|
-
"
|
1236
|
+
"roles" => MU::Cloud::Google::User.schema(config)[1]["roles"],
|
1237
|
+
"create_image" => {
|
1238
|
+
"properties" => {
|
1239
|
+
"family" => {
|
1240
|
+
"type" => "string",
|
1241
|
+
"description" => "Add a GCP image +family+ string to the created image(s)"
|
1242
|
+
}
|
1243
|
+
}
|
1244
|
+
},
|
1245
|
+
"availability_zone" => {
|
1246
|
+
"type" => "string",
|
1247
|
+
"description" => "Target this instance to a specific Availability Zone"
|
1248
|
+
},
|
1249
|
+
"ssh_user" => {
|
1109
1250
|
"type" => "string",
|
1110
|
-
"description" => "
|
1251
|
+
"description" => "Account to use when connecting via ssh. Google Cloud images don't come with predefined remote access users, and some don't work with our usual default of +root+, so we recommend using some other (non-root) username.",
|
1252
|
+
"default" => "muadmin"
|
1253
|
+
},
|
1254
|
+
"network_tags" => {
|
1255
|
+
"type" => "array",
|
1256
|
+
"items" => {
|
1257
|
+
"type" => "string",
|
1258
|
+
"description" => "Add a network tag to this host, which can be used to selectively apply routes or firewall rules."
|
1259
|
+
}
|
1260
|
+
},
|
1261
|
+
"service_account" => MU::Config::Ref.schema(
|
1262
|
+
type: "users",
|
1263
|
+
desc: "An existing service account to use instead of the default one generated by Mu during the deployment process."
|
1264
|
+
),
|
1265
|
+
"metadata" => {
|
1266
|
+
"type" => "array",
|
1267
|
+
"items" => {
|
1268
|
+
"type" => "object",
|
1269
|
+
"description" => "Custom key-value pairs to be added to the metadata of Google Cloud virtual machines",
|
1270
|
+
"required" => ["key", "value"],
|
1271
|
+
"properties" => {
|
1272
|
+
"key" => {
|
1273
|
+
"type" => "string"
|
1274
|
+
},
|
1275
|
+
"value" => {
|
1276
|
+
"type" => "string"
|
1277
|
+
}
|
1278
|
+
}
|
1279
|
+
}
|
1111
1280
|
},
|
1112
1281
|
"routes" => {
|
1113
1282
|
"type" => "array",
|
1114
1283
|
"items" => MU::Config::VPC.routeschema
|
1284
|
+
},
|
1285
|
+
"scopes" => {
|
1286
|
+
"type" => "array",
|
1287
|
+
"items" => {
|
1288
|
+
"type" => "string",
|
1289
|
+
"description" => "API scopes to make available to this resource's service account."
|
1290
|
+
},
|
1291
|
+
"default" => ["https://www.googleapis.com/auth/compute.readonly", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/devstorage.read_only"]
|
1115
1292
|
}
|
1116
1293
|
}
|
1117
1294
|
[toplevel_required, schema]
|
1118
1295
|
end
|
1119
1296
|
|
1297
|
+
@@instance_type_cache = {}
|
1298
|
+
|
1120
1299
|
# Confirm that the given instance size is valid for the given region.
|
1121
1300
|
# If someone accidentally specified an equivalent size from some other cloud provider, return something that makes sense. If nothing makes sense, return nil.
|
1122
1301
|
# @param size [String]: Instance type to check
|
1123
1302
|
# @param region [String]: Region to check against
|
1124
1303
|
# @return [String,nil]
|
1125
|
-
def self.validateInstanceType(size, region)
|
1126
|
-
|
1127
|
-
if
|
1304
|
+
def self.validateInstanceType(size, region, project: nil, credentials: nil)
|
1305
|
+
size = size.dup.to_s
|
1306
|
+
if @@instance_type_cache[project] and
|
1307
|
+
@@instance_type_cache[project][region] and
|
1308
|
+
@@instance_type_cache[project][region][size]
|
1309
|
+
return @@instance_type_cache[project][region][size]
|
1310
|
+
end
|
1311
|
+
|
1312
|
+
if size.match(/\/?custom-(\d+)-(\d+)(?:-ext)?$/)
|
1313
|
+
cpus = Regexp.last_match[1].to_i
|
1314
|
+
mem = Regexp.last_match[2].to_i
|
1315
|
+
ok = true
|
1316
|
+
if cpus < 1 or cpus > 32 or (cpus % 2 != 0 and cpus != 1)
|
1317
|
+
MU.log "Custom instance type #{size} illegal: CPU count must be 1 or an even number between 2 and 32", MU::ERR
|
1318
|
+
ok = false
|
1319
|
+
end
|
1320
|
+
if (mem % 256) != 0
|
1321
|
+
MU.log "Custom instance type #{size} illegal: Memory must be a multiple of 256 (MB)", MU::ERR
|
1322
|
+
ok = false
|
1323
|
+
end
|
1324
|
+
if ok
|
1325
|
+
return "custom-#{cpus.to_s}-#{mem.to_s}"
|
1326
|
+
else
|
1327
|
+
return nil
|
1328
|
+
end
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
@@instance_type_cache[project] ||= {}
|
1332
|
+
@@instance_type_cache[project][region] ||= {}
|
1333
|
+
types = (MU::Cloud::Google.listInstanceTypes(region, project: project, credentials: credentials))[project][region]
|
1334
|
+
realsize = size.dup
|
1335
|
+
|
1336
|
+
if types and (realsize.nil? or !types.has_key?(realsize))
|
1128
1337
|
# See if it's a type we can approximate from one of the other clouds
|
1129
|
-
atypes = (MU::Cloud::AWS.listInstanceTypes)[MU::Cloud::AWS.myRegion]
|
1130
1338
|
foundmatch = false
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1339
|
+
MU::Cloud.availableClouds.each { |cloud|
|
1340
|
+
next if cloud == "Google"
|
1341
|
+
cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
|
1342
|
+
foreign_types = (cloudbase.listInstanceTypes)[cloudbase.myRegion]
|
1343
|
+
if foreign_types and foreign_types.size > 0 and foreign_types.has_key?(size)
|
1344
|
+
vcpu = foreign_types[size]["vcpu"]
|
1345
|
+
mem = foreign_types[size]["memory"]
|
1346
|
+
ecu = foreign_types[size]["ecu"]
|
1347
|
+
types.keys.sort.reverse.each { |type|
|
1348
|
+
features = types[type]
|
1349
|
+
next if ecu == "Variable" and ecu != features["ecu"]
|
1350
|
+
next if features["vcpu"] != vcpu
|
1351
|
+
if (features["memory"] - mem.to_f).abs < 0.10*mem
|
1352
|
+
foundmatch = true
|
1353
|
+
MU.log "You specified #{cloud} instance type '#{realsize}.' Approximating with Google Compute type '#{type}.'", MU::WARN
|
1354
|
+
realsize = type
|
1355
|
+
break
|
1356
|
+
end
|
1357
|
+
}
|
1358
|
+
end
|
1359
|
+
break if foundmatch
|
1360
|
+
}
|
1361
|
+
|
1147
1362
|
if !foundmatch
|
1148
|
-
MU.log "Invalid size '#{
|
1363
|
+
MU.log "Invalid size '#{realsize}' for Google Compute instance in #{region} (checked project #{project}). Supported types:", MU::ERR, details: types.keys.sort.join(", ")
|
1364
|
+
@@instance_type_cache[project][region][size] = nil
|
1149
1365
|
return nil
|
1150
1366
|
end
|
1151
1367
|
end
|
1152
|
-
size
|
1368
|
+
@@instance_type_cache[project][region][size] = realsize
|
1369
|
+
@@instance_type_cache[project][region][size]
|
1153
1370
|
end
|
1154
1371
|
|
1155
1372
|
|
@@ -1160,17 +1377,57 @@ next if !create
|
|
1160
1377
|
def self.validateConfig(server, configurator)
|
1161
1378
|
ok = true
|
1162
1379
|
|
1163
|
-
server['
|
1164
|
-
|
1380
|
+
server['project'] ||= MU::Cloud::Google.defaultProject(server['credentials'])
|
1381
|
+
size = validateInstanceType(server["size"], server["region"], project: server['project'], credentials: server['credentials'])
|
1382
|
+
|
1383
|
+
if size.nil?
|
1384
|
+
MU.log "Failed to verify instance size #{server["size"]} for Server #{server['name']}", MU::WARN
|
1385
|
+
else
|
1386
|
+
server["size"] = size
|
1387
|
+
end
|
1165
1388
|
|
1166
1389
|
# If we're not targeting an availability zone, pick one randomly
|
1167
1390
|
if !server['availability_zone']
|
1168
1391
|
server['availability_zone'] = MU::Cloud::Google.listAZs(server['region']).sample
|
1169
1392
|
end
|
1170
1393
|
|
1394
|
+
if server['service_account']
|
1395
|
+
server['service_account']['cloud'] = "Google"
|
1396
|
+
server['service_account']['habitat'] ||= server['project']
|
1397
|
+
found = MU::Config::Ref.get(server['service_account'])
|
1398
|
+
if found.id and !found.kitten
|
1399
|
+
MU.log "GKE server #{server['name']} failed to locate service account #{server['service_account']} in project #{server['project']}", MU::ERR
|
1400
|
+
ok = false
|
1401
|
+
end
|
1402
|
+
else
|
1403
|
+
user = {
|
1404
|
+
"name" => server['name'],
|
1405
|
+
"cloud" => "Google",
|
1406
|
+
"project" => server["project"],
|
1407
|
+
"credentials" => server["credentials"],
|
1408
|
+
"type" => "service"
|
1409
|
+
}
|
1410
|
+
if server['roles']
|
1411
|
+
user['roles'] = server['roles'].dup
|
1412
|
+
end
|
1413
|
+
configurator.insertKitten(user, "users", true)
|
1414
|
+
server['dependencies'] ||= []
|
1415
|
+
server['service_account'] = MU::Config::Ref.get(
|
1416
|
+
type: "users",
|
1417
|
+
cloud: "Google",
|
1418
|
+
name: server["name"],
|
1419
|
+
project: server["project"],
|
1420
|
+
credentials: server["credentials"]
|
1421
|
+
)
|
1422
|
+
server['dependencies'] << {
|
1423
|
+
"type" => "user",
|
1424
|
+
"name" => server["name"]
|
1425
|
+
}
|
1426
|
+
end
|
1427
|
+
|
1171
1428
|
subnets = nil
|
1172
1429
|
if !server['vpc']
|
1173
|
-
vpcs = MU::Cloud::Google::VPC.find
|
1430
|
+
vpcs = MU::Cloud::Google::VPC.find(credentials: server['credentials'])
|
1174
1431
|
if vpcs["default"]
|
1175
1432
|
server["vpc"] ||= {}
|
1176
1433
|
server["vpc"]["vpc_id"] = vpcs["default"].self_link
|
@@ -1202,8 +1459,9 @@ next if !create
|
|
1202
1459
|
end
|
1203
1460
|
|
1204
1461
|
if server['image_id'].nil?
|
1205
|
-
|
1206
|
-
|
1462
|
+
img_id = MU::Cloud.getStockImage("Google", platform: server['platform'])
|
1463
|
+
if img_id
|
1464
|
+
server['image_id'] = configurator.getTail("server"+server['name']+"Image", value: img_id, prettyname: "server"+server['name']+"Image", cloudtype: "Google::Apis::ComputeV1::Image")
|
1207
1465
|
else
|
1208
1466
|
MU.log "No image specified for #{server['name']} and no default available for platform #{server['platform']}", MU::ERR, details: server
|
1209
1467
|
ok = false
|
@@ -1214,7 +1472,6 @@ next if !create
|
|
1214
1472
|
begin
|
1215
1473
|
real_image = MU::Cloud::Google::Server.fetchImage(server['image_id'].to_s, credentials: server['credentials'])
|
1216
1474
|
rescue ::Google::Apis::ClientError => e
|
1217
|
-
MU.log e.inspect, MU::WARN
|
1218
1475
|
end
|
1219
1476
|
|
1220
1477
|
if real_image.nil?
|
@@ -1226,31 +1483,38 @@ next if !create
|
|
1226
1483
|
img_project = Regexp.last_match[1]
|
1227
1484
|
img_name = Regexp.last_match[2]
|
1228
1485
|
begin
|
1486
|
+
img = MU::Cloud::Google.compute(credentials: server['credentials']).get_image(img_project, img_name)
|
1229
1487
|
snaps = MU::Cloud::Google.compute(credentials: server['credentials']).list_snapshots(
|
1230
1488
|
img_project,
|
1231
1489
|
filter: "name eq #{img_name}-.*"
|
1232
1490
|
)
|
1233
1491
|
server['storage'] ||= []
|
1234
1492
|
used_devs = server['storage'].map { |disk| disk['device'].gsub(/.*?\//, "") }
|
1235
|
-
snaps.items
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1493
|
+
if snaps and snaps.items
|
1494
|
+
snaps.items.each { |snap|
|
1495
|
+
next if !snap.labels.is_a?(Hash) or !snap.labels["mu-device-name"] or snap.labels["mu-parent-image"] != img_name
|
1496
|
+
devname = snap.labels["mu-device-name"]
|
1497
|
+
|
1498
|
+
if used_devs.include?(devname)
|
1499
|
+
MU.log "Device name #{devname} already declared in server #{server['name']} (snapshot #{snap.name} wants the name)", MU::ERR
|
1500
|
+
ok = false
|
1501
|
+
end
|
1502
|
+
server['storage'] << {
|
1503
|
+
"snapshot_id" => snap.self_link,
|
1504
|
+
"size" => snap.disk_size_gb,
|
1505
|
+
"delete_on_termination" => true,
|
1506
|
+
"device" => devname
|
1507
|
+
}
|
1508
|
+
used_devs << devname
|
1248
1509
|
}
|
1249
|
-
|
1250
|
-
|
1510
|
+
if snaps.items.size > 0
|
1511
|
+
# MU.log img_name, MU::WARN, details: snaps.items
|
1512
|
+
end
|
1513
|
+
end
|
1251
1514
|
rescue ::Google::Apis::ClientError => e
|
1252
1515
|
# it's ok, sometimes we don't have permission to list snapshots
|
1253
1516
|
# in other peoples' projects
|
1517
|
+
# MU.log img_name, MU::WARN, details: img
|
1254
1518
|
raise e if !e.message.match(/^forbidden: /)
|
1255
1519
|
end
|
1256
1520
|
end
|