Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pulls file credentials parsing out of Azure class #324

Merged
merged 6 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 3 additions & 37 deletions lib/train/transports/azure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
require 'train/plugins'
require 'ms_rest_azure'
require 'azure_mgmt_resources'
require 'inifile'
require 'socket'
require 'timeout'
require 'train/transports/helpers/azure/file_credentials'

module Train::Transports
class Azure < Train.plugin(1)
Expand All @@ -23,7 +23,7 @@ def connection(_ = nil)
@connection ||= Connection.new(@options)
end

class Connection < BaseConnection # rubocop:disable Metrics/ClassLength
class Connection < BaseConnection
attr_reader :options

def initialize(options)
Expand All @@ -38,7 +38,7 @@ def initialize(options)
@cache[:api_call] = {}

if @options[:client_secret].nil? && @options[:client_id].nil?
parse_credentials_file
@options.merge!(Helpers::Azure::FileCredentials.parse(@options))
end

@options[:msi_port] = @options[:msi_port].to_i unless @options[:msi_port].nil?
Expand Down Expand Up @@ -149,40 +149,6 @@ def port_open?(port, seconds = 1)
rescue Timeout::Error
false
end

def parse_credentials_file # rubocop:disable Metrics/AbcSize
# If an AZURE_CRED_FILE environment variable has been specified set the
# the credentials file to that, otherwise set the one in home
azure_creds_file = @options[:credentials_file]
azure_creds_file = File.join(Dir.home, '.azure', 'credentials') if azure_creds_file.nil?
return unless File.readable?(azure_creds_file)

credentials = IniFile.load(File.expand_path(azure_creds_file))
if @options[:subscription_id]
id = @options[:subscription_id]
elsif !ENV['AZURE_SUBSCRIPTION_NUMBER'].nil?
subscription_number = ENV['AZURE_SUBSCRIPTION_NUMBER'].to_i

# Check that the specified index is not greater than the number of subscriptions
if subscription_number > credentials.sections.length
raise format(
'Your credentials file only contains %s subscriptions. You specified number %s.',
@credentials.sections.length,
subscription_number,
)
end
id = credentials.sections[subscription_number - 1]
else
raise 'Multiple credentials detected, please set the AZURE_SUBSCRIPTION_ID environment variable.' if credentials.sections.count > 1
id = credentials.sections[0]
end

raise "No credentials found for subscription number #{id}" if credentials.sections.empty? || credentials[id].empty?
@options[:subscription_id] = id
@options[:tenant_id] = credentials[id]['tenant_id']
@options[:client_id] = credentials[id]['client_id']
@options[:client_secret] = credentials[id]['client_secret']
end
end
end
end
42 changes: 42 additions & 0 deletions lib/train/transports/helpers/azure/file_credentials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# encoding: utf-8

require 'inifile'
require 'train/transports/helpers/azure/file_parser'
require 'train/transports/helpers/azure/subscription_number_file_parser'
require 'train/transports/helpers/azure/subscription_id_file_parser'

module Train::Transports
module Helpers
module Azure
class FileCredentials
DEFAULT_FILE = ::File.join(Dir.home, '.azure', 'credentials')

def self.parse(subscription_id: nil, credentials_file: DEFAULT_FILE, **_)
return {} unless ::File.readable?(credentials_file)
credentials = IniFile.load(::File.expand_path(credentials_file))
subscription_id = parser(subscription_id, ENV['AZURE_SUBSCRIPTION_NUMBER'], credentials).subscription_id
creds(subscription_id, credentials)
end

def self.parser(subscription_id, subscription_number, credentials)
if subscription_id
SubscriptionIdFileParser.new(subscription_id, credentials)
elsif !subscription_number.nil?
SubscriptionNumberFileParser.new(subscription_number.to_i, credentials)
else
FileParser.new(credentials)
end
end

def self.creds(subscription_id, credentials)
{
subscription_id: subscription_id,
tenant_id: credentials[subscription_id]['tenant_id'],
client_id: credentials[subscription_id]['client_id'],
client_secret: credentials[subscription_id]['client_secret'],
}
end
end
end
end
end
25 changes: 25 additions & 0 deletions lib/train/transports/helpers/azure/file_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# encoding: utf-8

module Train::Transports
module Helpers
module Azure
class FileParser
def initialize(credentials)
@credentials = credentials

validate!
end

def validate!
return if @credentials.sections.count == 1

raise 'Credentials file must have one entry. Check your credentials file. If you have more than one entry set AZURE_SUBSCRIPTION_ID environment variable.'
end

def subscription_id
@subscription_id ||= @credentials.sections[0]
end
end
end
end
end
24 changes: 24 additions & 0 deletions lib/train/transports/helpers/azure/subscription_id_file_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# encoding: utf-8

module Train::Transports
module Helpers
module Azure
class SubscriptionIdFileParser
attr_reader :subscription_id

def initialize(subscription_id, credentials)
@subscription_id = subscription_id
@credentials = credentials

validate!
end

def validate!
if @credentials.sections.empty? || @credentials[subscription_id].empty?
raise "No credentials found for subscription number #{subscription_id}"
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# encoding: utf-8

module Train::Transports
module Helpers
module Azure
class SubscriptionNumberFileParser
def initialize(index, credentials)
@index = index
@credentials = credentials

validate!
end

def validate!
if @index == 0
raise 'Index must be greater than 0.'
end

if @index > @credentials.sections.length
raise "Your credentials file only contains #{@credentials.sections.length} subscriptions. You specified number #{@index}."
end
end

def subscription_id
@subscription_id ||= @credentials.sections[@index - 1]
end
end
end
end
end
55 changes: 0 additions & 55 deletions test/unit/transports/azure_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,59 +122,4 @@ def initialize(hash)
connection.unique_identifier.must_equal 'test_tenant_id'
end
end

