]> git.donarmstrong.com Git - dsa-puppet.git/blobdiff - 3rdparty/modules/keystone/lib/puppet/provider/openstack.rb
add stackforge/keystone to 3rdparty
[dsa-puppet.git] / 3rdparty / modules / keystone / lib / puppet / provider / openstack.rb
diff --git a/3rdparty/modules/keystone/lib/puppet/provider/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/openstack.rb
new file mode 100644 (file)
index 0000000..8236df7
--- /dev/null
@@ -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