Repository navigation
Conversation
gerrod3
left a comment
There was a problem hiding this comment.
Note
Posted by an agent on behalf of @gerrod3.
Could we avoid adding auto_discover_cosign by making companion discovery a cached helper? Automatic discovery is already the existing behavior, and excludes can control which companions we probe and sync.
For example, initialize this state on the stage:
self._bypass_taglist = False
self._probed_cosign_digests = set()
self._cosign_probe_semaphore = asyncio.Semaphore(20)Then replace _discover_cosign_companions_without_taglist() with a helper that populates the existing _cosign_tags collection:
async def _ensure_cosign_tags(self, digests):
if not self._bypass_taglist:
return # The normal path already populated _cosign_tags.
pending = sorted(set(digests) - self._probed_cosign_digests)
candidates = []
for digest in pending:
if not digest.startswith("sha256:"):
continue
stem = digest.replace("sha256:", "sha256-", 1)
candidates.extend(
f"{stem}{suffix}"
for suffix in (*COSIGN_TAG_SUFFIXES, "")
)
candidates = filter_resources(
candidates, [], self.remote.excludes or []
)
async def probe(tag):
async with self._cosign_probe_semaphore:
return tag if await self._tag_exists(tag) else None
results = await asyncio.gather(*(probe(tag) for tag in candidates))
self._cosign_tags.extend(tag for tag in results if tag is not None)
self._probed_cosign_digests.update(pending)Filtering candidates before probing preserves exclusions and avoids unnecessary requests. For example, excludes=["sha256-*"] suppresses every companion probe. This also removes the need for the special "harmless exclusions" check in _can_bypass_taglist(). That check can instead require populated, explicit digest includes, using validation such as re.fullmatch(r"sha256:[0-9a-f]{64}", digest), while retaining the mirror fallback. Existing exclusions apply to tag names; the current code appends digest includes separately from tag filtering.
We need to call this helper in two places. First, before checking whether an image has a Cosign signature:
async def _has_cosign_signature(self, digest):
await self._ensure_cosign_tags([digest])
# Continue with the existing .sig / V3 manifest checks.
...This fixes the ordering problem with signed_only: currently the bypass path checks signatures while _cosign_tags is empty, so it skips signed images before discovery happens when Cosign is the signature source.
Second, after processing the primary manifests in the bypass branch, set self._bypass_taglist from _can_bypass_taglist() before entering that branch and use:
await self._process_manifests(
digest_list, signature_source, "Processing Manifests"
)
# Includes child manifests discovered inside an index.
await self._ensure_cosign_tags(self._synced_digests)
companion_tags = self._find_cosign_companion_tags()
if companion_tags:
await self._process_manifests(
companion_tags,
signature_source,
"Processing Cosign Companion Tags",
)The cache prevents repeating probes already performed during signature checks.
Finally, _tag_exists() should only interpret HTTP 404 as absence:
except aiohttp.ClientResponseError as exc:
if exc.status == 404:
return False
raiseAuthentication failures, rate limits, and upstream errors should propagate rather than silently omit companions.
With that structure, we can remove the model field, serializer field, and migration. I'd retain the mirror fallback initially and test digest-only signed sync, exclusions, child-manifest companions, caching, and probe errors.
I would also consider moving alias support into a separate change, since it introduces new includes syntax and creates local tags.
When all entries in a remote's includes field are sha256 digests, bypass the expensive /tags/list enumeration and fetch manifests directly by digest. Cosign companion tags are discovered via cached HEAD probes filtered through the remote's excludes, fixing the signed_only ordering bug where signature checks ran before cosign discovery. Only HTTP 404 is treated as tag absence; auth failures and rate limits propagate. Tested against Satellite 6.19.5 with openshift-release-dev/ocp-v4.0-art-dev (50,000+ tags). SAT-49501
bc84c0f to
ea29e98
Compare
Summary
Fixes #2474
When all entries in a remote's
includesfield are sha256 digests orname=sha256:digestaliases, bypass/tags/listand fetch manifests directly. This avoids paginating 50,000+ tags when syncing a known set of digests, reducing sync time from minutes to seconds.name=sha256:digest) create a human-readable tag alongside the digest tag, enabling friendly image references in disconnected environments.sig,.att,.sbom) are discovered via concurrent HEAD probes instead of filtering the full tag list; a newauto_discover_cosignfield onContainerRemotecontrols this behaviorPerformance
Tested against
quay.io/openshift-release-dev/ocp-v4.0-art-dev(50K+ tags, 189 digest includes):Production validation
This optimization has been production-validated on Red Hat Satellite 6.19 using the equivalent pulp_container 2.26 implementation, adapted here for the 2.29 API surface (
includes/excludesfields,defaultdict-based_synced_digests, artifactless manifests). Customer requests for this optimization are tracked in SAT-49501.Changes
pulp_container/app/tasks/sync_stages.py— Core optimization:_parse_include_entry(),_can_bypass_taglist(),_tag_exists(),_discover_cosign_companions_without_taglist(), bypass logic inrun(), alias tag creation in_process_manifests()pulp_container/app/models.py— Addauto_discover_cosignBooleanField toContainerRemotepulp_container/app/serializers.py— Exposeauto_discover_cosignin the REST APIpulp_container/app/tasks/synchronize.py— Passmirrorflag toContainerFirstStagepulp_container/app/migrations/0051_*— Migration for new fieldpulp_container/tests/unit/test_sync_stages.py— 21 unit tests covering parse, bypass logic, cosign discovery, and HEAD probesCHANGES/2474.feature— Changelog fragmentTest plan
pytest pulp_container/tests/unit/test_sync_stages.py)podman pullpodman pullvalidated