Skip to content

Commit 860b8c9

Browse files
committed
New API: add TargetFile class
Add a new TargetFile class to tuf.api.metadata module make Targets class to use it. This class will contain information about the "targets" field from targets.json Also, update the tests for that change. Signed-off-by: Martin Vrachev <[email protected]>
1 parent da278ca commit 860b8c9

File tree

2 files changed

+79
-15
lines changed

2 files changed

+79
-15
lines changed

tests/test_api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def setUpModule():
3737
Root,
3838
Snapshot,
3939
Timestamp,
40-
Targets
40+
Targets,
41+
TargetFile
4142
)
4243

4344
from securesystemslib.interface import (
@@ -333,8 +334,9 @@ def test_metadata_targets(self):
333334
self.assertNotEqual(targets.signed.targets[filename], fileinfo)
334335
# Update an already existing fileinfo
335336
targets.signed.update(filename, fileinfo)
337+
expected_target_file = TargetFile(length=28, hashes=hashes)
336338
# Verify that data is updated
337-
self.assertEqual(targets.signed.targets[filename], fileinfo)
339+
self.assertEqual(targets.signed.targets[filename], expected_target_file)
338340

339341
# Run unit test.
340342
if __name__ == '__main__':

tuf/api/metadata.py

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -566,22 +566,74 @@ def update(
566566
self.meta[metadata_fn] = MetaFile(version, length, hashes)
567567

568568

569+
class TargetFile:
570+
"""A container with information about a particilur target file.
571+
Instances of TargetFile are used as values in a dictionary
572+
called "targets" in Targets.
573+
574+
targets: A dictionary that contains information about target files::
575+
{
576+
'target_file_path_1': <TargetFile INSTANCE_1>,
577+
'target_file_path_2': <TargetFile INSTANCE_2>,
578+
...
579+
}
580+
Attributes:
581+
length: An integer indicating the length of the target file.
582+
hashes: An dictionary containing hash algorithms and the
583+
hashes resulted from applying them over the target file::
584+
'hashes': {
585+
'<HASH ALGO 1>': '<TARGET FILE HASH 1>',
586+
'<HASH ALGO 2>': '<TARGET FILE HASH 2>',
587+
...
588+
}
589+
custom: An optional dictionary which may include version numbers,
590+
dependencies, or any other data that the application wants
591+
to include to describe the target file::
592+
'custom': {
593+
'type': 'metadata',
594+
'file_permissions': '0644',
595+
...
596+
} // optional
597+
598+
"""
599+
600+
def __init__(self, length: int, hashes: JsonDict,
601+
custom: Optional[JsonDict] = None) -> None:
602+
self.length = length
603+
self.hashes = hashes
604+
self.custom = custom
605+
606+
607+
def __eq__(self, other: Any) -> bool:
608+
"""Used to compare objects by their values instead of by their
609+
addresses."""
610+
if not isinstance(other, TargetFile):
611+
# don't attempt to compare against unrelated types
612+
return NotImplemented
613+
614+
return self.length == other.length and \
615+
self.hashes == other.hashes and \
616+
self.custom == other.custom
617+
618+
619+
def to_dict(self) -> JsonDict:
620+
"""Returns the JSON-serializable dictionary representation of self. """
621+
json_dict = {'length': self.length, 'hashes': self.hashes}
622+
623+
if self.custom is not None:
624+
json_dict['custom'] = self.custom
625+
626+
return json_dict
627+
628+
569629
class Targets(Signed):
570630
"""A container for the signed part of targets metadata.
571631
572632
Attributes:
573633
targets: A dictionary that contains information about target files::
574634
575635
{
576-
'<TARGET FILE NAME>': {
577-
'length': <TARGET FILE SIZE>,
578-
'hashes': {
579-
'<HASH ALGO 1>': '<TARGET FILE HASH 1>',
580-
'<HASH ALGO 2>': '<TARGETS FILE HASH 2>',
581-
...
582-
},
583-
'custom': <CUSTOM OPAQUE DICT> // optional
584-
},
636+
'<TARGET FILE NAME>': <TargetFile INSTANCE>,
585637
...
586638
}
587639
@@ -628,22 +680,32 @@ def __init__(
628680
expires: datetime, targets: JsonDict, delegations: JsonDict
629681
) -> None:
630682
super().__init__(_type, version, spec_version, expires)
631-
# TODO: Add class for meta
632-
self.targets = targets
683+
684+
self.targets = {}
685+
for target_path, target_dict in targets.items():
686+
self.targets[target_path] = TargetFile(target_dict['length'],
687+
target_dict['hashes'], target_dict.get('custom'))
688+
689+
# TO DO: Add Key and Role classes
633690
self.delegations = delegations
634691

635692

636693
# Serialization.
637694
def to_dict(self) -> JsonDict:
638695
"""Returns the JSON-serializable dictionary representation of self. """
639696
json_dict = super().to_dict()
697+
target_dict = {}
698+
for target_path, target_file_obj in self.targets.items():
699+
target_dict[target_path] = target_file_obj.to_dict()
700+
640701
json_dict.update({
641-
'targets': self.targets,
702+
'targets': target_dict,
642703
'delegations': self.delegations,
643704
})
644705
return json_dict
645706

646707
# Modification.
647708
def update(self, filename: str, fileinfo: JsonDict) -> None:
648709
"""Assigns passed target file info to meta dict. """
649-
self.targets[filename] = fileinfo
710+
self.targets[filename] = TargetFile(fileinfo['length'],
711+
fileinfo['hashes'], fileinfo.get('custom'))

0 commit comments

Comments
 (0)