Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

## [Unreleased]

### Sonar
## [Sonar] [v1.2] [2026-01-14]

### Added
- Added `--filter` flag which accepts the (Package Search Syntax)[https://docs.cloudsmith.com/artifact-management/search-filter-sort-packages] string. e.g. `--filter "downloads:>0"`.
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown link syntax is incorrect. It should be [Package Search Syntax](https://docs.cloudsmith.com/artifact-management/search-filter-sort-packages) with square brackets around the link text and parentheses around the URL, not the other way around.

Copilot uses AI. Check for mistakes.
- `Uploaded` date of the package now added to the results.

### Fixed
- Deletion now correctly works against non multi-arch images. Previously they where ignored and only multi-arch images were being deleted. Additionally improved log output for deletions.
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'where' to 'were'

Copilot uses AI. Check for mistakes.


Expand Down
30 changes: 25 additions & 5 deletions Docker/Sonar/sonar.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def get_digest_data(workspace, repo, img, digest, ntag_display, platform="unknow

status_raw = "Unknown"
dl = 0
uploaded_at = ""

if pkg_details:
statuses = set(find_key_recursive(pkg_details, 'status_str'))
Expand All @@ -262,12 +263,17 @@ def get_digest_data(workspace, repo, img, digest, ntag_display, platform="unknow
elif len(downloads) > 0:
dl = downloads[0]

uploaded_at = find_key_recursive(pkg_details, 'uploaded_at')
if uploaded_at:
uploaded = uploaded_at
Comment on lines +266 to +268
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable uploaded_at is assigned from find_key_recursive which returns a list, but then it's used to assign to uploaded without extracting the first element. This should check if the list is not empty and extract the first element, similar to how downloads are handled above. This will likely result in incorrect data being stored in the returned dictionary.

Copilot uses AI. Check for mistakes.

return {
"tag": ntag_display,
"type": "image",
"platform": platform,
"status": status_raw,
"downloads": dl,
"uploaded": uploaded,
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable uploaded is referenced here but it's never defined in this function. The code assigns to uploaded_at on line 253 and attempts to use uploaded on line 276, but uploaded is never initialized. This will cause a NameError when this code path is executed.

Copilot uses AI. Check for mistakes.
"digest": digest,
"is_child": True
}
Expand Down Expand Up @@ -323,6 +329,7 @@ def fetch_tag_data(workspace, repo, img, ntag, detailed=False, include_all=False
if pkg_details and len(pkg_details) > 0:
pkg = pkg_details[0]
parent_status = pkg.get('status_str', 'Unknown')
uploaded_at = pkg.get('uploaded_at')
slug = pkg.get('slug', '')
ver = pkg.get('version', '')
if ver and not ver.startswith('sha256:'):
Expand Down Expand Up @@ -413,6 +420,7 @@ def fetch_tag_data(workspace, repo, img, ntag, detailed=False, include_all=False
"type": "manifest/list" if is_list else "image",
"platform": parent_platform,
"status": parent_status,
"uploaded": uploaded_at,
"downloads": total_downloads,
"digest": index_digest,
"is_child": False,
Expand Down Expand Up @@ -441,6 +449,7 @@ def fetch_untagged_data(pkg, workspace, repo, img, detailed=False):
pkg_type = pkg.get('type_display', 'image')

platform_str = "unknown"
uploaded_at = pkg.get('uploaded_at')

# 1. Try to get architecture from Cloudsmith Package API (List Object)
api_arch = pkg.get('architecture')
Expand Down Expand Up @@ -516,10 +525,14 @@ def fetch_untagged_data(pkg, workspace, repo, img, detailed=False):
details_url = f"https://api.cloudsmith.io/v1/packages/{workspace}/{repo}/{slug}/"
details = make_request(details_url)
if details:
d_arch = details.get('architecture')
if d_arch and d_arch != 'unknown':
d_uploaded = details.get('uploaded_at')
if d_uploaded:
uploaded_at = d_uploaded

d_arch = details.get('architecture')
if d_arch and d_arch != 'unknown':
platform_str = d_arch
else:
else:
d_archs = details.get('architectures')
if d_archs:
names = []
Expand All @@ -531,7 +544,8 @@ def fetch_untagged_data(pkg, workspace, repo, img, detailed=False):
valid_names = [n for n in names if n and n != 'unknown']
if valid_names:
platform_str = " ".join(sorted(list(set(valid_names))))



tag_display = "(untagged)" if pkg_type == "manifest/list" else "(orphan)"

results = []
Expand All @@ -540,6 +554,7 @@ def fetch_untagged_data(pkg, workspace, repo, img, detailed=False):
"type": pkg_type,
"platform": platform_str,
"status": status,
"uploaded": uploaded_at,
"downloads": downloads,
"digest": digest,
"is_child": False,
Expand Down Expand Up @@ -846,6 +861,7 @@ def render_table(image_name, groups, is_untagged=False, has_action=False):
table.add_column("Type", style="magenta")
table.add_column("Platform")
table.add_column("Status")
table.add_column("Uploaded")
table.add_column("Downloads", justify="right")
table.add_column("Digest", style="dim")
if has_action:
Expand All @@ -866,6 +882,7 @@ def render_table(image_name, groups, is_untagged=False, has_action=False):
parent.get("type", ""),
parent.get("platform", ""),
format_status(parent.get("status", "")),
parent.get("uploaded", ""),
f"[bold cyan]{parent.get('downloads', 0)}[/bold cyan]",
f"[dim]{parent.get('digest', '')}[/dim]",
action_str if has_action else None
Expand All @@ -876,6 +893,7 @@ def render_table(image_name, groups, is_untagged=False, has_action=False):
f"[magenta]{parent.get('type', 'manifest/list')}[/magenta]",
parent.get('platform', 'multi'),
format_status(parent.get("status", "")),
f"[cyan]{parent.get('uploaded', '')}[/cyan]",
f"[bold cyan]{parent.get('downloads', 0)}[/bold cyan]",
f"[bold cyan]{parent.get('digest', '')}[/bold cyan]"
]
Expand All @@ -895,6 +913,7 @@ def render_table(image_name, groups, is_untagged=False, has_action=False):
f"[white]{row.get('type', '')}[/white]",
row.get("platform", ""),
format_status(row.get("status", "")),
f"[dim cyan]{parent.get('uploaded', '')}[/dim cyan]",
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line references parent.get('uploaded', '') but row is the child element being processed. This should be row.get('uploaded', '') instead to display the child's uploaded date rather than the parent's uploaded date.

Copilot uses AI. Check for mistakes.
f"[dim]{row.get('downloads', 0)}[/dim]",
f"[dim]{row.get('digest', '')}[/dim]"
]
Expand Down Expand Up @@ -1090,7 +1109,7 @@ def console(self): return console # fallback
elif args.output == 'csv':
# CSV Output (simple flat structure)
csv_lines = []
csv_lines.append(["Image", "Tag", "Type", "Platform", "Status", "Downloads", "Digest", "Action"]) # Header
csv_lines.append(["Image", "Tag", "Type", "Platform", "Status", "Uploaded", "Downloads", "Digest", "Action"]) # Header

for img_name, groups in collected_results:
for group in groups:
Expand All @@ -1103,6 +1122,7 @@ def console(self): return console # fallback
group.get("type", ""),
group.get("platform", ""),
group.get("status", ""),
group.get("uploaded", ""),
str(group.get("downloads", 0)),
group.get("digest", ""),
group.get("action", "")
Expand Down
Loading