2011-05-25 08:22:43 +02:00
# A grant is either global or per-db. This can be distinguished by the syntax
# of the name:
2012-02-09 20:26:00 +01:00
# user@host => global
# user@host/db => per-db
2011-05-25 08:22:43 +02:00
Puppet :: Type . type ( :database_grant ) . provide ( :mysql ) do
2012-02-09 20:26:00 +01:00
desc " Uses mysql as database. "
defaultfor :kernel = > 'Linux'
optional_commands :mysql = > 'mysql'
optional_commands :mysqladmin = > 'mysqladmin'
2012-04-27 20:38:45 +02:00
def self . prefetch ( resources )
@user_privs = query_user_privs
@db_privs = query_db_privs
end
def self . user_privs
@user_privs || query_user_privs
end
def self . db_privs
@db_privs || query_db_privs
end
def user_privs
self . class . user_privs
end
def db_privs
self . class . db_privs
end
def self . query_user_privs
2013-01-10 20:51:59 +01:00
results = mysql ( [ defaults_file , " mysql " , " -Be " , " describe user " ] . compact )
2012-04-27 20:38:45 +02:00
column_names = results . split ( / \ n / ) . map { | l | l . chomp . split ( / \ t / ) [ 0 ] }
2012-05-04 18:58:27 +02:00
@user_privs = column_names . delete_if { | e | ! ( e =~ / _priv$ / ) }
2012-04-27 20:38:45 +02:00
end
def self . query_db_privs
2013-01-10 20:51:59 +01:00
results = mysql ( [ defaults_file , " mysql " , " -Be " , " describe db " ] . compact )
2012-04-27 20:38:45 +02:00
column_names = results . split ( / \ n / ) . map { | l | l . chomp . split ( / \ t / ) [ 0 ] }
2012-05-04 18:58:27 +02:00
@db_privs = column_names . delete_if { | e | ! ( e =~ / _priv$ / ) }
2012-04-27 20:38:45 +02:00
end
2012-02-09 20:26:00 +01:00
def mysql_flush
2013-01-10 20:51:59 +01:00
mysqladmin ( [ defaults_file , " flush-privileges " ] . compact )
2012-02-09 20:26:00 +01:00
end
# this parses the
def split_name ( string )
matches = / ^([^@]*)@([^ \/ ]*)( \/ (.*))?$ / . match ( string ) . captures . compact
case matches . length
when 2
{
:type = > :user ,
:user = > matches [ 0 ] ,
:host = > matches [ 1 ]
}
when 4
{
:type = > :db ,
:user = > matches [ 0 ] ,
:host = > matches [ 1 ] ,
:db = > matches [ 3 ]
}
end
end
def create_row
unless @resource . should ( :privileges ) . empty?
name = split_name ( @resource [ :name ] )
case name [ :type ]
when :user
2013-01-10 20:51:59 +01:00
mysql ( [ defaults_file , " mysql " , " -e " , " INSERT INTO user (host, user) VALUES ('%s', '%s') " % [
2012-02-09 20:26:00 +01:00
name [ :host ] , name [ :user ] ,
2013-01-10 20:51:59 +01:00
] ] . compact )
2012-02-09 20:26:00 +01:00
when :db
2013-01-10 20:51:59 +01:00
mysql ( [ defaults_file , " mysql " , " -e " , " INSERT INTO db (host, user, db) VALUES ('%s', '%s', '%s') " % [
2012-02-09 20:26:00 +01:00
name [ :host ] , name [ :user ] , name [ :db ] ,
2013-01-10 20:51:59 +01:00
] ] . compact )
2012-02-09 20:26:00 +01:00
end
mysql_flush
end
end
def destroy
2013-01-10 20:51:59 +01:00
mysql ( [ defaults_file , " mysql " , " -e " , " REVOKE ALL ON '%s'.* FROM '%s@%s' " % [ @resource [ :privileges ] , @resource [ :database ] , @resource [ :name ] , @resource [ :host ] ] ] . compact )
2012-02-09 20:26:00 +01:00
end
def row_exists?
name = split_name ( @resource [ :name ] )
fields = [ :user , :host ]
if name [ :type ] == :db
fields << :db
end
2013-04-30 11:38:13 +02:00
not mysql ( [ defaults_file , " mysql " , '-NBe' , " SELECT '1' FROM %s WHERE %s " % [ name [ :type ] , fields . map do | f | " %s='%s' " % [ f , name [ f ] ] end . join ( ' AND ' ) ] ] . compact ) . empty?
2012-02-09 20:26:00 +01:00
end
def all_privs_set?
all_privs = case split_name ( @resource [ :name ] ) [ :type ]
when :user
2012-04-27 20:38:45 +02:00
user_privs
2012-02-09 20:26:00 +01:00
when :db
2012-04-27 20:38:45 +02:00
db_privs
2012-02-09 20:26:00 +01:00
end
2012-05-04 18:58:27 +02:00
all_privs = all_privs . collect do | p | p . downcase end . sort . join ( " | " )
privs = privileges . collect do | p | p . downcase end . sort . join ( " | " )
2012-02-09 20:26:00 +01:00
all_privs == privs
end
def privileges
name = split_name ( @resource [ :name ] )
privs = " "
case name [ :type ]
when :user
2013-04-30 11:38:13 +02:00
privs = mysql ( [ defaults_file , " mysql " , " -Be " , " select * from mysql.user where user='%s' and host='%s' " % [ name [ :user ] , name [ :host ] ] ] . compact )
2012-02-09 20:26:00 +01:00
when :db
2013-04-30 11:38:13 +02:00
privs = mysql ( [ defaults_file , " mysql " , " -Be " , " select * from mysql.db where user='%s' and host='%s' and db='%s' " % [ name [ :user ] , name [ :host ] , name [ :db ] ] ] . compact )
2012-02-09 20:26:00 +01:00
end
if privs . match ( / ^$ / )
privs = [ ] # no result, no privs
else
# returns a line with field names and a line with values, each tab-separated
privs = privs . split ( / \ n / ) . map! do | l | l . chomp . split ( / \ t / ) end
# transpose the lines, so we have key/value pairs
privs = privs [ 0 ] . zip ( privs [ 1 ] )
privs = privs . select do | p | p [ 0 ] . match ( / _priv$ / ) and p [ 1 ] == 'Y' end
end
2012-05-04 18:58:27 +02:00
privs . collect do | p | p [ 0 ] end
2012-02-09 20:26:00 +01:00
end
def privileges = ( privs )
unless row_exists?
create_row
end
# puts "Setting privs: ", privs.join(", ")
name = split_name ( @resource [ :name ] )
stmt = ''
where = ''
all_privs = [ ]
case name [ :type ]
when :user
stmt = 'update user set '
2013-04-30 11:38:13 +02:00
where = " where user='%s' and host='%s' " % [ name [ :user ] , name [ :host ] ]
2012-04-27 20:38:45 +02:00
all_privs = user_privs
2012-02-09 20:26:00 +01:00
when :db
stmt = 'update db set '
2013-04-30 11:38:13 +02:00
where = " where user='%s' and host='%s' and db='%s' " % [ name [ :user ] , name [ :host ] , name [ :db ] ]
2012-04-27 20:38:45 +02:00
all_privs = db_privs
2012-02-09 20:26:00 +01:00
end
2012-05-04 18:58:27 +02:00
if privs [ 0 ] . downcase == 'all'
2012-02-09 20:26:00 +01:00
privs = all_privs
end
2012-05-04 18:58:27 +02:00
# Downcase the requested priviliges for case-insensitive selection
# we don't map! here because the all_privs object has to remain in
# the same case the DB gave it to us in
privs = privs . map { | p | p . downcase }
2012-02-09 20:26:00 +01:00
# puts "stmt:", stmt
2012-05-04 18:58:27 +02:00
set = all_privs . collect do | p | " %s = '%s' " % [ p , privs . include? ( p . downcase ) ? 'Y' : 'N' ] end . join ( ', ' )
2012-02-09 20:26:00 +01:00
# puts "set:", set
stmt = stmt << set << where
Add priv validation to database_grant provider
The mysql database_grant provider currently has what is arguably a heinous
design flaw. At present:
1. The 'privileges' parameter for the database_grant type, mysql provider,
does not expect the same syntax as the mysql Grant command ('SELECT',
'UPDATE', 'DELETE', etc). Rather, it expects the user to supply column
names used to store raw grants in the mysql.db or mysql.user tables
internally ('Select_priv', 'Update_priv', 'Delete_priv', etc).
2. If a user supplies `privileges => [ 'SELECT', 'INSERT' ]` instead of
`privileges => [ 'Select_priv', 'Insert_priv' ]`, the provider fails
silently and will continuously attempt to update the privileges with
each successive puppet run. In the specific example provided, all privs
for the user/db will be set to false since e.g. 'INSERT' does not match
any valid privilege.
Unfortunately it doesn't look simple to modify the provider such that the
intuitive SELECT, DELETE, etc. keywords can be used without changing
existing behavior. Leaving that alone for now, it *is* pretty simple to add
a validation function that will at least fail cleanly if non-functional
privilege values are supplied that don't mean anything to the provider. If
the user is trying to use valid MySQL Grant syntax, the new validation
procedure will recognize this and suggest a correction. Hopefully giving
users this kind of warning will clue them in to what kind of input the
provider expects.
2012-08-04 07:28:56 +02:00
validate_privs privs , all_privs
2013-01-10 20:51:59 +01:00
mysql ( [ defaults_file , " mysql " , " -Be " , stmt ] . compact )
2012-02-09 20:26:00 +01:00
mysql_flush
end
Add priv validation to database_grant provider
The mysql database_grant provider currently has what is arguably a heinous
design flaw. At present:
1. The 'privileges' parameter for the database_grant type, mysql provider,
does not expect the same syntax as the mysql Grant command ('SELECT',
'UPDATE', 'DELETE', etc). Rather, it expects the user to supply column
names used to store raw grants in the mysql.db or mysql.user tables
internally ('Select_priv', 'Update_priv', 'Delete_priv', etc).
2. If a user supplies `privileges => [ 'SELECT', 'INSERT' ]` instead of
`privileges => [ 'Select_priv', 'Insert_priv' ]`, the provider fails
silently and will continuously attempt to update the privileges with
each successive puppet run. In the specific example provided, all privs
for the user/db will be set to false since e.g. 'INSERT' does not match
any valid privilege.
Unfortunately it doesn't look simple to modify the provider such that the
intuitive SELECT, DELETE, etc. keywords can be used without changing
existing behavior. Leaving that alone for now, it *is* pretty simple to add
a validation function that will at least fail cleanly if non-functional
privilege values are supplied that don't mean anything to the provider. If
the user is trying to use valid MySQL Grant syntax, the new validation
procedure will recognize this and suggest a correction. Hopefully giving
users this kind of warning will clue them in to what kind of input the
provider expects.
2012-08-04 07:28:56 +02:00
def validate_privs ( set_privs , all_privs )
all_privs = all_privs . collect { | p | p . downcase }
set_privs = set_privs . collect { | p | p . downcase }
invalid_privs = Array . new
hints = Array . new
# Test each of the user provided privs to see if they exist in all_privs
set_privs . each do | priv |
invalid_privs << priv unless all_privs . include? ( priv )
hints << " #{ priv } _priv " if all_privs . include? ( " #{ priv } _priv " )
end
unless invalid_privs . empty?
# Print a decently helpful and gramatically correct error message
hints = " Did you mean ' #{ hints . join ( ',' ) } '? " unless hints . empty?
p = invalid_privs . size > 1 ? [ 's' , 'are not valid' ] : [ '' , 'is not valid' ]
detail = [ " The privilege #{ p [ 0 ] } ' #{ invalid_privs . join ( ',' ) } ' #{ p [ 1 ] } . " ]
fail [ detail , hints ] . join ( ' ' )
end
end
2013-01-10 20:51:59 +01:00
# Optional defaults file
def self . defaults_file
if File . file? ( " #{ Facter . value ( :root_home ) } /.my.cnf " )
" --defaults-file= #{ Facter . value ( :root_home ) } /.my.cnf "
else
nil
end
end
def defaults_file
self . class . defaults_file
end
2011-05-25 08:22:43 +02:00
end