Completely overhaul keystone types and providers

This commit refactors the existing keystone
types and providers. The underlying functionally
that the previous types relied on was deprecated
as a part of the essex release.

It adds the following types:
  keystone_user
  keystone_tenant
  keystone_role
  keystone_user_role
  keystone_service
  keystone_endpoint

All of the types are implemented by the keystone
client tool (keystone)
This commit is contained in:
Dan Bode 2012-03-29 14:29:04 -07:00
parent b5f32c2220
commit ccb37d4ee5
18 changed files with 891 additions and 268 deletions

View File

@ -0,0 +1,70 @@
require 'puppet/util/inifile'
class Puppet::Provider::Keystone < Puppet::Provider
# retrieves the current token from keystone.conf
def self.admin_token
@admin_token ||= get_admin_token
end
def self.get_admin_token
"#{keystone_file['DEFAULT']['admin_token'].strip}"
end
def self.admin_endpoint
@admin_endpoint ||= get_admin_endpoint
end
def self.get_admin_endpoint
"http://127.0.0.1:#{keystone_file['DEFAULT']['admin_port'].strip}/v2.0/"
end
def self.keystone_file
return @keystone_file if @keystone_file
@keystone_file = Puppet::Util::IniConfig::File.new
@keystone_file.read('/etc/keystone/keystone.conf')
@keystone_file
end
def self.tenant_hash
@tenant_hash ||= build_tenant_hash
end
def tenant_hash
self.class.tenant_hash
end
def self.auth_keystone(*args)
keystone('--token', admin_token, '--endpoint', admin_endpoint, args)
end
def auth_keystone(*args)
self.class.auth_keystone(args)
end
private
def self.list_keystone_objects(type, number_columns)
# this assumes that all returned objects are of the form
# id, name, enabled_state, OTHER
list = auth_keystone("#{type}-list").split("\n")[3..-2].collect do |line|
row = line.split(/\s*\|\s*/)[1..-1]
if row.size != number_columns
raise(Puppet::Error, "Expected #{number_columns} columns for #{type} row, found #{row.size}. Line #{line}")
end
row
end
list
end
def self.get_keystone_object(type, id, attr)
auth_keystone("#{type}-get", id).split("\n")[3..-2].each do |line|
if line =~ /^\|\s*#{attr}\s*\|\s*(.*)?\s+\|$/
return $1.strip
else
nil
end
end
raise(Puppet::Error, "Could not find colummn #{attr} when getting #{type} #{id}")
end
end

View File

@ -0,0 +1,113 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_endpoint).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone endpoints
This provider makes a few assumptions/
1. assumes that the admin endpoint can be accessed via localhost.
2. Assumes that the admin token and port can be accessed from
/etc/keystone/keystone.conf
EOT
optional_commands :keystone => "keystone"
def self.endpoint_hash
@endpoint_hash ||= build_endpoint_hash
end
def endpoint_hash
self.class.endpoint_hash
end
def self.instances
endpoint_hash.collect do |k, v|
new(:name => k)
end
end
def create
optional_opts = []
{
:region => '--region',
:public_url => '--publicurl',
:internal_url => '--internalurl',
:admin_url => '--adminurl'
}.each do |param, opt|
if resource[param]
optional_opts.push(opt).push(resource[param])
end
end
service_id = self.class.list_keystone_objects('service', 4).detect do |user|
user[1] == resource[:name]
end.first
auth_keystone(
'endpoint-create',
'--service', service_id,
optional_opts
)
end
def exists?
endpoint_hash[resource[:name]]
end
def destroy
auth_keystone('endpoint-delete', endpoint_hash[resource[:name]][:id])
end
def id
endpoint_hash[resource[:name]][:id]
end
def region
endpoint_hash[resource[:name]][:region]
end
def public_url
endpoint_hash[resource[:name]][:public_url]
end
def internal_url
endpoint_hash[resource[:name]][:internal_url]
end
def admin_url
endpoint_hash[resource[:name]][:admin_url]
end
private
def self.build_endpoint_hash
hash = {}
list_keystone_objects('endpoint', 5).each do |endpoint|
service_id = get_service_id(endpoint[0])
service_name = get_keystone_object('service', service_id, 'name')
hash[service_name] = {
:id => endpoint[0],
:region => endpoint[1],
:public_url => endpoint[2],
:internal_url => endpoint[3],
:admin_url => endpoint[4],
:service_id => service_id
}
end
hash
end
# TODO - this needs to be replaced with a call to endpoint-get
# but endpoint-get is not currently supported from the admin url
def self.get_service_id(endpoint_id)
`python -c "from keystoneclient.v2_0 import client ; import os ; print [e.service_id for e in client.Client(endpoint='http://127.0.0.1:35357/v2.0/', token='service_token').endpoints.list() if e.id == u'#{endpoint_id}'][0]"`
end
end

