autopkg-wrapper 2025.11.1__tar.gz → 2026.2.5__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-2025.11.1 → autopkg_wrapper-2026.2.5}/.github/workflows/build-publish.yml +17 -20
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/.github/workflows/codeql.yml +3 -3
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/.github/workflows/dependency-review.yml +1 -1
- autopkg_wrapper-2026.2.5/PKG-INFO +107 -0
- autopkg_wrapper-2026.2.5/README.md +88 -0
- autopkg_wrapper-2026.2.5/actions-demo/requirements.txt +3 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/autopkg_wrapper.py +103 -52
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/notifier/slack.py +1 -1
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/utils/args.py +70 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/utils/git_functions.py +20 -5
- autopkg_wrapper-2026.2.5/autopkg_wrapper/utils/recipe_ordering.py +149 -0
- autopkg_wrapper-2026.2.5/autopkg_wrapper/utils/report_processor.py +674 -0
- autopkg_wrapper-2026.2.5/mise.toml +25 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/pyproject.toml +3 -2
- autopkg_wrapper-2026.2.5/tests/test_args_utils.py +79 -0
- autopkg_wrapper-2026.2.5/tests/test_autopkg_commands.py +89 -0
- autopkg_wrapper-2026.2.5/tests/test_git_functions.py +81 -0
- autopkg_wrapper-2026.2.5/tests/test_order_recipe_list.py +86 -0
- autopkg_wrapper-2026.2.5/tests/test_parse_recipe_list.py +120 -0
- autopkg_wrapper-2026.2.5/tests/test_report_processor.py +121 -0
- autopkg_wrapper-2026.2.5/tests/test_setup_logger.py +26 -0
- autopkg_wrapper-2026.2.5/tests/test_slack_notifier.py +84 -0
- autopkg_wrapper-2026.2.5/uv.lock +418 -0
- autopkg_wrapper-2025.11.1/.tool-versions +0 -1
- autopkg_wrapper-2025.11.1/PKG-INFO +0 -54
- autopkg_wrapper-2025.11.1/README.md +0 -36
- autopkg_wrapper-2025.11.1/actions-demo/requirements.txt +0 -3
- autopkg_wrapper-2025.11.1/uv.lock +0 -279
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/.github/dependabot.yml +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/.gitignore +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/.pre-commit-config.yaml +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/CONTRIBUTING +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/LICENSE +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/actions-demo/.github/workflows/autopkg-wrapper-demo.yml +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/actions-demo/overrides/Google_Chrome.pkg.recipe.yaml +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/actions-demo/repo_list.txt +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/__init__.py +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/notifier/__init__.py +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/utils/__init__.py +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/autopkg_wrapper/utils/logging.py +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/tests/__init__.py +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/tests/prefs.json +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/tests/prefs.plist +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/tests/recipe_list.json +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/tests/recipe_list.txt +0 -0
- {autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/tests/recipe_list.yaml +0 -0
|
@@ -34,7 +34,6 @@ jobs:
|
|
|
34
34
|
contents: write
|
|
35
35
|
|
|
36
36
|
steps:
|
|
37
|
-
# - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
|
38
37
|
- id: check-inputs
|
|
39
38
|
env:
|
|
40
39
|
INPUT_DRY_RUN: ${{ github.event.inputs.dry_run }}
|
|
@@ -104,26 +103,24 @@ jobs:
|
|
|
104
103
|
id-token: write
|
|
105
104
|
|
|
106
105
|
steps:
|
|
107
|
-
- uses: actions/checkout@
|
|
106
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
108
107
|
|
|
109
|
-
- name: Setup
|
|
110
|
-
uses:
|
|
108
|
+
- name: Setup mise
|
|
109
|
+
uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
|
|
111
110
|
with:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
cache-dependency-glob: uv.lock
|
|
111
|
+
install: true
|
|
112
|
+
cache: true
|
|
115
113
|
|
|
116
|
-
- name:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
- name: Run tests
|
|
115
|
+
run: mise run test
|
|
116
|
+
|
|
117
|
+
- name: Build package
|
|
118
|
+
env:
|
|
119
|
+
RELEASE_VERSION: ${{ needs.release.outputs.version }}
|
|
120
|
+
run: mise run build
|
|
120
121
|
|
|
121
|
-
- name: Build Package with UV
|
|
122
|
-
run: |
|
|
123
|
-
uv version ${{ needs.release.outputs.version }}
|
|
124
|
-
uv build
|
|
125
122
|
- name: Upload Package Artifacts
|
|
126
|
-
uses: actions/upload-artifact@
|
|
123
|
+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
127
124
|
with:
|
|
128
125
|
name: python-package-distributions
|
|
129
126
|
path: dist/
|
|
@@ -142,7 +139,7 @@ jobs:
|
|
|
142
139
|
|
|
143
140
|
steps:
|
|
144
141
|
- name: Download Package Artifacts
|
|
145
|
-
uses: actions/download-artifact@
|
|
142
|
+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
146
143
|
with:
|
|
147
144
|
name: python-package-distributions
|
|
148
145
|
path: dist/
|
|
@@ -166,7 +163,7 @@ jobs:
|
|
|
166
163
|
|
|
167
164
|
steps:
|
|
168
165
|
- name: Download Package Artifacts
|
|
169
|
-
uses: actions/download-artifact@
|
|
166
|
+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
170
167
|
with:
|
|
171
168
|
name: python-package-distributions
|
|
172
169
|
path: dist/
|
|
@@ -185,11 +182,11 @@ jobs:
|
|
|
185
182
|
|
|
186
183
|
steps:
|
|
187
184
|
- name: Download Package Artifacts
|
|
188
|
-
uses: actions/download-artifact@
|
|
185
|
+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
189
186
|
with:
|
|
190
187
|
name: python-package-distributions
|
|
191
188
|
path: dist/
|
|
192
|
-
- uses: sigstore/gh-action-sigstore-python@
|
|
189
|
+
- uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d # v3.2.0
|
|
193
190
|
with:
|
|
194
191
|
inputs: |
|
|
195
192
|
dist/*.whl
|
|
@@ -27,14 +27,14 @@ jobs:
|
|
|
27
27
|
|
|
28
28
|
steps:
|
|
29
29
|
- name: Checkout repository
|
|
30
|
-
uses: actions/checkout@
|
|
30
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
31
31
|
|
|
32
32
|
- name: Initialize CodeQL
|
|
33
|
-
uses: github/codeql-action/init@
|
|
33
|
+
uses: github/codeql-action/init@25a224b8085c21d4d61b7fc051468805fc3ac490 # codeql-bundle-v2.24.0
|
|
34
34
|
with:
|
|
35
35
|
languages: ${{ matrix.language }}
|
|
36
36
|
|
|
37
37
|
- name: Perform CodeQL Analysis
|
|
38
|
-
uses: github/codeql-action/analyze@
|
|
38
|
+
uses: github/codeql-action/analyze@25a224b8085c21d4d61b7fc051468805fc3ac490 # codeql-bundle-v2.24.0
|
|
39
39
|
with:
|
|
40
40
|
category: "/language:${{matrix.language}}"
|
{autopkg_wrapper-2025.11.1 → autopkg_wrapper-2026.2.5}/.github/workflows/dependency-review.yml
RENAMED
|
@@ -11,7 +11,7 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
13
|
- name: "Checkout Repository"
|
|
14
|
-
uses: actions/checkout@
|
|
14
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
15
15
|
|
|
16
16
|
- name: "Dependency Review"
|
|
17
17
|
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autopkg-wrapper
|
|
3
|
+
Version: 2026.2.5
|
|
4
|
+
Summary: A package used to execute some autopkg functions, primarily within the context of a GitHub Actions runner.
|
|
5
|
+
Project-URL: Repository, https://github.com/smithjw/autopkg-wrapper
|
|
6
|
+
Author-email: James Smith <james@smithjw.me>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: ~=3.14.0
|
|
10
|
+
Requires-Dist: chardet
|
|
11
|
+
Requires-Dist: idna
|
|
12
|
+
Requires-Dist: jamf-pro-sdk
|
|
13
|
+
Requires-Dist: pygithub
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: ruamel-yaml
|
|
16
|
+
Requires-Dist: toml
|
|
17
|
+
Requires-Dist: urllib3
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# autopkg-wrapper
|
|
21
|
+
|
|
22
|
+
`autopkg_wrapper` is a small package that can be used to run [`autopkg`](https://github.com/autopkg/autopkg) within CI/CD environments such as GitHub Actions.
|
|
23
|
+
|
|
24
|
+
The easiest way to run it is by installing with pip.
|
|
25
|
+
|
|
26
|
+
```shell
|
|
27
|
+
pip install autopkg-wrapper
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Command Line Parameters
|
|
31
|
+
|
|
32
|
+
```shell
|
|
33
|
+
-h, --help Show this help message and exit
|
|
34
|
+
--recipe-file RECIPE_FILE Path to a list of recipes to run (cannot be run with --recipes)
|
|
35
|
+
--recipes [RECIPES ...] Recipes to run with autopkg (cannot be run with --recipe-file)
|
|
36
|
+
--recipe-processing-order [RECIPE_PROCESSING_ORDER ...]
|
|
37
|
+
Optional processing order for recipe "types" (suffix segments after the first '.'); supports partial tokens like upload/auto_update; env var AW_RECIPE_PROCESSING_ORDER expects comma-separated values
|
|
38
|
+
--debug Enable debug logging when running script
|
|
39
|
+
--disable-recipe-trust-check If this option is used, recipe trust verification will not be run prior to a recipe run.
|
|
40
|
+
--github-token GITHUB_TOKEN A token used to publish a PR to your GitHub repo if overrides require their trust to be updated
|
|
41
|
+
--branch-name BRANCH_NAME Branch name to be used where recipe overrides have failed their trust verification and need to be updated.
|
|
42
|
+
By default, this will be in the format of "fix/update_trust_information/YYYY-MM-DDTHH-MM-SS"
|
|
43
|
+
--create-pr If enabled, autopkg_wrapper will open a PR for updated trust information
|
|
44
|
+
--create-issues Create a GitHub issue for recipes that fail during processing
|
|
45
|
+
--disable-git-commands If this option is used, git commands won't be run
|
|
46
|
+
--post-processors [POST_PROCESSORS ...]
|
|
47
|
+
One or more autopkg post processors to run after each recipe execution
|
|
48
|
+
--autopkg-prefs AW_AUTOPKG_PREFS_FILE
|
|
49
|
+
Path to the autopkg preferences you'd like to use
|
|
50
|
+
--overrides-repo-path AUTOPKG_OVERRIDES_REPO_PATH
|
|
51
|
+
The path on disk to the git repository containing the autopkg overrides directory. If none is provided, we will try to determine it for you.
|
|
52
|
+
--concurrency CONCURRENCY Number of recipes to run in parallel (default: 1)
|
|
53
|
+
--process-reports Process autopkg report directories or zip and emit markdown summaries (runs after recipes complete)
|
|
54
|
+
--reports-zip REPORTS_ZIP Path to an autopkg_report-*.zip to extract and process
|
|
55
|
+
--reports-extract-dir REPORTS_EXTRACT_DIR
|
|
56
|
+
Directory to extract the zip into (default: autopkg_reports_summary/reports)
|
|
57
|
+
--reports-dir REPORTS_DIR Directory of reports to process (if no zip provided). Defaults to /private/tmp/autopkg when processing after a run
|
|
58
|
+
--reports-out-dir REPORTS_OUT_DIR
|
|
59
|
+
Directory to write markdown outputs (default: autopkg_reports_summary/summary)
|
|
60
|
+
--reports-run-date REPORTS_RUN_DATE
|
|
61
|
+
Run date string to include in the summary
|
|
62
|
+
--reports-strict Exit non-zero if any errors are detected in processed reports
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
Run recipes (serial):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
autopkg_wrapper --recipes Foo.download Bar.download
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run 3 recipes concurrently and process reports afterward:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
autopkg_wrapper \
|
|
77
|
+
--recipe-file /path/to/recipe_list.txt \
|
|
78
|
+
--concurrency 3 \
|
|
79
|
+
--disable-git-commands \
|
|
80
|
+
--process-reports \
|
|
81
|
+
--reports-out-dir /tmp/autopkg_reports_summary \
|
|
82
|
+
--reports-strict
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Process a reports zip explicitly (no recipe run):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
autopkg_wrapper \
|
|
89
|
+
--process-reports \
|
|
90
|
+
--reports-zip /path/to/autopkg_report-2026-02-02.zip \
|
|
91
|
+
--reports-extract-dir /tmp/autopkg_reports \
|
|
92
|
+
--reports-out-dir /tmp/autopkg_reports_summary
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Notes:
|
|
96
|
+
|
|
97
|
+
- During recipe runs, per‑recipe plist reports are written to `/private/tmp/autopkg`.
|
|
98
|
+
- When `--process-reports` is supplied without `--reports-zip` or `--reports-dir`, the tool processes `/private/tmp/autopkg`.
|
|
99
|
+
- If `AUTOPKG_JSS_URL`, `AUTOPKG_CLIENT_ID`, and `AUTOPKG_CLIENT_SECRET` are set, uploaded package rows are enriched with Jamf package links.
|
|
100
|
+
- No extra CLI flag is required; enrichment runs automatically when all three env vars are present.
|
|
101
|
+
|
|
102
|
+
An example folder structure and GitHub Actions Workflow is available within the [`actions-demo`](actions-demo)
|
|
103
|
+
|
|
104
|
+
## Credits
|
|
105
|
+
|
|
106
|
+
- [`autopkg_tools` from Facebook](https://github.com/facebook/IT-CPE/tree/main/legacy/autopkg_tools)
|
|
107
|
+
- [`autopkg_tools` from Facebook, modified by Gusto](https://github.com/Gusto/it-cpe-opensource/tree/main/autopkg)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# autopkg-wrapper
|
|
2
|
+
|
|
3
|
+
`autopkg_wrapper` is a small package that can be used to run [`autopkg`](https://github.com/autopkg/autopkg) within CI/CD environments such as GitHub Actions.
|
|
4
|
+
|
|
5
|
+
The easiest way to run it is by installing with pip.
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
pip install autopkg-wrapper
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Command Line Parameters
|
|
12
|
+
|
|
13
|
+
```shell
|
|
14
|
+
-h, --help Show this help message and exit
|
|
15
|
+
--recipe-file RECIPE_FILE Path to a list of recipes to run (cannot be run with --recipes)
|
|
16
|
+
--recipes [RECIPES ...] Recipes to run with autopkg (cannot be run with --recipe-file)
|
|
17
|
+
--recipe-processing-order [RECIPE_PROCESSING_ORDER ...]
|
|
18
|
+
Optional processing order for recipe "types" (suffix segments after the first '.'); supports partial tokens like upload/auto_update; env var AW_RECIPE_PROCESSING_ORDER expects comma-separated values
|
|
19
|
+
--debug Enable debug logging when running script
|
|
20
|
+
--disable-recipe-trust-check If this option is used, recipe trust verification will not be run prior to a recipe run.
|
|
21
|
+
--github-token GITHUB_TOKEN A token used to publish a PR to your GitHub repo if overrides require their trust to be updated
|
|
22
|
+
--branch-name BRANCH_NAME Branch name to be used where recipe overrides have failed their trust verification and need to be updated.
|
|
23
|
+
By default, this will be in the format of "fix/update_trust_information/YYYY-MM-DDTHH-MM-SS"
|
|
24
|
+
--create-pr If enabled, autopkg_wrapper will open a PR for updated trust information
|
|
25
|
+
--create-issues Create a GitHub issue for recipes that fail during processing
|
|
26
|
+
--disable-git-commands If this option is used, git commands won't be run
|
|
27
|
+
--post-processors [POST_PROCESSORS ...]
|
|
28
|
+
One or more autopkg post processors to run after each recipe execution
|
|
29
|
+
--autopkg-prefs AW_AUTOPKG_PREFS_FILE
|
|
30
|
+
Path to the autopkg preferences you'd like to use
|
|
31
|
+
--overrides-repo-path AUTOPKG_OVERRIDES_REPO_PATH
|
|
32
|
+
The path on disk to the git repository containing the autopkg overrides directory. If none is provided, we will try to determine it for you.
|
|
33
|
+
--concurrency CONCURRENCY Number of recipes to run in parallel (default: 1)
|
|
34
|
+
--process-reports Process autopkg report directories or zip and emit markdown summaries (runs after recipes complete)
|
|
35
|
+
--reports-zip REPORTS_ZIP Path to an autopkg_report-*.zip to extract and process
|
|
36
|
+
--reports-extract-dir REPORTS_EXTRACT_DIR
|
|
37
|
+
Directory to extract the zip into (default: autopkg_reports_summary/reports)
|
|
38
|
+
--reports-dir REPORTS_DIR Directory of reports to process (if no zip provided). Defaults to /private/tmp/autopkg when processing after a run
|
|
39
|
+
--reports-out-dir REPORTS_OUT_DIR
|
|
40
|
+
Directory to write markdown outputs (default: autopkg_reports_summary/summary)
|
|
41
|
+
--reports-run-date REPORTS_RUN_DATE
|
|
42
|
+
Run date string to include in the summary
|
|
43
|
+
--reports-strict Exit non-zero if any errors are detected in processed reports
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Examples
|
|
47
|
+
|
|
48
|
+
Run recipes (serial):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
autopkg_wrapper --recipes Foo.download Bar.download
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run 3 recipes concurrently and process reports afterward:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
autopkg_wrapper \
|
|
58
|
+
--recipe-file /path/to/recipe_list.txt \
|
|
59
|
+
--concurrency 3 \
|
|
60
|
+
--disable-git-commands \
|
|
61
|
+
--process-reports \
|
|
62
|
+
--reports-out-dir /tmp/autopkg_reports_summary \
|
|
63
|
+
--reports-strict
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Process a reports zip explicitly (no recipe run):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
autopkg_wrapper \
|
|
70
|
+
--process-reports \
|
|
71
|
+
--reports-zip /path/to/autopkg_report-2026-02-02.zip \
|
|
72
|
+
--reports-extract-dir /tmp/autopkg_reports \
|
|
73
|
+
--reports-out-dir /tmp/autopkg_reports_summary
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Notes:
|
|
77
|
+
|
|
78
|
+
- During recipe runs, per‑recipe plist reports are written to `/private/tmp/autopkg`.
|
|
79
|
+
- When `--process-reports` is supplied without `--reports-zip` or `--reports-dir`, the tool processes `/private/tmp/autopkg`.
|
|
80
|
+
- If `AUTOPKG_JSS_URL`, `AUTOPKG_CLIENT_ID`, and `AUTOPKG_CLIENT_SECRET` are set, uploaded package rows are enriched with Jamf package links.
|
|
81
|
+
- No extra CLI flag is required; enrichment runs automatically when all three env vars are present.
|
|
82
|
+
|
|
83
|
+
An example folder structure and GitHub Actions Workflow is available within the [`actions-demo`](actions-demo)
|
|
84
|
+
|
|
85
|
+
## Credits
|
|
86
|
+
|
|
87
|
+
- [`autopkg_tools` from Facebook](https://github.com/facebook/IT-CPE/tree/main/legacy/autopkg_tools)
|
|
88
|
+
- [`autopkg_tools` from Facebook, modified by Gusto](https://github.com/Gusto/it-cpe-opensource/tree/main/autopkg)
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import plistlib
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from itertools import chain
|
|
9
10
|
from pathlib import Path
|
|
@@ -12,6 +13,8 @@ import autopkg_wrapper.utils.git_functions as git
|
|
|
12
13
|
from autopkg_wrapper.notifier import slack
|
|
13
14
|
from autopkg_wrapper.utils.args import setup_args
|
|
14
15
|
from autopkg_wrapper.utils.logging import setup_logger
|
|
16
|
+
from autopkg_wrapper.utils.recipe_ordering import order_recipe_list
|
|
17
|
+
from autopkg_wrapper.utils.report_processor import process_reports
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class Recipe(object):
|
|
@@ -34,43 +37,39 @@ class Recipe(object):
|
|
|
34
37
|
return name
|
|
35
38
|
|
|
36
39
|
def verify_trust_info(self, args):
|
|
37
|
-
verbose_output = ["-vvvv"] if args.debug else
|
|
40
|
+
verbose_output = ["-vvvv"] if args.debug else []
|
|
38
41
|
prefs_file = (
|
|
39
|
-
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else
|
|
42
|
+
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
|
|
40
43
|
)
|
|
41
|
-
|
|
42
|
-
cmd =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
p = subprocess.Popen(
|
|
48
|
-
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
|
|
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
49
|
)
|
|
50
|
-
(
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
logging.debug(f"cmd: {cmd}")
|
|
51
|
+
|
|
52
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
53
|
+
if result.returncode == 0:
|
|
53
54
|
self.verified = True
|
|
54
55
|
else:
|
|
55
|
-
|
|
56
|
-
self.results["message"] = err
|
|
56
|
+
self.results["message"] = (result.stderr or "").strip()
|
|
57
57
|
self.verified = False
|
|
58
58
|
return self.verified
|
|
59
59
|
|
|
60
60
|
def update_trust_info(self, args):
|
|
61
61
|
prefs_file = (
|
|
62
|
-
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else
|
|
62
|
+
["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
|
|
63
63
|
)
|
|
64
|
-
|
|
65
|
-
cmd =
|
|
66
|
-
cmd
|
|
67
|
-
logging.debug(f"cmd: {str(cmd)}")
|
|
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}")
|
|
68
67
|
|
|
69
68
|
# Fail loudly if this exits 0
|
|
70
69
|
try:
|
|
71
|
-
subprocess.check_call(cmd
|
|
70
|
+
subprocess.check_call(cmd)
|
|
72
71
|
except subprocess.CalledProcessError as e:
|
|
73
|
-
logging.error(e
|
|
72
|
+
logging.error(str(e))
|
|
74
73
|
raise e
|
|
75
74
|
|
|
76
75
|
def _parse_report(self, report):
|
|
@@ -94,7 +93,7 @@ class Recipe(object):
|
|
|
94
93
|
self.results["failed"] = True
|
|
95
94
|
self.results["imported"] = ""
|
|
96
95
|
else:
|
|
97
|
-
report_dir = Path("/tmp/autopkg")
|
|
96
|
+
report_dir = Path("/private/tmp/autopkg")
|
|
98
97
|
report_time = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
|
|
99
98
|
report_name = Path(f"{self.name}-{report_time}.plist")
|
|
100
99
|
|
|
@@ -106,9 +105,9 @@ class Recipe(object):
|
|
|
106
105
|
prefs_file = (
|
|
107
106
|
["--prefs", args.autopkg_prefs.as_posix()]
|
|
108
107
|
if args.autopkg_prefs
|
|
109
|
-
else
|
|
108
|
+
else []
|
|
110
109
|
)
|
|
111
|
-
verbose_output = ["-vvvv"] if args.debug else
|
|
110
|
+
verbose_output = ["-vvvv"] if args.debug else []
|
|
112
111
|
post_processor_cmd = (
|
|
113
112
|
list(
|
|
114
113
|
chain.from_iterable(
|
|
@@ -119,25 +118,25 @@ class Recipe(object):
|
|
|
119
118
|
)
|
|
120
119
|
)
|
|
121
120
|
if self.post_processors
|
|
122
|
-
else
|
|
121
|
+
else []
|
|
123
122
|
)
|
|
123
|
+
autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
|
|
124
124
|
cmd = [
|
|
125
|
-
|
|
125
|
+
autopkg_bin,
|
|
126
126
|
"run",
|
|
127
127
|
self.filename,
|
|
128
128
|
"--report-plist",
|
|
129
129
|
str(report),
|
|
130
130
|
]
|
|
131
|
-
cmd = cmd + post_processor_cmd
|
|
132
|
-
cmd = cmd + verbose_output if verbose_output else cmd
|
|
133
|
-
cmd = cmd + prefs_file if prefs_file else cmd
|
|
134
|
-
cmd = " ".join(cmd)
|
|
131
|
+
cmd = cmd + post_processor_cmd + verbose_output + prefs_file
|
|
135
132
|
|
|
136
|
-
logging.debug(f"cmd: {
|
|
133
|
+
logging.debug(f"cmd: {cmd}")
|
|
137
134
|
|
|
138
|
-
subprocess.
|
|
135
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
136
|
+
if result.returncode != 0:
|
|
137
|
+
self.error = True
|
|
139
138
|
|
|
140
|
-
except
|
|
139
|
+
except Exception:
|
|
141
140
|
self.error = True
|
|
142
141
|
|
|
143
142
|
self._has_run = True
|
|
@@ -244,7 +243,12 @@ def update_recipe_repo(recipe, git_info, disable_recipe_trust_check, args):
|
|
|
244
243
|
|
|
245
244
|
|
|
246
245
|
def parse_recipe_list(recipes, recipe_file, post_processors, args):
|
|
247
|
-
"""
|
|
246
|
+
"""Parse recipe inputs into a common list of recipe names.
|
|
247
|
+
|
|
248
|
+
The arguments assume that `recipes` and `recipe_file` are mutually exclusive.
|
|
249
|
+
If `args.recipe_processing_order` is provided, the list is re-ordered before
|
|
250
|
+
creating `Recipe` objects.
|
|
251
|
+
"""
|
|
248
252
|
recipe_list = None
|
|
249
253
|
|
|
250
254
|
logging.info(f"Recipes: {recipes}") if recipes else None
|
|
@@ -254,6 +258,12 @@ def parse_recipe_list(recipes, recipe_file, post_processors, args):
|
|
|
254
258
|
if recipe_file.suffix == ".json":
|
|
255
259
|
with open(recipe_file, "r") as f:
|
|
256
260
|
recipe_list = json.load(f)
|
|
261
|
+
elif recipe_file.suffix in {".yaml", ".yml"}:
|
|
262
|
+
from ruamel.yaml import YAML
|
|
263
|
+
|
|
264
|
+
yaml = YAML(typ="safe")
|
|
265
|
+
with open(recipe_file, "r", encoding="utf-8") as f:
|
|
266
|
+
recipe_list = yaml.load(f)
|
|
257
267
|
elif recipe_file.suffix == ".txt":
|
|
258
268
|
with open(recipe_file, "r") as f:
|
|
259
269
|
recipe_list = f.read().splitlines()
|
|
@@ -277,11 +287,16 @@ def parse_recipe_list(recipes, recipe_file, post_processors, args):
|
|
|
277
287
|
"""Please provide recipes to run via the following methods:
|
|
278
288
|
--recipes recipe_one.download recipe_two.download
|
|
279
289
|
--recipe-file path/to/recipe_list.json
|
|
280
|
-
Comma separated list in the
|
|
290
|
+
Comma separated list in the AW_RECIPES env variable"""
|
|
281
291
|
)
|
|
282
292
|
sys.exit(1)
|
|
283
293
|
|
|
284
|
-
|
|
294
|
+
if args.recipe_processing_order:
|
|
295
|
+
recipe_list = order_recipe_list(
|
|
296
|
+
recipe_list=recipe_list, order=args.recipe_processing_order
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
logging.info(f"Processing {len(recipe_list)} recipes.")
|
|
285
300
|
recipe_map = [Recipe(name, post_processors=post_processors) for name in recipe_list]
|
|
286
301
|
|
|
287
302
|
return recipe_map
|
|
@@ -358,31 +373,50 @@ def main():
|
|
|
358
373
|
|
|
359
374
|
failed_recipes = []
|
|
360
375
|
|
|
361
|
-
|
|
362
|
-
|
|
376
|
+
# Run recipes concurrently using a thread pool to parallelize subprocess calls
|
|
377
|
+
max_workers = max(1, int(getattr(args, "concurrency", 1)))
|
|
378
|
+
logging.info(f"Running recipes with concurrency={max_workers}")
|
|
379
|
+
|
|
380
|
+
def run_one(r: Recipe):
|
|
381
|
+
logging.info(f"Processing Recipe: {r.name}")
|
|
363
382
|
process_recipe(
|
|
364
|
-
recipe=
|
|
383
|
+
recipe=r,
|
|
365
384
|
disable_recipe_trust_check=args.disable_recipe_trust_check,
|
|
366
385
|
args=args,
|
|
367
386
|
)
|
|
387
|
+
# Git updates and notifications are applied serially after all recipes finish
|
|
388
|
+
return r
|
|
389
|
+
|
|
390
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
391
|
+
futures = [executor.submit(run_one, r) for r in recipe_list]
|
|
392
|
+
for fut in as_completed(futures):
|
|
393
|
+
r = fut.result()
|
|
394
|
+
if r.error or r.results.get("failed"):
|
|
395
|
+
failed_recipes.append(r)
|
|
396
|
+
|
|
397
|
+
# Apply git updates serially to avoid branch/commit conflicts when concurrency > 1
|
|
398
|
+
for r in recipe_list:
|
|
368
399
|
update_recipe_repo(
|
|
369
400
|
git_info=override_repo_info,
|
|
370
|
-
recipe=
|
|
401
|
+
recipe=r,
|
|
371
402
|
disable_recipe_trust_check=args.disable_recipe_trust_check,
|
|
372
403
|
args=args,
|
|
373
404
|
)
|
|
374
|
-
slack.send_notification(
|
|
375
|
-
recipe=recipe, token=args.slack_token
|
|
376
|
-
) if args.slack_token else None
|
|
377
405
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
406
|
+
# Send notifications serially to simplify rate limiting and ordering
|
|
407
|
+
if args.slack_token:
|
|
408
|
+
for r in recipe_list:
|
|
409
|
+
slack.send_notification(recipe=r, token=args.slack_token)
|
|
410
|
+
|
|
411
|
+
# Optionally open a PR for updated trust information
|
|
412
|
+
if args.create_pr and recipe_list:
|
|
413
|
+
# Choose a representative recipe for the PR title/body
|
|
414
|
+
rep_recipe = next(
|
|
415
|
+
(r for r in recipe_list if r.updated is True or r.verified is False),
|
|
416
|
+
recipe_list[0],
|
|
417
|
+
)
|
|
418
|
+
pr_url = git.create_pull_request(git_info=override_repo_info, recipe=rep_recipe)
|
|
419
|
+
logging.info(f"Created Pull Request for trust info updates: {pr_url}")
|
|
386
420
|
|
|
387
421
|
# Create GitHub issue for failed recipes
|
|
388
422
|
if args.create_issues and failed_recipes and args.github_token:
|
|
@@ -390,3 +424,20 @@ def main():
|
|
|
390
424
|
git_info=override_repo_info, failed_recipes=failed_recipes
|
|
391
425
|
)
|
|
392
426
|
logging.info(f"Created GitHub issue for failed recipes: {issue_url}")
|
|
427
|
+
|
|
428
|
+
# Optionally process reports after running recipes
|
|
429
|
+
if getattr(args, "process_reports", False):
|
|
430
|
+
rc = process_reports(
|
|
431
|
+
zip_file=getattr(args, "reports_zip", None),
|
|
432
|
+
extract_dir=getattr(
|
|
433
|
+
args, "reports_extract_dir", "autopkg_reports_summary/reports"
|
|
434
|
+
),
|
|
435
|
+
reports_dir=(getattr(args, "reports_dir", None) or "/private/tmp/autopkg"),
|
|
436
|
+
environment="",
|
|
437
|
+
run_date=getattr(args, "reports_run_date", ""),
|
|
438
|
+
out_dir=getattr(args, "reports_out_dir", "autopkg_reports_summary/summary"),
|
|
439
|
+
debug=bool(getattr(args, "debug", False)),
|
|
440
|
+
strict=bool(getattr(args, "reports_strict", False)),
|
|
441
|
+
)
|
|
442
|
+
if rc:
|
|
443
|
+
sys.exit(rc)
|
|
@@ -5,7 +5,7 @@ import requests
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def send_notification(recipe, token):
|
|
8
|
-
logging.debug("
|
|
8
|
+
logging.debug("Preparing Slack notification")
|
|
9
9
|
|
|
10
10
|
if token is None:
|
|
11
11
|
logging.error("Skipping Slack Notification as no SLACK_WEBHOOK_TOKEN defined!")
|