cloud-mu 3.1.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +10 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -3
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +135 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +165 -111
  45. data/modules/mu/adoption.rb +401 -68
  46. data/modules/mu/cleanup.rb +199 -306
  47. data/modules/mu/cloud.rb +100 -1632
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +46 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +920 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +165 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +4 -4
  61. data/modules/mu/config/cache_cluster.rb +1 -1
  62. data/modules/mu/config/collection.rb +4 -4
  63. data/modules/mu/config/container_cluster.rb +9 -4
  64. data/modules/mu/config/database.rb +83 -104
  65. data/modules/mu/config/database.yml +1 -2
  66. data/modules/mu/config/dnszone.rb +6 -6
  67. data/modules/mu/config/doc_helpers.rb +516 -0
  68. data/modules/mu/config/endpoint.rb +4 -4
  69. data/modules/mu/config/firewall_rule.rb +103 -4
  70. data/modules/mu/config/folder.rb +4 -4
  71. data/modules/mu/config/function.rb +3 -3
  72. data/modules/mu/config/group.rb +4 -4
  73. data/modules/mu/config/habitat.rb +4 -4
  74. data/modules/mu/config/loadbalancer.rb +60 -14
  75. data/modules/mu/config/log.rb +4 -4
  76. data/modules/mu/config/msg_queue.rb +4 -4
  77. data/modules/mu/config/nosqldb.rb +4 -4
  78. data/modules/mu/config/notifier.rb +3 -3
  79. data/modules/mu/config/ref.rb +365 -0
  80. data/modules/mu/config/role.rb +4 -4
  81. data/modules/mu/config/schema_helpers.rb +509 -0
  82. data/modules/mu/config/search_domain.rb +4 -4
  83. data/modules/mu/config/server.rb +97 -70
  84. data/modules/mu/config/server.yml +1 -0
  85. data/modules/mu/config/server_pool.rb +5 -9
  86. data/modules/mu/config/storage_pool.rb +1 -1
  87. data/modules/mu/config/tail.rb +200 -0
  88. data/modules/mu/config/user.rb +4 -4
  89. data/modules/mu/config/vpc.rb +70 -27
  90. data/modules/mu/config/vpc.yml +0 -1
  91. data/modules/mu/defaults/AWS.yaml +83 -60
  92. data/modules/mu/defaults/Azure.yaml +1 -0
  93. data/modules/mu/defaults/Google.yaml +3 -2
  94. data/modules/mu/deploy.rb +30 -26
  95. data/modules/mu/groomer.rb +17 -2
  96. data/modules/mu/groomers/ansible.rb +188 -41
  97. data/modules/mu/groomers/chef.rb +116 -55
  98. data/modules/mu/logger.rb +127 -148
  99. data/modules/mu/master.rb +389 -2
  100. data/modules/mu/master/chef.rb +3 -4
  101. data/modules/mu/master/ldap.rb +3 -3
  102. data/modules/mu/master/ssl.rb +12 -3
  103. data/modules/mu/mommacat.rb +217 -2612
  104. data/modules/mu/mommacat/daemon.rb +397 -0
  105. data/modules/mu/mommacat/naming.rb +473 -0
  106. data/modules/mu/mommacat/search.rb +495 -0
  107. data/modules/mu/mommacat/storage.rb +722 -0
  108. data/modules/mu/{clouds → providers}/README.md +1 -1
  109. data/modules/mu/{clouds → providers}/aws.rb +271 -112
  110. data/modules/mu/{clouds → providers}/aws/alarm.rb +5 -3
  111. data/modules/mu/{clouds → providers}/aws/bucket.rb +26 -22
  112. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +33 -67
  113. data/modules/mu/{clouds → providers}/aws/collection.rb +24 -23
  114. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +681 -721
  115. data/modules/mu/providers/aws/database.rb +1744 -0
  116. data/modules/mu/{clouds → providers}/aws/dnszone.rb +64 -63
  117. data/modules/mu/{clouds → providers}/aws/endpoint.rb +22 -27
  118. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +214 -244
  119. data/modules/mu/{clouds → providers}/aws/folder.rb +7 -7
  120. data/modules/mu/{clouds → providers}/aws/function.rb +17 -22
  121. data/modules/mu/{clouds → providers}/aws/group.rb +23 -23
  122. data/modules/mu/{clouds → providers}/aws/habitat.rb +17 -14
  123. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +57 -48
  124. data/modules/mu/{clouds → providers}/aws/log.rb +15 -12
  125. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +17 -16
  126. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +18 -11
  127. data/modules/mu/{clouds → providers}/aws/notifier.rb +11 -6
  128. data/modules/mu/{clouds → providers}/aws/role.rb +112 -86
  129. data/modules/mu/{clouds → providers}/aws/search_domain.rb +39 -33
  130. data/modules/mu/{clouds → providers}/aws/server.rb +835 -1133
  131. data/modules/mu/{clouds → providers}/aws/server_pool.rb +56 -60
  132. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +24 -42
  133. data/modules/mu/{clouds → providers}/aws/user.rb +21 -22
  134. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  135. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +0 -0
  136. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  137. data/modules/mu/{clouds → providers}/aws/vpc.rb +523 -929
  138. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  139. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  140. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  141. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  142. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  143. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  144. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  145. data/modules/mu/{clouds → providers}/azure/server.rb +95 -48
  146. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  147. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  148. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  149. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  150. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  151. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  152. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  153. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  156. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  160. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  161. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  162. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  163. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  164. data/modules/mu/{clouds → providers}/google.rb +67 -30
  165. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  166. data/modules/mu/{clouds → providers}/google/container_cluster.rb +84 -77
  167. data/modules/mu/{clouds → providers}/google/database.rb +10 -20
  168. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  169. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  170. data/modules/mu/{clouds → providers}/google/function.rb +139 -167
  171. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  172. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  173. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +18 -20
  174. data/modules/mu/{clouds → providers}/google/role.rb +92 -58
  175. data/modules/mu/{clouds → providers}/google/server.rb +242 -155
  176. data/modules/mu/{clouds → providers}/google/server_pool.rb +25 -44
  177. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  178. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  179. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  180. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  181. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  182. data/modules/tests/bucket.yml +4 -0
  183. data/modules/tests/centos6.yaml +11 -0
  184. data/modules/tests/centos7.yaml +11 -0
  185. data/modules/tests/centos8.yaml +12 -0
  186. data/modules/tests/ecs.yaml +23 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/rds.yaml +108 -0
  189. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  190. data/modules/tests/regrooms/bucket.yml +19 -0
  191. data/modules/tests/regrooms/rds.yaml +123 -0
  192. data/modules/tests/server-with-scrub-muisms.yaml +1 -0
  193. data/modules/tests/super_simple_bok.yml +1 -3
  194. data/modules/tests/win2k12.yaml +17 -5
  195. data/modules/tests/win2k16.yaml +25 -0
  196. data/modules/tests/win2k19.yaml +25 -0
  197. data/requirements.txt +1 -0
  198. data/spec/mu/clouds/azure_spec.rb +2 -2
  199. metadata +232 -154
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1985
@@ -64,10 +64,9 @@ module MU
64
64
  size = @config['basis']['launch_config']['size']