View File

@ -1,92 +0,0 @@
class Puppet::Provider::KeystoneManager < Puppet::Provider
# parent class that knows how to interact
# with keystone-manager
def self.user_hash
@user_hash ||= build_user_hash
end
def self.tenant_hash
@tenant_hash ||= build_tenant_hash
end
def self.role_hash
@role_hash ||= build_role_hash
end
def user_hash
self.class.user_hash
end
def tenant_hash
self.class.tenant_hash
end
def role_hash
self.class.role_hash
end
def property_not_support(property_name)
raise(Puppet::Error, "Provider #{self.class} does not yet support the ability to update the property #{property_name}")
end
private
def self.build_user_hash
hash = {}
list_keystone_objects('user', 4).each do |user|
validate_enabled(user[2])
hash[user[1]] = {
:id => user[0],
:enabled => user[2],
:tenant => user[3]
}
end
hash
end
def self.build_tenant_hash
hash = {}
list_keystone_objects('tenant', 3).each do |tenant|
validate_enabled(tenant[2])
hash[tenant[1]] = {
:id => tenant[0],
:enabled => tenant[2],
}
end
hash
end
def self.build_role_hash
hash = {}
list_keystone_objects('role', 4).each do |role|
Puppet.warning("Found deplicate role #{role[1]}") if hash[role[1]]
hash[role[1]] = {
:id => role[0],
:service_id => role[2],
:description => role[3]
}
end
hash
end
def self.list_keystone_objects(type, number_columns)
# this assumes that all returned objects are of the form
# id, name, enabled_state, OTHER
list = keystone_manage(type, 'list').split("\n")[5..-2].collect do |line|
row = line.split(/\s*\|\s*/)[1..-1]
if row.size != number_columns
raise(Puppet::Error, "Expected #{number_columns} columns for #{type} row, found #{list.size}. Line #{line}")
end
row
end
list
end
def self.validate_enabled(value)
unless value == 'True' || value == 'False'
raise(Puppet::Error, "Invalid value #{value} for enabled attribute")
end
end
end

View File

@ -0,0 +1,63 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_role).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone roles
EOT
optional_commands :keystone => "keystone"
def self.role_hash
@role_hash ||= build_role_hash
end
def role_hash
self.class.role_hash
end
def self.instances
role_hash.collect do |k, v|
new(:name => k)
end
end
def create
auth_keystone(
'role-create',
'--name', resource[:name]
)
end
def exists?
role_hash[resource[:name]]
end
def destroy
auth_keystone('role-delete', role_hash[resource[:name]][:id])
end
def id
role_hash[resource[:name]][:id]
end
private
def self.build_role_hash
hash = {}
list_keystone_objects('role', 2).each do |role|
hash[role[1]] = {
:id => role[0],
}
end
hash
end
end

View File

@ -1,52 +0,0 @@
require 'puppet/provider/keystone_manage'
Puppet::Type.type(:keystone_role).provide(
:keystone_manage,
:parent => Puppet::Provider::KeystoneManager
) do
optional_commands :keystone_manage => 'keystone-manage'
def self.instances
role_hash.collect do |k, v|
puts "|#{k}|"
new(:name => k)
end
end
def create
keystone_manage('role', 'add', resource[:name], resource[:service])
end
def exists?
role_hash[resource[:name]]
end
def destroy
raise(Puppet::Error, "keystone-manage does not support removing roles")
end
def id
role_hash[resource[:name]][:id]
end
def service
role_hash[resource[:name]][:service]
end
def description
role_hash[resource[:name]][:description]
end
def id=(id)
raise(Puppet::Error, "Id is a read only property")
end
def service=(service_name)
property_not_supported('service')
end
def description=(description)
property_not_supported('description')
end
end

