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