65
65
  @config['image_id'] = @config['basis']['launch_config']['image_id']
66
66
  end
67
- az = @config['availability_zone']
68
- if az.nil?
69
- az = MU::Cloud::Google.listAZs(@config['region']).sample
70
- end
67
+ # XXX this should create a non-regional instance group
68
+ # az = @config['availability_zone']
69
+ # az ||= MU::Cloud::Google.listAZs(@config['region']).sample
71
70
 
72
71
  metadata = { # :items?
73
72
  "startup-script" => @userdata
@@ -90,8 +89,8 @@ module MU
90
89
  machine_type: size,
91
90
  service_accounts: [@service_acct],
92
91
  labels: labels,
93
- disks: MU::Cloud::Google::Server.diskConfig(@config, false, false, credentials: @config['credentials']),
94
- network_interfaces: MU::Cloud::Google::Server.interfaceConfig(@config, @vpc),
92
+ disks: MU::Cloud.resourceClass("Google", "Server").diskConfig(@config, false, false, credentials: @config['credentials']),
93
+ network_interfaces: MU::Cloud.resourceClass("Google", "Server").interfaceConfig(@config, @vpc),
95
94
  metadata: metadata,
96
95
  tags: MU::Cloud::Google.compute(:Tags).new(items: [MU::Cloud::Google.nameStr(@mu_name)])
97
96
  )
@@ -170,8 +169,7 @@ module MU
170
169
  # Locate an existing ServerPool or ServerPools and return an array containing matching Google resource descriptors for those that match.
