Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
Add source-checksum option #619
Closed
tsimonq2
wants to merge 67 commits into
snapcore:master
from
tsimonq2:downloaded-files-checksum-bug-1585913
Commits
Show all changes
67 commits
Select commit
Hold shift + click to select a range
273c8fd
Add really broken start of a fix for bug 1585913
tsimonq2 01b3b31
Updated constructors thanks to a suggestion by sergiusens
tsimonq2 8eb9701
Fixed underindented lines
tsimonq2 e49d9a4
An attempt to improve logic
tsimonq2 ce1f4a4
Fixed the static tests
tsimonq2 5d250f6
Fixed a few unit test failures
tsimonq2 78f7fb3
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 f4434e7
Move pre_check_checksum and check_checksum to FileBase
tsimonq2 ccead19
[Trivial] Fixed static tests
tsimonq2 db5e67e
Merge branch 'downloaded-files-checksum-bug-1585913' of github.com:ts…
tsimonq2 d7ff355
Added forgotten declaration of source_checksum
tsimonq2 3055f77
Fixed a lot of unit tests (not all yet) thanks to some tips from kyrofa
tsimonq2 4965f62
Add unit tests for source-checksum, still failing
tsimonq2 823974b
[Trivial] fix static tests
tsimonq2 7dfdbb1
Remove incompatibility with source-checksum and zip files
tsimonq2 3d23d95
Changed the logic so that it only checks the checksum if source-check…
tsimonq2 4e55381
Fixed a few unit tests
tsimonq2 7aae732
Made some trivial changes
tsimonq2 c6fe61e
Fixed unit tests
tsimonq2 a19e129
Started working on some integration tests
tsimonq2 c9340c0
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 42a82cf
Added sha384 support
tsimonq2 449cb4c
Added SHA1 support
tsimonq2 637fb6b
Added SHA224 support
tsimonq2 65ec812
Improved logic fixing static tests
tsimonq2 03d43f1
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 32ee9c3
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 727ad1c
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 4a7019f
Merge branch 'downloaded-files-checksum-bug-1585913' of github.com:ts…
tsimonq2 6b1aede
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 2e68a4a
Add a zip checksum integration test
tsimonq2 e889d2a
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 a465960
Add unit tests for checksumming a tarball
tsimonq2 7aec169
Add zip unit test
tsimonq2 20c4f11
Fixed the logic behind the checksumming
tsimonq2 686001b
Add checksums for the zip files
tsimonq2 42fd2a2
Fixed the checksum mismatches
tsimonq2 f9fcddc
Add zip tests for double coverage
tsimonq2 4e01277
Add failure unit tests
tsimonq2 c23009f
Changed logic behind file checksums, changed checksums in integration…
tsimonq2 1b5be9b
Add half-working local file test
tsimonq2 149463e
Made the source checksum functions standalone
tsimonq2 56661b8
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 1c70283
Move checksum functions down
tsimonq2 744ebec
Fixed unit tests
tsimonq2 40b5e32
Fixed the integration tests
tsimonq2 a44a3a7
Added some more unit tests
tsimonq2 9fe67a5
Added an invalid checksum test
tsimonq2 86c8a7d
Fixed static tests
tsimonq2 3a5a154
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 509b9a9
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 3964acb
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 b1fb91e
Change if/else to dictionary
tsimonq2 2414d81
Merge branch 'downloaded-files-checksum-bug-1585913' of github.com:ts…
tsimonq2 078a30c
Fix failing tests
tsimonq2 be81b2d
Simplified checksum verification into one function
tsimonq2 9c1f20b
Change check_checksum() to verify_checksum()
tsimonq2 43090bf
Simplified test_checksum_of_tar() to test_checksum()
tsimonq2 d9d5f89
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 bfbce14
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 68ea3f0
Changed logic of the checksumming from a file, corrected unit tests t…
tsimonq2 bbb5707
Merge branch 'downloaded-files-checksum-bug-1585913' of github.com:ts…
tsimonq2 dd0f020
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 5bf0c42
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2 1ee3dc7
Fix some errors pointed out by sergiusens
tsimonq2 af40571
Make suggested changes to fix raising ChecksumDoesNotMatch
tsimonq2 b8f3590
Merge branch 'master' into downloaded-files-checksum-bug-1585913
tsimonq2
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1 @@ | ||
| +b40dd8099c3dd299b889d1981fb1e87dd058134cf38146615648753dffb7bead6eac0e9bacb53c553873406eae702632e432e70383b2a345ddc5768f6c7007e4 checksum.tar.gz |
Binary file not shown.
| @@ -0,0 +1 @@ | ||
| +c4f762b25393dafd1b3cd34c9bda82ecda6af552a46cc17d0c3abd9f19beb3abed2bd9948559aec8a81aa9d242cc112c08ef5ba80d4456ec0517ca517b8d3253 simple.zip |
| @@ -33,6 +33,12 @@ | ||
| control system or compression algorithm. The source-type key can tell | ||
| snapcraft exactly how to treat that content. | ||
| + - source-checksum: checksum-of-file | ||
| + | ||
| + Snapcraft will use either a file, URL, or raw checksum specified here to | ||
| + verify the integrity of the source. The source-type needs to be either tar | ||
| + or zip. | ||
| + | ||
| - source-branch: <branch-name> | ||
| Snapcraft will checkout a specific branch from the source tree. This | ||
| @@ -70,13 +76,14 @@ | ||
| import subprocess | ||
| import tempfile | ||
| import zipfile | ||
| +import hashlib | ||
| +import urllib | ||
| import glob | ||
| from snapcraft.internal import common | ||
| from snapcraft.internal.indicators import download_requests_stream | ||
| - | ||
| -logging.getLogger('urllib3').setLevel(logging.CRITICAL) | ||
| +logging.getLogger('urllib').setLevel(logging.CRITICAL) | ||
| class IncompatibleOptionsError(Exception): | ||
| @@ -85,11 +92,18 @@ def __init__(self, message): | ||
| self.message = message | ||
| +class ChecksumDoesNotMatch(Exception): | ||
| + | ||
| + def __init__(self, message): | ||
| + self.message = message | ||
| + | ||
| + | ||
| class Base: | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| self.source = source | ||
| + self.source_checksum = source_checksum | ||
| self.source_dir = source_dir | ||
| self.source_tag = source_tag | ||
| self.source_branch = source_branch | ||
| @@ -128,12 +142,16 @@ def download(self): | ||
| class Bazaar(Base): | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| - super().__init__(source, source_dir, source_tag, source_branch) | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| + super().__init__( | ||
| + source, source_dir, source_checksum, source_tag, source_branch) | ||
| if source_branch: | ||
| raise IncompatibleOptionsError( | ||
| 'can\'t specify a source-branch for a bzr source') | ||
| + elif source_checksum: | ||
| + raise IncompatibleOptionsError( | ||
| + 'can\'t specify a source-checksum for a bzr source') | ||
| def pull(self): | ||
| tag_opts = [] | ||
| @@ -152,13 +170,17 @@ def pull(self): | ||
| class Git(Base): | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| - super().__init__(source, source_dir, source_tag, source_branch) | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| + super().__init__( | ||
| + source, source_dir, source_checksum, source_tag, source_branch) | ||
| if source_tag and source_branch: | ||
| raise IncompatibleOptionsError( | ||
| 'can\'t specify both source-tag and source-branch for ' | ||
| 'a git source') | ||
| + elif source_checksum: | ||
| + raise IncompatibleOptionsError( | ||
| + 'can\'t specify source-checksum for a git source') | ||
| def pull(self): | ||
| if os.path.exists(os.path.join(self.source_dir, '.git')): | ||
| @@ -188,13 +210,17 @@ def pull(self): | ||
| class Mercurial(Base): | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| - super().__init__(source, source_dir, source_tag, source_branch) | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| + super().__init__( | ||
| + source, source_dir, source_checksum, source_tag, source_branch) | ||
| if source_tag and source_branch: | ||
| raise IncompatibleOptionsError( | ||
| 'can\'t specify both source-tag and source-branch for a ' | ||
| 'mercurial source') | ||
| + elif source_checksum: | ||
| + raise IncompatibleOptionsError( | ||
| + 'can\'t specify source-checksum for a mercurial source') | ||
| def pull(self): | ||
| if os.path.exists(os.path.join(self.source_dir, '.hg')): | ||
| @@ -215,9 +241,10 @@ def pull(self): | ||
| class Subversion(Base): | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| - super().__init__(source, source_dir, source_tag, source_branch) | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| + super().__init__( | ||
| + source, source_dir, source_checksum, source_tag, source_branch) | ||
| if source_tag: | ||
| if source_branch: | ||
| raise IncompatibleOptionsError( | ||
| @@ -229,6 +256,9 @@ def __init__(self, source, source_dir, source_tag=None, | ||
| elif source_branch: | ||
| raise IncompatibleOptionsError( | ||
| "Can't specify source-branch for a Subversion source") | ||
| + elif source_checksum: | ||
| + raise IncompatibleOptionsError( | ||
| + "Can't specify source-checksum for a Subversion source") | ||
| def pull(self): | ||
| if os.path.exists(os.path.join(self.source_dir, '.svn')): | ||
| @@ -247,9 +277,10 @@ def pull(self): | ||
| class Tar(FileBase): | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| - super().__init__(source, source_dir, source_tag, source_branch) | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| + super().__init__( | ||
| + source, source_dir, source_checksum, source_tag, source_branch) | ||
| if source_tag: | ||
| raise IncompatibleOptionsError( | ||
| 'can\'t specify a source-tag for a tar source') | ||
| @@ -261,6 +292,9 @@ def provision(self, dst, clean_target=True, keep_tarball=False): | ||
| # TODO add unit tests. | ||
| tarball = os.path.join(self.source_dir, os.path.basename(self.source)) | ||
| + if self.source_checksum: | ||
| + verify_checksum(self.source_checksum, tarball) | ||
| + | ||
| if clean_target: | ||
| tmp_tarball = tempfile.NamedTemporaryFile().name | ||
| shutil.move(tarball, tmp_tarball) | ||
| @@ -318,9 +352,10 @@ def _strip_prefix(self, common, member): | ||
| class Zip(FileBase): | ||
| - def __init__(self, source, source_dir, source_tag=None, | ||
| - source_branch=None): | ||
| - super().__init__(source, source_dir, source_tag, source_branch) | ||
| + def __init__(self, source, source_dir, source_checksum=None, | ||
| + source_tag=None, source_branch=None): | ||
| + super().__init__(source, source_dir, source_checksum, | ||
| + source_tag, source_branch) | ||
| if source_tag: | ||
| raise IncompatibleOptionsError( | ||
| 'can\'t specify a source-tag for a zip source') | ||
| @@ -331,6 +366,9 @@ def __init__(self, source, source_dir, source_tag=None, | ||
| def provision(self, dst, clean_target=True, keep_zip=False): | ||
| zip = os.path.join(self.source_dir, os.path.basename(self.source)) | ||
tsimonq2
Contributor
|
||
| + if self.source_checksum: | ||
| + verify_checksum(self.source_checksum, zip) | ||
| + | ||
| if clean_target: | ||
| tmp_zip = tempfile.NamedTemporaryFile().name | ||
| shutil.move(zip, tmp_zip) | ||
| @@ -377,12 +415,13 @@ def get(sourcedir, builddir, options): | ||
| :param options: source options. | ||
| """ | ||
| source_type = getattr(options, 'source_type', None) | ||
| + source_checksum = getattr(options, 'source_checksum', None) | ||
| source_tag = getattr(options, 'source_tag', None) | ||
| source_branch = getattr(options, 'source_branch', None) | ||
| handler_class = _get_source_handler(source_type, options.source) | ||
| - handler = handler_class(options.source, sourcedir, source_tag, | ||
| - source_branch) | ||
| + handler = handler_class(options.source, sourcedir, source_checksum, | ||
| + source_tag, source_branch) | ||
| handler.pull() | ||
| @@ -456,3 +495,52 @@ def _get_source_type_from_uri(source, ignore_errors=False): | ||
| raise ValueError('local source is not a directory') | ||
| return source_type | ||
| + | ||
| + | ||
| +def verify_checksum(source_checksum, checkfile): | ||
| + if source_checksum.startswith('http'): | ||
| + response = urllib.request.urlopen(source_checksum) | ||
| + data = response.read() | ||
| + source_checksum = data.decode('utf-8') | ||
| + if (' ' in source_checksum): | ||
| + source_checksum = source_checksum.split(' ', 1)[0] | ||
| + else: | ||
| + print('No file name detected in the checksum file, perhaps an ' | ||
| + 'invalid checksum file?') | ||
| + if os.path.isfile(source_checksum): | ||
| + filename = source_checksum | ||
| + try: | ||
| + filework = open(filename, 'r') | ||
| + source_checksum = filework.read() | ||
| + if (' ' in source_checksum): | ||
| + source_checksum = source_checksum.split(' ', 1)[0] | ||
| + else: | ||
| + print('No file name detected in the checksum file, perhaps an ' | ||
| + 'invalid checksum file?') | ||
| + finally: | ||
| + filework.close() | ||
| + | ||
| + _HASH_FUNCTIONS = { | ||
| + 32: hashlib.md5(), | ||
| + 40: hashlib.sha1(), | ||
| + 56: hashlib.sha224(), | ||
| + 64: hashlib.sha256(), | ||
| + 96: hashlib.sha384(), | ||
| + 128: hashlib.sha512() | ||
| + } | ||
| + | ||
| + try: | ||
| + checksum = _HASH_FUNCTIONS[len(source_checksum)] | ||
| + except KeyError: | ||
| + raise IncompatibleOptionsError('Invalid checksum format') | ||
| + | ||
| + with open(checkfile, 'rb') as f: | ||
| + for chunk in iter(lambda: f.read(4096), b''): | ||
| + checksum.update(chunk) | ||
| + | ||
| + checksum = checksum.hexdigest() | ||
| + | ||
| + if checksum != source_checksum: | ||
| + raise ChecksumDoesNotMatch( | ||
| + "the checksum ( {0} ) doesn't match the file ( {1} )".format( | ||
| + source_checksum, checksum)) | ||
Oops, something went wrong.
please use a different name than
zip,zipis a python keyword and it might get confusing.