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
@@ -0,0 +1,782 @@
1
+ # Copyright:: Copyright (c) 2019 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module MU
16
+ class Cloud
17
+ class Azure
18
+
19
+ # Creation of Virtual Private Clouds and associated artifacts (routes, subnets, etc).
20
+ class VPC < MU::Cloud::VPC
21
+ attr_reader :cloud_desc_cache
22
+ attr_reader :resource_group
23
+
24
+ # 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.
25
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
26
+ def initialize(**args)
27
+ super
28
+ @subnets = []
29
+ @subnetcachesemaphore = Mutex.new
30
+
31
+ if !mu_name.nil?
32
+ @mu_name = mu_name
33
+ if @cloud_id
34
+ cloud_desc
35
+ @cloud_id = Id.new(cloud_desc.id)
36
+ @resource_group ||= @cloud_id.resource_group
37
+ loadSubnets(use_cache: true)
38
+ end
39
+ elsif @config['scrub_mu_isms']
40
+ @mu_name = @config['name']
41
+ else
42
+ @mu_name = @deploy.getResourceName(@config['name'])
43
+ end
44
+ end
45
+
46
+ # Called automatically by {MU::Deploy#createResources}
47
+ def create
48
+ create_update
49
+ end
50
+
51
+
52
+ # Called automatically by {MU::Deploy#createResources}
53
+ def groom
54
+
55
+ if @config['peers']
56
+ count = 0
57
+ @config['peers'].each { |peer|
58
+ if peer['vpc']['name']
59
+ peer_obj = @deploy.findLitterMate(name: peer['vpc']['name'], type: "vpcs", habitat: peer['vpc']['project'])
60
+ next if peer_obj.mu_name < @mu_name # both of us would try to create this peering, otherwise, so don't step on each other
61
+ else
62
+ tag_key, tag_value = peer['vpc']['tag'].split(/=/, 2) if !peer['vpc']['tag'].nil?
63
+ if peer['vpc']['deploy_id'].nil? and peer['vpc']['id'].nil? and tag_key.nil?
64
+ peer['vpc']['deploy_id'] = @deploy.deploy_id
65
+ end
66
+
67
+ peer_obj = MU::MommaCat.findStray(
68
+ "Azure",
69
+ "vpcs",
70
+ deploy_id: peer['vpc']['deploy_id'],
71
+ cloud_id: peer['vpc']['id'],
72
+ name: peer['vpc']['name'],
73
+ tag_key: tag_key,
74
+ tag_value: tag_value,
75
+ dummy_ok: true
76
+ ).first
77
+ end
78
+
79
+ raise MuError, "No result looking for #{@mu_name}'s peer VPCs (#{peer['vpc']})" if peer_obj.nil?
80
+
81
+ ext_peerings = MU::Cloud::Azure.network(credentials: @credentials).virtual_network_peerings.list(@resource_group, @cloud_id)
82
+ peer_name = @mu_name+"-"+@config['name'].upcase+"-"+peer_obj.config['name'].upcase
83
+ peer_params = MU::Cloud::Azure.network(:VirtualNetworkPeering).new
84
+ peer_params.remote_virtual_network = peer_obj.cloud_desc
85
+ peer['allow_forwarded_traffic'] ||= false
86
+ peer_params.allow_forwarded_traffic = peer['allow_forwarded_traffic']
87
+ peer['allow_gateway_traffic'] ||= false
88
+ peer_params.allow_gateway_transit = peer['allow_gateway_traffic']
89
+
90
+ need_update = true
91
+ exists = false
92
+ ext_peerings.each { |ext_peering|
93
+ if ext_peering.remote_virtual_network.id == peer_obj.cloud_desc.id
94
+ exists = true
95
+ need_update = (ext_peering.allow_forwarded_traffic != peer_params.allow_forwarded_traffic or ext_peering.allow_gateway_transit != peer_params.allow_gateway_transit)
96
+ end
97
+ }
98
+
99
+ if need_update
100
+ if !exists
101
+ MU.log "Creating peering connection from #{@mu_name} to #{peer_obj.mu_name}", details: peer_params
102
+ else
103
+ MU.log "Updating peering connection from #{@mu_name} to #{peer_obj.mu_name}", MU::NOTICE, details: peer_params
104
+ end
105
+ MU::Cloud::Azure.network(credentials: @credentials).virtual_network_peerings.create_or_update(@resource_group, @cloud_id, peer_name, peer_params)
106
+ end
107
+ }
108
+ end
109
+
110
+ create_update
111
+ end
112
+
113
+ # Describe this VPC
114
+ # @return [Hash]
115
+ def notify
116
+ base = {}
117
+ base = MU.structToHash(cloud_desc)
118
+ base["cloud_id"] = @cloud_id.name
119
+ base.merge!(@config.to_h)
120
+ base
121
+ end
122
+ #
123
+ # Describe this VPC from the cloud platform's perspective
124
+ # @return [Hash]
125
+ def cloud_desc
126
+ if @cloud_desc_cache
127
+ return @cloud_desc_cache
128
+ end
129
+ @cloud_desc_cache = MU::Cloud::Azure::VPC.find(cloud_id: @cloud_id, resource_group: @resource_group).values.first
130
+
131
+ @cloud_id ||= Id.new(@cloud_desc_cache.id)
132
+ @cloud_desc_cache
133
+ end
134
+
135
+ # Locate and return cloud provider descriptors of this resource type
136
+ # which match the provided parameters, or all visible resources if no
137
+ # filters are specified. At minimum, implementations of +find+ must
138
+ # honor +credentials+ and +cloud_id+ arguments. We may optionally
139
+ # support other search methods, such as +tag_key+ and +tag_value+, or
140
+ # cloud-specific arguments like +project+. See also {MU::MommaCat.findStray}.
141
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
142
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
143
+ def self.find(**args)
144
+ found = {}
145
+
146
+ # Azure resources are namedspaced by resource group. If we weren't
147
+ # told one, we may have to search all the ones we can see.
148
+ resource_groups = if args[:resource_group]
149
+ [args[:resource_group]]
150
+ elsif args[:cloud_id] and args[:cloud_id].is_a?(MU::Cloud::Azure::Id)
151
+ [args[:cloud_id].resource_group]
152
+ else
153
+ MU::Cloud::Azure.resources(credentials: args[:credentials]).resource_groups.list.map { |rg| rg.name }
154
+ end
155
+
156
+ if args[:cloud_id]
157
+ id_str = args[:cloud_id].is_a?(MU::Cloud::Azure::Id) ? args[:cloud_id].name : args[:cloud_id]
158
+ resource_groups.each { |rg|
159
+ resp = MU::Cloud::Azure.network(credentials: args[:credentials]).virtual_networks.get(rg, id_str)
160
+
161
+ found[Id.new(resp.id)] = resp if resp
162
+ }
163
+ else
164
+ if args[:resource_group]
165
+ MU::Cloud::Azure.network(credentials: args[:credentials]).virtual_networks.list(args[:resource_group]).each { |net|
166
+ found[Id.new(net.id)] = net
167
+ }
168
+ else
169
+ MU::Cloud::Azure.network(credentials: args[:credentials]).virtual_networks.list_all.each { |net|
170
+ found[Id.new(net.id)] = net
171
+ }
172
+ end
173
+ end
174
+
175
+ found
176
+ end
177
+
178
+ # Return an array of MU::Cloud::Azure::VPC::Subnet objects describe the
179
+ # member subnets of this VPC.
180
+ #
181
+ # @return [Array<MU::Cloud::Azure::VPC::Subnet>]
182
+ def subnets
183
+ if @subnets.nil? or @subnets.size == 0
184
+ return loadSubnets
185
+ end
186
+ return @subnets
187
+ end
188
+
189
+ # Describe subnets associated with this VPC. We'll compose identifying
190
+ # information similar to what MU::Cloud.describe builds for first-class
191
+ # resources.
192
+ # @param use_cache [Boolean]: If available, use saved deployment metadata to describe subnets, instead of querying the cloud API
193
+ # @return [Array<Hash>]: A list of cloud provider identifiers of subnets associated with this VPC.
194
+ def loadSubnets(use_cache: false)
195
+ desc = cloud_desc
196
+ @subnets = []
197
+
198
+ MU::Cloud::Azure.network(credentials: @credentials).subnets.list(@resource_group, cloud_desc.name).each { |subnet|
199
+ subnet_cfg = {
200
+ "cloud_id" => subnet.name,
201
+ "mu_name" => subnet.name,
202
+ "credentials" => @config['credentials'],
203
+ "region" => @config['region'],
204
+ "ip_block" => subnet.address_prefix
205
+ }
206
+ if @config['subnets']
207
+ @config['subnets'].each { |s|
208
+ if s['ip_block'] == subnet_cfg['ip_block']
209
+ subnet_cfg['name'] = s['name']
210
+ break
211
+ end
212
+ }
213
+ end
214
+ subnet_cfg['name'] ||= subnet.name
215
+ @subnets << MU::Cloud::Azure::VPC::Subnet.new(self, subnet_cfg)
216
+ }
217
+ @subnets
218
+ end
219
+
220
+ # Given some search criteria try locating a NAT Gaateway in this VPC.
221
+ # @param nat_cloud_id [String]: The cloud provider's identifier for this NAT.
222
+ # @param nat_filter_key [String]: A cloud provider filter to help identify the resource, used in conjunction with nat_filter_value.
223
+ # @param nat_filter_value [String]: A cloud provider filter to help identify the resource, used in conjunction with nat_filter_key.
224
+ # @param region [String]: The cloud provider region of the target instance.
225
+ def findNat(nat_cloud_id: nil, nat_filter_key: nil, nat_filter_value: nil, region: MU.curRegion)
226
+ nil
227
+ end
228
+
229
+ # Given some search criteria for a {MU::Cloud::Server}, see if we can
230
+ # locate a NAT host in this VPC.
231
+ # @param nat_name [String]: The name of the resource as defined in its 'name' Basket of Kittens field, typically used in conjunction with deploy_id.
232
+ # @param nat_cloud_id [String]: The cloud provider's identifier for this NAT.
233
+ # @param nat_tag_key [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_value.
234
+ # @param nat_tag_value [String]: A cloud provider tag to help identify the resource, used in conjunction with tag_key.
235
+ # @param nat_ip [String]: An IP address associated with the NAT instance.
236
+ def findBastion(nat_name: nil, nat_cloud_id: nil, nat_tag_key: nil, nat_tag_value: nil, nat_ip: nil)
237
+ [:nat_name, :nat_cloud_id, :nat_tag_key, :nat_tag_value, :nat_ip].each { |var|
238
+ if binding.local_variable_get(var) != nil
239
+ binding.local_variable_set(var, var.to_s)
240
+ end
241
+
242
+ # If we're searching by name, assume it's part of this here deploy.
243
+ if nat_cloud_id.nil? and !@deploy.nil?
244
+ deploy_id = @deploy.deploy_id
245
+ end
246
+ found = MU::MommaCat.findStray(
247
+ "Azure",
248
+ "server",
249
+ name: nat_name,
250
+ cloud_id: nat_cloud_id,
251
+ deploy_id: deploy_id,
252
+ tag_key: nat_tag_key,
253
+ tag_value: nat_tag_value,
254
+ allow_multi: true,
255
+ dummy_ok: true,
256
+ calling_deploy: @deploy
257
+ )
258
+
259
+ return nil if found.nil? || found.empty?
260
+ if found.size == 1
261
+ return found.first
262
+ end
263
+
264
+ }
265
+ nil
266
+ end
267
+
268
+ # Check for a subnet in this VPC matching one or more of the specified
269
+ # criteria, and return it if found.
270
+ def getSubnet(cloud_id: nil, name: nil, tag_key: nil, tag_value: nil, ip_block: nil)
271
+ loadSubnets
272
+ if !cloud_id.nil? and cloud_id.match(/^https:\/\//)
273
+ cloud_id.gsub!(/.*?\//, "")
274
+ end
275
+ MU.log "getSubnet(cloud_id: #{cloud_id}, name: #{name}, tag_key: #{tag_key}, tag_value: #{tag_value}, ip_block: #{ip_block})", MU::DEBUG, details: caller[0]
276
+
277
+ @subnets.each { |subnet|
278
+ if !cloud_id.nil? and !subnet.cloud_id.nil? and subnet.cloud_id.to_s == cloud_id.to_s
279
+ return subnet
280
+ elsif !name.nil? and !subnet.name.nil? and subnet.name.to_s == name.to_s
281
+ return subnet
282
+ end
283
+ }
284
+ return nil
285
+ end
286
+ @route_cache = {}
287
+ @rtb_cache = {}
288
+ @rtb_cache_semaphore = Mutex.new
289
+ # Check whether we (the Mu Master) have a direct route to a particular
290
+ # instance. Useful for skipping hops through bastion hosts to get
291
+ # directly at child nodes in peered VPCs, the public internet, and the
292
+ # like.
293
+ # @param target_instance [OpenStruct]: The cloud descriptor of the instance to check.
294
+ # @param region [String]: The cloud provider region of the target subnet.
295
+ # @return [Boolean]
296
+ def self.haveRouteToInstance?(target_instance, region: MU.curRegion, credentials: nil)
297
+
298
+ # target_instance.network_profile.network_interfaces.each { |iface|
299
+ # iface_id = Id.new(iface.is_a?(Hash) ? iface['id'] : iface.id)
300
+ # iface_desc = MU::Cloud::Azure.network(credentials: credentials).network_interfaces.get(iface_id.resource_group, iface_id.to_s)
301
+ # iface_desc.ip_configurations.each { |ipcfg|
302
+ # if ipcfg.respond_to?(:public_ipaddress) and ipcfg.public_ipaddress
303
+ # return true # XXX invalid if Mu can't talk to the internet
304
+ # end
305
+ # }
306
+ # }
307
+
308
+ return false if MU.myCloud != "Azure"
309
+ # XXX if we're in Azure, see if this is in our VPC or if we're peered to its VPC
310
+ false
311
+ end
312
+
313
+
314
+ # Does this resource type exist as a global (cloud-wide) artifact, or
315
+ # is it localized to a region/zone?
316
+ # @return [Boolean]
317
+ def self.isGlobal?
318
+ false
319
+ end
320
+
321
+ # Denote whether this resource implementation is experiment, ready for
322
+ # testing, or ready for production use.
323
+ def self.quality
324
+ MU::Cloud::BETA
325
+ end
326
+
327
+ # Stub method. Azure resources are cleaned up by removing the parent
328
+ # resource group.
329
+ # @return [void]
330
+ def self.cleanup(**args)
331
+ end
332
+
333
+ # Reverse-map our cloud description into a runnable config hash.
334
+ # We assume that any values we have in +@config+ are placeholders, and
335
+ # calculate our own accordingly based on what's live in the cloud.
336
+ # XXX add flag to return the diff between @config and live cloud
337
+ def toKitten(rootparent: nil, billing: nil)
338
+ return nil if cloud_desc.name == "default" # parent project builds these
339
+ bok = {
340
+ "cloud" => "Azure",
341
+ "project" => @config['project'],
342
+ "credentials" => @config['credentials']
343
+ }
344
+
345
+ bok
346
+ end
347
+
348
+ # Cloud-specific configuration properties.
349
+ # @param config [MU::Config]: The calling MU::Config object
350
+ # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
351
+ def self.schema(config = nil)
352
+ toplevel_required = []
353
+ schema = {
354
+ "peers" => {
355
+ "items" => {
356
+ "properties" => {
357
+ "allow_forwarded_traffic" => {
358
+ "type" => "boolean",
359
+ "default" => false,
360
+ "description" => "Allow traffic originating from outside peered networks"
361
+ },
362
+ "allow_gateway_traffic" => {
363
+ "type" => "boolean",
364
+ "default" => false,
365
+ "description" => "Permit peered networks to use each others' VPN gateways"
366
+ }
367
+ }
368
+ }
369
+ }
370
+ }
371
+ [toplevel_required, schema]
372
+ end
373
+
374
+
375
+ # Cloud-specific pre-processing of {MU::Config::BasketofKittens::vpcs}, bare and unvalidated.
376
+ # @param vpc [Hash]: The resource to process and validate
377
+ # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
378
+ # @return [Boolean]: True if validation succeeded, False otherwise
379
+ def self.validateConfig(vpc, configurator)
380
+ ok = true
381
+ vpc['region'] ||= MU::Cloud::Azure.myRegion(vpc['credentials'])
382
+
383
+ if vpc['subnets']
384
+ vpc['subnets'].each { |subnet|
385
+ subnet_routes[subnet['route_table']] = Array.new if subnet_routes[subnet['route_table']].nil?
386
+ subnet_routes[subnet['route_table']] << subnet['name']
387
+ }
388
+ end
389
+
390
+ if (!vpc['subnets'] or vpc['subnets'].empty?) and vpc['create_standard_subnets']
391
+ subnets = configurator.divideNetwork(vpc['ip_block'], vpc['route_tables'].size, 28)
392
+ vpc['subnets'] ||= []
393
+ vpc['route_tables'].each { |rtb|
394
+ is_public = false
395
+ rtb['routes'].each { |route|
396
+ if route['gateway'] == "#INTERNET"
397
+ is_public = true
398
+ break
399
+ end
400
+ }
401
+ vpc['subnets'] << {
402
+ "name" => "Subnet#{rtb['name'].capitalize}",
403
+ "is_public" => is_public,
404
+ "ip_block" => subnets.shift,
405
+ "route_table" => rtb['name']
406
+ }
407
+ }
408
+ end
409
+
410
+ vpc['route_tables'].each { |rtb|
411
+ rtb['routes'] ||= []
412
+ rtb['routes'] << { "destination_network" => vpc['ip_block'] }
413
+ rtb['routes'].uniq!
414
+ }
415
+
416
+ default_acl = {
417
+ "name" => vpc['name']+"-defaultfw",
418
+ "cloud" => "Azure",
419
+ "region" => vpc['region'],
420
+ "credentials" => vpc['credentials'],
421
+ "rules" => [
422
+ {
423
+ "ingress" => true, "proto" => "all", "hosts" => [vpc['ip_block']]
424
+ },
425
+ {
426
+ "egress" => true, "proto" => "all", "hosts" => [vpc['ip_block']]
427
+ }
428
+ ]
429
+ }
430
+ vpc["dependencies"] ||= []
431
+ vpc["dependencies"] << {
432
+ "type" => "firewall_rule",
433
+ "name" => vpc['name']+"-defaultfw"
434
+ }
435
+
436
+ if !configurator.insertKitten(default_acl, "firewall_rules", true)
437
+ ok = false
438
+ end
439
+
440
+ ok
441
+ end
442
+
443
+ # @param route [Hash]: A route description, per the Basket of Kittens schema
444
+ # @param server [MU::Cloud::Azure::Server]: Instance to which this route will apply
445
+ def createRouteForInstance(route, server)
446
+ createRoute(route, network: @url, tags: [MU::Cloud::Azure.nameStr(server.mu_name)])
447
+ end
448
+
449
+ private
450
+
451
+ def create_update
452
+ @config = MU::Config.manxify(@config)
453
+ @config['region'] ||= MU::Cloud::Azure.myRegion(@config['credentials'])
454
+ tags = {}
455
+ if !@config['scrub_mu_isms']
456
+ tags = MU::MommaCat.listStandardTags
457
+ end
458
+ if @config['tags']
459
+ @config['tags'].each { |tag|
460
+ tags[tag['key']] = tag['value']
461
+ }
462
+ end
463
+
464
+ vpc_obj = MU::Cloud::Azure.network(:VirtualNetwork).new
465
+ addr_space_obj = MU::Cloud::Azure.network(:AddressSpace).new
466
+ addr_space_obj.address_prefixes = [
467
+ @config['ip_block']
468
+ ]
469
+ vpc_obj.address_space = addr_space_obj
470
+ vpc_obj.location = @config['region']
471
+ vpc_obj.tags = tags
472
+
473
+ my_fw = deploy.findLitterMate(type: "firewall_rule", name: @config['name']+"-defaultfw")
474
+
475
+ @resource_group = @deploy.deploy_id+"-"+@config['region'].upcase
476
+
477
+ need_apply = false
478
+ ext_vpc = nil
479
+ begin
480
+ ext_vpc = MU::Cloud::Azure.network(credentials: @config['credentials']).virtual_networks.get(
481
+ @resource_group,
482
+ @mu_name
483
+ )
484
+ rescue ::MU::Cloud::Azure::APIError => e
485
+ if e.message.match(/: ResourceNotFound:/)
486
+ need_apply = true
487
+ else
488
+ raise e
489
+ end
490
+ end
491
+ # XXX raw update seems to destroy child resources; if we just need to update
492
+ # tags, do that with .update_tags
493
+ if !ext_vpc
494
+ MU.log "Creating VPC #{@mu_name} (#{@config['ip_block']}) in #{@config['region']}", details: vpc_obj
495
+ need_apply = true
496
+ elsif ext_vpc.location != vpc_obj.location or
497
+ ext_vpc.tags != vpc_obj.tags or
498
+ ext_vpc.address_space.address_prefixes != vpc_obj.address_space.address_prefixes
499
+ MU.log "Updating VPC #{@mu_name} (#{@config['ip_block']}) in #{@config['region']}", MU::NOTICE, details: vpc_obj
500
+ need_apply = true
501
+ end
502
+
503
+ if need_apply
504
+ resp = MU::Cloud::Azure.network(credentials: @config['credentials']).virtual_networks.create_or_update(
505
+ @resource_group,
506
+ @mu_name,
507
+ vpc_obj
508
+ )
509
+ @cloud_id = Id.new(resp.id)
510
+ end
511
+
512
+ # this is slow, so maybe thread it
513
+ rtb_map = {}
514
+ routethreads = []
515
+ create_nat_gateway = false
516
+ @config['route_tables'].each { |rtb_cfg|
517
+ routethreads << Thread.new(rtb_cfg) { |rtb|
518
+ rtb_name = @mu_name+"-"+rtb['name'].upcase
519
+ rtb_obj = MU::Cloud::Azure.network(:RouteTable).new
520
+ rtb_obj.location = @config['region']
521
+
522
+ rtb_obj.tags = tags
523
+ rtb_ref_obj = MU::Cloud::Azure.network(:RouteTable).new
524
+ rtb_ref_obj.name = rtb_name
525
+ rtb_map[rtb['name']] = rtb_ref_obj
526
+
527
+ need_apply = false
528
+ ext_rtb = nil
529
+ begin
530
+ ext_rtb = MU::Cloud::Azure.network(credentials: @config['credentials']).route_tables.get(
531
+ @resource_group,
532
+ rtb_name
533
+ )
534
+ rtb_map[rtb['name']] = ext_rtb
535
+ rescue MU::Cloud::Azure::APIError => e
536
+ if e.message.match(/: ResourceNotFound:/)
537
+ need_apply = true
538
+ else
539
+ raise e
540
+ end
541
+ end
542
+
543
+ if !ext_rtb
544
+ MU.log "Creating route table #{rtb_name} in VPC #{@mu_name}", details: rtb_obj
545
+ need_apply = true
546
+ elsif ext_rtb.location != rtb_obj.location or
547
+ ext_rtb.tags != rtb_obj.tags
548
+ need_apply = true
549
+ MU.log "Updating route table #{rtb_name} in VPC #{@mu_name}", MU::NOTICE, details: rtb_obj
550
+ end
551
+
552
+ if need_apply
553
+ rtb_map[rtb['name']] = MU::Cloud::Azure.network(credentials: @config['credentials']).route_tables.create_or_update(
554
+ @resource_group,
555
+ rtb_name,
556
+ rtb_obj
557
+ )
558
+ end
559
+
560
+ rtb['routes'].each { |route|
561
+ route_obj = MU::Cloud::Azure.network(:Route).new
562
+ route_obj.address_prefix = route['destination_network']
563
+ routename = rtb_name+"-"+route['destination_network'].gsub(/[^a-z0-9]/i, "_")
564
+ route_obj.next_hop_type = if route['gateway'] == "#NAT" and @config['bastion']
565
+ routename = rtb_name+"-NAT"
566
+ bastion_ref = MU::Config::Ref.get(@config['bastion'])
567
+ if bastion_ref.kitten and bastion_ref.kitten.cloud_desc
568
+ iface_id = Id.new(bastion_ref.kitten.cloud_desc.network_profile.network_interfaces.first.id)
569
+ iface_desc = MU::Cloud::Azure.network(credentials: @credentials).network_interfaces.get(@resource_group, iface_id.name)
570
+ if iface_desc and iface_desc.ip_configurations and iface_desc.ip_configurations.size > 0
571
+ route_obj.next_hop_ip_address = iface_desc.ip_configurations.first.private_ipaddress
572
+ "VirtualAppliance"
573
+ else
574
+ "VnetLocal"
575
+ end
576
+ else
577
+ "VnetLocal"
578
+ end
579
+ # create_nat_gateway = true
580
+ elsif route['gateway'] == "#INTERNET"
581
+ routename = rtb_name+"-INTERNET"
582
+ "Internet"
583
+ else
584
+ routename = rtb_name+"-LOCAL"
585
+ "VnetLocal"
586
+ end
587
+ #next_hop_type 'VirtualNetworkGateway' is for VPNs I think
588
+
589
+ need_apply = false
590
+ ext_route = nil
591
+ begin
592
+ ext_route = MU::Cloud::Azure.network(credentials: @config['credentials']).routes.get(
593
+ @resource_group,
594
+ rtb_name,
595
+ routename
596
+ )
597
+ rescue MU::Cloud::Azure::APIError => e
598
+ if e.message.match(/\bNotFound\b/)
599
+ need_apply = true
600
+ else
601
+ raise e
602
+ end
603
+ end
604
+
605
+ if !ext_route
606
+ MU.log "Creating route #{routename} for #{route['destination_network']} in route table #{rtb_name}", details: rtb_obj
607
+ need_apply = true
608
+ elsif ext_route.next_hop_type != route_obj.next_hop_type or
609
+ ext_route.address_prefix != route_obj.address_prefix
610
+ MU.log "Updating route #{routename} for #{route['destination_network']} in route table #{rtb_name}", MU::NOTICE, details: [route_obj, ext_route]
611
+ need_apply = true
612
+ end
613
+
614
+ if need_apply
615
+ MU::Cloud::Azure.network(credentials: @config['credentials']).routes.create_or_update(
616
+ @resource_group,
617
+ rtb_name,
618
+ routename,
619
+ route_obj
620
+ )
621
+ end
622
+ }
623
+ }
624
+ }
625
+
626
+ routethreads.each { |t|
627
+ t.join
628
+ }
629
+
630
+ # TODO this is only available in westus as of 2019-09-29
631
+ # if create_nat_gateway
632
+ # nat_obj = MU::Cloud::Azure.network(:NatGateway).new
633
+ # nat_obj.location = @config['region']
634
+ # nat_obj.tags = tags
635
+ # MU.log "Creating NAT Gateway #{@mu_name}-NAT", details: nat_obj
636
+ # MU::Cloud::Azure.network(credentials: @config['credentials']).nat_gateways.create_or_update(
637
+ # @resource_group,
638
+ # @mu_name+"-NAT",
639
+ # nat_obj
640
+ # )
641
+ # end
642
+
643
+ if @config['subnets']
644
+ subnetthreads = []
645
+ @config['subnets'].each { |subnet_cfg|
646
+ subnetthreads << Thread.new(subnet_cfg) { |subnet|
647
+ subnet_obj = MU::Cloud::Azure.network(:Subnet).new
648
+ subnet_name = @mu_name+"-"+subnet['name'].upcase
649
+ subnet_obj.address_prefix = subnet['ip_block']
650
+ subnet_obj.route_table = rtb_map[subnet['route_table']]
651
+ if my_fw and my_fw.cloud_desc
652
+ subnet_obj.network_security_group = my_fw.cloud_desc
653
+ end
654
+
655
+ need_apply = false
656
+ ext_subnet = nil
657
+ begin
658
+ ext_subnet = MU::Cloud::Azure.network(credentials: @config['credentials']).subnets.get(
659
+ @resource_group,
660
+ @cloud_id.to_s,
661
+ subnet_name
662
+ )
663
+ rescue APIError => e
664
+ if e.message.match(/\bNotFound\b/)
665
+ need_apply = true
666
+ else
667
+ # raise e
668
+ end
669
+ end
670
+
671
+ if !ext_subnet
672
+ MU.log "Creating Subnet #{subnet_name} in VPC #{@mu_name}", details: subnet_obj
673
+ need_apply = true
674
+ elsif (!ext_subnet.route_table.nil? and !subnet_obj.route_table.nil? and ext_subnet.route_table.id != subnet_obj.route_table.id) or
675
+ ext_subnet.address_prefix != subnet_obj.address_prefix or
676
+ ext_subnet.network_security_group.nil? and !subnet_obj.network_security_group.nil? or
677
+ (!ext_subnet.network_security_group.nil? and !subnet_obj.network_security_group.nil? and ext_subnet.network_security_group.id != subnet_obj.network_security_group.id)
678
+ MU.log "Updating Subnet #{subnet_name} in VPC #{@mu_name}", MU::NOTICE, details: subnet_obj
679
+ need_apply = true
680
+
681
+ end
682
+
683
+ if need_apply
684
+ MU::Cloud::Azure.network(credentials: @config['credentials']).subnets.create_or_update(
685
+ @resource_group,
686
+ @cloud_id.to_s,
687
+ subnet_name,
688
+ subnet_obj
689
+ )
690
+ end
691
+ }
692
+ }
693
+
694
+ subnetthreads.each { |t|
695
+ t.join
696
+ }
697
+ end
698
+
699
+ loadSubnets
700
+ end
701
+
702
+ protected
703
+
704
+ # Subnets are almost a first-class resource. So let's kinda sorta treat
705
+ # them like one. This should only be invoked on objects that already
706
+ # exists in the cloud layer.
707
+ class Subnet < MU::Cloud::Azure::VPC
708
+
709
+ attr_reader :cloud_id
710
+ attr_reader :id
711
+ attr_reader :ip_block
712
+ attr_reader :mu_name
713
+ attr_reader :name
714
+ attr_reader :cloud_desc_cache
715
+ attr_reader :resource_group
716
+ attr_reader :az
717
+
718
+ # @param parent [MU::Cloud::Azure::VPC]: The parent VPC of this subnet.
719
+ # @param config [Hash<String>]:
720
+ def initialize(parent, config, precache_description: true)
721
+ @parent = parent
722
+ @deploy = parent.deploy
723
+ @config = MU::Config.manxify(config)
724
+ @cloud_id = config['cloud_id']
725
+ @mu_name = config['mu_name']
726
+ @name = config['name']
727
+ @deploydata = config # This is a dummy for the sake of describe()
728
+ @ip_block = config['ip_block']
729
+ @cloud_desc_cache = nil
730
+ @az = parent.config['region']
731
+ cloud_desc if precache_description
732
+ end
733
+
734
+ # Return the cloud identifier for the default route of this subnet.
735
+ def defaultRoute
736
+ if cloud_desc and cloud_desc.route_table
737
+ rtb_id = MU::Cloud::Azure::Id.new(cloud_desc.route_table.id)
738
+ routes = MU::Cloud::Azure.network(credentials: @config['credentials']).routes.list(
739
+ rtb_id.resource_group,
740
+ rtb_id.name
741
+ )
742
+ routes.each { |route|
743
+ return route if route.address_prefix == "0.0.0.0/0"
744
+ }
745
+ end
746
+ nil
747
+ end
748
+
749
+ # Describe this VPC Subnet
750
+ # @return [Hash]
751
+ def notify
752
+ MU.structToHash(cloud_desc)
753
+ end
754
+
755
+ # Describe this VPC Subnet from the cloud platform's perspective
756
+ def cloud_desc
757
+ return @cloud_desc_cache if !@cloud_desc_cache.nil?
758
+ @cloud_desc_cache = MU::Cloud::Azure.network(credentials: @parent.credentials).subnets.get(@parent.resource_group, @parent.cloud_desc.name, @cloud_id.to_s)
759
+ @cloud_desc_cache
760
+ end
761
+
762
+ # Is this subnet privately-routable only, or public?
763
+ # @return [Boolean]
764
+ def private?
765
+ if cloud_desc and cloud_desc.route_table
766
+ rtb_id = MU::Cloud::Azure::Id.new(cloud_desc.route_table.id)
767
+ routes = MU::Cloud::Azure.network(credentials: @config['credentials']).routes.list(
768
+ rtb_id.resource_group,
769
+ rtb_id.name
770
+ )
771
+ routes.each { |route|
772
+ return false if route.next_hop_type == "Internet"
773
+ }
774
+ true
775
+ end
776
+ end
777
+ end
778
+
779
+ end #class
780
+ end #class
781
+ end
782
+ end #module