Use openstackclient for keystone_tenant

This patch migrates the keystone_tenant provider to use the universal
openstack client instead of the keystone client. It uses the openstack
parent provider in openstacklib to handle multiple authenticating
methods. The keystone_tenant type uses the openstacklib openstack
utility to add a new auth parameter to the keystone_tenant type.

This patch also moves functionality for parsing keystone.conf for the
service token back to the keystone module from openstacklib. It creates
three tiers of inheritance: Keystone_tenant < Keystone < Openstack, so
that keystone-specific functionality can stay in keystone.

It also adds a flush method which should help improve performance.

blueprint use-openstackclient-in-module-resources

Change-Id: I2d9a16f334d1e60ebdd36805e2f8d8d2ef82cf39
(cherry picked from commit acf3dc6f06)
This commit is contained in:
Colleen Murphy 2014-11-16 16:23:00 -08:00 committed by Richard Megginson
parent 71a9df884b
commit a09bc1bc4a
12 changed files with 707 additions and 214 deletions

View File

@ -1,7 +1,38 @@
require 'puppet/util/inifile'
class Puppet::Provider::Keystone < Puppet::Provider
require 'puppet/provider/openstack'
class Puppet::Provider::Keystone < Puppet::Provider::Openstack
def request(service, action, object, credentials, *properties)
begin
super
rescue Puppet::Error::OpenstackAuthInputError => error
keystone_request(service, action, object, credentials, error, *properties)
end
end
def self.request(service, action, object, credentials, *properties)
begin
super
rescue Puppet::Error::OpenstackAuthInputError => error
keystone_request(service, action, object, credentials, error, *properties)
end
end
def keystone_request(service, action, object, credentials, error, *properties)
self.class.keystone_request(service, action, object, credentials, error, *properties)
end
def self.keystone_request(service, action, object, credentials, error, *properties)
credentials = {
'token' => get_admin_token,
'auth_url' => get_admin_endpoint,
}
raise error unless (credentials['token'] && credentials['auth_url'])
auth_args = token_auth_args(credentials)
args = [object, properties, auth_args].flatten.compact
authenticate_request(service, action, args)
end
# retrieves the current token from keystone.conf
def self.admin_token
@admin_token ||= get_admin_token
end
@ -10,7 +41,7 @@ class Puppet::Provider::Keystone < Puppet::Provider
if keystone_file and keystone_file['DEFAULT'] and keystone_file['DEFAULT']['admin_token']
return "#{keystone_file['DEFAULT']['admin_token'].strip}"
else
raise(Puppet::Error, "File: /etc/keystone/keystone.conf does not contain a section DEFAULT with the admin_token specified. Keystone types will not work if keystone is not correctly configured")
return nil
end
end
@ -18,24 +49,49 @@ class Puppet::Provider::Keystone < Puppet::Provider
@admin_endpoint ||= get_admin_endpoint
end
def self.get_admin_endpoint
admin_endpoint = keystone_file['DEFAULT']['admin_endpoint'] ? keystone_file['DEFAULT']['admin_endpoint'].strip.chomp('/') : nil
return "#{admin_endpoint}/v2.0/" if admin_endpoint
def get_admin_token
self.class.get_admin_token
end
admin_port = keystone_file['DEFAULT']['admin_port'] ? keystone_file['DEFAULT']['admin_port'].strip : '35357'
ssl = keystone_file['ssl'] && keystone_file['ssl']['enable'] ? keystone_file['ssl']['enable'].strip.downcase == 'true' : false
protocol = ssl ? 'https' : 'http'
if keystone_file and keystone_file['DEFAULT'] and keystone_file['DEFAULT']['admin_bind_host']
host = keystone_file['DEFAULT']['admin_bind_host'].strip
if host == "0.0.0.0"
host = "127.0.0.1"
def self.get_admin_endpoint
if keystone_file
if keystone_file['DEFAULT']
if keystone_file['DEFAULT']['admin_endpoint']
auth_url = keystone_file['DEFAULT']['admin_endpoint'].strip.chomp('/')
return "#{auth_url}/v2.0/"
end
if keystone_file['DEFAULT']['admin_port']
admin_port = keystone_file['DEFAULT']['admin_port'].strip
else
admin_port = '35357'
end
if keystone_file['DEFAULT']['admin_bind_host']
host = keystone_file['DEFAULT']['admin_bind_host'].strip
if host == "0.0.0.0"
host = "127.0.0.1"
end
else
host = "127.0.0.1"
end
end
if keystone_file['ssl'] && keystone_file['ssl']['enable'] && keystone_file['ssl']['enable'].strip.downcase == 'true'
protocol = 'https'
else
protocol = 'http'
end
else
host = "127.0.0.1"
end
"#{protocol}://#{host}:#{admin_port}/v2.0/"
end
def get_admin_endpoint
self.class.get_admin_endpoint
end
def self.keystone_file
return @keystone_file if @keystone_file
@keystone_file = Puppet::Util::IniConfig::File.new
@ -43,6 +99,20 @@ class Puppet::Provider::Keystone < Puppet::Provider
@keystone_file
end
def keystone_file
self.class.keystone_file
end
# Helper functions to use on the pre-validated enabled field
def bool_to_sym(bool)
bool == true ? :true : :false
end
def sym_to_bool(sym)
sym == :true ? true : false
end
# Deprecated methods - temporarily keeping them while moving providers to use openstackclient
def self.tenant_hash
@tenant_hash ||= build_tenant_hash
end
@ -73,6 +143,7 @@ class Puppet::Provider::Keystone < Puppet::Provider
end
end
# We'll need to move this functionality to keystone_request
def self.auth_keystone(*args)
authenv = {:OS_SERVICE_TOKEN => admin_token}
begin

