Skip to content

Commit

Permalink
Breaking: new API: consistent_snapshot optional
Browse files Browse the repository at this point in the history
NOTE: making consistent_snapshot optional requires using a default value
for the argument in __init__ in Root and thus consistent_snapshot should
be rearranged in the end.
Read more: #1394 (comment)

From chapter 7 in the spec (version 1.0.17)
"Finally, the root metadata should write the Boolean
"consistent_snapshot" attribute at the root level of its keys of
attributes.
If consistent snapshots are not written by the repository,
then the attribute may either be left unspecified or be set to the
False value. Otherwise, it must be set to the True value."

We want to make sure we support repositories
without consistent_snapshot set.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
  • Loading branch information
MVrachev committed May 17, 2021
1 parent 9a397b9 commit de2644f
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 6 deletions.
6 changes: 6 additions & 0 deletions tests/test_api.py
Expand Up @@ -425,6 +425,12 @@ def test_metadata_root(self):
with self.assertRaises(KeyError):
root.signed.remove_key('root', 'nosuchkey')

# Test serializing and deserializing without consistent_snapshot.
root_dict = root.to_dict()
del root_dict["signed"]["consistent_snapshot"]
root = Root.from_dict(copy.deepcopy(root_dict["signed"]))
self.assertEqual(root_dict["signed"], root.to_dict())

def test_delegated_role_class(self):
roles = [
{
Expand Down
13 changes: 7 additions & 6 deletions tuf/api/metadata.py
Expand Up @@ -503,8 +503,8 @@ class Root(Signed):
"""A container for the signed part of root metadata.
Attributes:
consistent_snapshot: A boolean indicating whether the repository
supports consistent snapshots.
consistent_snapshot: An optional boolean indicating whether the
repository supports consistent snapshots.
keys: A dictionary that contains a public key store used to verify
top level roles metadata signatures::
Expand Down Expand Up @@ -534,9 +534,9 @@ def __init__(
version: int,
spec_version: str,
expires: datetime,
consistent_snapshot: bool,
keys: Dict[str, Key],
roles: Dict[str, Role],
consistent_snapshot: Optional[bool] = None,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(version, spec_version, expires, unrecognized_fields)
Expand All @@ -548,7 +548,7 @@ def __init__(
def from_dict(cls, root_dict: Dict[str, Any]) -> "Root":
"""Creates Root object from its dict representation."""
common_args = cls._common_fields_from_dict(root_dict)
consistent_snapshot = root_dict.pop("consistent_snapshot")
consistent_snapshot = root_dict.pop("consistent_snapshot", None)
keys = root_dict.pop("keys")
roles = root_dict.pop("roles")

Expand All @@ -558,7 +558,7 @@ def from_dict(cls, root_dict: Dict[str, Any]) -> "Root":
roles[role_name] = Role.from_dict(role_dict)

# All fields left in the root_dict are unrecognized.
return cls(*common_args, consistent_snapshot, keys, roles, root_dict)
return cls(*common_args, keys, roles, consistent_snapshot, root_dict)

def to_dict(self) -> Dict[str, Any]:
"""Returns the dict representation of self."""
Expand All @@ -567,10 +567,11 @@ def to_dict(self) -> Dict[str, Any]:
roles = {}
for role_name, role in self.roles.items():
roles[role_name] = role.to_dict()
if self.consistent_snapshot is not None:
root_dict["consistent_snapshot"] = self.consistent_snapshot

root_dict.update(
{
"consistent_snapshot": self.consistent_snapshot,
"keys": keys,
"roles": roles,
}
Expand Down

0 comments on commit de2644f

Please sign in to comment.