scmrepo 3.3.5__tar.gz → 3.3.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of scmrepo might be problematic. Click here for more details.

Files changed (73) hide show
  1. {scmrepo-3.3.5 → scmrepo-3.3.7}/.github/workflows/tests.yaml +3 -4
  2. {scmrepo-3.3.5 → scmrepo-3.3.7}/.pre-commit-config.yaml +2 -2
  3. {scmrepo-3.3.5/src/scmrepo.egg-info → scmrepo-3.3.7}/PKG-INFO +2 -2
  4. {scmrepo-3.3.5 → scmrepo-3.3.7}/noxfile.py +0 -8
  5. {scmrepo-3.3.5 → scmrepo-3.3.7}/pyproject.toml +2 -1
  6. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +6 -6
  7. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/dulwich/client.py +1 -1
  8. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/gitpython.py +1 -1
  9. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/pygit2/__init__.py +4 -4
  10. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/pygit2/callbacks.py +1 -1
  11. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/credentials.py +12 -12
  12. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/fetch.py +2 -2
  13. {scmrepo-3.3.5 → scmrepo-3.3.7/src/scmrepo.egg-info}/PKG-INFO +2 -2
  14. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo.egg-info/requires.txt +1 -1
  15. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/docker-compose.yml +2 -2
  16. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_dulwich.py +75 -7
  17. {scmrepo-3.3.5 → scmrepo-3.3.7}/.coveragerc +0 -0
  18. {scmrepo-3.3.5 → scmrepo-3.3.7}/.cruft.json +0 -0
  19. {scmrepo-3.3.5 → scmrepo-3.3.7}/.gitattributes +0 -0
  20. {scmrepo-3.3.5 → scmrepo-3.3.7}/.github/dependabot.yml +0 -0
  21. {scmrepo-3.3.5 → scmrepo-3.3.7}/.github/workflows/release.yaml +0 -0
  22. {scmrepo-3.3.5 → scmrepo-3.3.7}/.github/workflows/update-template.yaml +0 -0
  23. {scmrepo-3.3.5 → scmrepo-3.3.7}/.gitignore +0 -0
  24. {scmrepo-3.3.5 → scmrepo-3.3.7}/CODE_OF_CONDUCT.rst +0 -0
  25. {scmrepo-3.3.5 → scmrepo-3.3.7}/CONTRIBUTING.rst +0 -0
  26. {scmrepo-3.3.5 → scmrepo-3.3.7}/LICENSE +0 -0
  27. {scmrepo-3.3.5 → scmrepo-3.3.7}/README.rst +0 -0
  28. {scmrepo-3.3.5 → scmrepo-3.3.7}/setup.cfg +0 -0
  29. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/__init__.py +0 -0
  30. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/asyn.py +0 -0
  31. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/base.py +0 -0
  32. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/exceptions.py +0 -0
  33. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/fs.py +0 -0
  34. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/__init__.py +0 -0
  35. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/__init__.py +0 -0
  36. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/base.py +0 -0
  37. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/dulwich/__init__.py +0 -0
  38. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/backend/pygit2/filter.py +0 -0
  39. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/config.py +0 -0
  40. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/__init__.py +0 -0
  41. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/client.py +0 -0
  42. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/exceptions.py +0 -0
  43. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/object.py +0 -0
  44. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/pointer.py +0 -0
  45. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/progress.py +0 -0
  46. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/smudge.py +0 -0
  47. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/lfs/storage.py +0 -0
  48. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/objects.py +0 -0
  49. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/git/stash.py +0 -0
  50. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/noscm.py +0 -0
  51. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/progress.py +0 -0
  52. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/py.typed +0 -0
  53. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/urls.py +0 -0
  54. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo/utils.py +0 -0
  55. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo.egg-info/SOURCES.txt +0 -0
  56. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo.egg-info/dependency_links.txt +0 -0
  57. {scmrepo-3.3.5 → scmrepo-3.3.7}/src/scmrepo.egg-info/top_level.txt +0 -0
  58. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/__init__.py +0 -0
  59. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/conftest.py +0 -0
  60. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/git-init/git.sh +0 -0
  61. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_credentials.py +0 -0
  62. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_fs.py +0 -0
  63. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_git.py +0 -0
  64. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_lfs.py +0 -0
  65. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_noscm.py +0 -0
  66. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_pygit2.py +0 -0
  67. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_scmrepo.py +0 -0
  68. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_stash.py +0 -0
  69. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/test_urls.py +0 -0
  70. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/user.key +0 -0
  71. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/user.key.pub +0 -0
  72. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/vendor/__init__.py +0 -0
  73. {scmrepo-3.3.5 → scmrepo-3.3.7}/tests/vendor/test_paramiko_vendor.py +0 -0
