
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
192 lines
6.0 KiB
Ruby
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
|