171
170
  # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching ServerPools
172
171
  def self.find(**args)
173
- args[:project] ||= args[:habitat]
174
- args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
172
+ args = MU::Cloud::Google.findLocationArgs(args)
175
173
 
176
174
  regions = if args[:region]
177
175
  [args[:region]]
@@ -213,7 +211,7 @@ module MU
213
211
  # Reverse-map our cloud description into a runnable config hash.
214
212
  # We assume that any values we have in +@config+ are placeholders, and
215
213
  # calculate our own accordingly based on what's live in the cloud.
216
- def toKitten(rootparent: nil, billing: nil, habitats: nil)
214
+ def toKitten(**_args)
217
215
  bok = {
218
216
  "cloud" => "Google",
219
217
  "credentials" => @credentials,
@@ -326,11 +324,11 @@ end
326
324
  def self.schema(config)
327
325
  toplevel_required = []
328
326
  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"],
327
+ "ssh_user" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["ssh_user"],
328
+ "metadata" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["metadata"],
329
+ "service_account" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["service_account"],
330
+ "scopes" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["scopes"],
331
+ "network_tags" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["network_tags"],
334
332
  "availability_zone" => {
335
333
  "type" => "string",
336
334
  "description" => "Target a specific availability zone for this pool, which will create zonal instance managers and scalers instead of regional ones."
@@ -362,7 +360,7 @@ end
362
360
  # @return [Boolean]: True if validation succeeded, False otherwise
363
361
  def self.validateConfig(pool, configurator)
364
362
  ok = true
365
- start = Time.now
363
+ #start = Time.now
366
364
  pool['project'] ||= MU::Cloud::Google.defaultProject(pool['credentials'])
367
365
  if pool['service_account']
368
366
  pool['service_account']['cloud'] = "Google"
@@ -373,29 +371,7 @@ start = Time.now
373
371
  ok = false
374
372
  end
375
373
  else
376
- user = {
377
- "name" => pool['name'],
378
- "cloud" => "Google",
379
- "project" => pool["project"],
380
- "credentials" => pool["credentials"],
381
- "type" => "service"
382
- }
383
- if user["name"].length < 6
384
- user["name"] += Password.pronounceable(6)
385
- end
386
- configurator.insertKitten(user, "users", true)
387
- pool['dependencies'] ||= []
388
- pool['service_account'] = MU::Config::Ref.get(
389
- type: "users",
390
- cloud: "Google",
391
- name: pool["name"],
392
- project: pool["project"],
393
- credentials: pool["credentials"]
394
- )
395
- pool['dependencies'] << {
396
- "type" => "user",
397
- "name" => pool["name"]
398
- }
374
+ pool = MU::Cloud::Google::User.genericServiceAccount(pool, configurator)
399
375
  end
400
376
 
401
377
  pool['named_ports'] ||= []
@@ -406,7 +382,7 @@ start = Time.now
406
382
  if pool['basis']['launch_config']
407
383
  launch = pool["basis"]["launch_config"]
408
384
 
409
- launch['size'] = MU::Cloud::Google::Server.validateInstanceType(launch["size"], pool["region"])
385
+ launch['size'] = MU::Cloud.resourceClass("Google", "Server").validateInstanceType(launch["size"], pool["region"])
410
386
  ok = false if launch['size'].nil?
411
387
 
412
388
  if launch['image_id'].nil?
@@ -421,7 +397,7 @@ start = Time.now
421
397
 
422
398
  real_image = nil
423
399
  begin
424
- real_image = MU::Cloud::Google::Server.fetchImage(launch['image_id'].to_s, credentials: pool['credentials'])
400
+ real_image = MU::Cloud.resourceClass("Google", "Server").fetchImage(launch['image_id'].to_s, credentials: pool['credentials'])
425
401
  rescue ::Google::Apis::ClientError => e
426
402
  MU.log e.inspect, MU::WARN
427
403
  end
@@ -456,14 +432,19 @@ start = Time.now
456
432
  # @param region [String]: The cloud provider region
457
433
  # @return [void]
458
434
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
459
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
460
- return if !MU::Cloud::Google::Habitat.isLive?(flags["project"], credentials)
435
+ flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials)
436
+ return if !MU::Cloud.resourceClass("Google", "Habitat").isLive?(flags["habitat"], credentials)
437
+ filter = %Q{(labels.mu-id = "#{MU.deploy_id.downcase}")}
438
+ if !ignoremaster and MU.mu_public_ip
439
+ filter += %Q{ AND (labels.mu-master-ip = "#{MU.mu_public_ip.gsub(/\./, "_")}")}
440
+ end
441
+ MU.log "Placeholder: Google ServerPool artifacts do not support labels, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: filter
461
442
 
462
443
  if !flags["global"]
463
444
  ["region_autoscaler", "region_instance_group_manager"].each { |type|
464
445
  MU::Cloud::Google.compute(credentials: credentials).delete(
465
446
  type,
466
- flags["project"],
447
+ flags["habitat"],
467
448
  region,
468
449
  noop
469
450
  )
@@ -471,7 +452,7 @@ start = Time.now
471
452
  else
472
453
  MU::Cloud::Google.compute(credentials: credentials).delete(
473
454
  "instance_template",
474
- flags["project"],
455
+ flags["habitat"],
475
456
  noop
476
457
  )
477
458
  end
@@ -26,10 +26,12 @@ module MU
26
26
  # If we're being reverse-engineered from a cloud descriptor, use that
27
27
  # to determine what sort of account we are.
28
28
  if args[:from_cloud_desc]
29
+ @cloud_desc_cache = args[:from_cloud_desc]
29
30
  MU::Cloud::Google.admin_directory
30
31
  MU::Cloud::Google.iam
31
32
  if args[:from_cloud_desc].class == ::Google::Apis::AdminDirectoryV1::User
32
33
  @config['type'] = "interactive"
34
+ @cloud_id = args[:from_cloud_desc].primary_email
33
35
  elsif args[:from_cloud_desc].class == ::Google::Apis::IamV1::ServiceAccount
34
36
  @config['type'] = "service"
35
37
  @config['name'] = args[:from_cloud_desc].display_name
@@ -48,6 +50,10 @@ module MU
48
50
  @config['name']
49
51
  end
50
52
 
53
+ if @config['type'] == "interactive" and @config['email']
54
+ @cloud_id ||= @config['email']
55
+ end
56
+
51
57
  end
52
58
 
53
59
  # Called automatically by {MU::Deploy#createResources}
@@ -58,7 +64,7 @@ module MU
58
64
  account_id: acct_id,
59
65
  service_account: MU::Cloud::Google.iam(:ServiceAccount).new(
60
66
  display_name: @mu_name,
61
- description: @config['scrub_mu_isms'] ? nil : @deploy.deploy_id
67
+ description: @config['scrub_mu_isms'] ? @config['description'] : @deploy.deploy_id
62
68
  )
63
69
  )
64
70
  if @config['use_if_exists']
@@ -90,7 +96,7 @@ module MU
90
96
  end
91
97
  elsif @config['external']
92
98
  @cloud_id = @config['email']
93
- MU::Cloud::Google::Role.bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
99
+ MU::Cloud.resourceClass("Google", "Role").bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
94
100
  else
95
101
  if !@config['email']
96
102
  domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer)
