summaryrefslogtreecommitdiff
path: root/lib/puppet/provider/mysql_grant/mysql.rb
blob: 2c44e0bc52dc208f0fd1a248170995bf9975af85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# A grant is either global or per-db. This can be distinguished by the syntax
# of the name:
#   user@host => global
#   user@host/db => per-db

require 'puppet/provider/package'

MYSQL_USER_PRIVS = [ :select_priv, :insert_priv, :update_priv, :delete_priv,
  :create_priv, :drop_priv, :reload_priv, :shutdown_priv, :process_priv,
  :file_priv, :grant_priv, :references_priv, :index_priv, :alter_priv,
  :show_db_priv, :super_priv, :create_tmp_table_priv, :lock_tables_priv,
  :execute_priv, :repl_slave_priv, :repl_client_priv, :create_view_priv,
  :show_view_priv, :create_routine_priv, :alter_routine_priv,
  :create_user_priv
]

MYSQL_DB_PRIVS = [ :select_priv, :insert_priv, :update_priv, :delete_priv,
  :create_priv, :drop_priv, :grant_priv, :references_priv, :index_priv,
  :alter_priv, :create_tmp_table_priv, :lock_tables_priv, :create_view_priv,
  :show_view_priv, :create_routine_priv, :alter_routine_priv, :execute_priv
]

Puppet::Type.type(:mysql_grant).provide(:mysql) do

  desc "Uses mysql as database."

  commands :mysql => '/usr/bin/mysql'
  commands :mysqladmin => '/usr/bin/mysqladmin'

  def mysql_flush 
    mysqladmin "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", "-e", "INSERT INTO user (host, user) VALUES ('%s', '%s')" % [
          name[:host], name[:user],
        ]
      when :db
        mysql "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", "-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", "-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
        MYSQL_USER_PRIVS
      when :db
        MYSQL_DB_PRIVS
    end
    all_privs = all_privs.collect do |p| p.to_s end.sort.join("|")
    privs = privileges.collect do |p| p.to_s end.sort.join("|")

    all_privs == privs
  end

  def privileges 
    name = split_name(@resource[:name])
    privs = ""

    case name[:type]
    when :user
      privs = mysql "mysql", "-Be", 'select * from user where user="%s" and host="%s"' % [ name[:user], name[:host] ]
    when :db
      privs = mysql "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| symbolize(p[0].downcase) 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 = MYSQL_USER_PRIVS
    when :db
      stmt = 'update db set '
      where = ' where user="%s" and host="%s"' % [ name[:user], name[:host] ]
      all_privs = MYSQL_DB_PRIVS
    end

    if privs[0] == :all 
      privs = all_privs
    end
  
    # puts "stmt:", stmt
    set = all_privs.collect do |p| "%s = '%s'" % [p, privs.include?(p) ? 'Y' : 'N'] end.join(', ')
    # puts "set:", set
    stmt = stmt << set << where

    mysql "mysql", "-Be", stmt
    mysql_flush
  end
end