1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
require "train/plugins"
require "ms_rest_azure"
require "azure_mgmt_resources"
require "azure_graph_rbac"
require "azure_mgmt_key_vault"
require "socket" unless defined?(Socket)
require "timeout" unless defined?(Timeout)
require "train/transports/helpers/azure/file_credentials"
require "train/transports/clients/azure/graph_rbac"
require "train/transports/clients/azure/vault"
module Train::Transports
class Azure < Train.plugin(1)
name "azure"
option :tenant_id, default: ENV["AZURE_TENANT_ID"]
option :client_id, default: ENV["AZURE_CLIENT_ID"]
option :client_secret, default: ENV["AZURE_CLIENT_SECRET"]
option :subscription_id, default: ENV["AZURE_SUBSCRIPTION_ID"]
option :msi_port, default: ENV["AZURE_MSI_PORT"] || "50342"
# This can provide the client id and secret
option :credentials_file, default: ENV["AZURE_CRED_FILE"]
def connection(_ = nil)
@connection ||= Connection.new(@options)
end
class Connection < BaseConnection
attr_reader :options
DEFAULT_FILE = ::File.join(Dir.home, ".azure", "credentials")
def initialize(options)
warn("Azure Connection with Train will be deprecated from Train 4")
@apis = {}
# Override for any cli options
# azure://subscription_id
options[:subscription_id] = options[:host] || options[:subscription_id]
super(options)
@cache_enabled[:api_call] = true
@cache[:api_call] = {}
if @options[:client_secret].nil? && @options[:client_id].nil?
options[:credentials_file] = DEFAULT_FILE if options[:credentials_file].nil?
@options.merge!(Helpers::Azure::FileCredentials.parse(**@options))
end
@options[:msi_port] = @options[:msi_port].to_i unless @options[:msi_port].nil?
# additional platform details
release = Gem.loaded_specs["azure_mgmt_resources"].version
@platform_details = { release: "azure_mgmt_resources-v#{release}" }
connect
end
def platform
force_platform!("azure", @platform_details)
end
def azure_client(klass = ::Azure::Resources::Profiles::Latest::Mgmt::Client, opts = {})
if cache_enabled?(:api_call)
return @cache[:api_call][klass.to_s.to_sym] unless @cache[:api_call][klass.to_s.to_sym].nil?
end
if klass == ::Azure::Resources::Profiles::Latest::Mgmt::Client
@credentials[:base_url] = MsRestAzure::AzureEnvironments::AzureCloud.resource_manager_endpoint_url
elsif klass == ::Azure::GraphRbac::Profiles::Latest::Client
client = GraphRbac.client(@credentials)
elsif klass == ::Azure::KeyVault::Profiles::Latest::Mgmt::Client
client = Vault.client(opts[:vault_name], @credentials)
end
client ||= klass.new(@credentials)
# Cache if enabled
@cache[:api_call][klass.to_s.to_sym] ||= client if cache_enabled?(:api_call)
client
end
def connect
if msi_auth?
# this needs set for azure cloud to authenticate
ENV["MSI_VM"] = "true"
provider = ::MsRestAzure::MSITokenProvider.new(@options[:msi_port])
else
provider = ::MsRestAzure::ApplicationTokenProvider.new(
@options[:tenant_id],
@options[:client_id],
@options[:client_secret]
)
end
@credentials = {
credentials: ::MsRest::TokenCredentials.new(provider),
subscription_id: @options[:subscription_id],
tenant_id: @options[:tenant_id],
}
@credentials[:client_id] = @options[:client_id] unless @options[:client_id].nil?
@credentials[:client_secret] = @options[:client_secret] unless @options[:client_secret].nil?
end
def uri
"azure://#{@options[:subscription_id]}"
end
# Returns the api version for the specified resource type
#
# If an api version has been specified in the options then the apis version table is updated
# with that value and it is returned
#
# However if it is not specified, or multiple types are being interrogated then this method
# will interrogate Azure for each of the types versions and pick the latest one. This is added
# to the apis table so that it can be retrieved quickly again of another one of those resources
# is encountered again in the resource collection.
#
# @param string resource_type The resource type for which the API is required
# @param hash options Options have that have been passed to the resource during the test.
# @option opts [String] :group_name Resource group name
# @option opts [String] :type Azure resource type
# @option opts [String] :name Name of specific resource to look for
# @option opts [String] :apiversion If looking for a specific item or type specify the api version to use
#
# @return string API Version of the specified resource type
def get_api_version(resource_type, options)
# if an api version has been set in the options, add to the apis hashtable with
# the resource type
if options[:apiversion]
@apis[resource_type] = options[:apiversion]
else
# only attempt to get the api version from Azure if the resource type
# is not present in the apis hashtable
unless @apis.key?(resource_type)
# determine the namespace for the resource type
namespace, type = resource_type.split(%r{/})
client = azure_client(::Azure::Resources::Profiles::Latest::Mgmt::Client)
provider = client.providers.get(namespace)
# get the latest API version for the type
# assuming that this is the first one in the list
api_versions = (provider.resource_types.find { |v| v.resource_type == type }).api_versions
@apis[resource_type] = api_versions[0]
end
end
# return the api version for the type
@apis[resource_type]
end
def unique_identifier
options[:subscription_id] || options[:tenant_id]
end
def msi_auth?
@options[:client_id].nil? && @options[:client_secret].nil? && port_open?(@options[:msi_port])
end
private
def port_open?(port, seconds = 3)
Timeout.timeout(seconds) do
TCPSocket.new("localhost", port).close
true
rescue SystemCallError
false
end
rescue Timeout::Error
false
end
end
end
end
|