Merge "Restructures authentication for resource providers"

This commit is contained in:
Jenkins
2015-06-10 02:44:16 +00:00
committed by Gerrit Code Review
7 changed files with 421 additions and 446 deletions

View File

@@ -12,185 +12,73 @@ 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)
rv = nil
timeout = 10
end_time = Time.now.to_i + timeout
loop do
begin
if(action == 'list')
response = openstack(service, action, '--quiet', '--format', 'csv', args)
response = parse_csv(response)
keys = response.delete_at(0) # ID,Name,Description,Enabled
rv = response.collect do |line|
hash = {}
keys.each_index do |index|
key = keys[index].downcase.gsub(/ /, '_').to_sym
hash[key] = line[index]
def self.request(service, action, properties, credentials=nil)
env = credentials ? credentials.to_env : {}
Puppet::Util.withenv(env) do
rv = nil
timeout = 10
end_time = Time.now.to_i + timeout
loop do
begin
if(action == 'list')
response = openstack(service, action, '--quiet', '--format', 'csv', properties)
response = parse_csv(response)
keys = response.delete_at(0) # ID,Name,Description,Enabled
rv = response.collect do |line|
hash = {}
keys.each_index do |index|
key = keys[index].downcase.gsub(/ /, '_').to_sym
hash[key] = line[index]
end
hash
end
elsif(action == 'show' || action == 'create')
rv = {}
# shell output is name="value"\nid="value2"\ndescription="value3" etc.
openstack(service, action, '--format', 'shell', properties).split("\n").each do |line|
# key is everything before the first "="
key, val = line.split("=", 2)
next unless val # Ignore warnings
# value is everything after the first "=", with leading and trailing double quotes stripped
val = val.gsub(/\A"|"\Z/, '')
rv[key.downcase.to_sym] = val
end
hash
end
elsif(action == 'show' || action == 'create')
rv = {}
# shell output is name="value"\nid="value2"\ndescription="value3" etc.
openstack(service, action, '--format', 'shell', args).split("\n").each do |line|
# key is everything before the first "="
key, val = line.split("=", 2)
next unless val # Ignore warnings
# value is everything after the first "=", with leading and trailing double quotes stripped
val = val.gsub(/\A"|"\Z/, '')
rv[key.downcase.to_sym] = val
end
else
rv = openstack(service, action, args)
end
break
rescue Puppet::ExecutionFailure => e
if e.message =~ /HTTP 401/
raise(Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate.')
elsif e.message =~ /Unable to establish connection/
current_time = Time.now.to_i
if current_time > end_time
break
else
wait = end_time - current_time
Puppet::debug("Non-fatal error: \"#{e.message}\"; retrying for #{wait} more seconds.")
if wait > timeout - 2 # Only notice the first time
notice("#{service} service is unavailable. Will retry for up to #{wait} seconds.")
end
rv = openstack(service, action, properties)
end
break
rescue Puppet::ExecutionFailure => e
if e.message =~ /HTTP 401/
raise(Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate.')
elsif e.message =~ /Unable to establish connection/
current_time = Time.now.to_i
if current_time > end_time
break
else
wait = end_time - current_time
Puppet::debug("Non-fatal error: \"#{e.message}\"; retrying for #{wait} more seconds.")
if wait > timeout - 2 # Only notice the first time
notice("#{service} service is unavailable. Will retry for up to #{wait} seconds.")
end
end
sleep(2)
else
raise e
end
sleep(2)
else
raise e
end
end
return rv
end
return rv
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['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)
# Ignore warnings - assume legitimate output starts with a double quoted
# string. Errors will be caught and raised prior to this
text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
return CSV.parse(text + "\n")
end
end

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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 'spec_helper'
require 'puppet/provider/openstack'
describe Puppet::Provider::Openstack do
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
@@ -17,239 +13,48 @@ describe Puppet::Provider::Openstack do
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 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
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
context 'with valid password credentials in parameters' 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
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-project-name', 'test', '--os-auth-url', 'http://127.0.0.1:5000/v2.0']])
.returns('"ID","Name","Description","Enabled"
it 'makes a successful request' do
provider.class.stubs(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.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
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq("Test tenant")
end
context 'with valid openrc file in parameters' 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
context 'on connection errors' 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)
.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')
.then
.returns('')
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
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
context 'with mixed stderr' do
text = "ERROR: Testing\n\"field\",\"test\",1,2,3\n"
@@ -281,5 +86,4 @@ Enabled="True"
end
end
end
end