Merge lp:~danilo/linaro-license-protection/staging-publish into lp:~linaro-automation/linaro-license-protection/trunk

Proposed by Данило Шеган
Status: Merged
Approved by: Stevan Radaković
Approved revision: 114
Merged at revision: 110
Proposed branch: lp:~danilo/linaro-license-protection/staging-publish
Merge into: lp:~linaro-automation/linaro-license-protection/trunk
Diff against target: 392 lines (+228/-61)
2 files modified
scripts/publish_to_snapshots.py (+90/-27)
tests/test_publish_to_snapshots.py (+138/-34)
To merge this branch: bzr merge lp:~danilo/linaro-license-protection/staging-publish
Reviewer Review Type Date Requested Status
Stevan Radaković code Approve
Review via email: mp+120861@code.launchpad.net

Description of the change

This adds an option to do publishing to staging as well, which includes
a trivial sanitization: the file is truncated and the base of the filename
is written as the contents instead.

Some side-refactoring, and mostly all things are tested.

To post a comment you must log in.
115. By Данило Шеган

Lint fixes.

Revision history for this message
Stevan Radaković (stevanr) wrote :

Looks good.
As I've mentioned, we should probably increase the list of accepted files but we can do it 'nother time.
Approve +1.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'scripts/publish_to_snapshots.py'
2--- scripts/publish_to_snapshots.py 2012-08-03 10:38:06 +0000
3+++ scripts/publish_to_snapshots.py 2012-08-22 19:44:18 +0000
4@@ -1,26 +1,12 @@
5 #!/usr/bin/env python
6 # Move artifacts from a temporary location to a permanent location on s.l.o
7
8+import argparse
9+import fnmatch
10 import os
11+import os.path
12+import shutil
13 import sys
14-import shutil
15-import argparse
16-
17-parser = argparse.ArgumentParser()
18-parser.add_argument("-t", "--job-type", dest="job_type",
19- help="Specify the job type (Ex: android/kernel-hwpack)")
20-parser.add_argument(
21- "-j", "--job-name", dest="job_name",
22- help=("Specify the job name which resulted the archive to "
23- "be stored. Ex: ${JOB_NAME} should be specified for "
24- "android/ubuntu-{hwpacks,images,sysroots}/binaries and for"
25- "kernel-hwpack ${KERNEL_JOB_NAME}"))
26-parser.add_argument(
27- "-n", "--build-num", dest="build_num", type=int,
28- help=("Specify the job build number for android/"
29- "ubuntu-{hwpacks,images,sysroots}/binaries"))
30-parser.add_argument("-m", "--manifest", dest="manifest", action='store_true',
31- help="Optional parameter to generate MANIFEST file")
32
33 uploads_path = '/srv/snapshots.linaro.org/uploads/'
34 target_path = '/srv/snapshots.linaro.org/www/'
35@@ -39,20 +25,87 @@
36 ]
37
38
39+def setup_parser():
40+ """Set up the argument parser for publish_to_snapshots script."""
41+ parser = argparse.ArgumentParser()
42+ parser.add_argument(
43+ "-s", "--staging", dest="staging", default=False,
44+ action='store_true',
45+ help=("Perform sanitization on the file to not expose any"
46+ "potentially sensitive data. Used for staging deployment."))
47+ parser.add_argument(
48+ "-t", "--job-type", dest="job_type",
49+ help="Specify the job type (Ex: android/kernel-hwpack)")
50+ parser.add_argument(
51+ "-j", "--job-name", dest="job_name",
52+ help=("Specify the job name which resulted the archive to "
53+ "be stored. Ex: ${JOB_NAME} should be specified for "
54+ "android/ubuntu-{hwpacks,images,sysroots}/binaries and for"
55+ "kernel-hwpack ${KERNEL_JOB_NAME}"))
56+ parser.add_argument(
57+ "-n", "--build-num", dest="build_num", type=int,
58+ help=("Specify the job build number for android/"
59+ "ubuntu-{hwpacks,images,sysroots}/binaries"))
60+ parser.add_argument(
61+ "-m", "--manifest", dest="manifest",
62+ action='store_true',
63+ help="Optional parameter to generate MANIFEST file")
64+ return parser
65+
66+
67+class PublisherArgumentException(Exception):
68+ """There was a problem with one of the publisher arguments."""
69+ pass
70+
71+
72 class SnapshotsPublisher(object):
73
74+ # Files that need no sanitization even when publishing to staging.
75+ STAGING_ACCEPTED_FILES = [
76+ 'BUILD-INFO.txt',
77+ 'EULA.txt',
78+ 'OPEN-EULA.txt',
79+ '*.EULA.txt.*',
80+ ]
81+
82+ def __init__(self, argument_parser=None):
83+ """Allow moving files around for publishing on snapshots.l.o."""
84+ self.argument_parser = argument_parser
85+
86+ @classmethod
87+ def is_accepted_for_staging(cls, filename):
88+ """Is filename is in a list of globs in STAGING_ACCEPTED_FILES?"""
89+ filename = os.path.basename(filename)
90+ for accepted_names in cls.STAGING_ACCEPTED_FILES:
91+ if fnmatch.fnmatch(filename, accepted_names):
92+ return True
93+ return False
94+
95+ @classmethod
96+ def sanitize_file(cls, file_path):
97+ """This truncates the file and fills it with its own filename."""
98+ assert not cls.is_accepted_for_staging(file_path)
99+ base_file_name = os.path.basename(file_path)
100+ protected_file = open(file_path, "w")
101+ # Nice property of this is that we are also saving on disk space
102+ # needed.
103+ protected_file.truncate()
104+ # To help distinguish files more easily when they are downloaded,
105+ # we write out the base file name as the contents.
106+ protected_file.write(base_file_name)
107+ protected_file.close()
108+
109 def validate_args(self, args):
110 # Validate that all the required information
111 # is passed on the command line
112 if (args.job_type == None or args.job_name == None or
113 args.build_num == None):
114- parser.error(
115+ raise PublisherArgumentException(
116 "\nYou must specify job-type, job-name and build-num")
117- return FAIL
118
119 if (args.job_type not in acceptable_job_types):
120- parser.error("Invalid job type")
121- return FAIL
122+ raise PublisherArgumentException("Invalid job type")
123+ return True
124
125 def jobname_to_target_subdir(self, args, jobname):
126 ret_val = None
127@@ -202,7 +255,7 @@
128 os.chdir(orig_dir)
129 return FAIL
130
131- def move_dir_content(self, src_dir, dest_dir):
132+ def move_dir_content(self, src_dir, dest_dir, sanitize=False):
133 filelist = os.listdir(src_dir)
134 try:
135 for file in filelist:
136@@ -214,6 +267,11 @@
137 continue
138 else:
139 os.remove(dest)
140+ if sanitize and not self.is_accepted_for_staging(src):
141+ # Perform the sanitization before moving the file
142+ # into place.
143+ print "Sanitizing contents of '%s'." % src
144+ self.sanitize_file(src)
145 print "Moving the src '", src, "'to dest'", dest, "'"
146 shutil.move(src, dest)
147 except shutil.Error:
148@@ -231,7 +289,8 @@
149 if not os.path.isdir(target_dir_path):
150 raise OSError
151
152- self.move_dir_content(build_dir_path, target_dir_path)
153+ self.move_dir_content(build_dir_path, target_dir_path,
154+ sanitize=args.staging)
155
156 if (args.job_type == "android" or
157 args.job_type == "ubuntu-hwpacks" or
158@@ -265,9 +324,13 @@
159
160
161 def main():
162- publisher = SnapshotsPublisher()
163- args = parser.parse_args()
164- publisher.validate_args(args)
165+ argument_parser = setup_parser()
166+ publisher = SnapshotsPublisher(argument_parser)
167+ args = argument_parser.parse_args()
168+ try:
169+ publisher.validate_args(args)
170+ except PublisherArgumentException as exception:
171+ argument_parser.error(exception.message)
172 try:
173 build_dir_path, target_dir_path = publisher.validate_paths(
174 args, uploads_path, target_path)
175
176=== modified file 'tests/test_publish_to_snapshots.py'
177--- tests/test_publish_to_snapshots.py 2012-08-03 10:38:06 +0000
178+++ tests/test_publish_to_snapshots.py 2012-08-22 19:44:18 +0000
179@@ -4,10 +4,13 @@
180 import sys
181 import shutil
182 import tempfile
183-import argparse
184 from StringIO import StringIO
185 from testtools import TestCase
186-from scripts.publish_to_snapshots import SnapshotsPublisher
187+from scripts.publish_to_snapshots import (
188+ PublisherArgumentException,
189+ SnapshotsPublisher,
190+ setup_parser,
191+ )
192
193
194 class TestSnapshotsPublisher(TestCase):
195@@ -18,13 +21,8 @@
196 orig_dir = os.getcwd()
197
198 def setUp(self):
199- self.parser = argparse.ArgumentParser()
200- self.parser.add_argument("-t", "--job-type", dest="job_type")
201- self.parser.add_argument("-j", "--job-name", dest="job_name")
202- self.parser.add_argument("-n", "--build-num", dest="build_num",
203- type=int)
204- self.parser.add_argument("-m", "--manifest", dest="manifest",
205- action='store_true')
206+ self.parser = setup_parser()
207+
208 if not os.path.isdir(self.uploads_path):
209 os.mkdir(self.uploads_path)
210
211@@ -72,21 +70,18 @@
212 ['-t', 'binaries', '-j', 'dummy_job_name', '-n', '1'])
213 self.publisher.validate_args(param)
214
215+ # Staging parameter is accepted as well.
216+ param = self.parser.parse_args(
217+ ['--staging', '-t', 'binaries', '-j', 'dummy_job_name', '-n', '1'])
218+ self.publisher.validate_args(param)
219+
220 def test_validate_args_invalid_job_type(self):
221- orig_stderr = sys.stderr
222- stderr = sys.stderr = StringIO()
223 self.publisher = SnapshotsPublisher()
224 param = self.parser.parse_args(
225 ['-t', 'invalid_job_type', '-j', 'dummy_job_name', '-n', '1'])
226- try:
227- self.publisher.validate_args(param)
228- except SystemExit, err:
229- self.assertEqual(err.code, 2, "Expected result")
230- finally:
231- sys.stderr = orig_stderr
232-
233- stderr.seek(0)
234- self.assertIn("Invalid job type", stderr.read())
235+ self.assertRaisesRegexp(
236+ PublisherArgumentException, "Invalid job type",
237+ self.publisher.validate_args, param)
238
239 def test_validate_args_run_invalid_argument(self):
240 orig_stderr = sys.stderr
241@@ -120,21 +115,13 @@
242 stderr.read())
243
244 def test_validate_args_run_none_values(self):
245- orig_stderr = sys.stderr
246- stderr = sys.stderr = StringIO()
247 self.publisher = SnapshotsPublisher()
248- try:
249- param = self.parser.parse_args(
250- ['-t', None, '-j', None, '-n', 0])
251- self.publisher.validate_args(param)
252- except SystemExit, err:
253- self.assertEqual(err.code, 2, "None values are not acceptable")
254- finally:
255- sys.stderr = orig_stderr
256-
257- stderr.seek(0)
258- self.assertIn("You must specify job-type, job-name and build-num",
259- stderr.read())
260+ param = self.parser.parse_args(
261+ ['-t', None, '-j', None, '-n', 0])
262+ self.assertRaisesRegexp(
263+ PublisherArgumentException,
264+ "You must specify job-type, job-name and build-num",
265+ self.publisher.validate_args, param)
266
267 def test_validate_paths_invalid_uploads_path(self):
268 orig_stdout = sys.stdout
269@@ -176,6 +163,123 @@
270 stdout.seek(0)
271 self.assertIn("Missing target path", stdout.read())
272
273+ def test_is_accepted_for_staging_EULA_txt(self):
274+ self.assertTrue(
275+ SnapshotsPublisher.is_accepted_for_staging("EULA.txt"))
276+ self.assertTrue(
277+ SnapshotsPublisher.is_accepted_for_staging("/path/to/EULA.txt"))
278+ # Full filename should be EULA.txt and nothing should be added to it.
279+ self.assertFalse(
280+ SnapshotsPublisher.is_accepted_for_staging(
281+ "/path/to/EULA.txt.something"))
282+
283+ def test_is_accepted_for_staging_OPEN_EULA_txt(self):
284+ self.assertTrue(
285+ SnapshotsPublisher.is_accepted_for_staging(
286+ "OPEN-EULA.txt"))
287+ self.assertTrue(
288+ SnapshotsPublisher.is_accepted_for_staging(
289+ "/path/to/OPEN-EULA.txt"))
290+
291+ def test_is_accepted_for_staging_per_file_EULA(self):
292+ self.assertTrue(
293+ SnapshotsPublisher.is_accepted_for_staging(
294+ "something.tar.gz.EULA.txt.ste"))
295+ self.assertTrue(
296+ SnapshotsPublisher.is_accepted_for_staging(
297+ "/path/to/something.tar.gz.EULA.txt.ste"))
298+ # We must have a "theme" for per-file license files in the
299+ # EULA-model.
300+ self.assertFalse(
301+ SnapshotsPublisher.is_accepted_for_staging(
302+ "something.tar.gz.EULA.txt"))
303+
304+ def test_is_accepted_for_staging_build_info(self):
305+ self.assertTrue(
306+ SnapshotsPublisher.is_accepted_for_staging(
307+ "BUILD-INFO.txt"))
308+ self.assertTrue(
309+ SnapshotsPublisher.is_accepted_for_staging(
310+ "/path/to/BUILD-INFO.txt"))
311+
312+ def test_sanitize_file_assert_on_accepted_files(self):
313+ # Since sanitize_file explicitely sanitizes a file,
314+ # one needs to ensure outside the function that it's
315+ # not being called on one of accepted file names.
316+ filename = '/path/to/EULA.txt'
317+ self.assertTrue(SnapshotsPublisher.is_accepted_for_staging(filename))
318+ self.assertRaises(
319+ AssertionError, SnapshotsPublisher.sanitize_file, filename)
320+
321+ def make_temporary_file(self, data, root=None):
322+ """Creates a temporary file and fills it with data.
323+
324+ Returns the full file path of the new temporary file.
325+ """
326+ tmp_file_handle, tmp_filename = tempfile.mkstemp(dir=root)
327+ tmp_file = os.fdopen(tmp_file_handle, "w")
328+ tmp_file.write(data)
329+ tmp_file.close()
330+ return tmp_filename
331+
332+ def test_sanitize_file_loses_original_contents(self):
333+ original_text = "Some garbage" * 100
334+ protected_filename = self.make_temporary_file(original_text)
335+
336+ SnapshotsPublisher.sanitize_file(protected_filename)
337+ new_contents = open(protected_filename).read()
338+ self.assertNotEqual(original_text, new_contents)
339+ # Clean-up.
340+ os.remove(protected_filename)
341+
342+ def test_sanitize_file_basename_as_contents(self):
343+ # It's useful to have an easy way to distinguish files by the content
344+ # as well, so we put the basename (filename without a path) in.
345+ protected_filename = self.make_temporary_file("Some contents")
346+ SnapshotsPublisher.sanitize_file(protected_filename)
347+ new_contents = open(protected_filename).read()
348+ # Incidentally, the contents are actually the file base name.
349+ self.assertEqual(os.path.basename(protected_filename), new_contents)
350+ # Clean-up.
351+ os.remove(protected_filename)
352+
353+ def test_move_dir_content_sanitize(self):
354+ # A directory containing a file to sanitize is moved with the
355+ # data being sanitized first.
356+ source_dir = tempfile.mkdtemp()
357+ destination_dir = tempfile.mkdtemp()
358+ protected_content = "Something secret" * 10
359+ protected_file = self.make_temporary_file(protected_content,
360+ root=source_dir)
361+ publisher = SnapshotsPublisher()
362+ publisher.move_dir_content(source_dir, destination_dir, sanitize=True)
363+ resulting_file = os.path.join(destination_dir,
364+ os.path.basename(protected_file))
365+ self.assertNotEqual(protected_content,
366+ open(resulting_file).read())
367+ shutil.rmtree(source_dir)
368+ shutil.rmtree(destination_dir)
369+
370+ def test_move_dir_content_no_sanitize(self):
371+ # A directory containing one of accepted files has it moved
372+ # without changes even with sanitization option on.
373+ source_dir = tempfile.mkdtemp()
374+ destination_dir = tempfile.mkdtemp()
375+ allowed_content = "Something public" * 10
376+ allowed_file_name = os.path.join(source_dir, "EULA.txt")
377+ allowed_file = open(allowed_file_name, "w")
378+ allowed_file.write(allowed_content)
379+ allowed_file.close()
380+
381+ publisher = SnapshotsPublisher()
382+ publisher.move_dir_content(source_dir, destination_dir, sanitize=True)
383+ resulting_file = os.path.join(destination_dir,
384+ os.path.basename(allowed_file_name))
385+ self.assertEqual(allowed_content,
386+ open(resulting_file).read())
387+ shutil.rmtree(source_dir)
388+ shutil.rmtree(destination_dir)
389+
390 def test_move_artifacts_kernel_successful_move(self):
391 orig_stdout = sys.stdout
392 stdout = sys.stdout = StringIO()

Subscribers

People subscribed via source and target branches