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,6 +28,15 @@ module MU
|
|
28
28
|
|
29
29
|
@@cloudformation_mode = false
|
30
30
|
|
31
|
+
# Return what we think of as a cloud object's habitat. In AWS, this means
|
32
|
+
# the +account_number+ in which it's resident. If this is not applicable,
|
33
|
+
# such as for a {Habitat} or {Folder}, returns nil.
|
34
|
+
# @param cloudobj [MU::Cloud::AWS]: The resource from which to extract the habitat id
|
35
|
+
# @return [String,nil]
|
36
|
+
def self.habitat(cloudobj)
|
37
|
+
cloudobj.respond_to?(:account_number) ? cloudobj.account_number : nil
|
38
|
+
end
|
39
|
+
|
31
40
|
# Toggle ourselves into a mode that will emit a CloudFormation template
|
32
41
|
# instead of actual infrastructure.
|
33
42
|
# @param set [Boolean]: Set the mode
|
@@ -78,8 +87,8 @@ module MU
|
|
78
87
|
# Stub method- there's no such thing as being "hosted" in a CloudFormation
|
79
88
|
# environment. Calls {MU::Cloud::AWS.myRegion} to return sensible
|
80
89
|
# values, if we happen to have AWS credentials configured.
|
81
|
-
def self.myRegion
|
82
|
-
MU::Cloud::AWS.myRegion
|
90
|
+
def self.myRegion(credentials = nil)
|
91
|
+
MU::Cloud::AWS.myRegion(credentials)
|
83
92
|
end
|
84
93
|
|
85
94
|
# Stub method- there's no such thing as being "hosted" in a CloudFormation
|
@@ -259,12 +268,6 @@ module MU
|
|
259
268
|
"Properties" => {
|
260
269
|
}
|
261
270
|
}
|
262
|
-
when "loggroup"
|
263
|
-
desc = {
|
264
|
-
"Type" => "AWS::EC2::LogGroup",
|
265
|
-
"Properties" => {
|
266
|
-
}
|
267
|
-
}
|
268
271
|
when "cache_subnets"
|
269
272
|
desc = {
|
270
273
|
"Type" => "AWS::ElastiCache::SubnetGroup",
|
@@ -649,7 +652,7 @@ module MU
|
|
649
652
|
child_name = resource['#MUOBJECT'].cloudobj.cfm_name
|
650
653
|
child_params = child_template[child_name]["Properties"]["Parameters"]
|
651
654
|
child_params = Hash.new if child_params.nil?
|
652
|
-
cfm_template["Parameters"].each { |key
|
655
|
+
cfm_template["Parameters"].keys.each { |key|
|
653
656
|
child_params[key] = { "Ref" => key }
|
654
657
|
}
|
655
658
|
MU::Cloud::CloudFormation.setCloudFormationProp(child_template[child_name], "Parameters", child_params)
|
@@ -94,10 +94,10 @@ module MU
|
|
94
94
|
# @param port_range [String]: A port range descriptor (e.g. 0-65535). Only valid with udp or tcp.
|
95
95
|
# @return [void]
|
96
96
|
def addRule(hosts,
|
97
|
-
proto:
|
98
|
-
port:
|
99
|
-
egress:
|
100
|
-
port_range:
|
97
|
+
proto: "tcp",
|
98
|
+
port: nil,
|
99
|
+
egress: false,
|
100
|
+
port_range: "0-65535"
|
101
101
|
)
|
102
102
|
rule = Hash.new
|
103
103
|
rule["proto"] = proto
|
@@ -146,7 +146,7 @@ module MU
|
|
146
146
|
# Manufacture an EC2 security group. The second parameter, rules, is an
|
147
147
|
# "ingress_rules" structure parsed and validated by MU::Config.
|
148
148
|
#########################################################################
|
149
|
-
def setRules(rules, add_to_self:
|
149
|
+
def setRules(rules, add_to_self: false, ingress: true, egress: false)
|
150
150
|
return if rules.nil? or rules.size == 0
|
151
151
|
|
152
152
|
if add_to_self
|
@@ -294,14 +294,6 @@ module MU
|
|
294
294
|
MU::Cloud::AWS::FirewallRule.schema(config)
|
295
295
|
end
|
296
296
|
|
297
|
-
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
|
298
|
-
# @param server [Hash]: The resource to process and validate
|
299
|
-
# @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
300
|
-
# @return [Boolean]: True if validation succeeded, False otherwise
|
301
|
-
def self.validateConfig(server, configurator)
|
302
|
-
MU::Cloud::AWS::FirewallRule.validateConfig(server, configurator)
|
303
|
-
end
|
304
|
-
|
305
297
|
# Does this resource type exist as a global (cloud-wide) artifact, or
|
306
298
|
# is it localized to a region/zone?
|
307
299
|
# @return [Boolean]
|
@@ -45,6 +45,7 @@ module MU
|
|
45
45
|
"muID" => MU.deploy_id,
|
46
46
|
"muUser" => MU.chef_user,
|
47
47
|
"publicIP" => MU.mu_public_ip,
|
48
|
+
"mommaCatPort" => MU.mommaCatPort,
|
48
49
|
"skipApplyUpdates" => @config['skipinitialupdates'],
|
49
50
|
"windowsAdminName" => @config['windows_admin_username'],
|
50
51
|
"resourceName" => @config["name"],
|
@@ -303,7 +304,7 @@ module MU
|
|
303
304
|
role_name: baserole.role_name,
|
304
305
|
policy_name: name
|
305
306
|
)
|
306
|
-
policies[name] = URI.
|
307
|
+
policies[name] = URI.decode_www_form(resp.policy_document)
|
307
308
|
}
|
308
309
|
}
|
309
310
|
end
|
@@ -340,6 +341,14 @@ module MU
|
|
340
341
|
nil
|
341
342
|
end
|
342
343
|
|
344
|
+
# Return the date/time a machine image was created.
|
345
|
+
# @param ami_id [String]: AMI identifier of an Amazon Machine Image
|
346
|
+
# @param credentials [String]
|
347
|
+
# @return [DateTime]
|
348
|
+
def self.imageTimeStamp(ami_id, credentials: nil, region: nil)
|
349
|
+
MU::Cloud::AWS.imageTimeStamp(ami_id, credentials: credentials, region: region)
|
350
|
+
end
|
351
|
+
|
343
352
|
# Cloud-specific configuration properties.
|
344
353
|
# @param config [MU::Config]: The calling MU::Config object
|
345
354
|
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
@@ -166,6 +166,7 @@ module MU
|
|
166
166
|
"deploySSHKey" => @deploy.ssh_public_key,
|
167
167
|
"muID" => MU.deploy_id,
|
168
168
|
"muUser" => MU.chef_user,
|
169
|
+
"mommaCatPort" => MU.mommaCatPort,
|
169
170
|
"publicIP" => MU.mu_public_ip,
|
170
171
|
"skipApplyUpdates" => @config['skipinitialupdates'],
|
171
172
|
"windowsAdminName" => @config['windows_admin_username'],
|
data/modules/mu/clouds/google.rb
CHANGED
@@ -29,13 +29,61 @@ module MU
|
|
29
29
|
@@authorizers = {}
|
30
30
|
@@acct_to_profile_map = {}
|
31
31
|
@@enable_semaphores = {}
|
32
|
+
@@readonly_semaphore = Mutex.new
|
33
|
+
@@readonly = {}
|
34
|
+
|
35
|
+
# Module used by {MU::Cloud} to insert additional instance methods into
|
36
|
+
# instantiated resources in this cloud layer.
|
37
|
+
module AdditionalResourceMethods
|
38
|
+
# Google Cloud url attribute, found in some form on most GCP cloud
|
39
|
+
# resources.
|
40
|
+
# @return [String]
|
41
|
+
def url
|
42
|
+
desc = cloud_desc
|
43
|
+
(desc and desc.self_link) ? desc.self_link : nil
|
44
|
+
end
|
45
|
+
end
|
32
46
|
|
33
47
|
# Any cloud-specific instance methods we require our resource
|
34
48
|
# implementations to have, above and beyond the ones specified by
|
35
49
|
# {MU::Cloud}
|
36
50
|
# @return [Array<Symbol>]
|
37
51
|
def self.required_instance_methods
|
38
|
-
[]
|
52
|
+
[:url]
|
53
|
+
end
|
54
|
+
|
55
|
+
# A hook that is always called just before any of the instance method of
|
56
|
+
# our resource implementations gets invoked, so that we can ensure that
|
57
|
+
# repetitive setup tasks (like resolving +:resource_group+ for Azure
|
58
|
+
# resources) have always been done.
|
59
|
+
# @param cloudobj [MU::Cloud]
|
60
|
+
# @param deploy [MU::MommaCat]
|
61
|
+
def self.resourceInitHook(cloudobj, deploy)
|
62
|
+
class << self
|
63
|
+
attr_reader :project_id
|
64
|
+
attr_reader :customer
|
65
|
+
# url is too complex for an attribute (we get it from the cloud API),
|
66
|
+
# so it's up in AdditionalResourceMethods instead
|
67
|
+
end
|
68
|
+
return if !cloudobj
|
69
|
+
|
70
|
+
cloudobj.instance_variable_set(:@customer, MU::Cloud::Google.customerID(cloudobj.config['credentials']))
|
71
|
+
|
72
|
+
# XXX ensure @cloud_id and @project_id if this is a habitat
|
73
|
+
# XXX skip project_id if this is a folder or group
|
74
|
+
if deploy
|
75
|
+
# XXX this may be wrong for new deploys (but def right for regrooms)
|
76
|
+
project = MU::Cloud::Google.projectLookup(cloudobj.config['project'], deploy, sibling_only: true, raise_on_fail: false)
|
77
|
+
project_id = project.nil? ? cloudobj.config['project'] : project.cloudobj.cloud_id
|
78
|
+
cloudobj.instance_variable_set(:@project_id, project_id)
|
79
|
+
else
|
80
|
+
cloudobj.instance_variable_set(:@project_id, cloudobj.config['project'])
|
81
|
+
end
|
82
|
+
|
83
|
+
# XXX @url? Well we're not likely to have @cloud_desc at this point, so maybe
|
84
|
+
# that needs to be a generic-to-google wrapper like def url; cloud_desc.self_link;end
|
85
|
+
|
86
|
+
# XXX something like: vpc["habitat"] = MU::Cloud::Google.projectToRef(vpc["project"], config: configurator, credentials: vpc["credentials"])
|
39
87
|
end
|
40
88
|
|
41
89
|
# If we're running this cloud, return the $MU_CFG blob we'd use to
|
@@ -62,6 +110,17 @@ module MU
|
|
62
110
|
sample
|
63
111
|
end
|
64
112
|
|
113
|
+
# If we reside in this cloud, return the VPC in which we, the Mu Master, reside.
|
114
|
+
# @return [MU::Cloud::VPC]
|
115
|
+
def self.myVPCObj
|
116
|
+
return nil if !hosted?
|
117
|
+
instance = MU.myCloudDescriptor
|
118
|
+
return nil if !instance or !instance.network_interfaces or instance.network_interfaces.size == 0
|
119
|
+
vpc = MU::MommaCat.findStray("Google", "vpc", cloud_id: instance.network_interfaces.first.network.gsub(/.*?\/([^\/]+)$/, '\1'), dummy_ok: true, habitats: [myProject])
|
120
|
+
return nil if vpc.nil? or vpc.size == 0
|
121
|
+
vpc.first
|
122
|
+
end
|
123
|
+
|
65
124
|
# Return the name strings of all known sets of credentials for this cloud
|
66
125
|
# @return [Array<String>]
|
67
126
|
def self.listCredentials
|
@@ -72,6 +131,88 @@ module MU
|
|
72
131
|
$MU_CFG['google'].keys
|
73
132
|
end
|
74
133
|
|
134
|
+
@@habmap = {}
|
135
|
+
|
136
|
+
# Return what we think of as a cloud object's habitat. In GCP, this means
|
137
|
+
# the +project_id+ in which is resident. If this is not applicable, such
|
138
|
+
# as for a {Habitat} or {Folder}, returns nil.
|
139
|
+
# @param cloudobj [MU::Cloud::Google]: The resource from which to extract the habitat id
|
140
|
+
# @return [String,nil]
|
141
|
+
def self.habitat(cloudobj, nolookup: false, deploy: nil)
|
142
|
+
@@habmap ||= {}
|
143
|
+
# XXX whaddabout config['habitat'] HNNNGH
|
144
|
+
|
145
|
+
return nil if !cloudobj.cloudclass.canLiveIn.include?(:Habitat)
|
146
|
+
|
147
|
+
# XXX users are assholes because they're valid two different ways ugh ugh
|
148
|
+
return nil if [MU::Cloud::Google::Group, MU::Cloud::Google::Folder].include?(cloudobj.cloudclass)
|
149
|
+
if cloudobj.config and cloudobj.config['project']
|
150
|
+
if nolookup
|
151
|
+
return cloudobj.config['project']
|
152
|
+
end
|
153
|
+
if @@habmap[cloudobj.config['project']]
|
154
|
+
return @@habmap[cloudobj.config['project']]
|
155
|
+
end
|
156
|
+
deploy ||= cloudobj.deploy if cloudobj.respond_to?(:deploy)
|
157
|
+
|
158
|
+
projectobj = projectLookup(cloudobj.config['project'], deploy, raise_on_fail: false)
|
159
|
+
|
160
|
+
if projectobj
|
161
|
+
@@habmap[cloudobj.config['project']] = projectobj.cloud_id
|
162
|
+
return projectobj.cloud_id
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# blow up if this resource *has* to live in a project
|
167
|
+
if cloudobj.cloudclass.canLiveIn == [:Habitat]
|
168
|
+
MU.log "Failed to find project for cloudobj of class #{cloudobj.cloudclass.class.name}", MU::ERR, details: cloudobj
|
169
|
+
raise MuError, "Failed to find project for cloudobj of class #{cloudobj.cloudclass.class.name}"
|
170
|
+
end
|
171
|
+
|
172
|
+
nil
|
173
|
+
end
|
174
|
+
|
175
|
+
# Take a plain string that might be a reference to sibling project
|
176
|
+
# declared elsewhere in the active stack, or the project id of a live
|
177
|
+
# cloud resource, and return a {MU::Config::Ref} object
|
178
|
+
# @param project [String]: The name of a sibling project, or project id of an active project in GCP
|
179
|
+
# @param config [MU::Config]: A {MU::Config} object containing sibling resources, typically what we'd pass if we're calling during configuration parsing
|
180
|
+
# @param credentials [String]:
|
181
|
+
# @return [MU::Config::Ref]
|
182
|
+
def self.projectToRef(project, config: nil, credentials: nil)
|
183
|
+
return nil if !project
|
184
|
+
|
185
|
+
if config and config.haveLitterMate?(project, "habitat")
|
186
|
+
ref = MU::Config::Ref.new(
|
187
|
+
name: project,
|
188
|
+
cloud: "Google",
|
189
|
+
credentials: credentials,
|
190
|
+
type: "habitats"
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
if !ref
|
195
|
+
resp = MU::MommaCat.findStray(
|
196
|
+
"Google",
|
197
|
+
"habitats",
|
198
|
+
cloud_id: project,
|
199
|
+
credentials: credentials,
|
200
|
+
dummy_ok: true
|
201
|
+
)
|
202
|
+
if resp and resp.size > 0
|
203
|
+
project_obj = resp.first
|
204
|
+
ref = MU::Config::Ref.new(
|
205
|
+
id: project_obj.cloud_id,
|
206
|
+
cloud: "Google",
|
207
|
+
credentials: credentials,
|
208
|
+
type: "habitats"
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
ref
|
214
|
+
end
|
215
|
+
|
75
216
|
# A shortcut for {MU::MommaCat.findStray} to resolve a shorthand project
|
76
217
|
# name into a cloud object, whether it refers to a sibling by internal
|
77
218
|
# name or by cloud identifier.
|
@@ -79,20 +220,21 @@ module MU
|
|
79
220
|
# @param deploy [String]
|
80
221
|
# @param raise_on_fail [Boolean]
|
81
222
|
# @param sibling_only [Boolean]
|
82
|
-
# @return [MU::
|
223
|
+
# @return [MU::Config::Habitat,nil]
|
83
224
|
def self.projectLookup(name, deploy = MU.mommacat, raise_on_fail: true, sibling_only: false)
|
84
|
-
project_obj = deploy.findLitterMate(type: "habitats", name: name)
|
225
|
+
project_obj = deploy.findLitterMate(type: "habitats", name: name) if deploy
|
85
226
|
|
86
227
|
if !project_obj and !sibling_only
|
87
228
|
resp = MU::MommaCat.findStray(
|
88
229
|
"Google",
|
89
230
|
"habitats",
|
90
|
-
deploy_id: deploy.deploy_id,
|
231
|
+
deploy_id: deploy ? deploy.deploy_id : nil,
|
91
232
|
cloud_id: name,
|
92
233
|
name: name,
|
93
234
|
dummy_ok: true
|
94
235
|
)
|
95
|
-
|
236
|
+
|
237
|
+
project_obj = resp.first if resp and resp.size > 0
|
96
238
|
end
|
97
239
|
|
98
240
|
if (!project_obj or !project_obj.cloud_id) and raise_on_fail
|
@@ -109,6 +251,9 @@ module MU
|
|
109
251
|
def self.adminBucketName(credentials = nil)
|
110
252
|
#XXX find a default if this particular account doesn't have a log_bucket_name configured
|
111
253
|
cfg = credConfig(credentials)
|
254
|
+
if cfg.nil?
|
255
|
+
raise MuError, "Failed to load Google credential set #{credentials}"
|
256
|
+
end
|
112
257
|
cfg['log_bucket_name']
|
113
258
|
end
|
114
259
|
|
@@ -124,7 +269,7 @@ module MU
|
|
124
269
|
# credentials. If no account name is specified, will return one flagged as
|
125
270
|
# default. Returns nil if GCP is not configured. Throws an exception if
|
126
271
|
# an account name is specified which does not exist.
|
127
|
-
# @param name [String]: The name of the key under '
|
272
|
+
# @param name [String]: The name of the key under 'google' in mu.yaml to return
|
128
273
|
# @return [Hash,nil]
|
129
274
|
def self.credConfig(name = nil, name_only: false)
|
130
275
|
# If there's nothing in mu.yaml (which is wrong), but we're running
|
@@ -134,23 +279,17 @@ module MU
|
|
134
279
|
return @@my_hosted_cfg if @@my_hosted_cfg
|
135
280
|
|
136
281
|
if hosted?
|
137
|
-
|
138
|
-
|
139
|
-
# if iam_data["InstanceProfileArn"] and !iam_data["InstanceProfileArn"].empty?
|
140
|
-
@@my_hosted_cfg = hosted_config
|
141
|
-
return name_only ? "#default" : @@my_hosted_cfg
|
142
|
-
# end
|
143
|
-
rescue JSON::ParserError => e
|
144
|
-
end
|
282
|
+
@@my_hosted_cfg = hosted_config
|
283
|
+
return name_only ? "#default" : @@my_hosted_cfg
|
145
284
|
end
|
146
285
|
|
147
286
|
return nil
|
148
287
|
end
|
149
288
|
|
150
289
|
if name.nil?
|
151
|
-
$MU_CFG['google'].each_pair { |
|
290
|
+
$MU_CFG['google'].each_pair { |set, cfg|
|
152
291
|
if cfg['default']
|
153
|
-
return name_only ?
|
292
|
+
return name_only ? set : cfg
|
154
293
|
end
|
155
294
|
}
|
156
295
|
else
|
@@ -173,10 +312,24 @@ module MU
|
|
173
312
|
elsif MU::Cloud::Google.hosted?
|
174
313
|
zone = MU::Cloud::Google.getGoogleMetaData("instance/zone")
|
175
314
|
@@myRegion_var = zone.gsub(/^.*?\/|\-\d+$/, "")
|
315
|
+
else
|
316
|
+
@@myRegion_var = "us-east4"
|
176
317
|
end
|
177
318
|
@@myRegion_var
|
178
319
|
end
|
179
320
|
|
321
|
+
# Do cloud-specific deploy instantiation tasks, such as copying SSH keys
|
322
|
+
# around, sticking secrets in buckets, creating resource groups, etc
|
323
|
+
# @param deploy [MU::MommaCat]
|
324
|
+
def self.initDeploy(deploy)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Purge cloud-specific deploy meta-artifacts (SSH keys, resource groups,
|
328
|
+
# etc)
|
329
|
+
# @param deploy_id [MU::MommaCat]
|
330
|
+
def self.cleanDeploy(deploy_id, credentials: nil, noop: false)
|
331
|
+
end
|
332
|
+
|
180
333
|
# Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
|
181
334
|
# @param deploy_id [String]: The deploy for which we're writing the secret
|
182
335
|
# @param value [String]: The contents of the secret
|
@@ -250,9 +403,10 @@ module MU
|
|
250
403
|
)
|
251
404
|
}
|
252
405
|
rescue ::Google::Apis::ClientError => e
|
406
|
+
MU.log e.message, MU::WARN, details: e.inspect
|
253
407
|
if e.inspect.match(/body: "Not Found"/)
|
254
408
|
raise MuError, "Google admin bucket #{adminBucketName(credentials)} or key #{name} does not appear to exist or is not visible with #{credentials ? credentials : "default"} credentials"
|
255
|
-
elsif e.
|
409
|
+
elsif e.message.match(/notFound: /)
|
256
410
|
if retries < 5
|
257
411
|
sleep 5
|
258
412
|
retries += 1
|
@@ -265,7 +419,7 @@ module MU
|
|
265
419
|
sleep 10
|
266
420
|
retry
|
267
421
|
else
|
268
|
-
raise MuError, "Got #{e.
|
422
|
+
raise MuError, "Got #{e.message} trying to set ACLs for #{deploy_id} in #{adminBucketName(credentials)}"
|
269
423
|
end
|
270
424
|
end
|
271
425
|
end
|
@@ -289,7 +443,7 @@ module MU
|
|
289
443
|
return @@is_in_gcp
|
290
444
|
end
|
291
445
|
|
292
|
-
if getGoogleMetaData("
|
446
|
+
if getGoogleMetaData("project/project-id")
|
293
447
|
@@is_in_gcp = true
|
294
448
|
return true
|
295
449
|
end
|
@@ -323,11 +477,11 @@ module MU
|
|
323
477
|
# @param name [String]: A resource name for the certificate
|
324
478
|
# @param cert [String,OpenSSL::X509::Certificate]: An x509 certificate
|
325
479
|
# @param key [String,OpenSSL::PKey]: An x509 private key
|
326
|
-
# @return [Google::Apis::
|
480
|
+
# @return [Google::Apis::ComputeV1::SslCertificate]
|
327
481
|
def self.createSSLCertificate(name, cert, key, flags = {}, credentials: nil)
|
328
482
|
flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
|
329
483
|
flags["description"] ||= MU.deploy_id
|
330
|
-
certobj = ::Google::Apis::
|
484
|
+
certobj = ::Google::Apis::ComputeV1::SslCertificate.new(
|
331
485
|
name: name,
|
332
486
|
certificate: cert.to_s,
|
333
487
|
private_key: key.to_s,
|
@@ -354,27 +508,40 @@ module MU
|
|
354
508
|
|
355
509
|
cfg = credConfig(credentials)
|
356
510
|
|
357
|
-
if cfg['project']
|
358
|
-
@@enable_semaphores[cfg['project']] ||= Mutex.new
|
359
|
-
end
|
360
|
-
|
361
511
|
if cfg
|
512
|
+
if cfg['project']
|
513
|
+
@@enable_semaphores[cfg['project']] ||= Mutex.new
|
514
|
+
end
|
362
515
|
data = nil
|
363
516
|
@@authorizers[credentials] ||= {}
|
364
517
|
|
365
|
-
def self.get_machine_credentials(scopes)
|
518
|
+
def self.get_machine_credentials(scopes, credentials = nil)
|
366
519
|
@@svc_account_name = MU::Cloud::Google.getGoogleMetaData("instance/service-accounts/default/email")
|
367
520
|
MU.log "We are hosted in GCP, so I will attempt to use the service account #{@@svc_account_name} to make API requests.", MU::DEBUG
|
368
521
|
|
369
522
|
@@authorizers[credentials][scopes.to_s] = ::Google::Auth.get_application_default(scopes)
|
370
523
|
@@authorizers[credentials][scopes.to_s].fetch_access_token!
|
371
524
|
@@default_project ||= MU::Cloud::Google.getGoogleMetaData("project/project-id")
|
525
|
+
begin
|
526
|
+
listRegions(credentials: credentials)
|
527
|
+
listInstanceTypes(credentials: credentials)
|
528
|
+
listProjects(credentials)
|
529
|
+
rescue ::Google::Apis::ClientError => e
|
530
|
+
MU.log "Found machine credentials #{@@svc_account_name}, but these don't appear to have sufficient permissions or scopes", MU::WARN, details: scopes
|
531
|
+
@@authorizers.delete(credentials)
|
532
|
+
return nil
|
533
|
+
end
|
372
534
|
@@authorizers[credentials][scopes.to_s]
|
373
535
|
end
|
374
536
|
|
375
|
-
if cfg["credentials_file"]
|
537
|
+
if cfg["credentials_file"] or cfg["credentials_encoded"]
|
538
|
+
|
376
539
|
begin
|
377
|
-
data =
|
540
|
+
data = if cfg["credentials_encoded"]
|
541
|
+
JSON.parse(Base64.decode64(cfg["credentials_encoded"]))
|
542
|
+
else
|
543
|
+
JSON.parse(File.read(cfg["credentials_file"]))
|
544
|
+
end
|
378
545
|
@@default_project ||= data["project_id"]
|
379
546
|
creds = {
|
380
547
|
:json_key_io => StringIO.new(MultiJson.dump(data)),
|
@@ -388,18 +555,20 @@ module MU
|
|
388
555
|
raise MuError, "Google Cloud credentials file #{cfg["credentials_file"]} is missing or invalid (#{e.message})"
|
389
556
|
end
|
390
557
|
MU.log "Google Cloud credentials file #{cfg["credentials_file"]} is missing or invalid", MU::WARN, details: e.message
|
391
|
-
return get_machine_credentials(scopes)
|
558
|
+
return get_machine_credentials(scopes, credentials)
|
392
559
|
end
|
393
560
|
elsif cfg["credentials"]
|
394
561
|
begin
|
395
562
|
vault, item = cfg["credentials"].split(/:/)
|
396
563
|
data = MU::Groomer::Chef.getSecret(vault: vault, item: item).to_h
|
397
|
-
rescue MU::Groomer::
|
564
|
+
rescue MU::Groomer::MuNoSuchSecret
|
398
565
|
if !MU::Cloud::Google.hosted?
|
399
566
|
raise MuError, "Google Cloud credentials not found in Vault #{vault}:#{item}"
|
400
567
|
end
|
401
568
|
MU.log "Google Cloud credentials not found in Vault #{vault}:#{item}", MU::WARN
|
402
|
-
|
569
|
+
found = get_machine_credentials(scopes, credentials)
|
570
|
+
raise MuError, "No valid credentials available! Either grant admin privileges to machine service account, or manually add a different one with mu-configure" if found.nil?
|
571
|
+
return found
|
403
572
|
end
|
404
573
|
|
405
574
|
@@default_project ||= data["project_id"]
|
@@ -411,7 +580,9 @@ module MU
|
|
411
580
|
@@authorizers[credentials][scopes.to_s] = ::Google::Auth::ServiceAccountCredentials.make_creds(creds)
|
412
581
|
return @@authorizers[credentials][scopes.to_s]
|
413
582
|
elsif MU::Cloud::Google.hosted?
|
414
|
-
|
583
|
+
found = get_machine_credentials(scopes, credentials)
|
584
|
+
raise MuError, "No valid credentials available! Either grant admin privileges to machine service account, or manually add a different one with mu-configure" if found.nil?
|
585
|
+
return found
|
415
586
|
else
|
416
587
|
raise MuError, "Google Cloud credentials not configured"
|
417
588
|
end
|
@@ -455,15 +626,39 @@ module MU
|
|
455
626
|
end
|
456
627
|
end
|
457
628
|
|
629
|
+
@@default_project_cache = {}
|
630
|
+
|
458
631
|
# Our credentials map to a project, an organizational structure in Google
|
459
632
|
# Cloud. This fetches the identifier of the project associated with our
|
460
633
|
# default credentials.
|
461
634
|
# @param credentials [String]
|
462
635
|
# @return [String]
|
463
636
|
def self.defaultProject(credentials = nil)
|
637
|
+
if @@default_project_cache.has_key?(credentials)
|
638
|
+
return @@default_project_cache[credentials]
|
639
|
+
end
|
464
640
|
cfg = credConfig(credentials)
|
465
|
-
|
641
|
+
if !cfg or !cfg['project']
|
642
|
+
if hosted?
|
643
|
+
@@default_project_cache[credentials] = myProject
|
644
|
+
return myProject
|
645
|
+
end
|
646
|
+
if cfg
|
647
|
+
begin
|
648
|
+
result = MU::Cloud::Google.resource_manager(credentials: credentials).list_projects
|
649
|
+
result.projects.reject! { |p| p.lifecycle_state == "DELETE_REQUESTED" }
|
650
|
+
available = result.projects.map { |p| p.project_id }
|
651
|
+
if available.size == 1
|
652
|
+
@@default_project_cache[credentials] = available[0]
|
653
|
+
return available[0]
|
654
|
+
end
|
655
|
+
rescue # fine
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
return nil if !cfg
|
466
660
|
loadCredentials(credentials) if !@@authorizers[credentials]
|
661
|
+
@@default_project_cache[credentials] = cfg['project']
|
467
662
|
cfg['project']
|
468
663
|
end
|
469
664
|
|
@@ -486,7 +681,7 @@ module MU
|
|
486
681
|
def self.listProjects(credentials = nil)
|
487
682
|
cfg = credConfig(credentials)
|
488
683
|
return [] if !cfg or !cfg['project']
|
489
|
-
result = MU::Cloud::Google.resource_manager.list_projects
|
684
|
+
result = MU::Cloud::Google.resource_manager(credentials: credentials).list_projects
|
490
685
|
result.projects.reject! { |p| p.lifecycle_state == "DELETE_REQUESTED" }
|
491
686
|
result.projects.map { |p| p.project_id }
|
492
687
|
end
|
@@ -530,23 +725,28 @@ module MU
|
|
530
725
|
# "translate" machine types across cloud providers.
|
531
726
|
# @param region [String]: Supported machine types can vary from region to region, so we look for the set we're interested in specifically
|
532
727
|
# @return [Hash]
|
533
|
-
def self.listInstanceTypes(region = myRegion)
|
534
|
-
return
|
535
|
-
if
|
536
|
-
|
728
|
+
def self.listInstanceTypes(region = self.myRegion, credentials: nil, project: MU::Cloud::Google.defaultProject)
|
729
|
+
return {} if !credConfig(credentials)
|
730
|
+
if @@instance_types and
|
731
|
+
@@instance_types[project] and
|
732
|
+
@@instance_types[project][region]
|
733
|
+
return @@instance_types
|
537
734
|
end
|
538
735
|
|
736
|
+
return {} if !project
|
737
|
+
|
539
738
|
@@instance_types ||= {}
|
540
|
-
@@instance_types[
|
541
|
-
|
739
|
+
@@instance_types[project] ||= {}
|
740
|
+
@@instance_types[project][region] ||= {}
|
741
|
+
result = MU::Cloud::Google.compute(credentials: credentials).list_machine_types(project, listAZs(region).first)
|
542
742
|
result.items.each { |type|
|
543
|
-
@@instance_types[region][type.name] ||= {}
|
544
|
-
@@instance_types[region][type.name]["memory"] = sprintf("%.1f", type.memory_mb/1024.0).to_f
|
545
|
-
@@instance_types[region][type.name]["vcpu"] = type.guest_cpus.to_f
|
743
|
+
@@instance_types[project][region][type.name] ||= {}
|
744
|
+
@@instance_types[project][region][type.name]["memory"] = sprintf("%.1f", type.memory_mb/1024.0).to_f
|
745
|
+
@@instance_types[project][region][type.name]["vcpu"] = type.guest_cpus.to_f
|
546
746
|
if type.is_shared_cpu
|
547
|
-
@@instance_types[region][type.name]["ecu"] = "Variable"
|
747
|
+
@@instance_types[project][region][type.name]["ecu"] = "Variable"
|
548
748
|
else
|
549
|
-
@@instance_types[region][type.name]["ecu"] = type.guest_cpus
|
749
|
+
@@instance_types[project][region][type.name]["ecu"] = type.guest_cpus
|
550
750
|
end
|
551
751
|
}
|
552
752
|
@@instance_types
|
@@ -563,22 +763,26 @@ module MU
|
|
563
763
|
# server resides (if it resides in this cloud provider's ecosystem).
|
564
764
|
# @param region [String]: The region to search.
|
565
765
|
# @return [Array<String>]: The Availability Zones in this region.
|
566
|
-
def self.listAZs(region =
|
766
|
+
def self.listAZs(region = self.myRegion)
|
767
|
+
return [] if !credConfig
|
567
768
|
MU::Cloud::Google.listRegions if !@@regions.has_key?(region)
|
568
|
-
|
769
|
+
if !@@regions.has_key?(region)
|
770
|
+
MU.log "Failed to get GCP region #{region}", MU::ERR, details: @@regions
|
771
|
+
raise MuError, "No such Google Cloud region '#{region}'" if !@@regions.has_key?(region)
|
772
|
+
end
|
569
773
|
@@regions[region]
|
570
774
|
end
|
571
775
|
|
572
776
|
# Google's Compute Service API
|
573
|
-
# @param subclass [<Google::Apis::
|
777
|
+
# @param subclass [<Google::Apis::ComputeV1>]: If specified, will return the class ::Google::Apis::ComputeV1::subclass instead of an API client instance
|
574
778
|
def self.compute(subclass = nil, credentials: nil)
|
575
|
-
require 'google/apis/
|
779
|
+
require 'google/apis/compute_v1'
|
576
780
|
|
577
781
|
if subclass.nil?
|
578
|
-
@@compute_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "
|
782
|
+
@@compute_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "ComputeV1::ComputeService", scopes: ['cloud-platform', 'compute.readonly'], credentials: credentials)
|
579
783
|
return @@compute_api[credentials]
|
580
784
|
elsif subclass.is_a?(Symbol)
|
581
|
-
return Object.const_get("::Google").const_get("Apis").const_get("
|
785
|
+
return Object.const_get("::Google").const_get("Apis").const_get("ComputeV1").const_get(subclass)
|
582
786
|
end
|
583
787
|
end
|
584
788
|
|
@@ -588,7 +792,7 @@ module MU
|
|
588
792
|
require 'google/apis/storage_v1'
|
589
793
|
|
590
794
|
if subclass.nil?
|
591
|
-
@@storage_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "StorageV1::StorageService", scopes: ['
|
795
|
+
@@storage_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "StorageV1::StorageService", scopes: ['cloud-platform'], credentials: credentials)
|
592
796
|
return @@storage_api[credentials]
|
593
797
|
elsif subclass.is_a?(Symbol)
|
594
798
|
return Object.const_get("::Google").const_get("Apis").const_get("StorageV1").const_get(subclass)
|
@@ -601,7 +805,7 @@ module MU
|
|
601
805
|
require 'google/apis/iam_v1'
|
602
806
|
|
603
807
|
if subclass.nil?
|
604
|
-
@@iam_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "IamV1::IamService", scopes: ['
|
808
|
+
@@iam_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "IamV1::IamService", scopes: ['cloud-platform', 'cloudplatformprojects', 'cloudplatformorganizations', 'cloudplatformfolders'], credentials: credentials)
|
605
809
|
return @@iam_api[credentials]
|
606
810
|
elsif subclass.is_a?(Symbol)
|
607
811
|
return Object.const_get("::Google").const_get("Apis").const_get("IamV1").const_get(subclass)
|
@@ -612,18 +816,29 @@ module MU
|
|
612
816
|
# @param subclass [<Google::Apis::AdminDirectoryV1>]: If specified, will return the class ::Google::Apis::AdminDirectoryV1::subclass instead of an API client instance
|
613
817
|
def self.admin_directory(subclass = nil, credentials: nil)
|
614
818
|
require 'google/apis/admin_directory_v1'
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
819
|
+
|
820
|
+
writescopes = ['admin.directory.group.member', 'admin.directory.group', 'admin.directory.user', 'admin.directory.domain', 'admin.directory.orgunit', 'admin.directory.rolemanagement', 'admin.directory.customer', 'admin.directory.user.alias', 'admin.directory.userschema']
|
821
|
+
readscopes = ['admin.directory.group.member.readonly', 'admin.directory.group.readonly', 'admin.directory.user.readonly', 'admin.directory.domain.readonly', 'admin.directory.orgunit.readonly', 'admin.directory.rolemanagement.readonly', 'admin.directory.customer.readonly', 'admin.directory.user.alias.readonly', 'admin.directory.userschema.readonly']
|
822
|
+
@@readonly_semaphore.synchronize {
|
823
|
+
use_scopes = readscopes+writescopes
|
824
|
+
if @@readonly[credentials] and @@readonly[credentials]["AdminDirectoryV1"]
|
825
|
+
use_scopes = readscopes.dup
|
622
826
|
end
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
827
|
+
|
828
|
+
if subclass.nil?
|
829
|
+
begin
|
830
|
+
@@admin_directory_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "AdminDirectoryV1::DirectoryService", scopes: use_scopes, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'], credentials: credentials)
|
831
|
+
rescue Signet::AuthorizationError => e
|
832
|
+
MU.log "Falling back to read-only access to DirectoryService API for credential set '#{credentials}'", MU::WARN
|
833
|
+
@@admin_directory_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "AdminDirectoryV1::DirectoryService", scopes: readscopes, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'], credentials: credentials)
|
834
|
+
@@readonly[credentials] ||= {}
|
835
|
+
@@readonly[credentials]["AdminDirectoryV1"] = true
|
836
|
+
end
|
837
|
+
return @@admin_directory_api[credentials]
|
838
|
+
elsif subclass.is_a?(Symbol)
|
839
|
+
return Object.const_get("::Google").const_get("Apis").const_get("AdminDirectoryV1").const_get(subclass)
|
840
|
+
end
|
841
|
+
}
|
627
842
|
end
|
628
843
|
|
629
844
|
# Google's Cloud Resource Manager API
|
@@ -632,8 +847,10 @@ module MU
|
|
632
847
|
require 'google/apis/cloudresourcemanager_v1'
|
633
848
|
|
634
849
|
if subclass.nil?
|
635
|
-
|
636
|
-
|
850
|
+
if !MU::Cloud::Google.credConfig(credentials)
|
851
|
+
raise MuError, "No such credential set #{credentials} defined in mu.yaml!"
|
852
|
+
end
|
853
|
+
@@resource_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV1::CloudResourceManagerService", scopes: ['cloud-platform', 'cloudplatformprojects', 'cloudplatformorganizations', 'cloudplatformfolders'], credentials: credentials, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'])
|
637
854
|
return @@resource_api[credentials]
|
638
855
|
elsif subclass.is_a?(Symbol)
|
639
856
|
return Object.const_get("::Google").const_get("Apis").const_get("CloudresourcemanagerV1").const_get(subclass)
|
@@ -646,7 +863,7 @@ module MU
|
|
646
863
|
require 'google/apis/cloudresourcemanager_v2'
|
647
864
|
|
648
865
|
if subclass.nil?
|
649
|
-
@@resource2_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV2::CloudResourceManagerService", scopes: ['
|
866
|
+
@@resource2_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV2::CloudResourceManagerService", scopes: ['cloud-platform', 'cloudplatformfolders'], credentials: credentials, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'])
|
650
867
|
return @@resource2_api[credentials]
|
651
868
|
elsif subclass.is_a?(Symbol)
|
652
869
|
return Object.const_get("::Google").const_get("Apis").const_get("CloudresourcemanagerV2").const_get(subclass)
|
@@ -659,7 +876,7 @@ module MU
|
|
659
876
|
require 'google/apis/container_v1'
|
660
877
|
|
661
878
|
if subclass.nil?
|
662
|
-
@@container_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "ContainerV1::ContainerService", scopes: ['
|
879
|
+
@@container_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "ContainerV1::ContainerService", scopes: ['cloud-platform'], credentials: credentials)
|
663
880
|
return @@container_api[credentials]
|
664
881
|
elsif subclass.is_a?(Symbol)
|
665
882
|
return Object.const_get("::Google").const_get("Apis").const_get("ContainerV1").const_get(subclass)
|
@@ -672,7 +889,7 @@ module MU
|
|
672
889
|
require 'google/apis/servicemanagement_v1'
|
673
890
|
|
674
891
|
if subclass.nil?
|
675
|
-
@@service_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "ServicemanagementV1::ServiceManagementService", scopes: ['
|
892
|
+
@@service_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "ServicemanagementV1::ServiceManagementService", scopes: ['cloud-platform'], credentials: credentials)
|
676
893
|
return @@service_api[credentials]
|
677
894
|
elsif subclass.is_a?(Symbol)
|
678
895
|
return Object.const_get("::Google").const_get("Apis").const_get("ServicemanagementV1").const_get(subclass)
|
@@ -685,7 +902,7 @@ module MU
|
|
685
902
|
require 'google/apis/sqladmin_v1beta4'
|
686
903
|
|
687
904
|
if subclass.nil?
|
688
|
-
@@sql_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "SqladminV1beta4::SQLAdminService", scopes: ['
|
905
|
+
@@sql_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "SqladminV1beta4::SQLAdminService", scopes: ['cloud-platform'], credentials: credentials)
|
689
906
|
return @@sql_api[credentials]
|
690
907
|
elsif subclass.is_a?(Symbol)
|
691
908
|
return Object.const_get("::Google").const_get("Apis").const_get("SqladminV1beta4").const_get(subclass)
|
@@ -698,7 +915,7 @@ module MU
|
|
698
915
|
require 'google/apis/firestore_v1'
|
699
916
|
|
700
917
|
if subclass.nil?
|
701
|
-
@@firestore_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "FirestoreV1::FirestoreService", scopes: ['
|
918
|
+
@@firestore_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "FirestoreV1::FirestoreService", scopes: ['cloud-platform'], credentials: credentials)
|
702
919
|
return @@firestore_api[credentials]
|
703
920
|
elsif subclass.is_a?(Symbol)
|
704
921
|
return Object.const_get("::Google").const_get("Apis").const_get("FirestoreV1").const_get(subclass)
|
@@ -711,7 +928,7 @@ module MU
|
|
711
928
|
require 'google/apis/logging_v2'
|
712
929
|
|
713
930
|
if subclass.nil?
|
714
|
-
@@logging_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "LoggingV2::LoggingService", scopes: ['
|
931
|
+
@@logging_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "LoggingV2::LoggingService", scopes: ['cloud-platform'], credentials: credentials)
|
715
932
|
return @@logging_api[credentials]
|
716
933
|
elsif subclass.is_a?(Symbol)
|
717
934
|
return Object.const_get("::Google").const_get("Apis").const_get("LoggingV2").const_get(subclass)
|
@@ -724,26 +941,86 @@ module MU
|
|
724
941
|
require 'google/apis/cloudbilling_v1'
|
725
942
|
|
726
943
|
if subclass.nil?
|
727
|
-
@@billing_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudbillingV1::CloudbillingService", scopes: ['
|
944
|
+
@@billing_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudbillingV1::CloudbillingService", scopes: ['cloud-platform', 'cloud-billing'], credentials: credentials, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'])
|
728
945
|
return @@billing_api[credentials]
|
729
946
|
elsif subclass.is_a?(Symbol)
|
730
947
|
return Object.const_get("::Google").const_get("Apis").const_get("CloudbillingV1").const_get(subclass)
|
731
948
|
end
|
732
949
|
end
|
733
950
|
|
951
|
+
# Retrieve the domains, if any, which these credentials can manage via
|
952
|
+
# GSuite or Cloud Identity.
|
953
|
+
# @param credentials [String]
|
954
|
+
# @return [Array<String>],nil]
|
955
|
+
def self.getDomains(credentials = nil)
|
956
|
+
my_org = getOrg(credentials)
|
957
|
+
return nil if !my_org
|
958
|
+
|
959
|
+
resp = MU::Cloud::Google.admin_directory(credentials: credentials).list_domains(MU::Cloud::Google.customerID(credentials))
|
960
|
+
resp.domains.map { |d| d.domain_name.downcase }
|
961
|
+
end
|
734
962
|
|
963
|
+
@@orgmap = {}
|
735
964
|
# Retrieve the organization, if any, to which these credentials belong.
|
736
965
|
# @param credentials [String]
|
737
966
|
# @return [Array<OpenStruct>],nil]
|
738
|
-
def self.getOrg(credentials = nil)
|
739
|
-
|
967
|
+
def self.getOrg(credentials = nil, with_id: nil)
|
968
|
+
creds = MU::Cloud::Google.credConfig(credentials)
|
969
|
+
credname = if creds and creds['name']
|
970
|
+
creds['name']
|
971
|
+
else
|
972
|
+
"default"
|
973
|
+
end
|
974
|
+
|
975
|
+
return @@orgmap[credname] if @@orgmap.has_key?(credname)
|
976
|
+
resp = MU::Cloud::Google.resource_manager(credentials: credname).search_organizations
|
740
977
|
if resp and resp.organizations
|
741
978
|
# XXX no idea if it's possible to be a member of multiple orgs
|
742
|
-
|
979
|
+
if !with_id
|
980
|
+
@@orgmap[credname] = resp.organizations.first
|
981
|
+
return resp.organizations.first
|
982
|
+
else
|
983
|
+
resp.organizations.each { |org|
|
984
|
+
if org.name == with_id
|
985
|
+
@@orgmap[credname] = org
|
986
|
+
return org
|
987
|
+
end
|
988
|
+
}
|
989
|
+
return nil
|
990
|
+
end
|
743
991
|
end
|
992
|
+
|
993
|
+
@@orgmap[credname] = nil
|
994
|
+
|
995
|
+
|
996
|
+
MU.log "Unable to list_organizations with credentials #{credname}. If this account is part of a GSuite or Cloud Identity domain, verify that Oauth delegation is properly configured and that 'masquerade_as' is properly set for the #{credname} Google credential set in mu.yaml.", MU::ERR, details: ["https://cloud.google.com/resource-manager/docs/creating-managing-organization", "https://admin.google.com/AdminHome?chromeless=1#OGX:ManageOauthClients"]
|
997
|
+
|
744
998
|
nil
|
745
999
|
end
|
746
1000
|
|
1001
|
+
@@customer_ids_cache = {}
|
1002
|
+
|
1003
|
+
# Fetch the GSuite/Cloud Identity customer id for the domain associated
|
1004
|
+
# with the given credentials, if a domain is set via the +masquerade_as+
|
1005
|
+
# configuration option.
|
1006
|
+
def self.customerID(credentials = nil)
|
1007
|
+
cfg = credConfig(credentials)
|
1008
|
+
if !cfg or !cfg['masquerade_as']
|
1009
|
+
return nil
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
if @@customer_ids_cache[credentials]
|
1013
|
+
return @@customer_ids_cache[credentials]
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
user = MU::Cloud::Google.admin_directory(credentials: credentials).get_user(cfg['masquerade_as'])
|
1017
|
+
if user and user.customer_id
|
1018
|
+
@@customer_ids_cache[credentials] = user.customer_id
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
@@customer_ids_cache[credentials]
|
1022
|
+
end
|
1023
|
+
|
747
1024
|
private
|
748
1025
|
|
749
1026
|
# Wrapper class for Google APIs, so that we can catch some common
|
@@ -752,18 +1029,37 @@ module MU
|
|
752
1029
|
class GoogleEndpoint
|
753
1030
|
@api = nil
|
754
1031
|
@credentials = nil
|
1032
|
+
@scopes = nil
|
1033
|
+
@masquerade = nil
|
755
1034
|
attr_reader :issuer
|
756
1035
|
|
757
1036
|
# Create a Google Cloud Platform API client
|
758
1037
|
# @param api [String]: Which API are we wrapping?
|
759
1038
|
# @param scopes [Array<String>]: Google auth scopes applicable to this API
|
760
|
-
def initialize(api: "
|
1039
|
+
def initialize(api: "ComputeV1::ComputeService", scopes: ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute.readonly'], masquerade: nil, credentials: nil)
|
761
1040
|
@credentials = credentials
|
1041
|
+
@scopes = scopes.map { |s|
|
1042
|
+
if !s.match(/\//) # allow callers to use shorthand
|
1043
|
+
s = "https://www.googleapis.com/auth/"+s
|
1044
|
+
end
|
1045
|
+
s
|
1046
|
+
}
|
1047
|
+
@masquerade = masquerade
|
762
1048
|
@api = Object.const_get("Google::Apis::#{api}").new
|
763
|
-
@api.authorization = MU::Cloud::Google.loadCredentials(scopes, credentials: credentials)
|
764
|
-
if
|
765
|
-
|
766
|
-
|
1049
|
+
@api.authorization = MU::Cloud::Google.loadCredentials(@scopes, credentials: credentials)
|
1050
|
+
raise MuError, "No useable Google credentials found#{credentials ? " with set '#{credentials}'" : ""}" if @api.authorization.nil?
|
1051
|
+
if @masquerade
|
1052
|
+
begin
|
1053
|
+
@api.authorization.sub = @masquerade
|
1054
|
+
@api.authorization.fetch_access_token!
|
1055
|
+
rescue Signet::AuthorizationError => e
|
1056
|
+
MU.log "Cannot masquerade as #{@masquerade} to API #{api}: #{e.message}", MU::ERROR, details: @scopes
|
1057
|
+
if e.message.match(/client not authorized for any of the scopes requested/)
|
1058
|
+
# XXX it'd be helpful to list *all* scopes we like, as well as the API client's numeric id
|
1059
|
+
MU.log "To grant access to API scopes for this service account, see:", MU::ERR, details: "https://admin.google.com/AdminHome?chromeless=1#OGX:ManageOauthClients"
|
1060
|
+
end
|
1061
|
+
raise e
|
1062
|
+
end
|
767
1063
|
end
|
768
1064
|
@issuer = @api.authorization.issuer
|
769
1065
|
end
|
@@ -780,9 +1076,9 @@ module MU
|
|
780
1076
|
resp = nil
|
781
1077
|
begin
|
782
1078
|
if region
|
783
|
-
resp = MU::Cloud::Google.compute(credentials: @credentials).send(list_sym, project, region, filter: filter)
|
1079
|
+
resp = MU::Cloud::Google.compute(credentials: @credentials).send(list_sym, project, region, filter: filter, mu_gcp_enable_apis: false)
|
784
1080
|
else
|
785
|
-
resp = MU::Cloud::Google.compute(credentials: @credentials).send(list_sym, project, filter: filter)
|
1081
|
+
resp = MU::Cloud::Google.compute(credentials: @credentials).send(list_sym, project, filter: filter, mu_gcp_enable_apis: false)
|
786
1082
|
end
|
787
1083
|
|
788
1084
|
rescue ::Google::Apis::ClientError => e
|
@@ -795,6 +1091,7 @@ module MU
|
|
795
1091
|
resp.items.each { |obj|
|
796
1092
|
threads << Thread.new {
|
797
1093
|
MU.dupGlobals(parent_thread_id)
|
1094
|
+
Thread.abort_on_exception = false
|
798
1095
|
MU.log "Removing #{type.gsub(/_/, " ")} #{obj.name}"
|
799
1096
|
delete_sym = "delete_#{type}".to_sym
|
800
1097
|
if !noop
|
@@ -813,9 +1110,10 @@ module MU
|
|
813
1110
|
failed = true
|
814
1111
|
retries += 1
|
815
1112
|
if resp.error.errors.first.code == "RESOURCE_IN_USE_BY_ANOTHER_RESOURCE" and retries < 6
|
816
|
-
sleep
|
1113
|
+
sleep 10
|
817
1114
|
else
|
818
1115
|
MU.log "Error deleting #{type.gsub(/_/, " ")} #{obj.name}", MU::ERR, details: resp.error.errors
|
1116
|
+
Thread.abort_on_exception = false
|
819
1117
|
raise MuError, "Failed to delete #{type.gsub(/_/, " ")} #{obj.name}"
|
820
1118
|
end
|
821
1119
|
else
|
@@ -824,6 +1122,8 @@ module MU
|
|
824
1122
|
# TODO validate that the resource actually went away, because it seems not to do so very reliably
|
825
1123
|
rescue ::Google::Apis::ClientError => e
|
826
1124
|
raise e if !e.message.match(/(^notFound: |operation in progress)/)
|
1125
|
+
rescue MU::Cloud::MuDefunctHabitat => e
|
1126
|
+
# this is ok- it's already deleted
|
827
1127
|
end while failed and retries < 6
|
828
1128
|
end
|
829
1129
|
}
|
@@ -841,10 +1141,31 @@ module MU
|
|
841
1141
|
def method_missing(method_sym, *arguments)
|
842
1142
|
retries = 0
|
843
1143
|
actual_resource = nil
|
1144
|
+
|
1145
|
+
enable_on_fail = true
|
1146
|
+
arguments.each { |arg|
|
1147
|
+
if arg.is_a?(Hash) and arg.has_key?(:mu_gcp_enable_apis)
|
1148
|
+
enable_on_fail = arg[:mu_gcp_enable_apis]
|
1149
|
+
arg.delete(:mu_gcp_enable_apis)
|
1150
|
+
|
1151
|
+
end
|
1152
|
+
}
|
1153
|
+
arguments.delete({})
|
1154
|
+
next_page_token = nil
|
1155
|
+
overall_retval = nil
|
1156
|
+
|
844
1157
|
begin
|
845
1158
|
MU.log "Calling #{method_sym}", MU::DEBUG, details: arguments
|
846
1159
|
retval = nil
|
847
1160
|
retries = 0
|
1161
|
+
wait_backoff = 5
|
1162
|
+
if next_page_token
|
1163
|
+
if arguments.size == 1 and arguments.first.is_a?(Hash)
|
1164
|
+
arguments[0][:page_token] = next_page_token
|
1165
|
+
else
|
1166
|
+
arguments << { :page_token => next_page_token }
|
1167
|
+
end
|
1168
|
+
end
|
848
1169
|
begin
|
849
1170
|
if !arguments.nil? and arguments.size == 1
|
850
1171
|
retval = @api.method(method_sym).call(arguments[0])
|
@@ -853,49 +1174,93 @@ module MU
|
|
853
1174
|
else
|
854
1175
|
retval = @api.method(method_sym).call
|
855
1176
|
end
|
1177
|
+
rescue ArgumentError => e
|
1178
|
+
MU.log "#{e.class.name} calling #{@api.class.name}.#{method_sym.to_s}: #{e.message}", MU::ERR, details: arguments
|
1179
|
+
raise e
|
856
1180
|
rescue ::Google::Apis::AuthorizationError => e
|
857
1181
|
if arguments.size > 0
|
858
1182
|
raise MU::MuError, "Service account #{MU::Cloud::Google.svc_account_name} has insufficient privileges to call #{method_sym} in project #{arguments.first}"
|
859
1183
|
else
|
860
1184
|
raise MU::MuError, "Service account #{MU::Cloud::Google.svc_account_name} has insufficient privileges to call #{method_sym}"
|
861
1185
|
end
|
1186
|
+
rescue ::Google::Apis::RateLimitError, ::Google::Apis::TransmissionError, ::ThreadError, ::Google::Apis::ServerError => e
|
1187
|
+
if retries <= 10
|
1188
|
+
sleep wait_backoff
|
1189
|
+
retries += 1
|
1190
|
+
wait_backoff = wait_backoff * 2
|
1191
|
+
retry
|
1192
|
+
else
|
1193
|
+
raise e
|
1194
|
+
end
|
862
1195
|
rescue ::Google::Apis::ClientError, OpenSSL::SSL::SSLError => e
|
863
|
-
if e.message.match(/^
|
864
|
-
|
1196
|
+
if e.message.match(/^quotaExceeded: Request rate/)
|
1197
|
+
if retries <= 10
|
1198
|
+
sleep wait_backoff
|
1199
|
+
retries += 1
|
1200
|
+
wait_backoff = wait_backoff * 2
|
1201
|
+
retry
|
1202
|
+
else
|
1203
|
+
raise e
|
1204
|
+
end
|
1205
|
+
elsif e.message.match(/^invalidParameter:|^badRequest:/)
|
1206
|
+
MU.log "#{e.class.name} calling #{@api.class.name}.#{method_sym.to_s}: "+e.message, MU::ERR, details: arguments
|
865
1207
|
# uncomment for debugging stuff; this can occur in benign situations so we don't normally want it logging
|
866
1208
|
elsif e.message.match(/^forbidden:/)
|
867
|
-
MU.log "
|
1209
|
+
MU.log "#{e.class.name} calling #{@api.class.name}.#{method_sym.to_s} got \"#{e.message}\" using credentials #{@credentials}#{@masquerade ? " (OAuth'd as #{@masquerade})": ""}.#{@scopes ? "\nScopes:\n#{@scopes.join("\n")}" : "" }", MU::DEBUG, details: arguments
|
1210
|
+
raise e
|
868
1211
|
end
|
869
1212
|
@@enable_semaphores ||= {}
|
870
1213
|
max_retries = 3
|
871
1214
|
wait_time = 90
|
872
|
-
if retries <= max_retries and e.message.match(/^accessNotConfigured/)
|
1215
|
+
if enable_on_fail and retries <= max_retries and e.message.match(/^accessNotConfigured/)
|
873
1216
|
enable_obj = nil
|
874
|
-
|
1217
|
+
|
1218
|
+
project = if arguments.size > 0 and arguments.first.is_a?(String)
|
1219
|
+
arguments.first
|
1220
|
+
else
|
1221
|
+
MU::Cloud::Google.defaultProject(@credentials)
|
1222
|
+
end
|
1223
|
+
# XXX validate that this actually looks like a project id, maybe
|
1224
|
+
if method_sym == :delete and !MU::Cloud::Google::Habitat.isLive?(project, @credentials)
|
1225
|
+
MU.log "Got accessNotConfigured while attempting to delete a resource in #{project}", MU::WARN
|
1226
|
+
|
1227
|
+
return
|
1228
|
+
end
|
1229
|
+
|
875
1230
|
@@enable_semaphores[project] ||= Mutex.new
|
876
1231
|
enable_obj = MU::Cloud::Google.service_manager(:EnableServiceRequest).new(
|
877
1232
|
consumer_id: "project:"+project
|
878
1233
|
)
|
879
1234
|
# XXX dumbass way to get this string
|
880
|
-
e.message.match(/by visiting https:\/\/console\.developers\.google\.com\/apis\/api\/(.+?)\//)
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
1235
|
+
if e.message.match(/by visiting https:\/\/console\.developers\.google\.com\/apis\/api\/(.+?)\//)
|
1236
|
+
|
1237
|
+
svc_name = Regexp.last_match[1]
|
1238
|
+
save_verbosity = MU.verbosity
|
1239
|
+
if svc_name != "servicemanagement.googleapis.com" and method_sym != :delete
|
1240
|
+
retries += 1
|
1241
|
+
@@enable_semaphores[project].synchronize {
|
1242
|
+
MU.setLogging(MU::Logger::NORMAL)
|
1243
|
+
MU.log "Attempting to enable #{svc_name} in project #{project}; will retry #{method_sym.to_s} in #{(wait_time/retries).to_s}s (#{retries.to_s}/#{max_retries.to_s})", MU::NOTICE
|
1244
|
+
MU.setLogging(save_verbosity)
|
1245
|
+
begin
|
1246
|
+
MU::Cloud::Google.service_manager(credentials: @credentials).enable_service(svc_name, enable_obj)
|
1247
|
+
rescue ::Google::Apis::ClientError => e
|
1248
|
+
MU.log "Error enabling #{svc_name} in #{project} for #{method_sym.to_s}: "+ e.message, MU::ERR, details: enable_obj
|
1249
|
+
raise e
|
1250
|
+
end
|
1251
|
+
}
|
1252
|
+
sleep wait_time/retries
|
1253
|
+
retry
|
1254
|
+
else
|
887
1255
|
MU.setLogging(MU::Logger::NORMAL)
|
888
|
-
MU.log "
|
1256
|
+
MU.log "Google Cloud's Service Management API must be enabled manually by visiting #{e.message.gsub(/.*?(https?:\/\/[^\s]+)(?:$|\s).*/, '\1')}", MU::ERR
|
889
1257
|
MU.setLogging(save_verbosity)
|
890
|
-
MU::
|
891
|
-
|
892
|
-
|
893
|
-
|
1258
|
+
raise MU::MuError, "Service Management API not yet enabled for this account/project"
|
1259
|
+
end
|
1260
|
+
elsif e.message.match(/scheduled for deletion and cannot be used for API calls/)
|
1261
|
+
raise MuDefunctHabitat, e.message
|
894
1262
|
else
|
895
|
-
MU.
|
896
|
-
MU.log "Google Cloud's Service Management API must be enabled manually by visiting #{e.message.gsub(/.*?(https?:\/\/[^\s]+)(?:$|\s).*/, '\1')}", MU::ERR
|
897
|
-
MU.setLogging(save_verbosity)
|
898
|
-
raise MU::MuError, "Service Management API not yet enabled for this account/project"
|
1263
|
+
MU.log "Unfamiliar error calling #{method_sym.to_s} "+e.message, MU::ERR, details: arguments
|
899
1264
|
end
|
900
1265
|
elsif retries <= 10 and
|
901
1266
|
e.message.match(/^resourceNotReady:/) or
|
@@ -914,9 +1279,17 @@ module MU
|
|
914
1279
|
end
|
915
1280
|
end
|
916
1281
|
|
917
|
-
if retval.class
|
1282
|
+
if retval.class.name.match(/.*?::Operation$/)
|
1283
|
+
|
918
1284
|
retries = 0
|
919
1285
|
orig_target = retval.name
|
1286
|
+
|
1287
|
+
# Check whether the various types of +Operation+ responses say
|
1288
|
+
# they're done, without knowing which specific API they're from
|
1289
|
+
def is_done?(retval)
|
1290
|
+
(retval.respond_to?(:status) and retval.status == "DONE") or (retval.respond_to?(:done) and retval.done)
|
1291
|
+
end
|
1292
|
+
|
920
1293
|
begin
|
921
1294
|
if retries > 0 and retries % 3 == 0
|
922
1295
|
MU.log "Waiting for #{method_sym} to be done (retry #{retries})", MU::NOTICE
|
@@ -924,14 +1297,37 @@ module MU
|
|
924
1297
|
MU.log "Waiting for #{method_sym} to be done (retry #{retries})", MU::DEBUG, details: retval
|
925
1298
|
end
|
926
1299
|
|
927
|
-
if retval
|
1300
|
+
if !is_done?(retval)
|
928
1301
|
sleep 7
|
929
1302
|
begin
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
1303
|
+
if retval.class.name.match(/::Compute[^:]*::/)
|
1304
|
+
resp = MU::Cloud::Google.compute(credentials: @credentials).get_global_operation(
|
1305
|
+
arguments.first, # there's always a project id
|
1306
|
+
retval.name
|
1307
|
+
)
|
1308
|
+
retval = resp
|
1309
|
+
elsif retval.class.name.match(/::Servicemanagement[^:]*::/)
|
1310
|
+
resp = MU::Cloud::Google.service_manager(credentials: @credentials).get_operation(
|
1311
|
+
retval.name
|
1312
|
+
)
|
1313
|
+
retval = resp
|
1314
|
+
elsif retval.class.name.match(/::Cloudresourcemanager[^:]*::/)
|
1315
|
+
resp = MU::Cloud::Google.resource_manager(credentials: @credentials).get_operation(
|
1316
|
+
retval.name
|
1317
|
+
)
|
1318
|
+
retval = resp
|
1319
|
+
if retval.error
|
1320
|
+
raise MuError, retval.error.message
|
1321
|
+
end
|
1322
|
+
elsif retval.class.name.match(/::Container[^:]*::/)
|
1323
|
+
resp = MU::Cloud::Google.container(credentials: @credentials).get_project_location_operation(
|
1324
|
+
retval.self_link.sub(/.*?\/projects\//, 'projects/')
|
1325
|
+
)
|
1326
|
+
retval = resp
|
1327
|
+
else
|
1328
|
+
pp retval
|
1329
|
+
raise MuError, "I NEED TO IMPLEMENT AN OPERATION HANDLER FOR #{retval.class.name}"
|
1330
|
+
end
|
935
1331
|
rescue ::Google::Apis::ClientError => e
|
936
1332
|
# this is ok; just means the operation is done and went away
|
937
1333
|
if e.message.match(/^notFound:/)
|
@@ -942,7 +1338,8 @@ module MU
|
|
942
1338
|
end
|
943
1339
|
retries = retries + 1
|
944
1340
|
end
|
945
|
-
|
1341
|
+
|
1342
|
+
end while !is_done?(retval)
|
946
1343
|
|
947
1344
|
# Most insert methods have a predictable get_* counterpart. Let's
|
948
1345
|
# take advantage.
|
@@ -960,6 +1357,10 @@ module MU
|
|
960
1357
|
faked_args.pop
|
961
1358
|
end
|
962
1359
|
faked_args.push(cloud_id)
|
1360
|
+
if get_method == :get_project_location_cluster
|
1361
|
+
faked_args[0] = faked_args[0]+"/clusters/"+faked_args[1]
|
1362
|
+
faked_args.pop
|
1363
|
+
end
|
963
1364
|
actual_resource = @api.method(get_method).call(*faked_args)
|
964
1365
|
#if method_sym == :insert_instance
|
965
1366
|
#MU.log "actual_resource", MU::WARN, details: actual_resource
|
@@ -982,7 +1383,43 @@ module MU
|
|
982
1383
|
return actual_resource
|
983
1384
|
end
|
984
1385
|
end
|
985
|
-
|
1386
|
+
|
1387
|
+
# This atrocity appends the pages of list_* results
|
1388
|
+
if overall_retval
|
1389
|
+
if method_sym.to_s.match(/^list_(.*)/)
|
1390
|
+
require 'google/apis/iam_v1'
|
1391
|
+
what = Regexp.last_match[1].to_sym
|
1392
|
+
whatassign = (Regexp.last_match[1]+"=").to_sym
|
1393
|
+
if overall_retval.class == ::Google::Apis::IamV1::ListServiceAccountsResponse
|
1394
|
+
what = :accounts
|
1395
|
+
whatassign = :accounts=
|
1396
|
+
end
|
1397
|
+
if retval.respond_to?(what) and retval.respond_to?(whatassign)
|
1398
|
+
if !retval.public_send(what).nil?
|
1399
|
+
newarray = retval.public_send(what) + overall_retval.public_send(what)
|
1400
|
+
overall_retval.public_send(whatassign, newarray)
|
1401
|
+
end
|
1402
|
+
else
|
1403
|
+
MU.log "Not sure how to append #{method_sym.to_s} results to #{overall_retval.class.name} (apparently #{what.to_s} and #{whatassign.to_s} aren't it), returning first page only", MU::WARN, details: retval
|
1404
|
+
return retval
|
1405
|
+
end
|
1406
|
+
else
|
1407
|
+
MU.log "Not sure how to append #{method_sym.to_s} results, returning first page only", MU::WARN, details: retval
|
1408
|
+
return retval
|
1409
|
+
end
|
1410
|
+
else
|
1411
|
+
overall_retval = retval
|
1412
|
+
end
|
1413
|
+
|
1414
|
+
arguments.delete({ :page_token => next_page_token })
|
1415
|
+
next_page_token = nil
|
1416
|
+
|
1417
|
+
if retval.respond_to?(:next_page_token) and !retval.next_page_token.nil?
|
1418
|
+
next_page_token = retval.next_page_token
|
1419
|
+
MU.log "Getting another page of #{method_sym.to_s}", MU::DEBUG, details: next_page_token
|
1420
|
+
else
|
1421
|
+
return overall_retval
|
1422
|
+
end
|
986
1423
|
rescue ::Google::Apis::ServerError, ::Google::Apis::ClientError, ::Google::Apis::TransmissionError => e
|
987
1424
|
if e.class.name == "Google::Apis::ClientError" and
|
988
1425
|
(!method_sym.to_s.match(/^insert_/) or !e.message.match(/^notFound: /) or
|
@@ -996,8 +1433,8 @@ module MU
|
|
996
1433
|
logs = MU::Cloud::Google.logging(credentials: @credentials).list_entry_log_entries(logreq)
|
997
1434
|
details = nil
|
998
1435
|
if logs.entries
|
999
|
-
details = logs.entries.map { |
|
1000
|
-
details.reject! { |
|
1436
|
+
details = logs.entries.map { |err| err.json_payload }
|
1437
|
+
details.reject! { |err| err["error"].nil? or err["error"].size == 0 }
|
1001
1438
|
end
|
1002
1439
|
|
1003
1440
|
raise MuError, "#{method_sym.to_s} of #{retval.target_id} appeared to succeed, but then the resource disappeared! #{details.to_s}"
|
@@ -1022,7 +1459,7 @@ module MU
|
|
1022
1459
|
sleep interval
|
1023
1460
|
MU.log method_sym.to_s.bold+" "+e.inspect, MU::WARN, details: arguments
|
1024
1461
|
retry
|
1025
|
-
end
|
1462
|
+
end while !next_page_token.nil?
|
1026
1463
|
end
|
1027
1464
|
end
|
1028
1465
|
|