Skip to content

Commit 37941dc

Browse files
committed
Added role and grant compare
Former-commit-id: 5bc5fdc
1 parent 8c20cc1 commit 37941dc

8 files changed

Lines changed: 691 additions & 54 deletions

File tree

foreignkey.go

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ import "github.com/joncrlsn/pgutil"
1111
type ForeignKeyRows []map[string]string
1212

1313
func (slice ForeignKeyRows) Len() int {
14-
return len(slice)
14+
return len(slice)
1515
}
1616

1717
func (slice ForeignKeyRows) Less(i, j int) bool {
18-
//fmt.Printf("--Less %s:%s with %s:%s", slice[i]["table_name"], slice[i]["column_name"], slice[j]["table_name"], slice[j]["column_name"])
19-
if slice[i]["table_name"] == slice[j]["table_name"] {
20-
return slice[i]["constraint_def"] < slice[j]["constraint_def"]
21-
}
22-
return slice[i]["table_name"] < slice[j]["table_name"]
18+
//fmt.Printf("--Less %s:%s with %s:%s", slice[i]["table_name"], slice[i]["column_name"], slice[j]["table_name"], slice[j]["column_name"])
19+
if slice[i]["table_name"] == slice[j]["table_name"] {
20+
return slice[i]["constraint_def"] < slice[j]["constraint_def"]
21+
}
22+
return slice[i]["table_name"] < slice[j]["table_name"]
2323
}
2424

2525
func (slice ForeignKeyRows) Swap(i, j int) {
26-
//fmt.Printf("--Swapping %d/%s:%s with %d/%s:%s \n", i, slice[i]["table_name"], slice[i]["index_name"], j, slice[j]["table_name"], slice[j]["index_name"])
27-
slice[i], slice[j] = slice[j], slice[i]
26+
//fmt.Printf("--Swapping %d/%s:%s with %d/%s:%s \n", i, slice[i]["table_name"], slice[i]["index_name"], j, slice[j]["table_name"], slice[j]["index_name"])
27+
slice[i], slice[j] = slice[j], slice[i]
2828
}
2929