@@ -122,10 +128,10 @@ module MU
122
128
  # Called automatically by {MU::Deploy#createResources}
123
129
  def groom
124
130
  if @config['external']
125
- MU::Cloud::Google::Role.bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
131
+ MU::Cloud.resourceClass("Google", "Role").bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
126
132
  elsif @config['type'] == "interactive"
127
133
  need_update = false
128
- MU::Cloud::Google::Role.bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
134
+ MU::Cloud.resourceClass("Google", "Role").bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials'])
129
135
 
130
136
  if @config['force_password_change'] and !cloud_desc.change_password_at_next_login
131
137
  MU.log "Forcing #{@mu_name} to change their password at next login", MU::NOTICE
@@ -170,7 +176,7 @@ module MU
170
176
  end
171
177
 
172
178
  else
173
- MU::Cloud::Google::Role.bindFromConfig("serviceAccount", @cloud_id.gsub(/.*?\/([^\/]+)$/, '\1'), @config['roles'], credentials: @config['credentials'])
179
+ MU::Cloud.resourceClass("Google", "Role").bindFromConfig("serviceAccount", @cloud_id.gsub(/.*?\/([^\/]+)$/, '\1'), @config['roles'], credentials: @config['credentials'])
174
180
  if @config['create_api_key']
175
181
  resp = MU::Cloud::Google.iam(credentials: @config['credentials']).list_project_service_account_keys(
176
182
  cloud_desc.name
@@ -187,21 +193,36 @@ module MU
187
193
  end
188
194
  end
189
195
 
196
+ @cloud_desc_cache = nil
190
197
  # Retrieve the cloud descriptor for this resource.
191
198
  # @return [Google::Apis::Core::Hashable]
192
- def cloud_desc
199
+ def cloud_desc(use_cache: true)
200
+ return @cloud_desc_cache if @cloud_desc_cache and use_cache
193
201
  if @config['type'] == "interactive" or !@config['type']
194
202
  @config['type'] ||= "interactive"
195
203
  if !@config['external']
196
- return MU::Cloud::Google.admin_directory(credentials: @config['credentials']).get_user(@cloud_id)
204
+ @cloud_id ||= @config['email']
205
+ @cloud_desc_cache = MU::Cloud::Google.admin_directory(credentials: @config['credentials']).get_user(@cloud_id)
197
206
  else
198
207
  return nil
199
208
  end
200
209
  else
201
210
  @config['type'] ||= "service"
202
- MU::Cloud::Google.iam(credentials: @config['credentials']).get_project_service_account(@cloud_id)
211
+ # this often fails even when it succeeded earlier, so try to be
212
+ # resilient on GCP's behalf
213
+ retries = 0
214
+ begin
215
+ @cloud_desc_cache = MU::Cloud::Google.iam(credentials: @config['credentials']).get_project_service_account(@cloud_id)
216
+ rescue ::Google::Apis::ClientError => e
217
+ if e.message.match(/notFound:/) and retries < 10
218
+ sleep 3
219
+ retries += 1
220
+ retry
221
+ end
222
+ end
203
223
  end
204
224
 
225
+ @cloud_desc_cache
205
226
  end
206
227
 
207
228
  # Return the metadata for this user configuration
@@ -212,7 +233,7 @@ module MU
212
233
  else
213
234
  {}
214
235
  end
215
- description.delete(:etag)
236
+ description.delete(:etag) if description
216
237
  description
217
238
  end
218
239
 
@@ -232,12 +253,17 @@ module MU
232
253
  # Remove all users associated with the currently loaded deployment.
233
254
  # @param noop [Boolean]: If true, will only print what would be done
234
255
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
235
- # @param region [String]: The cloud provider region
236
256
  # @return [void]
237
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
238
- my_domains = MU::Cloud::Google.getDomains(credentials)
257
+ def self.cleanup(noop: false, ignoremaster: false, credentials: nil, flags: {})
258
+ MU::Cloud::Google.getDomains(credentials)
239
259
  my_org = MU::Cloud::Google.getOrg(credentials)
240
260
 
261
+ filter = %Q{(labels.mu-id = "#{MU.deploy_id.downcase}")}
262
+ if !ignoremaster and MU.mu_public_ip
263
+ filter += %Q{ AND (labels.mu-master-ip = "#{MU.mu_public_ip.gsub(/\./, "_")}")}
264
+ end
265
+ MU.log "Placeholder: Google User artifacts do not support labels, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: filter
266
+
241
267
  # We don't have a good way of tagging directory users, so we rely
242
268
  # on the known parameter, which is pulled from deployment metadata
243
269
  if flags['known'] and my_org
@@ -256,15 +282,15 @@ module MU
256
282
  next if user_email.nil?
257
283
  next if !user_email.match(/^[^\/]+@[^\/]+$/)
258
284
 
259
- MU::Cloud::Google::Role.removeBindings("user", user_email, credentials: credentials, noop: noop)
285
+ MU::Cloud.resourceClass("Google", "Role").removeBindings("user", user_email, credentials: credentials, noop: noop)
260
286
  }
