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
@@ -18,38 +18,30 @@ module MU
|
|
18
18
|
# A server pool as configured in {MU::Config::BasketofKittens::server_pools}
|
19
19
|
class ServerPool < MU::Cloud::ServerPool
|
20
20
|
|
21
|
-
|
22
|
-
@
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
attr_reader :config
|
27
|
-
|
28
|
-
# @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
|
29
|
-
# @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::server_pools}
|
30
|
-
def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
|
31
|
-
@deploy = mommacat
|
32
|
-
@config = MU::Config.manxify(kitten_cfg)
|
33
|
-
@cloud_id ||= cloud_id
|
34
|
-
if !mu_name.nil?
|
35
|
-
@mu_name = mu_name
|
36
|
-
@config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
|
37
|
-
if !@project_id
|
38
|
-
project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
|
39
|
-
@project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
|
40
|
-
end
|
41
|
-
elsif @config['scrub_mu_isms']
|
42
|
-
@mu_name = @config['name']
|
43
|
-
else
|
44
|
-
@mu_name = @deploy.getResourceName(@config['name'])
|
45
|
-
end
|
21
|
+
# Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like <tt>@vpc</tt>, for us.
|
22
|
+
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
23
|
+
def initialize(**args)
|
24
|
+
super
|
25
|
+
@mu_name ||= @deploy.getResourceName(@config['name'])
|
46
26
|
end
|
47
27
|
|
48
28
|
# Called automatically by {MU::Deploy#createResources}
|
49
29
|
def create
|
50
|
-
@project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloudobj.cloud_id
|
51
30
|
port_objs = []
|
52
31
|
|
32
|
+
sa = MU::Config::Ref.get(@config['service_account'])
|
33
|
+
if !sa or !sa.kitten or !sa.kitten.cloud_desc
|
34
|
+
raise MuError, "Failed to get service account cloud id from #{@config['service_account'].to_s}"
|
35
|
+
end
|
36
|
+
@service_acct = MU::Cloud::Google.compute(:ServiceAccount).new(
|
37
|
+
email: sa.kitten.cloud_desc.email,
|
38
|
+
scopes: @config['scopes']
|
39
|
+
)
|
40
|
+
if !@config['scrub_mu_isms']
|
41
|
+
MU::Cloud::Google.grantDeploySecretAccess(@service_acct.email, credentials: @config['credentials'])
|
42
|
+
end
|
43
|
+
|
44
|
+
|
53
45
|
@config['named_ports'].each { |port_cfg|
|
54
46
|
port_objs << MU::Cloud::Google.compute(:NamedPort).new(
|
55
47
|
name: port_cfg['name'],
|
@@ -77,20 +69,30 @@ module MU
|
|
77
69
|
az = MU::Cloud::Google.listAZs(@config['region']).sample
|
78
70
|
end
|
79
71
|
|
72
|
+
metadata = { # :items?
|
73
|
+
"startup-script" => @userdata
|
74
|
+
}
|
75
|
+
if @config['metadata']
|
76
|
+
desc[:metadata] = Hash[@config['metadata'].map { |m|
|
77
|
+
[m["key"], m["value"]]
|
78
|
+
}]
|
79
|
+
end
|
80
|
+
deploykey = @config['ssh_user']+":"+@deploy.ssh_public_key
|
81
|
+
if desc[:metadata]["ssh-keys"]
|
82
|
+
desc[:metadata]["ssh-keys"] += "\n"+deploykey
|
83
|
+
else
|
84
|
+
desc[:metadata]["ssh-keys"] = deploykey
|
85
|
+
end
|
86
|
+
|
80
87
|
instance_props = MU::Cloud::Google.compute(:InstanceProperties).new(
|
81
88
|
can_ip_forward: !@config['src_dst_check'],
|
82
89
|
description: @deploy.deploy_id,
|
83
|
-
# machine_type: "zones/"+az+"/machineTypes/"+size,
|
84
90
|
machine_type: size,
|
91
|
+
service_accounts: [@service_acct],
|
85
92
|
labels: labels,
|
86
93
|
disks: MU::Cloud::Google::Server.diskConfig(@config, false, false, credentials: @config['credentials']),
|
87
94
|
network_interfaces: MU::Cloud::Google::Server.interfaceConfig(@config, @vpc),
|
88
|
-
metadata:
|
89
|
-
:items => [
|
90
|
-
:key => "ssh-keys",
|
91
|
-
:value => @config['ssh_user']+":"+@deploy.ssh_public_key
|
92
|
-
]
|
93
|
-
},
|
95
|
+
metadata: metadata,
|
94
96
|
tags: MU::Cloud::Google.compute(:Tags).new(items: [MU::Cloud::Google.nameStr(@mu_name)])
|
95
97
|
)
|
96
98
|
|
@@ -132,9 +134,9 @@ module MU
|
|
132
134
|
# TODO this thing supports based on CPU usage, LB usage, or an arbitrary Cloud
|
133
135
|
# Monitoring metric. The default is "sustained 60%+ CPU usage". We should
|
134
136
|
# support all that.
|
135
|
-
# http://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/
|
136
|
-
# http://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/
|
137
|
-
# http://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/
|
137
|
+
# http://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/ComputeV1/AutoscalingPolicyCpuUtilization
|
138
|
+
# http://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/ComputeV1/AutoscalingPolicyLoadBalancingUtilization
|
139
|
+
# http://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/ComputeV1/AutoscalingPolicyCustomMetricUtilization
|
138
140
|
policy_obj = MU::Cloud::Google.compute(:AutoscalingPolicy).new(
|
139
141
|
cooldown_period_sec: @config['default_cooldown'],
|
140
142
|
max_num_replicas: @config['max_size'],
|
@@ -166,16 +168,156 @@ module MU
|
|
166
168
|
end
|
167
169
|
|
168
170
|
# Locate an existing ServerPool or ServerPools and return an array containing matching Google resource descriptors for those that match.
|
169
|
-
# @
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
171
|
+
# @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching ServerPools
|
172
|
+
def self.find(**args)
|
173
|
+
args[:project] ||= args[:habitat]
|
174
|
+
args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
|
175
|
+
|
176
|
+
regions = if args[:region]
|
177
|
+
[args[:region]]
|
178
|
+
else
|
179
|
+
MU::Cloud::Google.listRegions
|
180
|
+
end
|
181
|
+
found = {}
|
182
|
+
|
183
|
+
regions.each { |r|
|
184
|
+
begin
|
185
|
+
resp = MU::Cloud::Google.compute(credentials: args[:credentials]).list_region_instance_group_managers(args[:project], args[:region])
|
186
|
+
if resp and resp.items
|
187
|
+
resp.items.each { |igm|
|
188
|
+
found[igm.name] = igm
|
189
|
+
}
|
190
|
+
end
|
191
|
+
rescue ::Google::Apis::ClientError => e
|
192
|
+
raise e if !e.message.match(/forbidden: /)
|
193
|
+
end
|
194
|
+
|
195
|
+
begin
|
196
|
+
# XXX can these guys have name collisions? test this
|
197
|
+
MU::Cloud::Google.listAZs(r).each { |az|
|
198
|
+
resp = MU::Cloud::Google.compute(credentials: args[:credentials]).list_instance_group_managers(args[:project], az)
|
199
|
+
if resp and resp.items
|
200
|
+
resp.items.each { |igm|
|
201
|
+
found[igm.name] = igm
|
202
|
+
}
|
203
|
+
end
|
204
|
+
}
|
205
|
+
rescue ::Google::Apis::ClientError => e
|
206
|
+
raise e if !e.message.match(/forbidden: /)
|
207
|
+
end
|
208
|
+
}
|
209
|
+
|
210
|
+
return found
|
211
|
+
end
|
212
|
+
|
213
|
+
# Reverse-map our cloud description into a runnable config hash.
|
214
|
+
# We assume that any values we have in +@config+ are placeholders, and
|
215
|
+
# calculate our own accordingly based on what's live in the cloud.
|
216
|
+
def toKitten(rootparent: nil, billing: nil, habitats: nil)
|
217
|
+
bok = {
|
218
|
+
"cloud" => "Google",
|
219
|
+
"credentials" => @credentials,
|
220
|
+
"cloud_id" => @cloud_id,
|
221
|
+
"region" => @config['region'],
|
222
|
+
"project" => @project_id,
|
223
|
+
}
|
224
|
+
bok['name'] = cloud_desc.name
|
225
|
+
|
226
|
+
scalers = if cloud_desc.zone and cloud_desc.zone.match(/-[a-z]$/)
|
227
|
+
bok['availability_zone'] = cloud_desc.zone.sub(/.*?\/([^\/]+)$/, '\1')
|
228
|
+
MU::Cloud::Google.compute(credentials: @credentials).list_autoscalers(@project_id, bok['availability_zone'])
|
229
|
+
else
|
230
|
+
MU::Cloud::Google.compute(credentials: @credentials).list_region_autoscalers(@project_id, @config['region'], filter: "target eq #{cloud_desc.self_link}")
|
231
|
+
end
|
232
|
+
|
233
|
+
if scalers and scalers.items and scalers.items.size > 0
|
234
|
+
scaler = scalers.items.first
|
235
|
+
MU.log bok['name'], MU::WARN, details: scaler.autoscaling_policy
|
236
|
+
# scaler.cpu_utilization.utilization_target
|
237
|
+
# scaler.cool_down_period_sec
|
238
|
+
bok['min_size'] = scaler.autoscaling_policy.min_num_replicas
|
239
|
+
bok['max_size'] = scaler.autoscaling_policy.max_num_replicas
|
240
|
+
else
|
241
|
+
bok['min_size'] = bok['max_size'] = cloud_desc.target_size
|
242
|
+
end
|
243
|
+
if cloud_desc.auto_healing_policies and cloud_desc.auto_healing_policies.size > 0
|
244
|
+
MU.log bok['name'], MU::WARN, details: cloud_desc.auto_healing_policies
|
245
|
+
end
|
246
|
+
|
247
|
+
template = MU::Cloud::Google.compute(credentials: @credentials).get_instance_template(@project_id, cloud_desc.instance_template.sub(/.*?\/([^\/]+)$/, '\1'))
|
248
|
+
|
249
|
+
iface = template.properties.network_interfaces.first
|
250
|
+
iface.network.match(/(?:^|\/)projects\/(.*?)\/.*?\/networks\/([^\/]+)(?:$|\/)/)
|
251
|
+
vpc_proj = Regexp.last_match[1]
|
252
|
+
vpc_id = Regexp.last_match[2]
|
253
|
+
|
254
|
+
bok['vpc'] = MU::Config::Ref.get(
|
255
|
+
id: vpc_id,
|
256
|
+
cloud: "Google",
|
257
|
+
habitat: MU::Config::Ref.get(
|
258
|
+
id: vpc_proj,
|
259
|
+
cloud: "Google",
|
260
|
+
credentials: @credentials,
|
261
|
+
type: "habitats"
|
262
|
+
),
|
263
|
+
credentials: @credentials,
|
264
|
+
type: "vpcs",
|
265
|
+
subnet_pref: "any" # "anywhere in this VPC" is what matters
|
266
|
+
)
|
267
|
+
|
268
|
+
bok['basis'] = {
|
269
|
+
"launch_config" => {
|
270
|
+
"name" => bok['name']
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
template.properties.disks.each { |disk|
|
275
|
+
if disk.initialize_params.source_image and disk.boot
|
276
|
+
bok['basis']['launch_config']['image_id'] ||= disk.initialize_params.source_image.sub(/^https:\/\/www\.googleapis\.com\/compute\/[^\/]+\//, '')
|
277
|
+
elsif disk.type != "SCRATCH"
|
278
|
+
bok['basis']['launch_config']['storage'] ||= []
|
279
|
+
storage_blob = {
|
280
|
+
"size" => disk.initialize_params.disk_size_gb,
|
281
|
+
"device" => "/dev/xvd"+(disk.index+97).chr.downcase
|
282
|
+
}
|
283
|
+
bok['basis']['launch_config']['storage'] << storage_blob
|
284
|
+
else
|
285
|
+
MU.log "Need to sort out scratch disks", MU::WARN, details: disk
|
286
|
+
end
|
287
|
+
|
288
|
+
}
|
289
|
+
|
290
|
+
if template.properties.labels
|
291
|
+
bok['tags'] = template.properties.labels.keys.map { |k| { "key" => k, "value" => template.properties.labels[k] } }
|
292
|
+
end
|
293
|
+
if template.properties.tags and template.properties.tags.items and template.properties.tags.items.size > 0
|
294
|
+
bok['network_tags'] = template.properties.tags.items
|
295
|
+
end
|
296
|
+
bok['src_dst_check'] = !template.properties.can_ip_forward
|
297
|
+
bok['basis']['launch_config']['size'] = template.properties.machine_type.sub(/.*?\/([^\/]+)$/, '\1')
|
298
|
+
bok['project'] = @project_id
|
299
|
+
if template.properties.service_accounts
|
300
|
+
bok['scopes'] = template.properties.service_accounts.map { |sa| sa.scopes }.flatten.uniq
|
301
|
+
end
|
302
|
+
if template.properties.metadata and template.properties.metadata.items
|
303
|
+
bok['metadata'] = template.properties.metadata.items.map { |m| MU.structToHash(m) }
|
304
|
+
end
|
305
|
+
|
306
|
+
# Skip nodes that are just members of GKE clusters
|
307
|
+
if bok['name'].match(/^gke-.*?-[a-f0-9]+-[a-z0-9]+$/) and
|
308
|
+
bok['basis']['launch_config']['image_id'].match(/(:?^|\/)projects\/gke-node-images\//)
|
309
|
+
gke_ish = true
|
310
|
+
bok['network_tags'].each { |tag|
|
311
|
+
gke_ish = false if !tag.match(/^gke-/)
|
312
|
+
}
|
313
|
+
if gke_ish
|
314
|
+
MU.log "ServerPool #{bok['name']} appears to belong to a ContainerCluster, skipping adoption", MU::NOTICE
|
315
|
+
return nil
|
316
|
+
end
|
317
|
+
end
|
318
|
+
#MU.log bok['name'], MU::WARN, details: [cloud_desc, template]
|
319
|
+
|
320
|
+
bok
|
179
321
|
end
|
180
322
|
|
181
323
|
# Cloud-specific configuration properties.
|
@@ -184,6 +326,15 @@ module MU
|
|
184
326
|
def self.schema(config)
|
185
327
|
toplevel_required = []
|
186
328
|
schema = {
|
329
|
+
"ssh_user" => MU::Cloud::Google::Server.schema(config)[1]["ssh_user"],
|
330
|
+
"metadata" => MU::Cloud::Google::Server.schema(config)[1]["metadata"],
|
331
|
+
"service_account" => MU::Cloud::Google::Server.schema(config)[1]["service_account"],
|
332
|
+
"scopes" => MU::Cloud::Google::Server.schema(config)[1]["scopes"],
|
333
|
+
"network_tags" => MU::Cloud::Google::Server.schema(config)[1]["network_tags"],
|
334
|
+
"availability_zone" => {
|
335
|
+
"type" => "string",
|
336
|
+
"description" => "Target a specific availability zone for this pool, which will create zonal instance managers and scalers instead of regional ones."
|
337
|
+
},
|
187
338
|
"named_ports" => {
|
188
339
|
"type" => "array",
|
189
340
|
"items" => {
|
@@ -211,6 +362,38 @@ module MU
|
|
211
362
|
# @return [Boolean]: True if validation succeeded, False otherwise
|
212
363
|
def self.validateConfig(pool, configurator)
|
213
364
|
ok = true
|
365
|
+
start = Time.now
|
366
|
+
pool['project'] ||= MU::Cloud::Google.defaultProject(pool['credentials'])
|
367
|
+
if pool['service_account']
|
368
|
+
pool['service_account']['cloud'] = "Google"
|
369
|
+
pool['service_account']['habitat'] ||= pool['project']
|
370
|
+
found = MU::Config::Ref.get(pool['service_account'])
|
371
|
+
if found.id and !found.kitten
|
372
|
+
MU.log "GKE pool #{pool['name']} failed to locate service account #{pool['service_account']} in project #{pool['project']}", MU::ERR
|
373
|
+
ok = false
|
374
|
+
end
|
375
|
+
else
|
376
|
+
user = {
|
377
|
+
"name" => pool['name'],
|
378
|
+
"cloud" => "Google",
|
379
|
+
"project" => pool["project"],
|
380
|
+
"credentials" => pool["credentials"],
|
381
|
+
"type" => "service"
|
382
|
+
}
|
383
|
+
configurator.insertKitten(user, "users", true)
|
384
|
+
pool['dependencies'] ||= []
|
385
|
+
pool['service_account'] = MU::Config::Ref.get(
|
386
|
+
type: "users",
|
387
|
+
cloud: "Google",
|
388
|
+
name: pool["name"],
|
389
|
+
project: pool["project"],
|
390
|
+
credentials: pool["credentials"]
|
391
|
+
)
|
392
|
+
pool['dependencies'] << {
|
393
|
+
"type" => "user",
|
394
|
+
"name" => pool["name"]
|
395
|
+
}
|
396
|
+
end
|
214
397
|
|
215
398
|
pool['named_ports'] ||= []
|
216
399
|
if !pool['named_ports'].include?({"name" => "ssh", "port" => 22})
|
@@ -224,8 +407,9 @@ module MU
|
|
224
407
|
ok = false if launch['size'].nil?
|
225
408
|
|
226
409
|
if launch['image_id'].nil?
|
227
|
-
|
228
|
-
|
410
|
+
img_id = MU::Cloud.getStockImage("Google", platform: pool['platform'])
|
411
|
+
if img_id
|
412
|
+
launch['image_id'] = configurator.getTail("server_pool"+pool['name']+"Image", value: img_id, prettyname: "server_pool"+pool['name']+"Image", cloudtype: "Google::Apis::ComputeV1::Image")
|
229
413
|
else
|
230
414
|
MU.log "No image specified for #{pool['name']} and no default available for platform #{pool['platform']}", MU::ERR, details: launch
|
231
415
|
ok = false
|
@@ -270,6 +454,7 @@ module MU
|
|
270
454
|
# @return [void]
|
271
455
|
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
272
456
|
flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
|
457
|
+
return if !MU::Cloud::Google::Habitat.isLive?(flags["project"], credentials)
|
273
458
|
|
274
459
|
if !flags["global"]
|
275
460
|
["region_autoscaler", "region_instance_group_manager"].each { |type|
|
@@ -17,45 +17,160 @@ module MU
|
|
17
17
|
class Google
|
18
18
|
# A user as configured in {MU::Config::BasketofKittens::users}
|
19
19
|
class User < MU::Cloud::User
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
|
21
|
+
# Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us.
|
22
|
+
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
23
|
+
def initialize(**args)
|
24
|
+
super
|
25
|
+
|
26
|
+
# If we're being reverse-engineered from a cloud descriptor, use that
|
27
|
+
# to determine what sort of account we are.
|
28
|
+
if args[:from_cloud_desc]
|
29
|
+
MU::Cloud::Google.admin_directory
|
30
|
+
MU::Cloud::Google.iam
|
31
|
+
if args[:from_cloud_desc].class == ::Google::Apis::AdminDirectoryV1::User
|
32
|
+
@config['type'] = "interactive"
|
33
|
+
elsif args[:from_cloud_desc].class == ::Google::Apis::IamV1::ServiceAccount
|
34
|
+
@config['type'] = "service"
|
35
|
+
@config['name'] = args[:from_cloud_desc].display_name
|
36
|
+
if @config['name'].nil? or @config['name'].empty?
|
37
|
+
@config['name'] = args[:from_cloud_desc].name.sub(/.*?\/([^\/@]+)(?:@[^\/]*)?$/, '\1')
|
38
|
+
end
|
39
|
+
@cloud_id = args[:from_cloud_desc].name
|
40
|
+
else
|
41
|
+
raise MuError, "Google::User got from_cloud_desc arg of class #{args[:from_cloud_desc].class.name}, but doesn't know what to do with it"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
@mu_name ||= if (@config['unique_name'] or @config['type'] == "service") and !@config['scrub_mu_isms']
|
46
|
+
@deploy.getResourceName(@config["name"])
|
47
|
+
else
|
48
|
+
@config['name']
|
49
|
+
end
|
50
|
+
|
33
51
|
end
|
34
52
|
|
35
53
|
# Called automatically by {MU::Deploy#createResources}
|
36
54
|
def create
|
37
|
-
if @config['type'] == "
|
38
|
-
|
39
|
-
else
|
55
|
+
if @config['type'] == "service"
|
56
|
+
acct_id = @config['scrub_mu_isms'] ? @config['name'] : @deploy.getResourceName(@config["name"], max_length: 30).downcase
|
40
57
|
req_obj = MU::Cloud::Google.iam(:CreateServiceAccountRequest).new(
|
41
|
-
account_id:
|
58
|
+
account_id: acct_id,
|
42
59
|
service_account: MU::Cloud::Google.iam(:ServiceAccount).new(
|
43
|
-
display_name: @mu_name
|
60
|
+
display_name: @mu_name,
|
61
|
+
description: @config['scrub_mu_isms'] ? nil : @deploy.deploy_id
|
62
|
+
)
|
63
|
+
)
|
64
|
+
if @config['use_if_exists']
|
65
|
+
# XXX maybe just set @cloud_id to projects/#{@project_id}/serviceAccounts/#{@mu_name}@#{@project_id}.iam.gserviceaccount.com and see if cloud_desc returns something
|
66
|
+
found = MU::Cloud::Google::User.find(project: @project_id, cloud_id: @mu_name)
|
67
|
+
if found.size == 1
|
68
|
+
@cloud_id = found.keys.first
|
69
|
+
MU.log "Service account #{@cloud_id} already existed, using it"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if !@cloud_id
|
74
|
+
MU.log "Creating service account #{@mu_name}"
|
75
|
+
resp = MU::Cloud::Google.iam(credentials: @config['credentials']).create_service_account(
|
76
|
+
"projects/"+@config['project'],
|
77
|
+
req_obj
|
44
78
|
)
|
79
|
+
@cloud_id = resp.name
|
80
|
+
end
|
81
|
+
|
82
|
+
# make sure we've been created before moving on
|
83
|
+
begin
|
84
|
+
cloud_desc
|
85
|
+
rescue ::Google::Apis::ClientError => e
|
86
|
+
if e.message.match(/notFound:/)
|
87
|
+
sleep 3
|
88
|
+
retry
|
89
|
+
end
|
90
|
+
end
|
91
|
+
elsif @config['external']
|
92
|
+
@cloud_id = @config['email']
|
93
|
+
MU::Cloud::Google::Role.bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
|
94
|
+
else
|
95
|
+
if !@config['email']
|
96
|
+
domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer)
|
97
|
+
@config['email'] = @mu_name.gsub(/@.*/, "")+"@"+domains.domains.first.domain_name
|
98
|
+
end
|
99
|
+
|
100
|
+
username_obj = MU::Cloud::Google.admin_directory(:UserName).new(
|
101
|
+
given_name: (@config['given_name'] || @config['name']),
|
102
|
+
family_name: (@config['family_name'] || @deploy.deploy_id),
|
103
|
+
full_name: @mu_name
|
45
104
|
)
|
46
|
-
|
47
|
-
MU::Cloud::Google.
|
48
|
-
|
49
|
-
|
105
|
+
|
106
|
+
user_obj = MU::Cloud::Google.admin_directory(:User).new(
|
107
|
+
name: username_obj,
|
108
|
+
primary_email: @config['email'],
|
109
|
+
suspended: @config['suspend'],
|
110
|
+
is_admin: @config['admin'],
|
111
|
+
password: MU.generateWindowsPassword,
|
112
|
+
change_password_at_next_login: (@config.has_key?('force_password_change') ? @config['force_password_change'] : true)
|
50
113
|
)
|
114
|
+
|
115
|
+
MU.log "Creating user #{@mu_name}", details: user_obj
|
116
|
+
resp = MU::Cloud::Google.admin_directory(credentials: @credentials).insert_user(user_obj)
|
117
|
+
@cloud_id = resp.primary_email
|
118
|
+
|
51
119
|
end
|
52
120
|
end
|
53
121
|
|
54
122
|
# Called automatically by {MU::Deploy#createResources}
|
55
123
|
def groom
|
56
|
-
if @config['
|
57
|
-
|
124
|
+
if @config['external']
|
125
|
+
MU::Cloud::Google::Role.bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
|
126
|
+
elsif @config['type'] == "interactive"
|
127
|
+
need_update = false
|
128
|
+
MU::Cloud::Google::Role.bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
|
129
|
+
|
130
|
+
if @config['force_password_change'] and !cloud_desc.change_password_at_next_login
|
131
|
+
MU.log "Forcing #{@mu_name} to change their password at next login", MU::NOTICE
|
132
|
+
need_update = true
|
133
|
+
elsif @config.has_key?("force_password_change") and
|
134
|
+
!@config['force_password_change'] and
|
135
|
+
cloud_desc.change_password_at_next_login
|
136
|
+
MU.log "No longer forcing #{@mu_name} to change their password at next login", MU::NOTICE
|
137
|
+
need_update = true
|
138
|
+
end
|
139
|
+
if @config['admin'] != cloud_desc.is_admin
|
140
|
+
MU.log "Setting 'is_admin' flag to #{@config['admin'].to_s} for directory user #{@mu_name}", MU::NOTICE
|
141
|
+
MU::Cloud::Google.admin_directory(credentials: @credentials).make_user_admin(@cloud_id, MU::Cloud::Google.admin_directory(:UserMakeAdmin).new(status: @config['admin']))
|
142
|
+
end
|
143
|
+
|
144
|
+
if @config['suspend'] != cloud_desc.suspended
|
145
|
+
need_update = true
|
146
|
+
end
|
147
|
+
if cloud_desc.name.given_name != (@config['given_name'] || @config['name']) or
|
148
|
+
cloud_desc.name.family_name != (@config['family_name'] || @deploy.deploy_id) or
|
149
|
+
cloud_desc.primary_email != @config['email']
|
150
|
+
need_update = true
|
151
|
+
end
|
152
|
+
|
153
|
+
if need_update
|
154
|
+
username_obj = MU::Cloud::Google.admin_directory(:UserName).new(
|
155
|
+
given_name: (@config['given_name'] || @config['name']),
|
156
|
+
family_name: (@config['family_name'] || @deploy.deploy_id),
|
157
|
+
full_name: @mu_name
|
158
|
+
)
|
159
|
+
user_obj = MU::Cloud::Google.admin_directory(:User).new(
|
160
|
+
name: username_obj,
|
161
|
+
primary_email: @config['email'],
|
162
|
+
suspended: @config['suspend'],
|
163
|
+
change_password_at_next_login: (@config.has_key?('force_password_change') ? @config['force_password_change'] : true)
|
164
|
+
)
|
165
|
+
|
166
|
+
MU.log "Updating directory user #{@mu_name}", MU::NOTICE, details: user_obj
|
167
|
+
|
168
|
+
resp = MU::Cloud::Google.admin_directory(credentials: @credentials).update_user(@cloud_id, user_obj)
|
169
|
+
@cloud_id = resp.primary_email
|
170
|
+
end
|
171
|
+
|
58
172
|
else
|
173
|
+
MU::Cloud::Google::Role.bindFromConfig("serviceAccount", @cloud_id.gsub(/.*?\/([^\/]+)$/, '\1'), @config['roles'], credentials: @config['credentials'])
|
59
174
|
if @config['create_api_key']
|
60
175
|
resp = MU::Cloud::Google.iam(credentials: @config['credentials']).list_project_service_account_keys(
|
61
176
|
cloud_desc.name
|
@@ -73,34 +188,32 @@ module MU
|
|
73
188
|
end
|
74
189
|
|
75
190
|
# Retrieve the cloud descriptor for this resource.
|
191
|
+
# @return [Google::Apis::Core::Hashable]
|
76
192
|
def cloud_desc
|
77
|
-
if @config['type'] == "interactive"
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
if resp and resp.accounts
|
85
|
-
resp.accounts.each { |sa|
|
86
|
-
if sa.display_name and sa.display_name == @mu_name
|
87
|
-
return sa
|
88
|
-
end
|
89
|
-
}
|
193
|
+
if @config['type'] == "interactive" or !@config['type']
|
194
|
+
@config['type'] ||= "interactive"
|
195
|
+
if !@config['external']
|
196
|
+
return MU::Cloud::Google.admin_directory(credentials: @config['credentials']).get_user(@cloud_id)
|
197
|
+
else
|
198
|
+
return nil
|
90
199
|
end
|
200
|
+
else
|
201
|
+
@config['type'] ||= "service"
|
202
|
+
return MU::Cloud::Google.iam(credentials: @config['credentials']).get_project_service_account(@cloud_id)
|
91
203
|
end
|
204
|
+
|
92
205
|
end
|
93
206
|
|
94
207
|
# Return the metadata for this user configuration
|
95
208
|
# @return [Hash]
|
96
209
|
def notify
|
97
|
-
description =
|
98
|
-
|
99
|
-
|
100
|
-
|
210
|
+
description = if !@config['external']
|
211
|
+
MU.structToHash(cloud_desc)
|
212
|
+
else
|
213
|
+
{}
|
101
214
|
end
|
102
|
-
|
103
|
-
|
215
|
+
description.delete(:etag)
|
216
|
+
description
|
104
217
|
end
|
105
218
|
|
106
219
|
# Does this resource type exist as a global (cloud-wide) artifact, or
|
@@ -113,7 +226,7 @@ module MU
|
|
113
226
|
# Denote whether this resource implementation is experiment, ready for
|
114
227
|
# testing, or ready for production use.
|
115
228
|
def self.quality
|
116
|
-
MU::Cloud::
|
229
|
+
MU::Cloud::RELEASE
|
117
230
|
end
|
118
231
|
|
119
232
|
# Remove all users associated with the currently loaded deployment.
|
@@ -122,6 +235,33 @@ module MU
|
|
122
235
|
# @param region [String]: The cloud provider region
|
123
236
|
# @return [void]
|
124
237
|
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
238
|
+
my_domains = MU::Cloud::Google.getDomains(credentials)
|
239
|
+
my_org = MU::Cloud::Google.getOrg(credentials)
|
240
|
+
|
241
|
+
# We don't have a good way of tagging directory users, so we rely
|
242
|
+
# on the known parameter, which is pulled from deployment metadata
|
243
|
+
if flags['known'] and my_org
|
244
|
+
dir_users = MU::Cloud::Google.admin_directory(credentials: credentials).list_users(customer: MU::Cloud::Google.customerID(credentials)).users
|
245
|
+
if dir_users
|
246
|
+
dir_users.each { |user|
|
247
|
+
if flags['known'].include?(user.primary_email)
|
248
|
+
MU.log "Deleting user #{user.primary_email} from #{my_org.display_name}", details: user
|
249
|
+
if !noop
|
250
|
+
MU::Cloud::Google.admin_directory(credentials: credentials).delete_user(user.id)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
}
|
254
|
+
|
255
|
+
flags['known'].each { |user_email|
|
256
|
+
next if user_email.nil?
|
257
|
+
next if !user_email.match(/^[^\/]+@[^\/]+$/)
|
258
|
+
|
259
|
+
MU::Cloud::Google::Role.removeBindings("user", user_email, credentials: credentials, noop: noop)
|
260
|
+
}
|
261
|
+
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
125
265
|
flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
|
126
266
|
resp = MU::Cloud::Google.iam(credentials: credentials).list_project_service_accounts(
|
127
267
|
"projects/"+flags["project"]
|
@@ -129,7 +269,8 @@ module MU
|
|
129
269
|
|
130
270
|
if resp and resp.accounts and MU.deploy_id
|
131
271
|
resp.accounts.each { |sa|
|
132
|
-
if sa.
|
272
|
+
if (sa.description and sa.description == MU.deploy_id) or
|
273
|
+
(sa.display_name and sa.display_name.match(/^#{Regexp.quote(MU.deploy_id)}-/i))
|
133
274
|
begin
|
134
275
|
MU.log "Deleting service account #{sa.name}", details: sa
|
135
276
|
if !noop
|
@@ -143,30 +284,167 @@ module MU
|
|
143
284
|
end
|
144
285
|
end
|
145
286
|
|
146
|
-
# Locate
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
287
|
+
# Locate and return cloud provider descriptors of this resource type
|
288
|
+
# which match the provided parameters, or all visible resources if no
|
289
|
+
# filters are specified. At minimum, implementations of +find+ must
|
290
|
+
# honor +credentials+ and +cloud_id+ arguments. We may optionally
|
291
|
+
# support other search methods, such as +tag_key+ and +tag_value+, or
|
292
|
+
# cloud-specific arguments like +project+. See also {MU::MommaCat.findStray}.
|
293
|
+
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
294
|
+
# @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
|
295
|
+
def self.find(**args)
|
296
|
+
cred_cfg = MU::Cloud::Google.credConfig(args[:credentials])
|
297
|
+
args[:project] ||= args[:habitat]
|
157
298
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
299
|
+
found = {}
|
300
|
+
|
301
|
+
if args[:cloud_id] and args[:flags] and
|
302
|
+
args[:flags]["skip_provider_owned"] and
|
303
|
+
MU::Cloud::Google::User.cannedServiceAcctName?(args[:cloud_id])
|
304
|
+
return found
|
305
|
+
end
|
306
|
+
|
307
|
+
# If the project id is embedded in the cloud_id, honor it
|
308
|
+
if args[:cloud_id]
|
309
|
+
if args[:cloud_id].match(/projects\/(.+?)\//)
|
310
|
+
args[:project] = Regexp.last_match[1]
|
311
|
+
elsif args[:cloud_id].match(/@([^\.]+)\.iam\.gserviceaccount\.com$/)
|
312
|
+
args[:project] = Regexp.last_match[1]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
if args[:project]
|
317
|
+
# project-local service accounts
|
318
|
+
resp = begin
|
319
|
+
MU::Cloud::Google.iam(credentials: args[:credentials]).list_project_service_accounts(
|
320
|
+
"projects/"+args[:project]
|
321
|
+
)
|
322
|
+
rescue ::Google::Apis::ClientError => e
|
323
|
+
MU.log "Do not have permissions to retrieve service accounts for project #{args[:project]}", MU::WARN
|
324
|
+
end
|
325
|
+
|
326
|
+
if resp and resp.accounts
|
327
|
+
resp.accounts.each { |sa|
|
328
|
+
if args[:flags] and args[:flags]["skip_provider_owned"] and
|
329
|
+
MU::Cloud::Google::User.cannedServiceAcctName?(sa.name)
|
330
|
+
next
|
331
|
+
end
|
332
|
+
if !args[:cloud_id] or (sa.display_name and sa.display_name == args[:cloud_id]) or (sa.name and sa.name == args[:cloud_id]) or (sa.email and sa.email == args[:cloud_id])
|
333
|
+
found[sa.name] = sa
|
334
|
+
end
|
335
|
+
}
|
336
|
+
end
|
337
|
+
else
|
338
|
+
if cred_cfg['masquerade_as']
|
339
|
+
resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).list_users(customer: MU::Cloud::Google.customerID(args[:credentials]), show_deleted: false)
|
340
|
+
if resp and resp.users
|
341
|
+
resp.users.each { |u|
|
342
|
+
found[u.primary_email] = u
|
343
|
+
}
|
163
344
|
end
|
164
|
-
|
345
|
+
end
|
165
346
|
end
|
166
347
|
|
167
348
|
found
|
168
349
|
end
|
169
350
|
|
351
|
+
# Try to determine whether the given string looks like a pre-configured
|
352
|
+
# GCP service account, as distinct from one we might create or manage
|
353
|
+
def self.cannedServiceAcctName?(name)
|
354
|
+
return false if !name
|
355
|
+
name.match(/\b\d+\-compute@developer\.gserviceaccount\.com$/) or
|
356
|
+
name.match(/\bproject-\d+@storage-transfer-service\.iam\.gserviceaccount\.com$/) or
|
357
|
+
name.match(/\b\d+@cloudbuild\.gserviceaccount\.com$/) or
|
358
|
+
name.match(/\bservice-\d+@containerregistry\.iam\.gserviceaccount\.com$/) or
|
359
|
+
name.match(/\bservice-\d+@container-analysis\.iam\.gserviceaccount\.com$/) or
|
360
|
+
name.match(/\bservice-\d+@gcp-sa-bigquerydatatransfer\.iam\.gserviceaccount\.com$/) or
|
361
|
+
name.match(/\bservice-\d+@gcp-sa-cloudasset\.iam\.gserviceaccount\.com$/) or
|
362
|
+
name.match(/\bservice-\d+@gcp-sa-cloudiot\.iam\.gserviceaccount\.com$/) or
|
363
|
+
name.match(/\bservice-\d+@gcp-sa-cloudscheduler\.iam\.gserviceaccount\.com$/) or
|
364
|
+
name.match(/\bservice-\d+@compute-system\.iam\.gserviceaccount\.com$/) or
|
365
|
+
name.match(/\bservice-\d+@container-engine-robot\.iam\.gserviceaccount\.com$/) or
|
366
|
+
name.match(/\bservice-\d+@gcp-admin-robot\.iam\.gserviceaccount\.com$/) or
|
367
|
+
name.match(/\bservice-\d+@gcp-sa-containerscanning\.iam\.gserviceaccount\.com$/) or
|
368
|
+
name.match(/\bservice-\d+@dataflow-service-producer-prod\.iam\.gserviceaccount\.com$/) or
|
369
|
+
name.match(/\bservice-\d+@dataproc-accounts\.iam\.gserviceaccount\.com$/) or
|
370
|
+
name.match(/\bservice-\d+@endpoints-portal\.iam\.gserviceaccount\.com$/) or
|
371
|
+
name.match(/\bservice-\d+@cloud-filer\.iam\.gserviceaccount\.com$/) or
|
372
|
+
name.match(/\bservice-\d+@cloud-redis\.iam\.gserviceaccount\.com$/) or
|
373
|
+
name.match(/\bservice-\d+@firebase-rules\.iam\.gserviceaccount\.com$/) or
|
374
|
+
name.match(/\bservice-\d+@cloud-tpu\.iam\.gserviceaccount\.com$/) or
|
375
|
+
name.match(/\bservice-\d+@gcp-sa-vpcaccess\.iam\.gserviceaccount\.com$/) or
|
376
|
+
name.match(/\bservice-\d+@gcp-sa-websecurityscanner\.iam\.gserviceaccount\.com$/) or
|
377
|
+
name.match(/\bservice-\d+@sourcerepo-service-accounts\.iam\.gserviceaccount\.com$/) or
|
378
|
+
name.match(/\bp\d+\-\d+@gcp-sa-logging\.iam\.gserviceaccount\.com$/)
|
379
|
+
end
|
380
|
+
|
381
|
+
# We can either refer to a service account, which is scoped to a project
|
382
|
+
# (a +Habitat+ in Mu parlance), or a "real" user, which comes from
|
383
|
+
# an external directory like GMail, GSuite, or Cloud Identity.
|
384
|
+
def self.canLiveIn
|
385
|
+
[:Habitat, nil]
|
386
|
+
end
|
387
|
+
|
388
|
+
# Reverse-map our cloud description into a runnable config hash.
|
389
|
+
# We assume that any values we have in +@config+ are placeholders, and
|
390
|
+
# calculate our own accordingly based on what's live in the cloud.
|
391
|
+
def toKitten(rootparent: nil, billing: nil, habitats: nil)
|
392
|
+
if MU::Cloud::Google::User.cannedServiceAcctName?(@cloud_id)
|
393
|
+
return nil
|
394
|
+
end
|
395
|
+
|
396
|
+
bok = {
|
397
|
+
"cloud" => "Google",
|
398
|
+
"credentials" => @config['credentials']
|
399
|
+
}
|
400
|
+
|
401
|
+
if cloud_desc.nil?
|
402
|
+
MU.log @config['name']+" couldn't fetch its cloud descriptor", MU::WARN, details: @cloud_id
|
403
|
+
return nil
|
404
|
+
end
|
405
|
+
|
406
|
+
user_roles = MU::Cloud::Google::Role.getAllBindings(@config['credentials'])["by_entity"]
|
407
|
+
|
408
|
+
if cloud_desc.nil?
|
409
|
+
MU.log "FAILED TO FIND CLOUD DESCRIPTOR FOR #{self}", MU::ERR, details: @config
|
410
|
+
return nil
|
411
|
+
end
|
412
|
+
|
413
|
+
bok['name'] = @config['name']
|
414
|
+
bok['cloud_id'] = @cloud_id
|
415
|
+
bok['type'] = @config['type']
|
416
|
+
bok['type'] ||= "service"
|
417
|
+
if bok['type'] == "service"
|
418
|
+
bok['project'] = @project_id
|
419
|
+
keys = MU::Cloud::Google.iam(credentials: @config['credentials']).list_project_service_account_keys(@cloud_id)
|
420
|
+
|
421
|
+
if keys and keys.keys and keys.keys.size > 0
|
422
|
+
bok['create_api_key'] = true
|
423
|
+
end
|
424
|
+
# MU.log "service account #{@cloud_id}", MU::NOTICE, details: MU::Cloud::Google.iam(credentials: @config['credentials']).get_project_service_account_iam_policy(cloud_desc.name)
|
425
|
+
if user_roles["serviceAccount"] and
|
426
|
+
user_roles["serviceAccount"][bok['cloud_id']] and
|
427
|
+
user_roles["serviceAccount"][bok['cloud_id']].size > 0
|
428
|
+
bok['roles'] = MU::Cloud::Google::Role.entityBindingsToSchema(user_roles["serviceAccount"][bok['cloud_id']])
|
429
|
+
end
|
430
|
+
else
|
431
|
+
if user_roles["user"] and
|
432
|
+
user_roles["user"][bok['cloud_id']] and
|
433
|
+
user_roles["user"][bok['cloud_id']].size > 0
|
434
|
+
bok['roles'] = MU::Cloud::Google::Role.entityBindingsToSchema(user_roles["user"][bok['cloud_id']], credentials: @config['credentials'])
|
435
|
+
end
|
436
|
+
bok['given_name'] = cloud_desc.name.given_name
|
437
|
+
bok['family_name'] = cloud_desc.name.family_name
|
438
|
+
bok['email'] = cloud_desc.primary_email
|
439
|
+
bok['suspend'] = cloud_desc.suspended
|
440
|
+
bok['admin'] = cloud_desc.is_admin
|
441
|
+
end
|
442
|
+
|
443
|
+
bok['use_if_exists'] = true
|
444
|
+
|
445
|
+
bok
|
446
|
+
end
|
447
|
+
|
170
448
|
# Cloud-specific configuration properties.
|
171
449
|
# @param config [MU::Config]: The calling MU::Config object
|
172
450
|
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
@@ -175,24 +453,68 @@ module MU
|
|
175
453
|
schema = {
|
176
454
|
"name" => {
|
177
455
|
"type" => "string",
|
178
|
-
"description" => "
|
456
|
+
"description" => "If the +type+ of this account is not +service+, this can include an optional @domain component (<tt>foo@example.com</tt>), which is equivalent to the +domain+ configuration option. The following rules apply to +directory+ (non-<tt>service</tt>) accounts only:
|
457
|
+
|
458
|
+
If the domain portion is not specified, and we manage exactly one GSuite or Cloud Identity domain, we will attempt to create the user in that domain.
|
459
|
+
|
460
|
+
If we do not manage any domains, and none are specified, we will assume <tt>@gmail.com</tt> for the domain and attempt to bind an existing external GMail user to roles under our jurisdiction.
|
461
|
+
|
462
|
+
If the domain portion is specified, and our credentials can manage that domain via GSuite or Cloud Identity, we will attempt to create the user in that domain.
|
463
|
+
|
464
|
+
If it is a domain we do not manage, we will attempt to bind an existing external user from that domain to roles under our jurisdiction.
|
465
|
+
|
466
|
+
If we are binding (rather than creating) a user and no roles are specified, we will default to +roles/viewer+ at the organization scope. If our credentials do not manage an organization, we will grant this role in our default project.
|
467
|
+
|
468
|
+
"
|
469
|
+
},
|
470
|
+
"domain" => {
|
471
|
+
"type" => "string",
|
472
|
+
"description" => "If creating or binding an +interactive+ user, this is the domain of which the user should be a member. This can instead be embedded in the {name} field: +foo@example.com+."
|
473
|
+
},
|
474
|
+
"given_name" => {
|
475
|
+
"type" => "string",
|
476
|
+
"description" => "Optionally set the +given_name+ field of a +directory+ account. Ignored for +service+ accounts."
|
477
|
+
},
|
478
|
+
"first_name" => {
|
479
|
+
"type" => "string",
|
480
|
+
"description" => "Alias for +given_name+"
|
481
|
+
},
|
482
|
+
"family_name" => {
|
483
|
+
"type" => "string",
|
484
|
+
"description" => "Optionally set the +family_name+ field of a +directory+ account. Ignored for +service+ accounts."
|
485
|
+
},
|
486
|
+
"last_name" => {
|
487
|
+
"type" => "string",
|
488
|
+
"description" => "Alias for +family_name+"
|
489
|
+
},
|
490
|
+
"email" => {
|
491
|
+
"type" => "string",
|
492
|
+
"description" => "Canonical email address for a +directory+ user. If not specified, will be set to +name@domain+."
|
493
|
+
},
|
494
|
+
"external" => {
|
495
|
+
"type" => "boolean",
|
496
|
+
"description" => "Explicitly flag this user as originating from an external domain. This should always autodetect correctly."
|
497
|
+
},
|
498
|
+
"admin" => {
|
499
|
+
"type" => "boolean",
|
500
|
+
"description" => "If the user is +interactive+ and resides in a domain we manage, set their +is_admin+ flag.",
|
501
|
+
"default" => false
|
502
|
+
},
|
503
|
+
"suspend" => {
|
504
|
+
"type" => "boolean",
|
505
|
+
"description" => "If the user is +interactive+ and resides in a domain we manage, this can be used to lock their account.",
|
506
|
+
"default" => false
|
179
507
|
},
|
180
508
|
"type" => {
|
181
509
|
"type" => "string",
|
182
|
-
"description" => "'interactive' will attempt to bind an existing user; 'service' will create a service account and generate API keys"
|
510
|
+
"description" => "'interactive' will either attempt to bind an existing user to a role under our jurisdiction, or create a new directory user, depending on the domain of the user specified and whether we manage any directories; 'service' will create a service account and generate API keys.",
|
511
|
+
"enum" => ["interactive", "service"],
|
512
|
+
"default" => "interactive"
|
183
513
|
},
|
184
514
|
"roles" => {
|
185
515
|
"type" => "array",
|
186
516
|
"description" => "One or more Google IAM roles to associate with this user.",
|
187
|
-
"
|
188
|
-
"items" => {
|
189
|
-
"type" => "string",
|
190
|
-
"description" => "One or more Google IAM roles to associate with this user. Google Cloud human user accounts (as distinct from service accounts) are not created directly; pre-existing Google accounts are associated with a project by being bound to one or more roles in that project. If no roles are specified, we default to +roles/viewer+, which permits read-only access project-wide."
|
191
|
-
}
|
192
|
-
},
|
193
|
-
"project" => {
|
194
|
-
"type" => "string",
|
195
|
-
"description" => "The project into which to deploy resources"
|
517
|
+
"items" => MU::Cloud::Google::Role.ref_schema
|
196
518
|
}
|
197
519
|
}
|
198
520
|
[toplevel_required, schema]
|
@@ -205,75 +527,107 @@ module MU
|
|
205
527
|
def self.validateConfig(user, configurator)
|
206
528
|
ok = true
|
207
529
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
530
|
+
my_domains = MU::Cloud::Google.getDomains(user['credentials'])
|
531
|
+
my_org = MU::Cloud::Google.getOrg(user['credentials'])
|
532
|
+
|
533
|
+
# Deal with these name alias fields, here for the convenience of your
|
534
|
+
# easily confused english-centric type of person
|
535
|
+
user['given_name'] ||= user['first_name']
|
536
|
+
user['family_name'] ||= user['last_name']
|
537
|
+
user.delete("first_name")
|
538
|
+
user.delete("last_name")
|
539
|
+
|
540
|
+
if user['name'].match(/@(.*+)$/)
|
541
|
+
domain = Regexp.last_match[1].downcase
|
542
|
+
if domain and user['domain'] and domain != user['domain'].downcase
|
543
|
+
MU.log "User #{user['name']} had a domain component, but the domain field was also specified (#{user['domain']}) and they don't match."
|
544
|
+
ok = false
|
545
|
+
end
|
546
|
+
user['domain'] = domain
|
547
|
+
if user['type'] == "service"
|
548
|
+
MU.log "Username #{user['name']} appears to be a directory or external username, cannot use with 'service'", MU::ERR
|
549
|
+
ok = false
|
550
|
+
else
|
551
|
+
user['type'] = "interactive"
|
552
|
+
if !my_domains or !my_domains.include?(domain)
|
553
|
+
user['external'] = true
|
554
|
+
|
555
|
+
if !["gmail.com", "google.com"].include?(domain)
|
556
|
+
MU.log "#{user['name']} appears to be a member of a domain that our credentials (#{user['credentials']}) do not manage; attempts to grant access for this user may fail!", MU::WARN
|
557
|
+
end
|
558
|
+
|
559
|
+
if !user['roles'] or user['roles'].empty?
|
560
|
+
user['roles'] = [
|
561
|
+
{
|
562
|
+
"role" => {
|
563
|
+
"id" => "roles/viewer"
|
564
|
+
}
|
565
|
+
}
|
566
|
+
]
|
567
|
+
MU.log "External Google user specified with no role binding, will grant 'viewer' in #{my_org ? "organization #{my_org.display_name}" : "project #{user['project']}"}", MU::WARN
|
568
|
+
end
|
569
|
+
else # this is actually targeting a domain we manage! yay!
|
570
|
+
end
|
571
|
+
end
|
572
|
+
elsif user['type'] != "service"
|
573
|
+
if !user['domain']
|
574
|
+
if my_domains.size == 1
|
575
|
+
user['domain'] = my_domains.first
|
576
|
+
elsif my_domains.size > 1
|
577
|
+
MU.log "Google interactive User #{user['name']} did not specify a domain, and we have multiple defaults available. Must specify exactly one.", MU::ERR, details: my_domains
|
578
|
+
ok = false
|
579
|
+
else
|
580
|
+
user['domain'] = "gmail.com"
|
581
|
+
end
|
582
|
+
end
|
212
583
|
end
|
213
584
|
|
214
|
-
if user['
|
215
|
-
|
216
|
-
|
585
|
+
if user['domain']
|
586
|
+
user['email'] ||= user['name'].gsub(/@.*/, "")+"@"+user['domain']
|
587
|
+
end
|
588
|
+
|
589
|
+
if user['groups'] and user['groups'].size > 0 and my_org.nil?
|
590
|
+
MU.log "Cannot change Google group memberships with credentials that do not manage GSuite or Cloud Identity.\nVisit https://groups.google.com to manage groups.", MU::ERR
|
217
591
|
ok = false
|
218
592
|
end
|
219
593
|
|
594
|
+
if user['type'] == "service"
|
595
|
+
user['project'] ||= MU::Cloud::Google.defaultProject(user['credentials'])
|
596
|
+
end
|
597
|
+
|
220
598
|
if user['type'] != "service" and user["create_api_key"]
|
221
599
|
MU.log "Only service accounts can have API keys in Google Cloud", MU::ERR
|
222
600
|
ok = false
|
223
601
|
end
|
224
602
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
603
|
+
user['dependencies'] ||= []
|
604
|
+
if user['roles']
|
605
|
+
user['roles'].each { |r|
|
606
|
+
if r['role'] and r['role']['name'] and
|
607
|
+
(!r['role']['deploy_id'] and !r['role']['id'])
|
608
|
+
user['dependencies'] << {
|
609
|
+
"type" => "role",
|
610
|
+
"name" => r['role']['name']
|
611
|
+
}
|
612
|
+
end
|
235
613
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
change_needed = true
|
244
|
-
b.members << "user:"+@config['name']
|
614
|
+
if !r["projects"] and !r["organizations"] and !r["folders"]
|
615
|
+
if my_org
|
616
|
+
r["organizations"] = [my_org.name]
|
617
|
+
else
|
618
|
+
r["projects"] = [
|
619
|
+
"id" => user["project"]
|
620
|
+
]
|
245
621
|
end
|
246
622
|
end
|
247
623
|
}
|
248
|
-
if !seen
|
249
|
-
ext_policy.bindings << MU::Cloud::Google.resource_manager(:Binding).new(
|
250
|
-
role: role,
|
251
|
-
members: ["user:"+@config['name']]
|
252
|
-
)
|
253
|
-
change_needed = true
|
254
|
-
end
|
255
|
-
}
|
256
|
-
|
257
|
-
if change_needed
|
258
|
-
req_obj = MU::Cloud::Google.resource_manager(:SetIamPolicyRequest).new(
|
259
|
-
policy: ext_policy
|
260
|
-
)
|
261
|
-
MU.log "Adding #{@config['name']} to Google Cloud project #{@config['project']}", details: @config['roles']
|
262
|
-
|
263
|
-
begin
|
264
|
-
MU::Cloud::Google.resource_manager(credentials: @config['credentials']).set_project_iam_policy(
|
265
|
-
@config['project'],
|
266
|
-
req_obj
|
267
|
-
)
|
268
|
-
rescue ::Google::Apis::ClientError => e
|
269
|
-
if e.message.match(/does not exist/i) and !MU::Cloud::Google.credConfig(@config['credentials'])['masquerade_as']
|
270
|
-
raise MuError, "User #{@config['name']} does not exist, and we cannot create Google user in non-GSuite environments.\nVisit https://accounts.google.com to create new accounts."
|
271
|
-
end
|
272
|
-
raise e
|
273
|
-
end
|
274
624
|
end
|
625
|
+
|
626
|
+
ok
|
275
627
|
end
|
276
628
|
|
629
|
+
private
|
630
|
+
|
277
631
|
end
|
278
632
|
end
|
279
633
|
end
|