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
@@ -17,34 +17,17 @@ module MU
17
17
  class Google
18
18
  # Support for Google Cloud Storage
19
19
  class Bucket < MU::Cloud::Bucket
20
- @deploy = nil
21
- @config = nil
22
- @project_id = 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::logs}
30
- def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
31
- @deploy = mommacat
32
- @config = MU::Config.manxify(kitten_cfg)
33
- @cloud_id ||= cloud_id
34
- if mu_name
35
- @mu_name = mu_name
36
- @config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
37
- if !@project_id
38
- project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
39
- @project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
40
- end
41
- 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
42
25
  @mu_name ||= @deploy.getResourceName(@config["name"])
43
26
  end
44
27
 
45
28
  # Called automatically by {MU::Deploy#createResources}
46
29
  def create
47
- @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloudobj.cloud_id
30
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloud_id
48
31
  MU::Cloud::Google.storage(credentials: credentials).insert_bucket(@project_id, bucket_descriptor)
49
32
  @cloud_id = @mu_name.downcase
50
33
  end
@@ -72,6 +55,18 @@ module MU
72
55
  changed = true
73
56
  end
74
57
 
58
+ if @config['bucket_wide_acls'] and (!current.iam_configuration or
59
+ !current.iam_configuration.bucket_policy_only or
60
+ !current.iam_configuration.bucket_policy_only.enabled)
61
+ MU.log "Converting Cloud Storage bucket #{@cloud_id} to use bucket-wide ACLs only", MU::NOTICE
62
+ changed = true
63
+ elsif !@config['bucket_wide_acls'] and current.iam_configuration and
64
+ current.iam_configuration.bucket_policy_only and
65
+ current.iam_configuration.bucket_policy_only.enabled
66
+ MU.log "Converting Cloud Storage bucket #{@cloud_id} to use bucket and object ACLs", MU::NOTICE
67
+ changed = true
68
+ end
69
+
75
70
  if changed
76
71
  MU::Cloud::Google.storage(credentials: credentials).patch_bucket(@cloud_id, bucket_descriptor)
77
72
  end
@@ -79,18 +74,19 @@ module MU
79
74
  if @config['policies']
80
75
  @config['policies'].each { |pol|
81
76
  pol['grant_to'].each { |grantee|
77
+ grantee['id'] ||= grantee["identifier"]
82
78
  entity = if grantee["type"]
83
79
  sibling = deploy_obj.findLitterMate(
84
- name: grantee["identifier"],
80
+ name: grantee["id"],
85
81
  type: grantee["type"]
86
82
  )
87
83
  if sibling
88
84
  sibling.cloudobj.cloud_id
89
85
  else
90
- raise MuError, "Couldn't find a #{grantee["type"]} named #{grantee["identifier"]} when generating Cloud Storage access policy"
86
+ raise MuError, "Couldn't find a #{grantee["type"]} named #{grantee["id"]} when generating Cloud Storage access policy"
91
87
  end
92
88
  else
93
- pol['grant_to'].first['identifier']
89
+ pol['grant_to'].first['id']
94
90
  end
95
91
 
96
92
  if entity.match(/@/) and !entity.match(/^(group|user)\-/)
@@ -123,6 +119,14 @@ module MU
123
119
  end
124
120
  end
125
121
 
122
+ # Upload a file to a bucket.
123
+ # @param url [String]: Target URL, of the form gs://bucket/folder/file
124
+ # @param acl [String]: Canned ACL permission to assign to the object we upload
125
+ # @param file [String]: Path to a local file to write to our target location. One of +file+ or +data+ must be specified.
126
+ # @param data [String]: Data to write to our target location. One of +file+ or +data+ must be specified.
127
+ def self.upload(url, acl: "private", file: nil, data: nil, credentials: nil)
128
+ end
129
+
126
130
  # Does this resource type exist as a global (cloud-wide) artifact, or
127
131
  # is it localized to a region/zone?
128
132
  # @return [Boolean]
@@ -166,18 +170,135 @@ module MU
166
170
  end
167
171
 
168
172
  # Locate an existing bucket.
169
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
170
- # @param region [String]: The cloud provider region.
171
- # @param flags [Hash]: Optional flags
172
173
  # @return [OpenStruct]: The cloud provider's complete descriptions of matching bucket.
173
- def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {}, tag_key: nil, tag_value: nil)
174
+ def self.find(**args)
175
+ args[:project] ||= args[:habitat]
176
+ args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
177
+
174
178
  found = {}
175
- if cloud_id
176
- found[cloud_id] = MU::Cloud::Google.storage(credentials: credentials).get_bucket(cloud_id)
179
+ if args[:cloud_id]
180
+ found[args[:cloud_id]] = MU::Cloud::Google.storage(credentials: args[:credentials]).get_bucket(args[:cloud_id])
181
+ else
182
+ resp = begin
183
+ MU::Cloud::Google.storage(credentials: args[:credentials]).list_buckets(args[:project])
184
+ rescue ::Google::Apis::ClientError => e
185
+ raise e if !e.message.match(/forbidden:/)
186
+ end
187
+
188
+ if resp and resp.items
189
+ resp.items.each { |bucket|
190
+ found[bucket.id] = bucket
191
+ }
192
+ end
177
193
  end
194
+
178
195
  found
179
196
  end
180
197
 
198
+ # Reverse-map our cloud description into a runnable config hash.
199
+ # We assume that any values we have in +@config+ are placeholders, and
200
+ # calculate our own accordingly based on what's live in the cloud.
201
+ def toKitten(rootparent: nil, billing: nil, habitats: nil)
202
+ bok = {
203
+ "cloud" => "Google",
204
+ "credentials" => @config['credentials'],
205
+ "cloud_id" => @cloud_id
206
+ }
207
+
208
+ bok['name'] = cloud_desc.name
209
+ bok['project'] = @project_id
210
+ bok['storage_class'] = cloud_desc.storage_class
211
+ if cloud_desc.versioning and cloud_desc.versioning.enabled
212
+ bok['versioning'] = true
213
+ end
214
+ if cloud_desc.website
215
+ bok['web'] = true
216
+ if cloud_desc.website.not_found_page
217
+ bok['web_error_object'] = cloud_desc.website.not_found_page
218
+ end
219
+ if cloud_desc.website.main_page_suffix
220
+ bok['web_index_object'] = cloud_desc.website.main_page_suffix
221
+ end
222
+ pp cloud_desc
223
+ end
224
+
225
+ # MU.log "get_bucket_iam_policy", MU::NOTICE, details: MU::Cloud::Google.storage(credentials: @credentials).get_bucket_iam_policy(@cloud_id)
226
+ pols = MU::Cloud::Google.storage(credentials: @credentials).get_bucket_iam_policy(@cloud_id)
227
+
228
+ if pols and pols.bindings and pols.bindings.size > 0
229
+ bok['policies'] = []
230
+ count = 0
231
+ grantees = {}
232
+ pols.bindings.each { |binding|
233
+ grantees[binding.role] ||= []
234
+ binding.members.each { |grantee|
235
+ if grantee.match(/^(user|group):(.*)/)
236
+ grantees[binding.role] << MU::Config::Ref.get(
237
+ id: Regexp.last_match[2],
238
+ type: Regexp.last_match[1]+"s",
239
+ cloud: "Google",
240
+ credentials: @credentials
241
+ )
242
+ elsif grantee == "allUsers" or
243
+ grantee == "allAuthenticatedUsers" or
244
+ grantee.match(/^project(?:Owner|Editor|Viewer):/)
245
+ grantees[binding.role] << { "id" => grantee }
246
+ elsif grantee.match(/^serviceAccount:(.*)/)
247
+ sa_name = Regexp.last_match[1]
248
+ if MU::Cloud::Google::User.cannedServiceAcctName?(sa_name)
249
+ grantees[binding.role] << { "id" => grantee }
250
+ else
251
+ grantees[binding.role] << MU::Config::Ref.get(
252
+ id: sa_name,
253
+ type: "users",
254
+ cloud: "Google",
255
+ credentials: @credentials
256
+ )
257
+ end
258
+ else
259
+ # *shrug*
260
+ grantees[binding.role] << { "id" => grantee }
261
+ end
262
+ }
263
+ }
264
+
265
+ # munge together roles that apply to the exact same set of
266
+ # principals
267
+ reverse_map = {}
268
+ grantees.each_pair { |perm, grant_to|
269
+ reverse_map[grant_to] ||= []
270
+ reverse_map[grant_to] << perm
271
+ }
272
+ already_done = []
273
+
274
+ grantees.each_pair { |perm, grant_to|
275
+ if already_done.include?(perm+grant_to.to_s)
276
+ next
277
+ end
278
+ bok['policies'] << {
279
+ "name" => "policy"+count.to_s,
280
+ "grant_to" => grant_to,
281
+ "permissions" => reverse_map[grant_to]
282
+ }
283
+ reverse_map[grant_to].each { |doneperm|
284
+ already_done << doneperm+grant_to.to_s
285
+ }
286
+ count = count+1
287
+ }
288
+ end
289
+
290
+ if cloud_desc.iam_configuration and
291
+ cloud_desc.iam_configuration.bucket_policy_only and
292
+ cloud_desc.iam_configuration.bucket_policy_only.enabled
293
+ bok['bucket_wide_acls'] = true
294
+ else
295
+ # MU.log "list_bucket_access_controls", MU::NOTICE, details: MU::Cloud::Google.storage(credentials: @credentials).list_bucket_access_controls(@cloud_id)
296
+ # MU.log "list_default_object_access_controls", MU::NOTICE, details: MU::Cloud::Google.storage(credentials: @credentials).list_default_object_access_controls(@cloud_id)
297
+ end
298
+
299
+ bok
300
+ end
301
+
181
302
  # Cloud-specific configuration properties.
182
303
  # @param config [MU::Config]: The calling MU::Config object
183
304
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
@@ -188,6 +309,11 @@ module MU
188
309
  "type" => "string",
189
310
  "enum" => ["MULTI_REGIONAL", "REGIONAL", "STANDARD", "NEARLINE", "COLDLINE", "DURABLE_REDUCED_AVAILABILITY"],
190
311
  "default" => "STANDARD"
312
+ },
313
+ "bucket_wide_acls" => {
314
+ "type" => "boolean",
315
+ "default" => false,
316
+ "description" => "Disables object-level access controls in favor of bucket-wide policies"
191
317
  }