261
287
 
262
288
  end
263
289
  end
264
290
 
265
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
291
+ flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials)
266
292
  resp = MU::Cloud::Google.iam(credentials: credentials).list_project_service_accounts(
267
- "projects/"+flags["project"]
293
+ "projects/"+flags["habitat"]
268
294
  )
269
295
 
270
296
  if resp and resp.accounts and MU.deploy_id
@@ -319,7 +345,7 @@ module MU
319
345
  MU::Cloud::Google.iam(credentials: args[:credentials]).list_project_service_accounts(
320
346
  "projects/"+args[:project]
321
347
  )
322
- rescue ::Google::Apis::ClientError => e
348
+ rescue ::Google::Apis::ClientError
323
349
  MU.log "Do not have permissions to retrieve service accounts for project #{args[:project]}", MU::WARN
324
350
  end
325
351
 
@@ -337,8 +363,10 @@ module MU
337
363
  else
338
364
  if cred_cfg['masquerade_as']
339
365
  resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).list_users(customer: MU::Cloud::Google.customerID(args[:credentials]), show_deleted: false)
366
+ # XXX this ain't exactly performant, do some caching or something
340
367
  if resp and resp.users
341
368
  resp.users.each { |u|
369
+ next if args[:cloud_id] and !args[:cloud_id] != u.primary_email
342
370
  found[u.primary_email] = u
343
371
  }
