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,413 @@
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
+ # A Kubernetes cluster as configured in {MU::Config::BasketofKittens::container_clusters}
19
+ class ContainerCluster < MU::Cloud::ContainerCluster
20
+
21
+ # Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like <tt>@vpc</tt>, for us.
22
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
23
+ def initialize(**args)
24
+ super
25
+
26
+ # @mu_name = mu_name ? mu_name : @deploy.getResourceName(@config["name"])
27
+ if !mu_name.nil?
28
+ @mu_name = mu_name
29
+ @cloud_id = Id.new(cloud_desc.id) if @cloud_id
30
+ else
31
+ @mu_name ||= @deploy.getResourceName(@config["name"], max_length: 31)
32
+ end
33
+ end
34
+
35
+
36
+ # Called automatically by {MU::Deploy#createResources}
37
+ # @return [String]: The cloud provider's identifier for this GKE instance.
38
+ def create
39
+ create_update
40
+ end
41
+
42
+ # Called automatically by {MU::Deploy#createResources}
43
+ def groom
44
+ create_update
45
+
46
+ kube_conf = @deploy.deploy_dir+"/kubeconfig-#{@config['name']}"
47
+
48
+ admin_creds = MU::Cloud::Azure.containers(credentials: @config['credentials']).managed_clusters.list_cluster_admin_credentials(
49
+ @resource_group,
50
+ @mu_name
51
+ )
52
+ admin_creds.kubeconfigs.each { |kube|
53
+ next if kube.name != "clusterAdmin"
54
+
55
+ cfgfile = ""
56
+ kube.value.each { |ord|
57
+ cfgfile += ord.chr
58
+ }
59
+
60
+ File.open(kube_conf, "w"){ |k|
61
+ k.puts cfgfile
62
+ }
63
+ }
64
+
65
+ if @config['kubernetes_resources']
66
+ MU::Master.applyKubernetesResources(
67
+ @config['name'],
68
+ @config['kubernetes_resources'],
69
+ kubeconfig: kube_conf,
70
+ outputdir: @deploy.deploy_dir
71
+ )
72
+ end
73
+
74
+ MU.log %Q{How to interact with your Kubernetes cluster\nkubectl --kubeconfig "#{kube_conf}" get events --all-namespaces\nkubectl --kubeconfig "#{kube_conf}" get all\nkubectl --kubeconfig "#{kube_conf}" create -f some_k8s_deploy.yml\nkubectl --kubeconfig "#{kube_conf}" get nodes}, MU::SUMMARY
75
+
76
+ end
77
+
78
+ # Locate and return cloud provider descriptors of this resource type
79
+ # which match the provided parameters, or all visible resources if no
80
+ # filters are specified. At minimum, implementations of +find+ must
81
+ # honor +credentials+ and +cloud_id+ arguments. We may optionally
82
+ # support other search methods, such as +tag_key+ and +tag_value+, or
83
+ # cloud-specific arguments like +project+. See also {MU::MommaCat.findStray}.
84
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
85
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
86
+ def self.find(**args)
87
+ found = {}
88
+
89
+ # Azure resources are namedspaced by resource group. If we weren't
90
+ # told one, we may have to search all the ones we can see.
91
+ resource_groups = if args[:resource_group]
92
+ [args[:resource_group]]
93
+ elsif args[:cloud_id] and args[:cloud_id].is_a?(MU::Cloud::Azure::Id)
94
+ [args[:cloud_id].resource_group]
95
+ else
96
+ MU::Cloud::Azure.resources(credentials: args[:credentials]).resource_groups.list.map { |rg| rg.name }
97
+ end
98
+
99
+ if args[:cloud_id]
100
+ id_str = args[:cloud_id].is_a?(MU::Cloud::Azure::Id) ? args[:cloud_id].name : args[:cloud_id]
101
+ resource_groups.each { |rg|
102
+ resp = MU::Cloud::Azure.containers(credentials: args[:credentials]).managed_clusters.get(rg, id_str)
103
+ found[Id.new(resp.id)] = resp if resp
104
+ }
105
+ else
106
+ if args[:resource_group]
107
+ MU::Cloud::Azure.containers(credentials: args[:credentials]).managed_clusters.list_by_resource_group(args[:resource_group]).each { |cluster|
108
+ found[Id.new(cluster.id)] = cluster
109
+ }
110
+ else
111
+ MU::Cloud::Azure.containers(credentials: args[:credentials]).managed_clusters.list.each { |cluster|
112
+ found[Id.new(cluster.id)] = cluster
113
+ }
114
+ end
115
+ end
116
+
117
+ found
118
+ end
119
+
120
+ # Register a description of this cluster instance with this deployment's metadata.
121
+ def notify
122
+ base = {}
123
+ base = MU.structToHash(cloud_desc)
124
+ base["cloud_id"] = @cloud_id.name
125
+ base.merge!(@config.to_h)
126
+ base
127
+ end
128
+
129
+ # Does this resource type exist as a global (cloud-wide) artifact, or
130
+ # is it localized to a region/zone?
131
+ # @return [Boolean]
132
+ def self.isGlobal?
133
+ false
134
+ end
135
+
136
+ # Denote whether this resource implementation is experiment, ready for
137
+ # testing, or ready for production use.
138
+ def self.quality
139
+ MU::Cloud::BETA
140
+ end
141
+
142
+ # Stub method. Azure resources are cleaned up by removing the parent
143
+ # resource group.
144
+ # @return [void]
145
+ def self.cleanup(**args)
146
+ end
147
+
148
+ # Cloud-specific configuration properties.
149
+ # @param config [MU::Config]: The calling MU::Config object
150
+ # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
151
+ def self.schema(config)
152
+ toplevel_required = []
153
+ schema = {
154
+ "flavor" => {
155
+ "enum" => ["Kubernetes", "OpenShift", "Swarm", "DC/OS"],
156
+ "description" => "The Azure container platform to deploy. Currently only +Kubernetes+ is supported.",
157
+ "default" => "Kubernetes"
158
+ },
159
+ "platform" => {
160
+ "description" => "The OS platform to deploy for workers and containers.",
161
+ "default" => "Linux",
162
+ "enum" => ["Linux", "Windows"]
163
+ },
164
+ "max_pods" => {
165
+ "type" => "integer",
166
+ "description" => "Maximum number of pods allowed on this cluster",
167
+ "default" => 30
168
+ },
169
+ "kubernetes" => {
170
+ "default" => { "version" => "1.12.8" }
171
+ },
172
+ "dns_prefix" => {
173
+ "type" => "string",
174
+ "description" => "DNS name prefix to use with the hosted Kubernetes API server FQDN. Will default to the global +appname+ value if not specified."
175
+ },
176
+ "disk_size_gb" => {
177
+ "type" => "integer",
178
+ "description" => "Size of the disk attached to each worker, specified in GB. The smallest allowed disk size is 30, the largest 1024.",
179
+ "default" => 100
180
+ },
181
+ }
182
+ [toplevel_required, schema]
183
+ end
184
+
185
+ # Cloud-specific pre-processing of {MU::Config::BasketofKittens::container_clusters}, bare and unvalidated.
186
+ # @param cluster [Hash]: The resource to process and validate
187
+ # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
188
+ # @return [Boolean]: True if validation succeeded, False otherwise
189
+ def self.validateConfig(cluster, configurator)
190
+ ok = true
191
+ # XXX validate k8s versions (master and node)
192
+ # XXX validate image types
193
+ # MU::Cloud::Azure.container.get_project_zone_serverconfig(@config["project"], @config['availability_zone'])
194
+ cluster["dns_prefix"] ||= $myAppName # XXX woof globals wtf
195
+ cluster['region'] ||= MU::Cloud::Azure.myRegion(cluster['credentials'])
196
+
197
+ if cluster["disk_size_gb"] < 30 or cluster["disk_size_gb"] > 1024
198
+ MU.log "Azure ContainerCluster disk_size_gb must be between 30 and 1024.", MU::ERR
199
+ ok = false
200
+ end
201
+
202
+ if cluster['min_size'] and cluster['instance_count'] < cluster['min_size']
203
+ cluster['instance_count'] = cluster['min_size']
204
+ end
205
+ if cluster['max_size'] and cluster['instance_count'] < cluster['max_size']
206
+ cluster['instance_count'] = cluster['max_size']
207
+ end
208
+
209
+ cluster['instance_type'] ||= "Standard_DS2_v2" # TODO when Server is implemented, it should have a validateInstanceType method we can use here
210
+
211
+ svcacct_desc = {
212
+ "name" => cluster["name"]+"user",
213
+ "region" => cluster["region"],
214
+ "type" => "service",
215
+ "cloud" => "Azure",
216
+ "create_api_key" => true,
217
+ "credentials" => cluster["credentials"],
218
+ "roles" => [
219
+ "Azure Kubernetes Service Cluster Admin Role"
220
+ ]
221
+ }
222
+ cluster['dependencies'] ||= []
223
+ cluster['dependencies'] << {
224
+ "type" => "user",
225
+ "name" => cluster["name"]+"user"
226
+ }
227
+
228
+ ok = false if !configurator.insertKitten(svcacct_desc, "users")
229
+
230
+ ok
231
+ end
232
+
233
+ private
234
+
235
+ def create_update
236
+ need_apply = false
237
+
238
+ ext_cluster = MU::Cloud::Azure.containers(credentials: @config[:credentials]).managed_clusters.get(
239
+ @resource_group,
240
+ @mu_name
241
+ )
242
+ if ext_cluster
243
+ @cloud_id = MU::Cloud::Azure::Id.new(ext_cluster.id)
244
+ end
245
+
246
+ key_obj = MU::Cloud::Azure.containers(:ContainerServiceSshPublicKey).new
247
+ key_obj.key_data = @deploy.ssh_public_key
248
+
249
+ ssh_obj = MU::Cloud::Azure.containers(:ContainerServiceSshConfiguration).new
250
+ ssh_obj.public_keys = [key_obj]
251
+
252
+ os_profile_obj = if !ext_cluster
253
+ if @config['platform'] == "Windows"
254
+ os_obj = MU::Cloud::Azure.containers(:ContainerServiceWindowsProfile, model_version: "V2019_02_01").new
255
+ os_obj.admin_username = "muadmin"
256
+ # Azure password constraints are extra-annoying
257
+ winpass = MU.generateWindowsPassword(safe_pattern: '!@#$%^&*()', retries: 150)
258
+ # TODO store this somewhere the user can get at it
259
+ os_obj.admin_password = winpass
260
+ os_obj
261
+ else
262
+ os_obj = MU::Cloud::Azure.containers(:ContainerServiceLinuxProfile).new
263
+ os_obj.admin_username = "muadmin"
264
+ os_obj.ssh = ssh_obj
265
+ os_obj
266
+ end
267
+ else
268
+ # Azure does not support updates to this parameter
269
+ @config['platform'] == "Windows" ? ext_cluster.windows_profile : ext_cluster.linux_profile
270
+ end
271
+
272
+ svc_principal_obj = MU::Cloud::Azure.containers(:ManagedClusterServicePrincipalProfile).new
273
+ # XXX this should come from a MU::Cloud::Azure::User object, but right now
274
+ # there's no way to get the 'secret' field from a user-assigned identity afaict
275
+ # For now, we'll cheat with Mu's system credentials.
276
+ creds = MU::Cloud::Azure.credConfig(@config['credentials'])
277
+ svc_principal_obj.client_id = creds["client_id"]
278
+ svc_principal_obj.secret = creds["client_secret"]
279
+
280
+ # svc_acct = @deploy.findLitterMate(type: "user", name: @config['name']+"user")
281
+ # raise MuError, "Failed to locate service account #{@config['name']}user" if !svc_acct
282
+ # svc_principal_obj.client_id = svc_acct.cloud_desc.client_id
283
+ # svc_principal_obj.secret = svc_acct.getSecret
284
+
285
+ agent_profiles = if !ext_cluster
286
+ profile_obj = MU::Cloud::Azure.containers(:ManagedClusterAgentPoolProfile).new
287
+ profile_obj.name = @deploy.getResourceName(@config["name"], max_length: 11).downcase.gsub(/[^0-9a-z]/, "")
288
+ if @config['min_size'] and @config['max_size']
289
+ # Special API features need to be enabled for scaling
290
+ MU::Cloud::Azure.ensureFeature("Microsoft.ContainerService/WindowsPreview", credentials: @config['credentials'])
291
+ MU::Cloud::Azure.ensureFeature("Microsoft.ContainerService/VMSSPreview", credentials: @config['credentials'])
292
+
293
+ profile_obj.min_count = @config['min_size']
294
+ profile_obj.max_count = @config['max_size']
295
+ profile_obj.enable_auto_scaling = true
296
+ profile_obj.type = MU::Cloud::Azure.containers(:AgentPoolType)::VirtualMachineScaleSets
297
+ # XXX if you actually try to do this:
298
+ # BadRequest: Virtual Machine Scale Set agent nodes are not allowed since feature "Microsoft.ContainerService/WindowsPreview" is not enabled.
299
+ end
300
+ profile_obj.count = @config['instance_count']
301
+ profile_obj.vm_size = @config['instance_type']
302
+ profile_obj.max_pods = @config['max_pods']
303
+ profile_obj.os_type = @config['platform']
304
+ profile_obj.os_disk_size_gb = @config['disk_size_gb']
305
+ # XXX correlate this with the one(s) we configured in @config['vpc']
306
+ # profile_obj.vnet_subnet_id = @vpc.subnets.first.cloud_desc.id # XXX has to have its own subnet for k8s apparently
307
+ [profile_obj]
308
+ else
309
+ # Azure does not support adding/removing agent profiles to a live
310
+ # cluster, but it does support changing some values on an existing
311
+ # one.
312
+ profile_obj = ext_cluster.agent_pool_profiles.first
313
+
314
+ nochange_map = {
315
+ "disk_size_gb" => :os_disk_size_gb,
316
+ "instance_type" => :vm_size,
317
+ "platform" => :os_type,
318
+ "max_pods" => :max_pods,
319
+ }
320
+
321
+ tried_to_change =[]
322
+ nochange_map.each_pair { |cfg, attribute|
323
+ if @config.has_key?(cfg) and
324
+ @config[cfg] != profile_obj.send(attribute)
325
+ tried_to_change << cfg
326
+ end
327
+ }
328
+ if @config['min_size'] and @config['max_size'] and
329
+ !profile_obj.enable_auto_scaling
330
+ tried_to_change << "enable_auto_scaling"
331
+ end
332
+ if tried_to_change.size > 0
333
+ MU.log "Changes specified to one or more immutable AKS Agent Pool parameters in cluster #{@mu_name}, ignoring.", MU::NOTICE, details: tried_to_change
334
+ end
335
+
336
+ if @config['min_size'] and @config['max_size'] and
337
+ profile_obj.enable_auto_scaling and
338
+ (
339
+ profile_obj.min_count != @config['min_size'] or
340
+ profile_obj.max_count != @config['max_size']
341
+ )
342
+ profile_obj.min_count = @config['min_size']
343
+ profile_obj.max_count = @config['max_size']
344
+ need_apply = true
345
+ end
346
+
347
+ if profile_obj.count != @config['instance_count']
348
+ profile_obj.count = @config['instance_count']
349
+ need_apply = true
350
+ end
351
+
352
+ [profile_obj]
353
+ end
354
+
355
+ cluster_obj = MU::Cloud::Azure.containers(:ManagedCluster).new
356
+
357
+ if ext_cluster
358
+ cluster_obj.dns_prefix = ext_cluster.dns_prefix
359
+ cluster_obj.location = ext_cluster.location
360
+ else
361
+ # Azure does not support updates to these parameters
362
+ cluster_obj.dns_prefix = @config['dns_prefix']
363
+ cluster_obj.location = @config['region']
364
+ end
365
+
366
+ cluster_obj.tags = @tags
367
+
368
+ cluster_obj.service_principal_profile = svc_principal_obj
369
+ if @config['platform'] == "Windows"
370
+ cluster_obj.windows_profile = os_profile_obj
371
+ else
372
+ cluster_obj.linux_profile = os_profile_obj
373
+ end
374
+ # cluster_obj.api_server_authorized_ipranges = [MU.mu_public_ip+"/32", MU.my_private_ip+"/32"] # XXX only allowed with Microsoft.ContainerService/APIServerSecurityPreview enabled
375
+ cluster_obj.agent_pool_profiles = agent_profiles
376
+
377
+ if @config['flavor'] == "Kubernetes"
378
+ cluster_obj.kubernetes_version = @config['kubernetes']['version'].to_s
379
+ if ext_cluster and @config['kubernetes']['version'] != ext_cluster.kubernetes_version
380
+ need_apply = true
381
+ end
382
+ end
383
+
384
+ # XXX it may be possible to create a new AgentPool and fall forward into it?
385
+ # API behavior suggests otherwise. Project for later.
386
+ # pool_obj = MU::Cloud::Azure.containers(:AgentPool).new
387
+ # pool_obj.count = @config['instance_count']
388
+ # pool_obj.vm_size = "Standard_DS2_v2"
389
+
390
+ if !ext_cluster
391
+ pp cluster_obj
392
+ MU.log "Creating AKS cluster #{@mu_name}", details: cluster_obj
393
+ need_apply = true
394
+ elsif need_apply
395
+ MU.log "Updating AKS cluster #{@mu_name}", MU::NOTICE, details: cluster_obj
396
+ end
397
+
398
+ if need_apply
399
+ resp = MU::Cloud::Azure.containers(credentials: @config['credentials']).managed_clusters.create_or_update(
400
+ @resource_group,
401
+ @mu_name,
402
+ cluster_obj
403
+ )
404
+
405
+ @cloud_id = Id.new(resp.id)
406
+ end
407
+
408
+ end
409
+
410
+ end #class
411
+ end #class
412
+ end
413
+ end #module
@@ -0,0 +1,500 @@
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
+
17
+ class Cloud
18
+ class Azure
19
+ # A firewall ruleset as configured in {MU::Config::BasketofKittens::firewall_rules}
20
+ class FirewallRule < MU::Cloud::FirewallRule
21
+
22
+ @admin_sgs = Hash.new
23
+ @admin_sg_semaphore = Mutex.new
24
+
25
+ # 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.
26
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
27
+ def initialize(**args)
28
+ super
29
+
30
+ if !mu_name.nil?
31
+ @mu_name = mu_name
32
+ else
33
+ @mu_name = @deploy.getResourceName(@config['name'], max_length: 61)
34
+ end
35
+
36
+ end
37
+
38
+ attr_reader :rulesets
39
+
40
+ # Called by {MU::Deploy#createResources}
41
+ def create
42
+ create_update
43
+ end
44
+
45
+ # Called by {MU::Deploy#createResources}
46
+ def groom
47
+ create_update
48
+
49
+ oldrules = {}
50
+ newrules = {}
51
+ cloud_desc.security_rules.each { |rule|
52
+ if rule.description and rule.description.match(/^#{Regexp.quote(@mu_name)} \d+:/)
53
+ oldrules[rule.name] = rule
54
+ end
55
+ }
56
+ used_priorities = oldrules.values.map { |r| r.priority }
57
+
58
+ newrules_semaphore = Mutex.new
59
+ num_rules = 0
60
+
61
+ rulethreads = []
62
+ return if !@config['rules']
63
+ @config['rules'].each { |rule_cfg|
64
+ num_rules += 1
65
+ rulethreads << Thread.new(rule_cfg, num_rules) { |rule, num|
66
+ was_new, desc = addRule(
67
+ rule["hosts"],
68
+ proto: rule["proto"],
69
+ port: rule["port"],
70
+ egress: rule["egress"],
71
+ port_range: rule["port_range"],
72
+ sgs: rule["sgs"],
73
+ lbs: rule["lbs"],
74
+ deny: rule["deny"],
75
+ weight: rule["weight"],
76
+ oldrules: oldrules,
77
+ num: num
78
+ )
79
+
80
+ newrules_semaphore.synchronize {
81
+ newrules[desc.name] = desc
82
+ if !was_new
83
+ oldrules[desc.name] = desc
84
+ end
85
+ }
86
+
87
+ } # rulethreads
88
+ }
89
+
90
+ rulethreads.each { |t|
91
+ t.join
92
+ }
93
+
94
+ # Purge old rules that we own (according to the description) but
95
+ # which are not part of our current configuration.
96
+ (oldrules.keys - newrules.keys).each { |oldrule|
97
+ MU.log "Dropping unused rule #{oldrule} from #{@mu_name}", MU::NOTICE
98
+ MU::Cloud::Azure.network(credentials: @config['credentials']).security_rules.delete(@resource_group, @mu_name, oldrule)
99
+ }
100
+
101
+ end
102
+
103
+ # Log metadata about this ruleset to the currently running deployment
104
+ def notify
105
+ MU.structToHash(cloud_desc)
106
+ end
107
+
108
+ # Insert a rule into an existing security group.
109
+ #
110
+ # @param hosts [Array<String>]: An array of CIDR network addresses to which this rule will apply.
111
+ # @param proto [String]: One of "tcp," "udp," or "icmp"
112
+ # @param port [Integer]: A port number. Only valid with udp or tcp.
113
+ # @param egress [Boolean]: Whether this is an egress ruleset, instead of ingress.
114
+ # @param port_range [String]: A port range descriptor (e.g. 0-65535). Only valid with udp or tcp.
115
+ # @return [Array<Boolean,OpenStruct>]
116
+ def addRule(hosts, proto: "tcp", port: nil, egress: false, port_range: "0-65535", sgs: [], lbs: [], deny: false, weight: nil, oldrules: nil, num: 0, description: "")
117
+ if !oldrules
118
+ oldrules = {}
119
+ cloud_desc(use_cache: false).security_rules.each { |rule|
120
+ if rule.description and rule.description.match(/^#{Regexp.quote(@mu_name)} \d+:/)
121
+ oldrules[rule.name] = rule
122
+ end
123
+ }
124
+ end
125
+ used_priorities = oldrules.values.map { |r| r.priority }
126
+
127
+ rule_obj = MU::Cloud::Azure.network(:SecurityRule).new
128
+ resolved_sgs = []
129
+ # XXX these are *Application* Security Groups, which are a different kind of
130
+ # artifact. They take no parameters. Are they essentially a stub that can be
131
+ # attached to certain artifacts to allow them to be referenced here?
132
+ # http://54.175.86.194/docs/azure/Azure/Network/Mgmt/V2019_02_01/ApplicationSecurityGroups.html#create_or_update-instance_method
133
+ if sgs
134
+ sgs.each { |sg|
135
+ # look up cloud id for... whatever these are
136
+ }
137
+ end
138
+
139
+ resolved_lbs = []
140
+ if lbs
141
+ lbs.each { |lb|
142
+ # TODO awaiting LoadBalancer implementation
143
+ }
144
+ end
145
+
146
+ if egress
147
+ rule_obj.direction = MU::Cloud::Azure.network(:SecurityRuleDirection)::Outbound
148
+ if hosts and !hosts.empty?
149
+ rule_obj.source_address_prefix = "*"
150
+ if hosts == ["*"]
151
+ rule_obj.destination_address_prefix = "*"
152
+ else
153
+ rule_obj.destination_address_prefixes = hosts
154
+ end
155
+ end
156
+ if !resolved_sgs.empty?
157
+ rule_obj.destination_application_security_groups = resolved_sgs
158
+ end
159
+ if !rule_obj.destination_application_security_groups and
160
+ !rule_obj.destination_address_prefix and
161
+ !rule_obj.destination_address_prefixes
162
+ rule_obj.source_address_prefix = "*"
163
+ rule_obj.destination_address_prefix = "*"
164
+ end
165
+ else
166
+ rule_obj.direction = MU::Cloud::Azure.network(:SecurityRuleDirection)::Inbound
167
+ if hosts and !hosts.empty?
168
+ if hosts == ["*"]
169
+ rule_obj.source_address_prefix = "*"
170
+ else
171
+ rule_obj.source_address_prefixes = hosts
172
+ end
173
+ rule_obj.destination_address_prefix = "*"
174
+ end
175
+ if !resolved_sgs.empty?
176
+ rule_obj.source_application_security_groups = resolved_sgs
177
+ end
178
+ if !rule_obj.source_application_security_groups and
179
+ !rule_obj.source_address_prefix and
180
+ !rule_obj.source_address_prefixes
181
+ # should probably only do this if a port or port_range is named
182
+ rule_obj.source_address_prefix = "*"
183
+ rule_obj.destination_address_prefix = "*"
184
+ end
185
+ end
186
+
187
+ rname_port = "port-"
188
+ if port and port.to_s != "-1"
189
+ rule_obj.destination_port_range = port.to_s
190
+ rname_port += port.to_s
191
+ elsif port_range and port_range != "-1"
192
+ rule_obj.destination_port_range = port_range
193
+ rname_port += port_range
194
+ else
195
+ rule_obj.destination_port_range = "*"
196
+ rname_port += "all"
197
+ end
198
+
199
+ # We don't bother supporting restrictions on originating ports,
200
+ # because practically nobody does that.
201
+ rule_obj.source_port_range = "*"
202
+
203
+ rule_obj.protocol = MU::Cloud::Azure.network(:SecurityRuleProtocol).const_get(proto.capitalize)
204
+ rname_proto = "proto-"+ (proto == "asterisk" ? "all" : proto)
205
+
206
+ if deny
207
+ rule_obj.access = MU::Cloud::Azure.network(:SecurityRuleAccess)::Deny
208
+ else
209
+ rule_obj.access = MU::Cloud::Azure.network(:SecurityRuleAccess)::Allow
210
+ end
211
+
212
+ rname = rule_obj.access.downcase+"-"+rule_obj.direction.downcase+"-"+rname_proto+"-"+rname_port+"-"+num.to_s
213
+
214
+ if weight
215
+ rule_obj.priority = weight
216
+ elsif oldrules[rname]
217
+ rule_obj.priority = oldrules[rname].priority
218
+ else
219
+ default_priority = 999
220
+ begin
221
+ default_priority += 1 + num
222
+ rule_obj.priority = default_priority
223
+ end while used_priorities.include?(default_priority)
224
+ end
225
+ used_priorities << rule_obj.priority
226
+
227
+ rule_obj.description = "#{@mu_name} #{num.to_s}: #{rname}"
228
+
229
+ # Now compare this to existing rules, and see if we need to update
230
+ # anything.
231
+ need_update = false
232
+ if oldrules[rname]
233
+ rule_obj.instance_variables.each { |var|
234
+ oldval = oldrules[rname].instance_variable_get(var)
235
+ newval = rule_obj.instance_variable_get(var)
236
+ need_update = true if oldval != newval
237
+ }
238
+
239
+ [:@destination_address_prefix, :@destination_address_prefixes,
240
+ :@destination_application_security_groups,
241
+ :@destination_address_prefix,
242
+ :@destination_address_prefixes,
243
+ :@destination_application_security_groups].each { |var|
244
+ next if !oldrules[rname].instance_variables.include?(var)
245
+ oldval = oldrules[rname].instance_variable_get(var)
246
+ newval = rule_obj.instance_variable_get(var)
247
+ if newval.nil? and !oldval.nil? and !oldval.empty?
248
+ need_update = true
249
+ end
250
+ }
251
+ else
252
+ need_update = true
253
+ end
254
+
255
+ if need_update
256
+ if oldrules[rname]
257
+ MU.log "Updating rule #{rname} in #{@mu_name}", MU::NOTICE, details: rule_obj
258
+ else
259
+ MU.log "Creating rule #{rname} in #{@mu_name}", details: rule_obj
260
+ end
261
+
262
+ resp = MU::Cloud::Azure.network(credentials: @config['credentials']).security_rules.create_or_update(@resource_group, @mu_name, rname, rule_obj)
263
+ return [!oldrules[rname].nil?, resp]
264
+ else
265
+ return [false, oldrules[rname]]
266
+ end
267
+
268
+ end
269
+
270
+ # Locate and return cloud provider descriptors of this resource type
271
+ # which match the provided parameters, or all visible resources if no
272
+ # filters are specified. At minimum, implementations of +find+ must
273
+ # honor +credentials+ and +cloud_id+ arguments. We may optionally
274
+ # support other search methods, such as +tag_key+ and +tag_value+, or
275
+ # cloud-specific arguments like +project+. See also {MU::MommaCat.findStray}.
276
+ # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
277
+ # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
278
+ def self.find(**args)
279
+ found = {}
280
+
281
+ # Azure resources are namedspaced by resource group. If we weren't
282
+ # told one, we may have to search all the ones we can see.
283
+ resource_groups = if args[:resource_group]
284
+ [args[:resource_group]]
285
+ elsif args[:cloud_id] and args[:cloud_id].is_a?(MU::Cloud::Azure::Id)
286
+ [args[:cloud_id].resource_group]
287
+ else
288
+ MU::Cloud::Azure.resources(credentials: args[:credentials]).resource_groups.list.map { |rg| rg.name }
289
+ end
290
+
291
+ if args[:cloud_id]
292
+ id_str = args[:cloud_id].is_a?(MU::Cloud::Azure::Id) ? args[:cloud_id].name : args[:cloud_id]
293
+ resource_groups.each { |rg|
294
+ begin
295
+ resp = MU::Cloud::Azure.network(credentials: args[:credentials]).network_security_groups.get(rg, id_str)
296
+ next if resp.nil?
297
+ found[Id.new(resp.id)] = resp
298
+ rescue MU::Cloud::Azure::APIError => e
299
+ # this is fine, we're doing a blind search after all
300
+ end
301
+ }
302
+ else
303
+ if args[:resource_group]
304
+ MU::Cloud::Azure.network(credentials: args[:credentials]).network_security_groups.list(args[:resource_group]).each { |net|
305
+ found[Id.new(net.id)] = net
306
+ }
307
+ else
308
+ MU::Cloud::Azure.network(credentials: args[:credentials]).network_security_groups.list_all.each { |net|
309
+ found[Id.new(net.id)] = net
310
+ }
311
+ end
312
+ end
313
+
314
+ found
315
+ end
316
+
317
+ # Does this resource type exist as a global (cloud-wide) artifact, or
318
+ # is it localized to a region/zone?
319
+ # @return [Boolean]
320
+ def self.isGlobal?
321
+ false
322
+ end
323
+
324
+ # Denote whether this resource implementation is experiment, ready for
325
+ # testing, or ready for production use.
326
+ def self.quality
327
+ MU::Cloud::BETA
328
+ end
329
+
330
+ # Stub method. Azure resources are cleaned up by removing the parent
331
+ # resource group.
332
+ # @return [void]
333
+ def self.cleanup(**args)
334
+ end
335
+
336
+ # Reverse-map our cloud description into a runnable config hash.
337
+ # We assume that any values we have in +@config+ are placeholders, and
338
+ # calculate our own accordingly based on what's live in the cloud.
339
+ def toKitten(rootparent: nil, billing: nil)
340
+ bok = {}
341
+
342
+ bok
343
+ end
344
+
345
+ # Cloud-specific configuration properties.
346
+ # @param config [MU::Config]: The calling MU::Config object
347
+ # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
348
+ def self.schema(config = nil)
349
+ toplevel_required = []
350
+ hosts_schema = MU::Config::CIDR_PRIMITIVE
351
+ hosts_schema["pattern"] = "^(\\d+\\.\\d+\\.\\d+\\.\\d+\/[0-9]{1,2}|\\*)$"
352
+ schema = {
353
+ "rules" => {
354
+ "items" => {
355
+ "properties" => {
356
+ "hosts" => {
357
+ "type" => "array",
358
+ "items" => hosts_schema
359
+ },
360
+ "weight" => {
361
+ "type" => "integer",
362
+ "description" => "Explicitly set a priority for this firewall rule, between 100 and 2096, with lower numbered priority rules having greater precedence."
363
+ },
364
+ "deny" => {
365
+ "type" => "boolean",
366
+ "default" => false,
367
+ "description" => "Set this rule to +DENY+ traffic instead of +ALLOW+"
368
+ },
369
+ "proto" => {
370
+ "description" => "The protocol to allow with this rule. The +standard+ keyword will expand to a series of identical rules covering +tcp+ and +udp; the +all+ keyword will allow all supported protocols. Currently only +tcp+ and +udp+ are supported by Azure, so the end result of these two keywords is identical.",
371
+ "enum" => ["all", "standard", "tcp", "udp"],
372
+ "default" => "standard"
373
+ },
374
+ # "source_tags" => {
375
+ # "type" => "array",
376
+ # "description" => "VMs with these tags, from which traffic will be allowed",
377
+ # "items" => {
378
+ # "type" => "string"
379
+ # }
380
+ # },
381
+ # "source_service_accounts" => {
382
+ # "type" => "array",
383
+ # "description" => "Resources using these service accounts, from which traffic will be allowed",
384
+ # "items" => {
385
+ # "type" => "string"
386
+ # }
387
+ # },
388
+ # "target_tags" => {
389
+ # "type" => "array",
390
+ # "description" => "VMs with these tags, to which traffic will be allowed",
391
+ # "items" => {
392
+ # "type" => "string"
393
+ # }
394
+ # },
395
+ # "target_service_accounts" => {
396
+ # "type" => "array",
397
+ # "description" => "Resources using these service accounts, to which traffic will be allowed",
398
+ # "items" => {
399
+ # "type" => "string"
400
+ # }
401
+ # }
402
+ }
403
+ }
404
+ },
405
+ }
406
+ [toplevel_required, schema]
407
+ end
408
+
409
+ # Cloud-specific pre-processing of {MU::Config::BasketofKittens::firewall_rules}, bare and unvalidated.
410
+ # @param acl [Hash]: The resource to process and validate
411
+ # @param config [MU::Config]: The overall deployment config of which this resource is a member
412
+ # @return [Boolean]: True if validation succeeded, False otherwise
413
+ def self.validateConfig(acl, config)
414
+ ok = true
415
+ acl['region'] ||= MU::Cloud::Azure.myRegion(acl['credentials'])
416
+
417
+ append = []
418
+ delete = []
419
+ acl['rules'] ||= []
420
+ acl['rules'].concat(config.adminFirewallRuleset(cloud: "Azure", region: acl['region'], rules_only: true))
421
+ acl['rules'].each { |r|
422
+ if r["weight"] and (r["weight"] < 100 or r["weight"] > 4096)
423
+ MU.log "FirewallRule #{acl['name']} weight must be between 100 and 4096", MU::ERR
424
+ ok = false
425
+ end
426
+ if r["hosts"]
427
+ r["hosts"].each { |cidr|
428
+ r["hosts"] << "*" if cidr == "0.0.0.0/0"
429
+ }
430
+ r["hosts"].delete("0.0.0.0/0")
431
+ end
432
+
433
+ if (!r['hosts'] or r['hosts'].empty?) and
434
+ (!r['lbs'] or r['lbs'].empty?) and
435
+ (!r['sgs'] or r['sgs'].empty?)
436
+ r["hosts"] = ["*"]
437
+ MU.log "FirewallRule #{acl['name']} did not specify any hosts, sgs or lbs, defaulting this rule to allow 0.0.0.0/0", MU::NOTICE
438
+ end
439
+
440
+
441
+ if r['proto'] == "standard"
442
+ ["tcp", "udp"].each { |p|
443
+ newrule = r.dup
444
+ newrule['proto'] = p
445
+ append << newrule
446
+ }
447
+ delete << r
448
+ elsif r['proto'] == "all" or !r['proto']
449
+ r['proto'] = "asterisk" # legit, the name of the constant
450
+ end
451
+ }
452
+ delete.each { |r|
453
+ acl['rules'].delete(r)
454
+ }
455
+ acl['rules'].concat(append)
456
+
457
+ ok
458
+ end
459
+
460
+ private
461
+
462
+ def create_update
463
+
464
+ fw_obj = MU::Cloud::Azure.network(:NetworkSecurityGroup).new
465
+ fw_obj.location = @config['region']
466
+ fw_obj.tags = @tags
467
+
468
+ need_apply = false
469
+ ext_ruleset = MU::Cloud::Azure.network(credentials: @config['credentials']).network_security_groups.get(
470
+ @resource_group,
471
+ @mu_name
472
+ )
473
+ if ext_ruleset
474
+ @cloud_id = MU::Cloud::Azure::Id.new(ext_ruleset.id)
475
+ end
476
+
477
+ if !ext_ruleset
478
+ MU.log "Creating Network Security Group #{@mu_name} in #{@config['region']}", details: fw_obj
479
+ need_apply = true
480
+ elsif ext_ruleset.location != fw_obj.location or
481
+ ext_ruleset.tags != fw_obj.tags
482
+ MU.log "Updating Network Security Group #{@mu_name} in #{@config['region']}", MU::NOTICE, details: fw_obj
483
+ need_apply = true
484
+ end
485
+
486
+ if need_apply
487
+ resp = MU::Cloud::Azure.network(credentials: @config['credentials']).network_security_groups.create_or_update(
488
+ @resource_group,
489
+ @mu_name,
490
+ fw_obj
491
+ )
492
+
493
+ @cloud_id = MU::Cloud::Azure::Id.new(resp.id)
494
+ end
495
+ end
496
+
497
+ end #class
498
+ end #class
499
+ end
500
+ end #module