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
@@ -129,13 +129,13 @@ module MU
129
129
 
130
130
  if launch_desc["storage"]
131
131
  launch_desc["storage"].each { |vol|
132
- mapping, cfm_mapping = MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)
132
+ mapping, cfm_mapping = MU::Cloud.resourceClass("AWS", "Server").convertBlockDeviceMapping(vol)
133
133
  if cfm_mapping.size > 0
134
134
  MU::Cloud::CloudFormation.setCloudFormationProp(@cfm_template[@cfm_launch_name], "BlockDeviceMappings", cfm_mapping)
135
135
  end
136
136
  }
137
137
  end
138
- MU::Cloud::AWS::Server.ephemeral_mappings.each { |mapping|
138
+ MU::Cloud.resourceClass("AWS", "Server.ephemeral_mappings").each { |mapping|
139
139
  MU::Cloud::CloudFormation.setCloudFormationProp(@cfm_template[@cfm_launch_name], "BlockDeviceMappings", { "DeviceName" => mapping[:device_name], "VirtualName" => mapping[:virtual_name] })
140
140
  }
141
141
 
@@ -263,7 +263,7 @@ module MU
263
263
  # @param config [MU::Config]: The calling MU::Config object
264
264
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
265
265
  def self.schema(config)
266
- MU::Cloud::AWS::ServerPool.schema(config)
266
+ MU::Cloud.resourceClass("AWS", "ServerPool").schema(config)
267
267
  end
268
268
 
269
269
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
@@ -271,14 +271,14 @@ module MU
271
271
  # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
272
272
  # @return [Boolean]: True if validation succeeded, False otherwise
273
273
  def self.validateConfig(server, configurator)
274
- MU::Cloud::AWS::ServerPool.validateConfig(server, configurator)
274
+ MU::Cloud.resourceClass("AWS", "ServerPool").validateConfig(server, configurator)
275
275
  end
276
276
 
277
277
  # Does this resource type exist as a global (cloud-wide) artifact, or
278
278
  # is it localized to a region/zone?
279
279
  # @return [Boolean]
280
280
  def self.isGlobal?
281
- MU::Cloud::AWS::ServerPool.isGlobal?
281
+ MU::Cloud.resourceClass("AWS", "ServerPool").isGlobal?
282
282
  end
283
283
 
284
284
  end
@@ -240,8 +240,6 @@ module MU
240
240
  {}
241
241
  end
242
242
 
243
- protected
244
-
245
243
  # Subnets are almost a first-class resource. So let's kinda sorta treat
246
244
  # them like one. This should only be invoked on objects that already
247
245
  # exists in the cloud layer.
@@ -288,13 +286,13 @@ module MU
288
286
 
289
287
  # Placeholder. This is a NOOP for CloudFormation, which doesn't build
290
288
  # resources directly.
291
- def self.find(*args)
289
+ def self.find(**args)
292
290
  MU.log "find() not implemented for CloudFormation layer", MU::DEBUG
293
291
  nil
294
292
  end
295
293
  # Placeholder. This is a NOOP for CloudFormation, which doesn't build
296
294
  # resources directly.
297
- def self.cleanup(*args)
295
+ def self.cleanup(**args)
298
296
  MU.log "cleanup() not implemented for CloudFormation layer", MU::DEBUG
299
297
  nil
300
298
  end
@@ -303,7 +301,7 @@ module MU
303
301
  # @param config [MU::Config]: The calling MU::Config object
304
302
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
305
303
  def self.schema(config)
306
- MU::Cloud::AWS::VPC.schema(config)
304
+ MU::Cloud.resourceClass("AWS", "VPC").schema(config)
307
305
  end
308
306
 
309
307
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
@@ -311,14 +309,14 @@ module MU
311
309
  # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
312
310
  # @return [Boolean]: True if validation succeeded, False otherwise
313
311
  def self.validateConfig(server, configurator)
314
- MU::Cloud::AWS::VPC.validateConfig(server, configurator)
312
+ MU::Cloud.resourceClass("AWS", "VPC").validateConfig(server, configurator)
315
313
  end
316
314
 
317
315
  # Does this resource type exist as a global (cloud-wide) artifact, or
318
316
  # is it localized to a region/zone?
319
317
  # @return [Boolean]
320
318
  def self.isGlobal?
321
- MU::Cloud::AWS::VPC.isGlobal?
319
+ MU::Cloud.resourceClass("AWS", "VPC").isGlobal?
322
320
  end
323
321
 
324
322
  end #class
@@ -52,6 +52,22 @@ module MU
52
52
  [:url]
53
53
  end
54
54
 
55
+ # Is this a "real" cloud provider, or a stub like CloudFormation?
56
+ def self.virtual?
57
+ false
58
+ end
59
+
60
+ # Most of our resource implementation +find+ methods have to mangle their
61
+ # args to make sure they've extracted a project or location argument from
62
+ # other available information. This does it for them.
63
+ # @return [Hash]
64
+ def self.findLocationArgs(**args)
65
+ args[:project] ||= args[:habitat]
66
+ args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
67
+ args[:location] ||= args[:region] || args[:availability_zone] || "-"
68
+ args
69
+ end
70
+
55
71
  # A hook that is always called just before any of the instance method of
56
72
  # our resource implementations gets invoked, so that we can ensure that
57
73
  # repetitive setup tasks (like resolving +:resource_group+ for Azure
@@ -141,10 +157,9 @@ module MU
141
157
  def self.habitat(cloudobj, nolookup: false, deploy: nil)
142
158
  @@habmap ||= {}
143
159
  # XXX whaddabout config['habitat'] HNNNGH
144
-
145
160
  return nil if !cloudobj.cloudclass.canLiveIn.include?(:Habitat)
146
161
 
147
- # XXX users are assholes because they're valid two different ways ugh ugh
162
+ # XXX these are assholes because they're valid two different ways ugh ugh
148
163
  return nil if [MU::Cloud::Google::Group, MU::Cloud::Google::Folder].include?(cloudobj.cloudclass)
149
164
  if cloudobj.config and cloudobj.config['project']
150
165
  if nolookup
@@ -154,7 +169,6 @@ module MU
154
169
  return @@habmap[cloudobj.config['project']]
155
170
  end
156
171
  deploy ||= cloudobj.deploy if cloudobj.respond_to?(:deploy)
157
-
158
172
  projectobj = projectLookup(cloudobj.config['project'], deploy, raise_on_fail: false)
159
173
 
160
174
  if projectobj
@@ -222,7 +236,7 @@ module MU
222
236
  # @param sibling_only [Boolean]
223
237
  # @return [MU::Config::Habitat,nil]
224
238
  def self.projectLookup(name, deploy = MU.mommacat, raise_on_fail: true, sibling_only: false)
225
- project_obj = deploy.findLitterMate(type: "habitats", name: name) if deploy
239
+ project_obj = deploy.findLitterMate(type: "habitats", name: name) if deploy if !caller.grep(/`findLitterMate'/) # XXX the dumbest
226
240
 
227
241
  if !project_obj and !sibling_only
228
242
  resp = MU::MommaCat.findStray(
@@ -328,6 +342,7 @@ module MU
328
342
  # etc)
329
343
  # @param deploy_id [MU::MommaCat]
330
344
  def self.cleanDeploy(deploy_id, credentials: nil, noop: false)
345
+ removeDeploySecretsAndRoles(deploy_id, noop: noop, credentials: credentials)
331
346
  end
332
347
 
333
348
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
@@ -363,7 +378,7 @@ module MU
363
378
  cfg = credConfig(credentials)
364
379
  return if !cfg or !cfg['project']
365
380
  flags["project"] ||= cfg['project']
366
- name = deploy_id+"-secret"
381
+
367
382
  resp = MU::Cloud::Google.storage(credentials: credentials).list_objects(
368
383
  adminBucketName(credentials),
369
384
  prefix: deploy_id
@@ -420,7 +435,7 @@ module MU
420
435
  MU.log e.message, MU::WARN, details: e.inspect
421
436
  if e.inspect.match(/body: "Not Found"/)
422
437
  raise MuError, "Google admin bucket #{adminBucketName(credentials)} or key #{name} does not appear to exist or is not visible with #{credentials ? credentials : "default"} credentials"
423
- elsif e.message.match(/notFound: |Unknown user:/)
438
+ elsif e.message.match(/notFound: |Unknown user:|conflict: /)
424
439
  if retries < 5
425
440
  sleep 5
426
441
  retries += 1
@@ -429,7 +444,7 @@ MU.log e.message, MU::WARN, details: e.inspect
429
444
  raise e
430
445
  end
431
446
  elsif e.inspect.match(/The metadata for object "null" was edited during the operation/)
432
- MU.log e.message+" - Google admin bucket #{adminBucketName(credentials)}/#{name} with #{credentials ? credentials : "default"} credentials", MU::WARN, details: aclobj
447
+ MU.log e.message+" - Google admin bucket #{adminBucketName(credentials)}/#{name} with #{credentials ? credentials : "default"} credentials", MU::DEBUG, details: aclobj
433
448
  sleep 10
434
449
  retry
435
450
  else
@@ -539,8 +554,8 @@ MU.log e.message, MU::WARN, details: e.inspect
539
554
  begin
540
555
  listRegions(credentials: credentials)
541
556
  listInstanceTypes(credentials: credentials)
542
- listProjects(credentials)
543
- rescue ::Google::Apis::ClientError => e
557
+ listHabitats(credentials)
558
+ rescue ::Google::Apis::ClientError
544
559
  MU.log "Found machine credentials #{@@svc_account_name}, but these don't appear to have sufficient permissions or scopes", MU::WARN, details: scopes
545
560
  @@authorizers.delete(credentials)
546
561
  return nil
@@ -691,13 +706,26 @@ MU.log e.message, MU::WARN, details: e.inspect
691
706
  nil
692
707
  end
693
708
 
709
+ @allprojects = []
710
+
694
711
  # List all Google Cloud Platform projects available to our credentials
695
- def self.listProjects(credentials = nil)
712
+ def self.listHabitats(credentials = nil, use_cache: true)
696
713
  cfg = credConfig(credentials)
697
- return [] if !cfg or !cfg['project']
714
+ return [] if !cfg
715
+ if cfg['restrict_to_habitats'] and cfg['restrict_to_habitats'].is_a?(Array)
716
+ cfg['restrict_to_habitats'] << cfg['project'] if cfg['project']
717
+ return cfg['restrict_to_habitats'].uniq
718
+ end
719
+ if @allprojects and !@allprojects.empty? and use_cache
720
+ return @allprojects
721
+ end
698
722
  result = MU::Cloud::Google.resource_manager(credentials: credentials).list_projects
699
723
  result.projects.reject! { |p| p.lifecycle_state == "DELETE_REQUESTED" }
700
- result.projects.map { |p| p.project_id }
724
+ @allprojects = result.projects.map { |p| p.project_id }
725
+ if cfg['ignore_habitats'] and cfg['ignore_habitats'].is_a?(Array)
726
+ @allprojects.reject! { |p| cfg['ignore_habitats'].include?(p) }
727
+ end
728
+ @allprojects
701
729
  end
702
730
 
703
731
  @@regions = {}
@@ -717,7 +745,6 @@ MU.log e.message, MU::WARN, details: e.inspect
717
745
  raise e
718
746
  end
719
747
 
720
- regions = []
721
748
  result.items.each { |region|
722
749
  @@regions[region.name] = []
723
750
  region.zones.each { |az|
@@ -846,7 +873,7 @@ MU.log e.message, MU::WARN, details: e.inspect
846
873
  if subclass.nil?
847
874
  begin
848
875
  @@admin_directory_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "AdminDirectoryV1::DirectoryService", scopes: use_scopes, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'], credentials: credentials, auth_error_quiet: true)
849
- rescue Signet::AuthorizationError => e
876
+ rescue Signet::AuthorizationError
850
877
  MU.log "Falling back to read-only access to DirectoryService API for credential set '#{credentials}'", MU::WARN
851
878
  @@admin_directory_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "AdminDirectoryV1::DirectoryService", scopes: readscopes, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'], credentials: credentials)
852
879
  @@readonly[credentials] ||= {}
@@ -1003,8 +1030,10 @@ MU.log e.message, MU::WARN, details: e.inspect
1003
1030
  "default"
1004
1031
  end
1005
1032
 
1033
+ with_id ||= creds['org'] if creds['org']
1006
1034
  return @@orgmap[credname] if @@orgmap.has_key?(credname)
1007
1035
  resp = MU::Cloud::Google.resource_manager(credentials: credname).search_organizations
1036
+
1008
1037
  if resp and resp.organizations
1009
1038
  # XXX no idea if it's possible to be a member of multiple orgs
1010
1039
  if !with_id
@@ -1012,7 +1041,8 @@ MU.log e.message, MU::WARN, details: e.inspect
1012
1041
  return resp.organizations.first
1013
1042
  else
1014
1043
  resp.organizations.each { |org|
1015
- if org.name == with_id
1044
+ if org.name == with_id or org.display_name == with_id or
1045
+ org.name == "organizations/#{with_id}"
1016
1046
  @@orgmap[credname] = org
1017
1047
  return org
1018
1048
  end
@@ -1052,8 +1082,6 @@ MU.log e.message, MU::WARN, details: e.inspect
1052
1082
  @@customer_ids_cache[credentials]
1053
1083
  end
1054
1084
 
1055
- private
1056
-
1057
1085
  # Wrapper class for Google APIs, so that we can catch some common
1058
1086
  # transient endpoint errors without having to spray rescues all over the
1059
1087
  # codebase.
@@ -1109,12 +1137,13 @@ MU.log e.message, MU::WARN, details: e.inspect
1109
1137
  # @param filter [String]: The Compute API filter string to use to isolate appropriate resources
1110
1138
  def delete(type, project, region = nil, noop = false, filter = "description eq #{MU.deploy_id}", credentials: nil)
1111
1139
  list_sym = "list_#{type.sub(/y$/, "ie")}s".to_sym
1140
+ credentials ||= @credentials
1112
1141
  resp = nil
1113
1142
  begin
1114
1143
  if region
1115
- resp = MU::Cloud::Google.compute(credentials: @credentials).send(list_sym, project, region, filter: filter, mu_gcp_enable_apis: false)
1144
+ resp = MU::Cloud::Google.compute(credentials: credentials).send(list_sym, project, region, filter: filter, mu_gcp_enable_apis: false)
1116
1145
  else
1117
- resp = MU::Cloud::Google.compute(credentials: @credentials).send(list_sym, project, filter: filter, mu_gcp_enable_apis: false)
1146
+ resp = MU::Cloud::Google.compute(credentials: credentials).send(list_sym, project, filter: filter, mu_gcp_enable_apis: false)
1118
1147
  end
1119
1148
 
1120
1149
  rescue ::Google::Apis::ClientError => e
@@ -1137,9 +1166,9 @@ MU.log e.message, MU::WARN, details: e.inspect
1137
1166
  resp = nil
1138
1167
  failed = false
1139
1168
  if region
1140
- resp = MU::Cloud::Google.compute(credentials: @credentials).send(delete_sym, project, region, obj.name)
1169
+ resp = MU::Cloud::Google.compute(credentials: credentials).send(delete_sym, project, region, obj.name)
1141
1170
  else
1142
- resp = MU::Cloud::Google.compute(credentials: @credentials).send(delete_sym, project, obj.name)
1171
+ resp = MU::Cloud::Google.compute(credentials: credentials).send(delete_sym, project, obj.name)
1143
1172
  end
1144
1173
 
1145
1174
  if resp.error and resp.error.errors and resp.error.errors.size > 0
@@ -1195,11 +1224,19 @@ MU.log e.message, MU::WARN, details: e.inspect
1195
1224
  retval = nil
1196
1225
  retries = 0
1197
1226
  wait_backoff = 5
1198
- if next_page_token
1199
- if arguments.size == 1 and arguments.first.is_a?(Hash)
1200
- arguments[0][:page_token] = next_page_token
1201
- else
1202
- arguments << { :page_token => next_page_token }
1227
+ if next_page_token
1228
+ if method_sym != :list_entry_log_entries
1229
+ if arguments.size == 1 and arguments.first.is_a?(Hash)
1230
+ arguments[0][:page_token] = next_page_token
1231
+ else
1232
+ arguments << { :page_token => next_page_token }
1233
+ end
1234
+ elsif arguments.first.class == ::Google::Apis::LoggingV2::ListLogEntriesRequest
1235
+ arguments[0] = ::Google::Apis::LoggingV2::ListLogEntriesRequest.new(
1236
+ resource_names: arguments.first.resource_names,
1237
+ filter: arguments.first.filter,
1238
+ page_token: next_page_token
1239
+ )
1203
1240
  end
1204
1241
  end
1205
1242
  begin
@@ -1318,7 +1355,6 @@ MU.log e.message, MU::WARN, details: e.inspect
1318
1355
  if retval.class.name.match(/.*?::Operation$/)
1319
1356
 
1320
1357
  retries = 0
1321
- orig_target = retval.name
1322
1358
 
1323
1359
  # Check whether the various types of +Operation+ responses say
1324
1360
  # they're done, without knowing which specific API they're from
@@ -1390,7 +1426,7 @@ MU.log e.message, MU::WARN, details: e.inspect
1390
1426
  # take advantage.
1391
1427
  # XXX might want to do something similar for delete ops? just the
1392
1428
  # but where we wait for the operation to definitely be done
1393
- had_been_found = false
1429
+ # had_been_found = false
1394
1430
  if method_sym.to_s.match(/^(insert|create|patch)_/)
1395
1431
  get_method = method_sym.to_s.gsub(/^(insert|patch|create_disk|create)_/, "get_").to_sym
1396
1432
  cloud_id = if retval.respond_to?(:target_link)
@@ -1417,7 +1453,7 @@ MU.log e.message, MU::WARN, details: e.inspect
1417
1453
  #if method_sym == :insert_instance
1418
1454
  #MU.log "actual_resource", MU::WARN, details: actual_resource
1419
1455
  #end
1420
- had_been_found = true
1456
+ # had_been_found = true
1421
1457
  if actual_resource.respond_to?(:status) and
1422
1458
  ["PROVISIONING", "STAGING", "PENDING", "CREATING", "RESTORING"].include?(actual_resource.status)
1423
1459
  retries = 0
@@ -1440,6 +1476,7 @@ MU.log e.message, MU::WARN, details: e.inspect
1440
1476
  if overall_retval
1441
1477
  if method_sym.to_s.match(/^list_(.*)/)
1442
1478
  require 'google/apis/iam_v1'
1479
+ require 'google/apis/logging_v2'
1443
1480
  what = Regexp.last_match[1].to_sym
1444
1481
  whatassign = (Regexp.last_match[1]+"=").to_sym
1445
1482
  if overall_retval.class == ::Google::Apis::IamV1::ListServiceAccountsResponse
@@ -1451,7 +1488,7 @@ MU.log e.message, MU::WARN, details: e.inspect
1451
1488
  newarray = retval.public_send(what) + overall_retval.public_send(what)
1452
1489
  overall_retval.public_send(whatassign, newarray)
1453
1490
  end
1454
- else
1491
+ elsif !retval.respond_to?(:next_page_token) or retval.next_page_token.nil? or retval.next_page_token.empty?
1455
1492
  MU.log "Not sure how to append #{method_sym.to_s} results to #{overall_retval.class.name} (apparently #{what.to_s} and #{whatassign.to_s} aren't it), returning first page only", MU::WARN, details: retval
1456
1493
  return retval
1457
1494
  end
@@ -34,7 +34,7 @@ module MU
34
34
 
35
35
  # Called automatically by {MU::Deploy#createResources}
36
36
  def groom
37
- @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloudobj.cloud_id
37
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloud_id
38
38
 
39
39
  current = cloud_desc
40
40
  changed = false
@@ -143,15 +143,14 @@ module MU
143
143
  # Remove all buckets associated with the currently loaded deployment.
144
144
  # @param noop [Boolean]: If true, will only print what would be done
145
145
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
146
- # @param region [String]: The cloud provider region
147
146
  # @return [void]
148
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
149
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
147
+ def self.cleanup(noop: false, ignoremaster: false, credentials: nil, flags: {})
148
+ flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials)
150
149
 
151
- resp = MU::Cloud::Google.storage(credentials: credentials).list_buckets(flags['project'])
150
+ resp = MU::Cloud::Google.storage(credentials: credentials).list_buckets(flags['habitat'])
152
151
  if resp and resp.items
153
152
  resp.items.each { |bucket|
154
- if bucket.labels and bucket.labels["mu-id"] == MU.deploy_id.downcase
153
+ if bucket.labels and bucket.labels["mu-id"] == MU.deploy_id.downcase and (ignoremaster or bucket.labels['mu-master-ip'] == MU.mu_public_ip.gsub(/\./, "_"))
155
154
  MU.log "Deleting Cloud Storage bucket #{bucket.name}"
156
155
  if !noop
157
156
  MU::Cloud::Google.storage(credentials: credentials).delete_bucket(bucket.name)
@@ -172,8 +171,7 @@ module MU
172
171
  # Locate an existing bucket.
173
172
  # @return [OpenStruct]: The cloud provider's complete descriptions of matching bucket.
174
173
  def self.find(**args)
175
- args[:project] ||= args[:habitat]
176
- args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
174
+ args = MU::Cloud::Google.findLocationArgs(args)
177
175
 
178
176
  found = {}
179
177
  if args[:cloud_id]
@@ -198,7 +196,7 @@ module MU
198
196
  # Reverse-map our cloud description into a runnable config hash.
199
197
  # We assume that any values we have in +@config+ are placeholders, and
200
198
  # calculate our own accordingly based on what's live in the cloud.
201
- def toKitten(rootparent: nil, billing: nil, habitats: nil)
199
+ def toKitten(**_args)
202
200
  bok = {
203
201
  "cloud" => "Google",
204
202
  "credentials" => @config['credentials'],
@@ -245,7 +243,7 @@ module MU
245
243
  grantees[binding.role] << { "id" => grantee }
246
244
  elsif grantee.match(/^serviceAccount:(.*)/)
247
245
  sa_name = Regexp.last_match[1]
248
- if MU::Cloud::Google::User.cannedServiceAcctName?(sa_name)
246
+ if MU::Cloud.resourceClass("Google", "User").cannedServiceAcctName?(sa_name)
249
247
  grantees[binding.role] << { "id" => grantee }
250
248
  else
251
249
  grantees[binding.role] << MU::Config::Ref.get(
@@ -300,14 +298,14 @@ module MU
300
298
  end
301
299
 
302
300
  # Cloud-specific configuration properties.
303
- # @param config [MU::Config]: The calling MU::Config object
301
+ # @param _config [MU::Config]: The calling MU::Config object
304
302
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
305
- def self.schema(config)
303
+ def self.schema(_config)
306
304
  toplevel_required = []
307
305
  schema = {
308
306
  "storage_class" => {
309
307
  "type" => "string",
310
- "enum" => ["MULTI_REGIONAL", "REGIONAL", "STANDARD", "NEARLINE", "COLDLINE", "DURABLE_REDUCED_AVAILABILITY"],
308
+ "enum" => ["MULTI_REGIONAL", "REGIONAL", "STANDARD", "NEARLINE", "COLDLINE", "DURABLE_REDUCED_AVAILABILITY", "ARCHIVE"],
311
309
  "default" => "STANDARD"
312
310
  },
313
311
  "bucket_wide_acls" => {
@@ -322,9 +320,9 @@ module MU
322
320
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::bucket}, bare and unvalidated.
323
321
 
324
322
  # @param bucket [Hash]: The resource to process and validate
325
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
323
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
326
324
  # @return [Boolean]: True if validation succeeded, False otherwise
327
- def self.validateConfig(bucket, configurator)
325
+ def self.validateConfig(bucket, _configurator)
328
326
  ok = true
329
327
  bucket['project'] ||= MU::Cloud::Google.defaultProject(bucket['credentials'])
330
328
 
@@ -250,7 +250,7 @@ module MU
250
250
  @config['master_az'] = @config['region']
251
251
  parent_arg = "projects/"+@config['project']+"/locations/"+@config['master_az']
252
252
 
253
- cluster = MU::Cloud::Google.container(credentials: @config['credentials']).create_project_location_cluster(
253
+ MU::Cloud::Google.container(credentials: @config['credentials']).create_project_location_cluster(
254
254
  parent_arg,
255
255
  requestobj
256
256
  )
@@ -277,11 +277,9 @@ module MU
277
277
 
278
278
  me = cloud_desc
279
279
 
280
- parent_arg = "projects/"+@config['project']+"/locations/"+me.location
281
-
282
280
  # Enable/disable basic auth
283
281
  authcfg = {}
284
- action = nil
282
+
285
283
  if @config['master_user'] and (me.master_auth.username != @config['master_user'] or !me.master_auth.password)
286
284
  authcfg[:username] = @config['master_user']
287
285
  authcfg[:password] = Password.pronounceable(16..18)
@@ -368,15 +366,24 @@ module MU
368
366
  }
369
367
  end
370
368
 
369
+ # map from GKE Kuberentes addon parameter names to our BoK equivalent
370
+ # fields so we can check all these programmatically
371
+ addon_map = {
372
+ :horizontal_pod_autoscaling => 'horizontal_pod_autoscaling',
373
+ :http_load_balancing => 'http_load_balancing',
374
+ :kubernetes_dashboard => 'dashboard',
375
+ :network_policy_config => 'network_policy_addon'
376
+ }
377
+
371
378
  if @config['kubernetes']
372
- if (me.addons_config.horizontal_pod_autoscaling.disabled and @config['kubernetes']['horizontal_pod_autoscaling']) or
373
- (!me.addons_config.horizontal_pod_autoscaling and !@config['kubernetes']['horizontal_pod_autoscaling']) or
374
- (me.addons_config.http_load_balancing.disabled and @config['kubernetes']['http_load_balancing']) or
375
- (!me.addons_config.http_load_balancing and !@config['kubernetes']['http_load_balancing']) or
376
- (me.addons_config.kubernetes_dashboard.disabled and @config['kubernetes']['dashboard']) or
377
- (!me.addons_config.kubernetes_dashboard and !@config['kubernetes']['dashboard']) or
378
- (me.addons_config.network_policy_config.disabled and @config['kubernetes']['network_policy_addon']) or
379
- (!me.addons_config.network_policy_config and !@config['kubernetes']['network_policy_addon'])
379
+ have_changes = false
380
+ addon_map.each_pair { |param, bok_param|
381
+ if (me.addons_config.send(param).disabled and @config['kubernetes'][bok_param]) or
382
+ (!me.addons_config.send(param) and !@config['kubernetes'][bok_param])
383
+ have_changes = true
384
+ end
385
+ }
386
+ if have_changes
380
387
  updates << { :desired_addons_config => MU::Cloud::Google.container(:AddonsConfig).new(
381
388
  horizontal_pod_autoscaling: MU::Cloud::Google.container(:HorizontalPodAutoscaling).new(
382
389
  disabled: !@config['kubernetes']['horizontal_pod_autoscaling']
@@ -467,13 +474,10 @@ module MU
467
474
  MU.log %Q{How to interact with your GKE cluster\nkubectl --kubeconfig "#{kube_conf}" get events --all-namespaces\nkubectl --kubeconfig "#{kube_conf}" get all\nkubectl --kubeconfig "#{kube_conf}" create -f some_k8s_deploy.yml\nkubectl --kubeconfig "#{kube_conf}" get nodes}, MU::SUMMARY
468
475
  end
469
476
 
470
-
471
477
  # Locate an existing ContainerCluster or ContainerClusters and return an array containing matching GCP resource descriptors for those that match.
472
478
  # @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching ContainerClusters
473
479
  def self.find(**args)
474
- args[:project] ||= args[:habitat]
475
- args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
476
- location = args[:region] || args[:availability_zone] || "-"
480
+ args = MU::Cloud::Google.findLocationArgs(args)
477
481
 
478
482
  found = {}
479
483
 
@@ -486,7 +490,7 @@ module MU
486
490
  found[args[:cloud_id]] = resp if resp
487
491
  else
488
492
  resp = begin
489
- MU::Cloud::Google.container(credentials: args[:credentials]).list_project_location_clusters("projects/#{args[:project]}/locations/#{location}")
493
+ MU::Cloud::Google.container(credentials: args[:credentials]).list_project_location_clusters("projects/#{args[:project]}/locations/#{args[:location]}")
490
494
  rescue ::Google::Apis::ClientError => e
491
495
  raise e if !e.message.match(/forbidden:/)
492
496
  end
@@ -503,7 +507,7 @@ module MU
503
507
  # Reverse-map our cloud description into a runnable config hash.
504
508
  # We assume that any values we have in +@config+ are placeholders, and
505
509
  # calculate our own accordingly based on what's live in the cloud.
506
- def toKitten(rootparent: nil, billing: nil, habitats: nil)
510
+ def toKitten(**_args)
507
511
 
508
512
  bok = {
509
513
  "cloud" => "Google",
@@ -562,22 +566,24 @@ module MU
562
566
  bok['kubernetes']['network_policy_addon'] = true
563
567
  end
564
568
 
565
- if cloud_desc.ip_allocation_policy.use_ip_aliases
566
- bok['ip_aliases'] = true
567
- end
568
- if cloud_desc.ip_allocation_policy.cluster_ipv4_cidr_block
569
- bok['pod_ip_block'] = cloud_desc.ip_allocation_policy.cluster_ipv4_cidr_block
570
- end
571
- if cloud_desc.ip_allocation_policy.services_ipv4_cidr_block
572
- bok['services_ip_block'] = cloud_desc.ip_allocation_policy.services_ipv4_cidr_block
573
- end
569
+ if cloud_desc.ip_allocation_policy
570
+ if cloud_desc.ip_allocation_policy.use_ip_aliases
571
+ bok['ip_aliases'] = true
572
+ end
573
+ if cloud_desc.ip_allocation_policy.cluster_ipv4_cidr_block
574
+ bok['pod_ip_block'] = cloud_desc.ip_allocation_policy.cluster_ipv4_cidr_block
575
+ end
576
+ if cloud_desc.ip_allocation_policy.services_ipv4_cidr_block
577
+ bok['services_ip_block'] = cloud_desc.ip_allocation_policy.services_ipv4_cidr_block
578
+ end
574
579
 
575
- if cloud_desc.ip_allocation_policy.create_subnetwork
576
- bok['custom_subnet'] = {
577
- "name" => (cloud_desc.ip_allocation_policy.subnetwork_name || cloud_desc.subnetwork)
578
- }
579
- if cloud_desc.ip_allocation_policy.node_ipv4_cidr_block
580
- bok['custom_subnet']['node_ip_block'] = cloud_desc.ip_allocation_policy.node_ipv4_cidr_block
580
+ if cloud_desc.ip_allocation_policy.create_subnetwork
581
+ bok['custom_subnet'] = {
582
+ "name" => (cloud_desc.ip_allocation_policy.subnetwork_name || cloud_desc.subnetwork)
583
+ }
584
+ if cloud_desc.ip_allocation_policy.node_ipv4_cidr_block
585
+ bok['custom_subnet']['node_ip_block'] = cloud_desc.ip_allocation_policy.node_ipv4_cidr_block
586
+ end
581
587
  end
582
588
  end
583
589
 
@@ -651,7 +657,7 @@ module MU
651
657
  end
652
658
 
653
659
  if bok['service_account']
654
- found = MU::Cloud::Google::User.find(
660
+ found = MU::Cloud.resourceClass("Google", "User").find(
655
661
  credentials: bok['credentials'],
656
662
  project: bok['project'],
657
663
  cloud_id: bok['service_account']
@@ -739,24 +745,25 @@ module MU
739
745
  # @param region [String]: The cloud provider region in which to operate
740
746
  # @return [void]
741
747
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
742
- skipsnapshots = flags["skipsnapshots"]
743
748
 
744
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
745
- return if !MU::Cloud::Google::Habitat.isLive?(flags["project"], credentials)
749
+ flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials)
750
+ return if !MU::Cloud.resourceClass("Google", "Habitat").isLive?(flags["habitat"], credentials)
746
751
  clusters = []
747
752
 
748
753
  # Make sure we catch regional *and* zone clusters
749
- found = MU::Cloud::Google.container(credentials: credentials).list_project_location_clusters("projects/#{flags['project']}/locations/#{region}")
754
+ found = MU::Cloud::Google.container(credentials: credentials).list_project_location_clusters("projects/#{flags['habitat']}/locations/#{region}")
750
755
  clusters.concat(found.clusters) if found and found.clusters
751
756
  MU::Cloud::Google.listAZs(region).each { |az|
752
- found = MU::Cloud::Google.container(credentials: credentials).list_project_location_clusters("projects/#{flags['project']}/locations/#{az}")
757
+ found = MU::Cloud::Google.container(credentials: credentials).list_project_location_clusters("projects/#{flags['habitat']}/locations/#{az}")
753
758
  clusters.concat(found.clusters) if found and found.clusters
754
759
  }
755
760
 
756
761
  clusters.uniq.each { |cluster|
757
762
  if !cluster.resource_labels or (
758
763
  !cluster.name.match(/^#{Regexp.quote(MU.deploy_id)}\-/i) and
759
- cluster.resource_labels['mu-id'] != MU.deploy_id.downcase
764
+ (cluster.resource_labels['mu-id'] != MU.deploy_id.downcase or
765
+ (!ignoremaster and cluster.resource_labels['mu-master-ip'] != MU.mu_public_ip.gsub(/\./, "_"))
766
+ )
760
767
  )
761
768
  next
762
769
  end
@@ -810,10 +817,10 @@ module MU
810
817
  "type" => "integer",
811
818
  "description" => "The number of local SSD disks to be attached to workers. See https://cloud.google.com/compute/docs/disks/local-ssd#local_ssd_limits"
812
819
  },
813
- "ssh_user" => MU::Cloud::Google::Server.schema(config)[1]["ssh_user"],
814
- "metadata" => MU::Cloud::Google::Server.schema(config)[1]["metadata"],
815
- "service_account" => MU::Cloud::Google::Server.schema(config)[1]["service_account"],
816
- "scopes" => MU::Cloud::Google::Server.schema(config)[1]["scopes"],
820
+ "ssh_user" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["ssh_user"],
821
+ "metadata" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["metadata"],
822
+ "service_account" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["service_account"],
823
+ "scopes" => MU::Cloud.resourceClass("Google", "Server").schema(config)[1]["scopes"],
817
824
  "private_cluster" => {
818
825
  "description" => "Set a GKE cluster to be private, that is segregated into its own hidden VPC.",
819
826
  "type" => "object",
@@ -1014,6 +1021,25 @@ module MU
1014
1021
  cluster['ip_aliases'] = true
1015
1022
  end
1016
1023
 
1024
+ # try to stake out some nice /21s for our networking config
1025
+ if cluster['ip_aliases'] and cluster["vpc"] and cluster["vpc"]["id"]
1026
+ habarg = if cluster["vpc"]["habitat"] and cluster["vpc"]["habitat"]["id"]
1027
+ cluster["vpc"]["habitat"]["id"]
1028
+ else
1029
+ cluster["project"]
1030
+ end
1031
+ found = MU::MommaCat.findStray("Google", "vpcs", cloud_id: cluster["vpc"]["id"], credentials: cluster["credentials"], habitats: [habarg], dummy_ok: true)
1032
+ if found and found.size == 1
1033
+ myvpc = found.first
1034
+ # XXX this might not make sense with custom_subnet
1035
+ cluster['pod_ip_block'] ||= myvpc.getUnusedAddressBlock(max_bits: 21)
1036
+ cluster['services_ip_block'] ||= myvpc.getUnusedAddressBlock(exclude: [cluster['pod_ip_block']], max_bits: 21)
1037
+ if cluster['tpu']
1038
+ cluster['tpu_ip_block'] ||= myvpc.getUnusedAddressBlock(exclude: [cluster['pod_ip_block'], cluster['services_ip_block']], max_bits: 21)
1039
+ end
1040
+ end
1041
+ end
1042
+
1017
1043
  if cluster['service_account']
1018
1044
  cluster['service_account']['cloud'] = "Google"
1019
1045
  cluster['service_account']['habitat'] ||= MU::Config::Ref.get(
@@ -1023,12 +1049,9 @@ module MU
1023
1049
  type: "habitats"
1024
1050
  )
1025
1051
  if cluster['service_account']['name'] and
1026
- !cluster['service_account']['id']
1027
- cluster['dependencies'] ||= []
1028
- cluster['dependencies'] << {
1029
- "type" => "user",
1030
- "name" => cluster['service_account']['name']
1031
- }
1052
+ !cluster['service_account']['id'] and
1053
+ !cluster['service_account']['deploy_id']
1054
+ MU::Config.addDependency(cluster, cluster['service_account']['name'], "user")
1032
1055
  end
1033
1056
  found = MU::Config::Ref.get(cluster['service_account'])
1034
1057
  # XXX verify that found.kitten fails when it's supposed to
@@ -1037,29 +1060,7 @@ module MU
1037
1060
  ok = false
1038
1061
  end
1039
1062
  else
1040
- user = {
1041
- "name" => cluster['name'],
1042
- "cloud" => "Google",
1043
- "project" => cluster["project"],
1044
- "credentials" => cluster["credentials"],
1045
- "type" => "service"
1046
- }
1047
- if user["name"].length < 6
1048
- user["name"] += Password.pronounceable(6)
1049
- end
1050
- configurator.insertKitten(user, "users", true)
1051
- cluster['dependencies'] ||= []
1052
- cluster['service_account'] = MU::Config::Ref.get(
1053
- type: "users",
1054
- cloud: "Google",
1055
- name: cluster["name"],
1056
- project: cluster["project"],
1057
- credentials: cluster["credentials"]
1058
- )
1059
- cluster['dependencies'] << {
1060
- "type" => "user",
1061
- "name" => user["name"]
1062
- }
1063
+ cluster = MU::Cloud.resourceClass("Google", "User").genericServiceAccount(cluster, configurator)
1063
1064
  end
1064
1065
 
1065
1066
  if cluster['dependencies']
@@ -1110,7 +1111,7 @@ module MU
1110
1111
  }
1111
1112
  if !match
1112
1113
  MU.log "No version matching #{cluster['kubernetes']['version']} available, will try floating minor revision", MU::WARN
1113
- cluster['kubernetes']['version'].sub!(/^(\d+\.\d+\.).*/i, '\1')
1114
+ cluster['kubernetes']['version'].sub!(/^(\d+\.\d+)\..*/i, '\1')
1114
1115
  master_versions.each { |v|
1115
1116
  if v.match(/^#{Regexp.quote(cluster['kubernetes']['version'])}/)
1116
1117
  match = true
@@ -1155,9 +1156,13 @@ module MU
1155
1156
  end
1156
1157
  end
1157
1158
 
1158
- cluster['instance_type'] = MU::Cloud::Google::Server.validateInstanceType(cluster["instance_type"], cluster["region"], project: cluster['project'], credentials: cluster['credentials'])
1159
+ cluster['instance_type'] = MU::Cloud.resourceClass("Google", "Server").validateInstanceType(cluster["instance_type"], cluster["region"], project: cluster['project'], credentials: cluster['credentials'])
1159
1160
  ok = false if cluster['instance_type'].nil?
1160
1161
 
1162
+ if !MU::Master.kubectl
1163
+ MU.log "Since I can't find a kubectl executable, you will have to handle all service account, user, and role bindings manually!", MU::WARN
1164
+ end
1165
+
1161
1166
  ok
1162
1167
  end
1163
1168
 
@@ -1204,7 +1209,8 @@ module MU
1204
1209
  labels["name"] = MU::Cloud::Google.nameStr(@mu_name)
1205
1210
 
1206
1211
  labelset = MU::Cloud::Google.container(:SetLabelsRequest).new(
1207
- resource_labels: labels
1212
+ resource_labels: labels,
1213
+ label_fingerprint: cloud_desc.label_fingerprint
1208
1214
  )
1209
1215
  MU::Cloud::Google.container(credentials: @config['credentials']).set_project_location_cluster_resource_labels(@cloud_id, labelset)
1210
1216
  end
@@ -1223,6 +1229,7 @@ module MU
1223
1229
  @@server_config[credentials][az] = MU::Cloud::Google.container(credentials: credentials).get_project_location_server_config(parent_arg)
1224
1230
  @@server_config[credentials][az]
1225
1231
  end
1232
+ private_class_method :defaults
1226
1233
 
1227
1234
  def writeKubeConfig
1228
1235
  kube_conf = @deploy.deploy_dir+"/kubeconfig-#{@config['name']}"
@@ -1247,7 +1254,7 @@ module MU
1247
1254
  # Take this opportunity to ensure that the 'client' service account
1248
1255
  # used by certificate authentication exists and has appropriate
1249
1256
  # privilege
1250
- if @username and @password
1257
+ if @username and @password and MU::Master.kubectl
1251
1258
  File.open(client_binding, "w"){ |k|
1252
1259
  k.puts <<-EOF
1253
1260
  kind: ClusterRoleBinding