View File

@ -0,0 +1,95 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_service).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone services
This provider makes a few assumptions/
1. assumes that the admin endpoint can be accessed via localhost.
2. Assumes that the admin token and port can be accessed from
/etc/keystone/keystone.conf
Does not support the ability to list all
EOT
optional_commands :keystone => "keystone"
def self.service_hash
@service_hash ||= build_service_hash
end
def service_hash
self.class.service_hash
end
def self.instances
service_hash.collect do |k, v|
new(:name => k)
end
end
def create
optional_opts = []
raise(Puppet::Error, "Required property type not specified for KeystoneService[#{resource[:name]}]") unless resource[:type]
if resource[:description]
optional_opts.push('--description').push(resource[:description])
end
auth_keystone(
'service-create',
'--name', resource[:name],
'--type', resource[:type],
optional_opts
)
end
def exists?
service_hash[resource[:name]]
end
def destroy
auth_keystone('service-delete', service_hash[resource[:name]][:id])
end
def id
service_hash[resource[:name]][:id]
end
def type
service_hash[resource[:name]][:type]
end
def type=(value)
raise(Puppet::Error, "service-update is not currently supported by the keystone sql driver")
end
def description
service_hash[resource[:name]][:description]
end
def description=(value)
raise(Puppet::Error, "service-update is not currently supported by the keystone sql driver")
end
private
def self.build_service_hash
hash = {}
list_keystone_objects('service', 4).each do |user|
hash[user[1]] = {
:id => user[0],
:type => user[2],
:description => user[3]
}
end
hash
end
end

View File

@ -0,0 +1,108 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_tenant).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone tenants
This provider makes a few assumptions/
1. assumes that the admin endpoint can be accessed via localhost.
2. Assumes that the admin token and port can be accessed from
/etc/keystone/keystone.conf
One string difference, is that it does not know how to change the
name of a tenant
EOT
optional_commands :keystone => "keystone"
def self.keystone_type
'tenant'
end
def self.tenant_hash
@tenant_hash ||= build_tenant_hash
end
def tenant_hash
self.class.tenant_hash
end
def self.instances
tenant_hash.collect do |k, v|
new(:name => k)
end
end
def create
optional_opts = []
if resource[:description]
optional_opts.push('--description').push(resource[:description])
end
auth_keystone(
'tenant-create',
'--name', resource[:name],
'--enabled', resource[:enabled],
optional_opts
)
end
def exists?
tenant_hash[resource[:name]]
end
def destroy
auth_keystone('tenant-delete', tenant_hash[resource[:name]][:id])
end
def enabled
tenant_hash[resource[:name]][:enabled]
end
def enabled=(value)
Puppet.warning("I am not sure if this is supported yet")
auth_keystone(
"tenant-update",
'--enabled', value,
tenant_hash[resource[:name]][:id]
)
end
def description
tenant_hash[resource[:name]][:description]
end
def description=(value)
auth_keystone(
"tenant-update",
'--description', value,
tenant_hash[resource[:name]][:id]
)
end
def id
tenant_hash[resource[:name]][:id]
end
private
def self.build_tenant_hash
hash = {}
list_keystone_objects('tenant', 3).each do |tenant|
# I may need to make a call to get to get the description
description = get_keystone_object('tenant', tenant[0], 'description')
hash[tenant[1]] = {
:id => tenant[0],
:description => description,
:enabled => tenant[2],
}
end
hash
end
end

View File