@@ -40,12 +40,11 @@ jobs:
40
40
  pip --version
41
41
  nox --version
42
42
 
43
- - name: Lint code and check dependencies
44
- continue-on-error: ${{ matrix.nox_pyv == '3.11' }}
45
- run: nox -s lint safety --verbose
43
+ - name: Lint code
44
+ run: nox -s lint
46
45
 
47
46
  - name: Run tests
48
- run: nox -s tests-${{ matrix.nox_pyv || matrix.pyv }} -- --slow --cov-report=xml
47
+ run: nox -s tests-${{ matrix.pyv }} -- --slow --cov-report=xml
49
48
 
50
49
  - name: Upload coverage report
51
50
  uses: codecov/codecov-action@v3.1.0
@@ -20,13 +20,13 @@ repos:
20
20
  - id: sort-simple-yaml
21
21
  - id: trailing-whitespace
22
22
  - repo: https://github.com/astral-sh/ruff-pre-commit
23
- rev: 'v0.4.4'
23
+ rev: 'v0.5.2'
24
24
  hooks:
25
25
  - id: ruff
26
26
  args: [--fix, --exit-non-zero-on-fix]
27
27
  - id: ruff-format
28
28
  - repo: https://github.com/codespell-project/codespell
29
- rev: v2.2.6
29
+ rev: v2.3.0
30
30
  hooks:
31
31
  - id: codespell