View File

@ -1,120 +0,0 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_tenant).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone tenants
This provider makes a few assumptions/
1. assumes that the admin endpoint can be accessed via localhost.
2. Assumes that the admin token and port can be accessed from
/etc/keystone/keystone.conf
One string difference, is that it does not know how to change the
name of a tenant
EOT
optional_commands :keystone => "keystone"
def self.prefetch(resource)
# rebuild the cahce for every puppet run
@tenant_hash = nil
end
def self.tenant_hash
@tenant_hash ||= build_tenant_hash
end
def tenant_hash
self.class.tenant_hash
end
def instance
tenant_hash[resource[:name]]
end
def self.instances
tenant_hash.collect do |k, v|
new(
:name => k,
:id => v[:id]
)
end
end
def create
optional_opts = []
if resource[:description]
optional_opts.push('--description').push(resource[:description])
end
results = auth_keystone(
'tenant-create',
'--name', resource[:name],
'--enabled', resource[:enabled],
optional_opts
)
if results =~ /Property\s|\sValue/
attrs = self.class.parse_keystone_object(results)
tenant_hash[resource[:name]] = {
:ensure => :present,
:name => resource[:name],
:id => attrs['id'],
:enabled => attrs['enabled'],
:description => attrs['description'],
}
else
fail("did not get expected message on tenant creation, got #{results}")
end
end
def exists?
instance
end
def destroy
auth_keystone('tenant-delete', instance[:id])
instance[:ensure] = :absent
end
def enabled=(value)
Puppet.warning("I am not sure if this is supported yet")
auth_keystone("tenant-update", '--enabled', value, instance[:id])
instance[:enabled] = value
end
def description
self.class.get_keystone_object('tenant', instance[:id], 'description')
end
def description=(value)
auth_keystone("tenant-update", '--description', value, instance[:id])
instance[:description] = value
end
[
:id,
:enabled,
].each do |attr|
define_method(attr.to_s) do
instance[attr] || :absent
end
end
private
def self.build_tenant_hash
hash = {}
list_keystone_objects('tenant', 3).each do |tenant|
hash[tenant[1]] = {
:id => tenant[0],
:enabled => tenant[2],
}
end
hash
end
end

