]> git.donarmstrong.com Git - dsa-puppet.git/blob - 3rdparty/modules/keystone/lib/puppet/provider/openstack.rb
8236df709f24e54e8096c26446503f6b26a0b127
[dsa-puppet.git] / 3rdparty / modules / keystone / lib / puppet / provider / openstack.rb
1 # TODO: This needs to be extracted out into openstacklib in the Kilo cycle
2 require 'csv'
3 require 'puppet'
4
5 class Puppet::Error::OpenstackAuthInputError < Puppet::Error
6 end
7
8 class Puppet::Error::OpenstackUnauthorizedError < Puppet::Error
9 end
10
11 class Puppet::Provider::Openstack < Puppet::Provider
12
13   initvars # so commands will work
14   commands :openstack => 'openstack'
15
16   def request(service, action, object, credentials, *properties)
17     if password_credentials_set?(credentials)
18       auth_args = password_auth_args(credentials)
19     elsif openrc_set?(credentials)
20       credentials = get_credentials_from_openrc(credentials['openrc'])
21       auth_args = password_auth_args(credentials)
22     elsif service_credentials_set?(credentials)
23       auth_args = token_auth_args(credentials)
24     elsif env_vars_set?
25       # noop; auth needs no extra arguments
26       auth_args = nil
27     else  # All authentication efforts failed
28       raise(Puppet::Error::OpenstackAuthInputError, 'No credentials provided.')
29     end
30     args = [object, properties, auth_args].flatten.compact
31     authenticate_request(service, action, args)
32   end
33
34   def self.request(service, action, object, *properties)
35     if env_vars_set?
36       # noop; auth needs no extra arguments
37       auth_args = nil
38     else  # All authentication efforts failed
39       raise(Puppet::Error::OpenstackAuthInputError, 'No credentials provided.')
40     end
41     args = [object, properties, auth_args].flatten.compact
42     authenticate_request(service, action, args)
43   end
44
45   # Returns an array of hashes, where the keys are the downcased CSV headers
46   # with underscores instead of spaces
47   def self.authenticate_request(service, action, *args)
48     rv = nil
49     timeout = 10
50     end_time = Time.now.to_i + timeout
51     loop do
52       begin
53         if(action == 'list')
54           response = openstack(service, action, '--quiet', '--format', 'csv', args)
55           response = parse_csv(response)
56           keys = response.delete_at(0) # ID,Name,Description,Enabled
57           rv = response.collect do |line|
58             hash = {}
59             keys.each_index do |index|
60               key = keys[index].downcase.gsub(/ /, '_').to_sym
61               hash[key] = line[index]
62             end
63             hash
64           end
65         elsif(action == 'show' || action == 'create')
66           rv = {}
67           # shell output is name="value"\nid="value2"\ndescription="value3" etc.
68           openstack(service, action, '--format', 'shell', args).split("\n").each do |line|
69             # key is everything before the first "="
70             key, val = line.split("=", 2)
71             next unless val # Ignore warnings
72             # value is everything after the first "=", with leading and trailing double quotes stripped
73             val = val.gsub(/\A"|"\Z/, '')
74             rv[key.downcase.to_sym] = val
75           end
76         else
77           rv = openstack(service, action, args)
78         end
79         break
80       rescue Puppet::ExecutionFailure => e
81         if e.message =~ /HTTP 401/
82           raise(Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate.')
83         elsif e.message =~ /Unable to establish connection/
84           current_time = Time.now.to_i
85           if current_time > end_time
86             break
87           else
88             wait = end_time - current_time
89             Puppet::debug("Non-fatal error: \"#{e.message}\"; retrying for #{wait} more seconds.")
90             if wait > timeout - 2 # Only notice the first time
91               notice("#{service} service is unavailable. Will retry for up to #{wait} seconds.")
92             end
93           end
94           sleep(2)
95         else
96           raise e
97         end
98       end
99     end
100     return rv
101   end
102
103   def authenticate_request(service, action, *args)
104     self.class.authenticate_request(service, action, *args)
105   end
106
107   private
108
109   def password_credentials_set?(auth_params)
110     auth_params && auth_params['username'] && auth_params['password'] && auth_params['tenant_name'] && auth_params['auth_url']
111   end
112
113
114   def openrc_set?(auth_params)
115     auth_params && auth_params['openrc']
116   end
117
118
119   def service_credentials_set?(auth_params)
120     auth_params && auth_params['token'] && auth_params['auth_url']
121   end
122
123
124   def self.env_vars_set?
125     ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['OS_TENANT_NAME'] && ENV['OS_AUTH_URL']
126   end
127
128
129   def env_vars_set?
130     self.class.env_vars_set?
131   end
132
133
134
135   def self.password_auth_args(credentials)
136     ['--os-username',    credentials['username'],
137      '--os-password',    credentials['password'],
138      '--os-tenant-name', credentials['tenant_name'],
139      '--os-auth-url',    credentials['auth_url']]
140   end
141
142   def password_auth_args(credentials)
143     self.class.password_auth_args(credentials)
144   end
145
146
147   def self.token_auth_args(credentials)
148     ['--os-token',    credentials['token'],
149      '--os-url', credentials['auth_url']]
150   end
151
152   def token_auth_args(credentials)
153     self.class.token_auth_args(credentials)
154   end
155
156   def get_credentials_from_openrc(file)
157     creds = {}
158     File.open(file).readlines.delete_if{|l| l=~ /^#/}.each do |line|
159       key, value = line.split('=')
160       key = key.split(' ').last.downcase.sub(/^os_/, '')
161       value = value.chomp.gsub(/'/, '')
162       creds[key] = value
163     end
164     return creds
165   end
166
167
168   def self.get_credentials_from_env
169     env = ENV.to_hash.dup.delete_if { |key, _| ! (key =~ /^OS_/) }
170     credentials = {}
171     env.each do |name, value|
172       credentials[name.downcase.sub(/^os_/, '')] = value
173     end
174     credentials
175   end
176
177   def get_credentials_from_env
178     self.class.get_credentials_from_env
179   end
180
181   def self.parse_csv(text)
182     # Ignore warnings - assume legitimate output starts with a double quoted
183     # string.  Errors will be caught and raised prior to this
184     text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n")
185     return CSV.parse(text + "\n")
186   end
187
188 end