autopkg-wrapper 2026.2.5__tar.gz → 2026.2.6__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.
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/PKG-INFO +1 -1
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/autopkg_wrapper.py +25 -139
- autopkg_wrapper-2026.2.6/autopkg_wrapper/models/recipe.py +139 -0
- autopkg_wrapper-2026.2.6/autopkg_wrapper/utils/recipe_batching.py +41 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/pyproject.toml +1 -1
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_autopkg_commands.py +41 -6
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/uv.lock +1 -1
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/dependabot.yml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/build-publish.yml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/codeql.yml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/dependency-review.yml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.gitignore +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.pre-commit-config.yaml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/CONTRIBUTING +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/LICENSE +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/README.md +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/.github/workflows/autopkg-wrapper-demo.yml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/overrides/Google_Chrome.pkg.recipe.yaml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/repo_list.txt +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/requirements.txt +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/__init__.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/notifier/__init__.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/notifier/slack.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/__init__.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/args.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/git_functions.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/logging.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/recipe_ordering.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/report_processor.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/mise.toml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/__init__.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/prefs.json +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/prefs.plist +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/recipe_list.json +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/recipe_list.txt +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/recipe_list.yaml +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_args_utils.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_git_functions.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_order_recipe_list.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_parse_recipe_list.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_report_processor.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_setup_logger.py +0 -0
- {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_slack_notifier.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autopkg-wrapper
|
|
3
|
-
Version: 2026.2.
|
|
3
|
+
Version: 2026.2.6
|
|
4
4
|
Summary: A package used to execute some autopkg functions, primarily within the context of a GitHub Actions runner.
|
|
5
5
|
Project-URL: Repository, https://github.com/smithjw/autopkg-wrapper
|
|
6
6
|
Author-email: James Smith <james@smithjw.me>
|
|
@@ -2,151 +2,20 @@
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import plistlib
|
|
5
|
-
import subprocess
|
|
6
5
|
import sys
|
|
7
6
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from itertools import chain
|
|
10
7
|
from pathlib import Path
|
|
11
8
|
|
|
12
9
|
import autopkg_wrapper.utils.git_functions as git
|
|
10
|
+
from autopkg_wrapper.models.recipe import Recipe
|
|
13
11
|
from autopkg_wrapper.notifier import slack
|
|
14
12
|
from autopkg_wrapper.utils.args import setup_args
|
|
15
13
|
from autopkg_wrapper.utils.logging import setup_logger
|
|
14
|
+
from autopkg_wrapper.utils.recipe_batching import build_recipe_batches, recipe_type_for
|
|
16
15
|
from autopkg_wrapper.utils.recipe_ordering import order_recipe_list
|
|
17
16
|
from autopkg_wrapper.utils.report_processor import process_reports
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
class Recipe(object):
|
|
21
|
-
def __init__(self, name: str, post_processors: list = None):
|
|
22
|
-
self.filename = name
|
|
23
|
-
self.error = False
|
|
24
|
-
self.results = {}
|
|
25
|
-
self.updated = False
|
|
26
|
-
self.verified = None
|
|
27
|
-
self.pr_url = None
|
|
28
|
-
self.post_processors = post_processors
|
|
29
|
-
|
|
30
|
-
self._keys = None
|
|
31
|
-
self._has_run = False
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def name(self):
|
|
35
|
-
name = self.filename.split(".")[0]
|
|
36
|
-
|
|
37
|
-
return name
|
|
38
|
-
|
|
39
|
-
def verify_trust_info(self, args):
|
|
40
|
-
verbose_output = ["-vvvv"] if args.debug else []
|
|
41
|
-
prefs_file = (
|
|
42
|
-
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
|
|
43
|
-
)
|
|
44
|
-
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
45
|
-
cmd = (
|
|
46
|
-
[autopkg_bin, "verify-trust-info", self.filename]
|
|
47
|
-
+ verbose_output
|
|
48
|
-
+ prefs_file
|
|
49
|
-
)
|
|
50
|
-
logging.debug(f"cmd: {cmd}")
|
|
51
|
-
|
|
52
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
53
|
-
if result.returncode == 0:
|
|
54
|
-
self.verified = True
|
|
55
|
-
else:
|
|
56
|
-
self.results["message"] = (result.stderr or "").strip()
|
|
57
|
-
self.verified = False
|
|
58
|
-
return self.verified
|
|
59
|
-
|
|
60
|
-
def update_trust_info(self, args):
|
|
61
|
-
prefs_file = (
|
|
62
|
-
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
|
|
63
|
-
)
|
|
64
|
-
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
65
|
-
cmd = [autopkg_bin, "update-trust-info", self.filename] + prefs_file
|
|
66
|
-
logging.debug(f"cmd: {cmd}")
|
|
67
|
-
|
|
68
|
-
# Fail loudly if this exits 0
|
|
69
|
-
try:
|
|
70
|
-
subprocess.check_call(cmd)
|
|
71
|
-
except subprocess.CalledProcessError as e:
|
|
72
|
-
logging.error(str(e))
|
|
73
|
-
raise e
|
|
74
|
-
|
|
75
|
-
def _parse_report(self, report):
|
|
76
|
-
with open(report, "rb") as f:
|
|
77
|
-
report_data = plistlib.load(f)
|
|
78
|
-
|
|
79
|
-
failed_items = report_data.get("failures", [])
|
|
80
|
-
imported_items = []
|
|
81
|
-
if report_data["summary_results"]:
|
|
82
|
-
# This means something happened
|
|
83
|
-
munki_results = report_data["summary_results"].get(
|
|
84
|
-
"munki_importer_summary_result", {}
|
|
85
|
-
)
|
|
86
|
-
imported_items.extend(munki_results.get("data_rows", []))
|
|
87
|
-
|
|
88
|
-
return {"imported": imported_items, "failed": failed_items}
|
|
89
|
-
|
|
90
|
-
def run(self, args):
|
|
91
|
-
if self.verified is False:
|
|
92
|
-
self.error = True
|
|
93
|
-
self.results["failed"] = True
|
|
94
|
-
self.results["imported"] = ""
|
|
95
|
-
else:
|
|
96
|
-
report_dir = Path("/private/tmp/autopkg")
|
|
97
|
-
report_time = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
|
|
98
|
-
report_name = Path(f"{self.name}-{report_time}.plist")
|
|
99
|
-
|
|
100
|
-
report_dir.mkdir(parents=True, exist_ok=True)
|
|
101
|
-
report = report_dir / report_name
|
|
102
|
-
report.touch(exist_ok=True)
|
|
103
|
-
|
|
104
|
-
try:
|
|
105
|
-
prefs_file = (
|
|
106
|
-
["--prefs", args.autopkg_prefs.as_posix()]
|
|
107
|
-
if args.autopkg_prefs
|
|
108
|
-
else []
|
|
109
|
-
)
|
|
110
|
-
verbose_output = ["-vvvv"] if args.debug else []
|
|
111
|
-
post_processor_cmd = (
|
|
112
|
-
list(
|
|
113
|
-
chain.from_iterable(
|
|
114
|
-
[
|
|
115
|
-
("--post", processor)
|
|
116
|
-
for processor in self.post_processors
|
|
117
|
-
]
|
|
118
|
-
)
|
|
119
|
-
)
|
|
120
|
-
if self.post_processors
|
|
121
|
-
else []
|
|
122
|
-
)
|
|
123
|
-
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
124
|
-
cmd = [
|
|
125
|
-
autopkg_bin,
|
|
126
|
-
"run",
|
|
127
|
-
self.filename,
|
|
128
|
-
"--report-plist",
|
|
129
|
-
str(report),
|
|
130
|
-
]
|
|
131
|
-
cmd = cmd + post_processor_cmd + verbose_output + prefs_file
|
|
132
|
-
|
|
133
|
-
logging.debug(f"cmd: {cmd}")
|
|
134
|
-
|
|
135
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
136
|
-
if result.returncode != 0:
|
|
137
|
-
self.error = True
|
|
138
|
-
|
|
139
|
-
except Exception:
|
|
140
|
-
self.error = True
|
|
141
|
-
|
|
142
|
-
self._has_run = True
|
|
143
|
-
self.results = self._parse_report(report)
|
|
144
|
-
if not self.results["failed"] and not self.error:
|
|
145
|
-
self.updated = True
|
|
146
|
-
|
|
147
|
-
return self.results
|
|
148
|
-
|
|
149
|
-
|
|
150
19
|
def get_override_repo_info(args):
|
|
151
20
|
if args.overrides_repo_path:
|
|
152
21
|
recipe_override_dirs = args.overrides_repo_path
|
|
@@ -387,12 +256,29 @@ def main():
|
|
|
387
256
|
# Git updates and notifications are applied serially after all recipes finish
|
|
388
257
|
return r
|
|
389
258
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
259
|
+
if args.recipe_processing_order:
|
|
260
|
+
batches = build_recipe_batches(
|
|
261
|
+
recipe_list=recipe_list,
|
|
262
|
+
recipe_processing_order=args.recipe_processing_order,
|
|
263
|
+
)
|
|
264
|
+
for batch in batches:
|
|
265
|
+
batch_type = recipe_type_for(batch[0]) if batch else ""
|
|
266
|
+
logging.info(
|
|
267
|
+
f"Running {len(batch)} recipes for type={batch_type or 'unknown'}"
|
|
268
|
+
)
|
|
269
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
270
|
+
futures = [executor.submit(run_one, r) for r in batch]
|
|
271
|
+
for fut in as_completed(futures):
|
|
272
|
+
r = fut.result()
|
|
273
|
+
if r.error or r.results.get("failed"):
|
|
274
|
+
failed_recipes.append(r)
|
|
275
|
+
else:
|
|
276
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
277
|
+
futures = [executor.submit(run_one, r) for r in recipe_list]
|
|
278
|
+
for fut in as_completed(futures):
|
|
279
|
+
r = fut.result()
|
|
280
|
+
if r.error or r.results.get("failed"):
|
|
281
|
+
failed_recipes.append(r)
|
|
396
282
|
|
|
397
283
|
# Apply git updates serially to avoid branch/commit conflicts when concurrency > 1
|
|
398
284
|
for r in recipe_list:
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import plistlib
|
|
5
|
+
import subprocess
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from itertools import chain
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Recipe(object):
|
|
12
|
+
def __init__(self, name: str, post_processors: list = None):
|
|
13
|
+
self.filename = name
|
|
14
|
+
self.error = False
|
|
15
|
+
self.results = {}
|
|
16
|
+
self.updated = False
|
|
17
|
+
self.verified = None
|
|
18
|
+
self.pr_url = None
|
|
19
|
+
self.post_processors = post_processors
|
|
20
|
+
|
|
21
|
+
self._keys = None
|
|
22
|
+
self._has_run = False
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def name(self):
|
|
26
|
+
name = self.filename.split(".")[0]
|
|
27
|
+
|
|
28
|
+
return name
|
|
29
|
+
|
|
30
|
+
def verify_trust_info(self, args):
|
|
31
|
+
verbose_output = ["-vvvv"] if args.debug else []
|
|
32
|
+
prefs_file = (
|
|
33
|
+
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
|
|
34
|
+
)
|
|
35
|
+
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
36
|
+
cmd = (
|
|
37
|
+
[autopkg_bin, "verify-trust-info", self.filename]
|
|
38
|
+
+ verbose_output
|
|
39
|
+
+ prefs_file
|
|
40
|
+
)
|
|
41
|
+
logging.debug(f"cmd: {cmd}")
|
|
42
|
+
|
|
43
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
44
|
+
if result.returncode == 0:
|
|
45
|
+
self.verified = True
|
|
46
|
+
else:
|
|
47
|
+
self.results["message"] = (result.stderr or "").strip()
|
|
48
|
+
self.verified = False
|
|
49
|
+
return self.verified
|
|
50
|
+
|
|
51
|
+
def update_trust_info(self, args):
|
|
52
|
+
prefs_file = (
|
|
53
|
+
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
|
|
54
|
+
)
|
|
55
|
+
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
56
|
+
cmd = [autopkg_bin, "update-trust-info", self.filename] + prefs_file
|
|
57
|
+
logging.debug(f"cmd: {cmd}")
|
|
58
|
+
|
|
59
|
+
# Fail loudly if this exits 0
|
|
60
|
+
try:
|
|
61
|
+
subprocess.check_call(cmd)
|
|
62
|
+
except subprocess.CalledProcessError as e:
|
|
63
|
+
logging.error(str(e))
|
|
64
|
+
raise e
|
|
65
|
+
|
|
66
|
+
def _parse_report(self, report):
|
|
67
|
+
with open(report, "rb") as f:
|
|
68
|
+
report_data = plistlib.load(f)
|
|
69
|
+
|
|
70
|
+
failed_items = report_data.get("failures", [])
|
|
71
|
+
imported_items = []
|
|
72
|
+
if report_data["summary_results"]:
|
|
73
|
+
# This means something happened
|
|
74
|
+
munki_results = report_data["summary_results"].get(
|
|
75
|
+
"munki_importer_summary_result", {}
|
|
76
|
+
)
|
|
77
|
+
imported_items.extend(munki_results.get("data_rows", []))
|
|
78
|
+
|
|
79
|
+
return {"imported": imported_items, "failed": failed_items}
|
|
80
|
+
|
|
81
|
+
def run(self, args):
|
|
82
|
+
if self.verified is False:
|
|
83
|
+
self.error = True
|
|
84
|
+
self.results["failed"] = True
|
|
85
|
+
self.results["imported"] = ""
|
|
86
|
+
else:
|
|
87
|
+
report_dir = Path("/private/tmp/autopkg")
|
|
88
|
+
report_time = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
|
|
89
|
+
report_name = Path(f"{self.name}-{report_time}.plist")
|
|
90
|
+
|
|
91
|
+
report_dir.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
report = report_dir / report_name
|
|
93
|
+
report.touch(exist_ok=True)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
prefs_file = (
|
|
97
|
+
["--prefs", args.autopkg_prefs.as_posix()]
|
|
98
|
+
if args.autopkg_prefs
|
|
99
|
+
else []
|
|
100
|
+
)
|
|
101
|
+
verbose_output = ["-vvvv"] if args.debug else []
|
|
102
|
+
post_processor_cmd = (
|
|
103
|
+
list(
|
|
104
|
+
chain.from_iterable(
|
|
105
|
+
[
|
|
106
|
+
("--post", processor)
|
|
107
|
+
for processor in self.post_processors
|
|
108
|
+
]
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
if self.post_processors
|
|
112
|
+
else []
|
|
113
|
+
)
|
|
114
|
+
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
115
|
+
cmd = (
|
|
116
|
+
[autopkg_bin, "run", self.filename, "--report-plist", report]
|
|
117
|
+
+ verbose_output
|
|
118
|
+
+ prefs_file
|
|
119
|
+
+ post_processor_cmd
|
|
120
|
+
)
|
|
121
|
+
logging.debug(f"cmd: {cmd}")
|
|
122
|
+
|
|
123
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
124
|
+
if result.returncode == 0:
|
|
125
|
+
report_info = self._parse_report(report)
|
|
126
|
+
self.results = report_info
|
|
127
|
+
else:
|
|
128
|
+
self.error = True
|
|
129
|
+
self.results["failed"] = True
|
|
130
|
+
self.results["message"] = (result.stderr or "").strip()
|
|
131
|
+
self.results["imported"] = ""
|
|
132
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
133
|
+
logging.error(f"Recipe run failed: {e}")
|
|
134
|
+
self.error = True
|
|
135
|
+
self.results["failed"] = True
|
|
136
|
+
self.results["message"] = (result.stderr or "").strip()
|
|
137
|
+
self.results["imported"] = ""
|
|
138
|
+
|
|
139
|
+
return self
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterable, Protocol, TypeVar
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HasFilename(Protocol):
|
|
7
|
+
filename: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T", bound=HasFilename)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def recipe_type_for(recipe: HasFilename) -> str:
|
|
14
|
+
parts = recipe.filename.split(".", 1)
|
|
15
|
+
return parts[1] if len(parts) == 2 else ""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_recipe_batches(
|
|
19
|
+
recipe_list: Iterable[T], recipe_processing_order
|
|
20
|
+
) -> list[list[T]]:
|
|
21
|
+
recipe_list = list(recipe_list)
|
|
22
|
+
if not recipe_list:
|
|
23
|
+
return []
|
|
24
|
+
if not recipe_processing_order:
|
|
25
|
+
return [recipe_list]
|
|
26
|
+
|
|
27
|
+
batches: list[list[T]] = []
|
|
28
|
+
current_batch: list[T] = []
|
|
29
|
+
current_type = None
|
|
30
|
+
for recipe in recipe_list:
|
|
31
|
+
r_type = recipe_type_for(recipe)
|
|
32
|
+
if current_type is None or r_type == current_type:
|
|
33
|
+
current_batch.append(recipe)
|
|
34
|
+
current_type = r_type
|
|
35
|
+
continue
|
|
36
|
+
batches.append(current_batch)
|
|
37
|
+
current_batch = [recipe]
|
|
38
|
+
current_type = r_type
|
|
39
|
+
if current_batch:
|
|
40
|
+
batches.append(current_batch)
|
|
41
|
+
return batches
|
|
@@ -4,7 +4,8 @@ from pathlib import Path as RealPath
|
|
|
4
4
|
from types import SimpleNamespace
|
|
5
5
|
from unittest.mock import patch
|
|
6
6
|
|
|
7
|
-
from autopkg_wrapper.
|
|
7
|
+
from autopkg_wrapper.models.recipe import Recipe
|
|
8
|
+
from autopkg_wrapper.utils.recipe_batching import build_recipe_batches
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class TestAutopkgCommands(unittest.TestCase):
|
|
@@ -14,7 +15,7 @@ class TestAutopkgCommands(unittest.TestCase):
|
|
|
14
15
|
debug=False, autopkg_prefs=None, autopkg_bin="/custom/autopkg"
|
|
15
16
|
)
|
|
16
17
|
|
|
17
|
-
with patch("autopkg_wrapper.
|
|
18
|
+
with patch("autopkg_wrapper.models.recipe.subprocess.run") as run:
|
|
18
19
|
run.return_value = SimpleNamespace(returncode=0, stderr="", stdout="")
|
|
19
20
|
ok = r.verify_trust_info(args)
|
|
20
21
|
|
|
@@ -30,7 +31,7 @@ class TestAutopkgCommands(unittest.TestCase):
|
|
|
30
31
|
debug=True, autopkg_prefs=None, autopkg_bin="/usr/local/bin/autopkg"
|
|
31
32
|
)
|
|
32
33
|
|
|
33
|
-
with patch("autopkg_wrapper.
|
|
34
|
+
with patch("autopkg_wrapper.models.recipe.subprocess.run") as run:
|
|
34
35
|
run.return_value = SimpleNamespace(
|
|
35
36
|
returncode=1, stderr="bad trust", stdout=""
|
|
36
37
|
)
|
|
@@ -43,7 +44,7 @@ class TestAutopkgCommands(unittest.TestCase):
|
|
|
43
44
|
r = Recipe("Foo.download")
|
|
44
45
|
args = SimpleNamespace(autopkg_prefs=None, autopkg_bin="/custom/autopkg")
|
|
45
46
|
|
|
46
|
-
with patch("autopkg_wrapper.
|
|
47
|
+
with patch("autopkg_wrapper.models.recipe.subprocess.check_call") as cc:
|
|
47
48
|
r.update_trust_info(args)
|
|
48
49
|
|
|
49
50
|
called_cmd = cc.call_args.args[0]
|
|
@@ -66,8 +67,8 @@ class TestAutopkgCommands(unittest.TestCase):
|
|
|
66
67
|
return RealPath(td)
|
|
67
68
|
return RealPath(arg)
|
|
68
69
|
|
|
69
|
-
with patch("autopkg_wrapper.
|
|
70
|
-
with patch("autopkg_wrapper.
|
|
70
|
+
with patch("autopkg_wrapper.models.recipe.Path", side_effect=fake_path):
|
|
71
|
+
with patch("autopkg_wrapper.models.recipe.subprocess.run") as run:
|
|
71
72
|
run.return_value = SimpleNamespace(
|
|
72
73
|
returncode=0, stderr="", stdout=""
|
|
73
74
|
)
|
|
@@ -84,6 +85,40 @@ class TestAutopkgCommands(unittest.TestCase):
|
|
|
84
85
|
self.assertEqual(called_cmd[1], "run")
|
|
85
86
|
self.assertIn("--report-plist", called_cmd)
|
|
86
87
|
|
|
88
|
+
def test_build_recipe_batches_without_processing_order(self):
|
|
89
|
+
recipes = [Recipe("Foo.upload.jamf"), Recipe("Foo.auto_install.jamf")]
|
|
90
|
+
batches = build_recipe_batches(
|
|
91
|
+
recipe_list=recipes, recipe_processing_order=None
|
|
92
|
+
)
|
|
93
|
+
self.assertEqual(len(batches), 1)
|
|
94
|
+
self.assertEqual(
|
|
95
|
+
[r.filename for r in batches[0]], [r.filename for r in recipes]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def test_build_recipe_batches_groups_by_type(self):
|
|
99
|
+
recipes = [
|
|
100
|
+
Recipe("Foo.upload.jamf"),
|
|
101
|
+
Recipe("Bar.upload.jamf"),
|
|
102
|
+
Recipe("Foo.auto_install.jamf"),
|
|
103
|
+
Recipe("Foo.self_service.jamf"),
|
|
104
|
+
]
|
|
105
|
+
batches = build_recipe_batches(
|
|
106
|
+
recipe_list=recipes, recipe_processing_order=["upload", "auto_install"]
|
|
107
|
+
)
|
|
108
|
+
self.assertEqual(len(batches), 3)
|
|
109
|
+
self.assertEqual(
|
|
110
|
+
[r.filename for r in batches[0]],
|
|
111
|
+
["Foo.upload.jamf", "Bar.upload.jamf"],
|
|
112
|
+
)
|
|
113
|
+
self.assertEqual(
|
|
114
|
+
[r.filename for r in batches[1]],
|
|
115
|
+
["Foo.auto_install.jamf"],
|
|
116
|
+
)
|
|
117
|
+
self.assertEqual(
|
|
118
|
+
[r.filename for r in batches[2]],
|
|
119
|
+
["Foo.self_service.jamf"],
|
|
120
|
+
)
|
|
121
|
+
|
|
87
122
|
|
|
88
123
|
if __name__ == "__main__":
|
|
89
124
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/dependency-review.yml
RENAMED
|
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
|
{autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/git_functions.py
RENAMED
|
File without changes
|
|
File without changes
|
{autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/recipe_ordering.py
RENAMED
|
File without changes
|
{autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/report_processor.py
RENAMED
|
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
|