Skip to content

Commit 7be78e4

Browse files
authored
Merge pull request #23 from typecode/develop
Version 0.3.2
2 parents a1176d9 + c6ad126 commit 7be78e4

File tree

6 files changed

+83
-52
lines changed

6 files changed

+83
-52
lines changed

create_readme.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import chevron
66

7-
PLUGINS_PATH = os.path.join(os.path.dirname(__file__), 'sykle.plugins')
7+
PLUGINS_PATH = os.path.join(os.path.dirname(__file__), 'sykle/plugins')
88
TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), 'README.mustache')
99
README_PATH = os.path.join(os.path.dirname(__file__), 'README.md')
1010

@@ -22,6 +22,7 @@ def create_readme(template, destination, docstring):
2222

2323
if os.path.isfile(destination):
2424
os.chmod(destination, 0o644)
25+
2526
with open(destination, 'w+', encoding='utf-8') as f:
2627
f.write(long_description)
2728

sykle/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# __init__.py
22

3-
__version__ = '0.3.1'
3+
__version__ = '0.3.2'
44

55

66
class Sykle():

sykle/plugins/sync_pg_data/README.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
Wrapper around `pg_restore`, `pg_dump` and `psql` that allows you to sync data from one location to another.
44

5-
### Data only
5+
### Usage Instructions
66

7-
This command does NOT do a full database dump and restore. Dumps and restores DATA ONLY so we can sync apps that still have active connections. This means all tables in the source that you would like to link to the destination must be present.
7+
This command will sync BOTH data and the schema. This has a couple implications:
88

9-
### Truncation
10-
11-
In order to avoid foreign key constraint errors when restoring data, any existing data must be removed. This means *THE ORIGINAL DESTINATION DATA WILL BE REMOVED*.
9+
- You will need to ensure that the place you are syncing FROM has code that is either the SAME or is OLDER than the place you are syncing to. Although you **COULD** dump data from a newer codebase into data from an older codebase, if the database schema has changed, the code and migrations will not work properly.
10+
- `production -> staging`
11+
- `staging -> development`
12+
- `production -> development`
13+
- ~~`staging -> production`~~
14+
- ~~`development -> staging`~~
15+
- ~~`development -> production`~~
16+
- Any services using the destination database will need to be stopped. You can use the `dependent_services` section of the config to list docker-compose services that should automatically be stopped and restarted.
17+
- Syncing will DELETE all data in the destination database and replace it with data from the source. You can make a backup before syncing using the `dump` command
1218

1319
### Why put this in syk?
1420

