Merge lp:~danilo/linaro-license-protection/sanitized-tree into lp:~linaro-automation/linaro-license-protection/trunk

Proposed by Данило Шеган
Status: Merged
Merged at revision: 115
Proposed branch: lp:~danilo/linaro-license-protection/sanitized-tree
Merge into: lp:~linaro-automation/linaro-license-protection/trunk
Diff against target: 356 lines (+236/-86)
3 files modified
scripts/linaroscript.py (+63/-0)
scripts/make-sanitized-tree-copy.py (+96/-0)
scripts/update-deployment.py (+77/-86)
To merge this branch: bzr merge lp:~danilo/linaro-license-protection/sanitized-tree
Reviewer Review Type Date Requested Status
Stevan Radaković Approve
Review via email: mp+120889@code.launchpad.net

Description of the change

Use the functionality of SnapshotsPublisher to create a sanitized copy of
an entire tree. We rely on shutil.copy2 and shutil.copystat to ensure
appropriate flags are transferred as well.

We ignore errors and simply log them. This will result in them coming to our inboxes via linaro-infrastructure-errors so we can insure they are not a problem (and we'll need to update the script then, but hey).

The script simply prints out the destination directory when done (all other output goes to stderr). The idea is to use this and store it in an environment variable and then pass it to rsync to copy from mombin (production) to kahaku (staging).

I am still not sure if it'd be better to leave the temp dir creation to the script caller as well. Makes for slightly more complicated wrapper script, but probably not by much. What do you think?

This also extracts some of the common bits (like supporting multiple -v options to increase verbosity of the logger output, and configuration of the logger) out into a separate class and shares it with update-deployment script (the changes in update-deployment are very minor: mostly reindentation and additions of "self." where appropriate).

To post a comment you must log in.
Revision history for this message
Stevan Radaković (stevanr) wrote :

This looks good. Temp directory creation should be left here, IMO.
It might make sense to change my validation script to extend the linaroscript class as well..
Approve +1.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'scripts/linaroscript.py'
--- scripts/linaroscript.py 1970-01-01 00:00:00 +0000
+++ scripts/linaroscript.py 2012-08-22 23:43:25 +0000
@@ -0,0 +1,63 @@
1# Copyright 2012 Linaro.
2
3"""Helper class for creating new scripts.
4
5It pre-defines a logger using the script_name (passed through the constructor)
6and allows different verbosity levels.
7
8Overload the work() method to define the main work to be done by the script.
9
10You can use the logger by accessing instance.logger attribute.
11You can use the parser (eg. to add another argument) by accessing
12instance.argument_parser attribute.
13
14Parsed arguments are available to your work() method in instance.arguments.
15"""
16
17import argparse
18import logging
19
20
21class LinaroScript(object):
22 def __init__(self, script_name, description=None):
23 self.script_name = script_name
24 self.description = description
25 self.argument_parser = argparse.ArgumentParser(
26 description=self.description)
27 self.setup_parser()
28
29 def work(self):
30 """The main body of the script. Overload when subclassing."""
31 raise NotImplementedError
32
33 def run(self):
34 self.arguments = self.argument_parser.parse_args()
35 logging_level = self.get_logging_level_from_verbosity(
36 self.arguments.verbose)
37 self.logger = logging.getLogger(self.script_name)
38 self.logger.setLevel(logging_level)
39 formatter = logging.Formatter(
40 fmt='%(asctime)s %(levelname)s: %(message)s')
41 handler = logging.StreamHandler()
42 handler.setFormatter(formatter)
43 self.logger.addHandler(handler)
44
45 self.work()
46
47 def setup_parser(self):
48 self.argument_parser.add_argument(
49 "-v", "--verbose", action='count',
50 help=("Increase the output verbosity. "
51 "Can be used multiple times"))
52
53 def get_logging_level_from_verbosity(self, verbosity):
54 """Return a logging level based on the number of -v arguments."""
55 if verbosity == 0:
56 logging_level = logging.ERROR
57 elif verbosity == 1:
58 logging_level = logging.INFO
59 elif verbosity >= 2:
60 logging_level = logging.DEBUG
61 else:
62 logging_level = logging.ERROR
63 return logging_level
064
=== added file 'scripts/make-sanitized-tree-copy.py'
--- scripts/make-sanitized-tree-copy.py 1970-01-01 00:00:00 +0000
+++ scripts/make-sanitized-tree-copy.py 2012-08-22 23:43:25 +0000
@@ -0,0 +1,96 @@
1#!/usr/bin/env python
2# Create a copy of a directory structure while preserving as much metadata
3# as possible and sanitizing any sensitive data.
4
5# Everything, unless whitelisted, is truncated and the contents are replaced
6# with the base file name itself.
7
8import os
9import shutil
10import tempfile
11
12from linaroscript import LinaroScript
13from publish_to_snapshots import SnapshotsPublisher
14
15
16class MakeSanitizedTreeCopyScript(LinaroScript):
17
18 def setup_parser(self):
19 super(MakeSanitizedTreeCopyScript, self).setup_parser()
20 self.argument_parser.add_argument(
21 'directory', metavar='DIR', type=str,
22 help="Directory to create a sanitized deep copy of.")
23
24 @staticmethod
25 def filter_accepted_files(directory, list_of_files):
26 accepted_files = []
27 for filename in list_of_files:
28 full_path = os.path.join(directory, filename)
29 if SnapshotsPublisher.is_accepted_for_staging(full_path):
30 accepted_files.append(filename)
31 return accepted_files
32
33 def copy_sanitized_tree(self, source, target):
34 """Copies the tree from `source` to `target` while sanitizing it.
35
36 Performs a recursive copy trying to preserve as many file
37 attributes as possible.
38 """
39 assert os.path.isdir(source) and os.path.isdir(target), (
40 "Both source (%s) and target (%s) must be directories." % (
41 source, target))
42 self.logger.debug("copy_sanitized_tree('%s', '%s')", source, target)
43 filenames = os.listdir(source)
44 for filename in filenames:
45 self.logger.debug("Copying '%s'...", filename)
46 source_file = os.path.join(source, filename)
47 target_file = os.path.join(target, filename)
48 try:
49 if os.path.isdir(source_file):
50 self.logger.debug("Making directory '%s'" % target_file)
51 os.makedirs(target_file)
52 self.copy_sanitized_tree(source_file, target_file)
53 elif SnapshotsPublisher.is_accepted_for_staging(source_file):
54 self.logger.debug(
55 "Copying '%s' to '%s' with no sanitization...",
56 source_file, target_file)
57 shutil.copy2(source_file, target_file)
58 else:
59 self.logger.debug(
60 "Creating sanitized file '%s'", target_file)
61 # This creates an target file.
62 open(target_file, "w").close()
63 shutil.copystat(source_file, target_file)
64 SnapshotsPublisher.sanitize_file(target_file)
65 except (IOError, os.error) as why:
66 self.logger.error(
67 "While copying '%s' to '%s' we hit:\n\t%s",
68 source_file, target_file, str(why))
69
70 try:
71 shutil.copystat(source, target)
72 except OSError as why:
73 self.logger.error(
74 "While copying '%s' to '%s' we hit:\n\t%s",
75 source, target, str(why))
76
77 def work(self):
78 source_directory = self.arguments.directory
79 self.logger.info("Copying and sanitizing '%s'...", source_directory)
80 target_directory = tempfile.mkdtemp()
81 self.logger.info("Temporary directory: '%s'", target_directory)
82
83 self.copy_sanitized_tree(
84 self.arguments.directory, target_directory)
85
86 print target_directory
87
88if __name__ == '__main__':
89 script = MakeSanitizedTreeCopyScript(
90 'make-sanitized-tree-copy',
91 description=(
92 "Makes a copy of a directory tree in a temporary location "
93 "and sanitize file that can contain potentially restricted "
94 "content. "
95 "Returns the path of a newly created temporary directory."))
96 script.run()
097
=== modified file 'scripts/update-deployment.py'
--- scripts/update-deployment.py 2012-08-16 13:09:23 +0000
+++ scripts/update-deployment.py 2012-08-22 23:43:25 +0000
@@ -34,13 +34,13 @@
3434
35"""35"""
3636
37import argparse
38import bzrlib.branch37import bzrlib.branch
39import bzrlib.workingtree38import bzrlib.workingtree
40import logging
41import os39import os
42import subprocess40import subprocess
4341
42from linaroscript import LinaroScript
43
44code_base = '/srv/shared-branches'44code_base = '/srv/shared-branches'
45branch_name = 'linaro-license-protection'45branch_name = 'linaro-license-protection'
46configs_branch_name = 'linaro-license-protection-config'46configs_branch_name = 'linaro-license-protection-config'
@@ -60,92 +60,83 @@
60configs_root = os.path.join(code_base, configs_branch_name)60configs_root = os.path.join(code_base, configs_branch_name)
6161
6262
63def refresh_branch(branch_dir):63class UpdateDeploymentScript(LinaroScript):
64 """Refreshes a branch checked-out to a branch_dir."""64
6565 def refresh_branch(self, branch_dir):
66 code_branch = bzrlib.branch.Branch.open(branch_dir)66 """Refreshes a branch checked-out to a branch_dir."""
67 parent_branch = bzrlib.branch.Branch.open(67
68 code_branch.get_parent())68 code_branch = bzrlib.branch.Branch.open(branch_dir)
69 result = code_branch.pull(source=parent_branch)69 parent_branch = bzrlib.branch.Branch.open(
70 if result.old_revno != result.new_revno:70 code_branch.get_parent())
71 logger.info("Updated %s from %d to %d.",71 result = code_branch.pull(source=parent_branch)
72 branch_dir, result.old_revno, result.new_revno)72 if result.old_revno != result.new_revno:
73 else:73 self.logger.info("Updated %s from %d to %d.",
74 logger.info("No changes to pull from %s.", code_branch.get_parent())74 branch_dir, result.old_revno, result.new_revno)
75 logger.debug("Updating working tree in %s.", branch_dir)75 else:
76 update_tree(branch_dir)76 self.logger.info(
77 return code_branch77 "No changes to pull from %s.", code_branch.get_parent())
7878 self.logger.debug("Updating working tree in %s.", branch_dir)
7979 self.update_tree(branch_dir)
80def update_tree(working_tree_dir):80 return code_branch
81 """Does a checkout update."""81
82 code_tree = bzrlib.workingtree.WorkingTree.open(working_tree_dir)82 def update_tree(self, working_tree_dir):
83 code_tree.update()83 """Does a checkout update."""
8484 code_tree = bzrlib.workingtree.WorkingTree.open(working_tree_dir)
8585 code_tree.update()
86def update_installation(config, installation_root):86
87 """Updates a single installation code and databases.87 def update_installation(self, config, installation_root):
8888 """Updates a single installation code and databases.
89 It expects code and config branches to be simple checkouts (working trees)89
90 so it only does an "update" on them.90 It expects code and config branches to be simple checkouts
9191 (working trees) so it only does an "update" on them.
92 Afterwards, it runs "syncdb" and "collectstatic" steps.92
93 """93 Afterwards, it runs "syncdb" and "collectstatic" steps.
94 refresh_branch(os.path.join(installation_root, branch_name))94 """
95 refresh_branch(os.path.join(installation_root, "configs"))95 self.refresh_branch(os.path.join(installation_root, branch_name))
96 os.environ["PYTHONPATH"] = (96 self.refresh_branch(os.path.join(installation_root, "configs"))
97 ":".join(97 os.environ["PYTHONPATH"] = (
98 [installation_root,98 ":".join(
99 os.path.join(installation_root, branch_name),99 [installation_root,
100 os.path.join(installation_root, "configs", "django"),100 os.path.join(installation_root, branch_name),
101 os.environ.get("PYTHONPATH", "")]))101 os.path.join(installation_root, "configs", "django"),
102102 os.environ.get("PYTHONPATH", "")]))
103 logger.info("Updating installation in %s with config %s...",103
104 installation_root, config)104 self.logger.info("Updating installation in %s with config %s...",
105 os.environ["DJANGO_SETTINGS_MODULE"] = config105 installation_root, config)
106 logger.debug("DJANGO_SETTINGS_MODULE=%s",106 os.environ["DJANGO_SETTINGS_MODULE"] = config
107 os.environ.get("DJANGO_SETTINGS_MODULE"))107 self.logger.debug("DJANGO_SETTINGS_MODULE=%s",
108108 os.environ.get("DJANGO_SETTINGS_MODULE"))
109 logger.debug("Doing 'syncdb'...")109
110 logger.debug(subprocess.check_output(110 self.logger.debug("Doing 'syncdb'...")
111 ["django-admin", "syncdb", "--noinput"], cwd=code_root))111 self.logger.debug(subprocess.check_output(
112112 ["django-admin", "syncdb", "--noinput"], cwd=code_root))
113 logger.debug("Doing 'collectstatic'...")113
114 logger.debug(subprocess.check_output(114 self.logger.debug("Doing 'collectstatic'...")
115 ["django-admin", "collectstatic", "--noinput"],115 self.logger.debug(subprocess.check_output(
116 cwd=code_root))116 ["django-admin", "collectstatic", "--noinput"],
117 cwd=code_root))
118
119 def setup_parser(self):
120 super(UpdateDeploymentScript, self).setup_parser()
121 self.argument_parser.add_argument(
122 'configs', metavar='CONFIG', nargs='+',
123 choices=configs_to_use.keys(),
124 help=("Django configuration module to use. One of " +
125 ', '.join(configs_to_use.keys())))
126
127 def work(self):
128 # Refresh code in shared-branches.
129 self.refresh_branch(code_root)
130 self.refresh_branch(configs_root)
131
132 # We update installations for all the configs we've got.
133 for config in self.arguments.configs:
134 self.update_installation(config, configs_to_use[config])
117135
118136
119if __name__ == '__main__':137if __name__ == '__main__':
120 parser = argparse.ArgumentParser(138 script = UpdateDeploymentScript(
139 'update-deployment',
121 description=(140 description=(
122 "Update staging deployment of lp:linaro-license-protection."))141 "Update staging deployment of lp:linaro-license-protection."))
123 parser.add_argument(142 script.run()
124 'configs', metavar='CONFIG', nargs='+', choices=configs_to_use.keys(),
125 help=("Django configuration module to use. One of " +
126 ', '.join(configs_to_use.keys())))
127 parser.add_argument("-v", "--verbose", action='count',
128 help=("Increase the output verbosity. "
129 "Can be used multiple times"))
130 args = parser.parse_args()
131
132 logging_level = logging.ERROR
133 if args.verbose == 0:
134 logging_level = logging.ERROR
135 elif args.verbose == 1:
136 logging_level = logging.INFO
137 elif args.verbose >= 2:
138 logging_level = logging.DEBUG
139
140 logger = logging.getLogger('update-staging')
141 logging.basicConfig(
142 format='%(asctime)s %(levelname)s: %(message)s',
143 level=logging_level)
144
145 # Refresh code in shared-branches.
146 refresh_branch(code_root)
147 refresh_branch(configs_root)
148
149 # We update installations for all the configs we've got.
150 for config in args.configs:
151 update_installation(config, configs_to_use[config])

Subscribers

People subscribed via source and target branches