344
372
  end
@@ -355,6 +383,7 @@ module MU
355
383
  name.match(/\b\d+\-compute@developer\.gserviceaccount\.com$/) or
356
384
  name.match(/\bproject-\d+@storage-transfer-service\.iam\.gserviceaccount\.com$/) or
357
385
  name.match(/\b\d+@cloudbuild\.gserviceaccount\.com$/) or
386
+ name.match(/\b\d+@cloudservices\.gserviceaccount\.com$/) or
358
387
  name.match(/\bservice-\d+@containerregistry\.iam\.gserviceaccount\.com$/) or
359
388
  name.match(/\bservice-\d+@container-analysis\.iam\.gserviceaccount\.com$/) or
360
389
  name.match(/\bservice-\d+@compute-system\.iam\.gserviceaccount\.com$/) or
@@ -382,7 +411,7 @@ module MU
382
411
  # Reverse-map our cloud description into a runnable config hash.
383
412
  # We assume that any values we have in +@config+ are placeholders, and
384
413
  # calculate our own accordingly based on what's live in the cloud.
385
- def toKitten(rootparent: nil, billing: nil, habitats: nil)
414
+ def toKitten(**_args)
386
415
  if MU::Cloud::Google::User.cannedServiceAcctName?(@cloud_id)
387
416
  return nil
388
417
  end
@@ -397,7 +426,7 @@ module MU
397
426
  return nil
398
427
  end
399
428
 
400
- user_roles = MU::Cloud::Google::Role.getAllBindings(@config['credentials'])["by_entity"]
429
+ user_roles = MU::Cloud.resourceClass("Google", "Role").getAllBindings(@config['credentials'])["by_entity"]
401
430
 
402
431
  if cloud_desc.nil?
403
432
  MU.log "FAILED TO FIND CLOUD DESCRIPTOR FOR #{self}", MU::ERR, details: @config
@@ -410,6 +439,10 @@ module MU
410
439
 
411
440
  if bok['type'] == "service"
412
441
  bok['name'].gsub!(/@.*/, '')
442
+ if cloud_desc.description and !cloud_desc.description.empty? and
443
+ !cloud_desc.description.match(/^[A-Z0-9_-]+-[A-Z0-9_-]+-\d{10}-[A-Z]{2}$/)
444
+ bok['description'] = cloud_desc.description
445
+ end
413
446
  bok['project'] = @project_id
414
447
  keys = MU::Cloud::Google.iam(credentials: @config['credentials']).list_project_service_account_keys(@cloud_id)
415
448
 
@@ -420,13 +453,13 @@ module MU
420
453
  if user_roles["serviceAccount"] and
421
454
  user_roles["serviceAccount"][bok['cloud_id']] and
422
455
  user_roles["serviceAccount"][bok['cloud_id']].size > 0
423
- bok['roles'] = MU::Cloud::Google::Role.entityBindingsToSchema(user_roles["serviceAccount"][bok['cloud_id']])
456
+ bok['roles'] = MU::Cloud.resourceClass("Google", "Role").entityBindingsToSchema(user_roles["serviceAccount"][bok['cloud_id']])
424
457
  end
425
458
  else
426
459
  if user_roles["user"] and
427
460
  user_roles["user"][bok['cloud_id']] and
428
461
  user_roles["user"][bok['cloud_id']].size > 0
429
- bok['roles'] = MU::Cloud::Google::Role.entityBindingsToSchema(user_roles["user"][bok['cloud_id']], credentials: @config['credentials'])
462
+ bok['roles'] = MU::Cloud.resourceClass("Google", "Role").entityBindingsToSchema(user_roles["user"][bok['cloud_id']], credentials: @config['credentials'])
430
463
  end