@@ -37,7 +43,7 @@ Usage instructions can be viewed after installation with `sykle sync_pg_data --h
3743
Sync PG Data
3844
3945
Usage:
40-
syk sync_pg_data truncate --dest=<name> [--debug]
46+
syk sync_pg_data recreate --dest=<name> [--debug]
4147
syk sync_pg_data restore --dest=<name> [--file=<name>] [--debug]
4248
syk sync_pg_data dump --src=<name> [--debug]
4349
syk sync_pg_data --src=<name> --dest=<name> [--debug]
@@ -51,14 +57,18 @@ Options:
5157
--file=<name> Restore from a file
5258
5359
Description:
54-
truncate Removes all data on db
55-
restore Restores from a file
60+
recreate Drops and then recreates a database (with data)
5661
dump Dumps data to a file
62+
restore Restores from a file
5763
5864
Example .sykle.json:
5965
{
6066
"plugins": {
6167
"sync_pg_data": {
68+
"dependent_services": [], // List of any services that should be
69+
// stopped while syncing and restarted
70+
// afterwards. (OPTIONAL)
71+
6272
"local": { // Name of location
6373
"env_file": ".env", // Envfile for args to use (OPTIONAL)
6474
"write": true, // Allow writes to location

sykle/plugins/sync_pg_data/README.mustache

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
Wrapper around `pg_restore`, `pg_dump` and `psql` that allows you to sync data from one location to another.
44

5-
### Data only
6-
7-
This command does NOT do a full database dump and restore. Dumps and restores DATA ONLY so we can sync apps that still have active connections. This means all tables in the source that you would like to link to the destination must be present.
8-
9-
### Truncation
10-
11-
In order to avoid foreign key constraint errors when restoring data, any existing data must be removed. This means *THE ORIGINAL DESTINATION DATA WILL BE REMOVED*.
5+
### Usage Instructions
6+
7+
This command will sync BOTH data and the schema. This has a couple implications:
8+
9+
- You will need to ensure that the place you are syncing FROM has code that is either the SAME or is OLDER than the place you are syncing to. Although you **COULD** dump data from a newer codebase into data from an older codebase, if the database schema has changed, the code and migrations will not work properly.
10+
- `production -> staging` ✅
11+
- `staging -> development` ✅
12+
- `production -> development` ✅
13+
- ~~`staging -> production`~~ ❌
14+
- ~~`development -> staging`~~ ❌
15+
- ~~`development -> production`~~ ❌
16+
- Any services using the destination database will need to be stopped. You can use the `dependent_services` section of the config to list docker-compose services that should automatically be stopped and restarted.
17+
- Syncing will DELETE all data in the destination database and replace it with data from the source. You can make a backup before syncing using the `dump` command
1218

1319
### Why put this in syk?
1420

sykle/plugins/sync_pg_data/__init__.py

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Sync PG Data
22
33
Usage:
4-
syk sync_pg_data truncate --dest=<name> [--debug]
4+
syk sync_pg_data recreate --dest=<name> [--debug]
55
syk sync_pg_data restore --dest=<name> [--file=<name>] [--debug]
66
syk sync_pg_data dump --src=<name> [--debug]
77
syk sync_pg_data --src=<name> --dest=<name> [--debug]
@@ -15,14 +15,18 @@
1515
--file=<name> Restore from a file
1616
1717
Description:
18-
truncate Removes all data on db
19-
restore Restores from a file
18+
recreate Drops and then recreates a database (with data)
2019
dump Dumps data to a file
20+
restore Restores from a file
2121
2222
Example .sykle.json:
2323
{
2424
"plugins": {
2525
"sync_pg_data": {
26+
"dependent_services": [], // List of any services that should be
27+
// stopped while syncing and restarted
28+
// afterwards. (OPTIONAL)
29+
2630
"local": { // Name of location
2731
"env_file": ".env", // Envfile for args to use (OPTIONAL)
2832
"write": true, // Allow writes to location
@@ -38,7 +42,7 @@
3842
}
3943
"""
4044

41-
__version__ = '0.0.3'
45+
__version__ = '0.1.0'
4246

4347
from sykle.call_subprocess import call_subprocess
4448
from sykle.plugin_utils import IPlugin
@@ -59,7 +63,8 @@ def get_dump_file_name(self, location):
5963
return os.path.join(self.dump_dir, filename)
6064

6165
def _get_location_args(self, location_name):
62-
location = self.config.get(location_name)
66+
locations = self.config.get("locations", {})
67+
location = locations.get(location_name)
6368
if location is None:
6469
raise Exception(
6570
'Unknown location "{}" (check "sync_pg_data" config)'
@@ -95,11 +100,10 @@ def dump(self, location, dump_file, debug=False):
95100
print('Dumping "{}" to "{}"...'.format(location, dump_file))
96101
call_subprocess(
97102
command=[
98-
'pg_dump', '-C', '-h', args['HOST'],
103+
'pg_dump', '-h', args['HOST'],
99104
'-v', '-U', args['USER'], args['NAME'],
100105
'-p', str(args.get('PORT', 5432)),
101106
'-f', dump_file,
102-
'--data-only',
103107
'--format', 'tar'
104108
],
105109
env={'PGPASSWORD': str(args['PASSWORD'])},
@@ -121,44 +125,45 @@ def restore(self, location, restore_file, debug=False):
121125
'--username', args['USER'],
122126
'--port', str(args.get('PORT', 5432)),
123127
'--dbname', args['NAME'],
124-
'--disable-triggers', restore_file,
125-
'--data-only'
128+
restore_file
126129
],
127130
env={'PGPASSWORD': str(args['PASSWORD'])},
128131
debug=debug
129132
)
130133
print('Restored "{}" to "{}".'.format(restore_file, location))
131134

132-
def truncate(self, location, debug=False):
135+
def recreate(self, location, debug=False):
133136
"""
134-
Deletes all data in the given location. (Does NOT drop tables)
137+
Deletes all data in the given location.
135138
"""
136-
# Double check to ensure we aren't truncating prod
139+
# Double check to ensure we aren't deleting write protected data
137140
self.check_write_permissions(location)
138141
args = self._get_location_args(location)
139-
psql_command = (
140-
" DO \$\$ DECLARE statements CURSOR FOR SELECT " +
141-
"tablename FROM pg_tables WHERE tableowner = '{}' "
142-
.format(args['NAME']) +
143-
"AND schemaname = 'public'; " +
144-
"BEGIN FOR stmt IN statements LOOP EXECUTE " +
145-
"'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || '" +
146-
" CASCADE;'; END LOOP; END; \$\$ LANGUAGE plpgsql;"
147-
)
148142

149-
print('Truncating "{}"...'.format(location))
143+
print('Droping "{}"...'.format(location))
144+
call_subprocess(
145+
command=[
146+
'dropdb', '--host', args['HOST'],
147+
'--username', args['USER'],
148+
'--port', str(args.get('PORT', 5432)),
149+
args['NAME']
150+
],
151+
env={'PGPASSWORD': str(args['PASSWORD'])},
152+
debug=debug
153+
)
154+
print('Dropped "{}".'.format(location))
155+
print('Creating "{}"...'.format(location))
150156
call_subprocess(
151157
command=[
152-
'psql', '--host', args['HOST'],
158+
'createdb', '--host', args['HOST'],
153159
'--username', args['USER'],
154160
'--port', str(args.get('PORT', 5432)),
155-
'-d', args['NAME'],
156-
'-c', '"{}"'.format(psql_command),
161+
args['NAME']
157162
],
158163
env={'PGPASSWORD': str(args['PASSWORD'])},
159164
debug=debug
160165
)
161-
print('Truncated "{}".'.format(location))
166+
print('Created "{}".'.format(location))
162167

163168
def confirm_delete(self, location):
164169
return input(
@@ -182,7 +187,8 @@ def confirm_restore(self, restore_file, location):
182187
.format(restore_file, location)) == 'y'
183188

184189
def check_write_permissions(self, location):
185-
if not self.config.get(location).get('write'):
190+
locations = self.config.get("locations", {})
191+
if not locations.get(location).get('write'):
186192
raise Exception(
187193
'Cannot delete/write data to "{}" '.format(location) +
188194
'("write" is not set to true)')
@@ -197,16 +203,21 @@ def run(self):
197203
src = args.get('--src')
198204
dest = args.get('--dest')
199205
debug = args.get('--debug')
206+
self.sykle.debug = debug
207+
208+
dependent_services = self.config.get("dependent_services", [])
209+
for service in dependent_services:
210+
self.sykle.dc(["stop", service])
200211

201-
if args['truncate']:
212+
if args['recreate']:
202213
self.check_write_permissions(dest)
203214
if self.confirm_delete(dest):
204-
self.truncate(dest, debug)
215+
self.recreate(dest, debug)
205216
elif args['restore']:
206217
self.check_write_permissions(dest)
207218
file = args['--file'] or self.most_recent_backup()
208219
if self.confirm_restore(file, dest) and self.confirm_delete(dest):
209-
self.truncate(dest, debug)
220+
self.recreate(dest, debug)
210221
self.restore(dest, file, debug)
211222
elif args['dump']:
212223
dump_file = self.get_dump_file_name(src)
@@ -217,5 +228,8 @@ def run(self):
217228
dump_file = self.get_dump_file_name(src)
218229
if self.confirm_delete(dest) and self.confirm_dump(dump_file):
219230
self.dump(src, dump_file, debug)
220-
self.truncate(dest, debug)
231+
self.recreate(dest, debug)
221232
self.restore(dest, dump_file, debug)
233+
234+
for service in dependent_services:
235+
self.sykle.dc(["start", service])

test/config_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ def test_docker_vars_for_depolyment(self):
1212
'staging': {
1313
'target': 'some-target',
1414
'docker_vars': {
15-
'non_env_var': 'thing',
16-
'env_var': '$ENV_VAR'
15+
'non_env_var': u'thing',
16+
'env_var': u'$ENV_VAR'
1717
}
1818
}
1919
}
@@ -33,8 +33,8 @@ def test_for_deployment_empty(self):
3333

3434
def test_interpolate_env_values(self):
3535
interpolated = Config.interpolate_env_values(
36-
{'non_env_var': 'A', 'env_var': '$BUILD_NUMBER'},
37-
{'BUILD_NUMBER': '1'}
36+
{'non_env_var': 'A', 'env_var': u'$BUILD_NUMBER'},
37+
{u'BUILD_NUMBER': u'1'}
3838
)
3939
self.assertEqual(
4040
interpolated,

0 commit comments

Comments
 (0)