posgra 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +15 -10
- data/README.md +47 -3
- data/docker-compose.yml +6 -0
- data/lib/posgra.rb +4 -0
- data/lib/posgra/cli/app.rb +3 -0
- data/lib/posgra/cli/database.rb +70 -0
- data/lib/posgra/client.rb +93 -1
- data/lib/posgra/driver.rb +130 -0
- data/lib/posgra/dsl.rb +8 -0
- data/lib/posgra/dsl/converter.rb +52 -2
- data/lib/posgra/dsl/database.rb +53 -0
- data/lib/posgra/dsl/database/role.rb +23 -0
- data/lib/posgra/dsl/database/role/database.rb +22 -0
- data/lib/posgra/exporter.rb +12 -4
- data/lib/posgra/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8af082dbe487f54a35c23d4c39947c58b12f403d
|
4
|
+
data.tar.gz: 9cb68ff5f9e321277c31246d6b2631bbfb0d67ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e48736a442d4592ea2310dae9884ab1dac1d1de688596517f08504dae133e0da0c98aa4552f22287518a7fc35894581bb654259a90011fb304bc48d1f117415d
|
7
|
+
data.tar.gz: b811a332a01d8960f9c672b2afcb8a89aeaf70589d25616c162cdddeb1ff118e14f0f343de293c026ab84f5ca3239dcfd9110e9eddd6d8bb736476f01b4a7d42
|
data/.travis.yml
CHANGED
@@ -1,19 +1,24 @@
|
|
1
|
-
|
1
|
+
dist: trusty
|
2
|
+
sudo: required
|
2
3
|
language: ruby
|
3
4
|
rvm:
|
4
5
|
- 2.0.0
|
5
6
|
- 2.1.8
|
6
7
|
- 2.2.4
|
7
8
|
- 2.3.0
|
8
|
-
before_install:
|
9
|
-
|
10
|
-
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
packages:
|
16
|
-
- libgmp-dev
|
9
|
+
before_install:
|
10
|
+
- gem install bundler
|
11
|
+
- sudo service postgresql stop
|
12
|
+
before_script:
|
13
|
+
- docker-compose up -d
|
14
|
+
- function pg_ping { PGPASSWORD=password pg_isready -U postgres -h 127.0.0.1 > /dev/null 2> /dev/null; }
|
15
|
+
- for i in {1..60}; do pg_ping && break; sleep 1; done
|
17
16
|
env:
|
18
17
|
global:
|
19
18
|
- POSGRA_TEST_USER=postgres
|
19
|
+
services:
|
20
|
+
- docker
|
21
|
+
addons:
|
22
|
+
apt:
|
23
|
+
packages:
|
24
|
+
- postgresql-client-9.4
|
data/README.md
CHANGED
@@ -28,9 +28,10 @@ Or install it yourself as:
|
|
28
28
|
```sh
|
29
29
|
$ posgra help
|
30
30
|
Commands:
|
31
|
-
posgra
|
32
|
-
posgra
|
33
|
-
posgra
|
31
|
+
posgra database SUBCOMMAND # Manage database grants
|
32
|
+
posgra grant SUBCOMMAND # Manage grants
|
33
|
+
posgra help [COMMAND] # Describe available commands or one specific command
|
34
|
+
posgra role SUBCOMMAND # Manage roles
|
34
35
|
|
35
36
|
Options:
|
36
37
|
-h, [--host=HOST]
|
@@ -62,6 +63,13 @@ posgra grant apply --dry-run pg_grants.rb
|
|
62
63
|
posgra grant apply pg_grants.rb
|
63
64
|
```
|
64
65
|
|
66
|
+
```sh
|
67
|
+
posgra database export pg_dbgrants.rb
|
68
|
+
vi pg_dbgrants.rb
|
69
|
+
posgra database apply --dry-run pg_dbgrants.rb
|
70
|
+
posgra database apply pg_dbgrants.rb
|
71
|
+
```
|
72
|
+
|
65
73
|
### for Redshift
|
66
74
|
|
67
75
|
```sh
|
@@ -106,6 +114,26 @@ role "bob" do
|
|
106
114
|
end
|
107
115
|
```
|
108
116
|
|
117
|
+
### DB Grant
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
role "alice" do
|
121
|
+
database "my_database" do
|
122
|
+
grant "CONNECT", :grantable => true
|
123
|
+
grant "CREATE"
|
124
|
+
grant "TEMPORARY"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
role "bob" do
|
129
|
+
database "my_database" do
|
130
|
+
grant "CONNECT"
|
131
|
+
grant "CREATE"
|
132
|
+
grant "TEMPORARY"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
109
137
|
### Template
|
110
138
|
|
111
139
|
```ruby
|
@@ -138,3 +166,19 @@ role "bob" do
|
|
138
166
|
end
|
139
167
|
end
|
140
168
|
```
|
169
|
+
|
170
|
+
## Running tests
|
171
|
+
|
172
|
+
```sh
|
173
|
+
docker-compose up -d
|
174
|
+
bundle install
|
175
|
+
bundle exec rake
|
176
|
+
```
|
177
|
+
|
178
|
+
### on OS X (docker-machine & VirtualBox)
|
179
|
+
|
180
|
+
Port forwarding is required.
|
181
|
+
|
182
|
+
```sh
|
183
|
+
VBoxManage controlvm default natpf1 "psql,tcp,127.0.0.1,5432,,5432"
|
184
|
+
```
|
data/docker-compose.yml
ADDED
data/lib/posgra.rb
CHANGED
@@ -15,12 +15,16 @@ require 'posgra/utils'
|
|
15
15
|
require 'posgra/cli'
|
16
16
|
require 'posgra/cli'
|
17
17
|
require 'posgra/cli/helper'
|
18
|
+
require 'posgra/cli/database'
|
18
19
|
require 'posgra/cli/grant'
|
19
20
|
require 'posgra/cli/role'
|
20
21
|
require 'posgra/cli/app'
|
21
22
|
require 'posgra/client'
|
22
23
|
require 'posgra/driver'
|
23
24
|
require 'posgra/dsl'
|
25
|
+
require 'posgra/dsl/database'
|
26
|
+
require 'posgra/dsl/database/role'
|
27
|
+
require 'posgra/dsl/database/role/database'
|
24
28
|
require 'posgra/dsl/grants'
|
25
29
|
require 'posgra/dsl/grants/role'
|
26
30
|
require 'posgra/dsl/grants/role/schema'
|
data/lib/posgra/cli/app.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
class Posgra::CLI::Database < Thor
|
2
|
+
include Posgra::CLI::Helper
|
3
|
+
include Posgra::Logger::Helper
|
4
|
+
|
5
|
+
DEFAULT_FILENAME = 'pg_dbgrants.rb'
|
6
|
+
|
7
|
+
class_option :'include-role'
|
8
|
+
class_option :'exclude-role'
|
9
|
+
class_option :'include-database'
|
10
|
+
class_option :'exclude-database'
|
11
|
+
|
12
|
+
desc 'apply FILE', 'Apply database grants'
|
13
|
+
option :'dry-run', :type => :boolean, :default => false
|
14
|
+
def apply(file)
|
15
|
+
check_fileanem(file)
|
16
|
+
updated = client.apply_databases(file)
|
17
|
+
|
18
|
+
unless updated
|
19
|
+
Posgra::Logger.instance.info('No change'.intense_blue)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'export [FILE]', 'Export database grants'
|
24
|
+
option :split, :type => :boolean, :default => false
|
25
|
+
def export(file = nil)
|
26
|
+
check_fileanem(file)
|
27
|
+
dsl = client.export_databases
|
28
|
+
|
29
|
+
if options[:split]
|
30
|
+
file = DEFAULT_FILENAME unless file
|
31
|
+
|
32
|
+
log(:info, 'Export Database Grants')
|
33
|
+
requires = []
|
34
|
+
|
35
|
+
dsl.each do |user, content|
|
36
|
+
user = user.gsub(/\s+/, '_')
|
37
|
+
user = '_' if user.empty?
|
38
|
+
grant_file = "#{user}.rb"
|
39
|
+
requires << grant_file
|
40
|
+
log(:info, " write `#{grant_file}`")
|
41
|
+
|
42
|
+
open(grant_file, 'wb') do |f|
|
43
|
+
f.puts Posgra::CLI::MAGIC_COMMENT
|
44
|
+
f.puts content
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
log(:info, " write `#{file}`")
|
49
|
+
|
50
|
+
open(file, 'wb') do |f|
|
51
|
+
f.puts Posgra::CLI::MAGIC_COMMENT
|
52
|
+
|
53
|
+
requires.each do |grant_file|
|
54
|
+
f.puts "require '#{File.basename grant_file}'"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
if file.nil? or file == '-'
|
59
|
+
puts dsl
|
60
|
+
else
|
61
|
+
log(:info, "Export Database Grants to `#{file}`")
|
62
|
+
|
63
|
+
open(file, 'wb') do |f|
|
64
|
+
f.puts Posgra::CLI::MAGIC_COMMENT
|
65
|
+
f.puts dsl
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/posgra/client.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
class Posgra::Client
|
2
2
|
DEFAULT_EXCLUDE_SCHEMA = /\A(?:pg_.*|information_schema)\z/
|
3
3
|
DEFAULT_EXCLUDE_ROLE = /\A\z/
|
4
|
+
DEFAULT_EXCLUDE_DATABASE = /\A(?:template\d+|postgres)\z/
|
4
5
|
|
5
6
|
def initialize(options = {})
|
6
7
|
if options[:exclude_schema]
|
@@ -21,6 +22,15 @@ class Posgra::Client
|
|
21
22
|
options[:exclude_role] = DEFAULT_EXCLUDE_ROLE
|
22
23
|
end
|
23
24
|
|
25
|
+
if options[:exclude_database]
|
26
|
+
options[:exclude_database] = Regexp.union(
|
27
|
+
options[:exclude_database],
|
28
|
+
DEFAULT_EXCLUDE_DATABASE
|
29
|
+
)
|
30
|
+
else
|
31
|
+
options[:exclude_database] = DEFAULT_EXCLUDE_DATABASE
|
32
|
+
end
|
33
|
+
|
24
34
|
@options = options
|
25
35
|
@client = connect(options)
|
26
36
|
@driver = Posgra::Driver.new(@client, options)
|
@@ -39,7 +49,6 @@ class Posgra::Client
|
|
39
49
|
if options[:split]
|
40
50
|
dsl_h = Hash.new {|hash, key| hash[key] = {} }
|
41
51
|
|
42
|
-
|
43
52
|
exported.each do |role, schemas|
|
44
53
|
dsl = Posgra::DSL.convert_grants({role => schemas}, options)
|
45
54
|
dsl_h[role] = dsl
|
@@ -51,6 +60,24 @@ class Posgra::Client
|
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
63
|
+
def export_databases(options = {})
|
64
|
+
options = @options.merge(options)
|
65
|
+
exported = Posgra::Exporter.export_databases(@driver, options)
|
66
|
+
|
67
|
+
if options[:split]
|
68
|
+
dsl_h = Hash.new {|hash, key| hash[key] = {} }
|
69
|
+
|
70
|
+
exported.each do |role, databases|
|
71
|
+
dsl = Posgra::DSL.convert_databases({role => databases}, options)
|
72
|
+
dsl_h[role] = dsl
|
73
|
+
end
|
74
|
+
|
75
|
+
dsl_h
|
76
|
+
else
|
77
|
+
Posgra::DSL.convert_databases(exported, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
54
81
|
def apply_roles(file, options = {})
|
55
82
|
options = @options.merge(options)
|
56
83
|
walk_for_roles(file, options)
|
@@ -61,6 +88,11 @@ class Posgra::Client
|
|
61
88
|
walk_for_grants(file, options)
|
62
89
|
end
|
63
90
|
|
91
|
+
def apply_databases(file, options = {})
|
92
|
+
options = @options.merge(options)
|
93
|
+
walk_for_database_grants(file, options)
|
94
|
+
end
|
95
|
+
|
64
96
|
def close
|
65
97
|
@client.close
|
66
98
|
end
|
@@ -87,6 +119,12 @@ class Posgra::Client
|
|
87
119
|
walk_roles(expected, actual)
|
88
120
|
end
|
89
121
|
|
122
|
+
def walk_for_database_grants(file, options)
|
123
|
+
expected = load_file(file, :parse_databases, options)
|
124
|
+
actual = Posgra::Exporter.export_databases(@driver, options)
|
125
|
+
walk_database_roles(expected, actual)
|
126
|
+
end
|
127
|
+
|
90
128
|
def walk_users(expected, actual)
|
91
129
|
updated = false
|
92
130
|
|
@@ -219,6 +257,60 @@ class Posgra::Client
|
|
219
257
|
updated
|
220
258
|
end
|
221
259
|
|
260
|
+
def walk_database_roles(expected, actual)
|
261
|
+
updated = false
|
262
|
+
|
263
|
+
expected.each do |expected_role, expected_databases|
|
264
|
+
actual_databases = actual.delete(expected_role) || {}
|
265
|
+
updated = walk_databases(expected_databases, actual_databases, expected_role) || updated
|
266
|
+
end
|
267
|
+
|
268
|
+
actual.each do |actual_role, actual_databases|
|
269
|
+
actual_databases.each do |database, _|
|
270
|
+
updated = @driver.revoke_all_on_database(actual_role, database) || updated
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
updated
|
275
|
+
end
|
276
|
+
|
277
|
+
def walk_databases(expected, actual, role)
|
278
|
+
updated = false
|
279
|
+
|
280
|
+
expected.each do |expected_database, expected_grants|
|
281
|
+
actual_grants = actual.delete(expected_database) || {}
|
282
|
+
updated = walk_database_grants(expected_grants, actual_grants, role, expected_database) || updated
|
283
|
+
end
|
284
|
+
|
285
|
+
actual.each do |actual_database, _|
|
286
|
+
updated = @driver.revoke_all_on_database(role, actual_database) || updated
|
287
|
+
end
|
288
|
+
|
289
|
+
updated
|
290
|
+
end
|
291
|
+
|
292
|
+
def walk_database_grants(expected, actual, role, database)
|
293
|
+
updated = false
|
294
|
+
|
295
|
+
expected.each do |expected_priv, expected_options|
|
296
|
+
actual_options = actual.delete(expected_priv)
|
297
|
+
|
298
|
+
if actual_options
|
299
|
+
if expected_options != actual_options
|
300
|
+
updated = @driver.update_database_grant_options(role, expected_priv, expected_options, database) || updated
|
301
|
+
end
|
302
|
+
else
|
303
|
+
updated = @driver.database_grant(role, expected_priv, expected_options, database) || updated
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
actual.each do |actual_priv, _|
|
308
|
+
updated = @driver.database_revoke(role, actual_priv, database) || updated
|
309
|
+
end
|
310
|
+
|
311
|
+
updated
|
312
|
+
end
|
313
|
+
|
222
314
|
def load_file(file, method, options)
|
223
315
|
if file.kind_of?(String)
|
224
316
|
open(file) do |f|
|
data/lib/posgra/driver.rb
CHANGED
@@ -5,6 +5,8 @@ class Posgra::Driver
|
|
5
5
|
DEFAULT_ACL_PRIVS = ENV['POSGRA_DEFAULT_ACL_PRIVS'] || 'arwdDxt'
|
6
6
|
DEFAULT_ACL = "{%s=#{DEFAULT_ACL_PRIVS}/%s}"
|
7
7
|
|
8
|
+
DEFAULT_DATABASE_ACL = "{%s=CTc/%s}"
|
9
|
+
|
8
10
|
DEFAULT_ACL_BY_KIND = {
|
9
11
|
'S' => '{%s=rwU/%s}'
|
10
12
|
}
|
@@ -21,6 +23,7 @@ class Posgra::Driver
|
|
21
23
|
'R' => 'RULE',
|
22
24
|
'X' => 'EXECUTE',
|
23
25
|
'C' => 'CREATE',
|
26
|
+
'c' => 'CONNECT',
|
24
27
|
'T' => 'TEMPORARY',
|
25
28
|
}
|
26
29
|
|
@@ -143,6 +146,18 @@ class Posgra::Driver
|
|
143
146
|
updated
|
144
147
|
end
|
145
148
|
|
149
|
+
def revoke_all_on_database(role, database)
|
150
|
+
sql = "REVOKE ALL ON DATABASE #{@client.escape_identifier(database)} FROM #{@client.escape_identifier(role)}"
|
151
|
+
log(:info, sql, :color => :green)
|
152
|
+
|
153
|
+
unless @options[:dry_run]
|
154
|
+
exec(sql)
|
155
|
+
updated = true
|
156
|
+
end
|
157
|
+
|
158
|
+
updated
|
159
|
+
end
|
160
|
+
|
146
161
|
def grant(role, priv, options, schema, object)
|
147
162
|
updated = false
|
148
163
|
|
@@ -216,6 +231,79 @@ class Posgra::Driver
|
|
216
231
|
updated
|
217
232
|
end
|
218
233
|
|
234
|
+
def database_grant(role, priv, options, database)
|
235
|
+
updated = false
|
236
|
+
|
237
|
+
sql = "GRANT #{priv} ON DATABASE #{@client.escape_identifier(database)} TO #{@client.escape_identifier(role)}"
|
238
|
+
|
239
|
+
if options['is_grantable']
|
240
|
+
sql << ' WITH GRANT OPTION'
|
241
|
+
end
|
242
|
+
|
243
|
+
log(:info, sql, :color => :green)
|
244
|
+
|
245
|
+
unless @options[:dry_run]
|
246
|
+
exec(sql)
|
247
|
+
updated = true
|
248
|
+
end
|
249
|
+
|
250
|
+
updated
|
251
|
+
end
|
252
|
+
|
253
|
+
def update_database_grant_options(role, priv, options, database)
|
254
|
+
updated = false
|
255
|
+
|
256
|
+
if options.fetch('is_grantable')
|
257
|
+
updated = grant_database_grant_option(role, priv, database)
|
258
|
+
else
|
259
|
+
updated = roveke_database_grant_option(role, priv, database)
|
260
|
+
end
|
261
|
+
|
262
|
+
updated
|
263
|
+
end
|
264
|
+
|
265
|
+
def grant_database_grant_option(role, priv, database)
|
266
|
+
updated = false
|
267
|
+
|
268
|
+
sql = "GRANT #{priv} ON DATABASE #{@client.escape_identifier(database)} TO #{@client.escape_identifier(role)} WITH GRANT OPTION"
|
269
|
+
log(:info, sql, :color => :green)
|
270
|
+
|
271
|
+
unless @options[:dry_run]
|
272
|
+
exec(sql)
|
273
|
+
updated = true
|
274
|
+
end
|
275
|
+
|
276
|
+
updated
|
277
|
+
end
|
278
|
+
|
279
|
+
def roveke_database_grant_option(role, priv, database)
|
280
|
+
updated = false
|
281
|
+
|
282
|
+
sql = "REVOKE GRANT OPTION FOR #{priv} ON DATABASE #{@client.escape_identifier(database)} FROM #{@client.escape_identifier(role)}"
|
283
|
+
log(:info, sql, :color => :green)
|
284
|
+
|
285
|
+
unless @options[:dry_run]
|
286
|
+
exec(sql)
|
287
|
+
updated = true
|
288
|
+
end
|
289
|
+
|
290
|
+
updated
|
291
|
+
end
|
292
|
+
|
293
|
+
def database_revoke(role, priv, database)
|
294
|
+
updated = false
|
295
|
+
|
296
|
+
sql = "REVOKE #{priv} ON DATABASE #{@client.escape_identifier(database)} FROM #{@client.escape_identifier(role)}"
|
297
|
+
log(:info, sql, :color => :green)
|
298
|
+
|
299
|
+
unless @options[:dry_run]
|
300
|
+
exec(sql)
|
301
|
+
updated = true
|
302
|
+
end
|
303
|
+
|
304
|
+
updated
|
305
|
+
end
|
306
|
+
|
219
307
|
def describe_objects(schema)
|
220
308
|
rs = exec <<-SQL
|
221
309
|
SELECT
|
@@ -294,6 +382,7 @@ class Posgra::Driver
|
|
294
382
|
SQL
|
295
383
|
|
296
384
|
grants_by_role = {}
|
385
|
+
|
297
386
|
rs.each do |row|
|
298
387
|
relname = row.fetch('relname')
|
299
388
|
nspname = row.fetch('nspname')
|
@@ -317,11 +406,52 @@ class Posgra::Driver
|
|
317
406
|
grants_by_role
|
318
407
|
end
|
319
408
|
|
409
|
+
def describe_databases
|
410
|
+
rs = exec <<-SQL
|
411
|
+
SELECT
|
412
|
+
pg_database.datname,
|
413
|
+
pg_database.datacl,
|
414
|
+
pg_user.usename
|
415
|
+
FROM
|
416
|
+
pg_database
|
417
|
+
INNER JOIN pg_user ON pg_database.datdba = pg_user.usesysid
|
418
|
+
SQL
|
419
|
+
|
420
|
+
database_grants_by_role = {}
|
421
|
+
|
422
|
+
rs.each do |row|
|
423
|
+
datname = row.fetch('datname')
|
424
|
+
datacl = row.fetch('datacl')
|
425
|
+
usename = row.fetch('usename')
|
426
|
+
|
427
|
+
next unless matched?(datname, @options[:include_database], @options[:exclude_database])
|
428
|
+
|
429
|
+
parse_database_aclitems(datacl, usename).each do |aclitem|
|
430
|
+
role = aclitem.fetch('grantee')
|
431
|
+
privs = aclitem.fetch('privileges')
|
432
|
+
next unless matched?(role, @options[:include_role], @options[:exclude_role])
|
433
|
+
database_grants_by_role[role] ||= {}
|
434
|
+
database_grants_by_role[role][datname] = privs
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
database_grants_by_role
|
439
|
+
end
|
440
|
+
|
320
441
|
private
|
321
442
|
|
322
443
|
def parse_aclitems(aclitems, owner, relkind)
|
323
444
|
aclitems_fmt = DEFAULT_ACL_BY_KIND.fetch(relkind, DEFAULT_ACL)
|
324
445
|
aclitems ||= aclitems_fmt % [owner, owner]
|
446
|
+
parse_aclitems0(aclitems)
|
447
|
+
end
|
448
|
+
|
449
|
+
def parse_database_aclitems(aclitems, owner)
|
450
|
+
aclitems ||= DEFAULT_DATABASE_ACL % [owner, owner]
|
451
|
+
parse_aclitems0(aclitems)
|
452
|
+
end
|
453
|
+
|
454
|
+
def parse_aclitems0(aclitems)
|
325
455
|
aclitems = aclitems[1..-2].split(',')
|
326
456
|
|
327
457
|
aclitems.map do |aclitem|
|
data/lib/posgra/dsl.rb
CHANGED
@@ -7,6 +7,10 @@ class Posgra::DSL
|
|
7
7
|
Posgra::DSL::Converter.convert_grants(exported, options)
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.convert_databases(exported, options = {})
|
11
|
+
Posgra::DSL::Converter.convert_databases(exported, options)
|
12
|
+
end
|
13
|
+
|
10
14
|
def self.parse_roles(dsl, path, options = {})
|
11
15
|
Posgra::DSL::Roles.eval(dsl, path, options).result
|
12
16
|
end
|
@@ -14,4 +18,8 @@ class Posgra::DSL
|
|
14
18
|
def self.parse_grants(dsl, path, options = {})
|
15
19
|
Posgra::DSL::Grants.eval(dsl, path, options).result
|
16
20
|
end
|
21
|
+
|
22
|
+
def self.parse_databases(dsl, path, options = {})
|
23
|
+
Posgra::DSL::Database.eval(dsl, path, options).result
|
24
|
+
end
|
17
25
|
end
|
data/lib/posgra/dsl/converter.rb
CHANGED
@@ -7,6 +7,10 @@ class Posgra::DSL::Converter
|
|
7
7
|
self.new(exported, options).convert_grants
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.convert_databases(exported, options = {})
|
11
|
+
self.new(exported, options).convert_databases
|
12
|
+
end
|
13
|
+
|
10
14
|
def initialize(exported, options = {})
|
11
15
|
@exported = exported
|
12
16
|
@options = options
|
@@ -27,6 +31,11 @@ class Posgra::DSL::Converter
|
|
27
31
|
output_roles(grants_by_role).strip
|
28
32
|
end
|
29
33
|
|
34
|
+
def convert_databases
|
35
|
+
database_grants_by_role = @exported || {}
|
36
|
+
output_database_roles(database_grants_by_role).strip
|
37
|
+
end
|
38
|
+
|
30
39
|
private
|
31
40
|
|
32
41
|
def output_users(users)
|
@@ -117,10 +126,10 @@ end
|
|
117
126
|
EOS
|
118
127
|
end
|
119
128
|
|
120
|
-
def output_grants(grants)
|
129
|
+
def output_grants(grants, indent = " ")
|
121
130
|
grants.sort_by {|g| g.to_s }.map {|privilege_type, options|
|
122
131
|
output_grant(privilege_type, options).strip
|
123
|
-
}.join("\n
|
132
|
+
}.join("\n#{indent}")
|
124
133
|
end
|
125
134
|
|
126
135
|
def output_grant(privilege_type, options)
|
@@ -133,4 +142,45 @@ end
|
|
133
142
|
|
134
143
|
out
|
135
144
|
end
|
145
|
+
|
146
|
+
def output_database_roles(database_grants_by_role)
|
147
|
+
database_grants_by_role.sort_by {|r, _| r }.map {|role, grants_by_database|
|
148
|
+
output_database_role(role, grants_by_database)
|
149
|
+
}.join("\n")
|
150
|
+
end
|
151
|
+
|
152
|
+
def output_database_role(role, grants_by_database)
|
153
|
+
if grants_by_database.empty?
|
154
|
+
databases = "# no databases"
|
155
|
+
else
|
156
|
+
databases = output_databases(grants_by_database)
|
157
|
+
end
|
158
|
+
|
159
|
+
<<-EOS
|
160
|
+
role #{role.inspect} do
|
161
|
+
#{databases}
|
162
|
+
end
|
163
|
+
EOS
|
164
|
+
end
|
165
|
+
|
166
|
+
def output_databases(grants_by_database)
|
167
|
+
grants_by_database.sort_by {|s, _| s }.map {|database, grants|
|
168
|
+
output_database(database, grants).strip
|
169
|
+
}.join("\n ")
|
170
|
+
end
|
171
|
+
|
172
|
+
def output_database(database, grants)
|
173
|
+
if grants.empty?
|
174
|
+
grants = "# no grants"
|
175
|
+
else
|
176
|
+
grants = output_grants(grants, ' ')
|
177
|
+
end
|
178
|
+
|
179
|
+
<<-EOS
|
180
|
+
database #{database.inspect} do
|
181
|
+
#{grants}
|
182
|
+
end
|
183
|
+
EOS
|
184
|
+
end
|
185
|
+
|
136
186
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Posgra::DSL::Database
|
2
|
+
include Posgra::Logger::Helper
|
3
|
+
include Posgra::TemplateHelper
|
4
|
+
include Posgra::Utils::Helper
|
5
|
+
|
6
|
+
def self.eval(dsl, path, options = {})
|
7
|
+
self.new(path, options) do
|
8
|
+
eval(dsl, binding, path)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :result
|
13
|
+
|
14
|
+
def initialize(path, options = {}, &block)
|
15
|
+
@path = path
|
16
|
+
@options = options
|
17
|
+
@result = {}
|
18
|
+
|
19
|
+
@context = Hashie::Mash.new(
|
20
|
+
:path => path,
|
21
|
+
:options => options,
|
22
|
+
:templates => {},
|
23
|
+
)
|
24
|
+
|
25
|
+
instance_eval(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def template(name, &block)
|
31
|
+
@context.templates[name.to_s] = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def require(file)
|
35
|
+
pgrantfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file))
|
36
|
+
|
37
|
+
if File.exist?(pgrantfile)
|
38
|
+
instance_eval(File.read(pgrantfile), pgrantfile)
|
39
|
+
elsif File.exist?(pgrantfile + '.rb')
|
40
|
+
instance_eval(File.read(pgrantfile + '.rb'), pgrantfile + '.rb')
|
41
|
+
else
|
42
|
+
Kernel.require(file)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def role(name, &block)
|
47
|
+
name = name.to_s
|
48
|
+
|
49
|
+
if matched?(name, @options[:include_role], @options[:exclude_role])
|
50
|
+
@result[name] = Posgra::DSL::Database::Role.new(@context, name, @options, &block).result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Posgra::DSL::Database::Role
|
2
|
+
include Posgra::Logger::Helper
|
3
|
+
include Posgra::TemplateHelper
|
4
|
+
include Posgra::Utils::Helper
|
5
|
+
|
6
|
+
attr_reader :result
|
7
|
+
|
8
|
+
def initialize(context, role, options, &block)
|
9
|
+
@role = role
|
10
|
+
@options = options
|
11
|
+
@context = context.merge(:role => role)
|
12
|
+
@result = {}
|
13
|
+
instance_eval(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def database(name, &block)
|
17
|
+
name = name.to_s
|
18
|
+
|
19
|
+
if matched?(name, @options[:include_database], @options[:exclude_database])
|
20
|
+
@result[name] = Posgra::DSL::Database::Role::Database.new(@context, name, @options, &block).result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Posgra::DSL::Database::Role::Database
|
2
|
+
include Posgra::Logger::Helper
|
3
|
+
include Posgra::TemplateHelper
|
4
|
+
|
5
|
+
attr_reader :result
|
6
|
+
|
7
|
+
def initialize(context, database, options, &block)
|
8
|
+
@database = database
|
9
|
+
@options = options
|
10
|
+
@context = context.merge(:database => database)
|
11
|
+
@result = {}
|
12
|
+
instance_eval(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def grant(name, options = {})
|
16
|
+
name = name.to_s
|
17
|
+
|
18
|
+
@result[name] = {
|
19
|
+
'is_grantable' => !!options[:grantable]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
data/lib/posgra/exporter.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
class Posgra::Exporter
|
2
|
-
def self.export_roles(driver, options = {}
|
3
|
-
self.new(driver, options).export_roles
|
2
|
+
def self.export_roles(driver, options = {})
|
3
|
+
self.new(driver, options).export_roles
|
4
4
|
end
|
5
5
|
|
6
|
-
def self.export_grants(driver, options = {}
|
7
|
-
self.new(driver, options).export_grants
|
6
|
+
def self.export_grants(driver, options = {})
|
7
|
+
self.new(driver, options).export_grants
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.export_databases(driver, options = {})
|
11
|
+
self.new(driver, options).export_databases
|
8
12
|
end
|
9
13
|
|
10
14
|
def initialize(driver, options = {})
|
@@ -22,4 +26,8 @@ class Posgra::Exporter
|
|
22
26
|
def export_grants
|
23
27
|
@driver.describe_grants
|
24
28
|
end
|
29
|
+
|
30
|
+
def export_databases
|
31
|
+
@driver.describe_databases
|
32
|
+
end
|
25
33
|
end
|
data/lib/posgra/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: posgra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- winebarrel
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -167,10 +167,12 @@ files:
|
|
167
167
|
- Rakefile
|
168
168
|
- bin/console
|
169
169
|
- bin/setup
|
170
|
+
- docker-compose.yml
|
170
171
|
- exe/posgra
|
171
172
|
- lib/posgra.rb
|
172
173
|
- lib/posgra/cli.rb
|
173
174
|
- lib/posgra/cli/app.rb
|
175
|
+
- lib/posgra/cli/database.rb
|
174
176
|
- lib/posgra/cli/grant.rb
|
175
177
|
- lib/posgra/cli/helper.rb
|
176
178
|
- lib/posgra/cli/role.rb
|
@@ -178,6 +180,9 @@ files:
|
|
178
180
|
- lib/posgra/driver.rb
|
179
181
|
- lib/posgra/dsl.rb
|
180
182
|
- lib/posgra/dsl/converter.rb
|
183
|
+
- lib/posgra/dsl/database.rb
|
184
|
+
- lib/posgra/dsl/database/role.rb
|
185
|
+
- lib/posgra/dsl/database/role/database.rb
|
181
186
|
- lib/posgra/dsl/grants.rb
|
182
187
|
- lib/posgra/dsl/grants/role.rb
|
183
188
|
- lib/posgra/dsl/grants/role/schema.rb
|