192
318
  }
193
319
  [toplevel_required, schema]
@@ -200,6 +326,7 @@ module MU
200
326
  # @return [Boolean]: True if validation succeeded, False otherwise
201
327
  def self.validateConfig(bucket, configurator)
202
328
  ok = true
329
+ bucket['project'] ||= MU::Cloud::Google.defaultProject(bucket['credentials'])
203
330
 
204
331
  if bucket['policies']
205
332
  bucket['policies'].each { |pol|
@@ -245,6 +372,20 @@ module MU
245
372
  params[:versioning] = MU::Cloud::Google.storage(:Bucket)::Versioning.new(enabled: false)
246
373
  end
247
374
 
375
+ if @config['bucket_wide_acls']
376
+ params[:iam_configuration] = MU::Cloud::Google.storage(:Bucket)::IamConfiguration.new(
377
+ bucket_policy_only: MU::Cloud::Google.storage(:Bucket)::IamConfiguration::BucketPolicyOnly.new(
378
+ enabled: @config['bucket_wide_acls']
379
+ )
380
+ )
381
+ else
382
+ params[:iam_configuration] = MU::Cloud::Google.storage(:Bucket)::IamConfiguration.new(
383
+ bucket_policy_only: MU::Cloud::Google.storage(:Bucket)::IamConfiguration::BucketPolicyOnly.new(
384
+ enabled: false
385
+ )
386
+ )
387
+ end
388
+
248
389
  MU::Cloud::Google.storage(:Bucket).new(params)
249
390
  end
250
391
 
@@ -1,4 +1,4 @@
1
- # Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
1
+ # Copyright:: Copyright (c) 2019 eGlobalTech, Inc., all rights reserved
2
2
  #
3
3
  # Licensed under the BSD-3 license (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -17,33 +17,13 @@ module MU
17
17
  class Google
18
18
  # A Kubernetes cluster as configured in {MU::Config::BasketofKittens::container_clusters}
19
19
  class ContainerCluster < MU::Cloud::ContainerCluster
20
- @deploy = nil
21
- @config = nil
22
- attr_reader :mu_name
23
- attr_reader :cloud_id
24
- attr_reader :config
25
- attr_reader :groomer
26
-
27
- # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
28
- # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::container_clusters}
29
- def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
30
- @deploy = mommacat
31
- @config = MU::Config.manxify(kitten_cfg)
32
- @cloud_id ||= cloud_id
33
- # @mu_name = mu_name ? mu_name : @deploy.getResourceName(@config["name"])
34
- @config["groomer"] = MU::Config.defaultGroomer unless @config["groomer"]
35
- @groomclass = MU::Groomer.loadGroomer(@config["groomer"])
36
-
37
- if !mu_name.nil?
38
- @mu_name = mu_name
39
- deploydata = describe[2]
40
- @config['availability_zone'] = deploydata['zone']
41
- @config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
42
- if !@project_id
43
- project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
44
- @project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
45
- end
46
- else
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
+
26
+ if !@mu_name
47
27
  @mu_name ||= @deploy.getResourceName(@config["name"], max_length: 40)
48
28
  end
49
29
  end
@@ -52,16 +32,11 @@ module MU
52
32
  # Called automatically by {MU::Deploy#createResources}
53
33
  # @return [String]: The cloud provider's identifier for this GKE instance.
54
34
  def create
55
- labels = {}
56
- MU::MommaCat.listStandardTags.each_pair { |name, value|
57
- if !value.nil?
58
- labels[name.downcase] = value.downcase.gsub(/[^a-z0-9\-\_]/i, "_")
59
- end
60
- }
35
+ labels = Hash[@tags.keys.map { |k|
36
+ [k.downcase, @tags[k].downcase.gsub(/[^-_a-z0-9]/, '-')] }
37
+ ]
61
38
  labels["name"] = MU::Cloud::Google.nameStr(@mu_name)
62
39
 
63
- @config['availability_zone'] ||= MU::Cloud::Google.listAZs(@config['region']).sample
64
-
65
40
  if @vpc.nil? and @config['vpc'] and @config['vpc']['vpc_name']
66
41
  @vpc = @deploy.findLitterMate(name: @config['vpc']['vpc_name'], type: "vpcs")
67
42
  end
@@ -70,124 +45,673 @@ module MU
70
45
  raise MuError, "ContainerCluster #{@config['name']} unable to locate its resident VPC from #{@config['vpc']}"
71
46
  end
72
47
 
73
- subnet = nil
74
- @vpc.subnets.each { |s|
75
- if s.az == @config['region']
76
- subnet = s
77
- break
48
+
49
+ sa = MU::Config::Ref.get(@config['service_account'])
50
+ if sa.name and @deploy.findLitterMate(name: sa.name, type: "users")
51
+ @service_acct = @deploy.findLitterMate(name: sa.name, type: "users").cloud_desc
52
+ else
53
+ if !sa or !sa.kitten or !sa.kitten.cloud_desc
54
+ raise MuError, "Failed to get service account cloud id from #{@config['service_account'].to_s}"
78
55
  end
79
- }
80
- puts @config['credentials']
81
- service_acct = MU::Cloud::Google::Server.createServiceAccount(
82
- @mu_name.downcase,
83
- @deploy,
84
- project: @config['project'],
85
- credentials: @config['credentials']
86
- )
87
- MU::Cloud::Google.grantDeploySecretAccess(service_acct.email, credentials: @config['credentials'])
56
+ @service_acct = sa.kitten.cloud_desc
57
+ end
58
+ if !@config['scrub_mu_isms']
59
+ MU::Cloud::Google.grantDeploySecretAccess(@service_acct.email, credentials: @config['credentials'])
60
+ end
88
61
 
89
- @config['ssh_user'] ||= "mu"
62
+ @config['ssh_user'] ||= "muadmin"
90
63
 
91
- node_desc = {
92
- :machine_type => @config['instance_type'],
93
- :preemptible => @config['preemptible'],
94
- :disk_size_gb => @config['disk_size_gb'],
95
- :labels => labels,
96
- :tags => [@mu_name.downcase],
97
- :service_account => service_acct.email,
98
- :oauth_scopes => ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.read_only"],
99
- :metadata => {
100
- "ssh-keys" => @config['ssh_user']+":"+@deploy.ssh_public_key
101
- }
102
- }
103
- [:local_ssd_count, :min_cpu_platform, :image_type].each { |field|
104
- if @config[field.to_s]
105
- node_desc[field] = @config[field.to_s]
106
- end
107
- }
108
64
 
109
- nodeobj = MU::Cloud::Google.container(:NodeConfig).new(node_desc)
65
+ nodeobj = if @config['min_size'] and @config['max_size']
66
+ MU::Cloud::Google.container(:NodePool).new(
67
+ name: @mu_name.downcase,
68
+ initial_node_count: @config['instance_count'] || @config['min_size'],
69
+ autoscaling: MU::Cloud::Google.container(:NodePoolAutoscaling).new(
70
+ enabled: true,
71
+ min_node_count: @config['min_size'],
72
+ max_node_count: @config['max_size'],
73
+ ),
74
+ management: MU::Cloud::Google.container(:NodeManagement).new(
75
+ auto_upgrade: @config['auto_upgrade'],
76
+ auto_repair: @config['auto_repair']
77
+ ),
78
+ config: MU::Cloud::Google.container(:NodeConfig).new(node_desc)
79
+ )
80
+ else
81
+ MU::Cloud::Google.container(:NodeConfig).new(node_desc)
82
+ end
83
+ locations = if @config['availability_zone']
84
+ [@config['availability_zone']]
85
+ else
86
+ MU::Cloud::Google.listAZs(@config['region'])
87
+ end
88
+
89
+ master_user = @config['master_user']
90
+ # We'll create a temporary basic auth config so that we can grant
91
+ # useful permissions to the Client Certificate user
92
+ master_user ||= "master_user"
93
+ master_pw = Password.pronounceable(18)
110
94
 
111
95
  desc = {
112
96
  :name => @mu_name.downcase,
113
97
  :description => @deploy.deploy_id,
114
98
  :network => @vpc.cloud_id,
115
- :subnetwork => subnet.cloud_id,
116
- :labels => labels,
99
+ :enable_tpu => @config['tpu'],
117
100
  :resource_labels => labels,
118
- :initial_cluster_version => @config['kubernetes']['version'],
119
- :initial_node_count => @config['instance_count'],
120
- :locations => MU::Cloud::Google.listAZs(@config['region']),
121
- :node_config => nodeobj
101
+ :locations => locations,
102
+ :master_auth => MU::Cloud::Google.container(:MasterAuth).new(
103
+ :client_certificate_config => MU::Cloud::Google.container(:ClientCertificateConfig).new(
104
+ :issue_client_certificate => true
105
+ ),
106
+ :username => master_user,
107
+ :password => master_pw
108
+ ),
122
109
  }
123
110
 
111
+ if @config['kubernetes']
112
+ desc[:addons_config] = MU::Cloud::Google.container(:AddonsConfig).new(
113
+ horizontal_pod_autoscaling: MU::Cloud::Google.container(:HorizontalPodAutoscaling).new(
114
+ disabled: !@config['kubernetes']['horizontal_pod_autoscaling']
115
+ ),
116
+ http_load_balancing: MU::Cloud::Google.container(:HttpLoadBalancing).new(
117
+ disabled: !@config['kubernetes']['http_load_balancing']
118
+ ),
119
+ kubernetes_dashboard: MU::Cloud::Google.container(:KubernetesDashboard).new(
120
+ disabled: !@config['kubernetes']['dashboard']
121
+ ),
122
+ network_policy_config: MU::Cloud::Google.container(:NetworkPolicyConfig).new(
123
+ disabled: !@config['kubernetes']['network_policy_addon']
124
+ )
125
+ )
126
+ end
127
+
128
+ # Pick an existing subnet from our VPC, if we're not going to create
129
+ # one.
130
+ if !@config['custom_subnet']
131
+ @vpc.subnets.each { |s|
132
+ if s.az == @config['region']
133
+ desc[:subnetwork] = s.cloud_id
134
+ break
135
+ end
136
+ }
137
+ end
138
+ if @config['log_facility'] == "kubernetes"
139
+ desc[:logging_service] = "logging.googleapis.com/kubernetes"
140
+ desc[:monitoring_service] = "monitoring.googleapis.com/kubernetes"
141
+ elsif @config['log_facility'] == "basic"
142
+ desc[:logging_service] = "logging.googleapis.com"
143
+ desc[:monitoring_service] = "monitoring.googleapis.com"
144
+ else
145
+ desc[:logging_service] = "none"
146
+ desc[:monitoring_service] = "none"
147
+ end
148
+
149
+ if nodeobj.is_a?(::Google::Apis::ContainerV1::NodeConfig)
150
+ desc[:node_config] = nodeobj
151
+ desc[:initial_node_count] = @config['instance_count']
152
+ else
153
+ desc[:node_pools] = [nodeobj]
154
+ end
155
+
156
+ if @config['kubernetes']
157
+ if @config['kubernetes']['version']
158
+ desc[:initial_cluster_version] = @config['kubernetes']['version']
159
+ end
160
+ if @config['kubernetes']['alpha']
161
+ desc[:enable_kubernetes_alpha] = @config['kubernetes']['alpha']
162
+ end
163
+ end
164
+
165
+ if @config['preferred_maintenance_window']
166
+ desc[:maintenance_policy] = MU::Cloud::Google.container(:MaintenancePolicy).new(
167
+ window: MU::Cloud::Google.container(:MaintenanceWindow).new(
168
+ daily_maintenance_window: MU::Cloud::Google.container(:DailyMaintenanceWindow).new(
169
+ start_time: @config['preferred_maintenance_window']
170
+ )
171
+ )
172
+ )
173
+ end
174
+
175
+ if @config['private_cluster']
176
+ desc[:private_cluster_config] = MU::Cloud::Google.container(:PrivateClusterConfig).new(
177
+ enable_private_endpoint: @config['private_cluster']['private_master'],
178
+ enable_private_nodes: @config['private_cluster']['private_nodes'],
179
+ master_ipv4_cidr_block: @config['private_cluster']['master_ip_block']
180
+ )
181
+ desc[:ip_allocation_policy] = MU::Cloud::Google.container(:IpAllocationPolicy).new(
182
+ use_ip_aliases: true
183
+ )
184
+ end
185
+
186
+ if @config['ip_aliases'] or @config['custom_subnet'] or
187
+ @config['services_ip_block'] or @config['services_ip_block_name'] or
188
+ @config['pod_ip_block'] or @config['pod_ip_block_name'] or
189
+ @config['tpu_ip_block']
190
+ alloc_desc = { :use_ip_aliases => @config['ip_aliases'] }
191
+
192
+ if @config['custom_subnet']
193
+ alloc_desc[:create_subnetwork] = true
194
+ alloc_desc[:subnetwork_name] = if @config['custom_subnet']['name']
195
+ @config['custom_subnet']['name']
196
+ else
197
+ @mu_name.downcase
198
+ end
199
+
200
+ if @config['custom_subnet']['node_ip_block']
201
+ alloc_desc[:node_ipv4_cidr_block] = @config['custom_subnet']['node_ip_block']
202
+ end
203
+ else
204
+ if @config['pod_ip_block_name']
205
+ alloc_desc[:cluster_secondary_range_name] = @config['pod_ip_block_name']
206
+ end
207
+ if @config['services_ip_block_name']
208
+ alloc_desc[:services_secondary_range_name] = @config['services_ip_block_name']
209
+ end
210
+ end
211
+
212
+ if @config['services_ip_block']
213
+ alloc_desc[:services_ipv4_cidr_block] = @config['services_ip_block']
214
+ end
215
+ if @config['tpu_ip_block']
216
+ alloc_desc[:tpu_ipv4_cidr_block] = @config['tpu_ip_block']
217
+ end
218
+ if @config['pod_ip_block']
219
+ alloc_desc[:cluster_ipv4_cidr_block] = @config['pod_ip_block']
220
+ end
221
+
222
+ desc[:ip_allocation_policy] = MU::Cloud::Google.container(:IpAllocationPolicy).new(alloc_desc)
223
+ pp alloc_desc
224
+ end
225
+
226
+ if @config['authorized_networks'] and @config['authorized_networks'].size > 0
227
+ desc[:master_authorized_networks_config] = MU::Cloud::Google.container(:MasterAuthorizedNetworksConfig).new(
228
+ enabled: true,
229
+ cidr_blocks: @config['authorized_networks'].map { |n|
230
+ MU::Cloud::Google.container(:CidrBlock).new(
231
+ cidr_block: n['ip_block'],
232
+ display_name: n['label']
233
+ )
234
+ }
235
+ )
236
+ end
237
+
238
+ if @config['kubernetes'] and @config['kubernetes']['max_pods'] and
239
+ @config['ip_aliases']
240
+ desc[:default_max_pods_constraint] = MU::Cloud::Google.container(:MaxPodsConstraint).new(
241
+ max_pods_per_node: @config['kubernetes']['max_pods']
242
+ )
243
+ end
244
+
124
245
  requestobj = MU::Cloud::Google.container(:CreateClusterRequest).new(
125
- :cluster => MU::Cloud::Google.container(:Cluster).new(desc)
246
+ :cluster => MU::Cloud::Google.container(:Cluster).new(desc),
126
247
  )
127
248
 
128
- MU.log "Creating GKE cluster #{@mu_name.downcase}", details: desc
129
- pp @vpc.subnets.map { |x| x.config['name'] }
130
- pp requestobj
131
- cluster = MU::Cloud::Google.container(credentials: @config['credentials']).create_cluster(
132
- @config['project'],
133
- @config['availability_zone'],
249
+ MU.log "Creating GKE cluster #{@mu_name.downcase}", details: requestobj
250
+ @config['master_az'] = @config['region']
251
+ parent_arg = "projects/"+@config['project']+"/locations/"+@config['master_az']
252
+
253
+ cluster = MU::Cloud::Google.container(credentials: @config['credentials']).create_project_location_cluster(
254
+ parent_arg,
134
255
  requestobj
135
256
  )
257
+ @cloud_id = parent_arg+"/clusters/"+@mu_name.downcase
136
258
 
137
259
  resp = nil
138
260
  begin
139
- resp = MU::Cloud::Google.container(credentials: @config['credentials']).get_zone_cluster(@config["project"], @config['availability_zone'], @mu_name.downcase)
261
+ resp = MU::Cloud::Google.container(credentials: @config['credentials']).get_project_location_cluster(@cloud_id)
262
+ if resp.status == "ERROR"
263
+ MU.log "GKE cluster #{@cloud_id} failed", MU::ERR, details: resp.status_message
264
+ raise MuError, "GKE cluster #{@cloud_id} failed: #{resp.status_message}"
265
+ end
140
266
  sleep 30 if resp.status != "RUNNING"
141
267
  end while resp.nil? or resp.status != "RUNNING"
142
- # labelCluster # XXX need newer API release
143
- @cloud_id = @mu_name.downcase
144
268
 
145
- # XXX wait until the thing is ready
269
+ writeKubeConfig
270
+
271
+ end
272
+
273
+
274
+ # Called automatically by {MU::Deploy#createResources}
275
+ def groom
276
+ labelCluster
277
+
278
+ me = cloud_desc
279
+
280
+ parent_arg = "projects/"+@config['project']+"/locations/"+me.location
281
+
282
+ # Enable/disable basic auth
283
+ authcfg = {}
284
+ action = nil
285
+ if @config['master_user'] and (me.master_auth.username != @config['master_user'] or !me.master_auth.password)
286
+ authcfg[:username] = @config['master_user']
287
+ authcfg[:password] = Password.pronounceable(16..18)
288
+ MU.log "Enabling basic auth for GKE cluster #{@mu_name.downcase}", MU::NOTICE, details: authcfg
289
+ elsif !@config['master_user'] and me.master_auth.username
290
+ authcfg[:username] = ""
291
+ MU.log "Disabling basic auth for GKE cluster #{@mu_name.downcase}", MU::NOTICE
292
+ end
293
+ if authcfg.size > 0
294
+ MU::Cloud::Google.container(credentials: @config['credentials']).set_project_location_cluster_master_auth(
295
+ @cloud_id,
296
+ MU::Cloud::Google.container(:SetMasterAuthRequest).new(
297
+ name: @cloud_id,
298
+ action: "SET_USERNAME",
299
+ update: MU::Cloud::Google.container(:MasterAuth).new(
300
+ authcfg
301
+ )
302
+ )
303
+ )
304
+ me = cloud_desc(use_cache: false)
305
+ end
306
+
307
+ # Now go through all the things that use update_project_location_cluster
308
+ updates = []
309
+
310
+ locations = if @config['availability_zone']
311
+ [@config['availability_zone']]
312
+ else
313
+ MU::Cloud::Google.listAZs(@config['region'])
314
+ end
315
+ if me.locations != locations
316
+ updates << { :desired_locations => locations }
317
+ end
318
+
319
+ if @config['min_size'] and @config['max_size'] and
320
+ (me.node_pools.first.autoscaling.min_node_count != @config['min_size'] or
321
+ me.node_pools.first.autoscaling.max_node_count != @config['max_size'])
322
+ updates << {
323
+ :desired_node_pool_autoscaling => MU::Cloud::Google.container(:NodePoolAutoscaling).new(
324
+ enabled: true,
325
+ max_node_count: @config['max_size'],
326
+ min_node_count: @config['min_size']
327
+ )
328
+ }
329
+ end
330
+
331
+ if @config['authorized_networks'] and @config['authorized_networks'].size > 0
332
+ desired = @config['authorized_networks'].map { |n|
333
+ MU::Cloud::Google.container(:CidrBlock).new(
334
+ cidr_block: n['ip_block'],
335
+ display_name: n['label']
336
+ )
337
+ }
338
+ if !me.master_authorized_networks_config or
339
+ !me.master_authorized_networks_config.enabled or
340
+ !me.master_authorized_networks_config.cidr_blocks or
341
+ me.master_authorized_networks_config.cidr_blocks.map {|n| n.cidr_block+n.display_name }.sort != desired.map {|n| n.cidr_block+n.display_name }.sort
342
+ updates << { :desired_master_authorized_networks_config => MU::Cloud::Google.container(:MasterAuthorizedNetworksConfig).new(
343
+ enabled: true,
344
+ cidr_blocks: desired
345
+ )}
346
+ end
347
+ elsif me.master_authorized_networks_config and
348
+ me.master_authorized_networks_config.enabled
349
+ updates << { :desired_master_authorized_networks_config => MU::Cloud::Google.container(:MasterAuthorizedNetworksConfig).new(
350
+ enabled: false
351
+ )}
352
+ end
353
+
354
+ if @config['log_facility'] == "kubernetes" and me.logging_service != "logging.googleapis.com/kubernetes"
355
+ updates << {
356
+ :desired_logging_service => "logging.googleapis.com/kubernetes",
357
+ :desired_monitoring_service => "monitoring.googleapis.com/kubernetes"
358
+ }
359
+ elsif @config['log_facility'] == "basic" and me.logging_service != "logging.googleapis.com"
360
+ updates << {
361
+ :desired_logging_service => "logging.googleapis.com",
362
+ :desired_monitoring_service => "monitoring.googleapis.com"
363
+ }
364
+ elsif @config['log_facility'] == "none" and me.logging_service != "none"
365
+ updates << {
366
+ :desired_logging_service => "none",
367
+ :desired_monitoring_service => "none"
368
+ }
369
+ end
370
+
371
+ 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'])
380
+ updates << { :desired_addons_config => MU::Cloud::Google.container(:AddonsConfig).new(
381
+ horizontal_pod_autoscaling: MU::Cloud::Google.container(:HorizontalPodAutoscaling).new(
382
+ disabled: !@config['kubernetes']['horizontal_pod_autoscaling']
383
+ ),
384
+ http_load_balancing: MU::Cloud::Google.container(:HttpLoadBalancing).new(
385
+ disabled: !@config['kubernetes']['http_load_balancing']
386
+ ),
387
+ kubernetes_dashboard: MU::Cloud::Google.container(:KubernetesDashboard).new(
388
+ disabled: !@config['kubernetes']['dashboard']
389
+ ),
390
+ network_policy_config: MU::Cloud::Google.container(:NetworkPolicyConfig).new(
391
+ disabled: !@config['kubernetes']['network_policy_addon']
392
+ )
393
+ )}
394
+ end
395
+ end
396
+
397
+ if @config['kubernetes'] and @config['kubernetes']['version']
398
+ if MU.version_sort(@config['kubernetes']['version'], me.current_master_version) > 0
399
+ updates << { :desired_master_version => @config['kubernetes']['version'] }
400
+ end
401
+ end
402
+
403
+ if @config['kubernetes'] and @config['kubernetes']['nodeversion']
404
+ if MU.version_sort(@config['kubernetes']['nodeversion'], me.current_node_version) > 0
405
+ updates << { :desired_node_version => @config['kubernetes']['nodeversion'] }
406
+ end
407
+ end
408
+
409
+ if updates.size > 0
410
+ updates.each { |mapping|
411
+ requestobj = MU::Cloud::Google.container(:UpdateClusterRequest).new(
412
+ :name => @cloud_id,
413
+ :update => MU::Cloud::Google.container(:ClusterUpdate).new(
414
+ mapping
415
+ )
416
+ )
417
+ MU.log "Updating GKE Cluster #{@mu_name.downcase}", MU::NOTICE, details: mapping
418
+ begin
419
+ MU::Cloud::Google.container(credentials: @config['credentials']).update_project_location_cluster(
420
+ @cloud_id,
421
+ requestobj
422
+ )
423
+ rescue ::Google::Apis::ClientError => e
424
+ MU.log e.message, MU::WARN
425
+ end
426
+ }
427
+ me = cloud_desc(use_cache: false)
428
+ end
429
+
430
+ if @config['preferred_maintenance_window'] and
431
+ (!me.maintenance_policy.window or
432
+ !me.maintenance_policy.window.daily_maintenance_window or
433
+ me.maintenance_policy.window.daily_maintenance_window.start_time != @config['preferred_maintenance_window'])
434
+ MU.log "Setting GKE Cluster #{@mu_name.downcase} maintenance time to #{@config['preferred_maintenance_window']}", MU::NOTICE
435
+ MU::Cloud::Google.container(credentials: @config['credentials']).set_project_location_cluster_maintenance_policy(
436
+ @cloud_id,
437
+ MU::Cloud::Google.container(:SetMaintenancePolicyRequest).new(
438
+ maintenance_policy: MU::Cloud::Google.container(:MaintenancePolicy).new(
439
+ window: MU::Cloud::Google.container(:MaintenanceWindow).new(
440
+ daily_maintenance_window: MU::Cloud::Google.container(:DailyMaintenanceWindow).new(
441
+ start_time: @config['preferred_maintenance_window']
442
+ )
443
+ )
444
+ )
445
+ )
446
+ )
447
+ elsif !@config['preferred_maintenance_window'] and me.maintenance_policy.window
448
+ MU.log "Unsetting GKE Cluster #{@mu_name.downcase} maintenance time to #{@config['preferred_maintenance_window']}", MU::NOTICE
449
+ MU::Cloud::Google.container(credentials: @config['credentials']).set_project_location_cluster_maintenance_policy(
450
+ @cloud_id,
451
+ nil
452
+ )
453
+ end
454
+
455
+
456
+ kube_conf = writeKubeConfig
457
+
458
+ if @config['kubernetes_resources']
459
+ MU::Master.applyKubernetesResources(
460
+ @config['name'],
461
+ @config['kubernetes_resources'],
462
+ kubeconfig: kube_conf,
463
+ outputdir: @deploy.deploy_dir
464
+ )
465
+ end
466
+
467
+ MU.log %Q{How to interact with your Kubernetes 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
146
468
  end
147
469
 
470
+
148
471
  # Locate an existing ContainerCluster or ContainerClusters and return an array containing matching GCP resource descriptors for those that match.
149
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
150
- # @param region [String]: The cloud provider region
151
- # @param tag_key [String]: A tag key to search.
152
- # @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
153
- # @param flags [Hash]: Optional flags
154
472
  # @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching ContainerClusters
155
- def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, flags: {}, credentials: nil)
156
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
473
+ 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] || "-"
477
+
478
+ found = {}
479
+
480
+ if args[:cloud_id]
481
+ resp = begin
482
+ MU::Cloud::Google.container(credentials: args[:credentials]).get_project_location_cluster(args[:cloud_id])
483
+ rescue ::Google::Apis::ClientError => e
484
+ raise e if !e.message.match(/forbidden:/)
485
+ end
486
+ found[args[:cloud_id]] = resp if resp
487
+ else
488
+ resp = begin
489
+ MU::Cloud::Google.container(credentials: args[:credentials]).list_project_location_clusters("projects/#{args[:project]}/locations/#{location}")
490
+ rescue ::Google::Apis::ClientError => e
491
+ raise e if !e.message.match(/forbidden:/)
492
+ end
493
+ if resp and resp.clusters and !resp.clusters.empty?
494
+ resp.clusters.each { |c|
495
+ found[c.self_link.sub(/.*?\/projects\//, 'projects/')] = c
496
+ }
497
+ end
498
+ end
499
+
500
+ found
157
501
  end
158
502
 
159
- # Called automatically by {MU::Deploy#createResources}
160
- def groom
161
- deploydata = describe[2]
162
- @config['availability_zone'] ||= deploydata['zone']
163
- resp = MU::Cloud::Google.container(credentials: @config['credentials']).get_zone_cluster(@config["project"], @config['availability_zone'], @mu_name.downcase)
164
- # pp resp
165
-
166
- # labelCluster # XXX need newer API release
167
-
168
- # desired_*:
169
- # addons_config
170
- # image_type
171
- # locations
172
- # master_authorized_networks_config
173
- # master_version
174
- # monitoring_service
175
- # node_pool_autoscaling
176
- # node_pool_id
177
- # node_version
178
- # update = {
179
-
180
- # }
181
- # pp update
182
- # requestobj = MU::Cloud::Google.container(:UpdateClusterRequest).new(
183
- # :cluster => MU::Cloud::Google.container(:ClusterUpdate).new(update)
184
- # )
185
- # XXX do all the kubernetes stuff like we do in AWS
503
+ # Reverse-map our cloud description into a runnable config hash.
504
+ # We assume that any values we have in +@config+ are placeholders, and
505
+ # calculate our own accordingly based on what's live in the cloud.
506
+ def toKitten(rootparent: nil, billing: nil, habitats: nil)
507
+
508
+ bok = {
509
+ "cloud" => "Google",
510
+ "project" => @config['project'],
511
+ "credentials" => @config['credentials'],
512
+ "cloud_id" => @cloud_id,
513
+ "name" => cloud_desc.name.dup
514
+ }
515
+
516
+ bok['region'] = cloud_desc.location.sub(/\-[a-z]$/, "")
517
+ if cloud_desc.locations.size == 1
518
+ bok['availability_zone'] = cloud_desc.locations.first
519
+ end
520
+ bok["instance_count"] = cloud_desc.current_node_count
521
+ cloud_desc.network_config.network.match(/^projects\/(.*?)\/.*?\/networks\/([^\/]+)(?:$|\/)/)
522
+ vpc_proj = Regexp.last_match[1]
523
+ vpc_id = Regexp.last_match[2]
524
+
525
+ bok['vpc'] = MU::Config::Ref.get(
526
+ id: vpc_id,
527
+ cloud: "Google",
528
+ habitat: MU::Config::Ref.get(
529
+ id: vpc_proj,
530
+ cloud: "Google",
531
+ credentials: @credentials,
532
+ type: "habitats"
533
+ ),
534
+ credentials: @config['credentials'],
535
+ type: "vpcs"
536
+ )
537
+
538
+
539
+ bok['kubernetes'] = {
540
+ "version" => cloud_desc.current_master_version,
541
+ "nodeversion" => cloud_desc.current_node_version
542
+ }
543
+ if cloud_desc.default_max_pods_constraint and
544
+ cloud_desc.default_max_pods_constraint.max_pods_per_node
545
+ bok['kubernetes']['max_pods'] = cloud_desc.default_max_pods_constraint.max_pods_per_node
546
+ end
547
+
548
+ if cloud_desc.addons_config.horizontal_pod_autoscaling and
549
+ cloud_desc.addons_config.horizontal_pod_autoscaling.disabled
550
+ bok['kubernetes']['horizontal_pod_autoscaling'] = false
551
+ end
552
+ if cloud_desc.addons_config.http_load_balancing and
553
+ cloud_desc.addons_config.http_load_balancing.disabled
554
+ bok['kubernetes']['http_load_balancing'] = false
555
+ end
556
+ if !cloud_desc.addons_config.kubernetes_dashboard or
557
+ !cloud_desc.addons_config.kubernetes_dashboard.disabled
558
+ bok['kubernetes']['dashboard'] = true
559
+ end
560
+ if !cloud_desc.addons_config.network_policy_config or
561
+ !cloud_desc.addons_config.network_policy_config.disabled
562
+ bok['kubernetes']['network_policy_addon'] = true
563
+ end
564
+
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
574
+
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
581
+ end
582
+ end
583
+
584
+ bok['log_facility'] = if cloud_desc.logging_service == "logging.googleapis.com"
585
+ "basic"
586
+ elsif cloud_desc.logging_service == "logging.googleapis.com/kubernetes"
587
+ "kubernetes"
588
+ else
589
+ "none"
590
+ end
591
+
592
+ if cloud_desc.master_auth and cloud_desc.master_auth.username
593
+ bok['master_user'] = cloud_desc.master_auth.username
594
+ end
595
+
596
+ if cloud_desc.maintenance_policy and
597
+ cloud_desc.maintenance_policy.window and
598
+ cloud_desc.maintenance_policy.window.daily_maintenance_window and
599
+ cloud_desc.maintenance_policy.window.daily_maintenance_window.start_time
600
+ bok['preferred_maintenance_window'] = cloud_desc.maintenance_policy.window.daily_maintenance_window.start_time
601
+ end
602
+
603
+ if cloud_desc.enable_tpu
604
+ bok['tpu'] = true
605
+ end
606
+ if cloud_desc.enable_kubernetes_alpha
607
+ bok['kubernetes'] ||= {}
608
+ bok['kubernetes']['alpha'] = true
609
+ end
610
+
611
+ if cloud_desc.node_pools and cloud_desc.node_pools.size > 0
612
+ pool = cloud_desc.node_pools.first # we don't really support multiples atm
613
+ bok["instance_type"] = pool.config.machine_type
614
+ bok["instance_count"] = pool.initial_node_count
615
+ bok['scopes'] = pool.config.oauth_scopes
616
+ if pool.config.metadata
617
+ bok["metadata"] = pool.config.metadata.keys.map { |k|
618
+ { "key" => k, "value" => pool.config.metadata[k] }
619
+ }
620
+ end
621
+ if pool.autoscaling and pool.autoscaling.enabled
622
+ bok['max_size'] = pool.autoscaling.max_node_count
623
+ bok['min_size'] = pool.autoscaling.min_node_count
624
+ end
625
+ bok['auto_repair'] = false
626
+ bok['auto_upgrade'] = false
627
+ if pool.management
628
+ bok['auto_repair'] = true if pool.management.auto_repair
629
+ bok['auto_upgrade'] = true if pool.management.auto_upgrade
630
+ end
631
+ [:local_ssd_count, :min_cpu_platform, :image_type, :disk_size_gb, :preemptible, :service_account].each { |field|
632
+ if pool.config.respond_to?(field)
633
+ bok[field.to_s] = pool.config.method(field).call
634
+ bok.delete(field.to_s) if bok[field.to_s].nil?
635
+ end
636
+ }
637
+ else
638
+ bok["instance_type"] = cloud_desc.node_config.machine_type
639
+ bok['scopes'] = cloud_desc.node_config.oauth_scopes
640
+ if cloud_desc.node_config.metadata
641
+ bok["metadata"] = cloud_desc.node_config.metadata.keys.map { |k|
642
+ { "key" => k, "value" => pool.config.metadata[k] }
643
+ }
644
+ end
645
+ [:local_ssd_count, :min_cpu_platform, :image_type, :disk_size_gb, :preemptible, :service_account].each { |field|
646
+ if cloud_desc.node_config.respond_to?(field)
647
+ bok[field.to_s] = cloud_desc.node_config.method(field).call
648
+ bok.delete(field.to_s) if bok[field.to_s].nil?
649
+ end
650
+ }
651
+ end
652
+
653
+ if bok['service_account']
654
+ found = MU::Cloud::Google::User.find(
655
+ credentials: bok['credentials'],
656
+ project: bok['project'],
657
+ cloud_id: bok['service_account']
658
+ )
659
+ if found and found.size == 1
660
+ sa = found.values.first
661
+ # Ignore generic Mu service accounts
662
+ if cloud_desc.resource_labels and
663
+ cloud_desc.resource_labels["mu-id"] and
664
+ sa.description and
665
+ cloud_desc.resource_labels["mu-id"].downcase == sa.description.downcase
666
+ bok.delete("service_account")
667
+ else
668
+ bok['service_account'] = MU::Config::Ref.get(
669
+ id: found.values.first.name,
670
+ cloud: "Google",
671
+ credentials: @config['credentials'],
672
+ type: "users"
673
+ )
674
+ end
675
+ else
676
+ bok.delete("service_account")
677
+ end
678
+ end
679
+
680
+ if cloud_desc.private_cluster_config
681
+ if cloud_desc.private_cluster_config.enable_private_nodes?
682
+ bok["private_cluster"] ||= {}
683
+ bok["private_cluster"]["private_nodes"] = true
684
+ end
685
+ if cloud_desc.private_cluster_config.enable_private_endpoint?
686
+ bok["private_cluster"] ||= {}
687
+ bok["private_cluster"]["private_master"] = true
688
+ end
689
+ if cloud_desc.private_cluster_config.master_ipv4_cidr_block
690
+ bok["private_cluster"] ||= {}
691
+ bok["private_cluster"]["master_ip_block"] = cloud_desc.private_cluster_config.master_ipv4_cidr_block
692
+ end
693
+ end
694
+
695
+ if cloud_desc.master_authorized_networks_config and
696
+ cloud_desc.master_authorized_networks_config.cidr_blocks and
697
+ cloud_desc.master_authorized_networks_config.cidr_blocks.size > 0
698
+ bok['authorized_networks'] = []
699
+ cloud_desc.master_authorized_networks_config.cidr_blocks.each { |c|
700
+ bok['authorized_networks'] << {
701
+ "ip_block" => c.cidr_block,
702
+ "label" => c.display_name
703
+ }
704
+ }
705
+ end
706
+
707
+ bok
186
708
  end
187
709
 
710
+
188
711
  # Register a description of this cluster instance with this deployment's metadata.
189
712
  def notify
190
- desc = MU.structToHash(MU::Cloud::Google.container(credentials: @config['credentials']).get_zone_cluster(@config["project"], @config['availability_zone'], @mu_name.downcase))
713
+ resp = MU::Cloud::Google.container(credentials: @config['credentials']).get_project_location_cluster(@cloud_id)
714
+ desc = MU.structToHash(resp)
191
715
  desc["project"] = @config['project']
192
716
  desc["cloud_id"] = @cloud_id
193
717
  desc["project_id"] = @project_id
@@ -205,7 +729,7 @@ puts @config['credentials']
205
729
  # Denote whether this resource implementation is experiment, ready for
206
730
  # testing, or ready for production use.
207
731
  def self.quality
208
- MU::Cloud::ALPHA
732
+ MU::Cloud::RELEASE
209
733
  end
210
734
 
211
735
  # Called by {MU::Cleanup}. Locates resources that were created by the
@@ -218,35 +742,51 @@ puts @config['credentials']
218
742
  skipsnapshots = flags["skipsnapshots"]
219
743
 
220
744
  flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
745
+ return if !MU::Cloud::Google::Habitat.isLive?(flags["project"], credentials)
746
+ clusters = []
747
+
748
+ # 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}")
750
+ clusters.concat(found.clusters) if found and found.clusters
221
751
  MU::Cloud::Google.listAZs(region).each { |az|
222
- found = MU::Cloud::Google.container(credentials: credentials).list_zone_clusters(flags["project"], az)
223
- if found and found.clusters
224
- found.clusters.each { |cluster|
752
+ found = MU::Cloud::Google.container(credentials: credentials).list_project_location_clusters("projects/#{flags['project']}/locations/#{az}")
753
+ clusters.concat(found.clusters) if found and found.clusters
754
+ }
225
755
 
226
- if !cluster.name.match(/^#{Regexp.quote(MU.deploy_id)}\-/i) and
227
- cluster.resource_labels['mu-id'] != MU.deploy_id.downcase
228
- next
229
- end
230
- MU.log "Deleting GKE cluster #{cluster.name}"
231
- if !noop
232
- MU::Cloud::Google.container(credentials: credentials).delete_zone_cluster(flags["project"], az, cluster.name)
233
- begin
234
- MU::Cloud::Google.container(credentials: credentials).get_zone_cluster(flags["project"], az, cluster.name)
235
- sleep 60
236
- rescue ::Google::Apis::ClientError => e
237
- if e.message.match(/is currently creating cluster/)
238
- sleep 60
239
- retry
240
- elsif !e.message.match(/notFound:/)
241
- raise e
242
- else
243
- break
244
- end
245
- end while true
756
+ clusters.uniq.each { |cluster|
757
+ if !cluster.resource_labels or (
758
+ !cluster.name.match(/^#{Regexp.quote(MU.deploy_id)}\-/i) and
759
+ cluster.resource_labels['mu-id'] != MU.deploy_id.downcase
760
+ )
761
+ next
762
+ end
763
+ MU.log "Deleting GKE cluster #{cluster.name}"
764
+ if !noop
765
+ cloud_id = cluster.self_link.sub(/.*?\/projects\//, 'projects/')
766
+ retries = 0
767
+ begin
768
+ MU::Cloud::Google.container(credentials: credentials).delete_project_location_cluster(cloud_id)
769
+ MU::Cloud::Google.container(credentials: credentials).get_project_location_cluster(cloud_id)
770
+ sleep 60
771
+ rescue ::Google::Apis::ClientError => e
772
+ if e.message.match(/notFound: /)
773
+ MU.log cloud_id, MU::WARN, details: e.inspect
774
+ break
775
+ elsif e.message.match(/failedPrecondition: /)
776
+ if (retries % 5) == 0
777
+ MU.log "Waiting to delete GKE cluster #{cluster.name}: #{e.message}", MU::NOTICE
778
+ end
779
+ sleep 60
780
+ retries += 1
781
+ retry
782
+ else
783
+ MU.log cloud_id, MU::WARN, details: e.inspect
784
+ raise e
246
785
  end
247
- }
786
+ end while true
248
787
  end
249
788
  }
789
+
250
790
  end
251
791
 
252
792
  # Cloud-specific configuration properties.
@@ -254,16 +794,104 @@ puts @config['credentials']
254
794
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
255
795
  def self.schema(config)
256
796
  toplevel_required = []
797
+ gke_defaults = defaults
257
798
  schema = {
799
+ "auto_upgrade" => {
800
+ "type" => "boolean",
801
+ "description" => "Automatically upgrade worker nodes during maintenance windows",
802
+ "default" => true
803
+ },
804
+ "auto_repair" => {
805
+ "type" => "boolean",
806
+ "description" => "Automatically replace worker nodes which fail health checks",
807
+ "default" => true
808
+ },
258
809
  "local_ssd_count" => {
259
810
  "type" => "integer",
260
811
  "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"
261
812
  },
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"],
817
+ "private_cluster" => {
818
+ "description" => "Set a GKE cluster to be private, that is segregated into its own hidden VPC.",
819
+ "type" => "object",
820
+ "properties" => {
821
+ "private_nodes" => {
822
+ "type" => "boolean",
823
+ "default" => true,
824
+ "description" => "Whether GKE worker nodes have internal IP addresses only."
825
+ },
826
+ "private_master" => {
827
+ "type" => "boolean",
828
+ "default" => false,
829
+ "description" => "Whether the GKE Kubernetes master's internal IP address is used as the cluster endpoint."
830
+ },
831
+ "master_ip_block" => {
832
+ "type" => "string",
833
+ "pattern" => MU::Config::CIDR_PATTERN,
834
+ "default" => "172.20.0.0/28",
835
+ "description" => "The private IP address range to use for the GKE master's network"
836
+ }
837
+ }
838
+ },
839
+ "custom_subnet" => {
840
+ "type" => "object",
841
+ "description" => "If set, GKE will create a new subnetwork specifically for this cluster",
842
+ "properties" => {
843
+ "name" => {
844
+ "type" => "string",
845
+ "description" => "Set a custom name for the generated subnet"
846
+ },
847
+ "node_ip_block" => {
848
+ "type" => "string",
849
+ "pattern" => MU::Config::CIDR_PATTERN,
850
+ "description" => "The IP address range of the worker nodes in this cluster, in CIDR notation"
851
+ }
852
+ }
853
+ },
854
+ "pod_ip_block" => {
855
+ "type" => "string",
856
+ "pattern" => MU::Config::CIDR_PATTERN,
857
+ "description" => "The IP address range of the container pods in this cluster, in CIDR notation"
858
+ },
859
+ "pod_ip_block_name" => {
860
+ "type" => "string",
861
+ "description" => "The name of the secondary range to be used for the pod CIDR block"
862
+ },
863
+ "services_ip_block" => {
864
+ "type" => "string",
865
+ "pattern" => MU::Config::CIDR_PATTERN,
866
+ "description" => "The IP address range of the services in this cluster, in CIDR notation"
867
+ },
868
+ "services_ip_block_name" => {
869
+ "type" => "string",
870
+ "description" => "The name of the secondary range to be used for the services CIDR block"
871
+ },
872
+ "ip_aliases" => {
873
+ "type" => "boolean",
874
+ "description" => "Whether alias IPs will be used for pod IPs in the cluster. Will be automatically enabled for functionality, such as +private_cluster+, which requires it."
875
+ },
876
+ "tpu_ip_block" => {
877
+ "type" => "string",
878
+ "pattern" => MU::Config::CIDR_PATTERN,
879
+ "description" => "The IP address range of any Cloud TPUs in this cluster, in CIDR notation"
880
+ },
262
881
  "disk_size_gb" => {
263
882
  "type" => "integer",
264
883
  "description" => "Size of the disk attached to each worker, specified in GB. The smallest allowed disk size is 10GB",
265
884
  "default" => 100
266
885
  },
886
+ "min_size" => {
887
+ "description" => "In GKE, this is the minimum number of nodes *per availability zone*, when scaling is enabled. Setting +min_size+ and +max_size+ enables scaling."
888
+ },
889
+ "max_size" => {
890
+ "description" => "In GKE, this is the maximum number of nodes *per availability zone*, when scaling is enabled. Setting +min_size+ and +max_size+ enables scaling."
891
+ },
892
+ "instance_count" => {
893
+ "description" => "In GKE, this value is ignored if +min_size+ and +max_size+ are set."
894
+ },
267
895
  "min_cpu_platform" => {
268
896
  "type" => "string",
269
897
  "description" => "Minimum CPU platform to be used by workers. The instances may be scheduled on the specified or newer CPU platform. Applicable values are the friendly names of CPU platforms, such as minCpuPlatform: 'Intel Haswell' or minCpuPlatform: 'Intel Sandy Bridge'."
@@ -275,7 +903,95 @@ puts @config['credentials']
275
903
  },
276
904
  "image_type" => {
277
905
  "type" => "string",
278
- "description" => "The image type to use for workers. Note that for a given image type, the latest version of it will be used."
906
+ "enum" => gke_defaults ? gke_defaults.valid_image_types : ["COS"],
907
+ "description" => "The image type to use for workers. Note that for a given image type, the latest version of it will be used.",
908
+ "default" => gke_defaults ? gke_defaults.default_image_type : "COS"
909
+ },
910
+ "availability_zone" => {
911
+ "type" => "string",
912
+ "description" => "Target a specific availability zone for this cluster"
913
+ },
914
+ "preferred_maintenance_window" => {
915
+ "type" => "string",
916
+ "description" => "The preferred daily time to perform node maintenance. Time format should be in [RFC3339](http://www.ietf.org/rfc/rfc3339.txt) format +HH:MM+ GMT.",
917
+ "pattern" => '^\d\d:\d\d$'
918
+ },
919
+ "kubernetes" => {
920
+ "description" => "Kubernetes-specific options",
921
+ "properties" => {
922
+ "version" => {
923
+ "type" => "string"
924
+ },
925
+ "nodeversion" => {
926
+ "type" => "string",
927
+ "description" => "The version of Kubernetes to install on GKE worker nodes."
928
+ },
929
+ "alpha" => {
930
+ "type" => "boolean",
931
+ "default" => false,
932
+ "description" => "Enable alpha-quality Kubernetes features on this cluster"
933
+ },
934
+ "dashboard" => {
935
+ "type" => "boolean",
936
+ "default" => false,
937
+ "description" => "Enable the Kubernetes Dashboard"
938
+ },
939
+ "horizontal_pod_autoscaling" => {
940
+ "type" => "boolean",
941
+ "default" => true,
942
+ "description" => "Increases or decreases the number of replica pods a replication controller has based on the resource usage of the existing pods."
943
+ },
944
+ "http_load_balancing" => {
945
+ "type" => "boolean",
946
+ "default" => true,
947
+ "description" => "HTTP (L7) load balancing controller addon, which makes it easy to set up HTTP load balancers for services in a cluster."
948
+ },
949
+ "network_policy_addon" => {
950
+ "type" => "boolean",
951
+ "default" => false,
952
+ "description" => "Enable the Network Policy addon"
953
+ }
954
+ }
955
+ },
956
+ "pod_ip_range" => {
957
+ "type" => "string",
958
+ "pattern" => MU::Config::CIDR_PATTERN,
959
+ "description" => "The IP address range of the container pods in this cluster, in CIDR notation"
960
+ },
961
+ "tpu" => {
962
+ "type" => "boolean",
963
+ "default" => false,
964
+ "description" => "Enable the ability to use Cloud TPUs in this cluster."
965
+ },
966
+ "log_facility" => {
967
+ "type" => "string",
968
+ "default" => "kubernetes",
969
+ "description" => "The +logging.googleapis.com+ and +monitoring.googleapis.com+ facilities that this cluster should use to write logs and metrics.",
970
+ "enum" => ["basic", "kubernetes", "none"]
971
+ },
972
+ "master_user" => {
973
+ "type" => "string",
974
+ "description" => "Enables Basic Auth for a GKE cluster with string as the master username"
975
+ },
976
+ "authorized_networks" => {
977
+ "type" => "array",
978
+ "items" => {
979
+ "description" => "GKE's Master authorized networks functionality",
980
+ "type" => "object",
981
+ "ip_block" => {
982
+ "type" => "string",
983
+ "description" => "CIDR block to allow",
984
+ "pattern" => MU::Config::CIDR_PATTERN,
985
+ },
986
+ "label" =>{
987
+ "description" => "Label for this CIDR block",
988
+ "type" => "string",
989
+ }
990
+ }
991
+ },
992
+ "master_az" => {
993
+ "type" => "string",
994
+ "description" => "Target a specific Availability Zone for the GKE master. If not set, we will choose one which has the most current versions of Kubernetes available."
279
995
  }
280
996
  }
281
997
  [toplevel_required, schema]
@@ -287,11 +1003,148 @@ puts @config['credentials']
287
1003
  # @return [Boolean]: True if validation succeeded, False otherwise
288
1004
  def self.validateConfig(cluster, configurator)
289
1005
  ok = true
290
- # XXX validate k8s versions (master and node)
291
- # XXX validate image types
292
- # MU::Cloud::Google.container.get_project_zone_serverconfig(@config["project"], @config['availability_zone'])
1006
+ cluster['project'] ||= MU::Cloud::Google.defaultProject(cluster['credentials'])
1007
+
1008
+ cluster['master_az'] ||= cluster['availability_zone'] if cluster['availability_zone']
1009
+
1010
+ if cluster['private_cluster'] or cluster['custom_subnet'] or
1011
+ cluster['services_ip_block'] or cluster['services_ip_block_name'] or
1012
+ cluster['pod_ip_block'] or cluster['pod_ip_block_name'] or
1013
+ cluster['tpu_ip_block']
1014
+ cluster['ip_aliases'] = true
1015
+ end
1016
+
1017
+ if cluster['service_account']
1018
+ cluster['service_account']['cloud'] = "Google"
1019
+ cluster['service_account']['habitat'] ||= MU::Config::Ref.get(
1020
+ id: cluster['project'],
1021
+ cloud: "Google",
1022
+ credentials: cluster['credentials'],
1023
+ type: "habitats"
1024
+ )
1025
+ 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
+ }
1032
+ end
1033
+ found = MU::Config::Ref.get(cluster['service_account'])
1034
+ # XXX verify that found.kitten fails when it's supposed to
1035
+ if cluster['service_account']['id'] and !found.kitten
1036
+ MU.log "GKE cluster #{cluster['name']} failed to locate service account #{cluster['service_account']} in project #{cluster['project']}", MU::ERR
1037
+ ok = false
1038
+ end
1039
+ else
1040
+ user = {
1041
+ "name" => cluster['name'],
1042
+ "cloud" => "Google",
1043
+ "project" => cluster["project"],
1044
+ "credentials" => cluster["credentials"],
1045
+ "type" => "service"
1046
+ }
1047
+ configurator.insertKitten(user, "users", true)
1048
+ cluster['dependencies'] ||= []
1049
+ cluster['service_account'] = MU::Config::Ref.get(
1050
+ type: "users",
1051
+ cloud: "Google",
1052
+ name: cluster["name"],
1053
+ project: cluster["project"],
1054
+ credentials: cluster["credentials"]
1055
+ )
1056
+ cluster['dependencies'] << {
1057
+ "type" => "user",
1058
+ "name" => cluster["name"]
1059
+ }
1060
+ end
1061
+
1062
+ if (cluster['pod_ip_block_name'] or cluster['services_ip_block_name']) and
1063
+ cluster['custom_subnet']
1064
+ MU.log "GKE cluster #{cluster['name']} cannot specify pod_ip_block_name or services_ip_block_name when using a custom subnet", MU::ERR
1065
+ ok = false
1066
+ end
1067
+
1068
+ # If we've enabled master authorized networks, make sure our Mu
1069
+ # Master is one of the things allowed in.
1070
+ if cluster['authorized_networks']
1071
+ found_me = false
1072
+ my_cidr = NetAddr::IPv4.parse(MU.mu_public_ip)
1073
+ cluster['authorized_networks'].each { |block|
1074
+ cidr_obj = NetAddr::IPv4Net.parse(block['ip_block'])
1075
+ if cidr_obj.contains(my_cidr)
1076
+ found_me = true
1077
+ break
1078
+ end
1079
+ }
1080
+ if !found_me
1081
+ cluster['authorized_networks'] << {
1082
+ "ip_block" => MU.mu_public_ip+"/32",
1083
+ "label" => "Mu Master #{$MU_CFG['hostname']}"
1084
+ }
1085
+ end
1086
+ end
1087
+
1088
+ master_versions = defaults(az: cluster['master_az']).valid_master_versions.sort { |a, b| MU.version_sort(a, b) }
1089
+ if cluster['kubernetes'] and cluster['kubernetes']['version']
1090
+ if cluster['kubernetes']['version'] == "latest"
1091
+ cluster['kubernetes']['version'] = master_versions.last
1092
+ elsif !master_versions.include?(cluster['kubernetes']['version'])
1093
+ match = false
1094
+ master_versions.each { |v|
1095
+ if v.match(/^#{Regexp.quote(cluster['kubernetes']['version'])}/)
1096
+ match = true
1097
+ break
1098
+ end
1099
+ }
1100
+ if !match
1101
+ MU.log "No version matching #{cluster['kubernetes']['version']} available, will try floating minor revision", MU::WARN
1102
+ cluster['kubernetes']['version'].sub!(/^(\d+\.\d+\.).*/i, '\1')
1103
+ master_versions.each { |v|
1104
+ if v.match(/^#{Regexp.quote(cluster['kubernetes']['version'])}/)
1105
+ match = true
1106
+ break
1107
+ end
1108
+ }
1109
+ if !match
1110
+ MU.log "Failed to find a GKE master version matching #{cluster['kubernetes']['version']} among available versions in #{cluster['master_az'] || cluster['region']}.", MU::ERR, details: master_versions
1111
+ ok = false
1112
+ end
1113
+ end
1114
+ end
1115
+ end
1116
+
1117
+ node_versions = defaults(az: cluster['master_az']).valid_node_versions.sort { |a, b| MU.version_sort(a, b) }
1118
+
1119
+ if cluster['kubernetes'] and cluster['kubernetes']['nodeversion']
1120
+ if cluster['kubernetes']['nodeversion'] == "latest"
1121
+ cluster['kubernetes']['nodeversion'] = node_versions.last
1122
+ elsif !node_versions.include?(cluster['kubernetes']['nodeversion'])
1123
+ match = false
1124
+ node_versions.each { |v|
1125
+ if v.match(/^#{Regexp.quote(cluster['kubernetes']['nodeversion'])}/)
1126
+ match = true
1127
+ break
1128
+ end
1129
+ }
1130
+ if !match
1131
+ MU.log "No version matching #{cluster['kubernetes']['nodeversion']} available, will try floating minor revision", MU::WARN
1132
+ cluster['kubernetes']['nodeversion'].sub!(/^(\d+\.\d+\.).*/i, '\1')
1133
+ node_versions.each { |v|
1134
+ if v.match(/^#{Regexp.quote(cluster['kubernetes']['nodeversion'])}/)
1135
+ match = true
1136
+ break
1137
+ end
1138
+ }
1139
+ if !match
1140
+ MU.log "Failed to find a GKE node version matching #{cluster['kubernetes']['nodeversion']} among available versions in #{cluster['master_az'] || cluster['region']}.", MU::ERR, details: node_versions
1141
+ ok = false
1142
+ end
1143
+ end
1144
+ end
1145
+ end
293
1146
 
294
- cluster['instance_type'] = MU::Cloud::Google::Server.validateInstanceType(cluster["instance_type"], cluster["region"])
1147
+ cluster['instance_type'] = MU::Cloud::Google::Server.validateInstanceType(cluster["instance_type"], cluster["region"], project: cluster['project'], credentials: cluster['credentials'])
295
1148
  ok = false if cluster['instance_type'].nil?
296
1149
 
297
1150
  ok
@@ -299,19 +1152,121 @@ puts @config['credentials']
299
1152
 
300
1153
  private
301
1154
 
302
- def labelCluster
303
- labels = {}
304
- MU::MommaCat.listStandardTags.each_pair { |name, value|
305
- if !value.nil?
306
- labels[name.downcase] = value.downcase.gsub(/[^a-z0-9\-\_]/i, "_")
1155
+ def node_desc
1156
+ labels = Hash[@tags.keys.map { |k|
1157
+ [k.downcase, @tags[k].downcase.gsub(/[^-_a-z0-9]/, '-')] }
1158
+ ]
1159
+ labels["name"] = MU::Cloud::Google.nameStr(@mu_name)
1160
+ desc = {
1161
+ :machine_type => @config['instance_type'],
1162
+ :preemptible => @config['preemptible'],
1163
+ :disk_size_gb => @config['disk_size_gb'],
1164
+ :labels => labels,
1165
+ :tags => [@mu_name.downcase],
1166
+ :service_account => @service_acct.email,
1167
+ :oauth_scopes => @config['scopes']
1168
+ }
1169
+ desc[:metadata] = {}
1170
+ deploykey = @config['ssh_user']+":"+@deploy.ssh_public_key
1171
+ if @config['metadata']
1172
+ desc[:metadata] = Hash[@config['metadata'].map { |m|
1173
+ [m["key"], m["value"]]
1174
+ }]
1175
+ end
1176
+ if desc[:metadata]["ssh-keys"]
1177
+ desc[:metadata]["ssh-keys"] += "\n"+deploykey
1178
+ else
1179
+ desc[:metadata]["ssh-keys"] = deploykey
1180
+ end
1181
+ [:local_ssd_count, :min_cpu_platform, :image_type].each { |field|
1182
+ if @config[field.to_s]
1183
+ desc[field] = @config[field.to_s]
307
1184
  end
308
1185
  }
1186
+ desc
1187
+ end
1188
+
1189
+ def labelCluster
1190
+ labels = Hash[@tags.keys.map { |k|
1191
+ [k.downcase, @tags[k].downcase.gsub(/[^-_a-z0-9]/, '-')] }
1192
+ ]
309
1193
  labels["name"] = MU::Cloud::Google.nameStr(@mu_name)
310
1194
 
311
1195
  labelset = MU::Cloud::Google.container(:SetLabelsRequest).new(
312
1196
  resource_labels: labels
313
1197
  )
314
- MU::Cloud::Google.container(credentials: @config['credentials']).resource_project_zone_cluster_labels(@config["project"], @config['availability_zone'], @mu_name.downcase, labelset)
1198
+ MU::Cloud::Google.container(credentials: @config['credentials']).set_project_location_cluster_resource_labels(@cloud_id, labelset)
1199
+ end
1200
+
1201
+ @@server_config = {}
1202
+ def self.defaults(credentials = nil, az: nil)
1203
+ az ||= MU::Cloud::Google.listAZs.sample
1204
+ return nil if az.nil?
1205
+ @@server_config[credentials] ||= {}
1206
+ if @@server_config[credentials][az]
1207
+ return @@server_config[credentials][az]
1208
+ end
1209
+
1210
+ parent_arg = "projects/"+MU::Cloud::Google.defaultProject(credentials)+"/locations/"+az
1211
+
1212
+ @@server_config[credentials][az] = MU::Cloud::Google.container(credentials: credentials).get_project_location_server_config(parent_arg)
1213
+ @@server_config[credentials][az]
1214
+ end
1215
+
1216
+ def writeKubeConfig
1217
+ kube_conf = @deploy.deploy_dir+"/kubeconfig-#{@config['name']}"
1218
+ client_binding = @deploy.deploy_dir+"/k8s-client-user-admin-binding.yaml"
1219
+ @endpoint = "https://"+cloud_desc.endpoint
1220
+ @cacert = cloud_desc.master_auth.cluster_ca_certificate
1221
+ @cluster = cloud_desc.name
1222
+ @clientcert = cloud_desc.master_auth.client_certificate
1223
+ @clientkey = cloud_desc.master_auth.client_key
1224
+ if cloud_desc.master_auth.username
1225
+ @username = cloud_desc.master_auth.username
1226
+ end
1227
+ if cloud_desc.master_auth.password
1228
+ @password = cloud_desc.master_auth.password
1229
+ end
1230
+
1231
+ kube = ERB.new(File.read(MU.myRoot+"/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb"))
1232
+ File.open(kube_conf, "w"){ |k|
1233
+ k.puts kube.result(binding)
1234
+ }
1235
+
1236
+ # Take this opportunity to ensure that the 'client' service account
1237
+ # used by certificate authentication exists and has appropriate
1238
+ # privilege
1239
+ if @username and @password
1240
+ File.open(client_binding, "w"){ |k|
1241
+ k.puts <<-EOF
1242
+ kind: ClusterRoleBinding
1243
+ apiVersion: rbac.authorization.k8s.io/v1
1244
+ metadata:
1245
+ name: client-binding
1246
+ namespace: kube-system
1247
+ roleRef:
1248
+ kind: ClusterRole
1249
+ name: cluster-admin
1250
+ apiGroup: rbac.authorization.k8s.io
1251
+ subjects:
1252
+ - kind: User
1253
+ name: client
1254
+ namespace: kube-system
1255
+ EOF
1256
+ }
1257
+ bind_cmd = %Q{#{MU::Master.kubectl} create serviceaccount client --namespace=kube-system --kubeconfig "#{kube_conf}" ; #{MU::Master.kubectl} --kubeconfig "#{kube_conf}" apply -f #{client_binding}}
1258
+ MU.log bind_cmd
1259
+ system(bind_cmd)
1260
+ end
1261
+ # unset the variables we set just for ERB
1262
+ [:@endpoint, :@cacert, :@cluster, :@clientcert, :@clientkey, :@username, :@password].each { |var|
1263
+ begin
1264
+ remove_instance_variable(var)
1265
+ rescue NameError
1266
+ end
1267
+ }
1268
+
1269
+ kube_conf
315
1270
  end
316
1271
 
317
1272
  end #class