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.
Files changed (291) hide show
  1. checksums.yaml +5 -5
  2. data/Berksfile +4 -5
  3. data/Berksfile.lock +179 -0
  4. data/README.md +1 -6
  5. data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +0 -0
  6. data/ansible/roles/mu-installer/README.md +33 -0
  7. data/ansible/roles/mu-installer/defaults/main.yml +2 -0
  8. data/ansible/roles/mu-installer/handlers/main.yml +2 -0
  9. data/ansible/roles/mu-installer/meta/main.yml +60 -0
  10. data/ansible/roles/mu-installer/tasks/main.yml +13 -0
  11. data/ansible/roles/mu-installer/tests/inventory +2 -0
  12. data/ansible/roles/mu-installer/tests/test.yml +5 -0
  13. data/ansible/roles/mu-installer/vars/main.yml +2 -0
  14. data/bin/mu-adopt +125 -0
  15. data/bin/mu-aws-setup +4 -4
  16. data/bin/mu-azure-setup +265 -0
  17. data/bin/mu-azure-tests +43 -0
  18. data/bin/mu-cleanup +20 -8
  19. data/bin/mu-configure +224 -98
  20. data/bin/mu-deploy +8 -3
  21. data/bin/mu-gcp-setup +16 -8
  22. data/bin/mu-gen-docs +92 -8
  23. data/bin/mu-load-config.rb +52 -12
  24. data/bin/mu-momma-cat +36 -0
  25. data/bin/mu-node-manage +34 -27
  26. data/bin/mu-self-update +2 -2
  27. data/bin/mu-ssh +12 -8
  28. data/bin/mu-upload-chef-artifacts +11 -4
  29. data/bin/mu-user-manage +3 -0
  30. data/cloud-mu.gemspec +8 -11
  31. data/cookbooks/firewall/libraries/helpers_iptables.rb +2 -2
  32. data/cookbooks/firewall/metadata.json +1 -1
  33. data/cookbooks/firewall/recipes/default.rb +5 -9
  34. data/cookbooks/mu-firewall/attributes/default.rb +2 -0
  35. data/cookbooks/mu-firewall/metadata.rb +1 -1
  36. data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +0 -0
  37. data/cookbooks/mu-master/Berksfile +2 -2
  38. data/cookbooks/mu-master/files/default/check_mem.pl +0 -0
  39. data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
  40. data/cookbooks/mu-master/metadata.rb +5 -4
  41. data/cookbooks/mu-master/recipes/389ds.rb +1 -1
  42. data/cookbooks/mu-master/recipes/basepackages.rb +30 -10
  43. data/cookbooks/mu-master/recipes/default.rb +59 -7
  44. data/cookbooks/mu-master/recipes/firewall-holes.rb +1 -1
  45. data/cookbooks/mu-master/recipes/init.rb +65 -47
  46. data/cookbooks/mu-master/recipes/{eks-kubectl.rb → kubectl.rb} +4 -10
  47. data/cookbooks/mu-master/recipes/sssd.rb +2 -1
  48. data/cookbooks/mu-master/recipes/update_nagios_only.rb +6 -6
  49. data/cookbooks/mu-master/templates/default/web_app.conf.erb +2 -2
  50. data/cookbooks/mu-master/templates/mods/ldap.conf.erb +4 -0
  51. data/cookbooks/mu-php54/Berksfile +1 -2
  52. data/cookbooks/mu-php54/metadata.rb +4 -5
  53. data/cookbooks/mu-php54/recipes/default.rb +1 -1
  54. data/cookbooks/mu-splunk/templates/default/splunk-init.erb +0 -0
  55. data/cookbooks/mu-tools/Berksfile +3 -2
  56. data/cookbooks/mu-tools/files/default/Mu_CA.pem +33 -0
  57. data/cookbooks/mu-tools/libraries/helper.rb +20 -8
  58. data/cookbooks/mu-tools/metadata.rb +5 -2
  59. data/cookbooks/mu-tools/recipes/apply_security.rb +2 -3
  60. data/cookbooks/mu-tools/recipes/eks.rb +1 -1
  61. data/cookbooks/mu-tools/recipes/gcloud.rb +5 -30
  62. data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
  63. data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -0
  64. data/cookbooks/mu-tools/recipes/selinux.rb +19 -0
  65. data/cookbooks/mu-tools/recipes/split_var_partitions.rb +0 -1
  66. data/cookbooks/mu-tools/recipes/windows-client.rb +256 -122
  67. data/cookbooks/mu-tools/resources/disk.rb +3 -1
  68. data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +1 -1
  69. data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
  70. data/cookbooks/mu-tools/templates/default/{kubeconfig.erb → kubeconfig-eks.erb} +0 -0
  71. data/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb +27 -0
  72. data/cookbooks/mu-tools/templates/windows-10/sshd_config.erb +137 -0
  73. data/cookbooks/mu-utility/recipes/nat.rb +4 -0
  74. data/extras/alpha.png +0 -0
  75. data/extras/beta.png +0 -0
  76. data/extras/clean-stock-amis +2 -2
  77. data/extras/generate-stock-images +131 -0
  78. data/extras/git-fix-permissions-hook +0 -0
  79. data/extras/image-generators/AWS/centos6.yaml +17 -0
  80. data/extras/image-generators/{aws → AWS}/centos7-govcloud.yaml +0 -0
  81. data/extras/image-generators/{aws → AWS}/centos7.yaml +0 -0
  82. data/extras/image-generators/{aws → AWS}/rhel7.yaml +0 -0
  83. data/extras/image-generators/{aws → AWS}/win2k12.yaml +0 -0
  84. data/extras/image-generators/{aws → AWS}/win2k16.yaml +0 -0
  85. data/extras/image-generators/{aws → AWS}/windows.yaml +0 -0
  86. data/extras/image-generators/{gcp → Google}/centos6.yaml +1 -0
  87. data/extras/image-generators/Google/centos7.yaml +18 -0
  88. data/extras/python_rpm/build.sh +0 -0
  89. data/extras/release.png +0 -0
  90. data/extras/ruby_rpm/build.sh +0 -0
  91. data/extras/ruby_rpm/muby.spec +1 -1
  92. data/install/README.md +43 -5
  93. data/install/deprecated-bash-library.sh +0 -0
  94. data/install/installer +1 -1
  95. data/install/jenkinskeys.rb +0 -0
  96. data/install/mu-master.yaml +55 -0
  97. data/modules/mommacat.ru +41 -7
  98. data/modules/mu.rb +444 -149
  99. data/modules/mu/adoption.rb +500 -0
  100. data/modules/mu/cleanup.rb +235 -158
  101. data/modules/mu/cloud.rb +675 -138
  102. data/modules/mu/clouds/aws.rb +156 -24
  103. data/modules/mu/clouds/aws/alarm.rb +4 -14
  104. data/modules/mu/clouds/aws/bucket.rb +60 -18
  105. data/modules/mu/clouds/aws/cache_cluster.rb +8 -20
  106. data/modules/mu/clouds/aws/collection.rb +12 -22
  107. data/modules/mu/clouds/aws/container_cluster.rb +209 -118
  108. data/modules/mu/clouds/aws/database.rb +120 -45
  109. data/modules/mu/clouds/aws/dnszone.rb +7 -18
  110. data/modules/mu/clouds/aws/endpoint.rb +5 -15
  111. data/modules/mu/clouds/aws/firewall_rule.rb +144 -72
  112. data/modules/mu/clouds/aws/folder.rb +4 -11
  113. data/modules/mu/clouds/aws/function.rb +6 -16
  114. data/modules/mu/clouds/aws/group.rb +4 -12
  115. data/modules/mu/clouds/aws/habitat.rb +11 -13
  116. data/modules/mu/clouds/aws/loadbalancer.rb +40 -28
  117. data/modules/mu/clouds/aws/log.rb +5 -13
  118. data/modules/mu/clouds/aws/msg_queue.rb +9 -24
  119. data/modules/mu/clouds/aws/nosqldb.rb +4 -12
  120. data/modules/mu/clouds/aws/notifier.rb +6 -13
  121. data/modules/mu/clouds/aws/role.rb +69 -40
  122. data/modules/mu/clouds/aws/search_domain.rb +17 -20
  123. data/modules/mu/clouds/aws/server.rb +184 -94
  124. data/modules/mu/clouds/aws/server_pool.rb +33 -38
  125. data/modules/mu/clouds/aws/storage_pool.rb +5 -12
  126. data/modules/mu/clouds/aws/user.rb +59 -33
  127. data/modules/mu/clouds/aws/userdata/linux.erb +18 -30
  128. data/modules/mu/clouds/aws/userdata/windows.erb +9 -9
  129. data/modules/mu/clouds/aws/vpc.rb +214 -145
  130. data/modules/mu/clouds/azure.rb +978 -44
  131. data/modules/mu/clouds/azure/container_cluster.rb +413 -0
  132. data/modules/mu/clouds/azure/firewall_rule.rb +500 -0
  133. data/modules/mu/clouds/azure/habitat.rb +167 -0
  134. data/modules/mu/clouds/azure/loadbalancer.rb +205 -0
  135. data/modules/mu/clouds/azure/role.rb +211 -0
  136. data/modules/mu/clouds/azure/server.rb +810 -0
  137. data/modules/mu/clouds/azure/user.rb +257 -0
  138. data/modules/mu/clouds/azure/userdata/README.md +4 -0
  139. data/modules/mu/clouds/azure/userdata/linux.erb +137 -0
  140. data/modules/mu/clouds/azure/userdata/windows.erb +275 -0
  141. data/modules/mu/clouds/azure/vpc.rb +782 -0
  142. data/modules/mu/clouds/cloudformation.rb +12 -9
  143. data/modules/mu/clouds/cloudformation/firewall_rule.rb +5 -13
  144. data/modules/mu/clouds/cloudformation/server.rb +10 -1
  145. data/modules/mu/clouds/cloudformation/server_pool.rb +1 -0
  146. data/modules/mu/clouds/cloudformation/vpc.rb +0 -2
  147. data/modules/mu/clouds/google.rb +554 -117
  148. data/modules/mu/clouds/google/bucket.rb +173 -32
  149. data/modules/mu/clouds/google/container_cluster.rb +1112 -157
  150. data/modules/mu/clouds/google/database.rb +24 -47
  151. data/modules/mu/clouds/google/firewall_rule.rb +344 -89
  152. data/modules/mu/clouds/google/folder.rb +156 -79
  153. data/modules/mu/clouds/google/group.rb +272 -82
  154. data/modules/mu/clouds/google/habitat.rb +177 -52
  155. data/modules/mu/clouds/google/loadbalancer.rb +9 -34
  156. data/modules/mu/clouds/google/role.rb +1211 -0
  157. data/modules/mu/clouds/google/server.rb +491 -227
  158. data/modules/mu/clouds/google/server_pool.rb +233 -48
  159. data/modules/mu/clouds/google/user.rb +479 -125
  160. data/modules/mu/clouds/google/userdata/linux.erb +3 -3
  161. data/modules/mu/clouds/google/userdata/windows.erb +9 -9
  162. data/modules/mu/clouds/google/vpc.rb +381 -223
  163. data/modules/mu/config.rb +689 -214
  164. data/modules/mu/config/bucket.rb +1 -1
  165. data/modules/mu/config/cache_cluster.rb +1 -1
  166. data/modules/mu/config/cache_cluster.yml +0 -4
  167. data/modules/mu/config/container_cluster.rb +18 -9
  168. data/modules/mu/config/database.rb +6 -23
  169. data/modules/mu/config/firewall_rule.rb +9 -15
  170. data/modules/mu/config/folder.rb +22 -21
  171. data/modules/mu/config/habitat.rb +22 -21
  172. data/modules/mu/config/loadbalancer.rb +2 -2
  173. data/modules/mu/config/role.rb +9 -40
  174. data/modules/mu/config/server.rb +26 -5
  175. data/modules/mu/config/server_pool.rb +1 -1
  176. data/modules/mu/config/storage_pool.rb +2 -2
  177. data/modules/mu/config/user.rb +4 -0
  178. data/modules/mu/config/vpc.rb +350 -110
  179. data/modules/mu/defaults/{amazon_images.yaml → AWS.yaml} +37 -39
  180. data/modules/mu/defaults/Azure.yaml +17 -0
  181. data/modules/mu/defaults/Google.yaml +24 -0
  182. data/modules/mu/defaults/README.md +1 -1
  183. data/modules/mu/deploy.rb +168 -125
  184. data/modules/mu/groomer.rb +2 -1
  185. data/modules/mu/groomers/ansible.rb +104 -32
  186. data/modules/mu/groomers/chef.rb +96 -44
  187. data/modules/mu/kittens.rb +20602 -0
  188. data/modules/mu/logger.rb +38 -11
  189. data/modules/mu/master.rb +90 -8
  190. data/modules/mu/master/chef.rb +2 -3
  191. data/modules/mu/master/ldap.rb +0 -1
  192. data/modules/mu/master/ssl.rb +250 -0
  193. data/modules/mu/mommacat.rb +917 -513
  194. data/modules/scratchpad.erb +1 -1
  195. data/modules/tests/super_complex_bok.yml +0 -0
  196. data/modules/tests/super_simple_bok.yml +0 -0
  197. data/roles/mu-master.json +2 -1
  198. data/spec/azure_creds +5 -0
  199. data/spec/mu.yaml +56 -0
  200. data/spec/mu/clouds/azure_spec.rb +164 -27
  201. data/spec/spec_helper.rb +5 -0
  202. data/test/clean_up.py +0 -0
  203. data/test/exec_inspec.py +0 -0
  204. data/test/exec_mu_install.py +0 -0
  205. data/test/exec_retry.py +0 -0
  206. data/test/smoke_test.rb +0 -0
  207. metadata +90 -118
  208. data/cookbooks/mu-jenkins/Berksfile +0 -14
  209. data/cookbooks/mu-jenkins/CHANGELOG.md +0 -13
  210. data/cookbooks/mu-jenkins/LICENSE +0 -37
  211. data/cookbooks/mu-jenkins/README.md +0 -105
  212. data/cookbooks/mu-jenkins/attributes/default.rb +0 -42
  213. data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +0 -73
  214. data/cookbooks/mu-jenkins/files/default/deploy_config.xml +0 -44
  215. data/cookbooks/mu-jenkins/metadata.rb +0 -21
  216. data/cookbooks/mu-jenkins/recipes/default.rb +0 -195
  217. data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +0 -54
  218. data/cookbooks/mu-jenkins/recipes/public_key.rb +0 -24
  219. data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +0 -24
  220. data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +0 -14
  221. data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +0 -6
  222. data/cookbooks/nagios/Berksfile +0 -11
  223. data/cookbooks/nagios/CHANGELOG.md +0 -589
  224. data/cookbooks/nagios/CONTRIBUTING.md +0 -11
  225. data/cookbooks/nagios/LICENSE +0 -37
  226. data/cookbooks/nagios/README.md +0 -328
  227. data/cookbooks/nagios/TESTING.md +0 -2
  228. data/cookbooks/nagios/attributes/config.rb +0 -171
  229. data/cookbooks/nagios/attributes/default.rb +0 -228
  230. data/cookbooks/nagios/chefignore +0 -102
  231. data/cookbooks/nagios/definitions/command.rb +0 -33
  232. data/cookbooks/nagios/definitions/contact.rb +0 -33
  233. data/cookbooks/nagios/definitions/contactgroup.rb +0 -33
  234. data/cookbooks/nagios/definitions/host.rb +0 -33
  235. data/cookbooks/nagios/definitions/hostdependency.rb +0 -33
  236. data/cookbooks/nagios/definitions/hostescalation.rb +0 -34
  237. data/cookbooks/nagios/definitions/hostgroup.rb +0 -33
  238. data/cookbooks/nagios/definitions/nagios_conf.rb +0 -38
  239. data/cookbooks/nagios/definitions/resource.rb +0 -33
  240. data/cookbooks/nagios/definitions/service.rb +0 -33
  241. data/cookbooks/nagios/definitions/servicedependency.rb +0 -33
  242. data/cookbooks/nagios/definitions/serviceescalation.rb +0 -34
  243. data/cookbooks/nagios/definitions/servicegroup.rb +0 -33
  244. data/cookbooks/nagios/definitions/timeperiod.rb +0 -33
  245. data/cookbooks/nagios/libraries/base.rb +0 -314
  246. data/cookbooks/nagios/libraries/command.rb +0 -91
  247. data/cookbooks/nagios/libraries/contact.rb +0 -230
  248. data/cookbooks/nagios/libraries/contactgroup.rb +0 -112
  249. data/cookbooks/nagios/libraries/custom_option.rb +0 -36
  250. data/cookbooks/nagios/libraries/data_bag_helper.rb +0 -23
  251. data/cookbooks/nagios/libraries/default.rb +0 -90
  252. data/cookbooks/nagios/libraries/host.rb +0 -412
  253. data/cookbooks/nagios/libraries/hostdependency.rb +0 -181
  254. data/cookbooks/nagios/libraries/hostescalation.rb +0 -173
  255. data/cookbooks/nagios/libraries/hostgroup.rb +0 -119
  256. data/cookbooks/nagios/libraries/nagios.rb +0 -282
  257. data/cookbooks/nagios/libraries/resource.rb +0 -59
  258. data/cookbooks/nagios/libraries/service.rb +0 -455
  259. data/cookbooks/nagios/libraries/servicedependency.rb +0 -215
  260. data/cookbooks/nagios/libraries/serviceescalation.rb +0 -195
  261. data/cookbooks/nagios/libraries/servicegroup.rb +0 -144
  262. data/cookbooks/nagios/libraries/timeperiod.rb +0 -160
  263. data/cookbooks/nagios/libraries/users_helper.rb +0 -54
  264. data/cookbooks/nagios/metadata.rb +0 -25
  265. data/cookbooks/nagios/recipes/_load_databag_config.rb +0 -153
  266. data/cookbooks/nagios/recipes/_load_default_config.rb +0 -241
  267. data/cookbooks/nagios/recipes/apache.rb +0 -48
  268. data/cookbooks/nagios/recipes/default.rb +0 -204
  269. data/cookbooks/nagios/recipes/nginx.rb +0 -82
  270. data/cookbooks/nagios/recipes/pagerduty.rb +0 -143
  271. data/cookbooks/nagios/recipes/server_package.rb +0 -40
  272. data/cookbooks/nagios/recipes/server_source.rb +0 -164
  273. data/cookbooks/nagios/templates/default/apache2.conf.erb +0 -96
  274. data/cookbooks/nagios/templates/default/cgi.cfg.erb +0 -266
  275. data/cookbooks/nagios/templates/default/commands.cfg.erb +0 -13
  276. data/cookbooks/nagios/templates/default/contacts.cfg.erb +0 -37
  277. data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +0 -25
  278. data/cookbooks/nagios/templates/default/hosts.cfg.erb +0 -15
  279. data/cookbooks/nagios/templates/default/htpasswd.users.erb +0 -6
  280. data/cookbooks/nagios/templates/default/nagios.cfg.erb +0 -22
  281. data/cookbooks/nagios/templates/default/nginx.conf.erb +0 -62
  282. data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +0 -185
  283. data/cookbooks/nagios/templates/default/resource.cfg.erb +0 -27
  284. data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +0 -15
  285. data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +0 -14
  286. data/cookbooks/nagios/templates/default/services.cfg.erb +0 -14
  287. data/cookbooks/nagios/templates/default/templates.cfg.erb +0 -31
  288. data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +0 -13
  289. data/extras/image-generators/aws/centos6.yaml +0 -18
  290. data/modules/mu/defaults/google_images.yaml +0 -16
  291. data/roles/mu-master-jenkins.json +0 -24
