require 'puppet/provider/keystone' Puppet::Type.type(:keystone_endpoint).provide( :openstack, :parent => Puppet::Provider::Keystone ) do desc "Provider to manage keystone endpoints." include PuppetX::Keystone::CompositeNamevar::Helpers @endpoints = nil @services = nil @credentials = Puppet::Provider::Openstack::CredentialsV3.new def initialize(value={}) super(value) @property_flush = {} end def create # Reset the cache. self.class.services = nil name = resource[:name] region = resource[:region] type = resource[:type] type = self.class.type_from_service(name) unless set?(:type) @property_hash[:type] = type services = self.class.services.find_all { |s| s[:name] == name } service = services.find { |s| s[:type] == type } if service.nil? && services.count == 1 # For backward comptatibility, match the service by name only. name = services[0][:id] else # Math the service by id. name = service[:id] if service end ids = [] created = false [:admin_url, :internal_url, :public_url].each do |scope| if resource[scope] created = true ids << endpoint_create(name, region, scope.to_s.sub(/_url$/, ''), resource[scope])[:id] end end if created @property_hash[:id] = ids.join(',') @property_hash[:ensure] = :present else warning('Specifying a keystone_endpoint without an ' \ 'admin_url/public_url/internal_url ' \ "won't create the endpoint at all, despite what Puppet is saying.") @property_hash[:ensure] = :absent end end def destroy ids = @property_hash[:id].split(',') ids.each do |id| self.class.request('endpoint', 'delete', id) end @property_hash.clear end def exists? @property_hash[:ensure] == :present end mk_resource_methods def public_url=(value) @property_flush[:public_url] = value end def internal_url=(value) @property_flush[:internal_url] = value end def admin_url=(value) @property_flush[:admin_url] = value end def region=(_) fail(Puppet::Error, "Updating the endpoint's region is not currently supported.") end def self.instances names = [] list = [] endpoints.each do |current| name = transform_name(current[:region], current[:service_name], current[:service_type]) unless names.include?(name) names << name endpoint = { :name => name, current[:interface].to_sym => current } endpoints.each do |ep_osc| if (ep_osc[:id] != current[:id]) && (ep_osc[:service_name] == current[:service_name]) && (ep_osc[:service_type] == current[:service_type]) endpoint.merge!(ep_osc[:interface].to_sym => ep_osc) end end list << endpoint end end list.collect do |endpoint| new( :name => endpoint[:name], :ensure => :present, :id => "#{endpoint[:admin][:id]},#{endpoint[:internal][:id]},#{endpoint[:public][:id]}", :region => endpoint[:admin][:region], :admin_url => endpoint[:admin][:url], :internal_url => endpoint[:internal][:url], :public_url => endpoint[:public][:url] ) end end def self.prefetch(resources) prefetch_composite(resources) do |sorted_namevars| name = sorted_namevars[0] region = sorted_namevars[1] type = sorted_namevars[2] transform_name(region, name, type) end end def flush if @property_flush && @property_hash[:id] ids = @property_hash[:id].split(',') if @property_flush[:admin_url] self.class.request('endpoint', 'set', [ids[0], "--url=#{resource[:admin_url]}"]) end if @property_flush[:internal_url] self.class.request('endpoint', 'set', [ids[1], "--url=#{resource[:internal_url]}"]) end if @property_flush[:public_url] self.class.request('endpoint', 'set', [ids[2], "--url=#{resource[:public_url]}"]) end end @property_hash = resource.to_hash end private def endpoint_create(name, region, interface, url) properties = [name, interface, url, '--region', region] self.class.request('endpoint', 'create', properties) end private def self.endpoints return @endpoints unless @endpoints.nil? @endpoints = request('endpoint', 'list') end def self.endpoints=(value) @endpoints = value end def self.services return @services unless @services.nil? @services = request('service', 'list') end def self.services=(value) @services = value end def self.endpoint_from_region_name(region, name) endpoints.find_all { |e| e[:region] == region && e[:service_name] == name } .map { |e| e[:service_type] }.uniq end def self.type_from_service(name) types = services.find_all { |s| s[:name] == name }.map { |e| e[:type] }.uniq if types.count == 1 types[0] else # We don't fail here as it can happen during a ensure => absent. PuppetX::Keystone::CompositeNamevar::Unset end end def self.service_type(services, region, name) nbr_of_services = services.count err_msg = ["endpoint matching #{region}/#{name}:"] type = nil case when nbr_of_services == 1 type = services[0] when nbr_of_services > 1 err_msg += [endpoint_from_region_name(region, name).join(' ')] when nbr_of_services < 1 # Then we try to get the type by service name. type = type_from_service(name) end if !type.nil? type else fail(Puppet::Error, 'Cannot get the correct endpoint type: ' \ "#{err_msg.join(' ')}") end end def self.transform_name(region, name, type) if type == PuppetX::Keystone::CompositeNamevar::Unset type = service_type(endpoint_from_region_name(region, name), region, name) end if type == PuppetX::Keystone::CompositeNamevar::Unset Puppet.debug("Could not find the type for endpoint #{region}/#{name}") "#{region}/#{name}" else "#{region}/#{name}::#{type}" end end end