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:
parent
71a9df884b
commit
a09bc1bc4a
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue