jartify 0.2.2__tar.gz → 0.2.4__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.
- {jartify-0.2.2 → jartify-0.2.4}/PKG-INFO +4 -1
- {jartify-0.2.2 → jartify-0.2.4}/README.md +3 -0
- {jartify-0.2.2 → jartify-0.2.4}/pyproject.toml +1 -1
- jartify-0.2.4/src/jartify/__init__.py +1 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/client.py +18 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/locate.py +109 -6
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify.egg-info/PKG-INFO +4 -1
- jartify-0.2.2/src/jartify/__init__.py +0 -1
- {jartify-0.2.2 → jartify-0.2.4}/setup.cfg +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/__main__.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/app.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/cli.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/config.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/repos.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/reset.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify/setup.py +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify.egg-info/SOURCES.txt +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify.egg-info/dependency_links.txt +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify.egg-info/entry_points.txt +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify.egg-info/requires.txt +0 -0
- {jartify-0.2.2 → jartify-0.2.4}/src/jartify.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jartify
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: TUI and CLI for searching JFrog Artifactory artifacts (version, filename, sha256)
|
|
5
5
|
Author-email: Jakob Holst <code@jakobholst.dk>
|
|
6
6
|
License: MIT
|
|
@@ -134,6 +134,9 @@ jartify-locate --init
|
|
|
134
134
|
# commits so Ctrl+C keeps everything already indexed):
|
|
135
135
|
jartify-locate --init --status=progress
|
|
136
136
|
|
|
137
|
+
# Or one AQL per repo, printing the repo name as it goes (commits per repo):
|
|
138
|
+
jartify-locate --init --status=step
|
|
139
|
+
|
|
137
140
|
# Then search locally:
|
|
138
141
|
jartify-locate # list everything (respects --repo / --limit)
|
|
139
142
|
jartify-locate '*.json' # glob
|
|
@@ -108,6 +108,9 @@ jartify-locate --init
|
|
|
108
108
|
# commits so Ctrl+C keeps everything already indexed):
|
|
109
109
|
jartify-locate --init --status=progress
|
|
110
110
|
|
|
111
|
+
# Or one AQL per repo, printing the repo name as it goes (commits per repo):
|
|
112
|
+
jartify-locate --init --status=step
|
|
113
|
+
|
|
111
114
|
# Then search locally:
|
|
112
115
|
jartify-locate # list everything (respects --repo / --limit)
|
|
113
116
|
jartify-locate '*.json' # glob
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.4"
|
|
@@ -19,6 +19,8 @@ from urllib.parse import urljoin
|
|
|
19
19
|
|
|
20
20
|
import requests
|
|
21
21
|
from packaging.utils import parse_sdist_filename, parse_wheel_filename
|
|
22
|
+
from requests.adapters import HTTPAdapter
|
|
23
|
+
from urllib3.util.retry import Retry
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
@dataclass
|
|
@@ -85,6 +87,22 @@ class ArtifactoryClient:
|
|
|
85
87
|
self.base_url = self.base_url + "/artifactory"
|
|
86
88
|
self.timeout = timeout
|
|
87
89
|
self.session = session or requests.Session()
|
|
90
|
+
# Transparently retry on connection drops (RemoteDisconnected, reset
|
|
91
|
+
# keep-alives) and transient 5xx. Exponential backoff: 0, 1, 2, 4, 8s.
|
|
92
|
+
_retry = Retry(
|
|
93
|
+
total=5,
|
|
94
|
+
connect=5,
|
|
95
|
+
read=5,
|
|
96
|
+
status=5,
|
|
97
|
+
backoff_factor=1.0,
|
|
98
|
+
status_forcelist=(502, 503, 504),
|
|
99
|
+
allowed_methods=frozenset(["GET", "POST", "HEAD"]),
|
|
100
|
+
raise_on_status=False,
|
|
101
|
+
respect_retry_after_header=True,
|
|
102
|
+
)
|
|
103
|
+
_adapter = HTTPAdapter(max_retries=_retry)
|
|
104
|
+
self.session.mount("http://", _adapter)
|
|
105
|
+
self.session.mount("https://", _adapter)
|
|
88
106
|
self.session.verify = verify
|
|
89
107
|
if not verify:
|
|
90
108
|
# One-shot: silence the per-request InsecureRequestWarning.
|
|
@@ -287,6 +287,76 @@ def _init_paginated(
|
|
|
287
287
|
return total_appended
|
|
288
288
|
|
|
289
289
|
|
|
290
|
+
def _init_step(
|
|
291
|
+
client: ArtifactoryClient, repos: list[str] | None
|
|
292
|
+
) -> int:
|
|
293
|
+
"""One AQL request per repo, printing the repo name as it goes.
|
|
294
|
+
|
|
295
|
+
Session-level retries handle transient connection drops. A per-repo
|
|
296
|
+
try/except catches anything that still fails after retries so one
|
|
297
|
+
bad repo doesn't abort the remaining ones; failures are listed at
|
|
298
|
+
the end.
|
|
299
|
+
"""
|
|
300
|
+
if repos is None:
|
|
301
|
+
all_repos = client.list_repositories()
|
|
302
|
+
repo_keys = [r.get("key") for r in all_repos if r.get("key")]
|
|
303
|
+
if not repo_keys:
|
|
304
|
+
print(" no repos visible to this token.")
|
|
305
|
+
return 0
|
|
306
|
+
else:
|
|
307
|
+
repo_keys = list(repos)
|
|
308
|
+
|
|
309
|
+
conn = _db_connect()
|
|
310
|
+
t0 = time.time()
|
|
311
|
+
total_appended = 0
|
|
312
|
+
skipped = 0
|
|
313
|
+
failed: list[tuple[str, str]] = []
|
|
314
|
+
width = len(str(len(repo_keys)))
|
|
315
|
+
try:
|
|
316
|
+
for i, repo in enumerate(repo_keys, 1):
|
|
317
|
+
prefix = f"[{i:>{width}}/{len(repo_keys)}]"
|
|
318
|
+
indent = " " * (len(prefix) + 1)
|
|
319
|
+
print(f"{prefix} {repo} …", flush=True)
|
|
320
|
+
try:
|
|
321
|
+
items = client.aql(_aql_query([repo]))
|
|
322
|
+
artifact_rows, location_rows, skip = _build_rows(items)
|
|
323
|
+
with conn:
|
|
324
|
+
_insert_batch(conn, artifact_rows, location_rows)
|
|
325
|
+
total_appended += len(artifact_rows)
|
|
326
|
+
skipped += skip
|
|
327
|
+
print(
|
|
328
|
+
f"{indent}{len(artifact_rows):,} files appended "
|
|
329
|
+
f"({len(items):,} received)",
|
|
330
|
+
flush=True,
|
|
331
|
+
)
|
|
332
|
+
except Exception as exc:
|
|
333
|
+
# One repo died — remember and move on. The commit on
|
|
334
|
+
# previous repos has already landed thanks to per-repo
|
|
335
|
+
# transactions.
|
|
336
|
+
summary = f"{type(exc).__name__}: {exc}"
|
|
337
|
+
failed.append((repo, summary))
|
|
338
|
+
print(f"{indent}FAILED — {summary}", file=sys.stderr, flush=True)
|
|
339
|
+
|
|
340
|
+
with conn:
|
|
341
|
+
_write_meta(conn)
|
|
342
|
+
_finalize(conn, t0, skipped)
|
|
343
|
+
if failed:
|
|
344
|
+
print(f"\n{len(failed)} repo(s) failed and were skipped:",
|
|
345
|
+
file=sys.stderr)
|
|
346
|
+
for repo, msg in failed:
|
|
347
|
+
print(f" {repo}: {msg}", file=sys.stderr)
|
|
348
|
+
print(
|
|
349
|
+
"Re-run with -R to retry just those repos, e.g.:\n"
|
|
350
|
+
" jartify-locate --init --status=step "
|
|
351
|
+
+ " ".join(f"-R {r}" for r, _ in failed[:3])
|
|
352
|
+
+ ("" if len(failed) <= 3 else " …"),
|
|
353
|
+
file=sys.stderr,
|
|
354
|
+
)
|
|
355
|
+
finally:
|
|
356
|
+
conn.close()
|
|
357
|
+
return total_appended
|
|
358
|
+
|
|
359
|
+
|
|
290
360
|
def _init_index(
|
|
291
361
|
client: ArtifactoryClient,
|
|
292
362
|
repos: list[str] | None,
|
|
@@ -296,6 +366,8 @@ def _init_index(
|
|
|
296
366
|
print(f"Fetching artifact index for {scope} via AQL…", flush=True)
|
|
297
367
|
if status == "progress":
|
|
298
368
|
return _init_paginated(client, repos)
|
|
369
|
+
if status == "step":
|
|
370
|
+
return _init_step(client, repos)
|
|
299
371
|
return _init_single_shot(client, repos)
|
|
300
372
|
|
|
301
373
|
|
|
@@ -314,11 +386,13 @@ def _glob_to_like(pattern: str) -> str:
|
|
|
314
386
|
|
|
315
387
|
|
|
316
388
|
_JOIN_SELECT = (
|
|
317
|
-
"SELECT l.repo, l.path, a.sha256, a.filename "
|
|
389
|
+
"SELECT l.repo, l.path, a.sha256, a.filename, a.size, a.created, a.modified "
|
|
318
390
|
"FROM locations l "
|
|
319
391
|
"JOIN artifacts a ON a.sha256 = l.sha256 AND a.filename = l.filename"
|
|
320
392
|
)
|
|
321
393
|
|
|
394
|
+
_ROW_KEYS = ("repo", "path", "sha256", "filename", "size", "created", "modified")
|
|
395
|
+
|
|
322
396
|
|
|
323
397
|
def _search_glob(conn, pattern: str | None, repos: list[str] | None, limit: int):
|
|
324
398
|
"""Pattern of None lists every (file, location) row."""
|
|
@@ -350,7 +424,7 @@ def _search_fuzzy(conn, query: str, repos: list[str] | None, limit: int):
|
|
|
350
424
|
out = []
|
|
351
425
|
for row in conn.execute(sql, params):
|
|
352
426
|
if rx.search(row[3]):
|
|
353
|
-
out.append(row
|
|
427
|
+
out.append(row)
|
|
354
428
|
if len(out) >= limit:
|
|
355
429
|
break
|
|
356
430
|
return out
|
|
@@ -374,6 +448,26 @@ def _print_rows(rows) -> None:
|
|
|
374
448
|
print(fmt.format(*row).rstrip())
|
|
375
449
|
|
|
376
450
|
|
|
451
|
+
def _print_json(rows) -> None:
|
|
452
|
+
print(json.dumps([dict(zip(_ROW_KEYS, r)) for r in rows], indent=2))
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
_EPILOG = """\
|
|
456
|
+
examples:
|
|
457
|
+
jartify-locate --init build the index (fast, single commit)
|
|
458
|
+
jartify-locate --init --status=progress paginated AQL with a live progress bar
|
|
459
|
+
jartify-locate --init --status=step one AQL per repo, prints each repo name
|
|
460
|
+
jartify-locate --init --all scan every visible repo, ignore filters
|
|
461
|
+
jartify-locate --init -R pypi-local refresh just one repo
|
|
462
|
+
jartify-locate list every indexed file
|
|
463
|
+
jartify-locate '*.json' glob
|
|
464
|
+
jartify-locate requests substring (no wildcards)
|
|
465
|
+
jartify-locate --fuzzy foojsn subsequence match
|
|
466
|
+
jartify-locate --all --json dump every indexed file as JSON
|
|
467
|
+
jartify-locate --db show index path + row counts
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
|
|
377
471
|
def _build_parser() -> argparse.ArgumentParser:
|
|
378
472
|
p = argparse.ArgumentParser(
|
|
379
473
|
prog="jartify-locate",
|
|
@@ -381,6 +475,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
381
475
|
"locate(1)-style search over an offline SQLite index of your "
|
|
382
476
|
"Artifactory. Run with --init first to build the index."
|
|
383
477
|
),
|
|
478
|
+
epilog=_EPILOG,
|
|
479
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
384
480
|
)
|
|
385
481
|
p.add_argument(
|
|
386
482
|
"pattern",
|
|
@@ -399,16 +495,20 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
399
495
|
"ARTIFACTORY_REPOSITORIES, and the yaml config.")
|
|
400
496
|
p.add_argument("--limit", type=int, default=500,
|
|
401
497
|
help="Max rows to print or fuzzy-scan (default 500).")
|
|
498
|
+
p.add_argument("--json", action="store_true",
|
|
499
|
+
help="Emit JSON (every column: repo, path, sha256, filename, "
|
|
500
|
+
"size, created, modified) instead of the aligned table.")
|
|
402
501
|
p.add_argument("--db", action="store_true",
|
|
403
502
|
help="Print the index path and a one-line summary, then exit.")
|
|
404
503
|
p.add_argument("--insecure", action="store_true",
|
|
405
504
|
help="Skip TLS cert verification during --init.")
|
|
406
505
|
p.add_argument(
|
|
407
506
|
"--status",
|
|
408
|
-
choices=("off", "progress"),
|
|
507
|
+
choices=("off", "progress", "step"),
|
|
409
508
|
default="off",
|
|
410
|
-
help="Progress display during --init. 'progress' paginates
|
|
411
|
-
"
|
|
509
|
+
help="Progress display during --init. 'progress' paginates AQL and "
|
|
510
|
+
"renders a live bar; 'step' runs one AQL per repo and prints "
|
|
511
|
+
"each repo name as it goes (default: off).",
|
|
412
512
|
)
|
|
413
513
|
p.add_argument("--version", action="version", version=f"jartify {__version__}")
|
|
414
514
|
return p
|
|
@@ -500,7 +600,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
500
600
|
finally:
|
|
501
601
|
conn.close()
|
|
502
602
|
|
|
503
|
-
|
|
603
|
+
if args.json:
|
|
604
|
+
_print_json(rows)
|
|
605
|
+
else:
|
|
606
|
+
_print_rows(rows)
|
|
504
607
|
return 0 if rows else 1
|
|
505
608
|
|
|
506
609
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jartify
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: TUI and CLI for searching JFrog Artifactory artifacts (version, filename, sha256)
|
|
5
5
|
Author-email: Jakob Holst <code@jakobholst.dk>
|
|
6
6
|
License: MIT
|
|
@@ -134,6 +134,9 @@ jartify-locate --init
|
|
|
134
134
|
# commits so Ctrl+C keeps everything already indexed):
|
|
135
135
|
jartify-locate --init --status=progress
|
|
136
136
|
|
|
137
|
+
# Or one AQL per repo, printing the repo name as it goes (commits per repo):
|
|
138
|
+
jartify-locate --init --status=step
|
|
139
|
+
|
|
137
140
|
# Then search locally:
|
|
138
141
|
jartify-locate # list everything (respects --repo / --limit)
|
|
139
142
|
jartify-locate '*.json' # glob
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.2"
|
|
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
|