cloud-mu 2.1.0beta → 3.0.0beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -28,27 +28,19 @@ module MU
28
28
  # Google Cloud, this amounts to a single Instance in an Unmanaged
29
29
  # Instance Group.
30
30
  class Server < MU::Cloud::Server
31
- @project_id = nil
32
-
33
- attr_reader :mu_name
34
- attr_reader :config
35
- attr_reader :deploy
36
- attr_reader :cloud_id
37
- attr_reader :cloud_desc
38
- attr_reader :groomer
39
- attr_accessor :mu_windows_name
40
-
41
- # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
42
- # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::servers}
43
- def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
44
- @deploy = mommacat
45
- @config = MU::Config.manxify(kitten_cfg)
46
- @cloud_id = cloud_id
47
-
48
- if @deploy
49
- @userdata = MU::Cloud.fetchUserdata(
31
+
32
+ # 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.
33
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
34
+ def initialize(**args)
35
+ super
36
+
37
+ @userdata = if @config['userdata_script']
38
+ @config['userdata_script']
39
+ elsif @deploy and !@config['scrub_mu_isms']
40
+ MU::Cloud.fetchUserdata(
50
41
  platform: @config["platform"],
51
- cloud: "google",
42
+ cloud: "Google",
43
+ credentials: @config['credentials'],
52
44
  template_variables: {
53
45
  "deployKey" => Base64.urlsafe_encode64(@deploy.public_key),
54
46
  "deploySSHKey" => @deploy.ssh_public_key,
@@ -57,6 +49,9 @@ module MU
57
49
  "publicIP" => MU.mu_public_ip,
58
50
  "skipApplyUpdates" => @config['skipinitialupdates'],
59
51
  "windowsAdminName" => @config['windows_admin_username'],
52
+ "adminBucketName" => MU::Cloud::Google.adminBucketName(@credentials),
53
+ "chefVersion" => MU.chefVersion,
54
+ "mommaCatPort" => MU.mommaCatPort,
60
55
  "resourceName" => @config["name"],
61
56
  "resourceType" => "server",
62
57
  "platform" => @config["platform"]
@@ -64,17 +59,11 @@ module MU
64
59
  custom_append: @config['userdata_script']
65
60
  )
66
61
  end
67
-
68
- if !mu_name.nil?
69
- @mu_name = mu_name
70
- @config['mu_name'] = @mu_name
62
+ # XXX writing things into @config at runtime is a bad habit and we should stop
63
+ if !@mu_name.nil?
64
+ @config['mu_name'] = @mu_name # XXX whyyyy
71
65
  # describe
72
66
  @mu_windows_name = @deploydata['mu_windows_name'] if @mu_windows_name.nil? and @deploydata
73
- @config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
74
- if !@project_id
75
- project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
76
- @project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
77
- end
78
67
  else
79
68
  if kitten_cfg.has_key?("basis")
80
69
  @mu_name = @deploy.getResourceName(@config['name'], need_unique_string: true)
@@ -83,60 +72,88 @@ module MU
83
72
  end
84
73
  @config['mu_name'] = @mu_name
85
74
 
86
- @config['instance_secret'] = Password.random(50)
87
75
  end
88
- @config['ssh_user'] ||= "mu"
89
- @groomer = MU::Groomer.new(self)
76
+ @config['instance_secret'] ||= Password.random(50)
77
+ @config['ssh_user'] ||= "muadmin"
90
78
 
91
79
  end
92
80
 
93
- # Generate a server-class specific service account, used to grant
94
- # permission to do various API things to a node.
95
- # @param rolename [String]:
96
- # @param project [String]:
97
- # @param scopes [Array<String>]: https://developers.google.com/identity/protocols/googlescopes
98
- # XXX this should be a MU::Cloud::Google::User resource
99
- def self.createServiceAccount(rolename, deploy, project: nil, scopes: ["https://www.googleapis.com/auth/compute.readonly", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/cloud-platform"], credentials: nil)
100
- project ||= MU::Cloud::Google.defaultProject(credentials)
101
-
102
- #https://www.googleapis.com/auth/devstorage.read_only ?
103
- name = deploy.getResourceName(rolename, max_length: 30).downcase
104
-
105
- saobj = MU::Cloud::Google.iam(:CreateServiceAccountRequest).new(
106
- account_id: name.gsub(/[^a-z]/, ""), # XXX this mangling isn't required in the console, so why is it here?
107
- service_account: MU::Cloud::Google.iam(:ServiceAccount).new(
108
- display_name: rolename,
109
- # do NOT specify project_id or name, we know that much
110
- )
111
- )
112
-
113
- resp = MU::Cloud::Google.iam(credentials: credentials).create_service_account(
114
- "projects/#{project}",
115
- saobj
116
- )
81
+ # Return the date/time a machine image was created.
82
+ # @param image_id [String]: URL to a Google disk image
83
+ # @param credentials [String]
84
+ # @return [DateTime]
85
+ def self.imageTimeStamp(image_id, credentials: nil)
86
+ begin
87
+ img = fetchImage(image_id, credentials: credentials)
88
+ return DateTime.new if img.nil?
89
+ return DateTime.parse(img.creation_timestamp)
90
+ rescue ::Google::Apis::ClientError => e
91
+ end
117
92
 
118
- MU::Cloud::Google.compute(:ServiceAccount).new(
119
- email: resp.email,
120
- scopes: scopes
121
- )
93
+ return DateTime.new
122
94
  end
123
95
 
96
+ @@image_id_map = {}
97
+
124
98
  # Retrieve the cloud descriptor for this machine image, which can be
125
99
  # a whole or partial URL. Will follow deprecation notices and retrieve
126
100
  # the latest version, if applicable.
127
101
  # @param image_id [String]: URL to a Google disk image
128
- # @return [Google::Apis::ComputeBeta::Image]
102
+ # @param credentials [String]
103
+ # @return [Google::Apis::ComputeV1::Image]
129
104
  def self.fetchImage(image_id, credentials: nil)
105
+ return @@image_id_map[image_id] if @@image_id_map[image_id]
106
+
130
107
  img_proj = img_name = nil
131
- begin
132
- img_proj = image_id.gsub(/.*?\/?projects\/([^\/]+)\/.*/, '\1')
108
+ if image_id.match(/\//)
109
+ img_proj = image_id.gsub(/(?:https?:\/\/.*?\.googleapis\.com\/compute\/.*?\/)?.*?\/?(?:projects\/)?([^\/]+)\/.*/, '\1')
133
110
  img_name = image_id.gsub(/.*?([^\/]+)$/, '\1')
111
+ else
112
+ img_name = image_id
113
+ end
114
+
115
+ begin
116
+ @@image_id_map[image_id] = MU::Cloud::Google.compute(credentials: credentials).get_image_from_family(img_proj, img_name)
117
+ return @@image_id_map[image_id]
118
+ rescue ::Google::Apis::ClientError
119
+ # This is fine- we don't know that what we asked for is really an
120
+ # image family name, instead of just an image.
121
+ end
122
+
123
+ begin
134
124
  img = MU::Cloud::Google.compute(credentials: credentials).get_image(img_proj, img_name)
135
125
  if !img.deprecated.nil? and !img.deprecated.replacement.nil?
136
126
  image_id = img.deprecated.replacement
127
+ img_proj = image_id.gsub(/(?:https?:\/\/.*?\.googleapis\.com\/compute\/.*?\/)?.*?\/?(?:projects\/)?([^\/]+)\/.*/, '\1')
128
+ img_name = image_id.gsub(/.*?([^\/]+)$/, '\1')
137
129
  end
130
+ rescue ::Google::Apis::ClientError => e
131
+ # SOME people *cough* don't use deprecation or image family names
132
+ # and just spew out images with a version appended to the name, so
133
+ # let's try some crude semantic versioning list.
134
+ if e.message.match(/^notFound: /) and img_name.match(/-[^\-]+$/)
135
+ list = MU::Cloud::Google.compute(credentials: credentials).list_images(img_proj, filter: "name eq #{img_name.sub(/-[^\-]+$/, '')}-.*")
136
+ if list and list.items
137
+ latest = nil
138
+ list.items.each { |candidate|
139
+ created = DateTime.parse(candidate.creation_timestamp)
140
+ if latest.nil? or created > latest
141
+ latest = created
142
+ img = candidate
143
+ end
144
+ }
145
+ if latest
146
+ MU.log "Mapped #{image_id} to #{img.name} with semantic versioning guesswork", MU::WARN
147
+ @@image_id_map[image_id] = img
148
+ return @@image_id_map[image_id]
149
+ end
150
+ end
151
+ end
152
+ raise e # if our little semantic versioning party trick failed
138
153
  end while !img.deprecated.nil? and img.deprecated.state == "DEPRECATED" and !img.deprecated.replacement.nil?
139
- MU::Cloud::Google.compute(credentials: credentials).get_image(img_proj, img_name)
154
+ final = MU::Cloud::Google.compute(credentials: credentials).get_image(img_proj, img_name)
155
+ @@image_id_map[image_id] = final
156
+ @@image_id_map[image_id]
140
157
  end
141
158
 
142
159
  # Generator for disk configuration parameters for a Compute instance
@@ -230,7 +247,7 @@ next if !create
230
247
  end
231
248
  subnet = vpc.getSubnet(name: subnet_cfg['subnet_name'], cloud_id: subnet_cfg['subnet_id'])
232
249
  if subnet.nil?
233
- raise MuError, "Couldn't find subnet details while configuring Server #{config['name']} (VPC: #{vpc.mu_name})"
250
+ raise MuError, "Couldn't find subnet details for #{subnet_cfg['subnet_name'] || subnet_cfg['subnet_id']} while configuring Server #{config['name']} (VPC: #{vpc.mu_name})"
234
251
  end
235
252
  base_iface_obj = {
236
253
  :network => vpc.url,
@@ -249,15 +266,21 @@ next if !create
249
266
 
250
267
  # Called automatically by {MU::Deploy#createResources}
251
268
  def create
252
- @project_id = MU::Cloud::Google.projectLookup(@config['project_id'], @deploy).cloudobj.cloud_id
269
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloud_id
270
+
271
+ sa = MU::Config::Ref.get(@config['service_account'])
253
272
 
254
- service_acct = MU::Cloud::Google::Server.createServiceAccount(
255
- @mu_name.downcase,
256
- @deploy,
257
- project: @project_id,
258
- credentials: @config['credentials']
273
+ if !sa or !sa.kitten or !sa.kitten.cloud_desc
274
+ raise MuError, "Failed to get service account cloud id from #{@config['service_account'].to_s}"
275
+ end
276
+
277
+ @service_acct = MU::Cloud::Google.compute(:ServiceAccount).new(
278
+ email: sa.kitten.cloud_desc.email,
279
+ scopes: @config['scopes']
259
280
  )
260
- MU::Cloud::Google.grantDeploySecretAccess(service_acct.email, credentials: @config['credentials'])
281
+ if !@config['scrub_mu_isms']
282
+ MU::Cloud::Google.grantDeploySecretAccess(@service_acct.email, credentials: @config['credentials'])
283
+ end
261
284
 
262
285
  begin
263
286
  disks = MU::Cloud::Google::Server.diskConfig(@config, credentials: @config['credentials'])
@@ -273,25 +296,36 @@ next if !create
273
296
  :name => MU::Cloud::Google.nameStr(@mu_name),
274
297
  :can_ip_forward => !@config['src_dst_check'],
275
298
  :description => @deploy.deploy_id,
276
- :service_accounts => [service_acct],
299
+ :service_accounts => [@service_acct],
277
300
  :network_interfaces => interfaces,
278
301
  :machine_type => "zones/"+@config['availability_zone']+"/machineTypes/"+@config['size'],
279
- :metadata => {
280
- :items => [
281
- {
282
- :key => "ssh-keys",
283
- :value => @config['ssh_user']+":"+@deploy.ssh_public_key
284
- },
285
- {
286
- :key => "startup-script",
287
- :value => @userdata
288
- }
289
- ]
290
- },
291
302
  :tags => MU::Cloud::Google.compute(:Tags).new(items: [MU::Cloud::Google.nameStr(@mu_name)])
292
303
  }
293
304
  desc[:disks] = disks if disks.size > 0
294
305
 
306
+ metadata = {}
307
+ if @config['metadata']
308
+ metadata = Hash[@config['metadata'].map { |m|
309
+ [m["key"], m["value"]]
310
+ }]
311
+ end
312
+ metadata["startup-script"] = @userdata if @userdata and !@userdata.empty?
313
+
314
+ deploykey = @config['ssh_user']+":"+@deploy.ssh_public_key
315
+ if metadata["ssh-keys"]
316
+ metadata["ssh-keys"] += "\n"+deploykey
317
+ else
318
+ metadata["ssh-keys"] = deploykey
319
+ end
320
+ desc[:metadata] = MU::Cloud::Google.compute(:Metadata).new(
321
+ :items => metadata.keys.map { |k|
322
+ MU::Cloud::Google.compute(:Metadata)::Item.new(
323
+ key: k,
324
+ value: metadata[k]
325
+ )
326
+ }
327
+ )
328
+
295
329
  # Tags in GCP means something other than what we think of;
296
330
  # labels are the thing you think you mean
297
331
  desc[:labels] = {}
@@ -302,10 +336,16 @@ next if !create
302
336
  }
303
337
  desc[:labels]["name"] = @mu_name.downcase
304
338
 
339
+ if @config['network_tags'] and @config['network_tags'].size > 0
340
+ desc[:tags] = U::Cloud::Google.compute(:Tags).new(
341
+ items: @config['network_tags']
342
+ )
343
+ end
305
344
 
306
345
  instanceobj = MU::Cloud::Google.compute(:Instance).new(desc)
307
346
 
308
- MU.log "Creating instance #{@mu_name}"
347
+ MU.log "Creating instance #{@mu_name}", MU::NOTICE, details: instanceobj
348
+
309
349
  begin
310
350
  instance = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_instance(
311
351
  @project_id,
@@ -351,7 +391,7 @@ next if !create
351
391
  parent_thread_id = Thread.current.object_id
352
392
  Thread.new {
353
393
  MU.dupGlobals(parent_thread_id)
354
- MU::Cloud::Google::Server.cleanup(noop: false, ignoremaster: false, flags: { "skipsnapshots" => true } )
394
+ MU::Cloud::Google::Server.cleanup(noop: false, ignoremaster: false, flags: { "skipsnapshots" => true }, region: @config['region'] )
355
395
  }
356
396
  end
357
397
  end
@@ -467,7 +507,7 @@ next if !create
467
507
  return false if !MU::MommaCat.lock(@cloud_id+"-orchestrate", true)
468
508
  return false if !MU::MommaCat.lock(@cloud_id+"-groom", true)
469
509
 
470
- # MU::MommaCat.createStandardTags(@cloud_id, region: @config['region'])
510
+ # MU::Cloud::AWS.createStandardTags(@cloud_id, region: @config['region'])
471
511
  # MU::MommaCat.createTag(@cloud_id, "Name", node, region: @config['region'])
472
512
  #
473
513
  # if @config['optional_tags']
@@ -586,80 +626,82 @@ next if !create
586
626
  end #postBoot
587
627
 
588
628
  # Locate an existing instance or instances and return an array containing matching AWS resource descriptors for those that match.
589
- # @param cloud_id [String]: The cloud provider's identifier for this resource.
590
- # @param region [String]: The cloud provider region
591
- # @param tag_key [String]: A tag key to search.
592
- # @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
593
- # @param ip [String]: An IP address associated with the instance
594
- # @param flags [Hash]: Optional flags
595
629
  # @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching instances
596
- def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, ip: nil, flags: {}, credentials: nil)
597
- # XXX put that 'ip' value into flags
598
- instance = nil
599
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
600
- if !region.nil? and MU::Cloud::Google.listRegions.include?(region)
601
- regions = [region]
630
+ def self.find(**args)
631
+ args[:project] ||= args[:habitat]
632
+ args[:project] ||= MU::Cloud::Google.defaultProject(args[:credentials])
633
+ if !args[:region].nil? and MU::Cloud::Google.listRegions.include?(args[:region])
634
+ regions = [args[:region]]
602
635
  else
603
636
  regions = MU::Cloud::Google.listRegions
604
637
  end
605
638
 
606
- found_instances = {}
639
+ found = {}
607
640
  search_semaphore = Mutex.new
608
641
  search_threads = []
609
642
 
610
643
  # If we got an instance id, go get it
611
- if !cloud_id.nil? and !cloud_id.empty?
612
- parent_thread_id = Thread.current.object_id
613
- regions.each { |region|
614
- search_threads << Thread.new {
615
- Thread.abort_on_exception = false
616
- MU.dupGlobals(parent_thread_id)
617
- MU.log "Hunting for instance with cloud id '#{cloud_id}' in #{region}", MU::DEBUG
618
- MU::Cloud::Google.listAZs(region).each { |az|
619
- resp = nil
620
- begin
621
- resp = MU::Cloud::Google.compute(credentials: credentials).get_instance(
622
- flags["project"],
644
+ parent_thread_id = Thread.current.object_id
645
+ regions.each { |r|
646
+ search_threads << Thread.new(r) { |region|
647
+ Thread.abort_on_exception = false
648
+ MU.dupGlobals(parent_thread_id)
649
+ MU.log "Hunting for instance with cloud id '#{args[:cloud_id]}' in #{region}", MU::DEBUG
650
+ MU::Cloud::Google.listAZs(region).each { |az|
651
+ begin
652
+ if !args[:cloud_id].nil? and !args[:cloud_id].empty?
653
+ resp = MU::Cloud::Google.compute(credentials: args[:credentials]).get_instance(
654
+ args[:project],
623
655
  az,
624
- cloud_id
656
+ args[:cloud_id]
657
+ )
658
+ search_semaphore.synchronize {
659
+ found[args[:cloud_id]] = resp if !resp.nil?
660
+ }
661
+ else
662
+ resp = MU::Cloud::Google.compute(credentials: args[:credentials]).list_instances(
663
+ args[:project],
664
+ az
625
665
  )
626
- rescue ::OpenSSL::SSL::SSLError => e
627
- MU.log "Got #{e.message} looking for instance #{cloud_id} in project #{flags["project"]} (#{az}). Usually this means we've tried to query a non-functional region.", MU::DEBUG
628
- rescue ::Google::Apis::ClientError => e
629
- raise e if !e.message.match(/^notFound: /)
666
+ if resp and resp.items
667
+ resp.items.each { |instance|
668
+ search_semaphore.synchronize {
669
+ found[instance.name] = instance
670
+ }
671
+ }
672
+ end
630
673
  end
631
- found_instances[cloud_id] = resp if !resp.nil?
632
- }
674
+ rescue ::OpenSSL::SSL::SSLError => e
675
+ MU.log "Got #{e.message} looking for instance #{args[:cloud_id]} in project #{args[:project]} (#{az}). Usually this means we've tried to query a non-functional region.", MU::DEBUG
676
+ rescue ::Google::Apis::ClientError => e
677
+ raise e if !e.message.match(/^(?:notFound|forbidden): /)
678
+ end
633
679
  }
634
680
  }
635
- done_threads = []
636
- begin
637
- search_threads.each { |t|
638
- joined = t.join(2)
639
- done_threads << joined if !joined.nil?
640
- }
641
- end while found_instances.size < 1 and done_threads.size != search_threads.size
642
- end
643
-
644
- if found_instances.size > 0
645
- return found_instances
646
- end
647
-
681
+ }
682
+ done_threads = []
683
+ begin
684
+ search_threads.reject! { |t| t.nil? }
685
+ search_threads.each { |t|
686
+ joined = t.join(2)
687
+ done_threads << joined if !joined.nil?
688
+ }
689
+ end while found.size < 1 and done_threads.size != search_threads.size
648
690
  # Ok, well, let's try looking it up by IP then
649
- if instance.nil? and !ip.nil?
650
- MU.log "Hunting for instance by IP '#{ip}'", MU::DEBUG
651
- end
691
+ # if instance.nil? and !args[:ip].nil?
692
+ # MU.log "Hunting for instance by IP '#{args[:ip]}'", MU::DEBUG
693
+ # end
652
694
 
653
- if !instance.nil?
654
- return {instance.name => instance} if !instance.nil?
655
- end
695
+ # if !instance.nil?
696
+ # return {instance.name => instance} if !instance.nil?
697
+ # end
656
698
 
657
699
  # Fine, let's try it by tag.
658
- if !tag_value.nil?
659
- MU.log "Searching for instance by tag '#{tag_key}=#{tag_value}'", MU::DEBUG
660
- end
700
+ # if !args[:tag_value].nil?
701
+ # MU.log "Searching for instance by tag '#{args[:tag_key]}=#{args[:tag_value]}'", MU::DEBUG
702
+ # end
661
703
 
662
- return found_instances
704
+ return found
663
705
  end
664
706
 
665
707
  # Return a description of this resource appropriate for deployment
@@ -724,7 +766,7 @@ next if !create
724
766
 
725
767
  # Called automatically by {MU::Deploy#createResources}
726
768
  def groom
727
- @project_id = MU::Cloud::Google.projectLookup(@config['project_id'], @deploy).cloudobj.cloud_id
769
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloud_id
728
770
 
729
771
  MU::MommaCat.lock(@cloud_id+"-groom")
730
772
 
@@ -801,12 +843,12 @@ next if !create
801
843
  instance_id: @cloud_id,
802
844
  region: @config['region'],
803
845
  storage: @config['storage'],
804
- family: ("mu-"+@config['platform']+"-"+MU.environment).downcase,
805
846
  project: @project_id,
806
847
  exclude_storage: img_cfg['image_exclude_storage'],
807
848
  make_public: img_cfg['public'],
808
849
  tags: @config['tags'],
809
850
  zone: @config['availability_zone'],
851
+ family: @config['family'],
810
852
  credentials: @config['credentials']
811
853
  )
812
854
  @deploy.notify("images", @config['name'], {"image_id" => image_id})
@@ -835,7 +877,7 @@ next if !create
835
877
  # @param region [String]: The cloud provider region
836
878
  # @param tags [Array<String>]: Extra/override tags to apply to the image.
837
879
  # @return [String]: The cloud provider identifier of the new machine image.
838
- def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, project: nil, make_public: false, tags: [], region: nil, family: "mu", zone: MU::Cloud::Google.listAZs.sample, credentials: nil)
880
+ def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, project: nil, make_public: false, tags: [], region: nil, family: nil, zone: MU::Cloud::Google.listAZs.sample, credentials: nil)
839
881
  project ||= MU::Cloud::Google.defaultProject(credentials)
840
882
  instance = MU::Cloud::Server.find(cloud_id: instance_id, region: region)
841
883
  if instance.nil?
@@ -891,46 +933,21 @@ next if !create
891
933
  end
892
934
 
893
935
  labels["name"] = instance_id.downcase
894
- imageobj = MU::Cloud::Google.compute(:Image).new(
895
- name: name,
896
- source_disk: bootdisk,
897
- description: "Mu image created from #{name}",
898
- labels: labels,
899
- family: family
900
- )
936
+ image_desc = {
937
+ :name => name,
938
+ :source_disk => bootdisk,
939
+ :description => "Mu image created from #{name}",
940
+ :labels => labels
941
+ }
942
+ image_desc[:family] = family if family
901
943
 
902
944
  newimage = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_image(
903
945
  project,
904
- imageobj
946
+ MU::Cloud::Google.compute(:Image).new(image_desc)
905
947
  )
906
948
  newimage.name
907
949
  end
908
950
 
909
- # def cloud_desc
910
- # max_retries = 5
911
- # retries = 0
912
- # if !@cloud_id.nil?
913
- # begin
914
- # return MU::Cloud::Google.compute(credentials: @config['credentials']).get_instance(
915
- # @project_id,
916
- # @config['availability_zone'],
917
- # @cloud_id
918
- # )
919
- # rescue ::Google::Apis::ClientError => e
920
- # if e.message.match(/^notFound: /)
921
- # return nil
922
- # else
923
- # raise e
924
- # end
925
- # end
926
- # end
927
- # nil
928
- # end
929
-
930
- def cloud_desc
931
- MU::Cloud::Google::Server.find(cloud_id: @cloud_id, credentials: @config['credentials']).values.first
932
- end
933
-
934
951
  # Return the IP address that we, the Mu server, should be using to access
935
952
  # this host via the network. Note that this does not factor in SSH
936
953
  # bastion hosts that may be in the path, see getSSHConfig if that's what
@@ -974,7 +991,8 @@ next if !create
974
991
  # @param dev [String]: Device name to use when attaching to instance
975
992
  # @param size [String]: Size (in gb) of the new volume
976
993
  # @param type [String]: Cloud storage type of the volume, if applicable
977
- def addVolume(dev, size, type: "pd-standard")
994
+ # @param delete_on_termination [Boolean]: Value of delete_on_termination flag to set
995
+ def addVolume(dev, size, type: "pd-standard", delete_on_termination: false)
978
996
  devname = dev.gsub(/.*?\/([^\/]+)$/, '\1')
979
997
  resname = MU::Cloud::Google.nameStr(@mu_name+"-"+devname)
980
998
  MU.log "Creating disk #{resname}"
@@ -1007,11 +1025,13 @@ next if !create
1007
1025
  end
1008
1026
 
1009
1027
  attachobj = MU::Cloud::Google.compute(:AttachedDisk).new(
1010
- auto_delete: true,
1011
1028
  device_name: devname,
1012
1029
  source: newdisk.self_link,
1013
- type: "PERSISTENT"
1030
+ type: "PERSISTENT",
1031
+ auto_delete: delete_on_termination
1014
1032
  )
1033
+
1034
+ MU.log "Attaching disk #{resname} to #{@cloud_id} at #{devname}"
1015
1035
  attachment = MU::Cloud::Google.compute(credentials: @config['credentials']).attach_disk(
1016
1036
  @project_id,
1017
1037
  @config['availability_zone'],
@@ -1028,6 +1048,113 @@ next if !create
1028
1048
  true
1029
1049
  end
1030
1050
 
1051
+ # Reverse-map our cloud description into a runnable config hash.
1052
+ # We assume that any values we have in +@config+ are placeholders, and
1053
+ # calculate our own accordingly based on what's live in the cloud.
1054
+ def toKitten(rootparent: nil, billing: nil, habitats: nil)
1055
+ bok = {
1056
+ "cloud" => "Google",
1057
+ "credentials" => @config['credentials'],
1058
+ "cloud_id" => @cloud_id,
1059
+ "project" => @project_id
1060
+ }
1061
+ if !cloud_desc
1062
+ MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
1063
+ return nil
1064
+ end
1065
+ bok['name'] = cloud_desc.name
1066
+
1067
+ # XXX we can have multiple network interfaces, and often do; need
1068
+ # language to account for this
1069
+ iface = cloud_desc.network_interfaces.first
1070
+ iface.network.match(/(?:^|\/)projects\/(.*?)\/.*?\/networks\/([^\/]+)(?:$|\/)/)
1071
+ vpc_proj = Regexp.last_match[1]
1072
+ vpc_id = Regexp.last_match[2]
1073
+
1074
+ bok['vpc'] = MU::Config::Ref.get(
1075
+ id: vpc_id,
1076
+ cloud: "Google",
1077
+ habitat: MU::Config::Ref.get(
1078
+ id: vpc_proj,
1079
+ cloud: "Google",
1080
+ credentials: @credentials,
1081
+ type: "habitats"
1082
+ ),
1083
+ credentials: @credentials,
1084
+ type: "vpcs",
1085
+ subnet_id: iface.subnetwork.sub(/.*?\/([^\/]+)$/, '\1')
1086
+ )
1087
+
1088
+ cloud_desc.disks.each { |disk|
1089
+ next if !disk.source
1090
+ disk.source.match(/\/projects\/([^\/]+)\/zones\/([^\/]+)\/disks\/(.*)/)
1091
+ proj = Regexp.last_match[1]
1092
+ az = Regexp.last_match[2]
1093
+ name = Regexp.last_match[3]
1094
+ begin
1095
+ disk_desc = MU::Cloud::Google.compute(credentials: @credentials).get_disk(proj, az, name)
1096
+ if disk_desc.source_image and disk.boot
1097
+ bok['image_id'] ||= disk_desc.source_image.sub(/^https:\/\/www\.googleapis\.com\/compute\/[^\/]+\//, '')
1098
+ else
1099
+ bok['storage'] ||= []
1100
+ storage_blob = {
1101
+ "size" => disk_desc.size_gb,
1102
+ "device" => "/dev/xvd"+(disk.index+97).chr.downcase
1103
+ }
1104
+ bok['storage'] << storage_blob
1105
+ end
1106
+ rescue ::Google::Apis::ClientError => e
1107
+ MU.log "Failed to retrieve disk #{name} attached to server #{@cloud_id} in #{proj}/#{az}", MU::WARN, details: e.message
1108
+ next
1109
+ end
1110
+
1111
+ }
1112
+
1113
+ if cloud_desc.labels
1114
+ bok['tags'] = cloud_desc.labels.keys.map { |k| { "key" => k, "value" => cloud_desc.labels[k] } }
1115
+ end
1116
+ if cloud_desc.tags and cloud_desc.tags.items and cloud_desc.tags.items.size > 0
1117
+ bok['network_tags'] = cloud_desc.tags.items
1118
+ end
1119
+ bok['src_dst_check'] = !cloud_desc.can_ip_forward
1120
+ bok['size'] = cloud_desc.machine_type.sub(/.*?\/([^\/]+)$/, '\1')
1121
+ bok['project'] = @project_id
1122
+ if cloud_desc.service_accounts
1123
+ bok['scopes'] = cloud_desc.service_accounts.map { |sa| sa.scopes }.flatten.uniq
1124
+ end
1125
+ if cloud_desc.metadata and cloud_desc.metadata.items
1126
+ bok['metadata'] = cloud_desc.metadata.items.map { |m| MU.structToHash(m) }
1127
+ end
1128
+
1129
+ # Skip nodes that are just members of GKE clusters
1130
+ if bok['name'].match(/^gke-.*?-[a-f0-9]+-[a-z0-9]+$/) and
1131
+ bok['image_id'].match(/(:?^|\/)projects\/gke-node-images\//)
1132
+ found_gke_tag = false
1133
+ bok['network_tags'].each { |tag|
1134
+ if tag.match(/^gke-/)
1135
+ found_gke_tag = true
1136
+ break
1137
+ end
1138
+ }
1139
+ if found_gke_tag
1140
+ MU.log "Server #{bok['name']} appears to belong to a ContainerCluster, skipping adoption", MU::DEBUG
1141
+ return nil
1142
+ end
1143
+ end
1144
+
1145
+ if bok['metadata']
1146
+ bok['metadata'].each { |item|
1147
+ if item[:key] == "created-by" and item[:value].match(/\/instanceGroupManagers\//)
1148
+ MU.log "Server #{bok['name']} appears to belong to a ServerPool, skipping adoption", MU::DEBUG, details: item[:value]
1149
+ return nil
1150
+ end
1151
+ }
1152
+ end
1153
+
1154
+
1155
+ bok
1156
+ end
1157
+
1031
1158
  # Does this resource type exist as a global (cloud-wide) artifact, or
1032
1159
  # is it localized to a region/zone?
1033
1160
  # @return [Boolean]
@@ -1048,6 +1175,7 @@ next if !create
1048
1175
  # @return [void]
1049
1176
  def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
1050
1177
  flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
1178
+ return if !MU::Cloud::Google::Habitat.isLive?(flags["project"], credentials)
1051
1179
  skipsnapshots = flags["skipsnapshots"]
1052
1180
  onlycloud = flags["onlycloud"]
1053
1181
  # XXX make damn sure MU.deploy_id is set
@@ -1105,51 +1233,140 @@ next if !create
1105
1233
  def self.schema(config)
1106
1234
  toplevel_required = []
1107
1235
  schema = {
1108
- "image_id" => {
1236
+ "roles" => MU::Cloud::Google::User.schema(config)[1]["roles"],
1237
+ "create_image" => {
1238
+ "properties" => {
1239
+ "family" => {
1240
+ "type" => "string",
1241
+ "description" => "Add a GCP image +family+ string to the created image(s)"
1242
+ }
1243
+ }
1244
+ },
1245
+ "availability_zone" => {
1246
+ "type" => "string",
1247
+ "description" => "Target this instance to a specific Availability Zone"
1248
+ },
1249
+ "ssh_user" => {
1109
1250
  "type" => "string",
1110
- "description" => "The Google Cloud Platform Image on which to base this instance. Will use the default appropriate for the platform, if not specified."
1251
+ "description" => "Account to use when connecting via ssh. Google Cloud images don't come with predefined remote access users, and some don't work with our usual default of +root+, so we recommend using some other (non-root) username.",
1252
+ "default" => "muadmin"
1253
+ },
1254
+ "network_tags" => {
1255
+ "type" => "array",
1256
+ "items" => {
1257
+ "type" => "string",
1258
+ "description" => "Add a network tag to this host, which can be used to selectively apply routes or firewall rules."
1259
+ }
1260
+ },
1261
+ "service_account" => MU::Config::Ref.schema(
1262
+ type: "users",
1263
+ desc: "An existing service account to use instead of the default one generated by Mu during the deployment process."
1264
+ ),
1265
+ "metadata" => {
1266
+ "type" => "array",
1267
+ "items" => {
1268
+ "type" => "object",
1269
+ "description" => "Custom key-value pairs to be added to the metadata of Google Cloud virtual machines",
1270
+ "required" => ["key", "value"],
1271
+ "properties" => {
1272
+ "key" => {
1273
+ "type" => "string"
1274
+ },
1275
+ "value" => {
1276
+ "type" => "string"
1277
+ }
1278
+ }
1279
+ }
1111
1280
  },
1112
1281
  "routes" => {
1113
1282
  "type" => "array",
1114
1283
  "items" => MU::Config::VPC.routeschema
1284
+ },
1285
+ "scopes" => {
1286
+ "type" => "array",
1287
+ "items" => {
1288
+ "type" => "string",
1289
+ "description" => "API scopes to make available to this resource's service account."
1290
+ },
1291
+ "default" => ["https://www.googleapis.com/auth/compute.readonly", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/devstorage.read_only"]
1115
1292
  }
1116
1293
  }
1117
1294
  [toplevel_required, schema]
1118
1295
  end
1119
1296
 
1297
+ @@instance_type_cache = {}
1298
+
1120
1299
  # Confirm that the given instance size is valid for the given region.
1121
1300
  # If someone accidentally specified an equivalent size from some other cloud provider, return something that makes sense. If nothing makes sense, return nil.
1122
1301
  # @param size [String]: Instance type to check
1123
1302
  # @param region [String]: Region to check against
1124
1303
  # @return [String,nil]
1125
- def self.validateInstanceType(size, region)
1126
- types = (MU::Cloud::Google.listInstanceTypes(region))[region]
1127
- if types and (size.nil? or !types.has_key?(size))
1304
+ def self.validateInstanceType(size, region, project: nil, credentials: nil)
1305
+ size = size.dup.to_s
1306
+ if @@instance_type_cache[project] and
1307
+ @@instance_type_cache[project][region] and
1308
+ @@instance_type_cache[project][region][size]
1309
+ return @@instance_type_cache[project][region][size]
1310
+ end
1311
+
1312
+ if size.match(/\/?custom-(\d+)-(\d+)(?:-ext)?$/)
1313
+ cpus = Regexp.last_match[1].to_i
1314
+ mem = Regexp.last_match[2].to_i
1315
+ ok = true
1316
+ if cpus < 1 or cpus > 32 or (cpus % 2 != 0 and cpus != 1)
1317
+ MU.log "Custom instance type #{size} illegal: CPU count must be 1 or an even number between 2 and 32", MU::ERR
1318
+ ok = false
1319
+ end
1320
+ if (mem % 256) != 0
1321
+ MU.log "Custom instance type #{size} illegal: Memory must be a multiple of 256 (MB)", MU::ERR
1322
+ ok = false
1323
+ end
1324
+ if ok
1325
+ return "custom-#{cpus.to_s}-#{mem.to_s}"
1326
+ else
1327
+ return nil
1328
+ end
1329
+ end
1330
+
1331
+ @@instance_type_cache[project] ||= {}
1332
+ @@instance_type_cache[project][region] ||= {}
1333
+ types = (MU::Cloud::Google.listInstanceTypes(region, project: project, credentials: credentials))[project][region]
1334
+ realsize = size.dup
1335
+
1336
+ if types and (realsize.nil? or !types.has_key?(realsize))
1128
1337
  # See if it's a type we can approximate from one of the other clouds
1129
- atypes = (MU::Cloud::AWS.listInstanceTypes)[MU::Cloud::AWS.myRegion]
1130
1338
  foundmatch = false
1131
- if atypes and atypes.size > 0 and atypes.has_key?(size)
1132
- vcpu = atypes[size]["vcpu"]
1133
- mem = atypes[size]["memory"]
1134
- ecu = atypes[size]["ecu"]
1135
- types.keys.sort.reverse.each { |type|
1136
- features = types[type]
1137
- next if ecu == "Variable" and ecu != features["ecu"]
1138
- next if features["vcpu"] != vcpu
1139
- if (features["memory"] - mem.to_f).abs < 0.10*mem
1140
- foundmatch = true
1141
- MU.log "You specified an Amazon instance type '#{size}.' Approximating with Google Compute type '#{type}.'", MU::WARN
1142
- size = type
1143
- break
1144
- end
1145
- }
1146
- end
1339
+ MU::Cloud.availableClouds.each { |cloud|
1340
+ next if cloud == "Google"
1341
+ cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1342
+ foreign_types = (cloudbase.listInstanceTypes)[cloudbase.myRegion]
1343
+ if foreign_types and foreign_types.size > 0 and foreign_types.has_key?(size)
1344
+ vcpu = foreign_types[size]["vcpu"]
1345
+ mem = foreign_types[size]["memory"]
1346
+ ecu = foreign_types[size]["ecu"]
1347
+ types.keys.sort.reverse.each { |type|
1348
+ features = types[type]
1349
+ next if ecu == "Variable" and ecu != features["ecu"]
1350
+ next if features["vcpu"] != vcpu
1351
+ if (features["memory"] - mem.to_f).abs < 0.10*mem
1352
+ foundmatch = true
1353
+ MU.log "You specified #{cloud} instance type '#{realsize}.' Approximating with Google Compute type '#{type}.'", MU::WARN
1354
+ realsize = type
1355
+ break
1356
+ end
1357
+ }
1358
+ end
1359
+ break if foundmatch
1360
+ }
1361
+
1147
1362
  if !foundmatch
1148
- MU.log "Invalid size '#{size}' for Google Compute instance in #{region}. Supported types:", MU::ERR, details: types.keys.sort.join(", ")
1363
+ MU.log "Invalid size '#{realsize}' for Google Compute instance in #{region} (checked project #{project}). Supported types:", MU::ERR, details: types.keys.sort.join(", ")
1364
+ @@instance_type_cache[project][region][size] = nil
1149
1365
  return nil
1150
1366
  end
1151
1367
  end
1152
- size
1368
+ @@instance_type_cache[project][region][size] = realsize
1369
+ @@instance_type_cache[project][region][size]
1153
1370
  end
1154
1371
 
1155
1372
 
@@ -1160,17 +1377,57 @@ next if !create
1160
1377
  def self.validateConfig(server, configurator)
1161
1378
  ok = true
1162
1379
 
1163
- server['size'] = validateInstanceType(server["size"], server["region"])
1164
- ok = false if server['size'].nil?
1380
+ server['project'] ||= MU::Cloud::Google.defaultProject(server['credentials'])
1381
+ size = validateInstanceType(server["size"], server["region"], project: server['project'], credentials: server['credentials'])
1382
+
1383
+ if size.nil?
1384
+ MU.log "Failed to verify instance size #{server["size"]} for Server #{server['name']}", MU::WARN
1385
+ else
1386
+ server["size"] = size
1387
+ end
1165
1388
 
1166
1389
  # If we're not targeting an availability zone, pick one randomly
1167
1390
  if !server['availability_zone']
1168
1391
  server['availability_zone'] = MU::Cloud::Google.listAZs(server['region']).sample
1169
1392
  end
1170
1393
 
1394
+ if server['service_account']
1395
+ server['service_account']['cloud'] = "Google"
1396
+ server['service_account']['habitat'] ||= server['project']
1397
+ found = MU::Config::Ref.get(server['service_account'])
1398
+ if found.id and !found.kitten
1399
+ MU.log "GKE server #{server['name']} failed to locate service account #{server['service_account']} in project #{server['project']}", MU::ERR
1400
+ ok = false
1401
+ end
1402
+ else
1403
+ user = {
1404
+ "name" => server['name'],
1405
+ "cloud" => "Google",
1406
+ "project" => server["project"],
1407
+ "credentials" => server["credentials"],
1408
+ "type" => "service"
1409
+ }
1410
+ if server['roles']
1411
+ user['roles'] = server['roles'].dup
1412
+ end
1413
+ configurator.insertKitten(user, "users", true)
1414
+ server['dependencies'] ||= []
1415
+ server['service_account'] = MU::Config::Ref.get(
1416
+ type: "users",
1417
+ cloud: "Google",
1418
+ name: server["name"],
1419
+ project: server["project"],
1420
+ credentials: server["credentials"]
1421
+ )
1422
+ server['dependencies'] << {
1423
+ "type" => "user",
1424
+ "name" => server["name"]
1425
+ }
1426
+ end
1427
+
1171
1428
  subnets = nil
1172
1429
  if !server['vpc']
1173
- vpcs = MU::Cloud::Google::VPC.find
1430
+ vpcs = MU::Cloud::Google::VPC.find(credentials: server['credentials'])
1174
1431
  if vpcs["default"]
1175
1432
  server["vpc"] ||= {}
1176
1433
  server["vpc"]["vpc_id"] = vpcs["default"].self_link
@@ -1202,8 +1459,9 @@ next if !create
1202
1459
  end
1203
1460
 
1204
1461
  if server['image_id'].nil?
1205
- if MU::Config.google_images.has_key?(server['platform'])
1206
- server['image_id'] = configurator.getTail("server"+server['name']+"Image", value: MU::Config.google_images[server['platform']], prettyname: "server"+server['name']+"Image", cloudtype: "Google::::Apis::ComputeBeta::Image")
1462
+ img_id = MU::Cloud.getStockImage("Google", platform: server['platform'])
1463
+ if img_id
1464
+ server['image_id'] = configurator.getTail("server"+server['name']+"Image", value: img_id, prettyname: "server"+server['name']+"Image", cloudtype: "Google::Apis::ComputeV1::Image")
1207
1465
  else
1208
1466
  MU.log "No image specified for #{server['name']} and no default available for platform #{server['platform']}", MU::ERR, details: server
1209
1467
  ok = false
@@ -1214,7 +1472,6 @@ next if !create
1214
1472
  begin
1215
1473
  real_image = MU::Cloud::Google::Server.fetchImage(server['image_id'].to_s, credentials: server['credentials'])
1216
1474
  rescue ::Google::Apis::ClientError => e
1217
- MU.log e.inspect, MU::WARN
1218
1475
  end
1219
1476
 
1220
1477
  if real_image.nil?
@@ -1226,31 +1483,38 @@ next if !create
1226
1483
  img_project = Regexp.last_match[1]
1227
1484
  img_name = Regexp.last_match[2]
1228
1485
  begin
1486
+ img = MU::Cloud::Google.compute(credentials: server['credentials']).get_image(img_project, img_name)
1229
1487
  snaps = MU::Cloud::Google.compute(credentials: server['credentials']).list_snapshots(
1230
1488
  img_project,
1231
1489
  filter: "name eq #{img_name}-.*"
1232
1490
  )
1233
1491
  server['storage'] ||= []
1234
1492
  used_devs = server['storage'].map { |disk| disk['device'].gsub(/.*?\//, "") }
1235
- snaps.items.each { |snap|
1236
- next if !snap.labels.is_a?(Hash) or !snap.labels["mu-device-name"] or snap.labels["mu-parent-image"] != img_name
1237
- devname = snap.labels["mu-device-name"]
1238
-
1239
- if used_devs.include?(devname)
1240
- MU.log "Device name #{devname} already declared in server #{server['name']} (snapshot #{snap.name} wants the name)", MU::ERR
1241
- ok = false
1242
- end
1243
- server['storage'] << {
1244
- "snapshot_id" => snap.self_link,
1245
- "size" => snap.disk_size_gb,
1246
- "delete_on_termination" => true,
1247
- "device" => devname
1493
+ if snaps and snaps.items
1494
+ snaps.items.each { |snap|
1495
+ next if !snap.labels.is_a?(Hash) or !snap.labels["mu-device-name"] or snap.labels["mu-parent-image"] != img_name
1496
+ devname = snap.labels["mu-device-name"]
1497
+
1498
+ if used_devs.include?(devname)
1499
+ MU.log "Device name #{devname} already declared in server #{server['name']} (snapshot #{snap.name} wants the name)", MU::ERR
1500
+ ok = false
1501
+ end
1502
+ server['storage'] << {
1503
+ "snapshot_id" => snap.self_link,
1504
+ "size" => snap.disk_size_gb,
1505
+ "delete_on_termination" => true,
1506
+ "device" => devname
1507
+ }
1508
+ used_devs << devname
1248
1509
  }
1249
- used_devs << devname
1250
- }
1510
+ if snaps.items.size > 0
1511
+ # MU.log img_name, MU::WARN, details: snaps.items
1512
+ end
1513
+ end
1251
1514
  rescue ::Google::Apis::ClientError => e
1252
1515
  # it's ok, sometimes we don't have permission to list snapshots
1253
1516
  # in other peoples' projects
1517
+ # MU.log img_name, MU::WARN, details: img
1254
1518
  raise e if !e.message.match(/^forbidden: /)
1255
1519
  end
1256
1520
  end