View File

@ -0,0 +1,100 @@
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_tenant).provide(
:openstack,
:parent => Puppet::Provider::Keystone
) do
desc "Provider to manage keystone tenants/projects."
def initialize(value={})
super(value)
@property_flush = {}
end
def create
properties = []
if resource[:enabled] == :true
properties << '--enable'
elsif resource[:enabled] == :false
properties << '--disable'
end
if resource[:description]
properties << '--description'
properties << resource[:description]
end
request('project', 'create', resource[:name], resource[:auth], properties)
end
def exists?
! instance(resource[:name]).empty?
end
def destroy
request('project', 'delete', resource[:name], resource[:auth])
end
def enabled=(value)
@property_flush[:enabled] = value
end
def enabled
bool_to_sym(instance(resource[:name])[:enabled])
end
def description=(value)
@property_flush[:description] = value
end
def description
instance(resource[:name])[:description]
end
def id
instance(resource[:name])[:id]
end
def self.instances
list = request('project', 'list', nil, nil, '--long')
list.collect do |project|
new(
:name => project[:name],
:ensure => :present,
:enabled => project[:enabled].downcase.chomp == 'true' ? true : false,
:description => project[:description],
:id => project[:id]
)
end
end
def instances
instances = request('project', 'list', nil, resource[:auth], '--long')
instances.collect do |project|
{
:name => project[:name],
:enabled => project[:enabled].downcase.chomp == 'true' ? true : false,
:description => project[:description],
:id => project[:id]
}
end
end
def instance(name)
@instances ||= instances.select { |instance| instance[:name] == name }.first || {}
end
def flush
options = []
if @property_flush
(options << '--enable') if @property_flush[:enabled] == :true
(options << '--disable') if @property_flush[:enabled] == :false
# There is a --description flag for the set command, but it does not work if the value is empty
(options << '--property' << "description=#{resource[:description]}") if @property_flush[:description]
request('project', 'set', resource[:name], resource[:auth], options) unless options.empty?
end
end
end

View File

