From f6923bbe0a4560fa10bb8ee6fd57b55da644c1b9 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Mon, 9 Mar 2015 14:13:24 +0000 Subject: [PATCH 01/11] add .project to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 899499c..b79a798 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ Gemfile.lock /pkg/ +.project From d2db94646781ba782ff012cc5a8c4333fd77d243 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Mon, 9 Mar 2015 14:13:33 +0000 Subject: [PATCH 02/11] rename and update usage for mysql_ to percona_ functions --- .../parser/functions/percona_password.rb | 9 + lib/puppet/provider/percona_database/mysql.rb | 49 +++++ lib/puppet/provider/percona_grant/mysql.rb | 197 ++++++++++++++++++ lib/puppet/provider/percona_user/mysql.rb | 54 +++++ lib/puppet/type/percona_database.rb | 26 +++ lib/puppet/type/percona_grant.rb | 74 +++++++ lib/puppet/type/percona_user.rb | 28 +++ manifests/database.pp | 2 +- manifests/rights.pp | 6 +- 9 files changed, 441 insertions(+), 4 deletions(-) create mode 100644 lib/puppet/parser/functions/percona_password.rb create mode 100644 lib/puppet/provider/percona_database/mysql.rb create mode 100644 lib/puppet/provider/percona_grant/mysql.rb create mode 100644 lib/puppet/provider/percona_user/mysql.rb create mode 100644 lib/puppet/type/percona_database.rb create mode 100644 lib/puppet/type/percona_grant.rb create mode 100644 lib/puppet/type/percona_user.rb diff --git a/lib/puppet/parser/functions/percona_password.rb b/lib/puppet/parser/functions/percona_password.rb new file mode 100644 index 0000000..3177f2d --- /dev/null +++ b/lib/puppet/parser/functions/percona_password.rb @@ -0,0 +1,9 @@ +# hash a string as mysql's "PASSWORD()" function would do it +require 'digest/sha1' + +module Puppet::Parser::Functions + newfunction(:percona_password, :type => :rvalue) do |args| + '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(args[0])).upcase + end +end + diff --git a/lib/puppet/provider/percona_database/mysql.rb b/lib/puppet/provider/percona_database/mysql.rb new file mode 100644 index 0000000..8ae8195 --- /dev/null +++ b/lib/puppet/provider/percona_database/mysql.rb @@ -0,0 +1,49 @@ +Puppet::Type.type(:percona_database).provide(:mysql) do + + desc "Use mysql as database." + + defaultfor :kernel => 'Linux' + + optional_commands :mysqladmin => 'mysqladmin' + optional_commands :mysql => 'mysql' + + def mysql_args(*args) + if @resource[:mgmt_cnf].is_a?(String) + args.insert(0, "--defaults-file=#{@resource[:mgmt_cnf]}") + elsif File.file?("#{Facter.value(:root_home)}/.my.cnf") + args.insert(0, "--defaults-file=#{Facter.value(:root_home)}/.my.cnf") + end + args + end + + def self.instances + mysql(mysql_args('-NBe', "show databases")).split("\n").collect do |name| + new(:name => name) + end + end + + def create + mysql(mysql_args('-NBe', "create database `#{@resource[:name]}` character set #{resource[:charset]}")) + end + + def destroy + mysqladmin(mysql_args('-f', 'drop', @resource[:name])) + end + + def charset + mysql(mysql_args('-NBe', "show create database `#{resource[:name]}`")).match(/.*?(\S+)\s\*\//)[1] + end + + def charset=(value) + mysql(mysql_args('-NBe', "alter database `#{resource[:name]}` CHARACTER SET #{value}")) + end + + def exists? + begin + mysql(mysql_args('-NBe', "show databases")).match(/^#{@resource[:name]}$/) + rescue => e + debug(e.message) + return nil + end + end +end diff --git a/lib/puppet/provider/percona_grant/mysql.rb b/lib/puppet/provider/percona_grant/mysql.rb new file mode 100644 index 0000000..dac99ad --- /dev/null +++ b/lib/puppet/provider/percona_grant/mysql.rb @@ -0,0 +1,197 @@ +Puppet::Type.type(:percona_grant).provide(:mysql) do + + desc "Uses mysql as database." + + defaultfor :kernel => 'Linux' + + optional_commands :mysql => 'mysql' + optional_commands :mysqladmin => 'mysqladmin' + + def mysql_args(*args) + if @resource and @resource[:mgmt_cnf].is_a?(String) + args.insert(0, "--defaults-file=#{@resource[:mgmt_cnf]}") + elsif File.file?("#{Facter.value(:root_home)}/.my.cnf") + args.insert(0, "--defaults-file=#{Facter.value(:root_home)}/.my.cnf") + end + args + end + + def user_privs + unless defined? @@user_privs + @@user_privs = query_user_privs + end + @@user_privs + end + + def db_privs + unless defined? @@db_privs + @@db_privs = query_db_privs + end + @@db_privs + end + + def query_user_privs + results = mysql(mysql_args("mysql", "-Be", "describe user")) + column_names = results.split(/\n/).map { |l| l.chomp.split(/\t/)[0] } + @user_privs = column_names.delete_if { |e| !( e =~/_priv$/) } + @user_privs + end + + def query_db_privs + results = mysql(mysql_args("mysql", "-Be", "describe db")) + column_names = results.split(/\n/).map { |l| l.chomp.split(/\t/)[0] } + @db_privs = column_names.delete_if { |e| !(e =~/_priv$/) } + @db_privs + end + + def mysql_flush + mysqladmin(mysql_args("flush-privileges")) + 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 + mysql(mysql_args("mysql", "-e", "INSERT INTO user (host, user) VALUES ('%s', '%s')" % [ + name[:host], name[:user], + ])) + when :db + mysql(mysql_args("mysql", "-e", "INSERT INTO db (host, user, db) VALUES ('%s', '%s', '%s')" % [ + name[:host], name[:user], name[:db], + ])) + end + mysql_flush + end + end + + def destroy + mysql(mysql_args("mysql", "-e", "REVOKE ALL ON '%s'.* FROM '%s@%s'" % [ + @resource[:privileges], @resource[:database], @resource[:name], @resource[:host] + ])) + end + + def row_exists? + name = split_name(@resource[:name]) + fields = [:user, :host] + if name[:type] == :db + fields << :db + end + not mysql(mysql_args("mysql", "-NBe", "SELECT '1' FROM %s WHERE %s" % [ name[:type], fields.map do |f| "%s=\"%s\"" % [f, name[f]] end.join(' AND ')])).empty? + end + + def all_privs_set? + all_privs = case split_name(@resource[:name])[:type] + when :user + user_privs + when :db + db_privs + end + all_privs = all_privs.collect do |p| p.downcase end.sort.join("|") + privs = privileges.collect do |p| p.downcase end.sort.join("|") + + all_privs == privs + end + + def privileges + name = split_name(@resource[:name]) + privs = "" + + case name[:type] + when :user + privs = mysql(mysql_args("mysql", "-Be", "select * from user where user='%s' and host='%s'" % [ name[:user], name[:host] ])) + when :db + privs = mysql(mysql_args("mysql", "-Be", "select * from db where user='%s' and host='%s' and db='%s'" % [ name[:user], name[:host], name[:db] ])) + 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 + + privs.collect do |p| p[0] end + 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 ' + where = " where user='%s' and host='%s'" % [ name[:user], name[:host] ] + all_privs = user_privs + when :db + stmt = 'update db set ' + where = " where user='%s' and host='%s'" % [ name[:user], name[:host] ] + all_privs = db_privs + end + + if privs[0].downcase == 'all' + privs = all_privs + end + + # 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 } + + set = all_privs.collect do |p| "%s = '%s'" % [p, privs.include?(p.downcase) ? 'Y' : 'N'] end.join(', ') + stmt = stmt << set << where + + validate_privs privs, all_privs + mysql(mysql_args("mysql", "-Be", stmt)) + mysql_flush + end + + 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 + +end diff --git a/lib/puppet/provider/percona_user/mysql.rb b/lib/puppet/provider/percona_user/mysql.rb new file mode 100644 index 0000000..4c48423 --- /dev/null +++ b/lib/puppet/provider/percona_user/mysql.rb @@ -0,0 +1,54 @@ +Puppet::Type.type(:percona_user).provide(:mysql) do + + desc "Use mysql as database." + + defaultfor :kernel => 'Linux' + + optional_commands :mysql => 'mysql' + optional_commands :mysqladmin => 'mysqladmin' + + def mysql_args(*args) + if @resource[:mgmt_cnf].is_a?(String) + args.insert(0, "--defaults-file=#{@resource[:mgmt_cnf]}") + elsif File.file?("#{Facter.value(:root_home)}/.my.cnf") + args.insert(0, "--defaults-file=#{Facter.value(:root_home)}/.my.cnf") + end + args + end + + # retrieve the current set of mysql users + def self.instances + users = mysql(mysql_args("mysql", '-BNe' "select concat(User, '@',Host) as User from mysql.user")).split("\n") + users.select{ |user| user =~ /.+@/ }.collect do |name| + new(:name => name) + end + end + + def mysql_flush + mysqladmin(mysql_args("flush-privileges")) + end + + def create + mysql(mysql_args("mysql", "-e", "create user '%s' identified by PASSWORD '%s'" % [ @resource[:name].sub("@", "'@'"), @resource.should(:password_hash) ])) + mysql_flush + end + + def destroy + mysql(mysql_args("mysql", "-e", "drop user '%s'" % @resource[:name].sub("@", "'@'"))) + mysql_flush + end + + def exists? + not mysql(mysql_args("mysql", "-NBe", "select '1' from user where CONCAT(user, '@', host) = '%s'" % @resource[:name])).empty? + end + + def password_hash + mysql(mysql_args("mysql", "-NBe", "select password from mysql.user where CONCAT( user, '@', host) = '%s'" % @resource.value(:name))).chomp + end + + def password_hash=(string) + mysql(mysql_args("mysql", "-e", "SET PASSWORD FOR '%s' = '%s'" % [ @resource[:name].sub("@", "'@'"), string ])) + mysql_flush + end +end + diff --git a/lib/puppet/type/percona_database.rb b/lib/puppet/type/percona_database.rb new file mode 100644 index 0000000..8ad3f5b --- /dev/null +++ b/lib/puppet/type/percona_database.rb @@ -0,0 +1,26 @@ +# This has to be a separate type to enable collecting +Puppet::Type.newtype(:percona_database) do + @doc = "Manage a database." + + ensurable + + newparam(:name, :namevar=>true) do + desc "The name of the database." + validate do |value| + unless value =~ /^\w+/ + raise ArgumentError, "%s is not a valid database name" % value + end + end + end + newparam(:mgmt_cnf) do + desc "The my.cnf to use for calls." + end + + newproperty(:charset) do + desc "The characterset to use for a database" + defaultto :utf8 + newvalue(/^\S+$/) + end + +end + diff --git a/lib/puppet/type/percona_grant.rb b/lib/puppet/type/percona_grant.rb new file mode 100644 index 0000000..3e8776e --- /dev/null +++ b/lib/puppet/type/percona_grant.rb @@ -0,0 +1,74 @@ +Puppet::Type.newtype(:percona_grant) do + @doc = "Manage a database user's rights." + #ensurable + + autorequire :mysql_database do + reqs = [] + matches = self[:name].match(/^([^@]+)@([^\/]+)\/(.+)$/) + unless matches.nil? + reqs << matches[3] + end + reqs + end + + autorequire :mysql_user do + reqs = [] + matches = self[:name].match(/^([^@]+)@([^\/]+).*$/) + unless matches.nil? + reqs << "%s@%s" % [ matches[1], matches[2] ] + end + reqs + end + + newparam(:name, :namevar=>true) do + desc "The primary key: either user@host for global privilges or user@host/database for database specific privileges" + end + + newparam(:mgmt_cnf) do + desc "The my.cnf to use for calls." + end + + newproperty(:privileges, :array_matching => :all) do + desc "The privileges the user should have. The possible values are implementation dependent." + + def should_to_s(newvalue = @should) + if newvalue + unless newvalue.is_a?(Array) + newvalue = [ newvalue ] + end + newvalue.collect do |v| v.downcase end.sort.join ", " + else + nil + end + end + + def is_to_s(currentvalue = @is) + if currentvalue + unless currentvalue.is_a?(Array) + currentvalue = [ currentvalue ] + end + currentvalue.collect do |v| v.downcase end.sort.join ", " + else + nil + end + end + + # use the sorted outputs for comparison + def insync?(is) + if defined? @should and @should + case self.should_to_s + when "all" + self.provider.all_privs_set? + when self.is_to_s(is) + true + else + false + end + else + true + end + end + + end +end + diff --git a/lib/puppet/type/percona_user.rb b/lib/puppet/type/percona_user.rb new file mode 100644 index 0000000..805ea43 --- /dev/null +++ b/lib/puppet/type/percona_user.rb @@ -0,0 +1,28 @@ +Puppet::Type.newtype(:percona_user) do + @doc = "Manage a database user." + + ensurable + + newparam(:name, :namevar => true) do + desc "The name of the user. This uses the 'username@hostname' form." + + validate do |value| + # https://dev.mysql.com/doc/refman/5.1/en/account-names.html + # Regex should problably be more like this: /^[`'"]?[^`'"]*[`'"]?@[`'"]?[\w%\.]+[`'"]?$/ + raise(ArgumentError, "Invalid database user #{value}") unless value =~ /[\w-]*@[\w%\.:]+/ + username = value.split('@')[0] + if username.size > 16 + raise ArgumentError, "MySQL usernames are limited to a maximum of 16 characters" + end + end + end + + newproperty(:password_hash) do + desc "The password hash of the user. Use mysql_password() for creating such a hash." + newvalue(/\w+/) + end + + newparam(:mgmt_cnf) do + desc "The my.cnf to use for calls." + end +end diff --git a/manifests/database.pp b/manifests/database.pp index 722fdfa..e0ef907 100644 --- a/manifests/database.pp +++ b/manifests/database.pp @@ -22,7 +22,7 @@ default => $mgmt_cnf, } - mysql_database { $name: + percona_database { $name: ensure => $ensure, charset => $charset, mgmt_cnf => $mycnf, diff --git a/manifests/rights.pp b/manifests/rights.pp index 0eb81f2..9650bad 100644 --- a/manifests/rights.pp +++ b/manifests/rights.pp @@ -128,11 +128,11 @@ if ! defined(Mysql_user["${_user}@${_host}"]) { $pwhash = $hash ? { - undef => mysql_password($password), + undef => percona_password($password), default => $hash, } - mysql_user { "${_user}@${_host}": + percona_user { "${_user}@${_host}": ensure => $ensure, password_hash => $pwhash, mgmt_cnf => $mycnf, @@ -142,7 +142,7 @@ } } - mysql_grant { $grant_name: + percona_grant { $grant_name: privileges => $priv, mgmt_cnf => $mycnf, require => [ From 22bf4197e68ef78b851eff91f506c3280d8cdae3 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Mon, 9 Mar 2015 14:21:44 +0000 Subject: [PATCH 03/11] Update percona_grant requires --- manifests/rights.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/rights.pp b/manifests/rights.pp index 9650bad..dabf02a 100644 --- a/manifests/rights.pp +++ b/manifests/rights.pp @@ -147,7 +147,7 @@ mgmt_cnf => $mycnf, require => [ Service[$::percona::service_name], - Mysql_user["${_user}@${_host}"], + Percona_user["${_user}@${_host}"], ] } From 0ef6b85e60fd790574c2afe1ae61abd763d76f23 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Mon, 9 Mar 2015 16:53:22 +0000 Subject: [PATCH 04/11] delete old mysql_ functions --- lib/puppet/parser/functions/mysql_password.rb | 9 - lib/puppet/provider/mysql_database/mysql.rb | 49 ----- lib/puppet/provider/mysql_grant/mysql.rb | 197 ------------------ lib/puppet/provider/mysql_user/mysql.rb | 54 ----- lib/puppet/type/mysql_database.rb | 26 --- lib/puppet/type/mysql_grant.rb | 74 ------- lib/puppet/type/mysql_user.rb | 28 --- 7 files changed, 437 deletions(-) delete mode 100644 lib/puppet/parser/functions/mysql_password.rb delete mode 100644 lib/puppet/provider/mysql_database/mysql.rb delete mode 100644 lib/puppet/provider/mysql_grant/mysql.rb delete mode 100644 lib/puppet/provider/mysql_user/mysql.rb delete mode 100644 lib/puppet/type/mysql_database.rb delete mode 100644 lib/puppet/type/mysql_grant.rb delete mode 100644 lib/puppet/type/mysql_user.rb diff --git a/lib/puppet/parser/functions/mysql_password.rb b/lib/puppet/parser/functions/mysql_password.rb deleted file mode 100644 index 6443d95..0000000 --- a/lib/puppet/parser/functions/mysql_password.rb +++ /dev/null @@ -1,9 +0,0 @@ -# hash a string as mysql's "PASSWORD()" function would do it -require 'digest/sha1' - -module Puppet::Parser::Functions - newfunction(:mysql_password, :type => :rvalue) do |args| - '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(args[0])).upcase - end -end - diff --git a/lib/puppet/provider/mysql_database/mysql.rb b/lib/puppet/provider/mysql_database/mysql.rb deleted file mode 100644 index d733565..0000000 --- a/lib/puppet/provider/mysql_database/mysql.rb +++ /dev/null @@ -1,49 +0,0 @@ -Puppet::Type.type(:mysql_database).provide(:mysql) do - - desc "Use mysql as database." - - defaultfor :kernel => 'Linux' - - optional_commands :mysqladmin => 'mysqladmin' - optional_commands :mysql => 'mysql' - - def mysql_args(*args) - if @resource[:mgmt_cnf].is_a?(String) - args.insert(0, "--defaults-file=#{@resource[:mgmt_cnf]}") - elsif File.file?("#{Facter.value(:root_home)}/.my.cnf") - args.insert(0, "--defaults-file=#{Facter.value(:root_home)}/.my.cnf") - end - args - end - - def self.instances - mysql(mysql_args('-NBe', "show databases")).split("\n").collect do |name| - new(:name => name) - end - end - - def create - mysql(mysql_args('-NBe', "create database `#{@resource[:name]}` character set #{resource[:charset]}")) - end - - def destroy - mysqladmin(mysql_args('-f', 'drop', @resource[:name])) - end - - def charset - mysql(mysql_args('-NBe', "show create database `#{resource[:name]}`")).match(/.*?(\S+)\s\*\//)[1] - end - - def charset=(value) - mysql(mysql_args('-NBe', "alter database `#{resource[:name]}` CHARACTER SET #{value}")) - end - - def exists? - begin - mysql(mysql_args('-NBe', "show databases")).match(/^#{@resource[:name]}$/) - rescue => e - debug(e.message) - return nil - end - end -end diff --git a/lib/puppet/provider/mysql_grant/mysql.rb b/lib/puppet/provider/mysql_grant/mysql.rb deleted file mode 100644 index 53dcc71..0000000 --- a/lib/puppet/provider/mysql_grant/mysql.rb +++ /dev/null @@ -1,197 +0,0 @@ -Puppet::Type.type(:mysql_grant).provide(:mysql) do - - desc "Uses mysql as database." - - defaultfor :kernel => 'Linux' - - optional_commands :mysql => 'mysql' - optional_commands :mysqladmin => 'mysqladmin' - - def mysql_args(*args) - if @resource and @resource[:mgmt_cnf].is_a?(String) - args.insert(0, "--defaults-file=#{@resource[:mgmt_cnf]}") - elsif File.file?("#{Facter.value(:root_home)}/.my.cnf") - args.insert(0, "--defaults-file=#{Facter.value(:root_home)}/.my.cnf") - end - args - end - - def user_privs - unless defined? @@user_privs - @@user_privs = query_user_privs - end - @@user_privs - end - - def db_privs - unless defined? @@db_privs - @@db_privs = query_db_privs - end - @@db_privs - end - - def query_user_privs - results = mysql(mysql_args("mysql", "-Be", "describe user")) - column_names = results.split(/\n/).map { |l| l.chomp.split(/\t/)[0] } - @user_privs = column_names.delete_if { |e| !( e =~/_priv$/) } - @user_privs - end - - def query_db_privs - results = mysql(mysql_args("mysql", "-Be", "describe db")) - column_names = results.split(/\n/).map { |l| l.chomp.split(/\t/)[0] } - @db_privs = column_names.delete_if { |e| !(e =~/_priv$/) } - @db_privs - end - - def mysql_flush - mysqladmin(mysql_args("flush-privileges")) - 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 - mysql(mysql_args("mysql", "-e", "INSERT INTO user (host, user) VALUES ('%s', '%s')" % [ - name[:host], name[:user], - ])) - when :db - mysql(mysql_args("mysql", "-e", "INSERT INTO db (host, user, db) VALUES ('%s', '%s', '%s')" % [ - name[:host], name[:user], name[:db], - ])) - end - mysql_flush - end - end - - def destroy - mysql(mysql_args("mysql", "-e", "REVOKE ALL ON '%s'.* FROM '%s@%s'" % [ - @resource[:privileges], @resource[:database], @resource[:name], @resource[:host] - ])) - end - - def row_exists? - name = split_name(@resource[:name]) - fields = [:user, :host] - if name[:type] == :db - fields << :db - end - not mysql(mysql_args("mysql", "-NBe", "SELECT '1' FROM %s WHERE %s" % [ name[:type], fields.map do |f| "%s=\"%s\"" % [f, name[f]] end.join(' AND ')])).empty? - end - - def all_privs_set? - all_privs = case split_name(@resource[:name])[:type] - when :user - user_privs - when :db - db_privs - end - all_privs = all_privs.collect do |p| p.downcase end.sort.join("|") - privs = privileges.collect do |p| p.downcase end.sort.join("|") - - all_privs == privs - end - - def privileges - name = split_name(@resource[:name]) - privs = "" - - case name[:type] - when :user - privs = mysql(mysql_args("mysql", "-Be", "select * from user where user='%s' and host='%s'" % [ name[:user], name[:host] ])) - when :db - privs = mysql(mysql_args("mysql", "-Be", "select * from db where user='%s' and host='%s' and db='%s'" % [ name[:user], name[:host], name[:db] ])) - 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 - - privs.collect do |p| p[0] end - 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 ' - where = " where user='%s' and host='%s'" % [ name[:user], name[:host] ] - all_privs = user_privs - when :db - stmt = 'update db set ' - where = " where user='%s' and host='%s'" % [ name[:user], name[:host] ] - all_privs = db_privs - end - - if privs[0].downcase == 'all' - privs = all_privs - end - - # 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 } - - set = all_privs.collect do |p| "%s = '%s'" % [p, privs.include?(p.downcase) ? 'Y' : 'N'] end.join(', ') - stmt = stmt << set << where - - validate_privs privs, all_privs - mysql(mysql_args("mysql", "-Be", stmt)) - mysql_flush - end - - 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 - -end diff --git a/lib/puppet/provider/mysql_user/mysql.rb b/lib/puppet/provider/mysql_user/mysql.rb deleted file mode 100644 index 17f64dc..0000000 --- a/lib/puppet/provider/mysql_user/mysql.rb +++ /dev/null @@ -1,54 +0,0 @@ -Puppet::Type.type(:mysql_user).provide(:mysql) do - - desc "Use mysql as database." - - defaultfor :kernel => 'Linux' - - optional_commands :mysql => 'mysql' - optional_commands :mysqladmin => 'mysqladmin' - - def mysql_args(*args) - if @resource[:mgmt_cnf].is_a?(String) - args.insert(0, "--defaults-file=#{@resource[:mgmt_cnf]}") - elsif File.file?("#{Facter.value(:root_home)}/.my.cnf") - args.insert(0, "--defaults-file=#{Facter.value(:root_home)}/.my.cnf") - end - args - end - - # retrieve the current set of mysql users - def self.instances - users = mysql(mysql_args("mysql", '-BNe' "select concat(User, '@',Host) as User from mysql.user")).split("\n") - users.select{ |user| user =~ /.+@/ }.collect do |name| - new(:name => name) - end - end - - def mysql_flush - mysqladmin(mysql_args("flush-privileges")) - end - - def create - mysql(mysql_args("mysql", "-e", "create user '%s' identified by PASSWORD '%s'" % [ @resource[:name].sub("@", "'@'"), @resource.should(:password_hash) ])) - mysql_flush - end - - def destroy - mysql(mysql_args("mysql", "-e", "drop user '%s'" % @resource[:name].sub("@", "'@'"))) - mysql_flush - end - - def exists? - not mysql(mysql_args("mysql", "-NBe", "select '1' from user where CONCAT(user, '@', host) = '%s'" % @resource[:name])).empty? - end - - def password_hash - mysql(mysql_args("mysql", "-NBe", "select password from mysql.user where CONCAT( user, '@', host) = '%s'" % @resource.value(:name))).chomp - end - - def password_hash=(string) - mysql(mysql_args("mysql", "-e", "SET PASSWORD FOR '%s' = '%s'" % [ @resource[:name].sub("@", "'@'"), string ])) - mysql_flush - end -end - diff --git a/lib/puppet/type/mysql_database.rb b/lib/puppet/type/mysql_database.rb deleted file mode 100644 index 42ea001..0000000 --- a/lib/puppet/type/mysql_database.rb +++ /dev/null @@ -1,26 +0,0 @@ -# This has to be a separate type to enable collecting -Puppet::Type.newtype(:mysql_database) do - @doc = "Manage a database." - - ensurable - - newparam(:name, :namevar=>true) do - desc "The name of the database." - validate do |value| - unless value =~ /^\w+/ - raise ArgumentError, "%s is not a valid database name" % value - end - end - end - newparam(:mgmt_cnf) do - desc "The my.cnf to use for calls." - end - - newproperty(:charset) do - desc "The characterset to use for a database" - defaultto :utf8 - newvalue(/^\S+$/) - end - -end - diff --git a/lib/puppet/type/mysql_grant.rb b/lib/puppet/type/mysql_grant.rb deleted file mode 100644 index 327a408..0000000 --- a/lib/puppet/type/mysql_grant.rb +++ /dev/null @@ -1,74 +0,0 @@ -Puppet::Type.newtype(:mysql_grant) do - @doc = "Manage a database user's rights." - #ensurable - - autorequire :mysql_database do - reqs = [] - matches = self[:name].match(/^([^@]+)@([^\/]+)\/(.+)$/) - unless matches.nil? - reqs << matches[3] - end - reqs - end - - autorequire :mysql_user do - reqs = [] - matches = self[:name].match(/^([^@]+)@([^\/]+).*$/) - unless matches.nil? - reqs << "%s@%s" % [ matches[1], matches[2] ] - end - reqs - end - - newparam(:name, :namevar=>true) do - desc "The primary key: either user@host for global privilges or user@host/database for database specific privileges" - end - - newparam(:mgmt_cnf) do - desc "The my.cnf to use for calls." - end - - newproperty(:privileges, :array_matching => :all) do - desc "The privileges the user should have. The possible values are implementation dependent." - - def should_to_s(newvalue = @should) - if newvalue - unless newvalue.is_a?(Array) - newvalue = [ newvalue ] - end - newvalue.collect do |v| v.downcase end.sort.join ", " - else - nil - end - end - - def is_to_s(currentvalue = @is) - if currentvalue - unless currentvalue.is_a?(Array) - currentvalue = [ currentvalue ] - end - currentvalue.collect do |v| v.downcase end.sort.join ", " - else - nil - end - end - - # use the sorted outputs for comparison - def insync?(is) - if defined? @should and @should - case self.should_to_s - when "all" - self.provider.all_privs_set? - when self.is_to_s(is) - true - else - false - end - else - true - end - end - - end -end - diff --git a/lib/puppet/type/mysql_user.rb b/lib/puppet/type/mysql_user.rb deleted file mode 100644 index a411cb5..0000000 --- a/lib/puppet/type/mysql_user.rb +++ /dev/null @@ -1,28 +0,0 @@ -Puppet::Type.newtype(:mysql_user) do - @doc = "Manage a database user." - - ensurable - - newparam(:name, :namevar => true) do - desc "The name of the user. This uses the 'username@hostname' form." - - validate do |value| - # https://dev.mysql.com/doc/refman/5.1/en/account-names.html - # Regex should problably be more like this: /^[`'"]?[^`'"]*[`'"]?@[`'"]?[\w%\.]+[`'"]?$/ - raise(ArgumentError, "Invalid database user #{value}") unless value =~ /[\w-]*@[\w%\.:]+/ - username = value.split('@')[0] - if username.size > 16 - raise ArgumentError, "MySQL usernames are limited to a maximum of 16 characters" - end - end - end - - newproperty(:password_hash) do - desc "The password hash of the user. Use mysql_password() for creating such a hash." - newvalue(/\w+/) - end - - newparam(:mgmt_cnf) do - desc "The my.cnf to use for calls." - end -end From b4e5d99bdf6fe3790a67a477d205bba0b53f88d9 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Tue, 10 Mar 2015 16:55:08 +0000 Subject: [PATCH 05/11] rename some more methods --- lib/puppet/type/percona_grant.rb | 4 ++-- manifests/rights.pp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/puppet/type/percona_grant.rb b/lib/puppet/type/percona_grant.rb index 3e8776e..8fd3866 100644 --- a/lib/puppet/type/percona_grant.rb +++ b/lib/puppet/type/percona_grant.rb @@ -2,7 +2,7 @@ @doc = "Manage a database user's rights." #ensurable - autorequire :mysql_database do + autorequire :percona_database do reqs = [] matches = self[:name].match(/^([^@]+)@([^\/]+)\/(.+)$/) unless matches.nil? @@ -11,7 +11,7 @@ reqs end - autorequire :mysql_user do + autorequire :percona_user do reqs = [] matches = self[:name].match(/^([^@]+)@([^\/]+).*$/) unless matches.nil? diff --git a/manifests/rights.pp b/manifests/rights.pp index dabf02a..a311d77 100644 --- a/manifests/rights.pp +++ b/manifests/rights.pp @@ -126,7 +126,7 @@ default => "${_user}@${_host}/${_database}", } - if ! defined(Mysql_user["${_user}@${_host}"]) { + if ! defined(Percona_user["${_user}@${_host}"]) { $pwhash = $hash ? { undef => percona_password($password), default => $hash, From dbb34e030ca0e2af05e0ac5c764e13bed7dac2da Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Wed, 11 Mar 2015 12:41:03 +0000 Subject: [PATCH 06/11] fix shared compat libraries with 5.6 install --- manifests/install.pp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manifests/install.pp b/manifests/install.pp index b74d345..3ddae34 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -47,6 +47,12 @@ default => $::percona::pkg_compat, } } + '5.6': { + $pkg_compat = $::percona::pkg_compat ? { + undef => 'Percona-Server-shared-compat', + default => $::percona::pkg_compat, + } + } default: { $pkg_compat = $::percona::pkg_compat ? { undef => 'Percona-SQL-shared-compat', From be2fa768847a0b86e12f5904e6ab34268b66b42c Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Wed, 11 Mar 2015 12:51:42 +0000 Subject: [PATCH 07/11] there is no compat library for 5.6 --- manifests/install.pp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/manifests/install.pp b/manifests/install.pp index 3ddae34..ca71623 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -48,10 +48,7 @@ } } '5.6': { - $pkg_compat = $::percona::pkg_compat ? { - undef => 'Percona-Server-shared-compat', - default => $::percona::pkg_compat, - } + $pkg_compat = $::percona::pkg_compat } default: { $pkg_compat = $::percona::pkg_compat ? { From ba9858bbf490308ebd1a749616b060687526caee Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Fri, 13 Mar 2015 10:40:46 +0000 Subject: [PATCH 08/11] allow you to set log_bin, relay_log and slow_query_log_file --- manifests/init.pp | 12 ++++++++---- manifests/params.pp | 6 +++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/manifests/init.pp b/manifests/init.pp index 13fd98e..8f64508 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -110,7 +110,11 @@ $template = $percona::params::template, $config_dir = $percona::params::config_dir, $config_file = $percona::params::_config_file, - + + $log_bin = $percona::params::log_bin, + $relay_log = $percona::params::relay_log, + $slow_query_log_file = $percona::params::slow_query_log_file, + ) inherits percona::params { $config_include_dir_default = $::percona::params::config_include_dir_default @@ -131,9 +135,9 @@ 'mysqld/socket' => $::percona::socket, 'mysqld/user' => $::percona::daemon_user, 'mysqld/innodb_log_group_home_dir' => $::percona::datadir, - 'mysqld/log_bin' => "${::percona::datadir}/${sanitized_servername}-bin", - 'mysqld/relay_log' => "${::percona::datadir}/${sanitized_servername}-relay", - 'mysqld/slow_query_log_file' => "${::percona::logdir}/${::percona::servername}-slow.log", + 'mysqld/log_bin' => $log_bin ? { undef => "${::percona::datadir}/${sanitized_servername}-bin", default => $log_bin }, + 'mysqld/relay_log' => $relay_log ? { undef => "${::percona::datadir}/${sanitized_servername}-relay", default => $relay_log }, + 'mysqld/slow_query_log_file' => $slow_query_log_file ? { undef => "${::percona::logdir}/${::percona::servername}-slow.log", default => $slow_query_log_file }, 'mysqld/symbolic-links' => '0', 'mysqld_safe/log-error' => $::percona::errorlog, diff --git a/manifests/params.pp b/manifests/params.pp index 0d46a60..7733793 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -88,7 +88,11 @@ '5.5' => {}, '5.1' => {}, 'global' => {}, - } + }, + + $log_bin = undef, + $relay_log = undef, + $slow_query_log_file = undef, ) { case $::operatingsystem { From d0efdd141ae5dddba35a636edc2fdc9c9233e4a9 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Fri, 13 Mar 2015 10:48:31 +0000 Subject: [PATCH 09/11] also check is logdir already defined, compatibility issue my puppetlabs-mysql --- manifests/config/server.pp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/manifests/config/server.pp b/manifests/config/server.pp index b76cdff..388d454 100644 --- a/manifests/config/server.pp +++ b/manifests/config/server.pp @@ -133,11 +133,13 @@ } } - file { $logdir : - ensure => 'directory', - mode => $config_dir_mode, - owner => $daemon_user, - group => $logdir_group, + unless File["${logdir}"] { + file { $logdir : + ensure => 'directory', + mode => $config_dir_mode, + owner => $daemon_user, + group => $logdir_group, + } } if $config_skip != true { From 7a76dd6d5042a768886833820ea9792f36229a21 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Tue, 13 Sep 2016 15:43:41 +0100 Subject: [PATCH 10/11] Add case for 5.7 for shared compats --- manifests/install.pp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manifests/install.pp b/manifests/install.pp index b74d345..71c95e5 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -47,6 +47,12 @@ default => $::percona::pkg_compat, } } + '5.7': { + $pkg_compat = $::percona::pkg_compat ? { + undef => 'Percona-Server-shared-compat-57', + default => $::percona::pkg_compat, + } + } default: { $pkg_compat = $::percona::pkg_compat ? { undef => 'Percona-SQL-shared-compat', From 4cbfdc0cee3cbac0e7093e1aba2cb57d4552e267 Mon Sep 17 00:00:00 2001 From: Craig Carnell Date: Mon, 9 Jan 2017 12:07:53 +0000 Subject: [PATCH 11/11] change to authentication_string --- lib/puppet/provider/percona_user/mysql.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/provider/percona_user/mysql.rb b/lib/puppet/provider/percona_user/mysql.rb index 4c48423..f373da2 100644 --- a/lib/puppet/provider/percona_user/mysql.rb +++ b/lib/puppet/provider/percona_user/mysql.rb @@ -43,7 +43,7 @@ def exists? end def password_hash - mysql(mysql_args("mysql", "-NBe", "select password from mysql.user where CONCAT( user, '@', host) = '%s'" % @resource.value(:name))).chomp + mysql(mysql_args("mysql", "-NBe", "select authentication_string from mysql.user where CONCAT( user, '@', host) = '%s'" % @resource.value(:name))).chomp end def password_hash=(string)