3030
// ==================================
@@ -35,34 +35,34 @@ func (slice ForeignKeyRows) Swap(i, j int) {
3535
// ForeignKeySchema holds a slice of rows from one of the databases as well as
3636
// a reference to the current row of data we're viewing.
3737
type ForeignKeySchema struct {
38-
rows ForeignKeyRows
39-
rowNum int
40-
done bool
38+
rows ForeignKeyRows
39+
rowNum int
40+
done bool
4141
}
4242

4343
// get returns the value from the current row for the given key
4444
func (c *ForeignKeySchema) get(key string) string {
45-
if c.rowNum >= len(c.rows) {
46-
return ""
47-
}
48-
return c.rows[c.rowNum][key]
45+
if c.rowNum >= len(c.rows) {
46+
return ""
47+
}
48+
return c.rows[c.rowNum][key]
4949
}
5050

5151
// get returns the current row for the given key
5252
func (c *ForeignKeySchema) getRow() map[string]string {
53-
if c.rowNum >= len(c.rows) {
54-
return make(map[string]string)
55-
}
56-
return c.rows[c.rowNum]
53+
if c.rowNum >= len(c.rows) {
54+
return make(map[string]string)
55+
}
56+
return c.rows[c.rowNum]
5757
}
5858

5959
// NextRow reads from the channel and tells you if there are (probably) more or not
6060
func (c *ForeignKeySchema) NextRow() bool {
61-
if c.rowNum >= len(c.rows)-1 {
62-
c.done = true
63-
}
64-
c.rowNum = c.rowNum + 1
65-
return !c.done
61+
if c.rowNum >= len(c.rows)-1 {
62+
c.done = true
63+
}
64+
c.rowNum = c.rowNum + 1
65+
return !c.done
6666
}
6767

6868
// Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row
@@ -117,21 +117,21 @@ WHERE c.contype = 'f';
117117
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
118118
rowChan2, _ := pgutil.QueryStrings(conn2, sql)
119119

120-
rows1 := make(ForeignKeyRows, 0)
121-
for row := range rowChan1 {
122-
rows1 = append(rows1, row)
123-
}
124-
sort.Sort(rows1)
125-
126-
rows2 := make(ForeignKeyRows, 0)
127-
for row := range rowChan2 {
128-
rows2 = append(rows2, row)
129-
}
130-
sort.Sort(rows2)
131-
132-
// We have to explicitly type this as Schema here for some unknown reason
133-
var schema1 Schema = &ForeignKeySchema{rows: rows1, rowNum: -1}
134-
var schema2 Schema = &ForeignKeySchema{rows: rows2, rowNum: -1}
120+
rows1 := make(ForeignKeyRows, 0)
121+
for row := range rowChan1 {
122+
rows1 = append(rows1, row)
123+
}
124+
sort.Sort(rows1)
125+
126+
rows2 := make(ForeignKeyRows, 0)
127+
for row := range rowChan2 {
128+
rows2 = append(rows2, row)
129+
}
130+
sort.Sort(rows2)
131+
132+
// We have to explicitly type this as Schema here for some unknown reason
133+
var schema1 Schema = &ForeignKeySchema{rows: rows1, rowNum: -1}
134+
var schema2 Schema = &ForeignKeySchema{rows: rows2, rowNum: -1}
135135

136136
// Compare the columns
137137
doDiff(schema1, schema2)

grant.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package main
2+
3+
import "sort"
4+
import "fmt"
5+
import "strings"
6+
import "database/sql"
7+
import "github.com/joncrlsn/pgutil"
8+
import "regexp"
9+
10+
var aclRegex = regexp.MustCompile(`([a-zA-Z0-9]+)*=([rwadDxtXUCcT]+)/([a-zA-Z0-9]+)$`)
11+
12+
var permMap map[string]string = map[string]string{
13+
"a": "INSERT",
14+
"r": "SELECT",
15+
"w": "UPDATE",
16+
"d": "DELETE",
17+
"D": "TRUNCATE",
18+
"x": "REFERENCES",
19+
"t": "TRIGGER",
20+
"X": "EXECUTE",
21+
"U": "USAGE",
22+
"C": "CREATE",
23+
"c": "CONNECT",
24+
"T": "TEMPORARY",
25+
}
26+
27+
// ==================================
28+
// GrantRows definition (an array of string maps)
29+
// ==================================
30+
type GrantRows []map[string]string
31+
32+
func (slice GrantRows) Len() int {
33+
return len(slice)
34+
}
35+
36+
func (slice GrantRows) Less(i, j int) bool {
37+
if slice[i]["schema"] != slice[j]["schema"] {
38+
return slice[i]["schema"] < slice[j]["schema"]
39+
}
40+
if slice[i]["relationship_name"] != slice[j]["relationship_name"] {
41+
return slice[i]["relationship_name"] < slice[j]["relationship_name"]
42+
}
43+
if slice[i]["column_name"] != slice[j]["column_name"] {
44+
return slice[i]["column_name"] < slice[j]["column_name"]
45+
}
46+
return false
47+
}
48+
49+
func (slice GrantRows) Swap(i, j int) {
50+
slice[i], slice[j] = slice[j], slice[i]
51+
}
52+
53+
// ==================================
54+
// GrantSchema definition
55+
// (implements Schema -- defined in pgdiff.go)
56+
// ==================================
57+
58+
// GrantSchema holds a slice of rows from one of the databases as well as
59+
// a reference to the current row of data we're viewing.
60+
type GrantSchema struct {
61+
rows GrantRows
62+
rowNum int
63+
done bool
64+
}
65+
66+
// get returns the value from the current row for the given key
67+
func (c *GrantSchema) get(key string) string {
68+
if c.rowNum >= len(c.rows) {
69+
return ""
70+
}
71+
return c.rows[c.rowNum][key]
72+
}
73+
74+
// get returns the current row for the given key
75+
func (c *GrantSchema) getRow() map[string]string {
76+
if c.rowNum >= len(c.rows) {
77+
return make(map[string]string)
78+
}
79+
return c.rows[c.rowNum]
80+
}
81+
82+
// NextRow increments the rowNum and tells you whether or not there are more
83+
func (c *GrantSchema) NextRow() bool {
84+
if c.rowNum >= len(c.rows)-1 {
85+
c.done = true
86+
}
87+
c.rowNum = c.rowNum + 1
88+
return !c.done
89+
}
90+
91+
// Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row
92+
func (c *GrantSchema) Compare(obj interface{}) int {
93+
c2, ok := obj.(*GrantSchema)
94+
if !ok {
95+
fmt.Println("Error!!!, change needs a GrantSchema instance", c2)
96+
return +999
97+
}
98+
99+
val := _compareString(c.get("schema"), c2.get("schema"))
100+
if val != 0 {
101+
return val
102+
}
103+
104+
val = _compareString(c.get("relationship_name"), c2.get("relationship_name"))
105+
if val != 0 {
106+
return val
107+
}
108+
109+
val = _compareString(c.get("column_name"), c2.get("column_name"))
110+
return val
111+
}
112+
113+
// Add prints SQL to add the column
114+
func (c *GrantSchema) Add() {
115+
fmt.Println("--Add")
116+
117+
if c.get("column_acl") != "null" && len(c.get("column_acl")) > 0 {
118+
lines := strings.Split(c.get("column_acl"), "\n")
119+
for _, line := range lines {
120+
roleName, perms := parseGrants(line)
121+
fmt.Printf("GRANT %s (%s) ON %s TO %s; \n", strings.Join(perms, ", "), c.get("column_name"), c.get("relationship_name"), roleName)
122+
}
123+
} else if c.get("relationship_acl") != "null" && len(c.get("relationship_acl")) > 0 {
124+
lines := strings.Split(c.get("relationship_acl"), "\n")
125+
for _, line := range lines {
126+
roleName, perms := parseGrants(line)
127+
fmt.Printf("GRANT %s ON %s TO %s; \n", strings.Join(perms, ", "), c.get("relationship_name"), roleName)
128+
}
129+
}
130+
}
131+
132+
// Drop prints SQL to drop the column
133+
func (c *GrantSchema) Drop() {
134+
fmt.Println("--Drop")
135+
136+
if c.get("column_acl") != "null" && len(c.get("column_acl")) > 0 {
137+
lines := strings.Split(c.get("column_acl"), "\n")
138+
for _, line := range lines {
139+
roleName, perms := parseGrants(line)
140+
fmt.Printf("REVOKE %s (%s) ON %s TO %s; \n", strings.Join(perms, ", "), c.get("column_name"), c.get("relationship_name"), roleName)
141+
}
142+
} else if c.get("relationship_acl") != "null" && len(c.get("relationship_acl")) > 0 {
143+
lines := strings.Split(c.get("relationship_acl"), "\n")
144+
for _, line := range lines {
145+
roleName, perms := parseGrants(line)
146+
fmt.Printf("REVOKE %s ON %s TO %s; \n", strings.Join(perms, ", "), c.get("relationship_name"), roleName)
147+
}
148+
}
149+
}
150+
151+
// Change handles the case where the table and column match, but the details do not
152+
func (c *GrantSchema) Change(obj interface{}) {
153+
c2, ok := obj.(*GrantSchema)
154+
if !ok {
155+
fmt.Println("-- Error!!!, change needs a GrantSchema instance", c2)
156+
}
157+
fmt.Println("--Change")
158+
fmt.Printf("rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c.get("relationship_name"), c.get("relationship_acl"), c.get("column_name"), c.get("column_acl"))
159+
fmt.Printf("rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c2.get("relationship_name"), c2.get("relationship_acl"), c2.get("column_name"), c2.get("column_acl"))
160+
}
161+
162+
// ==================================
163+
// Functions
164+
// ==================================
165+
166+
/*
167+
* Compare the columns in the two databases
168+
*/
169+
func compareGrants(conn1 *sql.DB, conn2 *sql.DB) {
170+
sql := `
171+
SELECT
172+
n.nspname AS schema
173+
, CASE c.relkind
174+
WHEN 'r' THEN 'TABLE'
175+
WHEN 'v' THEN 'VIEW'
176+
WHEN 'S' THEN 'SEQUENCE'
177+
WHEN 'f' THEN 'FOREIGN TABLE'
178+
END as type
179+
, c.relname AS relationship_name
180+
, pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
181+
, a.attname AS column_name
182+
, pg_catalog.array_to_string(a.attacl, E'\n') AS column_acl
183+
FROM pg_catalog.pg_class c
184+
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
185+
LEFT JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND NOT a.attisdropped AND a.attacl IS NOT NULL)
186+
WHERE c.relkind IN ('r', 'v', 'S', 'f')
187+
AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid);
188+
`
189+
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
190+
rowChan2, _ := pgutil.QueryStrings(conn2, sql)
191+
192+
rows1 := make(GrantRows, 0)
193+
for row := range rowChan1 {
194+
rows1 = append(rows1, row)
195+
}
196+
sort.Sort(rows1)
197+
198+
rows2 := make(GrantRows, 0)
199+
for row := range rowChan2 {
200+
rows2 = append(rows2, row)
201+
}
202+
sort.Sort(rows2)
203+
204+
// We have to explicitly type this as Schema here for some unknown reason
205+
var schema1 Schema = &GrantSchema{rows: rows1, rowNum: -1}
206+
var schema2 Schema = &GrantSchema{rows: rows2, rowNum: -1}
207+
208+
doDiff(schema1, schema2)
209+
}
210+
211+
/*
212+
parseGrants converts an ACL string into a slice of permission strings
213+
214+
Example of an ACL: c42ro=rwa/c42 (we want to split out the "rwa" part)
215+
216+
rolename=xxxx -- privileges granted to a role
217+
=xxxx -- privileges granted to PUBLIC
218+
r -- SELECT ("read")
219+
w -- UPDATE ("write")
220+
a -- INSERT ("append")
221+
d -- DELETE
222+
D -- TRUNCATE
223+
x -- REFERENCES
224+
t -- TRIGGER
225+
X -- EXECUTE
226+
U -- USAGE
227+
C -- CREATE
228+
c -- CONNECT
229+
T -- TEMPORARY
230+
arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
231+
* -- grant option for preceding privilege
232+
/yyyy -- role that granted this privilege
233+
*/
234+
func parseGrants(acl string) (string, []string) {
235+
236+
matches := aclRegex.FindStringSubmatch(acl)
237+
role := matches[1]
238+
perms := matches[2]
239+
var permWords []string
240+
for _, c := range strings.Split(perms, "") {
241+
permWord := permMap[c]
242+
if len(permWord) > 0 {
243+
permWords = append(permWords, permWord)
244+
}
245+
}
246+
return role, permWords
247+
}

0 commit comments

Comments
 (0)