Odyssey
fixture.py
1 """
2 DB and filesystem fixtures for credit unions.
3 """
4 
5 import distutils.dir_util
6 import json
7 import os.path
8 import psycopg2
9 import shellish
10 import shutil
11 import tarfile
12 import tempfile
13 
14 SCHEMA_VERSION = 1
15 FIXTURE_PATH = '/fixtures'
16 
17 
18 class Fixture(shellish.Command):
19  """ Load and save database fixtures. """
20 
21  name = 'fixture'
22 
23  def setup_args(self, parser):
24  self.add_subcommand(Load)
25  self.add_subcommand(Save)
26 
27 
28 class Load(shellish.Command):
29  """ Load a DB and/or FS fixture.
30 
31  Typically fixtures are tar/gz bundles located in your project's
32  `/fixtures` directory. The exact layout of these bundles is documented in
33  the README.md of this same directory. In short, each fixture bundle
34  represents data associated with a single customer (credit union). In the
35  Odyssey system credit union data consists of custom DB tables prefixed
36  with the credit union code, (cruisecu for example) and of customizable
37  filesystem assets which are typically located in /home/<cucode>/.
38  Initially the filesystem content is just boilerplate, but can be
39  customized for each customer. """
40 
41  name = 'load'
42  # owner/group id for destination container (php:apache)
43  file_owner = 33
44  file_group = 33
45 
46  def setup_args(self, parser):
47  self.add_argument('name', help='Name of fixture file (sans file '
48  'extension)')
49  self.add_argument('--dbhost', env='DATABASE_HOST',
50  help="Database server host or IP.")
51  self.add_argument('--dbport', type=int, env='DATABASE_PORT',
52  help="Database server port.")
53  self.add_argument('--dbuser', env='DATABASE_USER',
54  help="Database username.")
55  self.add_argument('--dbpassword', env='DATABASE_PASSWORD',
56  help="Database password.")
57  self.add_argument('--dbname', env='DATABASE_NAME',
58  help="Database to load into.")
59 
60 
61  def run(self, args):
62  path = '%s/%s' % (FIXTURE_PATH, args.name)
63  tarpath = '%s.tgz' % path
64  if os.path.isfile(tarpath):
65  shellish.vtmlprint('<b>Extracting: <blue>%s</blue></b>' % tarpath)
66  with tarfile.open(tarpath) as bundle:
67  with tempfile.TemporaryDirectory() as tmpdir:
68  bundle.extractall(tmpdir)
69  self.load_from_dir(args, tmpdir)
70  elif os.path.isdir(path):
71  shellish.vtmlprint('<b>Loading fixture directory: <blue>%s'
72  '</blue></b>' % path)
73  self.load_from_dir(args, path)
74  else:
75  raise SystemExit("Fixture not found: %s" % path)
76 
77  def load_from_dir(self, args, path):
78  os.chdir(path)
79  with open('./manifest.json') as f:
80  manifest = json.load(f)
81  shellish.vtmlprint("Name: %s" % manifest['name'])
82  shellish.vtmlprint("ID: %s" % manifest['id'])
83  shellish.vtmlprint("Bundle Version: %s" % manifest['version'])
84  shellish.vtmlprint("Schema Version: %s" % manifest['schema'])
85  if manifest['schema'] < SCHEMA_VERSION:
86  raise ValueError("Fixture schema is incompatible with this stack "
87  "(%s < %s)" % (manifest['schema'],
88  SCHEMA_VERSION))
89  try:
90  sqlfiles = sorted(x for x in os.listdir('db')
91  if not x.startswith('.'))
92  except FileNotFoundError:
93  sqlfiles = []
94  if not sqlfiles:
95  shellish.vtmlprint('<yellow>No database content found')
96  else:
97  with psycopg2.connect(host=args.dbhost, port=args.dbport,
98  user=args.dbuser, password=args.dbpassword,
99  dbname=args.dbname) as db:
100  for x in sqlfiles:
101  if x.endswith('.sql'):
102  shellish.vtmlprint("Loading DB file: <cyan>%s" % x)
103  with open('db/%s' % x) as f:
104  with db.cursor() as c:
105  c.execute(f.read())
106  else:
107  shellish.vtmlprint('<yellow>Invalid DB file: %s' % x)
108  self.install_files('fs', '/home/%s' % manifest['id'])
109  shellish.vtmlprint("\n<b>Fixture README:</b>\n")
110  with open('README.md') as f:
111  print(f.read())
112 
113  def install_files(self, src, dst):
114  """ Copy the file tree into the destination location and correct
115  ownership as needed. """
116  distutils.dir_util.copy_tree(src, dst, preserve_symlinks=1,
117  verbose=1)
118  shutil.chown(dst, self.file_owner, self.file_group)
119  for root, dirs, files in os.walk(dst):
120  for x in files + dirs:
121  shutil.chown(os.path.join(root, x), self.file_owner,
122  self.file_group)
123 
124 
125 class Save(shellish.Command):
126  """ Create a fixture bundle.
127 
128  TBD """
129 
130  name = 'save'
131 
132  def run(self, args):
133  raise NotImplementedError('implement me')
def load_from_dir(self, args, path)
Definition: fixture.py:77
def install_files(self, src, dst)
Definition: fixture.py:113