X-Git-Url: https://git.donarmstrong.com/?p=dsa-puppet.git;a=blobdiff_plain;f=3rdparty%2Fmodules%2Fkeystone%2Flib%2Fpuppet%2Fprovider%2Fopenstack.rb;fp=3rdparty%2Fmodules%2Fkeystone%2Flib%2Fpuppet%2Fprovider%2Fopenstack.rb;h=8236df709f24e54e8096c26446503f6b26a0b127;hp=0000000000000000000000000000000000000000;hb=b8fa2c1a5ec9dbcd1d2f9e9b41afdde4c603aa35;hpb=b7626cbcbb2fb8e7ce3dc5ac60e80a981175f9d3 diff --git a/3rdparty/modules/keystone/lib/puppet/provider/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/openstack.rb new file mode 100644 index 00000000..8236df70 --- /dev/null +++ b/3rdparty/modules/keystone/lib/puppet/provider/openstack.rb @@ -0,0 +1,188 @@ +# 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) + 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['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 + + 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