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.
- checksums.yaml +5 -5
- data/Berksfile +4 -5
- data/Berksfile.lock +179 -0
- data/README.md +1 -6
- data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +0 -0
- data/ansible/roles/mu-installer/README.md +33 -0
- data/ansible/roles/mu-installer/defaults/main.yml +2 -0
- data/ansible/roles/mu-installer/handlers/main.yml +2 -0
- data/ansible/roles/mu-installer/meta/main.yml +60 -0
- data/ansible/roles/mu-installer/tasks/main.yml +13 -0
- data/ansible/roles/mu-installer/tests/inventory +2 -0
- data/ansible/roles/mu-installer/tests/test.yml +5 -0
- data/ansible/roles/mu-installer/vars/main.yml +2 -0
- data/bin/mu-adopt +125 -0
- data/bin/mu-aws-setup +4 -4
- data/bin/mu-azure-setup +265 -0
- data/bin/mu-azure-tests +43 -0
- data/bin/mu-cleanup +20 -8
- data/bin/mu-configure +224 -98
- data/bin/mu-deploy +8 -3
- data/bin/mu-gcp-setup +16 -8
- data/bin/mu-gen-docs +92 -8
- data/bin/mu-load-config.rb +52 -12
- data/bin/mu-momma-cat +36 -0
- data/bin/mu-node-manage +34 -27
- data/bin/mu-self-update +2 -2
- data/bin/mu-ssh +12 -8
- data/bin/mu-upload-chef-artifacts +11 -4
- data/bin/mu-user-manage +3 -0
- data/cloud-mu.gemspec +8 -11
- data/cookbooks/firewall/libraries/helpers_iptables.rb +2 -2
- data/cookbooks/firewall/metadata.json +1 -1
- data/cookbooks/firewall/recipes/default.rb +5 -9
- data/cookbooks/mu-firewall/attributes/default.rb +2 -0
- data/cookbooks/mu-firewall/metadata.rb +1 -1
- data/cookbooks/mu-glusterfs/templates/default/mu-gluster-client.erb +0 -0
- data/cookbooks/mu-master/Berksfile +2 -2
- data/cookbooks/mu-master/files/default/check_mem.pl +0 -0
- data/cookbooks/mu-master/files/default/cloudamatic.png +0 -0
- data/cookbooks/mu-master/metadata.rb +5 -4
- data/cookbooks/mu-master/recipes/389ds.rb +1 -1
- data/cookbooks/mu-master/recipes/basepackages.rb +30 -10
- data/cookbooks/mu-master/recipes/default.rb +59 -7
- data/cookbooks/mu-master/recipes/firewall-holes.rb +1 -1
- data/cookbooks/mu-master/recipes/init.rb +65 -47
- data/cookbooks/mu-master/recipes/{eks-kubectl.rb → kubectl.rb} +4 -10
- data/cookbooks/mu-master/recipes/sssd.rb +2 -1
- data/cookbooks/mu-master/recipes/update_nagios_only.rb +6 -6
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +2 -2
- data/cookbooks/mu-master/templates/mods/ldap.conf.erb +4 -0
- data/cookbooks/mu-php54/Berksfile +1 -2
- data/cookbooks/mu-php54/metadata.rb +4 -5
- data/cookbooks/mu-php54/recipes/default.rb +1 -1
- data/cookbooks/mu-splunk/templates/default/splunk-init.erb +0 -0
- data/cookbooks/mu-tools/Berksfile +3 -2
- data/cookbooks/mu-tools/files/default/Mu_CA.pem +33 -0
- data/cookbooks/mu-tools/libraries/helper.rb +20 -8
- data/cookbooks/mu-tools/metadata.rb +5 -2
- data/cookbooks/mu-tools/recipes/apply_security.rb +2 -3
- data/cookbooks/mu-tools/recipes/eks.rb +1 -1
- data/cookbooks/mu-tools/recipes/gcloud.rb +5 -30
- data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
- data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -0
- data/cookbooks/mu-tools/recipes/selinux.rb +19 -0
- data/cookbooks/mu-tools/recipes/split_var_partitions.rb +0 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +256 -122
- data/cookbooks/mu-tools/resources/disk.rb +3 -1
- data/cookbooks/mu-tools/templates/amazon/sshd_config.erb +1 -1
- data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
- data/cookbooks/mu-tools/templates/default/{kubeconfig.erb → kubeconfig-eks.erb} +0 -0
- data/cookbooks/mu-tools/templates/default/kubeconfig-gke.erb +27 -0
- data/cookbooks/mu-tools/templates/windows-10/sshd_config.erb +137 -0
- data/cookbooks/mu-utility/recipes/nat.rb +4 -0
- data/extras/alpha.png +0 -0
- data/extras/beta.png +0 -0
- data/extras/clean-stock-amis +2 -2
- data/extras/generate-stock-images +131 -0
- data/extras/git-fix-permissions-hook +0 -0
- data/extras/image-generators/AWS/centos6.yaml +17 -0
- data/extras/image-generators/{aws → AWS}/centos7-govcloud.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/centos7.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/rhel7.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/win2k12.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/win2k16.yaml +0 -0
- data/extras/image-generators/{aws → AWS}/windows.yaml +0 -0
- data/extras/image-generators/{gcp → Google}/centos6.yaml +1 -0
- data/extras/image-generators/Google/centos7.yaml +18 -0
- data/extras/python_rpm/build.sh +0 -0
- data/extras/release.png +0 -0
- data/extras/ruby_rpm/build.sh +0 -0
- data/extras/ruby_rpm/muby.spec +1 -1
- data/install/README.md +43 -5
- data/install/deprecated-bash-library.sh +0 -0
- data/install/installer +1 -1
- data/install/jenkinskeys.rb +0 -0
- data/install/mu-master.yaml +55 -0
- data/modules/mommacat.ru +41 -7
- data/modules/mu.rb +444 -149
- data/modules/mu/adoption.rb +500 -0
- data/modules/mu/cleanup.rb +235 -158
- data/modules/mu/cloud.rb +675 -138
- data/modules/mu/clouds/aws.rb +156 -24
- data/modules/mu/clouds/aws/alarm.rb +4 -14
- data/modules/mu/clouds/aws/bucket.rb +60 -18
- data/modules/mu/clouds/aws/cache_cluster.rb +8 -20
- data/modules/mu/clouds/aws/collection.rb +12 -22
- data/modules/mu/clouds/aws/container_cluster.rb +209 -118
- data/modules/mu/clouds/aws/database.rb +120 -45
- data/modules/mu/clouds/aws/dnszone.rb +7 -18
- data/modules/mu/clouds/aws/endpoint.rb +5 -15
- data/modules/mu/clouds/aws/firewall_rule.rb +144 -72
- data/modules/mu/clouds/aws/folder.rb +4 -11
- data/modules/mu/clouds/aws/function.rb +6 -16
- data/modules/mu/clouds/aws/group.rb +4 -12
- data/modules/mu/clouds/aws/habitat.rb +11 -13
- data/modules/mu/clouds/aws/loadbalancer.rb +40 -28
- data/modules/mu/clouds/aws/log.rb +5 -13
- data/modules/mu/clouds/aws/msg_queue.rb +9 -24
- data/modules/mu/clouds/aws/nosqldb.rb +4 -12
- data/modules/mu/clouds/aws/notifier.rb +6 -13
- data/modules/mu/clouds/aws/role.rb +69 -40
- data/modules/mu/clouds/aws/search_domain.rb +17 -20
- data/modules/mu/clouds/aws/server.rb +184 -94
- data/modules/mu/clouds/aws/server_pool.rb +33 -38
- data/modules/mu/clouds/aws/storage_pool.rb +5 -12
- data/modules/mu/clouds/aws/user.rb +59 -33
- data/modules/mu/clouds/aws/userdata/linux.erb +18 -30
- data/modules/mu/clouds/aws/userdata/windows.erb +9 -9
- data/modules/mu/clouds/aws/vpc.rb +214 -145
- data/modules/mu/clouds/azure.rb +978 -44
- data/modules/mu/clouds/azure/container_cluster.rb +413 -0
- data/modules/mu/clouds/azure/firewall_rule.rb +500 -0
- data/modules/mu/clouds/azure/habitat.rb +167 -0
- data/modules/mu/clouds/azure/loadbalancer.rb +205 -0
- data/modules/mu/clouds/azure/role.rb +211 -0
- data/modules/mu/clouds/azure/server.rb +810 -0
- data/modules/mu/clouds/azure/user.rb +257 -0
- data/modules/mu/clouds/azure/userdata/README.md +4 -0
- data/modules/mu/clouds/azure/userdata/linux.erb +137 -0
- data/modules/mu/clouds/azure/userdata/windows.erb +275 -0
- data/modules/mu/clouds/azure/vpc.rb +782 -0
- data/modules/mu/clouds/cloudformation.rb +12 -9
- data/modules/mu/clouds/cloudformation/firewall_rule.rb +5 -13
- data/modules/mu/clouds/cloudformation/server.rb +10 -1
- data/modules/mu/clouds/cloudformation/server_pool.rb +1 -0
- data/modules/mu/clouds/cloudformation/vpc.rb +0 -2
- data/modules/mu/clouds/google.rb +554 -117
- data/modules/mu/clouds/google/bucket.rb +173 -32
- data/modules/mu/clouds/google/container_cluster.rb +1112 -157
- data/modules/mu/clouds/google/database.rb +24 -47
- data/modules/mu/clouds/google/firewall_rule.rb +344 -89
- data/modules/mu/clouds/google/folder.rb +156 -79
- data/modules/mu/clouds/google/group.rb +272 -82
- data/modules/mu/clouds/google/habitat.rb +177 -52
- data/modules/mu/clouds/google/loadbalancer.rb +9 -34
- data/modules/mu/clouds/google/role.rb +1211 -0
- data/modules/mu/clouds/google/server.rb +491 -227
- data/modules/mu/clouds/google/server_pool.rb +233 -48
- data/modules/mu/clouds/google/user.rb +479 -125
- data/modules/mu/clouds/google/userdata/linux.erb +3 -3
- data/modules/mu/clouds/google/userdata/windows.erb +9 -9
- data/modules/mu/clouds/google/vpc.rb +381 -223
- data/modules/mu/config.rb +689 -214
- data/modules/mu/config/bucket.rb +1 -1
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/cache_cluster.yml +0 -4
- data/modules/mu/config/container_cluster.rb +18 -9
- data/modules/mu/config/database.rb +6 -23
- data/modules/mu/config/firewall_rule.rb +9 -15
- data/modules/mu/config/folder.rb +22 -21
- data/modules/mu/config/habitat.rb +22 -21
- data/modules/mu/config/loadbalancer.rb +2 -2
- data/modules/mu/config/role.rb +9 -40
- data/modules/mu/config/server.rb +26 -5
- data/modules/mu/config/server_pool.rb +1 -1
- data/modules/mu/config/storage_pool.rb +2 -2
- data/modules/mu/config/user.rb +4 -0
- data/modules/mu/config/vpc.rb +350 -110
- data/modules/mu/defaults/{amazon_images.yaml → AWS.yaml} +37 -39
- data/modules/mu/defaults/Azure.yaml +17 -0
- data/modules/mu/defaults/Google.yaml +24 -0
- data/modules/mu/defaults/README.md +1 -1
- data/modules/mu/deploy.rb +168 -125
- data/modules/mu/groomer.rb +2 -1
- data/modules/mu/groomers/ansible.rb +104 -32
- data/modules/mu/groomers/chef.rb +96 -44
- data/modules/mu/kittens.rb +20602 -0
- data/modules/mu/logger.rb +38 -11
- data/modules/mu/master.rb +90 -8
- data/modules/mu/master/chef.rb +2 -3
- data/modules/mu/master/ldap.rb +0 -1
- data/modules/mu/master/ssl.rb +250 -0
- data/modules/mu/mommacat.rb +917 -513
- data/modules/scratchpad.erb +1 -1
- data/modules/tests/super_complex_bok.yml +0 -0
- data/modules/tests/super_simple_bok.yml +0 -0
- data/roles/mu-master.json +2 -1
- data/spec/azure_creds +5 -0
- data/spec/mu.yaml +56 -0
- data/spec/mu/clouds/azure_spec.rb +164 -27
- data/spec/spec_helper.rb +5 -0
- data/test/clean_up.py +0 -0
- data/test/exec_inspec.py +0 -0
- data/test/exec_mu_install.py +0 -0
- data/test/exec_retry.py +0 -0
- data/test/smoke_test.rb +0 -0
- metadata +90 -118
- data/cookbooks/mu-jenkins/Berksfile +0 -14
- data/cookbooks/mu-jenkins/CHANGELOG.md +0 -13
- data/cookbooks/mu-jenkins/LICENSE +0 -37
- data/cookbooks/mu-jenkins/README.md +0 -105
- data/cookbooks/mu-jenkins/attributes/default.rb +0 -42
- data/cookbooks/mu-jenkins/files/default/cleanup_deploy_config.xml +0 -73
- data/cookbooks/mu-jenkins/files/default/deploy_config.xml +0 -44
- data/cookbooks/mu-jenkins/metadata.rb +0 -21
- data/cookbooks/mu-jenkins/recipes/default.rb +0 -195
- data/cookbooks/mu-jenkins/recipes/node-ssh-config.rb +0 -54
- data/cookbooks/mu-jenkins/recipes/public_key.rb +0 -24
- data/cookbooks/mu-jenkins/templates/default/example_job.config.xml.erb +0 -24
- data/cookbooks/mu-jenkins/templates/default/org.jvnet.hudson.plugins.SSHBuildWrapper.xml.erb +0 -14
- data/cookbooks/mu-jenkins/templates/default/ssh_config.erb +0 -6
- data/cookbooks/nagios/Berksfile +0 -11
- data/cookbooks/nagios/CHANGELOG.md +0 -589
- data/cookbooks/nagios/CONTRIBUTING.md +0 -11
- data/cookbooks/nagios/LICENSE +0 -37
- data/cookbooks/nagios/README.md +0 -328
- data/cookbooks/nagios/TESTING.md +0 -2
- data/cookbooks/nagios/attributes/config.rb +0 -171
- data/cookbooks/nagios/attributes/default.rb +0 -228
- data/cookbooks/nagios/chefignore +0 -102
- data/cookbooks/nagios/definitions/command.rb +0 -33
- data/cookbooks/nagios/definitions/contact.rb +0 -33
- data/cookbooks/nagios/definitions/contactgroup.rb +0 -33
- data/cookbooks/nagios/definitions/host.rb +0 -33
- data/cookbooks/nagios/definitions/hostdependency.rb +0 -33
- data/cookbooks/nagios/definitions/hostescalation.rb +0 -34
- data/cookbooks/nagios/definitions/hostgroup.rb +0 -33
- data/cookbooks/nagios/definitions/nagios_conf.rb +0 -38
- data/cookbooks/nagios/definitions/resource.rb +0 -33
- data/cookbooks/nagios/definitions/service.rb +0 -33
- data/cookbooks/nagios/definitions/servicedependency.rb +0 -33
- data/cookbooks/nagios/definitions/serviceescalation.rb +0 -34
- data/cookbooks/nagios/definitions/servicegroup.rb +0 -33
- data/cookbooks/nagios/definitions/timeperiod.rb +0 -33
- data/cookbooks/nagios/libraries/base.rb +0 -314
- data/cookbooks/nagios/libraries/command.rb +0 -91
- data/cookbooks/nagios/libraries/contact.rb +0 -230
- data/cookbooks/nagios/libraries/contactgroup.rb +0 -112
- data/cookbooks/nagios/libraries/custom_option.rb +0 -36
- data/cookbooks/nagios/libraries/data_bag_helper.rb +0 -23
- data/cookbooks/nagios/libraries/default.rb +0 -90
- data/cookbooks/nagios/libraries/host.rb +0 -412
- data/cookbooks/nagios/libraries/hostdependency.rb +0 -181
- data/cookbooks/nagios/libraries/hostescalation.rb +0 -173
- data/cookbooks/nagios/libraries/hostgroup.rb +0 -119
- data/cookbooks/nagios/libraries/nagios.rb +0 -282
- data/cookbooks/nagios/libraries/resource.rb +0 -59
- data/cookbooks/nagios/libraries/service.rb +0 -455
- data/cookbooks/nagios/libraries/servicedependency.rb +0 -215
- data/cookbooks/nagios/libraries/serviceescalation.rb +0 -195
- data/cookbooks/nagios/libraries/servicegroup.rb +0 -144
- data/cookbooks/nagios/libraries/timeperiod.rb +0 -160
- data/cookbooks/nagios/libraries/users_helper.rb +0 -54
- data/cookbooks/nagios/metadata.rb +0 -25
- data/cookbooks/nagios/recipes/_load_databag_config.rb +0 -153
- data/cookbooks/nagios/recipes/_load_default_config.rb +0 -241
- data/cookbooks/nagios/recipes/apache.rb +0 -48
- data/cookbooks/nagios/recipes/default.rb +0 -204
- data/cookbooks/nagios/recipes/nginx.rb +0 -82
- data/cookbooks/nagios/recipes/pagerduty.rb +0 -143
- data/cookbooks/nagios/recipes/server_package.rb +0 -40
- data/cookbooks/nagios/recipes/server_source.rb +0 -164
- data/cookbooks/nagios/templates/default/apache2.conf.erb +0 -96
- data/cookbooks/nagios/templates/default/cgi.cfg.erb +0 -266
- data/cookbooks/nagios/templates/default/commands.cfg.erb +0 -13
- data/cookbooks/nagios/templates/default/contacts.cfg.erb +0 -37
- data/cookbooks/nagios/templates/default/hostgroups.cfg.erb +0 -25
- data/cookbooks/nagios/templates/default/hosts.cfg.erb +0 -15
- data/cookbooks/nagios/templates/default/htpasswd.users.erb +0 -6
- data/cookbooks/nagios/templates/default/nagios.cfg.erb +0 -22
- data/cookbooks/nagios/templates/default/nginx.conf.erb +0 -62
- data/cookbooks/nagios/templates/default/pagerduty.cgi.erb +0 -185
- data/cookbooks/nagios/templates/default/resource.cfg.erb +0 -27
- data/cookbooks/nagios/templates/default/servicedependencies.cfg.erb +0 -15
- data/cookbooks/nagios/templates/default/servicegroups.cfg.erb +0 -14
- data/cookbooks/nagios/templates/default/services.cfg.erb +0 -14
- data/cookbooks/nagios/templates/default/templates.cfg.erb +0 -31
- data/cookbooks/nagios/templates/default/timeperiods.cfg.erb +0 -13
- data/extras/image-generators/aws/centos6.yaml +0 -18
- data/modules/mu/defaults/google_images.yaml +0 -16
- 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
|