32
32
  additional_dependencies: ["tomli"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 3.3.5
3
+ Version: 3.3.7
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -36,7 +36,7 @@ Requires-Dist: pytest-mock; extra == "tests"
36
36
  Requires-Dist: pytest-sugar; extra == "tests"
37
37
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
38
38
  Provides-Extra: dev
39
- Requires-Dist: mypy==1.10.0; extra == "dev"
39
+ Requires-Dist: mypy==1.10.1; extra == "dev"
40
40
  Requires-Dist: scmrepo[tests]; extra == "dev"
41
41
  Requires-Dist: types-certifi; extra == "dev"
42
42
  Requires-Dist: types-mock; extra == "dev"
@@ -32,14 +32,6 @@ def lint(session: nox.Session) -> None:
32
32
  session.run("python", "-m", "mypy")
33
33
 
34
34
 
35
- @nox.session
36
- def safety(session: nox.Session) -> None:
37
- """Scan dependencies for insecure packages."""
38
- session.install(".[dev]")
39
- session.install("safety")
40
- session.run("safety", "check", "--full-report", "--ignore=67599")
41
-
42
-
43
35
  @nox.session
44
36
  def build(session: nox.Session) -> None:
45
37
  session.install("build", "setuptools", "twine")
@@ -50,7 +50,7 @@ tests = [
50
50
  "pytest-test-utils>=0.1.0,<0.2",
51
51
  ]
52
52
  dev = [
53
- "mypy==1.10.0",
53
+ "mypy==1.10.1",
54
54
  "scmrepo[tests]",
55
55
  "types-certifi",
56
56
  "types-mock",
@@ -123,6 +123,7 @@ ignore_missing_imports = true
123
123
 
124
124
  [tool.codespell]
125
125
  ignore-words-list = "cachable, keypair"
126
+ skip = "CODE_OF_CONDUCT.rst"
126
127
 
127
128
  [tool.ruff]
128
129
  output-format = "full"
@@ -66,8 +66,8 @@ class _StderrWrapper:
66
66
  class AsyncSSHWrapper(BaseAsyncObject):
67
67
  def __init__(self, conn: "SSHClientConnection", proc: "SSHClientProcess", **kwargs):
68
68
  super().__init__(**kwargs)
69
- self.conn: "SSHClientConnection" = conn
70
- self.proc: "SSHClientProcess" = proc
69
+ self.conn: SSHClientConnection = conn
70
+ self.proc: SSHClientProcess = proc
71
71
  self.stderr = _StderrWrapper(proc.stderr, self.loop)
72
72
 
73
73
  def can_read(self) -> bool:
@@ -143,7 +143,7 @@ class InteractiveSSHClient(SSHClient):
143
143
 
144
144
  def __init__(self, *args, **kwargs):
145
145
  super(*args, **kwargs)
146
- _passphrases: dict[str, str] = {}
146
+ self._passphrases: dict[str, str] = {}
147
147
 
148
148
  def connection_made(self, conn: "SSHClientConnection") -> None:
149
149
  self._conn = conn
@@ -152,7 +152,7 @@ class InteractiveSSHClient(SSHClient):
152
152
  def connection_lost(self, exc: Optional[Exception]) -> None:
153
153
  self._conn = None
154
154
 
155
- async def public_key_auth_requested( # noqa: C901, PLR0912
155
+ async def public_key_auth_requested( # noqa: C901
156
156
  self,
157
157
  ) -> Optional["KeyPairListArg"]:
158
158
  from asyncssh.public_key import (
@@ -298,7 +298,7 @@ class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
298
298
  host,
299
299
  port=port if port is not None else (),
300
300
  username=username if username is not None else (),
301
- password=password if password is not None else (),
301
+ password=password,
302
302
  client_keys=[key_filename] if key_filename else (),
303
303
  ignore_encrypted=not key_filename,
304
304
  known_hosts=None,
@@ -320,7 +320,7 @@ def get_unsupported_opts(config_paths: "ConfigPaths") -> Iterator[str]:
320
320
 
321
321
  if config_paths:
322
322
  if isinstance(config_paths, (str, PurePath)):
323
- paths: Sequence["FilePath"] = [config_paths]
323
+ paths: Sequence[FilePath] = [config_paths]
324
324
  else:
325
325
  paths = config_paths
326
326
 
@@ -22,7 +22,7 @@ class GitCredentialsHTTPClient(Urllib3HttpGitClient): # pylint: disable=abstrac
22
22
  config=config,
23
23
  **kwargs,
24
24
  )
25
- self._store_credentials: Optional["Credential"] = None
25
+ self._store_credentials: Optional[Credential] = None
26
26
 
27
27
  def _http_request(
28
28
  self,
@@ -468,7 +468,7 @@ class GitPythonBackend(BaseGitBackend): # pylint:disable=abstract-method
468
468
  except GitCommandError as exc:
469
469
  raise SCMError(f"Failed to set ref '{name}'") from exc
470
470
 
471
- def get_ref(self, name: str, follow: bool = True) -> Optional[str]: # noqa: C901, PLR0911, PLR0912
471
+ def get_ref(self, name: str, follow: bool = True) -> Optional[str]: # noqa: C901, PLR0911
472
472
  from git.exc import GitCommandError
473
473
 
474
474
  if name == "HEAD":
@@ -698,7 +698,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
698
698
  SCMError(f"Git failed to fetch ref from '{url}'"),
699
699
  ):
700
700
  with RemoteCallbacks(progress=progress) as cb:
701
- remote_refs: dict[str, "Oid"] = (
701
+ remote_refs: dict[str, Oid] = (
702
702
  {
703
703
  head["name"]: head["oid"]
704
704
  for head in remote.ls_remotes(callbacks=cb)
@@ -712,7 +712,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
712
712
  message="fetch",
713
713
  )
714
714
 
715
- result: dict[str, "SyncStatus"] = {}
715
+ result: dict[str, SyncStatus] = {}
716
716
  for refspec in refspecs:
717
717
  lh, rh = refspec.split(":")
718
718
  if lh.endswith("*"):
@@ -951,7 +951,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
951
951
  def iter_remote_refs(self, url: str, base: Optional[str] = None, **kwargs):
952
952
  raise NotImplementedError
953
953
 
954
- def merge( # noqa: C901, PLR0912
954
+ def merge( # noqa: C901
955
955
  self,
956
956
  rev: str,
957
957
  commit: bool = True,
@@ -1089,7 +1089,7 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
1089
1089
  from pygit2 import GitError
1090
1090
  from pygit2.enums import AttrCheck
1091
1091
 
1092
- commit: Optional["Commit"] = None
1092
+ commit: Optional[Commit] = None
1093
1093
  flags = AttrCheck.FILE_THEN_INDEX
1094
1094
  if source:
1095
1095
  try:
@@ -28,7 +28,7 @@ class RemoteCallbacks(_RemoteCallbacks, AbstractContextManager):
28
28
  ):
29
29
  super().__init__(*args, **kwargs)
30
30
  self.progress = GitProgressReporter(progress) if progress else None
31
- self._store_credentials: Optional["Credential"] = None
31
+ self._store_credentials: Optional[Credential] = None
32
32
  self._tried_credentials = False
33
33
  self.result: dict[str, SyncStatus] = {}
34
34
 
@@ -136,8 +136,8 @@ class GitCredentialHelper(CredentialHelper):
136
136
  if not shutil.which(executable) and shutil.which("git"):
137
137
  # If the helper cannot be found in PATH, it might be
138
138
  # a C git helper in GIT_EXEC_PATH
139
- git_exec_path = subprocess.check_output(
140
- ("git", "--exec-path"), # noqa: S603
139
+ git_exec_path = subprocess.check_output( # noqa: S603
140
+ ("git", "--exec-path"),
141
141
  text=True,
142
142
  ).strip()
143
143
  if shutil.which(executable, path=git_exec_path):
@@ -158,8 +158,8 @@ class GitCredentialHelper(CredentialHelper):
158
158
  helper_input.append("")
159
159
 
160
160
  try:
161
- res = subprocess.run(
162
- cmd, # noqa: S603
161
+ res = subprocess.run( # noqa: S603
162
+ cmd,
163
163
  check=True,
164
164
  capture_output=True,
165
165
  input="\n".join(helper_input),
@@ -199,8 +199,8 @@ class GitCredentialHelper(CredentialHelper):
199
199
  helper_input.append("")
200
200
 
201
201
  try:
202
- res = subprocess.run(
203
- cmd, # noqa: S603
202
+ res = subprocess.run( # noqa: S603
203
+ cmd,
204
204
  capture_output=True,
205
205
  input="\n".join(helper_input),
206
206
  encoding=self._encoding,
@@ -224,8 +224,8 @@ class GitCredentialHelper(CredentialHelper):
224
224
  helper_input.append("")
225
225
 
226
226
  try:
227
- res = subprocess.run(
228
- cmd, # noqa: S603
227
+ res = subprocess.run( # noqa: S603
228
+ cmd,
229
229
  capture_output=True,
230
230
  input="\n".join(helper_input),
231
231
  encoding=self._encoding,
@@ -243,7 +243,7 @@ class GitCredentialHelper(CredentialHelper):
243
243
  ) -> Iterator[tuple[str, bool]]:
244
244
  config = config or StackedConfig.default()
245
245
  if isinstance(config, StackedConfig):
246
- backends: Iterable["ConfigDict"] = config.backends
246
+ backends: Iterable[ConfigDict] = config.backends
247
247
  else:
248
248
  backends = [config]
249
249
 
@@ -324,7 +324,7 @@ class MemoryCredentialHelper(CredentialHelper):
324
324
 
325
325
  def __init__(self):
326
326
  super().__init__()
327
- self._credentials: dict["_CredentialKey", "Credential"] = {}
327
+ self._credentials: dict[_CredentialKey, Credential] = {}
328
328
 
329
329
  def __getitem__(self, key: object) -> "Credential":
330
330
  if isinstance(key, _CredentialKey):
@@ -448,8 +448,8 @@ class _AskpassCommand:
448
448
  def input(self, prompt: str) -> str:
449
449
  argv = [self.command, prompt]
450
450
  try:
451
- res = subprocess.run(
452
- argv, # noqa: S603
451
+ res = subprocess.run( # noqa: S603
452
+ argv,
453
453
  check=True,
454
454
  capture_output=True,
455
455
  encoding=locale.getpreferredencoding(),
@@ -46,7 +46,7 @@ def fetch(
46
46
  scm.lfs_storage.fetch(url, objects, progress=progress)
47
47
 
48
48
 
49
- def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901,PLR0912
49
+ def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901
50
50
  """Return LFS fetch URL for the specified repository."""
51
51
  git_config = scm.get_config()
52
52
 
@@ -56,7 +56,7 @@ def get_fetch_url(scm: "Git", remote: Optional[str] = None): # noqa: C901,PLR09
56
56
  except KeyError:
57
57
  pass
58
58
  try:
59
- lfs_config: Optional["Config"] = scm.get_config(
59
+ lfs_config: Optional[Config] = scm.get_config(
60
60
  os.path.join(scm.root_dir, ".lfsconfig")
61
61
  )
62
62
  except FileNotFoundError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scmrepo
3
- Version: 3.3.5
3
+ Version: 3.3.7
4
4
  Summary: scmrepo
5
5
  Author-email: Iterative <support@dvc.org>
6
6
  License: Apache-2.0
@@ -36,7 +36,7 @@ Requires-Dist: pytest-mock; extra == "tests"
36
36
  Requires-Dist: pytest-sugar; extra == "tests"
37
37
  Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
38
38
  Provides-Extra: dev
39
- Requires-Dist: mypy==1.10.0; extra == "dev"
39
+ Requires-Dist: mypy==1.10.1; extra == "dev"
40
40
  Requires-Dist: scmrepo[tests]; extra == "dev"
41
41
  Requires-Dist: types-certifi; extra == "dev"
42
42
  Requires-Dist: types-mock; extra == "dev"
@@ -10,7 +10,7 @@ aiohttp-retry>=2.5.0
10
10
  tqdm
11
11
 
12
12
  [dev]
13
- mypy==1.10.0
13
+ mypy==1.10.1
14
14
  scmrepo[tests]
15
15
  types-certifi
16
16
  types-mock
@@ -2,7 +2,7 @@
2
2
  version: '3.2'
3
3
  services:
4
4
  git-server:
5
- image: ghcr.io/linuxserver/openssh-server:9.0_p1-r2-ls100
5
+ image: ghcr.io/linuxserver/openssh-server
6
6
  environment:
7
7
  - USER_NAME=user
8
8
  - PUBLIC_KEY_FILE=/tmp/key
@@ -10,4 +10,4 @@ services:
10
10
  - 2222
11
11
  volumes:
12
12
  - ./user.key.pub:/tmp/key
13
- - ./git-init:/config/custom-cont-init.d
13
+ - ./git-init:/custom-cont-init.d
@@ -11,18 +11,74 @@ import pytest
11
11
  from pytest_mock import MockerFixture
12
12
  from pytest_test_utils.waiters import wait_until
13
13
 
14
+ from scmrepo.exceptions import AuthError
14
15
  from scmrepo.git.backend.dulwich.asyncssh_vendor import AsyncSSHVendor
15
16
 
16
- from .vendor.test_paramiko_vendor import (
17
- CLIENT_KEY,
18
- PASSWORD,
19
- USER,
20
- Server,
21
- )
22
-
23
17
  # pylint: disable=redefined-outer-name
24
18
 
25
19
 
20
+ USER = "testuser"
21
+ PASSWORD = "test"
22
+ CLIENT_KEY = """-----BEGIN RSA PRIVATE KEY-----
23
+ MIIEpAIBAAKCAQEAxvREKSElPOm/0z/nPO+j5rk2tjdgGcGc7We1QZ6TRXYLu7nN
24
+ GeEFIL4p8N1i6dmB+Eydt7xqCU79MWD6Yy4prFe1+/K1wCDUxIbFMxqQcX5zjJzd
25
+ i8j8PbcaUlVhP/OkjtkSxrXaGDO1BzfdV4iEBtTV/2l3zmLKJlt3jnOHLczP24CB
26
+ DTQKp3rKshbRefzot9Y+wnaK692RsYgsyo9YEP0GyWKG9topCHk13r46J6vGLeuj
27
+ ryUKqmbLJkzbJbIcEqwTDo5iHaCVqaMr5Hrb8BdMucSseqZQJsXSd+9tdRcIblUQ
28
+ 38kZjmFMm4SFbruJcpZCNM2wNSZPIRX+3eiwNwIDAQABAoIBAHSacOBSJsr+jIi5
29
+ KUOTh9IPtzswVUiDKwARCjB9Sf8p4lKR4N1L/n9kNJyQhApeikgGT2GCMftmqgoo
30
+ tlculQoHFgemBlOmak0MV8NNzF5YKEy/GzF0CDH7gJfEpoyetVFrdA+2QS5yD6U9
31
+ XqKQxiBi2VEqdScmyyeT8AwzNYTnPeH/DOEcnbdRjqiy/CD79F49CQ1lX1Fuqm0K
32
+ I7BivBH1xo/rVnUP4F+IzocDqoga+Pjdj0LTXIgJlHQDSbhsQqWujWQDDuKb+MAw
33
+ sNK4Zf8ErV3j1PyA7f/M5LLq6zgstkW4qikDHo4SpZX8kFOO8tjqb7kujj7XqeaB
34
+ CxqrOTECgYEA73uWkrohcmDJ4KqbuL3tbExSCOUiaIV+sT1eGPNi7GCmXD4eW5Z4
35
+ 75v2IHymW83lORSu/DrQ6sKr1nkuRpqr2iBzRmQpl/H+wahIhBXlnJ25uUjDsuPO
36
+ 1Pq2LcmyD+jTxVnmbSe/q7O09gZQw3I6H4+BMHmpbf8tC97lqimzpJ0CgYEA1K0W
37
+ ZL70Xtn9quyHvbtae/BW07NZnxvUg4UaVIAL9Zu34JyplJzyzbIjrmlDbv6aRogH
38
+ /KtuG9tfbf55K/jjqNORiuRtzt1hUN1ye4dyW7tHx2/7lXdlqtyK40rQl8P0kqf8
39
+ zaS6BqjnobgSdSpg32rWoL/pcBHPdJCJEgQ8zeMCgYEA0/PK8TOhNIzrP1dgGSKn
40
+ hkkJ9etuB5nW5mEM7gJDFDf6JPupfJ/xiwe6z0fjKK9S57EhqgUYMB55XYnE5iIw
41
+ ZQ6BV9SAZ4V7VsRs4dJLdNC3tn/rDGHJBgCaym2PlbsX6rvFT+h1IC8dwv0V79Ui
42
+ Ehq9WTzkMoE8yhvNokvkPZUCgYEAgBAFxv5xGdh79ftdtXLmhnDvZ6S8l6Fjcxqo
43
+ Ay/jg66Tp43OU226iv/0mmZKM8Dd1xC8dnon4GBVc19jSYYiWBulrRPlx0Xo/o+K
44
+ CzZBN1lrXH1i6dqufpc0jq8TMf/N+q1q/c1uMupsKCY1/xVYpc+ok71b7J7c49zQ
45
+ nOeuUW8CgYA9Infooy65FTgbzca0c9kbCUBmcAPQ2ItH3JcPKWPQTDuV62HcT00o
46
+ fZdIV47Nez1W5Clk191RMy8TXuqI54kocciUWpThc6j44hz49oUueb8U4bLcEHzA
47
+ WxtWBWHwxfSmqgTXilEA3ALJp0kNolLnEttnhENwJpZHlqtes0ZA4w==
48
+ -----END RSA PRIVATE KEY-----"""
49
+
50
+
51
+ class Server(paramiko.ServerInterface):
52
+ """http://docs.paramiko.org/en/2.4/api/server.html."""
53
+
54
+ def __init__(self, commands, *args, **kwargs) -> None:
55
+ super().__init__(*args, **kwargs)
56
+ self.commands = commands
57
+
58
+ def check_channel_exec_request(self, channel, command):
59
+ self.commands.append(command)
60
+ return True
61
+
62
+ def check_auth_password(self, username, password):
63
+ if username == USER and password == PASSWORD:
64
+ return paramiko.AUTH_SUCCESSFUL
65
+ return paramiko.AUTH_FAILED
66
+
67
+ def check_auth_publickey(self, username, key):
68
+ pubkey = paramiko.RSAKey.from_private_key(StringIO(CLIENT_KEY))
69
+ if username == USER and key == pubkey:
70
+ return paramiko.AUTH_SUCCESSFUL
71
+ return paramiko.AUTH_FAILED
72
+
73
+ def check_channel_request(self, kind, chanid):
74
+ if kind == "session":
75
+ return paramiko.OPEN_SUCCEEDED
76
+ return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
77
+
78
+ def get_allowed_auths(self, username):
79
+ return "password,publickey"
80
+
81
+
26
82
  @pytest.fixture
27
83
  def ssh_conn(request: pytest.FixtureRequest) -> dict[str, Any]:
28
84
  server = Server([])
@@ -77,6 +133,18 @@ def test_run_command_password(server: Server, ssh_port: int):
77
133
  assert b"test_run_command_password" in server.commands
78
134
 
79
135
 
136
+ def test_run_command_no_password(server: Server, ssh_port: int):
137
+ vendor = AsyncSSHVendor()
138
+ with pytest.raises(AuthError):
139
+ vendor.run_command(
140
+ "127.0.0.1",
141
+ "test_run_command_password",
142
+ username=USER,
143
+ port=ssh_port,
144
+ password=None,
145
+ )
146
+
147
+
80
148
  def test_run_command_with_privkey(server: Server, ssh_port: int):
81
149
  key = asyncssh.import_private_key(CLIENT_KEY)
82
150
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes