Merge "Restructures authentication for resource providers"
This commit is contained in:
@@ -12,45 +12,18 @@ class Puppet::Provider::Openstack < Puppet::Provider
|
|||||||
initvars # so commands will work
|
initvars # so commands will work
|
||||||
commands :openstack => 'openstack'
|
commands :openstack => 'openstack'
|
||||||
|
|
||||||
def request(service, action, object, credentials, *properties)
|
|
||||||
if password_credentials_set?(credentials)
|
|
||||||
auth_args = password_auth_args(credentials)
|
|
||||||
elsif openrc_set?(credentials)
|
|
||||||
credentials = get_credentials_from_openrc(credentials['openrc'])
|
|
||||||
auth_args = password_auth_args(credentials)
|
|
||||||
elsif service_credentials_set?(credentials)
|
|
||||||
auth_args = token_auth_args(credentials)
|
|
||||||
elsif env_vars_set?
|
|
||||||
# noop; auth needs no extra arguments
|
|
||||||
auth_args = nil
|
|
||||||
else # All authentication efforts failed
|
|
||||||
raise(Puppet::Error::OpenstackAuthInputError, 'No credentials provided.')
|
|
||||||
end
|
|
||||||
args = [object, properties, auth_args].flatten.compact
|
|
||||||
authenticate_request(service, action, args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.request(service, action, object, *properties)
|
|
||||||
if env_vars_set?
|
|
||||||
# noop; auth needs no extra arguments
|
|
||||||
auth_args = nil
|
|
||||||
else # All authentication efforts failed
|
|
||||||
raise(Puppet::Error::OpenstackAuthInputError, 'No credentials provided.')
|
|
||||||
end
|
|
||||||
args = [object, properties, auth_args].flatten.compact
|
|
||||||
authenticate_request(service, action, args)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns an array of hashes, where the keys are the downcased CSV headers
|
# Returns an array of hashes, where the keys are the downcased CSV headers
|
||||||
# with underscores instead of spaces
|
# with underscores instead of spaces
|
||||||
def self.authenticate_request(service, action, *args)
|
def self.request(service, action, properties, credentials=nil)
|
||||||
|
env = credentials ? credentials.to_env : {}
|
||||||
|
Puppet::Util.withenv(env) do
|
||||||
rv = nil
|
rv = nil
|
||||||
timeout = 10
|
timeout = 10
|
||||||
end_time = Time.now.to_i + timeout
|
end_time = Time.now.to_i + timeout
|
||||||
loop do
|
loop do
|
||||||
begin
|
begin
|
||||||
if(action == 'list')
|
if(action == 'list')
|
||||||
response = openstack(service, action, '--quiet', '--format', 'csv', args)
|
response = openstack(service, action, '--quiet', '--format', 'csv', properties)
|
||||||
response = parse_csv(response)
|
response = parse_csv(response)
|
||||||
keys = response.delete_at(0) # ID,Name,Description,Enabled
|
keys = response.delete_at(0) # ID,Name,Description,Enabled
|
||||||
rv = response.collect do |line|
|
rv = response.collect do |line|
|
||||||
@@ -64,7 +37,7 @@ class Puppet::Provider::Openstack < Puppet::Provider
|
|||||||
elsif(action == 'show' || action == 'create')
|
elsif(action == 'show' || action == 'create')
|
||||||
rv = {}
|
rv = {}
|
||||||
# shell output is name="value"\nid="value2"\ndescription="value3" etc.
|
# shell output is name="value"\nid="value2"\ndescription="value3" etc.
|
||||||
openstack(service, action, '--format', 'shell', args).split("\n").each do |line|
|
openstack(service, action, '--format', 'shell', properties).split("\n").each do |line|
|
||||||
# key is everything before the first "="
|
# key is everything before the first "="
|
||||||
key, val = line.split("=", 2)
|
key, val = line.split("=", 2)
|
||||||
next unless val # Ignore warnings
|
next unless val # Ignore warnings
|
||||||
@@ -73,7 +46,7 @@ class Puppet::Provider::Openstack < Puppet::Provider
|
|||||||
rv[key.downcase.to_sym] = val
|
rv[key.downcase.to_sym] = val
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
rv = openstack(service, action, args)
|
rv = openstack(service, action, properties)
|
||||||
end
|
end
|
||||||
break
|
break
|
||||||
rescue Puppet::ExecutionFailure => e
|
rescue Puppet::ExecutionFailure => e
|
||||||
@@ -98,99 +71,14 @@ class Puppet::Provider::Openstack < Puppet::Provider
|
|||||||
end
|
end
|
||||||
return rv
|
return rv
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticate_request(service, action, *args)
|
|
||||||
self.class.authenticate_request(service, action, *args)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def password_credentials_set?(auth_params)
|
|
||||||
auth_params && auth_params['username'] && auth_params['password'] && auth_params['project_name'] && auth_params['auth_url']
|
|
||||||
end
|
|
||||||
|
|
||||||
def openrc_set?(auth_params)
|
|
||||||
auth_params && auth_params['openrc']
|
|
||||||
end
|
|
||||||
|
|
||||||
def service_credentials_set?(auth_params)
|
|
||||||
auth_params && auth_params['token'] && auth_params['url']
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.env_vars_set?
|
|
||||||
ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['OS_PROJECT_NAME'] && ENV['OS_AUTH_URL']
|
|
||||||
end
|
|
||||||
|
|
||||||
def env_vars_set?
|
|
||||||
self.class.env_vars_set?
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.password_auth_args(credentials)
|
|
||||||
creds = [ '--os-username', credentials['username'],
|
|
||||||
'--os-password', credentials['password'],
|
|
||||||
'--os-project-name', credentials['project_name'],
|
|
||||||
'--os-auth-url', credentials['auth_url'] ]
|
|
||||||
|
|
||||||
if credentials.include?('project_domain_name')
|
|
||||||
creds << '--os-project-domain-name'
|
|
||||||
creds << credentials['project_domain_name']
|
|
||||||
end
|
|
||||||
|
|
||||||
if credentials.include?('user_domain_name')
|
|
||||||
creds << '--os-user-domain-name'
|
|
||||||
creds << credentials['user_domain_name']
|
|
||||||
end
|
|
||||||
|
|
||||||
creds
|
|
||||||
end
|
|
||||||
|
|
||||||
def password_auth_args(credentials)
|
|
||||||
self.class.password_auth_args(credentials)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.token_auth_args(credentials)
|
|
||||||
args = [ '--os-token', credentials['token'],
|
|
||||||
'--os-url', credentials['url'] ]
|
|
||||||
# Add the api version only if requested by the caller
|
|
||||||
if credentials['version']
|
|
||||||
args << [ '--os-identity-api-version', credentials['version'] ]
|
|
||||||
end
|
|
||||||
args
|
|
||||||
end
|
|
||||||
|
|
||||||
def token_auth_args(credentials)
|
|
||||||
self.class.token_auth_args(credentials)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_credentials_from_openrc(file)
|
|
||||||
creds = {}
|
|
||||||
File.open(file).readlines.delete_if{|l| l=~ /^#/}.each do |line|
|
|
||||||
key, value = line.split('=')
|
|
||||||
key = key.split(' ').last.downcase.sub(/^os_/, '')
|
|
||||||
value = value.chomp.gsub(/'/, '')
|
|
||||||
creds[key] = value
|
|
||||||
end
|
|
||||||
return creds
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.get_credentials_from_env
|
|
||||||
env = ENV.to_hash.dup.delete_if { |key, _| ! (key =~ /^OS_/) }
|
|
||||||
credentials = {}
|
|
||||||
env.each do |name, value|
|
|
||||||
credentials[name.downcase.sub(/^os_/, '')] = value
|
|
||||||
end
|
|
||||||
credentials
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_credentials_from_env
|
|
||||||
self.class.get_credentials_from_env
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parse_csv(text)
|
def self.parse_csv(text)
|
||||||
# Ignore warnings - assume legitimate output starts with a double quoted
|
# Ignore warnings - assume legitimate output starts with a double quoted
|
||||||
# string. Errors will be caught and raised prior to this
|
# string. Errors will be caught and raised prior to this
|
||||||
text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
|
text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
|
||||||
return CSV.parse(text + "\n")
|
return CSV.parse(text + "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
49
lib/puppet/provider/openstack/auth.rb
Normal file
49
lib/puppet/provider/openstack/auth.rb
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
require 'puppet/provider/openstack/credentials'
|
||||||
|
|
||||||
|
module Puppet::Provider::Openstack::Auth
|
||||||
|
|
||||||
|
RCFILENAME = "#{ENV['HOME']}/openrc"
|
||||||
|
|
||||||
|
def get_os_vars_from_env
|
||||||
|
env = {}
|
||||||
|
ENV.each { |k,v| env.merge!(k => v) if k =~ /^OS_/ }
|
||||||
|
return env
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_os_vars_from_rcfile(filename)
|
||||||
|
env = {}
|
||||||
|
if File.exists?(filename)
|
||||||
|
File.open(filename).readlines.delete_if{|l| l=~ /^#|^$/ }.each do |line|
|
||||||
|
key, value = line.split('=')
|
||||||
|
key = key.split(' ').last
|
||||||
|
value = value.chomp.gsub(/'/, '')
|
||||||
|
env.merge!(key => value) if key =~ /OS_/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return env
|
||||||
|
end
|
||||||
|
|
||||||
|
def rc_filename
|
||||||
|
RCFILENAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def request(service, action, properties=nil)
|
||||||
|
properties ||= []
|
||||||
|
set_credentials(@credentials, get_os_vars_from_env)
|
||||||
|
unless @credentials.set?
|
||||||
|
@credentials.unset
|
||||||
|
set_credentials(@credentials, get_os_vars_from_rcfile(rc_filename))
|
||||||
|
end
|
||||||
|
unless @credentials.set?
|
||||||
|
raise(Puppet::Error::OpenstackAuthInputError, 'Insufficient credentials to authenticate')
|
||||||
|
end
|
||||||
|
super(service, action, properties, @credentials)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_credentials(creds, env)
|
||||||
|
env.each do |key, val|
|
||||||
|
var = key.sub(/^OS_/,'').downcase
|
||||||
|
creds.set(var, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
lib/puppet/provider/openstack/credentials.rb
Normal file
56
lib/puppet/provider/openstack/credentials.rb
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
require 'puppet'
|
||||||
|
require 'puppet/provider/openstack'
|
||||||
|
|
||||||
|
class Puppet::Provider::Openstack::Credentials
|
||||||
|
|
||||||
|
KEYS = [
|
||||||
|
:auth_url, :password, :project_name, :username,
|
||||||
|
:token, :url,
|
||||||
|
:identity_api_version
|
||||||
|
]
|
||||||
|
|
||||||
|
KEYS.each { |var| attr_accessor var }
|
||||||
|
|
||||||
|
def self.defined?(name)
|
||||||
|
KEYS.include?(name.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(key, val)
|
||||||
|
if self.class.defined?(key.to_sym)
|
||||||
|
self.instance_variable_set("@#{key}".to_sym, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set?
|
||||||
|
return true if user_password_set? || service_token_set?
|
||||||
|
end
|
||||||
|
|
||||||
|
def service_token_set?
|
||||||
|
return true if @token && @url
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_env
|
||||||
|
env = {}
|
||||||
|
self.instance_variables.each do |var|
|
||||||
|
name = var.to_s.sub(/^@/,'OS_').upcase
|
||||||
|
env.merge!(name => self.instance_variable_get(var))
|
||||||
|
end
|
||||||
|
env
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_password_set?
|
||||||
|
return true if @username && @password && @project_name && @auth_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def unset
|
||||||
|
list = KEYS.delete_if { |key, val| key == :identity_api_version }
|
||||||
|
list.each { |key, val| self.set(key, '') if self.class.defined?("@#{key}".to_sym) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def version
|
||||||
|
self.class.to_s.sub(/.*V/,'').sub('_','.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Puppet::Provider::Openstack::CredentialsV2_0 < Puppet::Provider::Openstack::Credentials
|
||||||
|
end
|
@@ -1,66 +0,0 @@
|
|||||||
# Add the auth parameter to whatever type is given
|
|
||||||
module Puppet::Util::Openstack
|
|
||||||
def self.add_openstack_type_methods(type, comment)
|
|
||||||
|
|
||||||
type.newparam(:auth) do
|
|
||||||
|
|
||||||
desc <<EOT
|
|
||||||
Hash of authentication credentials. Credentials can be specified as either :
|
|
||||||
|
|
||||||
1. Using a project/user with a password
|
|
||||||
|
|
||||||
For Keystone API V2:
|
|
||||||
auth => {
|
|
||||||
'username' => 'test',
|
|
||||||
'password' => 'changeme',
|
|
||||||
'project_name' => 'test',
|
|
||||||
'auth_url' => 'http://localhost:35357/v2.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
or altenatively for Keystone API V3:
|
|
||||||
auth => {
|
|
||||||
'username' => 'test',
|
|
||||||
'password' => 'changeme',
|
|
||||||
'project_name' => 'test',
|
|
||||||
'project_domain_name' => 'domain1',
|
|
||||||
'user_domain_name' => 'domain1',
|
|
||||||
'auth_url' => 'http://localhost:35357/v3'
|
|
||||||
}
|
|
||||||
|
|
||||||
2. Using a path to an openrc file containing these credentials
|
|
||||||
|
|
||||||
auth => {
|
|
||||||
'openrc' => '/root/openrc'
|
|
||||||
}
|
|
||||||
|
|
||||||
3. Using a service token
|
|
||||||
|
|
||||||
For Keystone API V2:
|
|
||||||
auth => {
|
|
||||||
'token' => 'example',
|
|
||||||
'url' => 'http://localhost:35357/v2.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
Alternatively for Keystone API V3:
|
|
||||||
auth => {
|
|
||||||
'token' => 'example',
|
|
||||||
'url' => 'http://localhost:35357/v3.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
If not present, the provider will look for environment variables for
|
|
||||||
password credentials.
|
|
||||||
|
|
||||||
#{comment}
|
|
||||||
EOT
|
|
||||||
|
|
||||||
validate do |value|
|
|
||||||
raise(Puppet::Error, 'This property must be a hash') unless value.is_a?(Hash)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
type.autorequire(:package) do
|
|
||||||
'python-openstackclient'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
181
spec/unit/provider/openstack/auth_spec.rb
Normal file
181
spec/unit/provider/openstack/auth_spec.rb
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
require 'puppet'
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'puppet/provider/openstack'
|
||||||
|
require 'puppet/provider/openstack/auth'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
class Puppet::Provider::Openstack::AuthTester < Puppet::Provider::Openstack
|
||||||
|
extend Puppet::Provider::Openstack::Auth
|
||||||
|
end
|
||||||
|
|
||||||
|
klass = Puppet::Provider::Openstack::AuthTester
|
||||||
|
|
||||||
|
describe Puppet::Provider::Openstack::Auth do
|
||||||
|
|
||||||
|
let(:type) do
|
||||||
|
Puppet::Type.newtype(:test_resource) do
|
||||||
|
newparam(:name, :namevar => true)
|
||||||
|
newparam(:log_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:resource_attrs) do
|
||||||
|
{
|
||||||
|
:name => 'stubresource'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:provider) do
|
||||||
|
klass.new(type.new(resource_attrs))
|
||||||
|
end
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
ENV['OS_USERNAME'] = nil
|
||||||
|
ENV['OS_PASSWORD'] = nil
|
||||||
|
ENV['OS_PROJECT_NAME'] = nil
|
||||||
|
ENV['OS_AUTH_URL'] = nil
|
||||||
|
ENV['OS_TOKEN'] = nil
|
||||||
|
ENV['OS_URL'] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#set_credentials' do
|
||||||
|
it 'adds keys to the object' do
|
||||||
|
credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
|
||||||
|
set = { 'OS_USERNAME' => 'user', 'OS_PASSWORD' => 'secret',
|
||||||
|
'OS_PROJECT_NAME' => 'tenant',
|
||||||
|
'OS_AUTH_URL' => 'http://127.0.0.1:5000',
|
||||||
|
'OS_TOKEN' => 'token',
|
||||||
|
'OS_URL' => 'http://127.0.0.1:35357',
|
||||||
|
'OS_IDENTITY_API_VERSION' => '2.0'
|
||||||
|
}
|
||||||
|
klass.set_credentials(credentials, set)
|
||||||
|
expect(credentials.to_env).to eq("OS_AUTH_URL" => "http://127.0.0.1:5000",
|
||||||
|
"OS_IDENTITY_API_VERSION" => '2.0',
|
||||||
|
"OS_PASSWORD" => "secret",
|
||||||
|
"OS_PROJECT_NAME" => "tenant",
|
||||||
|
"OS_TOKEN" => "token",
|
||||||
|
"OS_URL" => "http://127.0.0.1:35357",
|
||||||
|
"OS_USERNAME" => "user")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#rc_filename' do
|
||||||
|
it 'returns RCFILENAME' do
|
||||||
|
expect(klass.rc_filename).to eq("#{ENV['HOME']}/openrc")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#get_os_from_env' do
|
||||||
|
context 'with Openstack environment variables set' do
|
||||||
|
it 'provides a hash' do
|
||||||
|
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
|
||||||
|
ENV['OS_PASSWORD'] = 'abc123'
|
||||||
|
ENV['OS_PROJECT_NAME'] = 'test'
|
||||||
|
ENV['OS_USERNAME'] = 'test'
|
||||||
|
response = klass.get_os_vars_from_env
|
||||||
|
expect(response).to eq({"OS_AUTH_URL" => "http://127.0.0.1:5000","OS_PASSWORD" => "abc123","OS_PROJECT_NAME" => "test","OS_USERNAME" => "test"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#get_os_vars_from_rcfile' do
|
||||||
|
context 'with a valid RC file' do
|
||||||
|
it 'provides a hash' do
|
||||||
|
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'"
|
||||||
|
filename = 'file'
|
||||||
|
File.expects(:exists?).with('file').returns(true)
|
||||||
|
File.expects(:open).with('file').returns(StringIO.new(mock))
|
||||||
|
|
||||||
|
response = klass.get_os_vars_from_rcfile(filename)
|
||||||
|
expect(response).to eq({"OS_AUTH_URL" => "http://127.0.0.1:5000","OS_PASSWORD" => "abc123","OS_PROJECT_NAME" => "test","OS_USERNAME" => "test"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an empty file' do
|
||||||
|
it 'provides an empty hash' do
|
||||||
|
filename = 'file'
|
||||||
|
File.expects(:exists?).with(filename).returns(true)
|
||||||
|
File.expects(:open).with(filename).returns(StringIO.new(""))
|
||||||
|
|
||||||
|
response = klass.get_os_vars_from_rcfile(filename)
|
||||||
|
expect(response).to eq({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
class Puppet::Provider::Openstack::AuthTester
|
||||||
|
@credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#request' do
|
||||||
|
context 'with no valid credentials' do
|
||||||
|
it 'fails to authenticate' do
|
||||||
|
expect { klass.request('project', 'list', ['--long']) }.to raise_error(Puppet::Error::OpenstackAuthInputError, "Insufficient credentials to authenticate")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with user credentials in env' do
|
||||||
|
it 'is successful' do
|
||||||
|
klass.expects(:get_os_vars_from_env)
|
||||||
|
.returns({ 'OS_USERNAME' => 'test',
|
||||||
|
'OS_PASSWORD' => 'abc123',
|
||||||
|
'OS_PROJECT_NAME' => 'test',
|
||||||
|
'OS_AUTH_URL' => 'http://127.0.0.1:5000' })
|
||||||
|
klass.expects(:openstack)
|
||||||
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
||||||
|
.returns('"ID","Name","Description","Enabled"
|
||||||
|
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
||||||
|
')
|
||||||
|
response = klass.request('project', 'list', ['--long'])
|
||||||
|
expect(response.first[:description]).to eq("Test tenant")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with service token credentials in env' do
|
||||||
|
it 'is successful' do
|
||||||
|
klass.expects(:get_os_vars_from_env)
|
||||||
|
.returns({ 'OS_TOKEN' => 'test',
|
||||||
|
'OS_URL' => 'http://127.0.0.1:5000' })
|
||||||
|
klass.expects(:openstack)
|
||||||
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
||||||
|
.returns('"ID","Name","Description","Enabled"
|
||||||
|
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
||||||
|
')
|
||||||
|
response = klass.request('project', 'list', ['--long'])
|
||||||
|
expect(response.first[:description]).to eq("Test tenant")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a RC file containing user credentials' do
|
||||||
|
it 'is successful' do
|
||||||
|
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'"
|
||||||
|
File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true)
|
||||||
|
File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock))
|
||||||
|
klass.expects(:openstack)
|
||||||
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
||||||
|
.returns('"ID","Name","Description","Enabled"
|
||||||
|
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
||||||
|
')
|
||||||
|
response = provider.class.request('project', 'list', ['--long'])
|
||||||
|
expect(response.first[:description]).to eq("Test tenant")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a RC file containing service token credentials' do
|
||||||
|
it 'is successful' do
|
||||||
|
mock = "export OS_TOKEN='test'\nexport OS_URL='abc123'\n"
|
||||||
|
File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true)
|
||||||
|
File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock))
|
||||||
|
klass.expects(:openstack)
|
||||||
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
||||||
|
.returns('"ID","Name","Description","Enabled"
|
||||||
|
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
||||||
|
')
|
||||||
|
response = klass.request('project', 'list', ['--long'])
|
||||||
|
expect(response.first[:description]).to eq("Test tenant")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
63
spec/unit/provider/openstack/credentials_spec.rb
Normal file
63
spec/unit/provider/openstack/credentials_spec.rb
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
require 'puppet'
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'puppet/provider/openstack'
|
||||||
|
require 'puppet/provider/openstack/credentials'
|
||||||
|
|
||||||
|
|
||||||
|
describe Puppet::Provider::Openstack::Credentials do
|
||||||
|
|
||||||
|
let(:creds) do
|
||||||
|
creds = Puppet::Provider::Openstack::CredentialsV2_0.new
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#service_token_set?' do
|
||||||
|
context "with service credentials" do
|
||||||
|
it 'is successful' do
|
||||||
|
creds.token = 'token'
|
||||||
|
creds.url = 'url'
|
||||||
|
expect(creds.service_token_set?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#password_set?' do
|
||||||
|
context "with user credentials" do
|
||||||
|
it 'is successful' do
|
||||||
|
creds.auth_url = 'auth_url'
|
||||||
|
creds.password = 'password'
|
||||||
|
creds.project_name = 'project_name'
|
||||||
|
creds.username = 'username'
|
||||||
|
expect(creds.user_password_set?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#set?' do
|
||||||
|
context "without any credential" do
|
||||||
|
it 'fails' do
|
||||||
|
expect(creds.set?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_env' do
|
||||||
|
context "with an exhaustive data set" do
|
||||||
|
it 'successfully returns content' do
|
||||||
|
creds.auth_url = 'auth_url'
|
||||||
|
creds.password = 'password'
|
||||||
|
creds.project_name = 'project_name'
|
||||||
|
creds.username = 'username'
|
||||||
|
creds.token = 'token'
|
||||||
|
creds.url = 'url'
|
||||||
|
creds.identity_api_version = 'identity_api_version'
|
||||||
|
expect(creds.auth_url).to eq("auth_url")
|
||||||
|
expect(creds.password).to eq("password")
|
||||||
|
expect(creds.project_name).to eq("project_name")
|
||||||
|
expect(creds.username).to eq("username")
|
||||||
|
expect(creds.token).to eq('token')
|
||||||
|
expect(creds.url).to eq('url')
|
||||||
|
expect(creds.identity_api_version).to eq('identity_api_version')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@@ -1,12 +1,8 @@
|
|||||||
# Load libraries from aviator here to simulate how they live together in a real puppet run
|
|
||||||
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'modules', 'aviator', 'lib'))
|
|
||||||
require 'puppet'
|
require 'puppet'
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'puppet/provider/openstack'
|
require 'puppet/provider/openstack'
|
||||||
|
|
||||||
|
|
||||||
describe Puppet::Provider::Openstack do
|
describe Puppet::Provider::Openstack do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
ENV['OS_USERNAME'] = nil
|
ENV['OS_USERNAME'] = nil
|
||||||
ENV['OS_PASSWORD'] = nil
|
ENV['OS_PASSWORD'] = nil
|
||||||
@@ -17,239 +13,48 @@ describe Puppet::Provider::Openstack do
|
|||||||
let(:type) do
|
let(:type) do
|
||||||
Puppet::Type.newtype(:test_resource) do
|
Puppet::Type.newtype(:test_resource) do
|
||||||
newparam(:name, :namevar => true)
|
newparam(:name, :namevar => true)
|
||||||
newparam(:auth)
|
|
||||||
newparam(:log_file)
|
newparam(:log_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'authenticating with environment variables using API v2' do
|
|
||||||
it 'makes a successful request' do
|
|
||||||
ENV['OS_USERNAME'] = 'test'
|
|
||||||
ENV['OS_PASSWORD'] = 'abc123'
|
|
||||||
ENV['OS_PROJECT_NAME'] = 'test'
|
|
||||||
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v2.0'
|
|
||||||
if provider.class == Class
|
|
||||||
provider.stubs(:openstack)
|
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [[ '--long' ]])
|
|
||||||
.returns('"ID","Name","Description","Enabled"
|
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
||||||
')
|
|
||||||
else
|
|
||||||
provider.class.stubs(:openstack)
|
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [[ '--long' ]])
|
|
||||||
.returns('"ID","Name","Description","Enabled"
|
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
||||||
')
|
|
||||||
end
|
|
||||||
response = provider.request('project', 'list', nil, nil, '--long' )
|
|
||||||
expect(response.first[:description]).to match /Test tenant/
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'authenticating with environment variables using API v3' do
|
|
||||||
it 'makes a successful request' do
|
|
||||||
ENV['OS_USERNAME'] = 'test'
|
|
||||||
ENV['OS_PASSWORD'] = 'abc123'
|
|
||||||
ENV['OS_PROJECT_NAME'] = 'test'
|
|
||||||
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v3'
|
|
||||||
if provider.class == Class
|
|
||||||
provider.stubs(:openstack)
|
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [[ '--long' ]])
|
|
||||||
.returns('"ID","Name","Description","Enabled"
|
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
||||||
')
|
|
||||||
else
|
|
||||||
provider.class.stubs(:openstack)
|
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [[ '--long' ]])
|
|
||||||
.returns('"ID","Name","Description","Enabled"
|
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
||||||
')
|
|
||||||
end
|
|
||||||
response = provider.request('project', 'list', nil, nil, '--long' )
|
|
||||||
expect(response.first[:description]).to match /Test tenant/
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'it has no credentials' do
|
|
||||||
it 'fails to authenticate' do
|
|
||||||
expect{ provider.request('project', 'list', nil, nil, '--long') }.to raise_error(Puppet::Error::OpenstackAuthInputError, /No credentials provided/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#request' do
|
describe '#request' do
|
||||||
|
|
||||||
context 'with valid password credentials in parameters' do
|
|
||||||
let(:resource_attrs) do
|
let(:resource_attrs) do
|
||||||
{
|
{
|
||||||
:name => 'stubresource',
|
:name => 'stubresource',
|
||||||
:auth => {
|
|
||||||
'username' => 'test',
|
|
||||||
'password' => 'abc123',
|
|
||||||
'project_name' => 'test',
|
|
||||||
'auth_url' => 'http://127.0.0.1:5000/v2.0',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:provider) do
|
let(:provider) do
|
||||||
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes a successful request' do
|
it 'makes a successful request' do
|
||||||
provider.class.stubs(:openstack)
|
provider.class.stubs(:openstack)
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-project-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
||||||
.returns('"ID","Name","Description","Enabled"
|
.returns('"ID","Name","Description","Enabled"
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
||||||
')
|
')
|
||||||
response = provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
|
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
||||||
expect(response.first[:description]).to match /Test tenant/
|
expect(response.first[:description]).to eq("Test tenant")
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with valid openrc file in parameters' do
|
context 'on connection errors' do
|
||||||
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000/v2.0'"
|
|
||||||
let(:resource_attrs) do
|
|
||||||
{
|
|
||||||
:name => 'stubresource',
|
|
||||||
:auth => {
|
|
||||||
'openrc' => '/root/openrc'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:provider) do
|
|
||||||
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'makes a successful request' do
|
|
||||||
File.expects(:open).with('/root/openrc').returns(StringIO.new(mock))
|
|
||||||
provider.class.stubs(:openstack)
|
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-project-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
|
|
||||||
.returns('"ID","Name","Description","Enabled"
|
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
||||||
')
|
|
||||||
response = provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
|
|
||||||
expect(response.first[:description]).to match /Test tenant/
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with valid service token in parameters' do
|
|
||||||
let(:resource_attrs) do
|
|
||||||
{
|
|
||||||
:name => 'stubresource',
|
|
||||||
:auth => {
|
|
||||||
'token' => 'secrettoken',
|
|
||||||
'url' => 'http://127.0.0.1:5000/v3',
|
|
||||||
'version' => '3'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:provider) do
|
|
||||||
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'makes a successful request' do
|
|
||||||
provider.class.stubs(:openstack)
|
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-token', 'secrettoken', '--os-url', 'http://127.0.0.1:5000/v3', '--os-identity-api-version', '3']])
|
|
||||||
.returns('"ID","Name","Description","Enabled"
|
|
||||||
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
|
|
||||||
')
|
|
||||||
response = provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
|
|
||||||
expect(response.first[:description]).to match /Test tenant/
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'makes a successful show request' do
|
|
||||||
provider.class.stubs(:openstack)
|
|
||||||
.with('project', 'show', '--format', 'shell', [['test', '--os-token', 'secrettoken', '--os-url', 'http://127.0.0.1:5000/v3', '--os-identity-api-version', '3']])
|
|
||||||
.returns('ID="1cb05cfed7c24279be884ba4f6520262"
|
|
||||||
Name="test"
|
|
||||||
Description="Test Tenant"
|
|
||||||
Enabled="True"
|
|
||||||
')
|
|
||||||
response = provider.request('project', 'show', 'test', resource_attrs[:auth])
|
|
||||||
expect(response[:description]).to match /Test Tenant/
|
|
||||||
expect(response[:id]).to match /1cb05cfed7c24279be884ba4f6520262/
|
|
||||||
expect(response[:name]).to match /test/
|
|
||||||
expect(response[:enabled]).to match /True/
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with valid password credentials in environment variables' do
|
|
||||||
it_behaves_like 'authenticating with environment variables using API v2' do
|
|
||||||
let(:resource_attrs) do
|
|
||||||
{
|
|
||||||
:name => 'stubresource',
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:provider) do
|
|
||||||
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with no valid credentials' do
|
|
||||||
it_behaves_like 'it has no credentials' do
|
|
||||||
let(:resource_attrs) do
|
|
||||||
{
|
|
||||||
:name => 'stubresource',
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:provider) do
|
|
||||||
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'it retries on connection errors' do
|
|
||||||
let(:resource_attrs) do
|
|
||||||
{
|
|
||||||
:name => 'stubresource',
|
|
||||||
:auth => {
|
|
||||||
'username' => 'test',
|
|
||||||
'password' => 'abc123',
|
|
||||||
'project_name' => 'test',
|
|
||||||
'auth_url' => 'http://127.0.0.1:5000/v2.0',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:provider) do
|
|
||||||
Puppet::Provider::Openstack.new(type.new(resource_attrs))
|
|
||||||
end
|
|
||||||
it 'retries' do
|
it 'retries' do
|
||||||
|
ENV['OS_USERNAME'] = 'test'
|
||||||
|
ENV['OS_PASSWORD'] = 'abc123'
|
||||||
|
ENV['OS_PROJECT_NAME'] = 'test'
|
||||||
|
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
|
||||||
provider.class.stubs(:openstack)
|
provider.class.stubs(:openstack)
|
||||||
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-project-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
|
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
|
||||||
.raises(Puppet::ExecutionFailure, 'Unable to establish connection')
|
.raises(Puppet::ExecutionFailure, 'Unable to establish connection')
|
||||||
.then
|
.then
|
||||||
.returns('')
|
.returns('')
|
||||||
provider.class.expects(:sleep).with(2).returns(nil)
|
provider.class.expects(:sleep).with(2).returns(nil)
|
||||||
provider.request('project', 'list', nil, resource_attrs[:auth], '--long')
|
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
describe '::request' do
|
|
||||||
|
|
||||||
context 'with valid password credentials in environment variables' do
|
|
||||||
it_behaves_like 'authenticating with environment variables using API v2' do
|
|
||||||
let(:resource_attrs) do
|
|
||||||
{
|
|
||||||
:name => 'stubresource',
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:provider) do
|
|
||||||
Puppet::Provider::Openstack.dup
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with no valid credentials' do
|
|
||||||
it_behaves_like 'it has no credentials' do
|
|
||||||
let(:provider) { Puppet::Provider::Openstack.dup }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'parse_csv' do
|
describe 'parse_csv' do
|
||||||
context 'with mixed stderr' do
|
context 'with mixed stderr' do
|
||||||
text = "ERROR: Testing\n\"field\",\"test\",1,2,3\n"
|
text = "ERROR: Testing\n\"field\",\"test\",1,2,3\n"
|
||||||
@@ -281,5 +86,4 @@ Enabled="True"
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user