@ -1,57 +0,0 @@
require 'puppet/provider/keystone_manage'
Puppet::Type.type(:keystone_tenant).provide(
:keystone_manage,
:parent => Puppet::Provider::KeystoneManager
) do
desc <<-EOT
Provider that uses the keystone-manage tool to
manage keystone tenants
As of the essex release, there is no way to delete an existing
tenant. A disabled tenant will be considered the same as an
absent tenant (although they are not quite the same, I do not
think it will be possible to create a tenant once it has been
deleted)
EOT
optional_commands :keystone_manage => 'keystone-manage'
def self.instances
tenant_hash.collect do |k, v|
new(:name => k)
end
end
def create
keystone_manage('tenant', 'add', resource[:name])
end
def exists?
# a tenant is absent if it doesnt exist or if it is disabled
tenant_hash[resource[:name]] and tenant_hash[resource[:name]][:enabled] == 'True'
end
def destroy
Puppet.warning("Deleting the tenant is not currently supported, it will be disabled")
keystone_manage('tenant', 'disable', resource[:name])
end
# def enabled=(state)
# if state == 'True'
# raise(Puppet::Error, 'Enabling a disabled Tenant is not currently supported')
# else
# keystone_manage('tenant', 'disable', resource[:name])
# end
# end
# def enabled
# tenant_hash[resource['name']][:enabled]
# end
def id
tenant_hash[resource['name']][:id]
end
end

View File

@ -0,0 +1,133 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_user).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone users
This provider makes a few assumptions/
1. assumes that the admin endpoint can be accessed via localhost.
2. Assumes that the admin token and port can be accessed from
/etc/keystone/keystone.conf
Does not support the ability to update the user's name
EOT
optional_commands :keystone => "keystone"
def self.user_hash
@user_hash ||= build_user_hash
end
def user_hash
self.class.user_hash
end
def self.instances
user_hash.collect do |k, v|
new(:name => k)
end
end
def create
optional_opts = []
if resource[:email]
optional_opts.push('--email').push(resource[:email])
end
if resource[:password]
optional_opts.push('--pass').push(resource[:password])
end
if resource[:tenant]
# TODO - do I need to convert this from the name to id?
optional_opts.push('--tenant_id').push(resource[:tenant])
end
auth_keystone(
'user-create',
'--name', resource[:name],
'--enabled', resource[:enabled],
optional_opts
)
end
def exists?
user_hash[resource[:name]]
end
def destroy
auth_keystone('user-delete', user_hash[resource[:name]][:id])
end
def enabled
user_hash[resource[:name]][:enabled]
end
def enabled=(value)
auth_keystone(
"user-update",
'--enabled', value,
user_hash[resource[:name]][:id]
)
end
# def password
# Puppet.warning("Cannot retrieve password")
# user_hash[resource[:name]][:password]
# end
#
# def password=(value)
# Puppet.warning('Cannot update password')
# # user-password-update does not support the ability know what the
# # current value is
# #auth_keystone(
# # 'user-password-update',
# # '--pass', value,
# # user_hash[resource[:name]][:id]
# end
def tenant
user_hash[resource[:name]][:tenant]
end
def email
user_hash[resource[:name]][:email]
end
def email=(value)
auth_keystone(
"user-update",
'--email', value,
user_hash[resource[:name]][:id]
)
end
def id
user_hash[resource[:name]][:id]
end
private
def self.build_user_hash
hash = {}
list_keystone_objects('user', 4).each do |user|
tenant = get_keystone_object('user', user[0], 'tenantId')
password = 'nil'
hash[user[3]] = {
:id => user[0],
:enabled => user[1],
:email => user[2],
:name => user[3],
:password => password,
:tenant => tenant
}
end
hash
end
end

View File

