From c63d6b41b27144d97271e6c43f335db422d53901 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 7 Mar 2025 14:42:05 +0200 Subject: [PATCH] ngclient: Create directories as needed Make sure ngclient creates metadata and artifact directories when needed. This was not useful when caller needed to populate the metadata dir with the initial root anyway, but now with boostrap argument it becomes a usability improvement. Signed-off-by: Jussi Kukkonen --- examples/client/client | 6 ------ tests/test_updater_top_level_update.py | 7 ++++--- tuf/ngclient/updater.py | 9 +++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/client/client b/examples/client/client index eeab472d3e..883fd52cba 100755 --- a/examples/client/client +++ b/examples/client/client @@ -38,9 +38,6 @@ def init_tofu(base_url: str) -> bool: metadata_dir = build_metadata_dir(base_url) - if not os.path.isdir(metadata_dir): - os.makedirs(metadata_dir) - response = urllib3.request("GET", f"{base_url}/metadata/1.root.json") if response.status != 200: print(f"Failed to download initial root {base_url}/metadata/1.root.json") @@ -81,9 +78,6 @@ def download(base_url: str, target: str) -> bool: print(f"Using trusted root in {metadata_dir}") - if not os.path.isdir(DOWNLOAD_DIR): - os.mkdir(DOWNLOAD_DIR) - try: # NOTE: initial root should be provided with ``bootstrap`` argument: # This examples uses unsafe Trust-On-First-Use initialization so it is diff --git a/tests/test_updater_top_level_update.py b/tests/test_updater_top_level_update.py index f4342f5f97..68a2a74eaf 100644 --- a/tests/test_updater_top_level_update.py +++ b/tests/test_updater_top_level_update.py @@ -12,6 +12,7 @@ import tempfile import unittest from datetime import timezone +from pathlib import Path from typing import TYPE_CHECKING from unittest.mock import MagicMock, call, patch @@ -57,8 +58,6 @@ def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() self.metadata_dir = os.path.join(self.temp_dir.name, "metadata") self.targets_dir = os.path.join(self.temp_dir.name, "targets") - os.mkdir(self.metadata_dir) - os.mkdir(self.targets_dir) self.sim = RepositorySimulator() @@ -134,7 +133,8 @@ def test_cached_root_missing_without_bootstrap(self) -> None: self._run_refresh(skip_bootstrap=True) # Metadata dir is empty - self.assertFalse(os.listdir(self.metadata_dir)) + with self.assertRaises(FileNotFoundError): + os.listdir(self.metadata_dir) def test_trusted_root_expired(self) -> None: # Create an expired root version @@ -166,6 +166,7 @@ def test_trusted_root_expired(self) -> None: def test_trusted_root_unsigned_without_bootstrap(self) -> None: # Cached root is not signed, bootstrap root is not used + Path(self.metadata_dir).mkdir(parents=True) root_path = os.path.join(self.metadata_dir, "root.json") md_root = Metadata.from_bytes(self.sim.signed_roots[0]) md_root.signatures.clear() diff --git a/tuf/ngclient/updater.py b/tuf/ngclient/updater.py index a40c1fca32..020f67a298 100644 --- a/tuf/ngclient/updater.py +++ b/tuf/ngclient/updater.py @@ -58,6 +58,7 @@ import os import shutil import tempfile +from pathlib import Path from typing import TYPE_CHECKING, cast from urllib import parse @@ -267,6 +268,7 @@ def download_target( if filepath is None: filepath = self._generate_target_file_path(targetinfo) + Path(filepath).parent.mkdir(exist_ok=True, parents=True) if target_base_url is None: if self._target_base_url is None: @@ -332,10 +334,9 @@ def _persist_root(self, version: int, data: bytes) -> None: The metadata is stored with version prefix (e.g. "root_history/1.root.json"). """ - rootdir = os.path.join(self._dir, "root_history") - with contextlib.suppress(FileExistsError): - os.mkdir(rootdir) - self._persist_file(os.path.join(rootdir, f"{version}.root.json"), data) + rootdir = Path(self._dir, "root_history") + rootdir.mkdir(exist_ok=True, parents=True) + self._persist_file(str(rootdir / f"{version}.root.json"), data) def _persist_file(self, filename: str, data: bytes) -> None: """Write a file to disk atomically to avoid data loss."""