@@ -15,30 +15,16 @@
15
15
  module MU
16
16
  class Cloud
17
17
  class Google
18
- # Creates an Google project as configured in {MU::Config::BasketofKittens::folders}
18
+ # Creates a Google folder as configured in {MU::Config::BasketofKittens::folders}
19
19
  class Folder < MU::Cloud::Folder
20
- @deploy = nil
21
- @config = nil
22
- @parent = nil
23
-
24
- attr_reader :mu_name
25
- attr_reader :config
26
- attr_reader :cloud_id
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::folders}
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
-
35
- if !mu_name.nil?
36
- @mu_name = mu_name
37
- elsif @config['scrub_mu_isms']
38
- @mu_name = @config['name']
39
- else
40
- @mu_name = @deploy.getResourceName(@config['name'])
41
- end
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 <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
+ cloud_desc if @cloud_id # XXX this maybe isn't my job
26
+
27
+ @mu_name ||= @deploy.getResourceName(@config['name'])
42
28
  end
43
29
 
44
30
  # Called automatically by {MU::Deploy#createResources}
@@ -54,6 +40,9 @@ module MU
54
40
  display_name: name_string
55
41
  }
56
42
 
43
+ if @config['parent']['name'] and !@config['parent']['id']
44
+ @config['parent']['deploy_id'] = @deploy.deploy_id
45
+ end
57
46
  parent = MU::Cloud::Google::Folder.resolveParent(@config['parent'], credentials: @config['credentials'])
