posgra 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|