autopkg-wrapper 2026.2.6__py3-none-any.whl → 2026.2.9__py3-none-any.whl
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.
- autopkg_wrapper/autopkg_wrapper.py +133 -42
- autopkg_wrapper/models/recipe.py +41 -1
- autopkg_wrapper/notifier/slack.py +8 -6
- autopkg_wrapper/utils/args.py +5 -0
- autopkg_wrapper/utils/git_functions.py +4 -3
- autopkg_wrapper/utils/recipe_batching.py +22 -5
- autopkg_wrapper/utils/recipe_ordering.py +13 -8
- autopkg_wrapper/utils/report_processor.py +232 -54
- autopkg_wrapper-2026.2.9.dist-info/METADATA +244 -0
- autopkg_wrapper-2026.2.9.dist-info/RECORD +17 -0
- autopkg_wrapper-2026.2.6.dist-info/METADATA +0 -107
- autopkg_wrapper-2026.2.6.dist-info/RECORD +0 -17
- {autopkg_wrapper-2026.2.6.dist-info → autopkg_wrapper-2026.2.9.dist-info}/WHEEL +0 -0
- {autopkg_wrapper-2026.2.6.dist-info → autopkg_wrapper-2026.2.9.dist-info}/entry_points.txt +0 -0
- {autopkg_wrapper-2026.2.6.dist-info → autopkg_wrapper-2026.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,11 +4,11 @@ import os
|
|
|
4
4
|
import plistlib
|
|
5
5
|
import re
|
|
6
6
|
import zipfile
|
|
7
|
-
from
|
|
7
|
+
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def find_report_dirs(base_path: str) ->
|
|
11
|
-
dirs:
|
|
10
|
+
def find_report_dirs(base_path: str) -> list[str]:
|
|
11
|
+
dirs: list[str] = []
|
|
12
12
|
if not os.path.exists(base_path):
|
|
13
13
|
return dirs
|
|
14
14
|
for root, subdirs, _files in os.walk(base_path):
|
|
@@ -28,9 +28,9 @@ def find_report_dirs(base_path: str) -> List[str]:
|
|
|
28
28
|
return sorted(dirs)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def parse_json_file(path: str) ->
|
|
31
|
+
def parse_json_file(path: str) -> dict:
|
|
32
32
|
try:
|
|
33
|
-
with open(path,
|
|
33
|
+
with open(path, encoding="utf-8") as f:
|
|
34
34
|
return json.load(f)
|
|
35
35
|
except Exception:
|
|
36
36
|
return {}
|
|
@@ -46,10 +46,46 @@ def _infer_recipe_name_from_filename(path: str) -> str:
|
|
|
46
46
|
return base
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
def _resolve_recipe_name(name: str, recipe_link_map: dict[str, str] | None) -> str:
|
|
50
|
+
if not recipe_link_map:
|
|
51
|
+
return name
|
|
52
|
+
if name in recipe_link_map:
|
|
53
|
+
return name
|
|
54
|
+
candidates = [
|
|
55
|
+
recipe_name
|
|
56
|
+
for recipe_name in recipe_link_map
|
|
57
|
+
if recipe_name.startswith(f"{name}.")
|
|
58
|
+
]
|
|
59
|
+
if len(candidates) == 1:
|
|
60
|
+
return candidates[0]
|
|
61
|
+
return name
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _build_recipe_link_map(
|
|
65
|
+
repo_path: str | None, repo_url: str | None, repo_branch: str | None
|
|
66
|
+
) -> dict[str, str]:
|
|
67
|
+
if not repo_path or not repo_url or not repo_branch:
|
|
68
|
+
return {}
|
|
69
|
+
repo_root = Path(repo_path)
|
|
70
|
+
if not repo_root.exists():
|
|
71
|
+
return {}
|
|
72
|
+
|
|
73
|
+
recipe_link_map: dict[str, str] = {}
|
|
74
|
+
for path in repo_root.rglob("*.recipe*"):
|
|
75
|
+
if not path.is_file():
|
|
76
|
+
continue
|
|
77
|
+
rel = path.relative_to(repo_root).as_posix()
|
|
78
|
+
recipe_base = path.name
|
|
79
|
+
recipe_name = recipe_base.split(".recipe", 1)[0]
|
|
80
|
+
if recipe_name not in recipe_link_map:
|
|
81
|
+
recipe_link_map[recipe_name] = f"{repo_url}/blob/{repo_branch}/{rel}"
|
|
82
|
+
return recipe_link_map
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_text_file(path: str) -> dict[str, list]:
|
|
86
|
+
uploads: list[dict] = []
|
|
87
|
+
policies: list[dict] = []
|
|
88
|
+
errors: list[str] = []
|
|
53
89
|
|
|
54
90
|
re_error = re.compile(r"ERROR[:\s-]+(.+)", re.IGNORECASE)
|
|
55
91
|
re_upload = re.compile(
|
|
@@ -59,7 +95,7 @@ def parse_text_file(path: str) -> Dict[str, List]:
|
|
|
59
95
|
re_policy = re.compile(r"Policy (created|updated):\s*(?P<name>.+)", re.IGNORECASE)
|
|
60
96
|
|
|
61
97
|
try:
|
|
62
|
-
with open(path,
|
|
98
|
+
with open(path, encoding="utf-8", errors="ignore") as f:
|
|
63
99
|
for line in f:
|
|
64
100
|
m_err = re_error.search(line)
|
|
65
101
|
if m_err:
|
|
@@ -91,13 +127,15 @@ def parse_text_file(path: str) -> Dict[str, List]:
|
|
|
91
127
|
return {"uploads": uploads, "policies": policies, "errors": errors}
|
|
92
128
|
|
|
93
129
|
|
|
94
|
-
def parse_plist_file(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
130
|
+
def parse_plist_file(
|
|
131
|
+
path: str, *, recipe_link_map: dict[str, str] | None = None
|
|
132
|
+
) -> dict[str, list]:
|
|
133
|
+
uploads: list[dict] = []
|
|
134
|
+
policies: list[dict] = []
|
|
135
|
+
errors: list[str] = []
|
|
136
|
+
upload_rows: list[dict] = []
|
|
137
|
+
policy_rows: list[dict] = []
|
|
138
|
+
error_rows: list[dict] = []
|
|
101
139
|
|
|
102
140
|
try:
|
|
103
141
|
with open(path, "rb") as f:
|
|
@@ -117,10 +155,16 @@ def parse_plist_file(path: str) -> Dict[str, List]:
|
|
|
117
155
|
sr = plist.get("summary_results", {}) or {}
|
|
118
156
|
|
|
119
157
|
recipe_name = _infer_recipe_name_from_filename(path)
|
|
120
|
-
|
|
158
|
+
if recipe_link_map:
|
|
159
|
+
recipe_name = _resolve_recipe_name(recipe_name, recipe_link_map)
|
|
160
|
+
recipe_identifier: str | None = None
|
|
161
|
+
recipe_link = (recipe_link_map or {}).get(recipe_name)
|
|
162
|
+
|
|
163
|
+
handled_keys: set[str] = set()
|
|
121
164
|
|
|
122
165
|
jpu = sr.get("jamfpackageuploader_summary_result")
|
|
123
166
|
if isinstance(jpu, dict):
|
|
167
|
+
handled_keys.add("jamfpackageuploader_summary_result")
|
|
124
168
|
rows = jpu.get("data_rows") or []
|
|
125
169
|
for row in rows:
|
|
126
170
|
name = (row.get("name") or row.get("pkg_display_name") or "-").strip()
|
|
@@ -140,12 +184,38 @@ def parse_plist_file(path: str) -> Dict[str, List]:
|
|
|
140
184
|
{
|
|
141
185
|
"recipe_name": recipe_name,
|
|
142
186
|
"recipe_identifier": recipe_identifier or "-",
|
|
187
|
+
"recipe_url": recipe_link,
|
|
143
188
|
"package": pkg_name,
|
|
144
189
|
"version": version or "-",
|
|
145
190
|
}
|
|
146
191
|
)
|
|
147
192
|
|
|
193
|
+
jpol = sr.get("jamfpolicyuploader_summary_result")
|
|
194
|
+
if isinstance(jpol, dict):
|
|
195
|
+
handled_keys.add("jamfpolicyuploader_summary_result")
|
|
196
|
+
rows = jpol.get("data_rows") or []
|
|
197
|
+
for row in rows:
|
|
198
|
+
name = (
|
|
199
|
+
row.get("policy")
|
|
200
|
+
or row.get("policy_name")
|
|
201
|
+
or row.get("name")
|
|
202
|
+
or row.get("title")
|
|
203
|
+
)
|
|
204
|
+
if not name:
|
|
205
|
+
continue
|
|
206
|
+
policies.append({"name": str(name).strip(), "action": "-"})
|
|
207
|
+
policy_rows.append(
|
|
208
|
+
{
|
|
209
|
+
"recipe_name": recipe_name,
|
|
210
|
+
"recipe_identifier": recipe_identifier or "-",
|
|
211
|
+
"recipe_url": recipe_link,
|
|
212
|
+
"policy": str(name).strip(),
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
148
216
|
for key, block in sr.items():
|
|
217
|
+
if key in handled_keys:
|
|
218
|
+
continue
|
|
149
219
|
if not isinstance(block, dict):
|
|
150
220
|
continue
|
|
151
221
|
hdr = [h.lower() for h in (block.get("header") or [])]
|
|
@@ -171,6 +241,7 @@ def parse_plist_file(path: str) -> Dict[str, List]:
|
|
|
171
241
|
{
|
|
172
242
|
"recipe_name": recipe_name,
|
|
173
243
|
"recipe_identifier": recipe_identifier or "-",
|
|
244
|
+
"recipe_url": recipe_link,
|
|
174
245
|
"policy": str(name).strip(),
|
|
175
246
|
}
|
|
176
247
|
)
|
|
@@ -200,7 +271,11 @@ def parse_plist_file(path: str) -> Dict[str, List]:
|
|
|
200
271
|
}
|
|
201
272
|
|
|
202
273
|
|
|
203
|
-
def aggregate_reports(
|
|
274
|
+
def aggregate_reports(
|
|
275
|
+
base_path: str,
|
|
276
|
+
*,
|
|
277
|
+
recipe_link_map: dict[str, str] | None = None,
|
|
278
|
+
) -> dict:
|
|
204
279
|
summary = {
|
|
205
280
|
"uploads": [],
|
|
206
281
|
"policies": [],
|
|
@@ -219,7 +294,7 @@ def aggregate_reports(base_path: str) -> Dict:
|
|
|
219
294
|
ext = os.path.splitext(fn)[1].lower()
|
|
220
295
|
|
|
221
296
|
if ext == ".plist":
|
|
222
|
-
data = parse_plist_file(p)
|
|
297
|
+
data = parse_plist_file(p, recipe_link_map=recipe_link_map)
|
|
223
298
|
summary["uploads"] += data.get("uploads", [])
|
|
224
299
|
summary["policies"] += data.get("policies", [])
|
|
225
300
|
summary["errors"] += data.get("errors", [])
|
|
@@ -270,8 +345,8 @@ def aggregate_reports(base_path: str) -> Dict:
|
|
|
270
345
|
|
|
271
346
|
|
|
272
347
|
def _aggregate_for_display(
|
|
273
|
-
summary:
|
|
274
|
-
) ->
|
|
348
|
+
summary: dict,
|
|
349
|
+
) -> tuple[dict[str, set], dict[str, set], dict[str, int]]:
|
|
275
350
|
uploads = summary.get("uploads", [])
|
|
276
351
|
policies = summary.get("policies", [])
|
|
277
352
|
errors = summary.get("errors", [])
|
|
@@ -281,11 +356,9 @@ def _aggregate_for_display(
|
|
|
281
356
|
return False
|
|
282
357
|
if n.lower() in {"apps", "packages", "pkg", "file", "37"}:
|
|
283
358
|
return False
|
|
284
|
-
|
|
285
|
-
return False
|
|
286
|
-
return True
|
|
359
|
+
return re.search(r"[A-Za-z]", n) is not None
|
|
287
360
|
|
|
288
|
-
uploads_by_app:
|
|
361
|
+
uploads_by_app: dict[str, set] = {}
|
|
289
362
|
for u in uploads:
|
|
290
363
|
if isinstance(u, dict):
|
|
291
364
|
name = (u.get("name") or "-").strip()
|
|
@@ -297,7 +370,7 @@ def _aggregate_for_display(
|
|
|
297
370
|
name = "-"
|
|
298
371
|
uploads_by_app.setdefault(name, set()).add(ver)
|
|
299
372
|
|
|
300
|
-
policies_by_name:
|
|
373
|
+
policies_by_name: dict[str, set] = {}
|
|
301
374
|
for p in policies:
|
|
302
375
|
if isinstance(p, dict):
|
|
303
376
|
name = (p.get("name") or "-").strip()
|
|
@@ -307,7 +380,7 @@ def _aggregate_for_display(
|
|
|
307
380
|
action = "-"
|
|
308
381
|
policies_by_name.setdefault(name, set()).add(action)
|
|
309
382
|
|
|
310
|
-
error_categories:
|
|
383
|
+
error_categories: dict[str, int] = {
|
|
311
384
|
"trust": 0,
|
|
312
385
|
"signature": 0,
|
|
313
386
|
"download": 0,
|
|
@@ -353,9 +426,9 @@ def _aggregate_for_display(
|
|
|
353
426
|
return uploads_by_app, policies_by_name, error_categories
|
|
354
427
|
|
|
355
428
|
|
|
356
|
-
def render_job_summary(summary:
|
|
357
|
-
lines:
|
|
358
|
-
title_bits:
|
|
429
|
+
def render_job_summary(summary: dict, environment: str, run_date: str) -> str:
|
|
430
|
+
lines: list[str] = []
|
|
431
|
+
title_bits: list[str] = []
|
|
359
432
|
if environment:
|
|
360
433
|
title_bits.append(environment)
|
|
361
434
|
if run_date:
|
|
@@ -394,8 +467,13 @@ def render_job_summary(summary: Dict, environment: str, run_date: str) -> str:
|
|
|
394
467
|
pkg = row.get("package", "-")
|
|
395
468
|
pkg_url = row.get("package_url")
|
|
396
469
|
pkg_cell = f"[{pkg}]({pkg_url})" if pkg_url else pkg
|
|
470
|
+
recipe_name = row.get("recipe_name", "-")
|
|
471
|
+
recipe_url = row.get("recipe_url")
|
|
472
|
+
recipe_cell = (
|
|
473
|
+
f"[{recipe_name}]({recipe_url})" if recipe_url else recipe_name
|
|
474
|
+
)
|
|
397
475
|
lines.append(
|
|
398
|
-
f"| {
|
|
476
|
+
f"| {recipe_cell} | {row.get('recipe_identifier', '-')} | {pkg_cell} | {row.get('version', '-')} |"
|
|
399
477
|
)
|
|
400
478
|
lines.append("")
|
|
401
479
|
else:
|
|
@@ -410,8 +488,16 @@ def render_job_summary(summary: Dict, environment: str, run_date: str) -> str:
|
|
|
410
488
|
for row in sorted(
|
|
411
489
|
summary["policy_rows"], key=lambda r: str(r.get("recipe_name", "")).lower()
|
|
412
490
|
):
|
|
491
|
+
recipe_name = row.get("recipe_name", "-")
|
|
492
|
+
recipe_url = row.get("recipe_url")
|
|
493
|
+
recipe_cell = (
|
|
494
|
+
f"[{recipe_name}]({recipe_url})" if recipe_url else recipe_name
|
|
495
|
+
)
|
|
496
|
+
policy = row.get("policy", "-")
|
|
497
|
+
policy_url = row.get("policy_url")
|
|
498
|
+
policy_cell = f"[{policy}]({policy_url})" if policy_url else policy
|
|
413
499
|
lines.append(
|
|
414
|
-
f"| {
|
|
500
|
+
f"| {recipe_cell} | {row.get('recipe_identifier', '-')} | {policy_cell} |"
|
|
415
501
|
)
|
|
416
502
|
lines.append("")
|
|
417
503
|
|
|
@@ -435,15 +521,15 @@ def render_job_summary(summary: Dict, environment: str, run_date: str) -> str:
|
|
|
435
521
|
return "\n".join(lines)
|
|
436
522
|
|
|
437
523
|
|
|
438
|
-
def render_issue_body(summary:
|
|
439
|
-
lines:
|
|
524
|
+
def render_issue_body(summary: dict, environment: str, run_date: str) -> str:
|
|
525
|
+
lines: list[str] = []
|
|
440
526
|
total_errors = len(summary.get("errors", []))
|
|
441
527
|
_uploads_by_app, _policies_by_name, _error_categories = _aggregate_for_display(
|
|
442
528
|
summary
|
|
443
529
|
)
|
|
444
530
|
|
|
445
531
|
prefix = "Autopkg run"
|
|
446
|
-
suffix_bits:
|
|
532
|
+
suffix_bits: list[str] = []
|
|
447
533
|
if run_date:
|
|
448
534
|
suffix_bits.append(f"on {run_date}")
|
|
449
535
|
if environment:
|
|
@@ -523,10 +609,10 @@ def _normalize_host(url: str) -> str:
|
|
|
523
609
|
return h.rstrip("/")
|
|
524
610
|
|
|
525
611
|
|
|
526
|
-
def build_pkg_map(jss_url: str, client_id: str, client_secret: str) ->
|
|
612
|
+
def build_pkg_map(jss_url: str, client_id: str, client_secret: str) -> dict[str, str]:
|
|
527
613
|
host = _normalize_host(jss_url)
|
|
528
614
|
_ = host # silence linters about unused var; kept for readability
|
|
529
|
-
pkg_map:
|
|
615
|
+
pkg_map: dict[str, str] = {}
|
|
530
616
|
try:
|
|
531
617
|
from jamf_pro_sdk import ( # type: ignore
|
|
532
618
|
ApiClientCredentialsProvider,
|
|
@@ -540,8 +626,8 @@ def build_pkg_map(jss_url: str, client_id: str, client_secret: str) -> Dict[str,
|
|
|
540
626
|
packages = client.pro_api.get_packages_v1()
|
|
541
627
|
for p in packages:
|
|
542
628
|
try:
|
|
543
|
-
name = str(
|
|
544
|
-
pid = str(
|
|
629
|
+
name = str(p.packageName).strip()
|
|
630
|
+
pid = str(p.id).strip()
|
|
545
631
|
except Exception as e: # noqa: F841
|
|
546
632
|
# ignore objects that do not match expected shape
|
|
547
633
|
continue
|
|
@@ -555,35 +641,92 @@ def build_pkg_map(jss_url: str, client_id: str, client_secret: str) -> Dict[str,
|
|
|
555
641
|
return pkg_map
|
|
556
642
|
|
|
557
643
|
|
|
558
|
-
def
|
|
644
|
+
def build_policy_map(
|
|
645
|
+
jss_url: str, client_id: str, client_secret: str
|
|
646
|
+
) -> dict[str, str]:
|
|
647
|
+
host = _normalize_host(jss_url)
|
|
648
|
+
_ = host # silence linters about unused var; kept for readability
|
|
649
|
+
policy_map: dict[str, str] = {}
|
|
650
|
+
try:
|
|
651
|
+
from jamf_pro_sdk import ( # type: ignore
|
|
652
|
+
ApiClientCredentialsProvider,
|
|
653
|
+
JamfProClient,
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
client = JamfProClient(
|
|
657
|
+
_normalize_host(jss_url),
|
|
658
|
+
ApiClientCredentialsProvider(client_id, client_secret),
|
|
659
|
+
)
|
|
660
|
+
policies = client.pro_api.get_policies()
|
|
661
|
+
for p in policies:
|
|
662
|
+
try:
|
|
663
|
+
name = str(p.name).strip()
|
|
664
|
+
pid = str(p.id).strip()
|
|
665
|
+
except Exception:
|
|
666
|
+
continue
|
|
667
|
+
if not name or not pid:
|
|
668
|
+
continue
|
|
669
|
+
url = f"{jss_url}/policies.html?id={pid}"
|
|
670
|
+
if name not in policy_map:
|
|
671
|
+
policy_map[name] = url
|
|
672
|
+
except Exception:
|
|
673
|
+
return {}
|
|
674
|
+
return policy_map
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def enrich_upload_rows(upload_rows: list[dict], pkg_map: dict[str, str]) -> int:
|
|
559
678
|
linked = 0
|
|
679
|
+
norm_map = {k.lower(): v for k, v in pkg_map.items()}
|
|
560
680
|
for row in upload_rows:
|
|
561
681
|
pkg_name = str(row.get("package") or "").strip()
|
|
562
|
-
url = pkg_map.get(pkg_name)
|
|
682
|
+
url = pkg_map.get(pkg_name) or norm_map.get(pkg_name.lower())
|
|
563
683
|
if url:
|
|
564
684
|
row["package_url"] = url
|
|
565
685
|
linked += 1
|
|
566
686
|
return linked
|
|
567
687
|
|
|
568
688
|
|
|
689
|
+
def enrich_policy_rows(policy_rows: list[dict], policy_map: dict[str, str]) -> int:
|
|
690
|
+
linked = 0
|
|
691
|
+
norm_map = {k.lower(): v for k, v in policy_map.items()}
|
|
692
|
+
for row in policy_rows:
|
|
693
|
+
policy_name = str(row.get("policy") or "").strip()
|
|
694
|
+
url = policy_map.get(policy_name) or norm_map.get(policy_name.lower())
|
|
695
|
+
if url:
|
|
696
|
+
row["policy_url"] = url
|
|
697
|
+
linked += 1
|
|
698
|
+
return linked
|
|
699
|
+
|
|
700
|
+
|
|
569
701
|
def enrich_upload_rows_with_jamf(
|
|
570
|
-
summary:
|
|
571
|
-
) ->
|
|
702
|
+
summary: dict, jss_url: str, client_id: str, client_secret: str
|
|
703
|
+
) -> tuple[int, list[str]]:
|
|
572
704
|
pkg_map = build_pkg_map(jss_url, client_id, client_secret)
|
|
573
705
|
linked = enrich_upload_rows(summary.get("upload_rows", []), pkg_map)
|
|
574
706
|
return linked, sorted(set(pkg_map.keys()))
|
|
575
707
|
|
|
576
708
|
|
|
709
|
+
def enrich_policy_rows_with_jamf(
|
|
710
|
+
summary: dict, jss_url: str, client_id: str, client_secret: str
|
|
711
|
+
) -> tuple[int, list[str]]:
|
|
712
|
+
policy_map = build_policy_map(jss_url, client_id, client_secret)
|
|
713
|
+
linked = enrich_policy_rows(summary.get("policy_rows", []), policy_map)
|
|
714
|
+
return linked, sorted(set(policy_map.keys()))
|
|
715
|
+
|
|
716
|
+
|
|
577
717
|
def process_reports(
|
|
578
718
|
*,
|
|
579
|
-
zip_file:
|
|
719
|
+
zip_file: str | None,
|
|
580
720
|
extract_dir: str,
|
|
581
|
-
reports_dir:
|
|
721
|
+
reports_dir: str | None,
|
|
582
722
|
environment: str = "",
|
|
583
723
|
run_date: str = "",
|
|
584
724
|
out_dir: str,
|
|
585
725
|
debug: bool,
|
|
586
726
|
strict: bool,
|
|
727
|
+
repo_url: str | None = None,
|
|
728
|
+
repo_branch: str | None = None,
|
|
729
|
+
repo_path: str | None = None,
|
|
587
730
|
) -> int:
|
|
588
731
|
os.makedirs(out_dir, exist_ok=True)
|
|
589
732
|
|
|
@@ -598,23 +741,38 @@ def process_reports(
|
|
|
598
741
|
else:
|
|
599
742
|
process_dir = reports_dir or extract_dir
|
|
600
743
|
|
|
601
|
-
|
|
744
|
+
recipe_link_map = _build_recipe_link_map(repo_path, repo_url, repo_branch)
|
|
745
|
+
summary = aggregate_reports(process_dir, recipe_link_map=recipe_link_map)
|
|
602
746
|
|
|
603
747
|
jss_url = os.environ.get("AUTOPKG_JSS_URL")
|
|
604
748
|
jss_client_id = os.environ.get("AUTOPKG_CLIENT_ID")
|
|
605
749
|
jss_client_secret = os.environ.get("AUTOPKG_CLIENT_SECRET")
|
|
606
750
|
jamf_attempted = False
|
|
607
751
|
jamf_linked = 0
|
|
608
|
-
jamf_keys:
|
|
752
|
+
jamf_keys: list[str] = []
|
|
753
|
+
jamf_policy_linked = 0
|
|
754
|
+
jamf_policy_keys: list[str] = []
|
|
609
755
|
jamf_total = len(summary.get("upload_rows", []))
|
|
610
|
-
|
|
756
|
+
jamf_policy_total = len(summary.get("policy_rows", []))
|
|
757
|
+
if (
|
|
758
|
+
jss_url
|
|
759
|
+
and jss_client_id
|
|
760
|
+
and jss_client_secret
|
|
761
|
+
and (jamf_total or jamf_policy_total)
|
|
762
|
+
):
|
|
611
763
|
jamf_attempted = True
|
|
612
764
|
try:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
765
|
+
if jamf_total:
|
|
766
|
+
jamf_linked, jamf_keys = enrich_upload_rows_with_jamf(
|
|
767
|
+
summary, jss_url, jss_client_id, jss_client_secret
|
|
768
|
+
)
|
|
769
|
+
if jamf_policy_total:
|
|
770
|
+
jamf_policy_linked, jamf_policy_keys = enrich_policy_rows_with_jamf(
|
|
771
|
+
summary, jss_url, jss_client_id, jss_client_secret
|
|
772
|
+
)
|
|
616
773
|
except Exception:
|
|
617
774
|
jamf_linked = 0
|
|
775
|
+
jamf_policy_linked = 0
|
|
618
776
|
|
|
619
777
|
job_md = render_job_summary(summary, environment, run_date)
|
|
620
778
|
issue_md = None
|
|
@@ -636,20 +794,38 @@ def process_reports(
|
|
|
636
794
|
str(r.get("package") or "").strip()
|
|
637
795
|
for r in summary.get("upload_rows", [])
|
|
638
796
|
]
|
|
797
|
+
policy_names = [
|
|
798
|
+
str(r.get("policy") or "").strip()
|
|
799
|
+
for r in summary.get("policy_rows", [])
|
|
800
|
+
]
|
|
639
801
|
matched = [
|
|
640
802
|
r for r in summary.get("upload_rows", []) if r.get("package_url")
|
|
641
803
|
]
|
|
642
804
|
unmatched = [
|
|
643
805
|
r for r in summary.get("upload_rows", []) if not r.get("package_url")
|
|
644
806
|
]
|
|
807
|
+
policy_matched = [
|
|
808
|
+
r for r in summary.get("policy_rows", []) if r.get("policy_url")
|
|
809
|
+
]
|
|
810
|
+
policy_unmatched = [
|
|
811
|
+
r for r in summary.get("policy_rows", []) if not r.get("policy_url")
|
|
812
|
+
]
|
|
645
813
|
diag = {
|
|
646
814
|
"jss_url": jss_url or "",
|
|
647
815
|
"jamf_keys_count": len(jamf_keys),
|
|
648
816
|
"jamf_keys_sample": jamf_keys[:20],
|
|
817
|
+
"jamf_policy_keys_count": len(jamf_policy_keys),
|
|
818
|
+
"jamf_policy_keys_sample": jamf_policy_keys[:20],
|
|
649
819
|
"uploads_count": len(upload_pkg_names),
|
|
650
820
|
"matched_count": len(matched),
|
|
651
821
|
"unmatched_count": len(unmatched),
|
|
652
822
|
"unmatched_names": [r.get("package") for r in unmatched][:20],
|
|
823
|
+
"policies_count": len(policy_names),
|
|
824
|
+
"policy_matched_count": len(policy_matched),
|
|
825
|
+
"policy_unmatched_count": len(policy_unmatched),
|
|
826
|
+
"policy_unmatched_names": [r.get("policy") for r in policy_unmatched][
|
|
827
|
+
:20
|
|
828
|
+
],
|
|
653
829
|
}
|
|
654
830
|
with open(jamf_log_path, "w", encoding="utf-8") as jf:
|
|
655
831
|
json.dump(diag, jf, indent=2)
|
|
@@ -662,11 +838,13 @@ def process_reports(
|
|
|
662
838
|
f"Errors file: {'errors_issue.md' if issue_md else 'none'}",
|
|
663
839
|
]
|
|
664
840
|
if jamf_attempted:
|
|
665
|
-
status.append(
|
|
841
|
+
status.append(
|
|
842
|
+
f"Jamf links added: packages {jamf_linked}/{jamf_total}, policies {jamf_policy_linked}/{jamf_policy_total}"
|
|
843
|
+
)
|
|
666
844
|
if jamf_log_path:
|
|
667
845
|
status.append(f"Jamf lookup log: '{jamf_log_path}'")
|
|
668
846
|
else:
|
|
669
|
-
status.append("Jamf links: skipped (missing env or no uploads)")
|
|
847
|
+
status.append("Jamf links: skipped (missing env or no uploads/policies)")
|
|
670
848
|
logging.info(". ".join(status))
|
|
671
849
|
|
|
672
850
|
if strict and summary.get("errors"):
|