Skip to content

Commit a417f67

Browse files
Merge pull request #321 from vladimir-v-diaz/address-pr#295-review
Address pr#295 review
2 parents 1bed3e0 + 05d0aca commit a417f67

File tree

4 files changed

+87
-72
lines changed

4 files changed

+87
-72
lines changed

tests/test_updater.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,18 +465,18 @@ def test_2__import_delegations(self):
465465

466466

467467

468-
def test_2__versioninfo_has_changed(self):
468+
def test_2__versioninfo_has_been_updated(self):
469469
# Verify that the method returns 'False' if a versioninfo was not changed.
470470
snapshot_filepath = os.path.join(self.client_metadata_current, 'snapshot.json')
471471
snapshot_signable = tuf.util.load_json_file(snapshot_filepath)
472472
root_versioninfo = snapshot_signable['signed']['meta']['root.json']
473473

474-
self.assertFalse(self.repository_updater._versioninfo_has_changed('root.json',
474+
self.assertFalse(self.repository_updater._versioninfo_has_been_updated('root.json',
475475
root_versioninfo))
476476

477477
# Verify that the method returns 'True' if Root's version number changes.
478478
root_versioninfo['version'] = 8
479-
self.assertTrue(self.repository_updater._versioninfo_has_changed('root.json',
479+
self.assertTrue(self.repository_updater._versioninfo_has_been_updated('root.json',
480480
root_versioninfo))
481481

482482

tuf/client/updater.py

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,17 +1555,33 @@ def _update_metadata_if_changed(self, metadata_role,
15551555
# Ensure the referenced metadata has been loaded. The 'root' role may be
15561556
# updated without having 'snapshot' available.
15571557
if referenced_metadata not in self.metadata['current']:
1558-
message = 'Cannot update ' + repr(metadata_role) + ' because ' \
1559-
+ referenced_metadata + ' is missing.'
1560-
raise tuf.RepositoryError(message)
1558+
raise tuf.RepositoryError('Cannot update ' + repr(metadata_role) +
1559+
' because ' + referenced_metadata + ' is missing.')
15611560

1562-
# The referenced metadata has been loaded. Extract the new
1563-
# versioninfo for 'metadata_role' from it.
1561+
# The referenced metadata has been loaded. Extract the new versioninfo for
1562+
# 'metadata_role' from it.
15641563
else:
1565-
message = repr(metadata_role) + ' referenced in ' +\
1566-
repr(referenced_metadata)+ '. ' + repr(metadata_role)+' may be updated.'
1567-
logger.debug(message)
1564+
logger.debug(repr(metadata_role) + ' referenced in ' +
1565+
repr(referenced_metadata)+ '. ' + repr(metadata_role) +
1566+
' may be updated.')
1567+
1568+
# Extract the versioninfo of the uncompressed version of 'metadata_role'.
1569+
expected_versioninfo = self.metadata['current'][referenced_metadata] \
1570+
['meta'] \
1571+
[uncompressed_metadata_filename]
1572+
1573+
# Simply return if the metadata for 'metadata_role' has not been updated,
1574+
# according to the uncompressed metadata provided by the referenced
1575+
# metadata. The metadata is considered updated if its version number is
1576+
# strictly greater than its currently trusted version number.
1577+
if not self._versioninfo_has_been_updated(uncompressed_metadata_filename,
1578+
expected_versioninfo):
1579+
logger.info(repr(uncompressed_metadata_filename) + ' up-to-date.')
1580+
1581+
return
15681582

1583+
logger.debug('Metadata ' + repr(uncompressed_metadata_filename) + ' has changed.')
1584+
15691585
# There might be a compressed version of 'snapshot.json' or Targets
15701586
# metadata available for download. Check the 'meta' field of
15711587
# 'referenced_metadata' to see if it is listed when 'metadata_role'
@@ -1581,38 +1597,22 @@ def _update_metadata_if_changed(self, metadata_role,
15811597
# decompressing a file that may be invalid or partially intact.
15821598
compression = None
15831599

1584-
# Extract the versioninfo of the uncompressed version of 'metadata_role'.
1585-
expected_versioninfo = self.metadata['current'][referenced_metadata] \
1586-
['meta'] \
1587-
[uncompressed_metadata_filename]
1588-
15891600
# Check for the availability of compressed versions of 'snapshot.json',
15901601
# 'targets.json', and delegated Targets (that also start with 'targets').
15911602
# For 'targets.json' and delegated metadata, 'referenced_metata'
15921603
# should always be 'snapshot'. 'snapshot.json' specifies all roles
15931604
# provided by a repository, including their version numbers.
15941605
if metadata_role == 'snapshot' or metadata_role.startswith('targets'):
1595-
gzip_metadata_filename = uncompressed_metadata_filename + '.gz'
15961606
if 'gzip' in self.metadata['current']['root']['compression_algorithms']:
15971607
compression = 'gzip'
1598-
1599-
logger.debug('Compressed version of ' + \
1600-
repr(uncompressed_metadata_filename) + ' is available at ' + \
1601-
repr(gzip_metadata_filename) + '.')
1602-
else:
1603-
logger.debug('Compressed version of ' + \
1604-
repr(uncompressed_metadata_filename) + ' not available.')
1605-
1606-
# Simply return if the file has not changed, according to the metadata
1607-
# about the uncompressed file provided by the referenced metadata.
1608-
if not self._versioninfo_has_changed(uncompressed_metadata_filename,
1609-
expected_versioninfo):
1610-
logger.info(repr(uncompressed_metadata_filename) + ' up-to-date.')
1608+
gzip_metadata_filename = uncompressed_metadata_filename + '.gz'
1609+
logger.debug('Compressed version of ' +
1610+
repr(uncompressed_metadata_filename) + ' is available at ' +
1611+
repr(gzip_metadata_filename) + '.')
16111612

1612-
return
1613-
1614-
logger.debug('Metadata ' + repr(uncompressed_metadata_filename) + \
1615-
' has changed.')
1613+
else:
1614+
logger.debug('Compressed version of ' +
1615+
repr(uncompressed_metadata_filename) + ' not available.')
16161616

16171617
# The file lengths of metadata are unknown, only their version numbers are
16181618
# known. Set an upper limit for the length of the downloaded file for each
@@ -1659,14 +1659,15 @@ def _update_metadata_if_changed(self, metadata_role,
16591659

16601660

16611661

1662-
def _versioninfo_has_changed(self, metadata_filename, new_versioninfo):
1662+
def _versioninfo_has_been_updated(self, metadata_filename, new_versioninfo):
16631663
"""
16641664
<Purpose>
16651665
Non-public method that determines whether the current versioninfo of
1666-
'metadata_filename' differs from 'new_versioninfo'. The 'new_versioninfo'
1667-
argument should be extracted from the latest copy of the metadata that
1668-
references 'metadata_filename'. Example: 'root.json' would be referenced
1669-
by 'snapshot.json'.
1666+
'metadata_filename' is less than 'new_versioninfo' (i.e., the version
1667+
number has been incremented). The 'new_versioninfo' argument should be
1668+
extracted from the latest copy of the metadata that references
1669+
'metadata_filename'. Example: 'root.json' would be referenced by
1670+
'snapshot.json'.
16701671
16711672
'new_versioninfo' should only be 'None' if this is for updating
16721673
'root.json' without having 'snapshot.json' available.
@@ -1689,11 +1690,11 @@ def _versioninfo_has_changed(self, metadata_filename, new_versioninfo):
16891690
None.
16901691
16911692
<Side Effects>
1692-
If there is no versioninfo currently loaded for 'metada_filename',
1693-
try to load it.
1693+
If there is no versioninfo currently loaded for 'metadata_filename', try
1694+
to load it.
16941695
16951696
<Returns>
1696-
Boolean. True if the versioninfo has changed, false otherwise.
1697+
Boolean. True if the versioninfo has changed, False otherwise.
16971698
"""
16981699

16991700
# If there is no versioninfo currently stored for 'metadata_filename',
@@ -1763,9 +1764,10 @@ def _update_versioninfo(self, metadata_filename):
17631764
self.metadata['current']['timestamp']['version']
17641765

17651766
# When updating snapshot.json, the client either (1) has a copy of
1766-
# snapshot.json, or (2) in the process of obtaining it by first downloading
1767-
# timestamp.json. Note: Clients may have only root.json and perform a
1768-
# refresh of top-level metadata to obtain the remaining roles.
1767+
# snapshot.json, or (2) is in the process of obtaining it by first
1768+
# downloading timestamp.json. Note: Clients are allowed to have only
1769+
# root.json initially, and perform a refresh of top-level metadata to
1770+
# obtain the remaining roles.
17691771
elif metadata_filename == 'snapshot.json':
17701772

17711773
# Verify the version number of the currently trusted snapshot.json in
@@ -2791,8 +2793,9 @@ def download_target(self, target, destination_directory):
27912793
<Purpose>
27922794
Download 'target' and verify it is trusted.
27932795
2794-
This will only store the file at 'destination_directory' if the downloaded
2795-
file matches the description of the file in the trusted metadata.
2796+
This will only store the file at 'destination_directory' if the
2797+
downloaded file matches the description of the file in the trusted
2798+
metadata.
27962799
27972800
<Arguments>
27982801
target:
@@ -2809,6 +2812,10 @@ def download_target(self, target, destination_directory):
28092812
tuf.NoWorkingMirrorError:
28102813
If a target could not be downloaded from any of the mirrors.
28112814
2815+
Although expected to be rare, there might be OSError exceptions (except
2816+
errno.EEXIST) raised when creating the destination directory (if it
2817+
doesn't exist).
2818+
28122819
<Side Effects>
28132820
A target file is saved to the local system.
28142821
@@ -2834,15 +2841,20 @@ def download_target(self, target, destination_directory):
28342841
target_file_object = self._get_target_file(target_filepath, trusted_length,
28352842
trusted_hashes)
28362843

2837-
# We acquired a target file object from a mirror. Move the file into
2838-
# place (i.e., locally to 'destination_directory'). Note: join() discards
2839-
# 'destination_directory' if 'target_path' contains a leading path separator
2840-
# (i.e., is treated as an absolute path).
2844+
# We acquired a target file object from a mirror. Move the file into place
2845+
# (i.e., locally to 'destination_directory'). Note: join() discards
2846+
# 'destination_directory' if 'target_path' contains a leading path
2847+
# separator (i.e., is treated as an absolute path).
28412848
destination = os.path.join(destination_directory,
28422849
target_filepath.lstrip(os.sep))
28432850
destination = os.path.abspath(destination)
28442851
target_dirpath = os.path.dirname(destination)
2845-
2852+
2853+
# When attempting to create the leaf directory of 'target_dirpath', ignore
2854+
# any exceptions raised if the root directory already exists. All other
2855+
# exceptions potentially thrown by os.makedirs() are re-raised.
2856+
# Note: os.makedirs can raise OSError if the leaf directory already exists
2857+
# or cannot be created.
28462858
try:
28472859
os.makedirs(target_dirpath)
28482860

tuf/formats.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,13 +1037,11 @@ def make_versioninfo(version_number):
10371037
in Snapshot metadata.
10381038
10391039
<Exceptions>
1040-
tuf.FormatError, if the 'VERSIONINFO_SCHEMA' to be returned
1041-
does not have the correct format.
1040+
tuf.FormatError, if the dict to be returned does not have the correct
1041+
format (i.e., VERSIONINFO_SCHEMA).
10421042
10431043
<Side Effects>
1044-
If any of the arguments are incorrectly formatted, the dict
1045-
returned will be checked for formatting errors, and if found,
1046-
will raise a 'tuf.FormatError' exception.
1044+
None.
10471045
10481046
<Returns>
10491047
A dictionary conformant to 'VERSIONINFO_SCHEMA', containing the version
@@ -1053,9 +1051,14 @@ def make_versioninfo(version_number):
10531051
versioninfo = {'version' : version_number}
10541052

10551053
# Raise 'tuf.FormatError' if 'versioninfo' is improperly formatted.
1056-
VERSIONINFO_SCHEMA.check_match(versioninfo)
1057-
1058-
return versioninfo
1054+
try:
1055+
VERSIONINFO_SCHEMA.check_match(versioninfo)
1056+
1057+
except:
1058+
raise
1059+
1060+
else:
1061+
return versioninfo
10591062

10601063

10611064

tuf/repository_lib.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,9 @@ def _delete_obsolete_metadata(metadata_directory, snapshot_metadata,
451451
# metadata might co-exist if write() and
452452
# write(consistent_snapshot=True) are mixed, so ensure only
453453
# '<version_number>.filename' metadata is stripped.
454-
embeded_version_number = None
454+
embedded_version_number = None
455455
if metadata_name not in snapshot_metadata['meta']:
456-
metadata_name, embeded_version_number = \
456+
metadata_name, embedded_version_number = \
457457
_strip_consistent_snapshot_version_number(metadata_name, consistent_snapshot)
458458

459459
# Strip filename extensions. The role database does not include the
@@ -474,10 +474,10 @@ def _delete_obsolete_metadata(metadata_directory, snapshot_metadata,
474474
# file extension of roles. TODO: Should we leave it up to integrators
475475
# to remove outdated consistent snapshots?
476476
"""
477-
if consistent_snapshot and embeded_version_number is not None:
477+
if consistent_snapshot and embedded_version_number is not None:
478478
file_hashes = list(snapshot_metadata['meta'][metadata_name_extension] \
479479
['hashes'].values())
480-
if embeded_digest not in file_hashes:
480+
if embedded_digest not in file_hashes:
481481
logger.info('Removing outdated metadata: ' + repr(metadata_path))
482482
os.remove(metadata_path)
483483
"""
@@ -508,24 +508,24 @@ def _strip_consistent_snapshot_version_number(metadata_filename,
508508
Strip from 'metadata_filename' any version data (in the expected
509509
'{dirname}/version_number.filename' format) that it may contain, and return
510510
the stripped filename and its version number as a tuple.
511+
'consistent_snapshot' is a boolean indicating if 'metadata_filename' contains
512+
prepended version number.
511513
"""
512514

513-
embeded_version_number = ''
515+
embedded_version_number = ''
514516

515517
# Strip the version number if 'consistent_snapshot' is True.
516518
# Example: 'targets/unclaimed/10.django.json' -->
517519
# 'targets/unclaimed/django.json'
518520
if consistent_snapshot:
519521
dirname, basename = os.path.split(metadata_filename)
520-
embeded_version_number = basename[:basename.find('.')]
522+
embedded_version_number, basename = basename.split('.', 1)
523+
stripped_metadata_filename = os.path.join(dirname, basename)
521524

522-
# Ensure the version number, including the period, is stripped.
523-
basename = basename[basename.find('.') + 1:]
524-
525-
metadata_filename = os.path.join(dirname, basename)
525+
return stripped_metadata_filename, embedded_version_number
526526

527-
528-
return metadata_filename, embeded_version_number
527+
else:
528+
return metadata_filename, ''
529529

530530

531531

0 commit comments

Comments
 (0)