Skip to content

Commit fd340ad

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 e0ccd60 commit fd340ad

File tree

2 files changed

+80
-20
lines changed

2 files changed

+80
-20
lines changed

tests/test_api.py

Lines changed: 4 additions & 6 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+
TargetInfo
4142
)
4243

4344
from securesystemslib.interface import (
@@ -324,15 +325,12 @@ def test_metadata_targets(self):
324325
"sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0"
325326
},
326327

327-
fileinfo = {
328-
'hashes': hashes,
329-
'length': 28
330-
}
328+
fileinfo = TargetInfo(length=28, hashes=hashes)
331329

332330
# Assert that data is not aleady equal
333331
self.assertNotEqual(targets.signed.targets[filename], fileinfo)
334332
# Update an already existing fileinfo
335-
targets.signed.update(filename, fileinfo)
333+
targets.signed.update(filename, fileinfo.to_dict())
336334
# Verify that data is updated
337335
self.assertEqual(targets.signed.targets[filename], fileinfo)
338336

tuf/api/metadata.py

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -574,22 +574,66 @@ def update(
574574
self.meta[metadata_fn] = MetadataInfo(version, length, hashes)
575575

576576

577+
class TargetInfo:
578+
"""A container with information about a particular target file.
579+
Instances of TargetInfo are used as values in a dictionary
580+
called "targets" in Targets.
581+
582+
Attributes:
583+
length: An integer indicating the length of the target file.
584+
hashes: A dictionary containing hash algorithms and the
585+
hashes resulted from applying them over the target file::
586+
587+
'hashes': {
588+
'<HASH ALGO 1>': '<TARGET FILE HASH 1>',
589+
'<HASH ALGO 2>': '<TARGET FILE HASH 2>',
590+
...
591+
}
592+
593+
custom: An optional dictionary which may include version numbers,
594+
dependencies, or any other data that the application wants
595+
to include to describe the target file::
596+
597+
'custom': {
598+
'type': 'metadata',
599+
'file_permissions': '0644',
600+
...
601+
} // optional
602+
603+
"""
604+
605+
def __init__(self, length: int, hashes: JsonDict,
606+
custom: Optional[JsonDict] = None) -> None:
607+
self.length = length
608+
self.hashes = hashes
609+
self.custom = custom
610+
611+
612+
def __eq__(self, other: 'TargetInfo') -> bool:
613+
"""Compare objects by their values instead of by their addresses."""
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+
577629
class Targets(Signed):
578630
"""A container for the signed part of targets metadata.
579631
580632
Attributes:
581633
targets: A dictionary that contains information about target files::
582634
583635
{
584-
'<TARGET FILE NAME>': {
585-
'length': <TARGET FILE SIZE>,
586-
'hashes': {
587-
'<HASH ALGO 1>': '<TARGET FILE HASH 1>',
588-
'<HASH ALGO 2>': '<TARGETS FILE HASH 2>',
589-
...
590-
},
591-
'custom': <CUSTOM OPAQUE DICT> // optional
592-
},
636+
'<TARGET FILE NAME>': <TargetInfo INSTANCE>,
593637
...
594638
}
595639
@@ -633,25 +677,43 @@ class Targets(Signed):
633677
# pylint: disable=too-many-arguments
634678
def __init__(
635679
self, _type: str, version: int, spec_version: str,
636-
expires: datetime, targets: JsonDict, delegations: JsonDict
637-
) -> None:
680+
expires: datetime, targets: Dict[str, TargetInfo],
681+
delegations: JsonDict) -> None:
638682
super().__init__(_type, version, spec_version, expires)
639-
# TODO: Add class for meta
640683
self.targets = targets
684+
685+
# TODO: Add Key and Role classes
641686
self.delegations = delegations
642687

643688

689+
@classmethod
690+
def from_dict(cls, signed_dict: JsonDict) -> 'Targets':
691+
"""Creates Targets object from its JSON/dict representation. """
692+
693+
# signed is passed by reference, make sure we are not changing it.
694+
changed_signed = signed_dict.copy()
695+
for target_path in signed_dict['targets'].keys():
696+
changed_signed['targets'][target_path] = TargetInfo(
697+
**signed_dict['targets'][target_path])
698+
699+
return super().from_dict(changed_signed)
700+
701+
644702
# Serialization.
645703
def to_dict(self) -> JsonDict:
646704
"""Returns the JSON-serializable dictionary representation of self. """
647705
json_dict = super().to_dict()
706+
target_dict = {}
707+
for target_path, target_file_obj in self.targets.items():
708+
target_dict[target_path] = target_file_obj.to_dict()
709+
648710
json_dict.update({
649-
'targets': self.targets,
711+
'targets': target_dict,
650712
'delegations': self.delegations,
651713
})
652714
return json_dict
653715

654716
# Modification.
655717
def update(self, filename: str, fileinfo: JsonDict) -> None:
656718
"""Assigns passed target file info to meta dict. """
657-
self.targets[filename] = fileinfo
719+
self.targets[filename] = TargetInfo(**fileinfo)

0 commit comments

Comments
 (0)