@ -0,0 +1,151 @@
# TODO: This needs to be extracted out into openstacklib in the Kilo cycle
require 'csv'
require 'puppet'
class Puppet::Error::OpenstackAuthInputError < Puppet::Error
end
class Puppet::Error::OpenstackUnauthorizedError < Puppet::Error
end
class Puppet::Provider::Openstack < Puppet::Provider
initvars # so commands will work
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
# with underscores instead of spaces
def self.authenticate_request(service, action, *args)
begin
if(action == 'list')
response = openstack(service, action, '--quiet', '--format', 'csv', args)
response = CSV.parse(response.to_s)
keys = response.delete_at(0) # ID,Name,Description,Enabled
response.collect do |line|
hash = {}
keys.each_index do |index|
key = keys[index].downcase.gsub(/ /, '_').to_sym
hash[key] = line[index]
end
hash
end
else
openstack(service, action, args)
end
rescue Puppet::ExecutionFailure => e
if e.message =~ /HTTP 401/
raise(Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate.')
else
raise e
end
end
end
def authenticate_request(service, action, *args)
self.class.authenticate_request(service, action, *args)
end
private
def password_credentials_set?(auth_params)
auth_params && auth_params['username'] && auth_params['password'] && auth_params['tenant_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['auth_url']
end
def self.env_vars_set?
ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['OS_TENANT_NAME'] && ENV['OS_AUTH_URL']
end
def env_vars_set?
self.class.env_vars_set?
end
def self.password_auth_args(credentials)
['--os-username', credentials['username'],
'--os-password', credentials['password'],
'--os-tenant-name', credentials['tenant_name'],
'--os-auth-url', credentials['auth_url']]
end
def password_auth_args(credentials)
self.class.password_auth_args(credentials)
end
def self.token_auth_args(credentials)
['--os-token', credentials['token'],
'--os-url', credentials['auth_url']]
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
end

View File

@ -1,38 +1,47 @@
require 'puppet/util/openstack'
Puppet::Type.newtype(:keystone_tenant) do
desc <<-EOT
This type can be used to manage
keystone tenants.
This is assumed to be running on the same node
as your keystone API server.
EOT
desc 'This type can be used to manage keystone tenants.'
ensurable
newparam(:name, :namevar => true) do
desc 'The name of the tenant.'
newvalues(/\w+/)
end
newproperty(:enabled) do
newvalues(/(t|T)rue/, /(f|F)alse/)
defaultto('True')
desc 'Whether the tenant should be enabled. Defaults to true.'
newvalues(/(t|T)rue/, /(f|F)alse/, true, false )
defaultto(true)
munge do |value|
value.to_s.capitalize
value.to_s.downcase.to_sym
end
end
newproperty(:description)
newproperty(:description) do
desc 'A description of the tenant.'
defaultto('')
end
newproperty(:id) do
desc 'Read-only property of the tenant.'
validate do |v|
raise(Puppet::Error, 'This is a read only property')
end
end
# we should not do anything until the keystone service is started
# This ensures the service is started and therefore the keystone
# config is configured IF we need them for authentication.
# If there is no keystone config, authentication credentials
# need to come from another source.
autorequire(:service) do
['keystone']
end
auth_param_doc=<<EOT
If no other credentials are present, the provider will search in
/etc/keystone/keystone.conf for an admin token and auth url.
EOT
Puppet::Util::Openstack.add_openstack_type_methods(self, auth_param_doc)
end

View File

@ -0,0 +1,48 @@
# TODO: This should be extracted into openstacklib during the kilo cycle
# 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
password credentials, e.g.:
auth => {
'username' => 'test',
'password' => 'passw0rd',
'tenant_name' => 'test',
'auth_url' => 'http://localhost:35357/v2.0',
}
or a path to an openrc file containing these credentials, e.g.:
auth => {
'openrc' => '/root/openrc',
}
or a service token and host, e.g.:
auth => {
'service_token' => 'ADMIN',
'auth_url' => 'http://localhost:35357/v2.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

View File

@ -397,6 +397,10 @@ class keystone(
ensure => $package_ensure,
name => $::keystone::params::package_name,
}
# TODO: Move this to openstacklib::openstackclient in Kilo
package { 'python-openstackclient':
ensure => present,
}
group { 'keystone':
ensure => present,

View File

@ -1,3 +1,5 @@
# Load libraries from openstacklib here to simulate how they live together in a real puppet run (for provider unit tests)
$LOAD_PATH.push(File.join(File.dirname(__FILE__), 'fixtures', 'modules', 'openstacklib', 'lib'))
require 'puppetlabs_spec_helper/module_spec_helper'
require 'shared_examples'

View File

@ -15,34 +15,27 @@ describe Puppet::Provider::Keystone do
describe 'when retrieving the security token' do
it 'should fail if there is no keystone config file' do
it 'should return nothing if there is no keystone config file' do
ini_file = Puppet::Util::IniConfig::File.new
t = Tempfile.new('foo')
path = t.path
t.unlink
ini_file.read(path)
expect do
klass.get_admin_token
end.to raise_error(Puppet::Error, /Keystone types will not work/)
expect(klass.get_admin_token).to be_nil
end
it 'should fail if the keystone config file does not have a DEFAULT section' do
it 'should return nothing if the keystone config file does not have a DEFAULT section' do
mock = {}
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
expect do
klass.get_admin_token
end.to raise_error(Puppet::Error, /Keystone types will not work/)
expect(klass.get_admin_token).to be_nil
end
it 'should fail if the keystone config file does not contain an admin token' do
mock = {'DEFAULT' => {'not_a_token' => 'foo'}}
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
expect do
klass.get_admin_token
end.to raise_error(Puppet::Error, /Keystone types will not work/)
expect(klass.get_admin_token).to be_nil
end
it 'should parse the admin token if it is in the config file' do

View File

@ -1,55 +0,0 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/keystone_tenant/keystone'
provider_class = Puppet::Type.type(:keystone_tenant).provider(:keystone)
describe provider_class do
describe 'when updating a tenant' do
let :tenant_name do
'foo'
end
let :tenant_attrs do
{
:name => tenant_name,
:description => '',
:ensure => 'present',
:enabled => 'True',
}
end
let :tenant_hash do
{ tenant_name => {
:id => 'id',
:name => tenant_name,
:description => '',
:ensure => 'present',
:enabled => 'True',
}
}
end
let :resource do
Puppet::Type::Keystone_tenant.new(tenant_attrs)
end
let :provider do
provider_class.new(resource)
end
before :each do
provider_class.expects(:build_tenant_hash).returns(tenant_hash)
end
it 'should call tenant-update to set enabled' do
provider.expects(:auth_keystone).with('tenant-update',
'--enabled',
'False',
'id')
provider.enabled=('False')
end
end
end

View File

@ -0,0 +1,102 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/keystone_tenant/openstack'
provider_class = Puppet::Type.type(:keystone_tenant).provider(:openstack)
describe provider_class do
describe 'when updating a tenant' do
let(:tenant_attrs) do
{
:name => 'foo',
:description => 'foo',
:ensure => 'present',
:enabled => 'True',
:auth => {
'username' => 'test',
'password' => 'abc123',
'tenant_name' => 'foo',
'auth_url' => 'http://127.0.0.1:5000/v2.0',
}
}
end
let(:resource) do
Puppet::Type::Keystone_tenant.new(tenant_attrs)
end
let(:provider) do
provider_class.new(resource)
end
describe '#create' do
it 'creates a tenant' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","foo","foo",True
')
provider.class.stubs(:openstack)
.with('project', 'create', [['foo', '--enable', '--description', 'foo', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
provider.create
expect(provider.exists?).to be_truthy
end
end
describe '#destroy' do
it 'destroys a tenant' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"')
provider.class.stubs(:openstack)
.with('project', 'delete', [['foo', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
provider.destroy
expect(provider.exists?).to be_falsey
end
end
describe '#exists' do
context 'when tenant exists' do
subject(:response) do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","foo","foo",True
')
response = provider.exists?
end
it { is_expected.to be_truthy }
end
context 'when tenant does not exist' do
subject(:response) do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"')
response = provider.exists?
end
it { is_expected.to be_falsey }
end
end
describe '#instances' do
it 'finds every tenant' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-name', 'foo', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","foo","foo",True
')
instances = provider.instances
expect(instances.count).to eq(1)
end
end
end
end

View File

@ -0,0 +1,188 @@
# TODO: This should be extracted into openstacklib during the Kilo cycle
# 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 'spec_helper'
require 'puppet/provider/openstack'
describe Puppet::Provider::Openstack do
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
ENV['OS_TENANT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
end
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:auth)
newparam(:log_file)
end
end
shared_examples 'authenticating with environment variables' do
it 'makes a successful request' do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_TENANT_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 '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
context 'with valid password credentials in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'test',
'password' => 'abc123',
'tenant_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 'makes a successful request' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', [['--long', '--os-username', 'test', '--os-password', 'abc123', '--os-tenant-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 openrc file in parameters' do
mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_TENANT_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-tenant-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',
'auth_url' => 'http://127.0.0.1:5000/v2.0'
}
}
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/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 password credentials in environment variables' do
it_behaves_like 'authenticating with environment variables' 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
end
describe '::request' do
context 'with valid password credentials in environment variables' do
it_behaves_like 'authenticating with environment variables' 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
end