197 lines
6.2 KiB
Ruby
197 lines
6.2 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)
|
|
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
|