describe 'parse_credentials_file' do
let(:cred_file) do
require 'tempfile'
file = Tempfile.new('cred_file')
info = <<-INFO
[my_subscription_id]
client_id = "my_client_id"
client_secret = "my_client_secret"
tenant_id = "my_tenant_id"

[my_subscription_id2]
client_id = "my_client_id2"
client_secret = "my_client_secret2"
tenant_id = "my_tenant_id2"
INFO
file.write(info)
file.close
file
end

it 'validate credentials from file' do
options[:credentials_file] = cred_file.path
options[:subscription_id] = 'my_subscription_id'
connection.send(:parse_credentials_file)

options[:tenant_id].must_equal 'my_tenant_id'
options[:client_id].must_equal 'my_client_id'
options[:client_secret].must_equal 'my_client_secret'
options[:subscription_id].must_equal 'my_subscription_id'
end

it 'validate credentials from file subscription override' do
options[:credentials_file] = cred_file.path
options[:subscription_id] = 'my_subscription_id2'
connection.send(:parse_credentials_file)

options[:tenant_id].must_equal 'my_tenant_id2'
options[:client_id].must_equal 'my_client_id2'
options[:client_secret].must_equal 'my_client_secret2'
options[:subscription_id].must_equal 'my_subscription_id2'
end

it 'validate credentials from file subscription index' do
options[:credentials_file] = cred_file.path
options[:subscription_id] = nil
ENV['AZURE_SUBSCRIPTION_NUMBER'] = '2'
connection.send(:parse_credentials_file)

options[:tenant_id].must_equal 'my_tenant_id2'
options[:client_id].must_equal 'my_client_id2'
options[:client_secret].must_equal 'my_client_secret2'
options[:subscription_id].must_equal 'my_subscription_id2'
end
end
end
121 changes: 121 additions & 0 deletions test/unit/transports/helpers/azure/file_credentials_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# encoding: utf-8

require 'helper'
require 'tempfile'
require 'train/transports/helpers/azure/file_credentials'

describe 'parse_credentials_file' do
let(:cred_file_single_entry) do
file = Tempfile.new('cred_file')
info = <<-INFO
[my_subscription_id]
client_id = "my_client_id"
client_secret = "my_client_secret"
tenant_id = "my_tenant_id"
INFO
file.write(info)
file.close
file
end

let(:cred_file_multiple_entries) do
file = Tempfile.new('cred_file')
info = <<-INFO
[my_subscription_id]
client_id = "my_client_id"
client_secret = "my_client_secret"
tenant_id = "my_tenant_id"

[my_subscription_id2]
client_id = "my_client_id2"
client_secret = "my_client_secret2"
tenant_id = "my_tenant_id2"
INFO
file.write(info)
file.close
file
end

let(:options) { { credentials_file: cred_file_multiple_entries.path } }

it 'returns empty hash when no credentials file detected' do
result = Train::Transports::Helpers::Azure::FileCredentials.parse({})

assert_empty(result)
end

it 'loads only entry from file when no subscription id given' do
options[:credentials_file] = cred_file_single_entry.path

result = Train::Transports::Helpers::Azure::FileCredentials.parse(options)

assert_equal('my_tenant_id', result[:tenant_id])
assert_equal('my_client_id', result[:client_id])
assert_equal('my_client_secret', result[:client_secret])
assert_equal('my_subscription_id', result[:subscription_id])
end

it 'raises an error when no subscription id given and multiple entries' do
error = assert_raises RuntimeError do
Train::Transports::Helpers::Azure::FileCredentials.parse(options)
end

assert_equal('Credentials file must have one entry. Check your credentials file. If you have more than one entry set AZURE_SUBSCRIPTION_ID environment variable.', error.message)
end

it 'loads entry when subscription id is given' do
options[:subscription_id] = 'my_subscription_id'

result = Train::Transports::Helpers::Azure::FileCredentials.parse(options)

assert_equal('my_tenant_id', result[:tenant_id])
assert_equal('my_client_id', result[:client_id])
assert_equal('my_client_secret', result[:client_secret])
assert_equal('my_subscription_id', result[:subscription_id])
end

it 'raises an error when subscription id not found' do
options[:subscription_id] = 'missing_subscription_id'

error = assert_raises RuntimeError do
Train::Transports::Helpers::Azure::FileCredentials.parse(options)
end

assert_equal('No credentials found for subscription number missing_subscription_id', error.message)
end

it 'loads entry based on index' do
ENV['AZURE_SUBSCRIPTION_NUMBER'] = '2'

result = Train::Transports::Helpers::Azure::FileCredentials.parse(options)

ENV.delete('AZURE_SUBSCRIPTION_NUMBER')

assert_equal('my_tenant_id2', result[:tenant_id])
assert_equal('my_client_id2', result[:client_id])
assert_equal('my_client_secret2', result[:client_secret])
assert_equal('my_subscription_id2', result[:subscription_id])
end

it 'raises an error when index is out of bounds' do
ENV['AZURE_SUBSCRIPTION_NUMBER'] = '3'

error = assert_raises RuntimeError do
Train::Transports::Helpers::Azure::FileCredentials.parse(options)
end
ENV.delete('AZURE_SUBSCRIPTION_NUMBER')

assert_equal('Your credentials file only contains 2 subscriptions. You specified number 3.', error.message)
end

it 'raises an error when index 0 is given' do
ENV['AZURE_SUBSCRIPTION_NUMBER'] = '0'

error = assert_raises RuntimeError do
Train::Transports::Helpers::Azure::FileCredentials.parse(options)
end
ENV.delete('AZURE_SUBSCRIPTION_NUMBER')

assert_equal('Index must be greater than 0.', error.message)
end
end