cloud-mu 2.1.0beta → 3.0.0beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
|