431
464
  bok['given_name'] = cloud_desc.name.given_name if cloud_desc.name.given_name and !cloud_desc.name.given_name.empty?
432
465
  bok['family_name'] = cloud_desc.name.family_name if cloud_desc.name.family_name and !cloud_desc.name.family_name.empty?
@@ -441,9 +474,9 @@ module MU
441
474
  end
442
475
 
443
476
  # Cloud-specific configuration properties.
444
- # @param config [MU::Config]: The calling MU::Config object
477
+ # @param _config [MU::Config]: The calling MU::Config object
445
478
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
446
- def self.schema(config)
479
+ def self.schema(_config)
447
480
  toplevel_required = []
448
481
  schema = {
449
482
  "name" => {
@@ -482,6 +515,10 @@ If we are binding (rather than creating) a user and no roles are specified, we w
482
515
  "type" => "string",
483
516
  "description" => "Alias for +family_name+"
484
517
  },
518
+ "description" => {
519
+ "type" => "string",
520
+ "description" => "Comment field for service accounts, which we normally use to store the originating deploy's deploy id, since GCP service accounts do not have labels. This field is only honored if +scrub_mu_isms+ is set."
521
+ },
485
522
  "email" => {
486
523
  "type" => "string",
487
524
  "description" => "Canonical email address for a +directory+ user. If not specified, will be set to +name@domain+."
@@ -509,7 +546,7 @@ If we are binding (rather than creating) a user and no roles are specified, we w
509
546
  "roles" => {
510
547
  "type" => "array",
511
548
  "description" => "One or more Google IAM roles to associate with this user.",
512
- "items" => MU::Cloud::Google::Role.ref_schema
549
+ "items" => MU::Cloud.resourceClass("Google", "Role").ref_schema
513
550
  }
514
551
  }
515
552
  [toplevel_required, schema]
@@ -517,9 +554,9 @@ If we are binding (rather than creating) a user and no roles are specified, we w
517
554
 
518
555
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::users}, bare and unvalidated.
519
556
  # @param user [Hash]: The resource to process and validate
520
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
557
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
521
558
  # @return [Boolean]: True if validation succeeded, False otherwise
522
- def self.validateConfig(user, configurator)
559
+ def self.validateConfig(user, _configurator)
523
560
  ok = true
524
561
 
525
562
  my_domains = MU::Cloud::Google.getDomains(user['credentials'])
@@ -595,15 +632,11 @@ If we are binding (rather than creating) a user and no roles are specified, we w
595
632
  ok = false
596
633
  end
597
634
 
598
- user['dependencies'] ||= []
599
635
  if user['roles']
600
636
  user['roles'].each { |r|
601
637
  if r['role'] and r['role']['name'] and
602
638
  (!r['role']['deploy_id'] and !r['role']['id'])
603
- user['dependencies'] << {
604
- "type" => "role",
605
- "name" => r['role']['name']
606
- }
639
+ MU::Config.addDependency(user, r['role']['name'], "role")
607
640
  end
608
641
 
609
642
  if !r["projects"] and !r["organizations"] and !r["folders"]
@@ -621,7 +654,38 @@ If we are binding (rather than creating) a user and no roles are specified, we w
621
654
  ok
622
655
  end
623
656
 
624
- private
657
+ # Create and inject a service account on behalf of the parent resource.
658
+ # Return the modified parent configuration hash with references to the
659
+ # new addition.
660
+ # @param parent [Hash]
661
+ # @param configurator [MU::Config]
662
+ # @return [Hash]
663
+ def self.genericServiceAccount(parent, configurator)
664
+ user = {
665
+ "name" => parent['name'],
666
+ "cloud" => "Google",
667
+ "project" => parent["project"],
668
+ "credentials" => parent["credentials"],
669
+ "type" => "service"
670
+ }
671
+ if user["name"].length < 6
672
+ user["name"] += Password.pronounceable(6)
673
+ end
674
+ if parent['roles']
675
+ user['roles'] = parent['roles'].dup
676
+ end
677
+ configurator.insertKitten(user, "users", true)
678
+ parent['service_account'] = MU::Config::Ref.get(
679
+ type: "users",
680
+ cloud: "Google",
681
+ name: user["name"],
682
+ project: user["project"],
683
+ credentials: user["credentials"]
684
+ )
685
+ MU::Config.addDependency(parent, user['name'], "user")
686
+
687
+ parent
688
+ end
625
689
 
626
690
  end
627
691
  end