58
47
 
59
48
  folder_obj = MU::Cloud::Google.folder(:Folder).new(params)
@@ -79,6 +68,20 @@ module MU
79
68
  end
80
69
  end while found.size == 0
81
70
 
71
+ @habitat = parent
72
+
73
+ end
74
+
75
+ # Retrieve the IAM bindings for this folder (associates between IAM roles and groups/users)
76
+ def bindings
77
+ MU::Cloud::Google::Folder.bindings(@cloud_id, credentials: @config['credentials'])
78
+ end
79
+
80
+ # Retrieve the IAM bindings for this folder (associates between IAM roles and groups/users)
81
+ # @param folder [String]:
82
+ # @param credentials [String]:
83
+ def self.bindings(folder, credentials: nil)
84
+ MU::Cloud::Google.folder(credentials: credentials).get_folder_iam_policy(folder).bindings
82
85
  end
83
86
 
84
87
  # Given a {MU::Config::Folder.reference} configuration block, resolve
@@ -88,9 +91,9 @@ module MU
88
91
  # @return [String]
89
92
  def self.resolveParent(parentblock, credentials: nil)
90
93
  my_org = MU::Cloud::Google.getOrg(credentials)
91
- if !parentblock or parentblock['id'] == my_org.name or
94
+ if my_org and (!parentblock or parentblock['id'] == my_org.name or
92
95
  parentblock['name'] == my_org.display_name or (parentblock['id'] and
93
- "organizations/"+parentblock['id'] == my_org.name)
96
+ "organizations/"+parentblock['id'] == my_org.name))
94
97
  return my_org.name