@ -1,54 +0,0 @@
require 'puppet/provider/keystone_manage'
Puppet::Type.type(:keystone_user).provide(
:keystone_manage,
:parent => Puppet::Provider::KeystoneManager
) do
optional_commands :keystone_manage => 'keystone-manage'
def self.instances
user_hash.collect do |k, v|
puts "|#{k}|"
new(:name => k)
end
end
def create
['tenant', 'password'].each do |x|
raise(Puppet::Error, "Cannot create keystone user without parameter: #{x}") unless self[x.to_sym]
end
keystone_manage(
'user',
'add',
resource[:name],
resource[:password],
resource[:tenant]
)
end
def exists?
user_hash[resource[:name]] and user_hash[resource[:name]][:enabled] == 'True'
end
def destroy
Puppet.warning("Deleting the user is not currently supported, it will be disabled")
keystone_manage('user', 'disable', resource[:name])
end
def tenant=(tenant)
raise(Puppet::Error, "Setting the user tenant property is currently not supported")
end
def tenant
# I need to translate this from the other command
tenant_id = user_hash[resource[:name]][:tenant]
if tenant_id == 'None'
'None'
else
# maybe I should check more explicityly
# to see if the id is valid
tenant_hash.find {|k,v| v[:id] == tenant_id }[0]
end
end
end

View File

@ -0,0 +1,167 @@
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'puppet/provider/keystone'
Puppet::Type.type(:keystone_user_role).provide(
:keystone,
:parent => Puppet::Provider::Keystone
) do
desc <<-EOT
Provider that uses the keystone client tool to
manage keystone role assignments to users
EOT
optional_commands :keystone => "keystone"
def self.user_role_hash
@user_role_hash ||= build_user_role_hash
end
def user_role_hash
self.class.user_role_hash
end
def self.instances
user_role_hash.collect do |k, v|
new(:name => k)
end
end
def create
user_id, tenant_id = get_user_and_tenant
resource[:roles].each do |role_name|
role_id = self.class.get_roles[role_name]
auth_keystone(
'user-role-add',
'--user', user_id,
'--tenant_id', tenant_id,
'--role', role_id
)
end
end
def get_user_and_tenant
user, tenant = resource[:name].split('@', 2)
[self.class.get_users[user], self.class.get_tenants[tenant]]
end
def exists?
user_role_hash[resource[:name]]
end
def destroy
user_role_hash[resource[:name]][:role_ids].each do |role_id|
auth_keystone(
'user-role-remove',
'--user', user_role_hash[resource[:name]][:user_id],
'--tenant_id', user_role_hash[resource[:name]][:tenant_id],
'--role', role_id
)
end
end
def id
user_role_hash[resource[:name]][:id]
end
def roles
user_role_hash[resource[:name]][:role_names]
end
def roles=(value)
# determine the roles to be added and removed
require 'ruby-debug';debugger
remove = roles - Array(value)
add = Array(value) - roles
user_id, tenant_id = get_user_and_tenant
add.each do |role_name|
role_id = self.class.get_roles[role_name]
auth_keystone(
'user-role-add',
'--user', user_id,
'--tenant_id', tenant_id,
'--role', role_id
)
end
remove.each do |role_name|
role_id = self.class.get_roles[role_name]
auth_keystone(
'user-role-remove',
'--user', user_id,
'--tenant_id', tenant_id,
'--role', role_id
)
end
end
private
def self.build_user_role_hash
hash = {}
get_users.each do |user_name, user_id|
get_tenants.each do |tenant_name, tenant_id|
list_user_roles(user_id, tenant_id).each do |role|
hash["#{user_name}@#{tenant_name}"] ||= {
:user_id => user_id,
:tenant_id => tenant_id,
:role_names => [],
:role_ids => []
}
hash["#{user_name}@#{tenant_name}"][:role_names].push(role[1])
hash["#{user_name}@#{tenant_name}"][:role_ids].push(role[0])
end
end
end
#require 'ruby-debug';debugger
hash
end
def self.list_user_roles(user_id, tenant_id)
# this assumes that all returned objects are of the form
# id, name, enabled_state, OTHER
number_columns = 2
role_output = auth_keystone('role-list', '--user', user_id, '--tenant_id', tenant_id)
list = role_output.split("\n")[3..-2].collect do |line|
row = line.split(/\s*\|\s*/)[1..-1]
if row.size != number_columns
raise(Puppet::Error, "Expected #{number_columns} columns for #{type} row, found #{list.size}. Line #{line}")
end
row
end
list
end
def self.get_users
return @users if @users
@users = {}
list_keystone_objects('user', 4).each do |user|
@users[user[3]] = user[0]
end
@users
end
def self.get_tenants
return @tenants if @tenants
@tenants = {}
list_keystone_objects('tenant', 3).each do |tenant|
@tenants[tenant[1]] = tenant[0]
end
@tenants
end
def self.get_roles
return @roles if @roles
@roles = {}
list_keystone_objects('role', 2).each do |role|
@roles[role[1]] = role[0]
end
@roles
end
end

