Files
puppet-openstacklib/lib/puppet/provider/openstack.rb
Gilles Dubreuil d4073c2721 Targeting Keystone V3 API support
The default domain (id 'default', name 'Default') is where the V2
tenants/users are defined. So V3, which is now the default API's version
can and should be used.  Beeing able to use V3 domains needs to be
supported by specifying the domain name for a project/user.

This patch :
- Adds project and user domain names
- Renames tenant (v2) as project (v3)
- Renames os-auth-url to os-url, when using an authicated token against a
  service url, to distinct them from each other, as in OSC
(opentackclient)
- Updates newparam(:auth) accordingly to describe v2/v3 credential
  examples

Note: Keystone API v2 is deprecated [1]

[1] http://docs.openstack.org/developer/keystone/http-api.html#should-i-use-v2-0-or-v3

Change-Id: I72f79129a6875eb433eeb8a62f928e7210db134a
2015-04-03 13:17:25 +11:00

192 lines
6.0 KiB
Ruby

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)
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]
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
end
sleep(2)
else
raise e
end
end
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)
[ '--os-token', credentials['token'],
'--os-url', credentials['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
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