cloud-mu 2.1.0beta → 3.0.0beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/modules/mu/clouds/azure.rb
CHANGED
@@ -12,27 +12,136 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require
|
16
|
-
require '
|
17
|
-
require '
|
18
|
-
require 'stringio'
|
15
|
+
require 'open-uri'
|
16
|
+
require 'json'
|
17
|
+
require 'timeout'
|
19
18
|
|
20
19
|
module MU
|
21
20
|
class Cloud
|
22
21
|
# Support for Microsoft Azure as a provisioning layer.
|
23
22
|
class Azure
|
24
23
|
@@is_in_azure = nil
|
24
|
+
@@metadata = nil
|
25
|
+
@@acct_to_profile_map = nil #WHAT EVEN IS THIS?
|
26
|
+
@@myRegion_var = nil
|
27
|
+
@@default_subscription = nil
|
28
|
+
@@regions = []
|
25
29
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
30
|
+
# Module used by {MU::Cloud} to insert additional instance methods into
|
31
|
+
# instantiated resources in this cloud layer.
|
32
|
+
module AdditionalResourceMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
# Exception class for exclusive use by {MU::Cloud::Azure::SDKClient::ClientCallWrapper}
|
36
|
+
class APIError < MU::MuError
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return a random Azure-valid GUID, because for some baffling reason some
|
40
|
+
# API calls expect us to roll our own.
|
41
|
+
def self.genGUID
|
42
|
+
hexchars = Array("a".."f") + Array(0..9)
|
43
|
+
guid_chunks = []
|
44
|
+
[8, 4, 4, 4, 12].each { |count|
|
45
|
+
guid_chunks << Array.new(count) { hexchars.sample }.join
|
46
|
+
}
|
47
|
+
guid_chunks.join("-")
|
29
48
|
end
|
30
49
|
|
50
|
+
# A hook that is always called just before any of the instance method of
|
51
|
+
# our resource implementations gets invoked, so that we can ensure that
|
52
|
+
# repetitive setup tasks (like resolving +:resource_group+ for Azure
|
53
|
+
# resources) have always been done.
|
54
|
+
# @param cloudobj [MU::Cloud]
|
55
|
+
# @param deploy [MU::MommaCat]
|
56
|
+
def self.resourceInitHook(cloudobj, deploy)
|
57
|
+
class << self
|
58
|
+
attr_reader :resource_group
|
59
|
+
end
|
60
|
+
return if !cloudobj
|
61
|
+
|
62
|
+
rg = if !deploy
|
63
|
+
return if !hosted?
|
64
|
+
MU.myInstanceId.resource_group
|
65
|
+
else
|
66
|
+
region = cloudobj.config['region'] || MU::Cloud::Azure.myRegion(cloudobj.config['credentials'])
|
67
|
+
deploy.deploy_id+"-"+region.upcase
|
68
|
+
end
|
69
|
+
|
70
|
+
cloudobj.instance_variable_set(:@resource_group, rg)
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Any cloud-specific instance methods we require our resource implementations to have, above and beyond the ones specified by {MU::Cloud}
|
75
|
+
# @return [Array<Symbol>]
|
76
|
+
def self.required_instance_methods
|
77
|
+
[:resource_group]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Stub class to represent Azure's resource identifiers, which look like:
|
81
|
+
# /subscriptions/3d20ddd8-4652-4074-adda-0d127ef1f0e0/resourceGroups/mu/providers/Microsoft.Network/virtualNetworks/mu-vnet
|
82
|
+
# Various API calls need chunks of this in different contexts, and this
|
83
|
+
# full string is necessary to guarantee that a +cloud_id+ is a unique
|
84
|
+
# identifier for a given resource. So we'll use this object of our own
|
85
|
+
# devising to represent it.
|
86
|
+
class Id
|
87
|
+
attr_reader :subscription
|
88
|
+
attr_reader :resource_group
|
89
|
+
attr_reader :provider
|
90
|
+
attr_reader :type
|
91
|
+
attr_reader :name
|
92
|
+
attr_reader :raw
|
93
|
+
|
94
|
+
# The name of the attribute on a cloud object from this provider which
|
95
|
+
# has the provider's long-form cloud identifier (Google Cloud URL,
|
96
|
+
# Amazon ARN, etc).
|
97
|
+
def self.idattr
|
98
|
+
:id
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(*args)
|
102
|
+
if args.first.is_a?(String)
|
103
|
+
@raw = args.first
|
104
|
+
junk, junk, @subscription, junk, @resource_group, junk, @provider, @resource_type, @name = @raw.split(/\//)
|
105
|
+
if @subscription.nil? or @resource_group.nil? or @provider.nil? or @resource_type.nil? or @name.nil?
|
106
|
+
# Not everything has a resource group
|
107
|
+
if @raw.match(/^\/subscriptions\/#{Regexp.quote(@subscription)}\/providers/)
|
108
|
+
junk, junk, @subscription, junk, @provider, @resource_type, @name = @raw.split(/\//)
|
109
|
+
if @subscription.nil? or @provider.nil? or @resource_type.nil? or @name.nil?
|
110
|
+
raise MuError, "Failed to parse Azure resource id string #{@raw} (got subscription: #{@subscription}, provider: #{@provider}, resource_type: #{@resource_type}, name: #{@name}"
|
111
|
+
end
|
112
|
+
|
113
|
+
else
|
114
|
+
raise MuError, "Failed to parse Azure resource id string #{@raw} (got subscription: #{@subscription}, resource_group: #{@resource_group}, provider: #{@provider}, resource_type: #{@resource_type}, name: #{@name}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
else
|
118
|
+
args.each { |arg|
|
119
|
+
if arg.is_a?(Hash)
|
120
|
+
arg.each_pair { |k, v|
|
121
|
+
self.instance_variable_set(("@"+k.to_s).to_sym, v)
|
122
|
+
}
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
if @resource_group.nil? or @name.nil?
|
127
|
+
raise MuError, "Failed to extract at least name and resource_group fields from #{args.flatten.join(", ").to_s}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return a reasonable string representation of this {MU::Cloud::Azure::Id}
|
133
|
+
def to_s
|
134
|
+
@name
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# UTILITY METHODS
|
31
140
|
# Determine whether we (the Mu master, presumably) are hosted in Azure.
|
32
141
|
# @return [Boolean]
|
33
142
|
def self.hosted?
|
34
|
-
if $MU_CFG.has_key?("azure_is_hosted")
|
35
|
-
@@
|
143
|
+
if $MU_CFG and $MU_CFG.has_key?("azure_is_hosted")
|
144
|
+
@@is_in_azure = $MU_CFG["azure_is_hosted"]
|
36
145
|
return $MU_CFG["azure_is_hosted"]
|
37
146
|
end
|
38
147
|
|
@@ -56,52 +165,330 @@ module MU
|
|
56
165
|
false
|
57
166
|
end
|
58
167
|
|
168
|
+
# If we reside in this cloud, return the VPC in which we, the Mu Master, reside.
|
169
|
+
# @return [MU::Cloud::VPC]
|
170
|
+
def self.myVPC
|
171
|
+
return nil if !hosted?
|
172
|
+
# XXX do me
|
173
|
+
end
|
174
|
+
|
175
|
+
# Alias for #{MU::Cloud::Azure.hosted?}
|
176
|
+
def self.hosted
|
177
|
+
return MU::Cloud::Azure.hosted?
|
178
|
+
end
|
179
|
+
|
180
|
+
# If we're running this cloud, return the $MU_CFG blob we'd use to
|
181
|
+
# describe this environment as our target one.
|
59
182
|
def self.hosted_config
|
60
|
-
|
183
|
+
return nil if !hosted?
|
184
|
+
region = get_metadata()['compute']['location']
|
185
|
+
subscription = get_metadata()['compute']['subscriptionId']
|
186
|
+
{
|
187
|
+
"region" => region,
|
188
|
+
"subscriptionId" => subscription
|
189
|
+
}
|
61
190
|
end
|
62
191
|
|
63
|
-
#
|
64
|
-
#
|
65
|
-
|
66
|
-
|
192
|
+
# Azure's API response objects don't implement +to_h+, so we'll wing it
|
193
|
+
# ourselves
|
194
|
+
# @param struct [MsRestAzure]
|
195
|
+
# @return [Hash]
|
196
|
+
def self.respToHash(struct)
|
197
|
+
hash = {}
|
198
|
+
struct.class.instance_methods(false).each { |m|
|
199
|
+
next if m.to_s.match(/=$/)
|
200
|
+
hash[m.to_s] = struct.send(m)
|
201
|
+
}
|
202
|
+
struct.instance_variables.each { |a|
|
203
|
+
hash[a.to_s.sub(/^@/, "")] = struct.instance_variable_get(a)
|
204
|
+
}
|
205
|
+
hash
|
67
206
|
end
|
68
207
|
|
69
|
-
|
70
|
-
|
208
|
+
# Method that returns the default Azure region for this Mu Master
|
209
|
+
# @return [string]
|
210
|
+
def self.myRegion(credentials = nil)
|
211
|
+
if @@myRegion_var
|
212
|
+
return @@myRegion_var
|
213
|
+
end
|
214
|
+
|
215
|
+
cfg = credConfig(credentials)
|
216
|
+
|
217
|
+
@@myRegion_var = if cfg['default_region']
|
218
|
+
cfg['default_region']
|
219
|
+
elsif MU::Cloud::Azure.hosted?
|
220
|
+
# IF WE ARE HOSTED IN AZURE CHECK FOR THE REGION OF THE INSTANCE
|
221
|
+
metadata = get_metadata()
|
222
|
+
metadata['compute']['location']
|
223
|
+
else
|
224
|
+
"eastus"
|
225
|
+
end
|
226
|
+
|
227
|
+
return @@myRegion_var
|
228
|
+
end
|
229
|
+
|
230
|
+
# lookup the default subscription that will be used by methods
|
231
|
+
def self.default_subscription(credentials = nil)
|
232
|
+
cfg = credConfig(credentials)
|
233
|
+
if @@default_subscription.nil?
|
234
|
+
if cfg['subscription']
|
235
|
+
# MU.log "Found default subscription in mu.yml. Using that..."
|
236
|
+
@@default_subscription = cfg['subscription']
|
237
|
+
|
238
|
+
elsif listSubscriptions().length == 1
|
239
|
+
#MU.log "Found a single subscription on your account. Using that... (This may be incorrect)", MU::WARN, details: e.message
|
240
|
+
@@default_subscription = listSubscriptions()[0]
|
241
|
+
|
242
|
+
elsif MU::Cloud::Azure.hosted?
|
243
|
+
#MU.log "Found a subscriptionID in my metadata. Using that... (This may be incorrect)", MU::WARN, details: e.message
|
244
|
+
@@default_subscription = get_metadata()['compute']['subscriptionId']
|
245
|
+
|
246
|
+
else
|
247
|
+
raise MuError, "Default Subscription was not found. Please run mu-configure to setup a default subscription"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
return @@default_subscription
|
252
|
+
end
|
253
|
+
|
254
|
+
# List visible Azure regions
|
255
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
256
|
+
# return [Array<String>]
|
257
|
+
def self.listRegions(credentials: nil)
|
258
|
+
cfg = credConfig(credentials)
|
259
|
+
return nil if !cfg and !hosted?
|
260
|
+
subscription = cfg['subscription']
|
261
|
+
subscription ||= default_subscription()
|
262
|
+
|
263
|
+
if @@regions.length() > 0 && subscription == default_subscription()
|
264
|
+
return @@regions
|
265
|
+
end
|
266
|
+
|
267
|
+
begin
|
268
|
+
sdk_response = MU::Cloud::Azure.subs(credentials: credentials).subscriptions().list_locations(subscription)
|
269
|
+
rescue Exception => e
|
270
|
+
MU.log e.inspect, MU::ERR, details: e.backtrace
|
271
|
+
#pp "Error Getting the list of regions from Azure" #TODO: SWITCH THIS TO MU LOG
|
272
|
+
return @@regions if @@regions and @@regions.size > 0
|
273
|
+
raise e
|
274
|
+
end
|
275
|
+
|
276
|
+
sdk_response.value.each do | region |
|
277
|
+
@@regions.push(region.name)
|
278
|
+
end
|
279
|
+
|
280
|
+
return @@regions
|
71
281
|
end
|
72
282
|
|
73
|
-
|
74
|
-
|
283
|
+
# List subscriptions visible to the given credentials
|
284
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
285
|
+
# return [Array<String>]
|
286
|
+
def self.listSubscriptions(credentials = nil)
|
287
|
+
subscriptions = []
|
288
|
+
|
289
|
+
sdk_response = MU::Cloud::Azure.subs(credentials: credentials).subscriptions().list
|
290
|
+
|
291
|
+
sdk_response.each do |subscription|
|
292
|
+
subscriptions.push(subscription.subscription_id)
|
293
|
+
end
|
294
|
+
|
295
|
+
return subscriptions
|
75
296
|
end
|
76
297
|
|
298
|
+
# List the Availability Zones associated with a given Azure region.
|
299
|
+
# If no region is given, search the one in which this MU master
|
300
|
+
# server resides (if it resides in this cloud provider's ecosystem).
|
301
|
+
# @param region [String]: The region to search.
|
302
|
+
# @return [Array<String>]: The Availability Zones in this region.
|
77
303
|
def self.listAZs(region = nil)
|
78
|
-
[]
|
304
|
+
az_list = ['1', '2', '3']
|
305
|
+
|
306
|
+
# Pulled from this chart: https://docs.microsoft.com/en-us/azure/availability-zones/az-overview#services-support-by-region
|
307
|
+
az_enabled_regions = ['centralus', 'eastus', 'eastus2', 'westus2', 'francecentral', 'northeurope', 'uksouth', 'westeurope', 'japaneast', 'southeastasia']
|
308
|
+
|
309
|
+
if not az_enabled_regions.include?(region)
|
310
|
+
az_list = []
|
311
|
+
end
|
312
|
+
|
313
|
+
return az_list
|
79
314
|
end
|
80
315
|
|
316
|
+
# A non-working example configuration
|
81
317
|
def self.config_example
|
82
|
-
|
318
|
+
sample = hosted_config
|
319
|
+
sample ||= {
|
320
|
+
"region" => "eastus",
|
321
|
+
"subscriptionId" => "99999999-9999-9999-9999-999999999999",
|
322
|
+
}
|
323
|
+
|
324
|
+
sample["credentials_file"] = "~/.azure/credentials"
|
325
|
+
sample["log_bucket_name"] = "my-mu-s3-bucket"
|
326
|
+
sample
|
83
327
|
end
|
84
328
|
|
85
|
-
|
86
|
-
|
329
|
+
# Do cloud-specific deploy instantiation tasks, such as copying SSH keys
|
330
|
+
# around, sticking secrets in buckets, creating resource groups, etc
|
331
|
+
# @param deploy [MU::MommaCat]
|
332
|
+
def self.initDeploy(deploy)
|
333
|
+
deploy.credsUsed.each { |creds|
|
334
|
+
next if !credConfig(creds)
|
335
|
+
listRegions.each { |region|
|
336
|
+
next if !deploy.regionsUsed.include?(region)
|
337
|
+
begin
|
338
|
+
createResourceGroup(deploy.deploy_id+"-"+region.upcase, region, credentials: creds)
|
339
|
+
rescue ::MsRestAzure::AzureOperationError
|
340
|
+
end
|
341
|
+
}
|
342
|
+
}
|
87
343
|
end
|
88
344
|
|
345
|
+
@@rg_semaphore = Mutex.new
|
346
|
+
|
347
|
+
# Purge cloud-specific deploy meta-artifacts (SSH keys, resource groups,
|
348
|
+
# etc)
|
349
|
+
# @param deploy_id [String]
|
350
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
351
|
+
def self.cleanDeploy(deploy_id, credentials: nil, noop: false)
|
352
|
+
threads = []
|
353
|
+
|
354
|
+
@@rg_semaphore.synchronize {
|
355
|
+
MU::Cloud::Azure.resources(credentials: credentials).resource_groups.list.each { |rg|
|
356
|
+
if rg.tags and rg.tags["MU-ID"] == deploy_id
|
357
|
+
threads << Thread.new(rg) { |rg_obj|
|
358
|
+
Thread.abort_on_exception = false
|
359
|
+
MU.log "Removing resource group #{rg_obj.name} from #{rg_obj.location}"
|
360
|
+
if !noop
|
361
|
+
MU::Cloud::Azure.resources(credentials: credentials).resource_groups.delete(rg_obj.name)
|
362
|
+
end
|
363
|
+
}
|
364
|
+
end
|
365
|
+
}
|
366
|
+
threads.each { |t|
|
367
|
+
t.join
|
368
|
+
}
|
369
|
+
}
|
370
|
+
end
|
371
|
+
|
372
|
+
# Azure resources are deployed into a containing artifact called a Resource Group, which we will map 1:1 with Mu deployments
|
373
|
+
# @param name [String]: A name for this resource group
|
374
|
+
# @param region [String]: The region in which to create this resource group
|
375
|
+
def self.createResourceGroup(name, region, credentials: nil)
|
376
|
+
rg_obj = MU::Cloud::Azure.resources(:ResourceGroup).new
|
377
|
+
rg_obj.location = region
|
378
|
+
rg_obj.tags = MU::MommaCat.listStandardTags
|
379
|
+
|
380
|
+
MU::Cloud::Azure.resources(credentials: credentials).resource_groups.list.each { |rg|
|
381
|
+
if rg.name == name and rg.location == region and rg.tags == rg_obj.tags
|
382
|
+
MU.log "Resource group #{name} already exists in #{region}", MU::DEBUG, details: rg_obj
|
383
|
+
return rg # already exists? Do nothing
|
384
|
+
end
|
385
|
+
}
|
386
|
+
MU.log "Configuring resource group #{name} in #{region}", details: rg_obj
|
387
|
+
|
388
|
+
MU::Cloud::Azure.resources(credentials: credentials).resource_groups.create_or_update(
|
389
|
+
name,
|
390
|
+
rg_obj
|
391
|
+
)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
|
395
|
+
# @param deploy_id [String]: The deploy for which we're writing the secret
|
396
|
+
# @param value [String]: The contents of the secret
|
397
|
+
def self.writeDeploySecret(deploy_id, value, name = nil, credentials: nil)
|
398
|
+
# XXX this ain't it hoss
|
399
|
+
end
|
400
|
+
|
401
|
+
# Return the name strings of all known sets of credentials for this cloud
|
402
|
+
# @return [Array<String>]
|
89
403
|
def self.listCredentials
|
90
|
-
|
404
|
+
if !$MU_CFG['azure']
|
405
|
+
return hosted? ? ["#default"] : nil
|
406
|
+
end
|
407
|
+
|
408
|
+
$MU_CFG['azure'].keys
|
91
409
|
end
|
92
410
|
|
93
|
-
|
94
|
-
|
411
|
+
# Return what we think of as a cloud object's habitat. If this is not
|
412
|
+
# applicable, such as for a {Habitat} or {Folder}, returns nil.
|
413
|
+
# @param cloudobj [MU::Cloud::Azure]: The resource from which to extract the habitat id
|
414
|
+
# @return [String,nil]
|
415
|
+
def self.habitat(cloudobj, nolookup: false, deploy: nil)
|
416
|
+
nil # we don't know how to do anything with subscriptions yet, really
|
95
417
|
end
|
96
418
|
|
97
|
-
|
98
|
-
|
419
|
+
@@my_hosted_cfg = nil
|
420
|
+
# Return the $MU_CFG data associated with a particular profile/name/set of
|
421
|
+
# credentials. If no account name is specified, will return one flagged as
|
422
|
+
# default. Returns nil if Azure is not configured. Throws an exception if
|
423
|
+
# an account name is specified which does not exist.
|
424
|
+
# @param name [String]: The name of the key under 'azure' in mu.yaml to return
|
425
|
+
# @return [Hash,nil]
|
426
|
+
def self.credConfig (name = nil, name_only: false)
|
427
|
+
if !$MU_CFG['azure'] or !$MU_CFG['azure'].is_a?(Hash) or $MU_CFG['azure'].size == 0
|
428
|
+
return @@my_hosted_cfg if @@my_hosted_cfg
|
429
|
+
|
430
|
+
if hosted?
|
431
|
+
@@my_hosted_cfg = hosted_config
|
432
|
+
return name_only ? "#default" : @@my_hosted_cfg
|
433
|
+
end
|
434
|
+
|
435
|
+
return nil
|
436
|
+
end
|
437
|
+
|
438
|
+
if name.nil?
|
439
|
+
$MU_CFG['azure'].each_pair { |set, cfg|
|
440
|
+
if cfg['default']
|
441
|
+
return name_only ? set : cfg
|
442
|
+
end
|
443
|
+
}
|
444
|
+
else
|
445
|
+
if $MU_CFG['azure'][name]
|
446
|
+
return name_only ? name : $MU_CFG['azure'][name]
|
447
|
+
# elsif @@acct_to_profile_map[name.to_s]
|
448
|
+
# return name_only ? name : @@acct_to_profile_map[name.to_s]
|
449
|
+
end
|
450
|
+
# XXX whatever process might lead us to populate @@acct_to_profile_map with some mappings, like projectname -> account profile, goes here
|
451
|
+
return nil
|
452
|
+
end
|
453
|
+
|
99
454
|
end
|
100
|
-
|
455
|
+
|
456
|
+
@@instance_types = nil
|
457
|
+
# Query the Azure API for a list of valid instance types.
|
458
|
+
# @param region [String]: Supported machine types can vary from region to region, so we look for the set we're interested in specifically
|
459
|
+
# @return [Hash]
|
460
|
+
def self.listInstanceTypes(region = self.myRegion)
|
461
|
+
return @@instance_types if @@instance_types and @@instance_types[region]
|
462
|
+
if !MU::Cloud::Azure.default_subscription()
|
463
|
+
return {}
|
464
|
+
end
|
465
|
+
|
466
|
+
@@instance_types ||= {}
|
467
|
+
@@instance_types[region] ||= {}
|
468
|
+
result = MU::Cloud::Azure.compute.virtual_machine_sizes.list(region)
|
469
|
+
raise MuError, "Failed to fetch Azure instance type list" if !result
|
470
|
+
result.value.each { |type|
|
471
|
+
@@instance_types[region][type.name] ||= {}
|
472
|
+
@@instance_types[region][type.name]["memory"] = sprintf("%.1f", type.memory_in_mb/1024.0).to_f
|
473
|
+
@@instance_types[region][type.name]["vcpu"] = type.number_of_cores.to_f
|
474
|
+
@@instance_types[region][type.name]["ecu"] = type.number_of_cores
|
475
|
+
}
|
476
|
+
|
477
|
+
@@instance_types
|
478
|
+
end
|
479
|
+
|
480
|
+
# Resolve the administrative Cloud Storage bucket for a given credential
|
481
|
+
# set, or return a default.
|
482
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
483
|
+
# @return [String]
|
101
484
|
def self.adminBucketName(credentials = nil)
|
102
485
|
"TODO"
|
103
486
|
end
|
104
487
|
|
488
|
+
# Resolve the administrative Cloud Storage bucket for a given credential
|
489
|
+
# set, or return a default.
|
490
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
491
|
+
# @return [String]
|
105
492
|
def self.adminBucketUrl(credentials = nil)
|
106
493
|
"TODO"
|
107
494
|
end
|
@@ -109,28 +496,575 @@ module MU
|
|
109
496
|
#END REQUIRED METHODS
|
110
497
|
|
111
498
|
|
112
|
-
# Fetch
|
113
|
-
# @return [
|
114
|
-
def self.get_metadata()
|
115
|
-
|
116
|
-
|
499
|
+
# Fetch (ALL) Azure instance metadata
|
500
|
+
# @return [Hash, nil]
|
501
|
+
def self.get_metadata(svc = "instance", api_version = "2017-08-01", args: {}, debug: false)
|
502
|
+
loglevel = debug ? MU::NOTICE : MU::DEBUG
|
503
|
+
return @@metadata if svc == "instance" and @@metadata
|
504
|
+
base_url = "http://169.254.169.254/metadata/#{svc}"
|
505
|
+
args["api-version"] = api_version
|
506
|
+
arg_str = args.keys.map { |k| k.to_s+"="+args[k].to_s }.join("&")
|
507
|
+
|
117
508
|
begin
|
118
|
-
|
119
|
-
|
120
|
-
|
509
|
+
Timeout.timeout(2) do
|
510
|
+
resp = JSON.parse(open("#{base_url}/?#{arg_str}","Metadata"=>"true").read)
|
511
|
+
MU.log "curl -H Metadata:true "+"#{base_url}/?#{arg_str}", loglevel, details: resp
|
512
|
+
if svc != "instance"
|
513
|
+
return resp
|
514
|
+
else
|
515
|
+
@@metadata = resp
|
516
|
+
end
|
121
517
|
end
|
518
|
+
return @@metadata
|
122
519
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
520
|
+
rescue Timeout::Error => e
|
521
|
+
# MU.log "Timeout querying Azure Metadata"
|
522
|
+
return nil
|
523
|
+
rescue
|
524
|
+
# MU.log "Failed to get Azure MetaData."
|
128
525
|
return nil
|
129
526
|
end
|
130
527
|
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
528
|
|
529
|
+
# Map our SDK authorization options from MU configuration into an options
|
530
|
+
# hash that Azure understands. Raises an exception if any fields aren't
|
531
|
+
# available.
|
532
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
533
|
+
# @return [Hash]
|
534
|
+
def self.getSDKOptions(credentials = nil)
|
535
|
+
cfg = credConfig(credentials)
|
536
|
+
|
537
|
+
if cfg and MU::Cloud::Azure.hosted?
|
538
|
+
token = MU::Cloud::Azure.get_metadata("identity/oauth2/token", "2018-02-01", args: { "resource"=>"https://management.azure.com/" })
|
539
|
+
if !token
|
540
|
+
MU::Cloud::Azure.get_metadata("identity/oauth2/token", "2018-02-01", args: { "resource"=>"https://management.azure.com/" }, debug: true)
|
541
|
+
raise MuError, "Failed to get machine oauth token"
|
542
|
+
end
|
543
|
+
machine = MU::Cloud::Azure.get_metadata
|
544
|
+
return {
|
545
|
+
credentials: MsRest::TokenCredentials.new(token["access_token"]),
|
546
|
+
client_id: token["client_id"],
|
547
|
+
subscription: machine["compute"]["subscriptionId"],
|
548
|
+
subscription_id: machine["compute"]["subscriptionId"]
|
549
|
+
}
|
550
|
+
end
|
551
|
+
|
552
|
+
return nil if !cfg
|
553
|
+
|
554
|
+
map = { #... from mu.yaml-ese to Azure SDK-ese
|
555
|
+
"directory_id" => :tenant_id,
|
556
|
+
"client_id" => :client_id,
|
557
|
+
"client_secret" => :client_secret,
|
558
|
+
"subscription" => :subscription_id
|
559
|
+
}
|
135
560
|
|
561
|
+
options = {}
|
136
562
|
|
563
|
+
map.each_pair { |k, v|
|
564
|
+
options[v] = cfg[k] if cfg[k]
|
565
|
+
}
|
566
|
+
|
567
|
+
if cfg['credentials_file']
|
568
|
+
file = File.open cfg['credentials_file']
|
569
|
+
credfile = JSON.load file
|
570
|
+
map.each_pair { |k, v|
|
571
|
+
options[v] = credfile[k] if credfile[k]
|
572
|
+
}
|
573
|
+
end
|
574
|
+
|
575
|
+
missing = []
|
576
|
+
map.values.each { |v|
|
577
|
+
missing << v if !options[v]
|
578
|
+
}
|
579
|
+
|
580
|
+
if missing.size > 0
|
581
|
+
if (!credentials or credentials == "#default") and hosted?
|
582
|
+
# Let the SDK try to use machine credentials
|
583
|
+
return nil
|
584
|
+
end
|
585
|
+
raise MuError, "Missing fields while trying to load Azure SDK options for credential set #{credentials ? credentials : "<default>" }: #{missing.map { |m| m.to_s }.join(", ")}"
|
586
|
+
end
|
587
|
+
|
588
|
+
MU.log "Loaded credential set #{credentials ? credentials : "<default>" }", MU::DEBUG, details: options
|
589
|
+
|
590
|
+
return options
|
591
|
+
end
|
592
|
+
|
593
|
+
# Find or allocate a static public IP address resource
|
594
|
+
# @param resource_group [String]
|
595
|
+
# @param name [String]
|
596
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
597
|
+
# @param region [String]
|
598
|
+
# @param tags [Hash<String>]
|
599
|
+
# @return [Azure::Network::Mgmt::V2019_02_01::Models::PublicIPAddress]
|
600
|
+
def self.fetchPublicIP(resource_group, name, credentials: nil, region: nil, tags: nil)
|
601
|
+
if !name or !resource_group
|
602
|
+
raise MuError, "Must supply resource_group and name to create or retrieve an Azure PublicIPAddress"
|
603
|
+
end
|
604
|
+
region ||= MU::Cloud::Azure.myRegion(credentials)
|
605
|
+
|
606
|
+
resp = MU::Cloud::Azure.network(credentials: credentials).public_ipaddresses.get(resource_group, name)
|
607
|
+
if !resp
|
608
|
+
ip_obj = MU::Cloud::Azure.network(:PublicIPAddress).new
|
609
|
+
ip_obj.location = region
|
610
|
+
ip_obj.tags = tags if tags
|
611
|
+
ip_obj.public_ipallocation_method = "Dynamic"
|
612
|
+
MU.log "Allocating PublicIpAddress #{name}", details: ip_obj
|
613
|
+
resp = MU::Cloud::Azure.network(credentials: credentials).public_ipaddresses.create_or_update(resource_group, name, ip_obj)
|
614
|
+
end
|
615
|
+
|
616
|
+
resp
|
617
|
+
end
|
618
|
+
|
619
|
+
# BEGIN SDK STUBS
|
620
|
+
#
|
621
|
+
# Azure Subscription Manager API
|
622
|
+
# @param model [<Azure::Apis::Subscriptions::Mgmt::V2015_11_01::Models>]: If specified, will return the class ::Azure::Apis::Subscriptions::Mgmt::V2015_11_01::Models::model instead of an API client instance
|
623
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
624
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
625
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
626
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
627
|
+
def self.subs(model = nil, alt_object: nil, credentials: nil, model_version: "V2015_11_01")
|
628
|
+
require 'azure_mgmt_subscriptions'
|
629
|
+
|
630
|
+
if model and model.is_a?(Symbol)
|
631
|
+
return Object.const_get("Azure").const_get("Subscriptions").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
632
|
+
else
|
633
|
+
@@subscriptions_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Subscriptions", credentials: credentials, subclass: alt_object)
|
634
|
+
end
|
635
|
+
|
636
|
+
return @@subscriptions_api[credentials]
|
637
|
+
end
|
638
|
+
|
639
|
+
# An alternative version of the Azure Subscription Manager API, which appears to support subscription creation
|
640
|
+
# @param model [<Azure::Apis::Subscriptions::Mgmt::V2018_03_01_preview::Models>]: If specified, will return the class ::Azure::Apis::Subscriptions::Mgmt::V2018_03_01_preview::Models::model instead of an API client instance
|
641
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
642
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
643
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
644
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
645
|
+
def self.subfactory(model = nil, alt_object: nil, credentials: nil, model_version: "V2018_03_01_preview")
|
646
|
+
require 'azure_mgmt_subscriptions'
|
647
|
+
|
648
|
+
if model and model.is_a?(Symbol)
|
649
|
+
return Object.const_get("Azure").const_get("Subscriptions").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
650
|
+
else
|
651
|
+
@@subscriptions_factory_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Subscriptions", credentials: credentials, profile: "V2018_03_01_preview", subclass: alt_object)
|
652
|
+
end
|
653
|
+
|
654
|
+
return @@subscriptions_factory_api[credentials]
|
655
|
+
end
|
656
|
+
|
657
|
+
# The Azure Compute API
|
658
|
+
# @param model [<Azure::Apis::Compute::Mgmt::V2019_04_01::Models>]: If specified, will return the class ::Azure::Apis::Compute::Mgmt::V2019_04_01::Models::model instead of an API client instance
|
659
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
660
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
661
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
662
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
663
|
+
def self.compute(model = nil, alt_object: nil, credentials: nil, model_version: "V2019_03_01")
|
664
|
+
require 'azure_mgmt_compute'
|
665
|
+
|
666
|
+
if model and model.is_a?(Symbol)
|
667
|
+
return Object.const_get("Azure").const_get("Compute").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
668
|
+
else
|
669
|
+
@@compute_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Compute", credentials: credentials, subclass: alt_object)
|
670
|
+
end
|
671
|
+
|
672
|
+
return @@compute_api[credentials]
|
673
|
+
end
|
674
|
+
|
675
|
+
# The Azure Network API
|
676
|
+
# @param model [<Azure::Apis::Network::Mgmt::V2019_02_01::Models>]: If specified, will return the class ::Azure::Apis::Network::Mgmt::V2019_02_01::Models::model instead of an API client instance
|
677
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
678
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
679
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
680
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
681
|
+
def self.network(model = nil, alt_object: nil, credentials: nil, model_version: "V2019_02_01")
|
682
|
+
require 'azure_mgmt_network'
|
683
|
+
|
684
|
+
if model and model.is_a?(Symbol)
|
685
|
+
return Object.const_get("Azure").const_get("Network").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
686
|
+
else
|
687
|
+
@@network_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Network", credentials: credentials, subclass: alt_object)
|
688
|
+
end
|
689
|
+
|
690
|
+
return @@network_api[credentials]
|
691
|
+
end
|
692
|
+
|
693
|
+
# The Azure Storage API
|
694
|
+
# @param model [<Azure::Apis::Storage::Mgmt::V2019_04_01::Models>]: If specified, will return the class ::Azure::Apis::Storage::Mgmt::V2019_04_01::Models::model instead of an API client instance
|
695
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
696
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
697
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
698
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
699
|
+
def self.storage(model = nil, alt_object: nil, credentials: nil, model_version: "V2019_04_01")
|
700
|
+
require 'azure_mgmt_storage'
|
701
|
+
|
702
|
+
if model and model.is_a?(Symbol)
|
703
|
+
return Object.const_get("Azure").const_get("Storage").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
704
|
+
else
|
705
|
+
@@storage_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Storage", credentials: credentials, subclass: alt_object)
|
706
|
+
end
|
707
|
+
|
708
|
+
return @@storage_api[credentials]
|
709
|
+
end
|
710
|
+
|
711
|
+
# The Azure ApiManagement API
|
712
|
+
# @param model [<Azure::Apis::ApiManagement::Mgmt::V2019_01_01::Models>]: If specified, will return the class ::Azure::Apis::ApiManagement::Mgmt::V2019_01_01::Models::model instead of an API client instance
|
713
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
714
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
715
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
716
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
717
|
+
def self.apis(model = nil, alt_object: nil, credentials: nil, model_version: "V2019_01_01")
|
718
|
+
require 'azure_mgmt_api_management'
|
719
|
+
|
720
|
+
if model and model.is_a?(Symbol)
|
721
|
+
return Object.const_get("Azure").const_get("ApiManagement").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
722
|
+
else
|
723
|
+
@@apis_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "ApiManagement", credentials: credentials, subclass: alt_object)
|
724
|
+
end
|
725
|
+
|
726
|
+
return @@apis_api[credentials]
|
727
|
+
end
|
728
|
+
|
729
|
+
# The Azure MarketplaceOrdering API
|
730
|
+
# @param model [<Azure::Apis::MarketplaceOrdering::Mgmt::V2015_06_01::Models>]: If specified, will return the class ::Azure::Apis::MarketplaceOrdering::Mgmt::V2015_06_01::Models::model instead of an API client instance
|
731
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
732
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
733
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
734
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
735
|
+
def self.marketplace(model = nil, alt_object: nil, credentials: nil, model_version: "V2015_06_01")
|
736
|
+
require 'azure_mgmt_marketplace_ordering'
|
737
|
+
|
738
|
+
if model and model.is_a?(Symbol)
|
739
|
+
return Object.const_get("Azure").const_get("Resources").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
740
|
+
else
|
741
|
+
@@marketplace_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "MarketplaceOrdering", credentials: credentials, subclass: alt_object)
|
742
|
+
end
|
743
|
+
|
744
|
+
return @@marketplace_api[credentials]
|
745
|
+
end
|
746
|
+
|
747
|
+
# The Azure Resources API
|
748
|
+
# @param model [<Azure::Apis::Resources::Mgmt::V2018_05_01::Models>]: If specified, will return the class ::Azure::Apis::Resources::Mgmt::V2018_05_01::Models::model instead of an API client instance
|
749
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
750
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
751
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
752
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
753
|
+
def self.resources(model = nil, alt_object: nil, credentials: nil, model_version: "V2018_05_01")
|
754
|
+
require 'azure_mgmt_resources'
|
755
|
+
|
756
|
+
if model and model.is_a?(Symbol)
|
757
|
+
return Object.const_get("Azure").const_get("Resources").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
758
|
+
else
|
759
|
+
@@resources_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Resources", credentials: credentials, subclass: alt_object)
|
760
|
+
end
|
761
|
+
|
762
|
+
return @@resources_api[credentials]
|
763
|
+
end
|
764
|
+
|
765
|
+
# The Azure Features API
|
766
|
+
# @param model [<Azure::Apis::Features::Mgmt::V2015_12_01::Models>]: If specified, will return the class ::Azure::Apis::Features::Mgmt::V2015_12_01::Models::model instead of an API client instance
|
767
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
768
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
769
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
770
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
771
|
+
def self.features(model = nil, alt_object: nil, credentials: nil, model_version: "V2015_12_01")
|
772
|
+
require 'azure_mgmt_features'
|
773
|
+
|
774
|
+
if model and model.is_a?(Symbol)
|
775
|
+
return Object.const_get("Azure").const_get("Features").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
776
|
+
else
|
777
|
+
@@features_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Features", credentials: credentials, subclass: alt_object)
|
778
|
+
end
|
779
|
+
|
780
|
+
return @@features_api[credentials]
|
781
|
+
end
|
782
|
+
|
783
|
+
# The Azure ContainerService API
|
784
|
+
# @param model [<Azure::Apis::ContainerService::Mgmt::V2019_04_01::Models>]: If specified, will return the class ::Azure::Apis::ContainerService::Mgmt::V2019_04_01::Models::model instead of an API client instance
|
785
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
786
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
787
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
788
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
789
|
+
def self.containers(model = nil, alt_object: nil, credentials: nil, model_version: "V2019_04_01")
|
790
|
+
require 'azure_mgmt_container_service'
|
791
|
+
|
792
|
+
if model and model.is_a?(Symbol)
|
793
|
+
return Object.const_get("Azure").const_get("ContainerService").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
794
|
+
else
|
795
|
+
@@containers_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "ContainerService", credentials: credentials, subclass: alt_object)
|
796
|
+
end
|
797
|
+
|
798
|
+
return @@containers_api[credentials]
|
799
|
+
end
|
800
|
+
|
801
|
+
# The Azure ManagedServiceIdentity API
|
802
|
+
# @param model [<Azure::Apis::ManagedServiceIdentity::Mgmt::V2015_08_31_preview::Models>]: If specified, will return the class ::Azure::Apis::ManagedServiceIdentity::Mgmt::V2015_08_31_preview::Models::model instead of an API client instance
|
803
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
804
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
805
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
806
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
807
|
+
def self.serviceaccts(model = nil, alt_object: nil, credentials: nil, model_version: "V2015_08_31_preview")
|
808
|
+
require 'azure_mgmt_msi'
|
809
|
+
|
810
|
+
if model and model.is_a?(Symbol)
|
811
|
+
return Object.const_get("Azure").const_get("ManagedServiceIdentity").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
812
|
+
else
|
813
|
+
@@service_identity_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "ManagedServiceIdentity", credentials: credentials, subclass: alt_object)
|
814
|
+
end
|
815
|
+
|
816
|
+
return @@service_identity_api[credentials]
|
817
|
+
end
|
818
|
+
|
819
|
+
# The Azure Authorization API
|
820
|
+
# @param model [<Azure::Apis::Authorization::Mgmt::V2015_07_01::Models>]: If specified, will return the class ::Azure::Apis::Authorization::Mgmt::V2015_07_01::Models::model instead of an API client instance
|
821
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
822
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
823
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
824
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
825
|
+
def self.authorization(model = nil, alt_object: nil, credentials: nil, model_version: "V2015_07_01", endpoint_profile: "Latest")
|
826
|
+
require 'azure_mgmt_authorization'
|
827
|
+
|
828
|
+
if model and model.is_a?(Symbol)
|
829
|
+
return Object.const_get("Azure").const_get("Authorization").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
830
|
+
else
|
831
|
+
@@authorization_api[credentials] ||= {}
|
832
|
+
@@authorization_api[credentials][endpoint_profile] ||= MU::Cloud::Azure::SDKClient.new(api: "Authorization", credentials: credentials, subclass: "AuthorizationManagementClass", profile: endpoint_profile)
|
833
|
+
end
|
834
|
+
|
835
|
+
return @@authorization_api[credentials][endpoint_profile]
|
836
|
+
end
|
837
|
+
|
838
|
+
# The Azure Billing API
|
839
|
+
# @param model [<Azure::Apis::Billing::Mgmt::V2018_03_01_preview::Models>]: If specified, will return the class ::Azure::Apis::Billing::Mgmt::V2018_03_01_preview::Models::model instead of an API client instance
|
840
|
+
# @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
|
841
|
+
# @param alt_object [String]: Return an instance of something other than the usual API client object
|
842
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
843
|
+
# @return [MU::Cloud::Azure::SDKClient]
|
844
|
+
def self.billing(model = nil, alt_object: nil, credentials: nil, model_version: "V2018_03_01_preview")
|
845
|
+
require 'azure_mgmt_billing'
|
846
|
+
|
847
|
+
if model and model.is_a?(Symbol)
|
848
|
+
return Object.const_get("Azure").const_get("Billing").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
|
849
|
+
else
|
850
|
+
@@billing_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "Billing", credentials: credentials, subclass: alt_object)
|
851
|
+
end
|
852
|
+
|
853
|
+
return @@billing_api[credentials]
|
854
|
+
end
|
855
|
+
|
856
|
+
# Make sure that a provider is enabled ("Registered" in Azure-ese).
|
857
|
+
# @param provider [String]: Provider name, typically formatted like +Microsoft.ContainerService+
|
858
|
+
# @param force [Boolean]: Run the operation even if the provider already appears to be enabled
|
859
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
860
|
+
def self.ensureProvider(provider, force: false, credentials: nil)
|
861
|
+
state = MU::Cloud::Azure.resources(credentials: credentials).providers.get(provider)
|
862
|
+
if state.registration_state != "Registered" or force
|
863
|
+
begin
|
864
|
+
if state.registration_state == "NotRegistered" or force
|
865
|
+
MU.log "Registering Provider #{provider}", MU::NOTICE
|
866
|
+
MU::Cloud::Azure.resources(credentials: credentials).providers.register(provider)
|
867
|
+
force = false
|
868
|
+
sleep 30
|
869
|
+
elsif state.registration_state == "Registering"
|
870
|
+
MU.log "Waiting for Provider #{provider} to finish registering", MU::NOTICE, details: state.registration_state
|
871
|
+
sleep 30
|
872
|
+
end
|
873
|
+
state = MU::Cloud::Azure.resources(credentials: credentials).providers.get(provider)
|
874
|
+
end while state and state.registration_state != "Registered"
|
875
|
+
end
|
876
|
+
end
|
877
|
+
|
878
|
+
# Make sure that a feature is enabled ("Registered" in Azure-ese), usually invoked for preview features which are off by default.
|
879
|
+
# @param feature_string [String]: The name of a feature, such as +WindowsPreview+
|
880
|
+
# @param credentials [String]: The credential set (subscription, effectively) in which to operate
|
881
|
+
def self.ensureFeature(feature_string, credentials: nil)
|
882
|
+
provider, feature = feature_string.split(/\//)
|
883
|
+
feature_state = MU::Cloud::Azure.features(credentials: credentials).features.get(provider, feature)
|
884
|
+
changed = false
|
885
|
+
begin
|
886
|
+
if feature_state
|
887
|
+
if feature_state.properties.state == "Registering"
|
888
|
+
MU.log "Waiting for Feature #{provider}/#{feature} to finish registering", MU::NOTICE, details: feature_state.properties.state
|
889
|
+
sleep 30
|
890
|
+
elsif feature_state.properties.state == "NotRegistered"
|
891
|
+
MU.log "Registering Feature #{provider}/#{feature}", MU::NOTICE
|
892
|
+
MU::Cloud::Azure.features(credentials: credentials).features.register(provider, feature)
|
893
|
+
changed = true
|
894
|
+
sleep 30
|
895
|
+
else
|
896
|
+
MU.log "#{provider}/#{feature} registration state: #{feature_state.properties.state}", MU::DEBUG
|
897
|
+
end
|
898
|
+
feature_state = MU::Cloud::Azure.features(credentials: credentials).features.get(provider, feature)
|
899
|
+
end
|
900
|
+
end while feature_state and feature_state.properties.state != "Registered"
|
901
|
+
ensureProvider(provider, credentials: credentials, force: true) if changed
|
902
|
+
end
|
903
|
+
|
904
|
+
# END SDK STUBS
|
905
|
+
|
906
|
+
# BEGIN SDK CLIENT
|
907
|
+
private
|
908
|
+
|
909
|
+
@@authorization_api = {}
|
910
|
+
@@subscriptions_api = {}
|
911
|
+
@@subscriptions_factory_api = {}
|
912
|
+
@@compute_api = {}
|
913
|
+
@@billing_api = {}
|
914
|
+
@@apis_api = {}
|
915
|
+
@@network_api = {}
|
916
|
+
@@storage_api = {}
|
917
|
+
@@resources_api = {}
|
918
|
+
@@containers_api = {}
|
919
|
+
@@features_api = {}
|
920
|
+
@@apis_api = {}
|
921
|
+
@@marketplace_api = {}
|
922
|
+
@@service_identity_api = {}
|
923
|
+
|
924
|
+
# Generic wrapper for connections to Azure APIs
|
925
|
+
class SDKClient
|
926
|
+
@api = nil
|
927
|
+
@credentials = nil
|
928
|
+
@cred_hash = nil
|
929
|
+
@wrappers = {}
|
930
|
+
|
931
|
+
attr_reader :issuer
|
932
|
+
attr_reader :subclass
|
933
|
+
attr_reader :api
|
934
|
+
|
935
|
+
def initialize(api: "Compute", credentials: nil, profile: "Latest", subclass: nil)
|
936
|
+
subclass ||= api.sub(/s$/, '')+"Client"
|
937
|
+
@subclass = subclass
|
938
|
+
@wrapper_semaphore = Mutex.new
|
939
|
+
@wrapper_semaphore.synchronize {
|
940
|
+
@wrappers ||= {}
|
941
|
+
}
|
942
|
+
|
943
|
+
@credentials = MU::Cloud::Azure.credConfig(credentials, name_only: true)
|
944
|
+
@cred_hash = MU::Cloud::Azure.getSDKOptions(credentials)
|
945
|
+
if !@cred_hash
|
946
|
+
raise MuError, "Failed to load Azure credentials #{credentials ? credentials : "<default>"}"
|
947
|
+
end
|
948
|
+
|
949
|
+
# There seem to be multiple ways to get at clients, and different
|
950
|
+
# profiles available depending which way you do it, so... try that?
|
951
|
+
stdpath = "::Azure::#{api}::Profiles::#{profile}::Mgmt::Client"
|
952
|
+
begin
|
953
|
+
# Standard approach: get a client from a canned, approved profile
|
954
|
+
@api = Object.const_get(stdpath).new(@cred_hash)
|
955
|
+
rescue NameError => e
|
956
|
+
raise e if !@cred_hash[:client_secret]
|
957
|
+
# Weird approach: generate our own credentials object and invoke a
|
958
|
+
# client directly from a particular model profile
|
959
|
+
token_provider = MsRestAzure::ApplicationTokenProvider.new(
|
960
|
+
@cred_hash[:tenant_id],
|
961
|
+
@cred_hash[:client_id],
|
962
|
+
@cred_hash[:client_secret]
|
963
|
+
)
|
964
|
+
@cred_obj = MsRest::TokenCredentials.new(token_provider)
|
965
|
+
begin
|
966
|
+
modelpath = "::Azure::#{api}::Mgmt::#{profile}::#{@subclass}"
|
967
|
+
@api = Object.const_get(modelpath).new(@cred_obj)
|
968
|
+
rescue NameError => e
|
969
|
+
raise MuError, "Unable to locate a profile #{profile} of Azure API #{api}. I tried:\n#{stdpath}\n#{modelpath}"
|
970
|
+
end
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
# For method calls into the Azure API
|
975
|
+
# @param method_sym [Symbol]
|
976
|
+
# @param arguments [Array]
|
977
|
+
def method_missing(method_sym, *arguments)
|
978
|
+
aoe_orig = Thread.abort_on_exception
|
979
|
+
Thread.abort_on_exception = false
|
980
|
+
@wrapper_semaphore.synchronize {
|
981
|
+
return @wrappers[method_sym] if @wrappers[method_sym]
|
982
|
+
}
|
983
|
+
# there's a low-key race condition here, but it's harmless and I'm
|
984
|
+
# trying to pin down an odd deadlock condition on cleanup calls
|
985
|
+
if !arguments.nil? and arguments.size == 1
|
986
|
+
retval = @api.method(method_sym).call(arguments[0])
|
987
|
+
elsif !arguments.nil? and arguments.size > 0
|
988
|
+
retval = @api.method(method_sym).call(*arguments)
|
989
|
+
else
|
990
|
+
retval = @api.method(method_sym).call
|
991
|
+
end
|
992
|
+
deep_retval = ClientCallWrapper.new(retval, method_sym.to_s, self)
|
993
|
+
@wrapper_semaphore.synchronize {
|
994
|
+
@wrappers[method_sym] ||= deep_retval
|
995
|
+
}
|
996
|
+
Thread.abort_on_exception = aoe_orig
|
997
|
+
return @wrappers[method_sym]
|
998
|
+
end
|
999
|
+
|
1000
|
+
# The Azure SDK embeds several "sub-APIs" in each SDK client, and most
|
1001
|
+
# API calls are made from these second-tier objects. We add an extra
|
1002
|
+
# wrapper layer for these so that we can gracefully handle errors,
|
1003
|
+
# retries, etc.
|
1004
|
+
class ClientCallWrapper
|
1005
|
+
|
1006
|
+
def initialize(myobject, myname, parent)
|
1007
|
+
@myobject = myobject
|
1008
|
+
@myname = myname
|
1009
|
+
@parent = parent
|
1010
|
+
@parentname = parent.subclass
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
# For method calls into the Azure API
|
1014
|
+
# @param method_sym [Symbol]
|
1015
|
+
# @param arguments [Array]
|
1016
|
+
def method_missing(method_sym, *arguments)
|
1017
|
+
MU.log "Calling #{@parentname}.#{@myname}.#{method_sym.to_s}", MU::DEBUG, details: arguments
|
1018
|
+
begin
|
1019
|
+
if !arguments.nil? and arguments.size == 1
|
1020
|
+
retval = @myobject.method(method_sym).call(arguments[0])
|
1021
|
+
elsif !arguments.nil? and arguments.size > 0
|
1022
|
+
retval = @myobject.method(method_sym).call(*arguments)
|
1023
|
+
else
|
1024
|
+
retval = @myobject.method(method_sym).call
|
1025
|
+
end
|
1026
|
+
rescue ::Net::ReadTimeout, ::Faraday::TimeoutError => e
|
1027
|
+
sleep 5
|
1028
|
+
retry
|
1029
|
+
rescue ::MsRestAzure::AzureOperationError, ::MsRest::HttpOperationError => e
|
1030
|
+
MU.log "Error calling #{@parent.api.class.name}.#{@myname}.#{method_sym.to_s}", MU::DEBUG, details: arguments
|
1031
|
+
begin
|
1032
|
+
parsed = JSON.parse(e.message)
|
1033
|
+
if parsed["response"] and parsed["response"]["body"]
|
1034
|
+
response = JSON.parse(parsed["response"]["body"])
|
1035
|
+
err = if response["code"] and response["message"]
|
1036
|
+
response
|
1037
|
+
elsif response["error"] and response["error"]["code"] and
|
1038
|
+
response["error"]["message"]
|
1039
|
+
response["error"]
|
1040
|
+
end
|
1041
|
+
if err
|
1042
|
+
if method_sym == :get and
|
1043
|
+
["ResourceNotFound", "NotFound"].include?(err["code"])
|
1044
|
+
return nil
|
1045
|
+
elsif err["code"] == "AnotherOperationInProgress"
|
1046
|
+
sleep 10
|
1047
|
+
retry
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
MU.log "#{@parent.api.class.name}.#{@myname}.#{method_sym.to_s} returned '"+err["code"]+"' - "+err["message"], MU::WARN, details: caller
|
1051
|
+
MU.log e.backtrace[0], MU::WARN, details: parsed
|
1052
|
+
raise MU::Cloud::Azure::APIError, err["code"]+": "+err["message"]+" (call was #{@parent.api.class.name}.#{@myname}.#{method_sym.to_s})"
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
rescue JSON::ParserError
|
1056
|
+
end
|
1057
|
+
# MU.log e.inspect, MU::ERR, details: caller
|
1058
|
+
# MU.log e.message, MU::ERR, details: @parent.credentials
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
retval
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
end
|
1067
|
+
# END SDK CLIENT
|
1068
|
+
end
|
1069
|
+
end
|
1070
|
+
end
|