View File

@ -0,0 +1,36 @@
Puppet::Type.newtype(:keystone_endpoint) do
desc <<-EOT
This is currently used to model the management of
keystone endpoint.
EOT
ensurable
newparam(:name, :namevar => true) do
newvalues(/\S+/)
end
newproperty(:id) do
validate do |v|
raise(Puppet::Error, 'This is a read only property')
end
end
newproperty(:region) do
defaultto('regionOne')
end
newproperty(:public_url) do
end
newproperty(:internal_url) do
end
newproperty(:admin_url) do
end
end

View File

@ -1,20 +1,22 @@
Puppet::Type.newtype(:keystone_role) do
desc <<-EOT
Type to create new keystone roles.
This is currently used to model the creation of
keystone roles.
EOT
ensurable
newparam(:name, :namevar => true) do
newvalues(/\S+/)
end
newproperty(:id) do
validate do |v|
raise(Puppet::Error, 'This is a read only property')
end
end
newproperty(:service) do
end
newproperty(:description) do
end
end

View File

@ -0,0 +1,28 @@
Puppet::Type.newtype(:keystone_service) do
desc <<-EOT
This is currently used to model the management of
keystone services.
EOT
ensurable
newparam(:name, :namevar => true) do
newvalues(/\S+/)
end
newproperty(:id) do
validate do |v|
raise(Puppet::Error, 'This is a read only property')
end
end
newproperty(:type) do
end
newproperty(:description) do
end
end

View File

@ -1,8 +1,11 @@
Puppet::Type.newtype(:keystone_tenant) do
desc <<-EOT
This type can be used to create
This type can be used to manage
keystone tenants.
This is assumed to be running on the same node
as your keystone API server.
EOT
ensurable
@ -11,12 +14,15 @@ Puppet::Type.newtype(:keystone_tenant) do
newvalues(/\w+/)
end
# newproperty(:enabled) do
# newvalues(/(t|T)rue/, /(f|F)alse/)
# munge do |value|
# value.to_s.capitalize
# end
# end
newproperty(:enabled) do
newvalues(/(t|T)rue/, /(f|F)alse/)
defaultto('True')
munge do |value|
value.to_s.capitalize
end
end
newproperty(:description)
newproperty(:id) do
validate do |v|

View File

@ -10,10 +10,22 @@ Puppet::Type.newtype(:keystone_user) do
EOT
# TODO support description??
ensurable
newparam(:name, :namevar => true) do
newvalues(/\S+/)
end
newproperty(:enabled) do
newvalues(/(t|T)rue/, /(f|F)alse/)
defaultto('True')
munge do |value|
value.to_s.capitalize
end
end
newparam(:password) do
newvalues(/\S+/)
end
@ -22,5 +34,14 @@ Puppet::Type.newtype(:keystone_user) do
newvalues(/\S+/)
end
newproperty(:email) do
newvalues(/\S+@\S+/)
end
newproperty(:id) do
validate do |v|
raise(Puppet::Error, 'This is a read only property')
end
end
end

View File

@ -0,0 +1,36 @@
Puppet::Type.newtype(:keystone_user_role) do
desc <<-EOT
This is currently used to model the creation of
keystone users roles.
User roles are an assigment of a role to a user on
a certain tenant. The combintation of all of these
attributes is unique.
EOT
ensurable
newparam(:name, :namevar => true) do
newvalues(/^\S+@\S+$/)
#munge do |value|
# matchdata = /(\S+)@(\S+)/.match(value)
# {
# :user => matchdata[1],
# :tenant => matchdata[2]
# }
#nd
end
newproperty(:roles, :array_matching => :all) do
end
newproperty(:id) do
validate do |v|
raise(Puppet::Error, 'This is a read only property')
end
end
end