95
98
  end
96
99
 
@@ -103,12 +106,12 @@ module MU
103
106
  name: parentblock['name']
104
107
  ).first
105
108
  if sib_folder
106
- return "folders/"+sib_folder.cloudobj.cloud_id
109
+ return sib_folder.cloud_desc.name
107
110
  end
108
111
  end
109
112
 
110
113
  begin
111
- found = MU::Cloud::Google::Folder.find(cloud_id: parentblock['id'], credentials: credentials, flags: { 'display_name' => parentblock['name'] })
114
+ found = MU::Cloud::Google::Folder.find(cloud_id: parentblock['id'], credentials: credentials, flags: { 'display_name' => parentblock['name'] })
112
115
  rescue ::Google::Apis::ClientError => e
113
116
  if !e.message.match(/Invalid request status_code: 404/)
114
117
  raise e
@@ -123,11 +126,14 @@ module MU
123
126
  end
124
127
 
125
128
  # Return the cloud descriptor for the Folder
129
+ # @return [Google::Apis::Core::Hashable]
126
130
  def cloud_desc
127
- MU::Cloud::Google::Folder.find(cloud_id: @cloud_id).values.first
131
+ @cached_cloud_desc ||= MU::Cloud::Google::Folder.find(cloud_id: @cloud_id, credentials: @config['credentials']).values.first
132
+ @habitat_id ||= @cached_cloud_desc.parent.sub(/^(folders|organizations)\//, "")
133
+ @cached_cloud_desc
128
134
  end
129
135
 
130
- # Return the metadata for this project's configuration
136
+ # Return the metadata for this folders's configuration
131
137
  # @return [Hash]
132
138
  def notify
133
139
  desc = MU.structToHash(MU::Cloud::Google.folder(credentials: @config['credentials']).get_folder("folders/"+@cloud_id))
@@ -147,7 +153,7 @@ module MU
147
153
  # Denote whether this resource implementation is experiment, ready for
148
154
  # testing, or ready for production use.
149
155
  def self.quality
150
- MU::Cloud::BETA
156
+ MU::Cloud::RELEASE
151
157
  end
152
158
 
153
159
  # Remove all Google projects associated with the currently loaded deployment. Try to, anyway.
@@ -158,54 +164,70 @@ module MU
158
164
  # We can't label GCP folders, and their names are too short to encode
159
165
  # Mu deploy IDs, so all we can do is rely on flags['known'] passed in
160
166
  # from cleanup, which relies on our metadata to know what's ours.
161
-
167
+ #noop = true
162
168
  if flags and flags['known']
169
+ threads = []
163
170
  flags['known'].each { |cloud_id|
164
- found = self.find(cloud_id: cloud_id, credentials: credentials)
165
- if found.size > 0 and found.values.first.lifecycle_state == "ACTIVE"
166
- MU.log "Deleting folder #{found.values.first.display_name} (#{found.keys.first})"
167
- if !noop
168
- max_retries = 10
169
- retries = 0
170
- success = false
171
- begin
172
- MU::Cloud::Google.folder(credentials: credentials).delete_folder(
173
- "folders/"+found.keys.first
174
- )
175
- found = self.find(cloud_id: cloud_id, credentials: credentials)
176
- if found and found.size > 0 and found.values.first.lifecycle_state != "DELETE_REQUESTED"
177
- if retries < max_retries
171
+ threads << Thread.new {
172
+
173
+ found = self.find(cloud_id: cloud_id, credentials: credentials)
174
+
175
+ if found.size > 0 and found.values.first.lifecycle_state == "ACTIVE"
176
+ MU.log "Deleting folder #{found.values.first.display_name} (#{found.keys.first})"
177
+ if !noop
178
+ max_retries = 10
179
+ retries = 0
180
+ success = false
181
+ begin
182
+ MU::Cloud::Google.folder(credentials: credentials).delete_folder(
183
+ "folders/"+found.keys.first
184
+ )
185
+ found = self.find(cloud_id: cloud_id, credentials: credentials)
186
+ if found and found.size > 0 and found.values.first.lifecycle_state != "DELETE_REQUESTED"
187
+ if retries < max_retries
188
+ sleep 30
189
+ retries += 1
190
+ puts retries
191
+ else
192
+ MU.log "Folder #{cloud_id} still exists after #{max_retries.to_s} attempts to delete", MU::ERR
193
+ break
194
+ end
195
+ else
196
+ success = true
197
+ end
198
+
199
+ rescue ::Google::Apis::ClientError => e
200
+ # XXX maybe see if the folder has disappeared already?
201
+ # XXX look for child folders that haven't been deleted, that's what this tends
202
+ # to mean
203
+ if e.message.match(/failedPrecondition/) and retries < max_retries
178
204
  sleep 30
179
205
  retries += 1
180
- puts retries
206
+ retry
181
207
  else
182
- MU.log "Folder #{cloud_id} still exists after #{max_retries.to_s} attempts to delete", MU::ERR
208
+ MU.log "Got 'failedPrecondition' a bunch while trying to delete #{found.values.first.display_name} (#{found.keys.first})", MU::ERR
183
209
  break
184
210
  end
185
- else
186
- success = true
187
- end
188
-
189
- rescue ::Google::Apis::ClientError => e
190
- if e.message.match(/failedPrecondition/) and retries < max_retries
191
- sleep 30
192
- retries += 1
193
- retry
194
- else
195
- raise e
196
- end
197
- end while !success
211
+ end while !success
212
+ end
198
213
  end
199
- end
214
+ }
215
+ }
216
+ threads.each { |t|
217
+ t.join
200
218
  }
201
219
  end
202
220
  end
203
221
 
204
- # Locate an existing project
205
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
206
- # @param flags [Hash]: Optional flags
207
- # @return [OpenStruct]: The cloud provider's complete descriptions of matching project
208
- def self.find(cloud_id: nil, credentials: nil, flags: {}, tag_key: nil, tag_value: nil)
222
+ # Locate and return cloud provider descriptors of this resource type
223
+ # which match the provided parameters, or all visible resources if no
224
+ # filters are specified. At minimum, implementations of +find+ must
225
+ # honor +credentials+ and +cloud_id+ arguments. We may optionally
226
+ # support other search methods, such as +tag_key+ and +tag_value+, or
227
+ # cloud-specific arguments like +project+. See also {MU::MommaCat.findStray}.
228
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
229
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
230
+ def self.find(**args)
209
231
  found = {}
210
232
 
211
233
  # Recursively search a GCP folder hierarchy for a folder matching our
@@ -227,33 +249,88 @@ module MU
227
249
  nil
228
250
  end
229
251
 
230
- if cloud_id
231
- found[cloud_id.sub(/^folders\//, "")] = MU::Cloud::Google.folder(credentials: credentials).get_folder("folders/"+cloud_id.sub(/^folders\//, ""))
232
- elsif flags['display_name']
233
- parent = if flags['parent_id']
234
- flags['parent_id']
235
- else
236
- my_org = MU::Cloud::Google.getOrg(credentials)
237
- my_org.name
238
- end
252
+ parent = if args[:flags] and args[:flags]['parent_id']
253
+ args[:flags]['parent_id']
254
+ else
255
+ my_org = MU::Cloud::Google.getOrg(args[:credentials])
256
+ my_org.name
257
+ end
258
+
259
+ if args[:cloud_id]
260
+ raw_id = args[:cloud_id].sub(/^folders\//, "")
261
+ resp = MU::Cloud::Google.folder(credentials: args[:credentials]).get_folder("folders/"+raw_id)
262
+ found[resp.name] = resp if resp
263
+
264
+ elsif args[:flags] and args[:flags]['display_name']
239
265
 
240
266
  if parent
241
- resp = self.find_matching_folder(parent, name: flags['display_name'], credentials: credentials)
267
+ resp = self.find_matching_folder(parent, name: args[:flags]['display_name'], credentials: args[:credentials])
242
268
  if resp
243
- found[resp.name.sub(/^folders\//, "")] = resp
269
+ found[resp.name] = resp
244
270
  end
245
271
  end
272
+ else
273
+ resp = MU::Cloud::Google.folder(credentials: args[:credentials]).list_folders(parent: parent)
274
+ if resp and resp.folders
275
+ resp.folders.each { |folder|
276
+ next if folder.lifecycle_state == "DELETE_REQUESTED"
277
+ found[folder.name] = folder
278
+ # recurse so that we'll pick up child folders
279
+ children = self.find(
280
+ credentials: args[:credentials],
281
+ flags: { 'parent_id' => folder.name }
282
+ )
283
+ if !children.nil? and !children.empty?
284
+ found.merge!(children)
285
+ end
286
+ }
287
+ end
246
288
  end
247
-
289
+
248
290
  found
249
291
  end
250
292
 
293
+ # Reverse-map our cloud description into a runnable config hash.
294
+ # We assume that any values we have in +@config+ are placeholders, and
295
+ # calculate our own accordingly based on what's live in the cloud.
296
+ def toKitten(rootparent: nil, billing: nil, habitats: nil)
297
+ bok = {
298
+ "cloud" => "Google",
299
+ "credentials" => @config['credentials']
300
+ }
301
+
302
+ bok['display_name'] = cloud_desc.display_name
303
+ bok['cloud_id'] = cloud_desc.name
304
+ bok['name'] = cloud_desc.display_name#+bok['cloud_id'] # only way to guarantee uniqueness
305
+ if cloud_desc.parent.match(/^folders\/(.*)/)
306
+ MU.log bok['display_name']+" generating reference", MU::NOTICE, details: cloud_desc.parent
307
+ bok['parent'] = MU::Config::Ref.get(
308
+ id: cloud_desc.parent,
309
+ cloud: "Google",
310
+ credentials: @config['credentials'],
311
+ type: "folders"
312
+ )
313
+ elsif rootparent
314
+ bok['parent'] = {
315
+ 'id' => rootparent.is_a?(String) ? rootparent : rootparent.cloud_desc.name
316
+ }
317
+ else
318
+ bok['parent'] = { 'id' => cloud_desc.parent }
319
+ end
320
+
321
+ bok
322
+ end
323
+
251
324
  # Cloud-specific configuration properties.
252
325
  # @param config [MU::Config]: The calling MU::Config object
253
326
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
254
327
  def self.schema(config)
255
328
  toplevel_required = []
256
329
  schema = {
330
+ "display_name" => {
331
+ "type" => "string",
332
+ "description" => "The +display_name+ field of this folder, specified only if we want it to be something other than the automatically-generated string derived from the +name+ field.",
333
+ }
257
334
  }
258
335
  [toplevel_required, schema]
259
336
  end
@@ -266,7 +343,7 @@ module MU
266
343
  ok = true
267
344
 
268
345
  if !MU::Cloud::Google.getOrg(folder['credentials'])
269
- MU.log "Cannot manage Google Cloud projects in environments without an organization. See also: https://cloud.google.com/resource-manager/docs/creating-managing-organization", MU::ERR
346
+ MU.log "Cannot manage Google Cloud folders in environments without an organization", MU::ERR
270
347
  ok = false
271
348
  end
272
349
 
@@ -17,36 +17,102 @@ module MU
17
17
  class Google
18
18
  # A group as configured in {MU::Config::BasketofKittens::groups}
19
19
  class Group < MU::Cloud::Group
20
- @deploy = nil
21
- @config = nil
22
- attr_reader :mu_name
23
- attr_reader :config
24
- attr_reader :cloud_id
25
-
26
- # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
27
- # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::groups}
28
- def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
29
- @deploy = mommacat
30
- @config = MU::Config.manxify(kitten_cfg)
31
- @cloud_id ||= cloud_id
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 <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
+
32
26
  @mu_name ||= @deploy.getResourceName(@config["name"])
33
27
  end
34
28
 
35
29
  # Called automatically by {MU::Deploy#createResources}
36
30
  def create
37
- bind_group
31
+ if !@config['external']
32
+ if !@config['email']
33
+ domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer)
34
+ @config['email'] = @mu_name.downcase+"@"+domains.domains.first.domain_name
35
+ end
36
+ group_obj = MU::Cloud::Google.admin_directory(:Group).new(
37
+ name: @mu_name,
38
+ email: @config['email'],
39
+ description: @deploy.deploy_id
40
+ )
41
+
42
+ MU.log "Creating group #{@mu_name}", details: group_obj
43
+
44
+ resp = MU::Cloud::Google.admin_directory(credentials: @credentials).insert_group(group_obj)
45
+ @cloud_id = resp.email
46
+
47
+ MU::Cloud::Google::Role.bindFromConfig("group", @cloud_id, @config['roles'], credentials: @config['credentials'])
48
+ else
49
+ @cloud_id = @config['name'].sub(/@.*/, "")+"@"+@config['domain']
50
+ end
38
51
  end
39
52
 
40
53
  # Called automatically by {MU::Deploy#createResources}
41
54
  def groom
42
- bind_group
55
+ MU::Cloud::Google::Role.bindFromConfig("group", @cloud_id, @config['roles'], credentials: @config['credentials'], debug: true)
56
+
57
+ if @config['members']
58
+ resolved_desired = []
59
+ @config['members'].each { |m|
60
+ sibling_user = @deploy.findLitterMate(name: m, type: "users")
61
+ usermail = if sibling_user
62
+ sibling_user.cloud_id
63
+ elsif !m.match(/@/)
64
+ domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer)
65
+ m+"@"+domains.domains.first.domain_name
66
+ else
67
+ m
68
+ end
69
+ resolved_desired << usermail
70
+ next if members.include?(usermail)
71
+ MU.log "Adding user #{usermail} to group #{@mu_name}"
72
+ MU::Cloud::Google.admin_directory(credentials: @credentials).insert_member(
73
+ @cloud_id,
74
+ MU::Cloud::Google.admin_directory(:Member).new(
75
+ email: usermail
76
+ )
77
+ )
78
+ }
79
+
80
+ deletia = members - resolved_desired
81
+ deletia.each { |m|
82
+ MU.log "Removing user #{m} from group #{@mu_name}", MU::NOTICE
83
+ MU::Cloud::Google.admin_directory(credentials: @credentials).delete_member(@cloud_id, m)
84
+ }
85
+
86
+ # Theoretically there can be a delay
87
+ begin
88
+ if members.sort != resolved_desired.sort
89
+ sleep 3
90
+ end
91
+ end while members.sort != resolved_desired.sort
92
+ end
93
+
94
+ end
95
+
96
+ # Retrieve a list of users (by cloud id) of this group
97
+ def members
98
+ resp = MU::Cloud::Google.admin_directory(credentials: @credentials).list_members(@cloud_id)
99
+ members = []
100
+ if resp and resp.members
101
+ members = resp.members.map { |m| m.email }
102
+ # XXX reject status != "ACTIVE" ?
103
+ end
104
+ members
43
105
  end
44
106
 
45
107
  # Return the metadata for this group configuration
46
108
  # @return [Hash]
47
109
  def notify
48
- {
49
- }
110
+ if !@config['external']
111
+ base = MU.structToHash(cloud_desc)
112
+ end
113
+ base ||= {}
114
+
115
+ base
50
116
  end
51
117
 
52
118
  # Does this resource type exist as a global (cloud-wide) artifact, or
@@ -56,10 +122,18 @@ module MU
56
122
  true
57
123
  end
58
124
 
125
+ # Return the list of "container" resource types in which this resource
126
+ # can reside. The list will include an explicit nil if this resource
127
+ # can exist outside of any container.
128
+ # @return [Array<Symbol,nil>]
129
+ def self.canLiveIn
130
+ [nil]
131
+ end
132
+
59
133
  # Denote whether this resource implementation is experiment, ready for
60
134
  # testing, or ready for production use.
61
135
  def self.quality
62
- MU::Cloud::ALPHA
136
+ MU::Cloud::BETA
63
137
  end
64
138
 
65
139
  # Remove all groups associated with the currently loaded deployment.
@@ -68,19 +142,90 @@ module MU
68
142
  # @param region [String]: The cloud provider region
69
143
  # @return [void]
70
144
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
145
+ my_domains = MU::Cloud::Google.getDomains(credentials)
146
+ my_org = MU::Cloud::Google.getOrg(credentials)
147
+
148
+ if my_org
149
+ groups = MU::Cloud::Google.admin_directory(credentials: credentials).list_groups(customer: MU::Cloud::Google.customerID(credentials)).groups
150
+ if groups
151
+ groups.each { |group|
152
+ if group.description == MU.deploy_id
153
+ MU.log "Deleting group #{group.name} from #{my_org.display_name}", details: group
154
+ if !noop
155
+ MU::Cloud::Google.admin_directory(credentials: credentials).delete_group(group.id)
156
+ end
157
+ end
158
+ }
159
+ end
160
+ end
161
+
162
+ if flags['known']
163
+ flags['known'].each { |group|
164
+ MU::Cloud::Google::Role.removeBindings("group", group, credentials: credentials, noop: noop)
165
+ }
166
+ end
71
167
  end
72
168
 
73
- # Locate an existing group group.
74
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
75
- # @param region [String]: The cloud provider region.
76
- # @param flags [Hash]: Optional flags
77
- # @return [OpenStruct]: The cloud provider's complete descriptions of matching group group.
78
- def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {}, tag_key: nil, tag_value: nil)
79
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
80
- found = nil
169
+ # Locate and return cloud provider descriptors of this resource type
170
+ # which match the provided parameters, or all visible resources if no
171
+ # filters are specified. At minimum, implementations of +find+ must
172
+ # honor +credentials+ and +cloud_id+ arguments. We may optionally
173
+ # support other search methods, such as +tag_key+ and +tag_value+, or
174
+ # cloud-specific arguments like +project+. See also {MU::MommaCat.findStray}.
175
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
176
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
177
+ def self.find(**args)
178
+ found = {}
179
+
180
+ # The API treats the email address field as its main identifier, so
181
+ # we'll go ahead and respect that.
182
+ if args[:cloud_id]
183
+ begin
184
+ resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).get_group(args[:cloud_id])
185
+ found[resp.email] = resp if resp
186
+ rescue ::Google::Apis::ClientError => e
187
+ raise e if !e.message.match(/forbidden: /)
188
+ end
189
+ else
190
+ resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).list_groups(customer: MU::Cloud::Google.customerID(args[:credentials]))
191
+ if resp and resp.groups
192
+ found = Hash[resp.groups.map { |g| [g.email, g] }]
193
+ end
194
+ end
195
+ # XXX what about Google Groups groups and other external groups? Where do we fish for those? Do we even need to?
81
196
  found
82
197
  end
83
198
 
199
+ # Reverse-map our cloud description into a runnable config hash.
200
+ # We assume that any values we have in +@config+ are placeholders, and
201
+ # calculate our own accordingly based on what's live in the cloud.
202
+ def toKitten(rootparent: nil, billing: nil, habitats: nil)
203
+
204
+ bok = {
205
+ "cloud" => "Google",
206
+ "credentials" => @config['credentials']
207
+ }
208
+
209
+ bok['name'] = cloud_desc.name
210
+ bok['cloud_id'] = cloud_desc.email
211
+ bok['members'] = members
212
+ bok['members'].each { |m|
213
+ m = MU::Config::Ref.get(
214
+ id: m,
215
+ cloud: "Google",
216
+ credentials: @config['credentials'],
217
+ type: "users"
218
+ )
219
+ }
220
+ group_roles = MU::Cloud::Google::Role.getAllBindings(@config['credentials'])["by_entity"]
221
+ if group_roles["group"] and group_roles["group"][bok['cloud_id']] and
222
+ group_roles["group"][bok['cloud_id']].size > 0
223
+ bok['roles'] = MU::Cloud::Google::Role.entityBindingsToSchema(group_roles["group"][bok['cloud_id']], credentials: @config['credentials'])
224
+ end
225
+
226
+ bok
227
+ end
228
+
84
229
  # Cloud-specific configuration properties.
85
230
  # @param config [MU::Config]: The calling MU::Config object
86
231
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
@@ -89,20 +234,32 @@ module MU
89
234
  schema = {
90
235
  "name" => {
91
236
  "type" => "string",
92
- "description" => "This must be the email address of an existing Google Group (+foo@googlegroups.com+), or of a federated GSuite or Cloud Identity domain group from your organization."
237
+ "description" => "This can include an optional @domain component (<tt>foo@example.com</tt>).
238
+
239
+ If the domain portion is not specified, and we manage exactly one GSuite or Cloud Identity domain, we will attempt to create the group in that domain.
240
+
241
+ If we do not manage any domains, and none are specified, we will assume <tt>@googlegroups.com</tt> for the domain and attempt to bind an existing external Google Group to roles under our jurisdiction.
242
+
243
+ If the domain portion is specified, and our credentials can manage that domain via GSuite or Cloud Identity, we will attempt to create the group in that domain.
244
+
245
+ If it is a domain we do not manage, we will attempt to bind an existing external group from that domain to roles under our jurisdiction.
246
+
247
+ If we are binding (rather than creating) a group 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.
248
+
249
+ "
250
+ },
251
+ "domain" => {
252
+ "type" => "string",
253
+ "description" => "The domain from which the group originates or in which it should be created. This can instead be embedded in the {name} field: +foo@example.com+."
93
254
  },
255
+ "external" => {
256
+ "type" => "boolean",
257
+ "description" => "Explicitly flag this group as originating from an external domain. This should always autodetect correctly."
258
+ },
259
+
94
260
  "roles" => {
95
261
  "type" => "array",
96
- "description" => "One or more Google IAM roles to associate with this group.",
97
- "default" => ["roles/viewer"],
98
- "items" => {
99
- "type" => "string",
100
- "description" => "One or more Google IAM roles to associate with this group. Google Cloud groups are not created directly; pre-existing Google Groups 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."
101
- }
102
- },
103
- "project" => {
104
- "type" => "string",
105
- "description" => "The project into which to deploy resources"
262
+ "items" => MU::Cloud::Google::Role.ref_schema
106
263
  }
107
264
  }
108
265
  [toplevel_required, schema]
@@ -114,64 +271,97 @@ module MU
114
271
  # @return [Boolean]: True if validation succeeded, False otherwise
115
272
  def self.validateConfig(group, configurator)
116
273
  ok = true
117
- if group['members'] and group['members'].size > 0 and
118
- !$MU_CFG['google']['masquerade_as']
119
- MU.log "Cannot change Google group memberships in non-GSuite environments.\nVisit https://groups.google.com to manage groups.", MU::ERR
120
- ok = false
121
- end
122
274
 
123
- ok
124
- end
275
+ my_domains = MU::Cloud::Google.getDomains(group['credentials'])
276
+ my_org = MU::Cloud::Google.getOrg(group['credentials'])
125
277
 
126
- private
278
+ if group['name'].match(/@(.*+)$/)
279
+ domain = Regexp.last_match[1].downcase
280
+ if domain and group['domain'] and domain != group['domain'].downcase
281
+ MU.log "Group #{group['name']} had a domain component, but the domain field was also specified (#{group['domain']}) and they don't match."
282
+ ok = false
283
+ end
284
+ group['domain'] = domain
285
+
286
+ if !my_domains or !my_domains.include?(domain)
287
+ group['external'] = true
127
288
 
128
- def bind_group
129
- bindings = []
130
- ext_policy = MU::Cloud::Google.resource_manager(credentials: @config['credentials']).get_project_iam_policy(
131
- @config['project']
132
- )
133
-
134
- change_needed = false
135
- @config['roles'].each { |role|
136
- seen = false
137
- ext_policy.bindings.each { |b|
138
- if b.role == role
139
- seen = true
140
- if !b.members.include?("user:"+@config['name'])
141
- change_needed = true
142
- b.members << "group:"+@config['name']
289
+ if !["googlegroups.com", "google.com"].include?(domain)
290
+ MU.log "#{group['name']} appears to be a member of a domain that our credentials (#{group['credentials']}) do not manage; attempts to grant access for this group may fail!", MU::WARN
291
+ end
292
+
293
+ if !group['roles'] or group['roles'].empty?
294
+ group['roles'] = [
295
+ {
296
+ "role" => {
297
+ "id" => "roles/viewer"
298
+ }
299
+ }
300
+ ]
301
+ if my_org
302
+ group['roles'][0]["organizations"] = [my_org.name]
303
+ else
304
+ group['roles'][0]["projects"] = {
305
+ "id" => group["project"]
306
+ }
143
307
  end
308
+ MU.log "External Google group specified with no role binding, will grant 'viewer' in #{my_org ? "organization #{my_org.display_name}" : "project #{group['project']}"}", MU::WARN
144
309
  end
145
- }
146
- if !seen
147
- ext_policy.bindings << MU::Cloud::Google.resource_manager(:Binding).new(
148
- role: role,
149
- members: ["group:"+@config['name']]
150
- )
151
- change_needed = true
152
310
  end
153
- }
311
+ else
312
+ if !group['domain']
313
+ if my_domains.size == 1
314
+ group['domain'] = my_domains.first
315
+ elsif my_domains.size > 1
316
+ MU.log "Google interactive User #{group['name']} did not specify a domain, and we have multiple defaults available. Must specify exactly one.", MU::ERR, details: my_domains
317
+ ok = false
318
+ else
319
+ group['domain'] = "googlegroups.com"
320
+ end
321
+ end
322
+ end
154
323
 
155
- if change_needed
156
- req_obj = MU::Cloud::Google.resource_manager(:SetIamPolicyRequest).new(
157
- policy: ext_policy
158
- )
159
- MU.log "Adding group #{@config['name']} to Google Cloud project #{@config['project']}", details: @config['roles']
160
324
 
161
- begin
162
- MU::Cloud::Google.resource_manager(credentials: @config['credentials']).set_project_iam_policy(
163
- @config['project'],
164
- req_obj
165
- )
166
- rescue ::Google::Apis::ClientError => e
167
- if e.message.match(/does not exist/i) and !MU::Cloud::Google.credConfig(@config['credentials'])['masquerade_as']
168
- raise MuError, "Group #{@config['name']} does not exist, and we cannot create Google groups in non-GSuite environments.\nVisit https://groups.google.com to manage groups."
169
- end
170
- raise e
325
+ credcfg = MU::Cloud::Google.credConfig(group['credentials'])
326
+
327
+ if group['external'] and group['members']
328
+ MU.log "Cannot manage memberships for external group #{group['name']}", MU::ERR
329
+ if group['domain'] == "googlegroups.com"
330
+ MU.log "Visit https://groups.google.com to manage Google Groups.", MU::ERR
171
331
  end
332
+ ok = false
172
333
  end
334
+
335
+ if group['members']
336
+ group['members'].each { |m|
337
+ if configurator.haveLitterMate?(m, "users")
338
+ group['dependencies'] ||= []
339
+ group['dependencies'] << {
340
+ "name" => m,
341
+ "type" => "user"
342
+ }
343
+ end
344
+ }
345
+ end
346
+
347
+ if group['roles']
348
+ group['roles'].each { |r|
349
+ if r['role'] and r['role']['name'] and
350
+ (!r['role']['deploy_id'] and !r['role']['id'])
351
+ group['dependencies'] ||= []
352
+ group['dependencies'] << {
353
+ "type" => "role",
354
+ "name" => r['role']['name']
355
+ }
356
+ end
357
+ }
358
+ end
359
+
360
+ ok
173
361
  end
174
362
 
363
+ private
364
+
175
365
  end
176
366
  end
177
367
  end