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
@@ -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