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
|
@@ -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
|