action: emit assets manifest JSON alongside release uploads#9772
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds three new action inputs ( ChangesManifest Generation Feature
sequenceDiagram
participant GH as GitHub Actions
participant Script as Inline Python Script
participant Index as ARMBIAN_INDEX_URL
participant FS as Workspace Filesystem
GH->>Script: start step with env (ARMBIAN_DOWNLOAD_BASE_URL, ARMBIAN_DOWNLOAD_REPOSITORY, ARMBIAN_INDEX_URL)
Script->>FS: read image data and local metadata files
alt ARMBIAN_INDEX_URL provided
Script->>Index: fetch index metadata
Index-->>Script: return metadata JSON
end
Script->>Script: enrich image entries, build download URIs
Script->>FS: write assets-<suffix>.json files
Script-->>GH: exit status and logs
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@action.yml`:
- Around line 254-256: The ARMBIAN_DOWNLOAD_REPOSITORY value is used raw and can
contain leading/trailing slashes, which breaks composed URLs (e.g.,
file_url/redi_url); normalize it by trimming both leading and trailing slashes
when reading the env var (assign download_repo =
os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/") ) and apply
the same normalization wherever download_repo is recomputed or used later (also
update the occurrences around the block referenced at lines ~329-337) so URL
composition uses a clean repository segment.
- Around line 306-313: The current candidate-selection logic (variables
candidates, EXTS, and the IMAGES_DIR.glob loop) picks the first-seen extension
rather than the smallest file; change it to group files by stem and choose the
file with the smallest actual disk size (use file.stat().st_size) for each stem
instead of relying on EXTS order. Specifically, iterate all files from
IMAGES_DIR.glob for all EXTS, compute stem as before, and for each stem compare
sizes and replace the chosen extension when a smaller file is found so
candidates[stem] always points to the extension with the smallest file on disk.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
afaaa8a to
cc05455
Compare
…-URL manifest Two changes that go together: 1. Switch the action ref from `armbian/build@main` to `armbian/build@action-release-assets-manifest` so this PR's aggregator can be tested end-to-end against the unmerged manifest feature in armbian/build#9772. Reverts to @main once #9772 lands. 2. Set `armbian_download_base_url` to the GitHub release URL for this run, and `armbian_download_repository` to empty. Empty repository selects the flat URL shape '<base>/<filename>' (added in #9772) instead of the dl.armbian.com '<base>/<board>/<repo>/ <filename>' shape, and clears the redi_url* fields since GitHub releases have no friendly-redirect convention. Result: per-cell manifests now carry the *actual* download URL for SDK images instead of an aspirational dl.armbian.com path.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@action.yml`:
- Around line 374-380: The JSON/YAML currently emits boolean-like fields as
strings (e.g., "promoted": "false", "platinum": "false", "platinum_expired":
"false"), causing consumers to treat them as truthy; change those entries so
they are actual booleans (promoted: false, platinum: false, platinum_expired:
false) or ensure the variables assigned to promoted/platinum/platinum_expired
are boolean values before serialization (use explicit bool() coercion or literal
false), leaving other fields (e.g., download_repository, file_extension,
kernel_version, platinum_until) unchanged.
- Line 277: The warning-print calls that emit f"::warning::Could not parse
{INFO_JSON}: {e}" are writing to stderr so the runner won't process them; update
the print statements that include INFO_JSON (the occurrences currently passing
file=sys.stderr) to write to stdout instead (remove the file=sys.stderr argument
or use file=sys.stdout) so the GitHub Actions workflow command is emitted over
stdout; make the same change for the second similar print (the other occurrence
around the parse failure) and leave the already-correct occurrence that omits
file=sys.stderr unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
dcdca83 to
71b96d3
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (4)
action.yml (4)
261-261:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize
ARMBIAN_DOWNLOAD_REPOSITORYbefore URL composition.Line 261 uses the repository segment as-is; leading/trailing slashes will produce malformed paths at Line 379.
Suggested fix
- download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive") + download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")Also applies to: 379-379
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` at line 261, Normalize the ARMBIAN_DOWNLOAD_REPOSITORY value after reading it into download_repo by stripping any leading/trailing slashes so it cannot introduce duplicate or missing slashes during later URL/path composition; locate the download_repo assignment and ensure you call .strip("/") (or equivalent normalization) on that value before it is used to build the download URL/path (the place where the repo segment is concatenated into the final URL).
423-423:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSerialize boolean fields as booleans, not
"false"strings.Line 423, Line 427, and Line 428 emit strings, which downstream consumers can treat as truthy values.
Suggested fix
- "promoted": "false", + "promoted": False, ... - "platinum": "false", - "platinum_expired": "false", + "platinum": False, + "platinum_expired": False,Also applies to: 427-428
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` at line 423, The YAML is emitting boolean fields as quoted strings (e.g. "promoted": "false") which should be real booleans; update the affected keys (for example promoted and the other two boolean keys shown in the diff) in action.yml to use unquoted true/false literals (promoted: false, etc.) so downstream consumers parse them as booleans rather than truthy strings.
350-357:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPick the selected artifact by actual file size, not extension order.
Line 350-357 currently chooses the first-seen extension per stem, which can violate the “most-compressed form” objective when another available format is smaller.
Suggested fix
- candidates = {} # stem -> chosen extension + EXT_RANK = {ext: i for i, ext in enumerate(EXTS)} # stable tie-breaker + candidates = {} # stem -> (chosen_ext, chosen_size) for ext in EXTS: for f in IMAGES_DIR.glob(f"*.{ext}"): stem = f.name[: -(len(ext) + 1)] - if stem not in candidates: - candidates[stem] = ext + size = f.stat().st_size + prev = candidates.get(stem) + if ( + prev is None + or size < prev[1] + or (size == prev[1] and EXT_RANK[ext] < EXT_RANK[prev[0]]) + ): + candidates[stem] = (ext, size) assets = [] - for stem, ext in sorted(candidates.items()): + for stem, (ext, _) in sorted(candidates.items()): img = IMAGES_DIR / f"{stem}.{ext}"Also applies to: 359-360
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 350 - 357, The current selection logic stores the first-seen extension per stem (variables: candidates, EXTS, IMAGES_DIR, stem, f) which can pick a larger file; change it to compare actual file sizes and pick the smallest file for each stem by using f.stat().st_size (or Path.stat().st_size) when iterating all matching files, updating candidates to the extension (or full Path) of the smallest-seen file; apply the same size-based selection fix to the other similar block referenced (lines 359-360) so every stem consistently chooses the smallest artifact rather than the first extension encountered.
308-308:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEmit
::warning::workflow commands on stdout, not stderr.Line 308 and Line 363 send warning commands to
sys.stderr; GitHub runner command parsing expects stdout, so annotations may not be created.Suggested fix
- print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr) + print(f"::warning::Could not parse {INFO_JSON}: {e}") ... - print(f"::warning::Unrecognised filename {img.name}, skipping", file=sys.stderr) + print(f"::warning::Unrecognised filename {img.name}, skipping")Also applies to: 363-363
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` at line 308, The print statements emitting GitHub workflow warning commands currently write to stderr (e.g., print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr)); change them to emit on stdout instead (remove the file=sys.stderr argument or set file=sys.stdout) so the runner can parse annotations; update both occurrences (the one referencing INFO_JSON and exception variable e and the other similar print at the second location) to write to stdout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@action.yml`:
- Line 261: Normalize the ARMBIAN_DOWNLOAD_REPOSITORY value after reading it
into download_repo by stripping any leading/trailing slashes so it cannot
introduce duplicate or missing slashes during later URL/path composition; locate
the download_repo assignment and ensure you call .strip("/") (or equivalent
normalization) on that value before it is used to build the download URL/path
(the place where the repo segment is concatenated into the final URL).
- Line 423: The YAML is emitting boolean fields as quoted strings (e.g.
"promoted": "false") which should be real booleans; update the affected keys
(for example promoted and the other two boolean keys shown in the diff) in
action.yml to use unquoted true/false literals (promoted: false, etc.) so
downstream consumers parse them as booleans rather than truthy strings.
- Around line 350-357: The current selection logic stores the first-seen
extension per stem (variables: candidates, EXTS, IMAGES_DIR, stem, f) which can
pick a larger file; change it to compare actual file sizes and pick the smallest
file for each stem by using f.stat().st_size (or Path.stat().st_size) when
iterating all matching files, updating candidates to the extension (or full
Path) of the smallest-seen file; apply the same size-based selection fix to the
other similar block referenced (lines 359-360) so every stem consistently
chooses the smallest artifact rather than the first extension encountered.
- Line 308: The print statements emitting GitHub workflow warning commands
currently write to stderr (e.g., print(f"::warning::Could not parse {INFO_JSON}:
{e}", file=sys.stderr)); change them to emit on stdout instead (remove the
file=sys.stderr argument or set file=sys.stdout) so the runner can parse
annotations; update both occurrences (the one referencing INFO_JSON and
exception variable e and the other similar print at the second location) to
write to stdout.
71b96d3 to
14f5ffb
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
action.yml (4)
308-308:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEmit GitHub Actions
::warning::commands on stdout, not stderr.These commands are currently printed to
sys.stderr, which prevents runner annotation parsing.Suggested fix
- print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr) + print(f"::warning::Could not parse {INFO_JSON}: {e}") ... - print(f"::warning::Unrecognised filename {img.name}, skipping", file=sys.stderr) + print(f"::warning::Unrecognised filename {img.name}, skipping")Also applies to: 386-386
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` at line 308, The print statements that emit GitHub Actions annotations use file=sys.stderr which prevents the runner from parsing them; update the two calls that look like print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr) (and the similar one at the later occurrence) to write to stdout instead (e.g., remove the file=sys.stderr argument or explicitly use file=sys.stdout) so the ::warning:: commands are emitted on stdout and will be recognized by the Actions runner.
461-466:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSerialize manifest booleans as JSON booleans, not strings.
"false"is a string, not a boolean; downstream truthiness checks can misclassify these entries as enabled.Suggested fix
- "promoted": "false", + "promoted": False, ... - "platinum": "false", - "platinum_expired": "false", + "platinum": False, + "platinum_expired": False,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 461 - 466, The manifest currently emits boolean fields as strings (e.g., "promoted", "platinum", "platinum_expired" set to "false"), which breaks downstream truthiness checks; update the manifest serialization so those keys are emitted as real JSON/YAML booleans (false/true) instead of quoted strings—locate the block that builds the manifest object containing "promoted", "download_repository", "file_extension", "kernel_version", "platinum", and "platinum_expired" and replace the quoted boolean literals with actual booleans or ensure the serializer writes booleans (false/true) for those fields.
370-380:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSelect the smallest file by actual size, not extension priority.
The current first-seen rule can violate the “most-compressed form” objective when
.zstis smaller than.xzfor the same logical image.Suggested fix
- candidates = {} # (stem, image_format) -> chosen extension + ext_rank = {ext: i for i, (ext, _) in enumerate(EXTS)} + candidates = {} # (stem, image_format) -> (chosen_ext, chosen_size) for ext, image_format in EXTS: for f in IMAGES_DIR.glob(f"*.{ext}"): stem = f.name[: -(len(ext) + 1)] key = (stem, image_format) - if key not in candidates: - candidates[key] = ext + size = f.stat().st_size + prev = candidates.get(key) + if ( + prev is None + or size < prev[1] + or (size == prev[1] and ext_rank[ext] < ext_rank[prev[0]]) + ): + candidates[key] = (ext, size) assets = [] - for (stem, image_format), ext in sorted(candidates.items()): + for (stem, image_format), (ext, _) in sorted(candidates.items()): img = IMAGES_DIR / f"{stem}.{ext}"Also applies to: 382-383
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 370 - 380, The current selection logic around EXTS / candidates picks the first-seen extension rather than the smallest file; change it to compare actual file sizes: iterate IMAGES_DIR.glob for all ext,image_format pairs and for each file compute size = f.stat().st_size, then for key = (stem, image_format) store the ext only if the key is new or this file's size is smaller than the currently recorded size. Implement this by replacing the single candidates dict with either a dict mapping key -> (ext, size) or a parallel sizes dict and update references to candidates (and the similar logic later referenced around the same block) to use the chosen ext from the smallest-size entry.
261-262:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize
ARMBIAN_DOWNLOAD_REPOSITORYbefore composing URLs.Using
download_reporaw allows values like/archive/, which produces malformed URL paths and inconsistentdownload_repositoryvalues in output.Suggested fix
- download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive") + download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 261 - 262, The env var value read into download_repo (from ARMBIAN_DOWNLOAD_REPOSITORY) must be normalized to remove any leading/trailing slashes before it is used to build URLs or emitted in outputs; change the code that sets download_repo to strip() or trim slashes (e.g., download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")) and then use that normalized download_repo when composing index_url/other URLs and when writing the download_repository field so paths are consistent and won't produce malformed URLs (leave ARMBIAN_INDEX_URL handling unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@action.yml`:
- Around line 355-359: The kernel parsing drops pre-release suffixes because it
uses kernel.split("-", 1)[0] (splitting at the first hyphen) after reading
last_token; change that to use rsplit("-", 1)[0] so only the final suffix is
removed (preserving versions like "6.19.0-rc5" or "6.6.0-rc1-sdk"), keeping the
existing last_token logic and file_application assignment (variables: kernel,
last_token, file_application).
---
Duplicate comments:
In `@action.yml`:
- Line 308: The print statements that emit GitHub Actions annotations use
file=sys.stderr which prevents the runner from parsing them; update the two
calls that look like print(f"::warning::Could not parse {INFO_JSON}: {e}",
file=sys.stderr) (and the similar one at the later occurrence) to write to
stdout instead (e.g., remove the file=sys.stderr argument or explicitly use
file=sys.stdout) so the ::warning:: commands are emitted on stdout and will be
recognized by the Actions runner.
- Around line 461-466: The manifest currently emits boolean fields as strings
(e.g., "promoted", "platinum", "platinum_expired" set to "false"), which breaks
downstream truthiness checks; update the manifest serialization so those keys
are emitted as real JSON/YAML booleans (false/true) instead of quoted
strings—locate the block that builds the manifest object containing "promoted",
"download_repository", "file_extension", "kernel_version", "platinum", and
"platinum_expired" and replace the quoted boolean literals with actual booleans
or ensure the serializer writes booleans (false/true) for those fields.
- Around line 370-380: The current selection logic around EXTS / candidates
picks the first-seen extension rather than the smallest file; change it to
compare actual file sizes: iterate IMAGES_DIR.glob for all ext,image_format
pairs and for each file compute size = f.stat().st_size, then for key = (stem,
image_format) store the ext only if the key is new or this file's size is
smaller than the currently recorded size. Implement this by replacing the single
candidates dict with either a dict mapping key -> (ext, size) or a parallel
sizes dict and update references to candidates (and the similar logic later
referenced around the same block) to use the chosen ext from the smallest-size
entry.
- Around line 261-262: The env var value read into download_repo (from
ARMBIAN_DOWNLOAD_REPOSITORY) must be normalized to remove any leading/trailing
slashes before it is used to build URLs or emitted in outputs; change the code
that sets download_repo to strip() or trim slashes (e.g., download_repo =
os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")) and then
use that normalized download_repo when composing index_url/other URLs and when
writing the download_repository field so paths are consistent and won't produce
malformed URLs (leave ARMBIAN_INDEX_URL handling unchanged).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
c00cf4d to
752f8e8
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (4)
action.yml (4)
316-316:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEmit
::warning::workflow commands to stdout, not stderr.These two warning commands are sent to
sys.stderr; runner annotations are only reliably parsed from stdout.Suggested patch
- print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr) + print(f"::warning::Could not parse {INFO_JSON}: {e}") ... - print(f"::warning::Unrecognised filename {img.name}, skipping", file=sys.stderr) + print(f"::warning::Unrecognised filename {img.name}, skipping")Also applies to: 394-394
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` at line 316, The warning workflow commands are being written to stderr; change the print calls that emit the GitHub workflow annotation (e.g., the print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr) occurrences) to write to stdout instead (remove the file=sys.stderr argument or use file=sys.stdout) so the runner can reliably parse the ::warning:: annotations; update both occurrences (around the INFO_JSON parse and the similar line at the other reported spot).
260-262:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize
ARMBIAN_DOWNLOAD_REPOSITORYbefore composing URLs.
download_repois used raw, so values like/archive/generate malformedfile_url/redi_urlpaths.Suggested patch
- download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive") + download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")Also applies to: 419-421
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 260 - 262, The environment value for ARMBIAN_DOWNLOAD_REPOSITORY (read into download_repo) is not normalized and can contain leading/trailing slashes, causing malformed URLs; update the code that reads download_repo to strip leading and trailing slashes (e.g., download_repo = os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")) and ensure the same normalization is applied at the other occurrence noted (the block around the second read at lines ~419-421) so that downstream URL composition for file_url and redi_url always uses a cleaned repository segment.
378-388:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSelect by actual file size, not extension priority.
The current logic uses
xz > zst > rawordering; that can violate the “most-compressed form” requirement when.zstis smaller.Suggested patch
- candidates = {} # (stem, image_format) -> chosen extension + EXT_RANK = {ext: i for i, (ext, _) in enumerate(EXTS)} # tie-breaker + candidates = {} # (stem, image_format) -> (chosen_ext, chosen_size) for ext, image_format in EXTS: for f in IMAGES_DIR.glob(f"*.{ext}"): stem = f.name[: -(len(ext) + 1)] key = (stem, image_format) - if key not in candidates: - candidates[key] = ext + size = f.stat().st_size + prev = candidates.get(key) + if ( + prev is None + or size < prev[1] + or (size == prev[1] and EXT_RANK[ext] < EXT_RANK[prev[0]]) + ): + candidates[key] = (ext, size) assets = [] - for (stem, image_format), ext in sorted(candidates.items()): + for (stem, image_format), (ext, _) in sorted(candidates.items()):Also applies to: 390-391
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 378 - 388, The current selection uses extension order (EXTS) to pick a candidate into the candidates dict, which breaks the “most-compressed form” requirement; change the logic in the loop over EXTS/IMAGES_DIR so you compute file size (f.stat().st_size) for each found file and store the extension yielding the smallest size per key=(stem, image_format) instead of first-seen. Keep using the same variables (EXTS, IMAGES_DIR, candidates, stem, image_format, ext, f) and update the comparison to replace candidates[key] when a smaller-sized file is found; apply the same size-based selection to the similar block referenced around lines 390-391.
363-367:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve kernel pre-release suffixes when parsing
kernel_version.Current split removes
-rc*segments (6.19.0-rc5becomes6.19.0), which corrupts kernel metadata.Suggested patch
if "-" in kernel: last_token = kernel.rsplit("-", 1)[1] - kernel = kernel.split("-", 1)[0] - if last_token.isalpha() and not file_application: - file_application = last_token + if last_token.isalpha(): + if not file_application: + file_application = last_token + kernel = kernel.rsplit("-", 1)[0]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 363 - 367, The kernel parsing currently strips any trailing hyphen token (e.g., "-rc5") because it blindly splits on "-" and treats the last token as a potential file_application; update the logic around the variables kernel, last_token, and file_application so that you only treat the last token as file_application when it is purely alphabetic and not a kernel pre-release (e.g., tokens starting with "rc" or matching /^rc\d+$/); if last_token is a prerelease marker keep it as part of kernel (do not split), otherwise set file_application as before. Ensure the modified condition lives in the same parsing block that handles kernel (the code that currently does last_token = kernel.rsplit("-", 1)[1] and kernel = kernel.split("-", 1)[0]) so kernel_version retains "-rc*" suffixes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@action.yml`:
- Line 316: The warning workflow commands are being written to stderr; change
the print calls that emit the GitHub workflow annotation (e.g., the
print(f"::warning::Could not parse {INFO_JSON}: {e}", file=sys.stderr)
occurrences) to write to stdout instead (remove the file=sys.stderr argument or
use file=sys.stdout) so the runner can reliably parse the ::warning::
annotations; update both occurrences (around the INFO_JSON parse and the similar
line at the other reported spot).
- Around line 260-262: The environment value for ARMBIAN_DOWNLOAD_REPOSITORY
(read into download_repo) is not normalized and can contain leading/trailing
slashes, causing malformed URLs; update the code that reads download_repo to
strip leading and trailing slashes (e.g., download_repo =
os.environ.get("ARMBIAN_DOWNLOAD_REPOSITORY", "archive").strip("/")) and ensure
the same normalization is applied at the other occurrence noted (the block
around the second read at lines ~419-421) so that downstream URL composition for
file_url and redi_url always uses a cleaned repository segment.
- Around line 378-388: The current selection uses extension order (EXTS) to pick
a candidate into the candidates dict, which breaks the “most-compressed form”
requirement; change the logic in the loop over EXTS/IMAGES_DIR so you compute
file size (f.stat().st_size) for each found file and store the extension
yielding the smallest size per key=(stem, image_format) instead of first-seen.
Keep using the same variables (EXTS, IMAGES_DIR, candidates, stem, image_format,
ext, f) and update the comparison to replace candidates[key] when a
smaller-sized file is found; apply the same size-based selection to the similar
block referenced around lines 390-391.
- Around line 363-367: The kernel parsing currently strips any trailing hyphen
token (e.g., "-rc5") because it blindly splits on "-" and treats the last token
as a potential file_application; update the logic around the variables kernel,
last_token, and file_application so that you only treat the last token as
file_application when it is purely alphabetic and not a kernel pre-release
(e.g., tokens starting with "rc" or matching /^rc\d+$/); if last_token is a
prerelease marker keep it as part of kernel (do not split), otherwise set
file_application as before. Ensure the modified condition lives in the same
parsing block that handles kernel (the code that currently does last_token =
kernel.rsplit("-", 1)[1] and kernel = kernel.split("-", 1)[0]) so kernel_version
retains "-rc*" suffixes.
752f8e8 to
690ef1e
Compare
…-URL manifest Two changes that go together: 1. Switch the action ref from `armbian/build@main` to `armbian/build@action-release-assets-manifest` so this PR's aggregator can be tested end-to-end against the unmerged manifest feature in armbian/build#9772. Reverts to @main once #9772 lands. 2. Set `armbian_download_base_url` to the GitHub release URL for this run, and `armbian_download_repository` to empty. Empty repository selects the flat URL shape '<base>/<filename>' (added in #9772) instead of the dl.armbian.com '<base>/<board>/<repo>/ <filename>' shape, and clears the redi_url* fields since GitHub releases have no friendly-redirect convention. Result: per-cell manifests now carry the *actual* download URL for SDK images instead of an aspirational dl.armbian.com path.
…-URL manifest Two changes that go together: 1. Switch the action ref from `armbian/build@main` to `armbian/build@action-release-assets-manifest` so this PR's aggregator can be tested end-to-end against the unmerged manifest feature in armbian/build#9772. Reverts to @main once #9772 lands. 2. Set `armbian_download_base_url` to the GitHub release URL for this run, and `armbian_download_repository` to empty. Empty repository selects the flat URL shape '<base>/<filename>' (added in #9772) instead of the dl.armbian.com '<base>/<board>/<repo>/ <filename>' shape, and clears the redi_url* fields since GitHub releases have no friendly-redirect convention. Result: per-cell manifests now carry the *actual* download URL for SDK images instead of an aspirational dl.armbian.com path.
…-URL manifest Two changes that go together: 1. Switch the action ref from `armbian/build@main` to `armbian/build@action-release-assets-manifest` so this PR's aggregator can be tested end-to-end against the unmerged manifest feature in armbian/build#9772. Reverts to @main once #9772 lands. 2. Set `armbian_download_base_url` to the GitHub release URL for this run, and `armbian_download_repository` to empty. Empty repository selects the flat URL shape '<base>/<filename>' (added in #9772) instead of the dl.armbian.com '<base>/<board>/<repo>/ <filename>' shape, and clears the redi_url* fields since GitHub releases have no friendly-redirect convention. Result: per-cell manifests now carry the *actual* download URL for SDK images instead of an aspirational dl.armbian.com path.
d2d7cc0 to
3231ae4
Compare
Adds a step to the composite action that walks build/output/images after build, parses each image filename, reads board metadata from the build framework's existing output/info/image-info.json, optionally enriches per-board fields by fetching the canonical github.armbian.com/armbian-images.json (Bigin-resolved company info, platinum status, and a fallback for board_* metadata), and writes one '<image_filename>.assets.json' next to each artefact. The existing release-action glob picks the manifests up automatically — no caller change needed. Schema mirrors the canonical github.armbian.com/armbian-images.json shape verbatim, so a downstream aggregator can union per-build fragments into the central index without bespoke parsing. Filename parsing details (mirrored from armbian.github.io/scripts/generate-armbian-images-json.sh): - Variant token can carry an optional '.application' (e.g. 'minimal.hyperv'). - Kernel field can carry '-suffix'. Strip only the LAST dash-segment via rsplit so multi-dash kernels preserve any intermediate tag in kernel_version (e.g. '6.6.0-rc1-sdk' -> kernel '6.6.0-rc1', app 'sdk'). Accept the stripped suffix as file_application iff every char is a letter (sdk, omv, kali, homeassistant, openhab, ufs, …) so digit-bearing pre-release tags like 'rc5' don't pose as apps. Variant-side application takes precedence when both sources have one. Image format handling: - Each image file is keyed by (stem, image_format) where image_format is one of img, img.qcow2, img.vhdx, hyperv.zip. Same stem with multiple formats produces multiple manifest entries — raw and qcow2 builds of the same logical image are distinct products. - Per (stem, image_format) the most-compressed form wins (xz > zst > raw uncompressed). - Format suffix shows up in redi_url: 'minimal-qcow2', 'minimal-hyperv'. Three new optional inputs configure the URL shape and enrichment: armbian_download_base_url default: https://dl.armbian.com armbian_download_repository default: archive armbian_index_url default: https://github.armbian.com/armbian-images.json armbian_download_repository selects between two URL shapes: set -> {base}/{board}/{repo}/{filename} dl.armbian.com style redi_url* uses the friendly {distro}_{branch}_{variant} redirect path on dl.armbian.com. empty -> {base}/{filename} flat (e.g. GitHub release) redi_url* mirror file_url* since there is no separate redirect convention. Leading/trailing slashes are stripped from the input so 'archive/', '/archive/', and a slash-only value (which collapses to '' = flat mode) all behave correctly. armbian_index_url is fetched best-effort (timeout 30s, warning on failure) to build a board_slug -> {board_*, company_*, platinum_*} lookup. Local image-info.json wins per-field for board_*; company_* and platinum_* are sourced only from the index. Empty disables enrichment entirely. Per-image manifest naming '<image_filename>.assets.json' guarantees uniqueness across (a) multiple images in this build call (raw + .img.qcow2 + .img.vhdx all coexist when image-output-* extensions are enabled) and (b) parallel matrix cells with different extension lists landing in the same release. Schema stays {"assets": [...]} so consumers expecting the array (jq -s 'map(.assets) | flatten') keep working unchanged. Tested locally against an 18-image build/output/images tree on a developer host: 18/18 filenames parse, board_name / vendor / support / introduced populate from image-info.json, company_* + platinum_* populate from the live published index (340 boards loaded). Synthetic SDK-style filenames (kernel '6.18.26-sdk') strip cleanly to kernel_version='6.18.26' / file_application='sdk'. Multi-dash cases (rc kernel + app) preserve the rc tag. Edge cases (rc5/beta1/alpha2/pre3 without app, digit-suffix detection, hyperv variant suffixes) all behave correctly.
3231ae4 to
8842c5b
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (2)
action.yml (2)
428-439:⚠️ Potential issue | 🟠 Major | ⚡ Quick winChoose the kept artifact by actual size, not extension order.
Line 433 iterates
EXTSin priority order and Line 437 locks in the first match, so this can keep a larger.xzwhen the.zstis smaller. That breaks the stated "most-compressed form" rule.Suggested fix
+ EXT_RANK = {ext: i for i, (ext, _) in enumerate(EXTS)} + def collect_candidates(): """Walk IMAGES_DIR, group files by (stem, image_format). Within each group keep the most-compressed form (EXTS is ordered xz > zst > raw, first-seen wins).""" out = {} for ext, image_format in EXTS: for f in IMAGES_DIR.glob(f"*.{ext}"): stem = f.name[: -(len(ext) + 1)] key = (stem, image_format) - if key not in out: - out[key] = ext - return out + size = f.stat().st_size + prev = out.get(key) + if ( + prev is None + or size < prev[1] + or (size == prev[1] and EXT_RANK[ext] < EXT_RANK[prev[0]]) + ): + out[key] = (ext, size) + return {key: ext for key, (ext, _) in out.items()}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 428 - 439, The collect_candidates() function currently picks the first-seen extension from EXTS instead of the smallest file, which can keep a larger .xz over a smaller .zst; change the logic to compare actual file sizes using f.stat().st_size for each file under IMAGES_DIR and keep the extension (and/or record) of the smallest-size file per key (stem, image_format). In practice, for each Path f in IMAGES_DIR.glob, compute size = f.stat().st_size and update out[key] only if key is new or size < recorded_size (store size alongside the chosen ext or replace ext when smaller) so the kept artifact is truly the most-compressed form.
307-311:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve
promoted/platinum*as real booleans from enrichment.
promotedis never loaded from the canonical index here, and Lines 476/480/481 serialize fallback values as"false"strings. That means promoted images are always downgraded in the fragment, and downstream consumers will read these fields incorrectly.Suggested fix
+ BOOLEAN_FIELDS = {"promoted", "platinum", "platinum_expired"} ENRICHMENT_FIELDS = ( "board_name", "board_vendor", "board_support", "board_introduced", "company_name", "company_website", "company_logo", + "promoted", "platinum", "platinum_expired", "platinum_until", ) @@ - if slug and slug not in out: - out[slug] = {k: asset.get(k) or "" for k in ENRICHMENT_FIELDS} + if slug and slug not in out: + out[slug] = { + k: asset.get(k, False) if k in BOOLEAN_FIELDS else asset.get(k, "") + for k in ENRICHMENT_FIELDS + } @@ - "promoted": "false", + "promoted": enrich.get("promoted", False), "download_repository": DOWNLOAD_REPO, "file_extension": ext, "kernel_version": parsed["kernel_version"], - "platinum": enrich.get("platinum") or "false", - "platinum_expired": enrich.get("platinum_expired") or "false", + "platinum": enrich.get("platinum", False), + "platinum_expired": enrich.get("platinum_expired", False), "platinum_until": enrich.get("platinum_until", ""),Also applies to: 327-330, 476-481
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@action.yml` around lines 307 - 311, ENRICHMENT_FIELDS currently omits "promoted" and downstream fallback assignments serialize boolean-like values as the string "false", so update the ENRICHMENT_FIELDS tuple to include "promoted" and change the enrichment-loading/serialization logic that writes fallback values (the code paths that set promoted, platinum, platinum_expired, platinum_until when enrichment is missing) to emit real booleans/None (e.g., promoted: False, platinum: False, platinum_expired: False, platinum_until: None) and normalize any incoming string "true"/"false" to Python booleans when reading enrichment; look for the ENRICHMENT_FIELDS constant and the functions that build the fragment/fallback values and replace string literals "false"/"true" with proper boolean/None conversions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@action.yml`:
- Around line 428-439: The collect_candidates() function currently picks the
first-seen extension from EXTS instead of the smallest file, which can keep a
larger .xz over a smaller .zst; change the logic to compare actual file sizes
using f.stat().st_size for each file under IMAGES_DIR and keep the extension
(and/or record) of the smallest-size file per key (stem, image_format). In
practice, for each Path f in IMAGES_DIR.glob, compute size = f.stat().st_size
and update out[key] only if key is new or size < recorded_size (store size
alongside the chosen ext or replace ext when smaller) so the kept artifact is
truly the most-compressed form.
- Around line 307-311: ENRICHMENT_FIELDS currently omits "promoted" and
downstream fallback assignments serialize boolean-like values as the string
"false", so update the ENRICHMENT_FIELDS tuple to include "promoted" and change
the enrichment-loading/serialization logic that writes fallback values (the code
paths that set promoted, platinum, platinum_expired, platinum_until when
enrichment is missing) to emit real booleans/None (e.g., promoted: False,
platinum: False, platinum_expired: False, platinum_until: None) and normalize
any incoming string "true"/"false" to Python booleans when reading enrichment;
look for the ENRICHMENT_FIELDS constant and the functions that build the
fragment/fallback values and replace string literals "false"/"true" with proper
boolean/None conversions.
) * ci: aggregate per-cell asset manifests into armbian-sdk-images.json The build action (armbian/build PR #9772) now writes a per-cell `assets-<board>-<release>-<branch>-<ui>.json` next to each Matrix cell's image upload. With 8 cells (board × os × extension) the release ends up with 8 small JSONs and one logical inventory split across them. Add a final Aggregate job that runs after Matrix completes, downloads every `assets-*.json` from the release, and merges them into a single `armbian-sdk-images.json` uploaded back to the same release. Downstream consumers (and the central index server at github.armbian.com) get one canonical inventory file per release instead of having to enumerate per-cell fragments. The existing per-cell manifests are left in place so individual cells remain inspectable; only the merged file is created on top. `if: always() && !cancelled()` lets the aggregator still merge manifests for the cells that did succeed when one matrix cell fails — fail-fast: false is already set on the Matrix strategy. * ci: pin to armbian/build PR #9772 branch for testing + configure flat-URL manifest Two changes that go together: 1. Switch the action ref from `armbian/build@main` to `armbian/build@action-release-assets-manifest` so this PR's aggregator can be tested end-to-end against the unmerged manifest feature in armbian/build#9772. Reverts to @main once #9772 lands. 2. Set `armbian_download_base_url` to the GitHub release URL for this run, and `armbian_download_repository` to empty. Empty repository selects the flat URL shape '<base>/<filename>' (added in #9772) instead of the dl.armbian.com '<base>/<board>/<repo>/ <filename>' shape, and clears the redi_url* fields since GitHub releases have no friendly-redirect convention. Result: per-cell manifests now carry the *actual* download URL for SDK images instead of an aspirational dl.armbian.com path. * ci: fix typo armbian_release_tittle -> armbian_release_title Pre-existing typo (double 't') made the SDK release title silently fall back to the build action's default 'Armbian image' instead of 'Armbian SDK'. Surfaced as a GitHub Actions warning: Warning: Unexpected input(s) 'armbian_release_tittle', valid inputs are [..., 'armbian_release_title', ...] Same fix in the inline comment. * ci: switch aggregator pattern to *.assets.json (per-image manifest) Pairs with the build action's switch from one combined manifest per matrix cell to one manifest per image file. Per-cell naming ('assets-<board>-<release>-<branch>-<ui>.json') collided when two matrix cells differed only in their extension list (e.g. one adds image-output-qcow2, the other doesn't) — both wrote the same name and the later upload silently overwrote the earlier. Per-image naming ('<image_filename>.assets.json') is guaranteed unique across cells because image filenames are. The aggregator just needs the matching glob. * ci: name aggregated manifest armbian-images.json Match the canonical name used by the central index at github.armbian.com/armbian-images.json so consumers can use the same filename across sources. Was applied previously but lost during a branch cleanup; reapplying.
ncipollo/release-action defaults artifactErrorsFailBuild to false, so GitHub's 2 GiB per-asset cap (and other upload failures) only surfaced as warnings while the job reported success. Flip it so over-size or broken uploads fail the job loudly.
Wires through to ncipollo/release-action's prerelease flag so matrix builds can keep the release in pre-release state while images are still uploading, then promote it once the post-matrix aggregator finishes. Defaults to false to preserve current behaviour for callers that don't opt in.
Summary
Expands the composite action so each build run emits a small JSON manifest describing every image it produced, written next to the image artefacts and uploaded to the GitHub release alongside them. The schema mirrors the canonical
https://github.armbian.com/armbian-images.json1:1, so a downstream aggregator can flatten per-build fragments into the central index withjq -s 'map(.assets) | flatten'and no bespoke parsing.Use case: armbian/sdk and any other consumer of
armbian/build@mainends up with a per-image manifest in its release. Easy to consume.What the action does
After the build (and
Sign) step, beforencipollo/release-action:build/output/imagesfor each known image format —img,img.qcow2,img.vhdx,hyperv.zip— in theirxz/zst/ raw forms.(stem, image_format). Within each group keep the most-compressed form. Different formats coexist as separate manifest entries — the raw image and the qcow2 build of the same logical image are distinct products.Armbian_…andArmbian-unofficial_…prefixes and treats the trailing_variant[.app]segment as optional.BOARD_NAME,BOARD_VENDOR,BOARD_SUPPORT_LEVEL,INTRODUCED) from the build framework's existingbuild/output/info/image-info.json.company_name/company_website/company_logo(Bigin-resolved by armbian.github.io's pipeline) andplatinum/platinum_expired/platinum_until, plus a fallback for board_* fields whenimage-info.jsondoesn't have them.file_urlandredi_url(and their.asc/.sha/.torrentsiblings) from a configurable base URL + repository segment.<image_filename>.assets.jsonnext to each image. The existingncipollo/release-actionglob (${{ inputs.armbian_artifacts }}*) picks them up automatically — no caller change needed.Per-image filenames are unique by construction across both multiple formats in one build call AND parallel matrix cells with different extension lists, so per-cell-named manifests don't collide on the release.
New optional inputs
armbian_download_base_urlhttps://dl.armbian.comfile_url/redi_url).armbian_download_repositoryarchive<base>/<board>/<repo>/. Set to empty string for the flat URL shape<base>/<filename>— useful when images publish directly to a GitHub release. Leading/trailing slashes are stripped.armbian_index_urlhttps://github.armbian.com/armbian-images.jsoncompany_*/platinum_*enrichment. Empty disables.URL shapes
armbian_download_repositoryselects between two URL conventions:set(defaultarchive) —{base}/{board}/{repo}/{filename}.redi_url*uses the friendly{Distro}_{branch}_{variant}[-format|app]redirect path. Format suffix wins over app suffix (canonical convention:minimal-qcow2,minimal-hyperv).{base}/{filename}(flat).redi_url*mirrorfile_url*since GitHub releases have no friendly-redirect convention.Schema
{ "assets": [ { "board_slug": "uefi-arm64", "board_name": "UEFI arm64", "board_vendor": "generic", "board_support": "conf", "board_introduced": "2022", "company_name": "Generic", "company_website": "", "company_logo": "https://cache.armbian.com/images/vendors/150/generic.png", "armbian_version": "26.05.0-trunk", "file_url": "https://dl.armbian.com/uefi-arm64/archive/Armbian_…_minimal.img.xz", "file_url_asc": "…/…img.xz.asc", "file_url_sha": "…/…img.xz.sha", "file_url_torrent": "…/…img.xz.torrent", "redi_url": "https://dl.armbian.com/uefi-arm64/Noble_current_minimal", "redi_url_asc": "…", "redi_url_sha": "…", "redi_url_torrent": "…", "file_size": "299047896", "file_date": "2026-05-06T08:11:42Z", "distro": "noble", "branch": "current", "variant": "minimal", "file_application": "", "promoted": "false", "download_repository": "archive", "file_extension": "img.xz", "kernel_version": "6.18.21", "platinum": "false", "platinum_expired": "false", "platinum_until": "" } ] }Filename parsing
Mirrors
generate-armbian-images-json.sh:_variant[.app](e.g.minimal.hyperv). The.apppart is optional.-. The trailing token becomesfile_applicationiff it's all letters (sosdk,omv,kali,homeassistant,openhab,ufs→ app;rc5,beta1,alpha2,pre3stay attached tokernel_version). This way6.6.0-rc1-sdkparses askernel_version=6.6.0-rc1,file_application=sdk.Test results
Dry-run against an 18-image
build/output/images/tree on a developer host:Armbian-unofficialprefix, etc.).board_name/board_vendor/board_support/board_introducedpopulate fromimage-info.jsonfor boards listed there; fall back to the canonical index for others.company_*andplatinum_*populate from the live published index (340 boards loaded;uefi-arm64resolves tocompany_name="Generic"/company_logo=…/generic.png;inovato-quadraresolves toplatinum="true"/platinum_until="2026-06-15").6.18.26-sdk) strip cleanly tokernel_version="6.18.26"/file_application="sdk". Multi-dash cases (6.6.0-rc1-sdk) preserve the rc tag. Edge cases (rc5/beta1/alpha2/pre3 without app, hyperv variant suffixes) all behave correctly.::warning::workflow commands go to stdout so GitHub Actions parses them into proper annotations.Test plan
armbian/sdkworkflow (which uses this action). Confirm the resulting GitHub release contains an<image_filename>.assets.jsonper.img*artefact.jq '.assets | length' *.assets.jsonreturns1for each.file_urlresolves to a downloadable file,redi_urlmatches the existingdl.armbian.comredirect convention (or mirrorsfile_urlin flat mode), andcompany_*/platinum_*are populated for boards listed in the canonical index..img.xzand.img.qcow2.xzand confirm two distinct manifest entries with differentfile_extensionandredi_url.Summary by CodeRabbit
Release Notes