-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add a new crate finder - CLI changes to support it - Drive-by: Switch vscode formatter to Ruff The cli.py got attacked by Ruff which normalized all the quotes. Deal with it.
- Loading branch information
Showing
6 changed files
with
208 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ class SourceType(enum.Enum): | |
java = "java" | ||
go = "go" | ||
nuget = "nuget" | ||
crate = "crate" | ||
|
||
|
||
@enum.unique | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates | ||
# All rights reserved. | ||
|
||
|
||
from soufi import exceptions, finder | ||
|
||
DEFAULT_INDEX = "https://index.crates.io/" | ||
|
||
|
||
class CrateFinder(finder.SourceFinder): | ||
"""Find Rust Crates. | ||
Traverses the supplied index, defaulting to the one at index.crates.io. | ||
:param index: optional index server; defaults to | ||
https://index.crates.io/ | ||
""" | ||
|
||
distro = finder.SourceType.crate.value | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.index = kwargs.pop("index", DEFAULT_INDEX) | ||
if self.index[-1] != "/": | ||
self.index += "/" | ||
super().__init__(*args, **kwargs) | ||
|
||
def _find(self): | ||
source_url = self.get_source_url() | ||
return CrateDiscoveredSource([source_url], timeout=self.timeout) | ||
|
||
def get_source_url(self): | ||
"""Examine the index to find the source URL for the package. | ||
This is simply a matter of using the index's API to do a package query, | ||
and returning a computed URL for any exact match. | ||
Note: the Create index server's API does not do exact matches itself, | ||
so we need to iterate the results. | ||
""" | ||
dl = self.get_index_dl() | ||
# This is a bit of a short cut and is assuming a particular file format | ||
# for each crate. Nominally this is usually the case but we might | ||
# need to revist. | ||
url = f"{dl}/{self.name}/{self.name}-{self.version}.crate" | ||
if self.test_url(url): | ||
return url | ||
raise exceptions.SourceNotFound | ||
|
||
def get_index_dl(self): | ||
"""Return the 'dl' value from the config.json at the index root.""" | ||
# 'dl' is the URL prefix from which all downloads are made. | ||
config = self.get_url(f"{self.index}config.json").json() | ||
try: | ||
return config["dl"] | ||
except KeyError: | ||
raise exceptions.DownloadError( | ||
"Index is corrupt: No 'dl' key in index config.json" | ||
) | ||
|
||
|
||
class CrateDiscoveredSource(finder.DiscoveredSource): | ||
"""A discovered Rust Crate package.""" | ||
|
||
make_archive = finder.DiscoveredSource.remote_url_is_archive | ||
archive_extension = "" # .crate is part of the URL | ||
|
||
def populate_archive(self, *args, **kwargs): # pragma: no cover | ||
# Required by the base class but Crates are already tarballs so | ||
# nothing to do. | ||
pass | ||
|
||
def __repr__(self): | ||
return self.urls[0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates | ||
# All rights reserved. | ||
|
||
from unittest import mock | ||
|
||
import requests | ||
|
||
from soufi import exceptions | ||
from soufi.finder import SourceType | ||
from soufi.finders import crate | ||
from soufi.testing import base | ||
|
||
|
||
class TestPythonFinder(base.TestCase): | ||
def make_finder(self, name=None, version=None, index=None): | ||
if name is None: | ||
name = self.factory.make_string("name") | ||
if version is None: | ||
version = self.factory.make_string("version") | ||
kwargs = dict(name=name, version=version, s_type=SourceType.crate) | ||
if index is not None: | ||
kwargs["index"] = index | ||
return crate.CrateFinder(**kwargs) | ||
|
||
def test_get_source_url(self): | ||
finder = self.make_finder() | ||
get_index_dl = self.patch(finder, "get_index_dl") | ||
index_dl = self.factory.make_url() | ||
get_index_dl.return_value = index_dl | ||
|
||
get = self.patch_head_with_response(requests.codes.ok) | ||
found_url = finder.get_source_url() | ||
expected_url = ( | ||
f"{index_dl}/{finder.name}/{finder.name}-{finder.version}.crate" | ||
) | ||
self.assertEqual(expected_url, found_url) | ||
call = mock.call( | ||
expected_url, | ||
timeout=30, | ||
) | ||
self.assertIn(call, get.call_args_list) | ||
|
||
def test_get_source_url_source_not_found(self): | ||
finder = self.make_finder() | ||
self.patch( | ||
finder, "get_index_dl" | ||
).return_value = self.factory.make_url() | ||
# Crates index returns a 403 when the crate is not found. ¯\_(ツ)_/¯ | ||
self.patch_head_with_response(requests.codes.forbidden) | ||
self.assertRaises(exceptions.SourceNotFound, finder.get_source_url) | ||
|
||
def test_get_index_dl(self): | ||
index = self.factory.make_url() | ||
finder = self.make_finder(index=index) | ||
get_url = self.patch(finder, "get_url") | ||
index = self.factory.make_url() | ||
get_url.return_value.json.return_value = {"dl": index} | ||
|
||
found_index = finder.get_index_dl() | ||
self.assertEqual(index, found_index) | ||
call = mock.call(f"{finder.index}config.json") | ||
self.assertIn(call, get_url.call_args_list) | ||
|
||
def test_get_index_dl_no_dl_key(self): | ||
finder = self.make_finder() | ||
get_url = self.patch(finder, "get_url") | ||
get_url.return_value.json.return_value = {} | ||
|
||
self.assertRaises(exceptions.DownloadError, finder.get_index_dl) | ||
|
||
def test_find(self): | ||
finder = self.make_finder() | ||
url = self.factory.make_url() | ||
self.patch(finder, "get_source_url").return_value = url | ||
|
||
disc_source = finder.find() | ||
self.assertIsInstance(disc_source, crate.CrateDiscoveredSource) | ||
self.assertEqual([url], disc_source.urls) | ||
|
||
|
||
class TestCrateDiscoveredSource(base.TestCase): | ||
def make_discovered_source(self, url=None): | ||
if url is None: | ||
url = self.factory.make_url() | ||
return crate.CrateDiscoveredSource([url]) | ||
|
||
def test_repr(self): | ||
url = self.factory.make_url() | ||
pds = self.make_discovered_source(url) | ||
self.assertEqual(url, repr(pds)) | ||
|
||
def test_make_archive(self): | ||
cds = self.make_discovered_source() | ||
self.assertEqual(cds.make_archive, cds.remote_url_is_archive) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters