pg_easy_replicate 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Dockerfile +2 -2
- data/Gemfile.lock +1 -3
- data/README.md +38 -1
- data/bin/release.sh +5 -15
- data/docker-compose.yml +14 -12
- data/lib/pg_easy_replicate/cli.rb +12 -2
- data/lib/pg_easy_replicate/helper.rb +4 -0
- data/lib/pg_easy_replicate/orchestrate.rb +19 -6
- data/lib/pg_easy_replicate/stats.rb +0 -1
- data/lib/pg_easy_replicate/version.rb +1 -1
- data/lib/pg_easy_replicate.rb +116 -28
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c253c57988226f7d90116cfc4276a51ac6a2753daea88169ba244fe4ce069e76
|
4
|
+
data.tar.gz: cadca2e9e430f06be91ce4e1623fdf333fdc96edded99e83729f7b56e9356191
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dee2a8f5c96289076276b6d2f92e5e0623ac1c2ed7d885bfa7ab472bbc80fc525665fa72ed54fb772d63e9fd564acecea5c0a3d107dcb5e46370e539c20ab2ac
|
7
|
+
data.tar.gz: b7c9c94cf5c5b9288a81e6d2a144f71cc387096b464f732f38755f293c1d7344120cf4107f1803981524773edd95cabfa34c56b973004dfd0efec69e9f066f8a
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.
|
1
|
+
3.1.4
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pg_easy_replicate (0.1.
|
5
|
-
lockbox (~> 1.2.0)
|
4
|
+
pg_easy_replicate (0.1.4)
|
6
5
|
ougai (~> 2.0.0)
|
7
6
|
pg (~> 1.5.3)
|
8
7
|
sequel (~> 5.69.0)
|
@@ -19,7 +18,6 @@ GEM
|
|
19
18
|
thor
|
20
19
|
tilt
|
21
20
|
json (2.6.3)
|
22
|
-
lockbox (1.2.0)
|
23
21
|
method_source (1.0.0)
|
24
22
|
oj (3.14.3)
|
25
23
|
ougai (2.0.0)
|
data/README.md
CHANGED
@@ -15,6 +15,9 @@ Battle tested in production at [Tines](https://www.tines.com/) 🚀
|
|
15
15
|
- [Replicating all tables with a single group](#replicating-all-tables-with-a-single-group)
|
16
16
|
- [Config check](#config-check)
|
17
17
|
- [Bootstrap](#bootstrap)
|
18
|
+
- [Bootstrap and Config Check with special user role (AWS/GCP/Custom)](#bootstrap-and-config-check-with-special-user-role--aws-gcp-custom-)
|
19
|
+
- [Config Check](#config-check)
|
20
|
+
- [Bootstrap](#bootstrap-1)
|
18
21
|
- [Start sync](#start-sync)
|
19
22
|
- [Stats](#stats)
|
20
23
|
- [Performing switchover](#performing-switchover)
|
@@ -22,6 +25,7 @@ Battle tested in production at [Tines](https://www.tines.com/) 🚀
|
|
22
25
|
- [Switchover strategies with minimal downtime](#switchover-strategies-with-minimal-downtime)
|
23
26
|
- [Rolling restart strategy](#rolling-restart-strategy)
|
24
27
|
- [DNS Failover strategy](#dns-failover-strategy)
|
28
|
+
- [Contributing](#contributing)
|
25
29
|
|
26
30
|
## Installation
|
27
31
|
|
@@ -51,7 +55,7 @@ https://hub.docker.com/r/shayonj/pg_easy_replicate
|
|
51
55
|
|
52
56
|
- PostgreSQL 10 and later
|
53
57
|
- Ruby 2.7 and later
|
54
|
-
- Database user should have permissions for `SUPERUSER`
|
58
|
+
- Database user should have permissions for `SUPERUSER` or pass in the special user role that has the privileges to create role, schema, publication and subscription on both databases. More on `--special-user-role` section below.
|
55
59
|
- Both databases should have the same schema
|
56
60
|
|
57
61
|
## Limits
|
@@ -116,6 +120,31 @@ $ pg_easy_replicate bootstrap --group-name database-cluster-1
|
|
116
120
|
...
|
117
121
|
```
|
118
122
|
|
123
|
+
### Bootstrap and Config Check with special user role (AWS/GCP/Custom)
|
124
|
+
|
125
|
+
If you don't want your primary login user to have `superuser` privileges or you are on AWS or GCP, you will need to pass in the special user role that has the privileges to create role, schema, publication and subscription. This is required so `pg_easy_replicate` can create a dedicated user for replication which is granted the respective special user role to carry out its functionalities.
|
126
|
+
|
127
|
+
For AWS the special user role is `rds_superuser`, and for GCP it is `cloudsqlsuperuser`. Please refer to docs for the most up to date information.
|
128
|
+
|
129
|
+
**Note**: The user in the connection url must be part of the special user role being supplied.
|
130
|
+
|
131
|
+
#### Config Check
|
132
|
+
|
133
|
+
```bash
|
134
|
+
$ pg_easy_replicate config_check --special-user-role="rds_superuser"
|
135
|
+
|
136
|
+
✅ Config is looking good.
|
137
|
+
```
|
138
|
+
|
139
|
+
#### Bootstrap
|
140
|
+
|
141
|
+
```bash
|
142
|
+
$ pg_easy_replicate bootstrap --group-name database-cluster-1 --special-user-role="rds_superuser"
|
143
|
+
|
144
|
+
{"name":"pg_easy_replicate","hostname":"PKHXQVK6DW","pid":21485,"level":30,"time":"2023-06-19T15:51:11.015-04:00","v":0,"msg":"Setting up schema","version":"0.1.0"}
|
145
|
+
...
|
146
|
+
```
|
147
|
+
|
119
148
|
### Start sync
|
120
149
|
|
121
150
|
Once the bootstrap is complete, you can start the sync. Starting the sync sets up the publication, subscription and performs other minor housekeeping things.
|
@@ -216,3 +245,11 @@ Next, you can set up a program that watches the `stats` and waits until `switcho
|
|
216
245
|
In this strategy, you have a weighted based DNS system (example [AWS Route53 weighted records](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-values-weighted.html)) where 100% of traffic goes to a primary origin and 0% to a secondary origin. The primary origin here is the DNS host for your source database and secondary origin is the DNS host for your target database. You can set up your application ahead of time to interact with the database using DNS from the weighted group.
|
217
246
|
|
218
247
|
Next, you can set up a program that watches the `stats` and waits until `switchover_completed_at` is reporting as `true`. Once that happens it updates the weight in the DNS weighted group where 100% of the requests now go to the new/target database. Note: Keeping a low `ttl` is recommended.
|
248
|
+
|
249
|
+
## Contributing
|
250
|
+
|
251
|
+
PRs most welcome. You can get started locally by
|
252
|
+
|
253
|
+
- `docker compose down -v && docker compose up --remove-orphans --build`
|
254
|
+
- Install ruby `3.1.4` using RVM ([instruction](https://rvm.io/rvm/install#any-other-system))
|
255
|
+
- `bundle exec rspec` for specs
|
data/bin/release.sh
CHANGED
@@ -1,28 +1,18 @@
|
|
1
1
|
export VERSION=$1
|
2
2
|
echo "VERSION: ${VERSION}"
|
3
3
|
|
4
|
-
# echo "=== Pushing tags to github ===="
|
5
|
-
# git tag v"$VERSION"
|
6
|
-
# git push origin --tags
|
7
|
-
|
8
4
|
echo "=== Building Gem ===="
|
9
5
|
gem build pg_easy_replicate.gemspec
|
10
6
|
|
11
7
|
echo "=== Pushing gem ===="
|
12
8
|
gem push pg_easy_replicate-"$VERSION".gem
|
13
9
|
|
14
|
-
echo "=== Sleeping for
|
15
|
-
sleep
|
16
|
-
|
17
|
-
echo "=== Building Image ===="
|
18
|
-
docker build . --build-arg VERSION="$VERSION" -t shayonj/pg_easy_replicate:"$VERSION"
|
19
|
-
|
20
|
-
echo "=== Tagging Image ===="
|
21
|
-
docker image tag shayonj/pg_easy_replicate:"$VERSION" shayonj/pg_easy_replicate:latest
|
10
|
+
echo "=== Sleeping for 15s ===="
|
11
|
+
sleep 15
|
22
12
|
|
23
|
-
echo "=== Pushing
|
24
|
-
|
25
|
-
|
13
|
+
echo "=== Pushing tags to github ===="
|
14
|
+
git tag v"$VERSION"
|
15
|
+
git push origin --tags
|
26
16
|
|
27
17
|
echo "=== Cleaning up ===="
|
28
18
|
rm pg_easy_replicate-"$VERSION".gem
|
data/docker-compose.yml
CHANGED
@@ -1,32 +1,34 @@
|
|
1
1
|
version: "3.7"
|
2
2
|
services:
|
3
3
|
source_db:
|
4
|
-
image: postgres:12
|
4
|
+
image: postgres:12
|
5
5
|
ports:
|
6
6
|
- "5432:5432"
|
7
7
|
environment:
|
8
8
|
POSTGRES_USER: jamesbond
|
9
|
-
POSTGRES_PASSWORD:
|
9
|
+
POSTGRES_PASSWORD: jamesbond123@7!'3aaR
|
10
10
|
POSTGRES_DB: postgres
|
11
|
-
command:
|
12
|
-
-
|
13
|
-
-
|
14
|
-
-
|
11
|
+
command: >
|
12
|
+
-c wal_level=logical
|
13
|
+
-c ssl=on
|
14
|
+
-c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
15
|
+
-c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
|
15
16
|
networks:
|
16
17
|
localnet:
|
17
18
|
|
18
19
|
target_db:
|
19
|
-
image: postgres:12
|
20
|
+
image: postgres:12
|
20
21
|
ports:
|
21
22
|
- "5433:5432"
|
22
23
|
environment:
|
23
24
|
POSTGRES_USER: jamesbond
|
24
|
-
POSTGRES_PASSWORD:
|
25
|
+
POSTGRES_PASSWORD: jamesbond123@7!'3aaR
|
25
26
|
POSTGRES_DB: postgres
|
26
|
-
command:
|
27
|
-
-
|
28
|
-
-
|
29
|
-
-
|
27
|
+
command: >
|
28
|
+
-c wal_level=logical
|
29
|
+
-c ssl=on
|
30
|
+
-c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
31
|
+
-c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
|
30
32
|
networks:
|
31
33
|
localnet:
|
32
34
|
|
@@ -8,8 +8,14 @@ module PgEasyReplicate
|
|
8
8
|
|
9
9
|
desc "config_check",
|
10
10
|
"Prints if source and target database have the required config"
|
11
|
-
|
12
|
-
|
11
|
+
method_option :special_user_role,
|
12
|
+
aliases: "-s",
|
13
|
+
desc:
|
14
|
+
"Name of the role that has superuser permissions. Usually useful for AWS (rds_superuser) or GCP (cloudsqlsuperuser)."
|
15
|
+
def config_check(options)
|
16
|
+
PgEasyReplicate.assert_config(
|
17
|
+
special_user_role: options[:special_user_role],
|
18
|
+
)
|
13
19
|
|
14
20
|
puts "✅ Config is looking good."
|
15
21
|
end
|
@@ -18,6 +24,10 @@ module PgEasyReplicate
|
|
18
24
|
aliases: "-g",
|
19
25
|
required: true,
|
20
26
|
desc: "Name of the group to provision"
|
27
|
+
method_option :special_user_role,
|
28
|
+
aliases: "-s",
|
29
|
+
desc:
|
30
|
+
"Name of the role that has superuser permissions. Usually useful with AWS (rds_superuser) or GCP (cloudsqlsuperuser)."
|
21
31
|
desc "bootstrap",
|
22
32
|
"Sets up temporary tables for information required during runtime"
|
23
33
|
def bootstrap
|
@@ -9,8 +9,6 @@ module PgEasyReplicate
|
|
9
9
|
DEFAULT_WAIT = 5 # seconds
|
10
10
|
|
11
11
|
def start_sync(options)
|
12
|
-
PgEasyReplicate.assert_config
|
13
|
-
|
14
12
|
create_publication(
|
15
13
|
group_name: options[:group_name],
|
16
14
|
conn_string: source_db_url,
|
@@ -62,10 +60,14 @@ module PgEasyReplicate
|
|
62
60
|
"Setting up publication",
|
63
61
|
{ publication_name: publication_name(group_name) },
|
64
62
|
)
|
63
|
+
|
65
64
|
Query.run(
|
66
65
|
query: "create publication #{publication_name(group_name)}",
|
67
66
|
connection_url: conn_string,
|
67
|
+
user: db_user(conn_string),
|
68
68
|
)
|
69
|
+
rescue => e
|
70
|
+
raise "Unable to create publication: #{e.message}"
|
69
71
|
end
|
70
72
|
|
71
73
|
def add_tables_to_publication(
|
@@ -91,6 +93,8 @@ module PgEasyReplicate
|
|
91
93
|
schema: schema,
|
92
94
|
)
|
93
95
|
end
|
96
|
+
rescue => e
|
97
|
+
raise "Unable to add tables to publication: #{e.message}"
|
94
98
|
end
|
95
99
|
|
96
100
|
def list_all_tables(schema:, conn_string:)
|
@@ -112,7 +116,10 @@ module PgEasyReplicate
|
|
112
116
|
Query.run(
|
113
117
|
query: "DROP PUBLICATION IF EXISTS #{publication_name(group_name)}",
|
114
118
|
connection_url: conn_string,
|
119
|
+
user: db_user(conn_string),
|
115
120
|
)
|
121
|
+
rescue => e
|
122
|
+
raise "Unable to drop publication: #{e.message}"
|
116
123
|
end
|
117
124
|
|
118
125
|
def create_subscription(
|
@@ -132,6 +139,7 @@ module PgEasyReplicate
|
|
132
139
|
query:
|
133
140
|
"CREATE SUBSCRIPTION #{subscription_name(group_name)} CONNECTION '#{source_conn_string}' PUBLICATION #{publication_name(group_name)}",
|
134
141
|
connection_url: target_conn_string,
|
142
|
+
user: db_user(target_conn_string),
|
135
143
|
transaction: false,
|
136
144
|
)
|
137
145
|
rescue Sequel::DatabaseError => e
|
@@ -141,7 +149,7 @@ module PgEasyReplicate
|
|
141
149
|
)
|
142
150
|
end
|
143
151
|
|
144
|
-
raise
|
152
|
+
raise "Unable to create subscription: #{e.message}"
|
145
153
|
end
|
146
154
|
|
147
155
|
def drop_subscription(group_name:, target_conn_string:)
|
@@ -157,11 +165,11 @@ module PgEasyReplicate
|
|
157
165
|
connection_url: target_conn_string,
|
158
166
|
transaction: false,
|
159
167
|
)
|
168
|
+
rescue => e
|
169
|
+
raise "Unable to drop subscription: #{e.message}"
|
160
170
|
end
|
161
171
|
|
162
172
|
def stop_sync(target_conn_string:, source_conn_string:, group_name:)
|
163
|
-
PgEasyReplicate.assert_config
|
164
|
-
|
165
173
|
logger.info(
|
166
174
|
"Stopping sync",
|
167
175
|
{
|
@@ -177,6 +185,8 @@ module PgEasyReplicate
|
|
177
185
|
group_name: group_name,
|
178
186
|
target_conn_string: target_conn_string,
|
179
187
|
)
|
188
|
+
rescue => e
|
189
|
+
raise "Unable to stop sync user: #{e.message}"
|
180
190
|
end
|
181
191
|
|
182
192
|
def switchover(
|
@@ -185,7 +195,6 @@ module PgEasyReplicate
|
|
185
195
|
target_conn_string: target_db_url,
|
186
196
|
lag_delta_size: DEFAULT_LAG
|
187
197
|
)
|
188
|
-
PgEasyReplicate.assert_config
|
189
198
|
group = Group.find(group_name)
|
190
199
|
|
191
200
|
watch_lag(group_name: group_name, lag: lag_delta_size)
|
@@ -258,6 +267,8 @@ module PgEasyReplicate
|
|
258
267
|
"SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE usename = '#{db_user(source_db_url)}';"
|
259
268
|
|
260
269
|
Query.run(query: kill_sql, connection_url: source_db_url)
|
270
|
+
rescue => e
|
271
|
+
raise "Unable to revoke connections on source db: #{e.message}"
|
261
272
|
end
|
262
273
|
|
263
274
|
def restore_connections_on_source_db(group_name)
|
@@ -298,6 +309,8 @@ module PgEasyReplicate
|
|
298
309
|
SQL
|
299
310
|
|
300
311
|
Query.run(query: sql, connection_url: conn_string, schema: schema)
|
312
|
+
rescue => e
|
313
|
+
raise "Unable to refresh sequences: #{e.message}"
|
301
314
|
end
|
302
315
|
|
303
316
|
def mark_switchover_complete(group_name)
|
data/lib/pg_easy_replicate.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require "json"
|
4
4
|
require "ougai"
|
5
|
-
require "lockbox"
|
6
5
|
require "pg"
|
7
6
|
require "sequel"
|
8
7
|
|
@@ -22,17 +21,20 @@ module PgEasyReplicate
|
|
22
21
|
extend Helper
|
23
22
|
|
24
23
|
class << self
|
25
|
-
def config
|
24
|
+
def config(special_user_role: nil)
|
26
25
|
abort_with("SOURCE_DB_URL is missing") if source_db_url.nil?
|
27
26
|
abort_with("TARGET_DB_URL is missing") if target_db_url.nil?
|
27
|
+
|
28
28
|
@config ||=
|
29
29
|
begin
|
30
30
|
q =
|
31
31
|
"select name, setting from pg_settings where name in ('max_wal_senders', 'max_worker_processes', 'wal_level', 'max_replication_slots', 'max_logical_replication_workers');"
|
32
32
|
|
33
33
|
{
|
34
|
-
|
35
|
-
|
34
|
+
source_db_is_super_user:
|
35
|
+
is_super_user?(source_db_url, special_user_role),
|
36
|
+
target_db_is_super_user:
|
37
|
+
is_super_user?(target_db_url, special_user_role),
|
36
38
|
source_db:
|
37
39
|
Query.run(
|
38
40
|
query: q,
|
@@ -51,33 +53,43 @@ module PgEasyReplicate
|
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
54
|
-
def assert_config
|
55
|
-
|
56
|
+
def assert_config(special_user_role: nil)
|
57
|
+
config_hash = config(special_user_role: special_user_role)
|
58
|
+
|
59
|
+
unless assert_wal_level_logical(config_hash.dig(:source_db))
|
56
60
|
abort_with("WAL_LEVEL should be LOGICAL on source DB")
|
57
61
|
end
|
58
62
|
|
59
|
-
unless assert_wal_level_logical(
|
63
|
+
unless assert_wal_level_logical(config_hash.dig(:target_db))
|
60
64
|
abort_with("WAL_LEVEL should be LOGICAL on target DB")
|
61
65
|
end
|
62
66
|
|
63
|
-
unless
|
64
|
-
abort_with("User on source database
|
67
|
+
unless config_hash.dig(:source_db_is_super_user)
|
68
|
+
abort_with("User on source database does not have super user privilege")
|
65
69
|
end
|
66
70
|
|
67
|
-
return if
|
68
|
-
abort_with("User on target database
|
71
|
+
return if config_hash.dig(:target_db_is_super_user)
|
72
|
+
abort_with("User on target database does not have super user privilege")
|
69
73
|
end
|
70
74
|
|
71
75
|
def bootstrap(options)
|
72
|
-
assert_config
|
73
76
|
logger.info("Setting up schema")
|
74
77
|
setup_schema
|
75
78
|
|
76
79
|
logger.info("Setting up replication user on source database")
|
77
|
-
create_user(
|
80
|
+
create_user(
|
81
|
+
conn_string: source_db_url,
|
82
|
+
group_name: options[:group_name],
|
83
|
+
special_user_role: options[:special_user_role],
|
84
|
+
grant_permissions_on_schema: true,
|
85
|
+
)
|
78
86
|
|
79
87
|
logger.info("Setting up replication user on target database")
|
80
|
-
create_user(
|
88
|
+
create_user(
|
89
|
+
conn_string: target_db_url,
|
90
|
+
group_name: options[:group_name],
|
91
|
+
special_user_role: options[:special_user_role],
|
92
|
+
)
|
81
93
|
|
82
94
|
logger.info("Setting up groups tables")
|
83
95
|
Group.setup
|
@@ -123,7 +135,10 @@ module PgEasyReplicate
|
|
123
135
|
query: "DROP SCHEMA IF EXISTS #{internal_schema_name} CASCADE",
|
124
136
|
connection_url: source_db_url,
|
125
137
|
schema: internal_schema_name,
|
138
|
+
user: db_user(target_db_url),
|
126
139
|
)
|
140
|
+
rescue => e
|
141
|
+
raise "Unable to drop schema: #{e.message}"
|
127
142
|
end
|
128
143
|
|
129
144
|
def setup_schema
|
@@ -139,6 +154,8 @@ module PgEasyReplicate
|
|
139
154
|
schema: internal_schema_name,
|
140
155
|
user: db_user(target_db_url),
|
141
156
|
)
|
157
|
+
rescue => e
|
158
|
+
raise "Unable to setup schema: #{e.message}"
|
142
159
|
end
|
143
160
|
|
144
161
|
def logger
|
@@ -160,38 +177,109 @@ module PgEasyReplicate
|
|
160
177
|
end
|
161
178
|
end
|
162
179
|
|
163
|
-
def is_super_user?(url)
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
180
|
+
def is_super_user?(url, special_user_role = nil)
|
181
|
+
if special_user_role
|
182
|
+
sql = <<~SQL
|
183
|
+
SELECT r.rolname AS username,
|
184
|
+
r1.rolname AS "role"
|
185
|
+
FROM pg_catalog.pg_roles r
|
186
|
+
LEFT JOIN pg_catalog.pg_auth_members m ON (m.member = r.oid)
|
187
|
+
LEFT JOIN pg_roles r1 ON (m.roleid=r1.oid)
|
188
|
+
WHERE r.rolname = '#{db_user(url)}'
|
189
|
+
ORDER BY 1;
|
190
|
+
SQL
|
191
|
+
|
192
|
+
r =
|
193
|
+
Query.run(
|
194
|
+
query: sql,
|
195
|
+
connection_url: url,
|
196
|
+
user: db_user(target_db_url),
|
197
|
+
)
|
198
|
+
# If special_user_role is passed just ensure the url in conn_string has been granted
|
199
|
+
# the special_user_role
|
200
|
+
r.any? { |q| q[:role] == special_user_role }
|
201
|
+
else
|
202
|
+
r =
|
203
|
+
Query.run(
|
204
|
+
query:
|
205
|
+
"SELECT rolname, rolsuper FROM pg_roles where rolname = '#{db_user(url)}';",
|
206
|
+
connection_url: url,
|
207
|
+
user: db_user(target_db_url),
|
208
|
+
)
|
209
|
+
r.any? { |q| q[:rolsuper] }
|
210
|
+
end
|
211
|
+
rescue => e
|
212
|
+
raise "Unable to check superuser conditions: #{e.message}"
|
172
213
|
end
|
173
214
|
|
174
|
-
def create_user(
|
175
|
-
|
215
|
+
def create_user(
|
216
|
+
conn_string:,
|
217
|
+
group_name:,
|
218
|
+
special_user_role: nil,
|
219
|
+
grant_permissions_on_schema: false
|
220
|
+
)
|
221
|
+
password = connection_info(conn_string)[:password].gsub("'") { "''" }
|
222
|
+
|
176
223
|
sql = <<~SQL
|
177
224
|
drop role if exists #{internal_user_name};
|
178
|
-
create role #{internal_user_name} with password '#{password}' login
|
225
|
+
create role #{internal_user_name} with password '#{password}' login createdb createrole;
|
226
|
+
grant all privileges on database #{db_name(conn_string)} TO #{internal_user_name};
|
179
227
|
SQL
|
180
228
|
|
181
229
|
Query.run(
|
182
230
|
query: sql,
|
183
231
|
connection_url: conn_string,
|
184
|
-
user: db_user(
|
232
|
+
user: db_user(conn_string),
|
233
|
+
transaction: false,
|
234
|
+
)
|
235
|
+
|
236
|
+
sql =
|
237
|
+
if special_user_role
|
238
|
+
"grant #{special_user_role} to #{internal_user_name};"
|
239
|
+
else
|
240
|
+
"alter user #{internal_user_name} with superuser;"
|
241
|
+
end
|
242
|
+
|
243
|
+
Query.run(
|
244
|
+
query: sql,
|
245
|
+
connection_url: conn_string,
|
246
|
+
user: db_user(conn_string),
|
247
|
+
transaction: false,
|
185
248
|
)
|
249
|
+
|
250
|
+
return unless grant_permissions_on_schema
|
251
|
+
Query.run(
|
252
|
+
query:
|
253
|
+
"grant all on schema #{internal_schema_name} to #{internal_user_name}",
|
254
|
+
connection_url: conn_string,
|
255
|
+
user: db_user(conn_string),
|
256
|
+
transaction: false,
|
257
|
+
)
|
258
|
+
rescue => e
|
259
|
+
raise "Unable to create user: #{e.message}"
|
186
260
|
end
|
187
261
|
|
188
262
|
def drop_user(conn_string:, group_name:)
|
189
|
-
sql =
|
263
|
+
sql = <<~SQL
|
264
|
+
revoke all privileges on database #{db_name(conn_string)} from #{internal_user_name};
|
265
|
+
SQL
|
190
266
|
Query.run(
|
191
267
|
query: sql,
|
192
268
|
connection_url: conn_string,
|
193
269
|
user: db_user(conn_string),
|
194
270
|
)
|
271
|
+
|
272
|
+
sql = <<~SQL
|
273
|
+
drop role if exists #{internal_user_name};
|
274
|
+
SQL
|
275
|
+
|
276
|
+
Query.run(
|
277
|
+
query: sql,
|
278
|
+
connection_url: conn_string,
|
279
|
+
user: db_user(conn_string),
|
280
|
+
)
|
281
|
+
rescue => e
|
282
|
+
raise "Unable to drop user: #{e.message}"
|
195
283
|
end
|
196
284
|
end
|
197
285
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_easy_replicate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shayon Mukherjee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: lockbox
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.0
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.0
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: ougai
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|