]> git.openstreetmap.org Git - chef.git/blob - cookbooks/postgresql/libraries/postgresql.rb
Remove old cleanup code
[chef.git] / cookbooks / postgresql / libraries / postgresql.rb
1 require "chef/mixin/shell_out"
2
3 module OpenStreetMap
4   class PostgreSQL
5     include Chef::Mixin::ShellOut
6
7     SCHEMA_PRIVILEGES = [
8       :create, :usage
9     ].freeze
10
11     TABLE_PRIVILEGES = [
12       :select, :insert, :update, :delete, :truncate, :references, :trigger
13     ].freeze
14
15     SEQUENCE_PRIVILEGES = [
16       :usage, :select, :update
17     ].freeze
18
19     def initialize(cluster)
20       @cluster = cluster
21     end
22
23     def version
24       @cluster.split("/").first.to_f
25     end
26
27     def execute(options)
28       # Create argument array
29       args = []
30
31       # Add the cluster
32       args.push("--cluster")
33       args.push(@cluster)
34
35       # Set output format
36       args.push("--no-align") unless options.fetch(:align, true)
37
38       # Run command in a transaction if requested
39       args.push("--single-transaction") if options.fetch(:transaction, false)
40
41       # Add any SQL command to execute
42       if options[:command]
43         args.push("--command")
44         args.push(options[:command])
45       end
46
47       # Add any file to execute SQL commands from
48       if options[:file]
49         args.push("--file")
50         args.push(options[:file])
51       end
52
53       # Add the database name
54       args.push(options[:database] || "template1")
55
56       # Get the user and group to run as
57       user = options[:user] || "postgres"
58       group = options[:group] || "postgres"
59
60       # Run the command
61       shell_out!("/usr/bin/psql", *args, :user => user, :group => group)
62     end
63
64     def query(sql, options = {})
65       # Run the query
66       result = execute(options.merge(:command => sql, :align => false))
67
68       # Split the output into lines
69       lines = result.stdout.split("\n")
70
71       # Remove the "(N rows)" line from the end
72       lines.pop
73
74       # Get the field names
75       fields = lines.shift.split("|")
76
77       # Extract the record data
78       lines.collect do |line|
79         record = {}
80         fields.zip(line.split("|")) { |name, value| record[name.to_sym] = value }
81         record
82       end
83     end
84
85     def users
86       @users ||= query("SELECT *, ARRAY(SELECT groname FROM pg_group WHERE usesysid = ANY(grolist)) AS roles FROM pg_user").each_with_object({}) do |user, users|
87         users[user[:usename]] = {
88           :superuser => user[:usesuper] == "t",
89           :createdb => user[:usercreatedb] == "t",
90           :createrole => user[:usecatupd] == "t",
91           :replication => user[:userepl] == "t",
92           :roles => parse_array(user[:roles] || "{}")
93         }
94       end
95     end
96
97     def databases
98       @databases ||= query("SELECT d.datname, u.usename, d.encoding, d.datcollate, d.datctype FROM pg_database AS d INNER JOIN pg_user AS u ON d.datdba = u.usesysid").each_with_object({}) do |database, databases|
99         databases[database[:datname]] = {
100           :owner => database[:usename],
101           :encoding => database[:encoding],
102           :collate => database[:datcollate],
103           :ctype => database[:datctype]
104         }
105       end
106     end
107
108     def extensions(database)
109       @extensions ||= {}
110       @extensions[database] ||= query("SELECT extname, extversion FROM pg_extension", :database => database).each_with_object({}) do |extension, extensions|
111         extensions[extension[:extname]] = {
112           :version => extension[:extversion]
113         }
114       end
115     end
116
117     def tablespaces
118       @tablespaces ||= query("SELECT spcname, usename FROM pg_tablespace AS t INNER JOIN pg_user AS u ON t.spcowner = u.usesysid").each_with_object({}) do |tablespace, tablespaces|
119         tablespaces[tablespace[:spcname]] = {
120           :owner => tablespace[:usename]
121         }
122       end
123     end
124
125     def schemas(database)
126       @schemas ||= {}
127       @schemas[database] ||= query("SELECT n.nspname, pg_catalog.pg_get_userbyid(n.nspowner) AS usename, n.nspacl FROM pg_namespace AS n WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'", :database => database).each_with_object({}) do |schema, schemas|
128         name = schema[:nspname]
129
130         schemas[name] = {
131           :owner => schema[:usename],
132           :permissions => parse_acl(schema[:nspacl] || "{}")
133         }
134       end
135     end
136
137     def tables(database)
138       @tables ||= {}
139       @tables[database] ||= query("SELECT n.nspname, c.relname, u.usename, c.relacl FROM pg_class AS c INNER JOIN pg_user AS u ON c.relowner = u.usesysid INNER JOIN pg_namespace AS n ON c.relnamespace = n.oid WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND c.relkind IN ('r', 'p')", :database => database).each_with_object({}) do |table, tables|
140         name = "#{table[:nspname]}.#{table[:relname]}"
141
142         tables[name] = {
143           :owner => table[:usename],
144           :permissions => parse_acl(table[:relacl] || "{}")
145         }
146       end
147     end
148
149     def sequences(database)
150       @sequences ||= {}
151       @sequences[database] ||= query("SELECT n.nspname, c.relname, u.usename, c.relacl FROM pg_class AS c INNER JOIN pg_user AS u ON c.relowner = u.usesysid INNER JOIN pg_namespace AS n ON c.relnamespace = n.oid WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND c.relkind = 'S'", :database => database).each_with_object({}) do |sequence, sequences|
152         name = "#{sequence[:nspname]}.#{sequence[:relname]}"
153
154         sequences[name] = {
155           :owner => sequence[:usename],
156           :permissions => parse_acl(sequence[:relacl] || "{}")
157         }
158       end
159     end
160
161     private
162
163     def parse_array(array)
164       array.sub(/^\{(.*)\}$/, "\\1").split(",")
165     end
166
167     def parse_acl(acl)
168       parse_array(acl).each_with_object({}) do |entry, permissions|
169         entry = entry.sub(/^"(.*)"$/) { Regexp.last_match[1].gsub('\"', '"') }.sub(%r{/.*$}, "")
170         user, privileges = entry.split("=")
171
172         user = user.sub(/^"(.*)"$/, "\\1")
173         user = "public" if user == ""
174
175         permissions[user] = {
176           "r" => :select, "a" => :insert, "w" => :update, "d" => :delete,
177           "D" => :truncate, "x" => :references, "t" => :trigger,
178           "C" => :create, "c" => :connect, "T" => :temporary,
179           "X" => :execute, "U" => :usage, "s" => :set, "A" => :alter_system
180         }.values_at(*privileges.chars).compact
181       end
182     end
183   end
184 end