diff --git a/pkg/appview/db/models.go b/pkg/appview/db/models.go index bfc8554..b33f86c 100644 --- a/pkg/appview/db/models.go +++ b/pkg/appview/db/models.go @@ -127,11 +127,14 @@ type PlatformInfo struct { Architecture string Variant string OSVersion string + Digest string // child platform manifest digest (for manifest lists) + HoldEndpoint string // hold endpoint for this platform manifest } // TagWithPlatforms extends Tag with platform information type TagWithPlatforms struct { Tag + HoldEndpoint string // hold endpoint from the tag's own manifest Platforms []PlatformInfo IsMultiArch bool HasAttestations bool // true if manifest list contains attestation references diff --git a/pkg/appview/db/queries.go b/pkg/appview/db/queries.go index 9038621..99aef17 100644 --- a/pkg/appview/db/queries.go +++ b/pkg/appview/db/queries.go @@ -636,14 +636,18 @@ func GetTagsWithPlatforms(db DBTX, did, repository string) ([]TagWithPlatforms, t.created_at, m.media_type, m.artifact_type, + m.hold_endpoint, COALESCE(mr.platform_os, '') as platform_os, COALESCE(mr.platform_architecture, '') as platform_architecture, COALESCE(mr.platform_variant, '') as platform_variant, COALESCE(mr.platform_os_version, '') as platform_os_version, - COALESCE(mr.is_attestation, 0) as is_attestation + COALESCE(mr.is_attestation, 0) as is_attestation, + COALESCE(mr.digest, '') as child_digest, + COALESCE(child_m.hold_endpoint, m.hold_endpoint, '') as child_hold_endpoint FROM tags t JOIN manifests m ON t.digest = m.digest AND t.did = m.did AND t.repository = m.repository LEFT JOIN manifest_references mr ON m.id = mr.manifest_id + LEFT JOIN manifests child_m ON mr.digest = child_m.digest AND child_m.did = t.did AND child_m.repository = t.repository WHERE t.did = ? AND t.repository = ? ORDER BY t.created_at DESC, mr.reference_index `, did, repository) @@ -659,11 +663,15 @@ func GetTagsWithPlatforms(db DBTX, did, repository string) ([]TagWithPlatforms, for rows.Next() { var t Tag - var mediaType, artifactType, platformOS, platformArch, platformVariant, platformOSVersion string + var mediaType, artifactType, holdEndpoint string + var platformOS, platformArch, platformVariant, platformOSVersion string var isAttestation bool + var childDigest, childHoldEndpoint string if err := rows.Scan(&t.ID, &t.DID, &t.Repository, &t.Tag, &t.Digest, &t.CreatedAt, - &mediaType, &artifactType, &platformOS, &platformArch, &platformVariant, &platformOSVersion, &isAttestation); err != nil { + &mediaType, &artifactType, &holdEndpoint, + &platformOS, &platformArch, &platformVariant, &platformOSVersion, + &isAttestation, &childDigest, &childHoldEndpoint); err != nil { return nil, err } @@ -672,6 +680,7 @@ func GetTagsWithPlatforms(db DBTX, did, repository string) ([]TagWithPlatforms, if _, exists := tagMap[tagKey]; !exists { tagMap[tagKey] = &TagWithPlatforms{ Tag: t, + HoldEndpoint: holdEndpoint, Platforms: []PlatformInfo{}, ArtifactType: artifactType, } @@ -692,6 +701,8 @@ func GetTagsWithPlatforms(db DBTX, did, repository string) ([]TagWithPlatforms, Architecture: platformArch, Variant: platformVariant, OSVersion: platformOSVersion, + Digest: childDigest, + HoldEndpoint: childHoldEndpoint, }) } } diff --git a/pkg/appview/handlers/repository.go b/pkg/appview/handlers/repository.go index 57ea051..14d02c2 100644 --- a/pkg/appview/handlers/repository.go +++ b/pkg/appview/handlers/repository.go @@ -230,22 +230,32 @@ func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request artifactType = manifests[0].ArtifactType } - // Collect digests for batch scan-result request - var scanDigests []string - var scanHoldEndpoint string - for _, m := range manifests { - if !m.IsManifestList && m.HoldEndpoint != "" { - if scanHoldEndpoint == "" { - scanHoldEndpoint = m.HoldEndpoint + // Collect digests for batch scan-result requests, grouped by hold endpoint + holdDigests := make(map[string][]string) // holdEndpoint → []hexDigest + seen := make(map[string]bool) // dedup digests + for _, t := range tagsWithPlatforms { + if len(t.Platforms) > 0 { + // Multi-arch: collect each platform's child digest + for _, p := range t.Platforms { + if p.Digest != "" && p.HoldEndpoint != "" && !seen[p.Digest] { + seen[p.Digest] = true + hex := strings.TrimPrefix(p.Digest, "sha256:") + holdDigests[p.HoldEndpoint] = append(holdDigests[p.HoldEndpoint], hex) + } } - if m.HoldEndpoint == scanHoldEndpoint { - scanDigests = append(scanDigests, strings.TrimPrefix(m.Digest, "sha256:")) + } else if t.HoldEndpoint != "" { + // Single-arch: use tag's own digest + if !seen[t.Digest] { + seen[t.Digest] = true + hex := strings.TrimPrefix(t.Digest, "sha256:") + holdDigests[t.HoldEndpoint] = append(holdDigests[t.HoldEndpoint], hex) } } } - var scanBatchParams string - if len(scanDigests) > 0 { - scanBatchParams = "holdEndpoint=" + url.QueryEscape(scanHoldEndpoint) + "&digests=" + strings.Join(scanDigests, ",") + var scanBatchParams []template.HTML + for hold, digests := range holdDigests { + scanBatchParams = append(scanBatchParams, template.HTML( + "holdEndpoint="+url.QueryEscape(hold)+"&digests="+strings.Join(digests, ","))) } // Build page meta @@ -285,7 +295,7 @@ func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request IsOwner bool // Whether current user owns this repository ReadmeHTML template.HTML ArtifactType string // Dominant artifact type: container-image, helm-chart, unknown - ScanBatchParams template.HTML // Pre-encoded query string for batch scan-result endpoint + ScanBatchParams []template.HTML // Pre-encoded query strings for batch scan-result endpoint (one per hold) }{ PageData: NewPageData(r, &h.BaseUIHandler), Meta: meta, @@ -299,7 +309,7 @@ func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request IsOwner: isOwner, ReadmeHTML: readmeHTML, ArtifactType: artifactType, - ScanBatchParams: template.HTML(scanBatchParams), + ScanBatchParams: scanBatchParams, } if err := h.Templates.ExecuteTemplate(w, "repository", data); err != nil { diff --git a/pkg/appview/templates/pages/repository.html b/pkg/appview/templates/pages/repository.html index 3a33ccc..b6e0439 100644 --- a/pkg/appview/templates/pages/repository.html +++ b/pkg/appview/templates/pages/repository.html @@ -150,20 +150,30 @@ {{ end }} -
{{ .Tag.Digest }}
-
- {{ .Tag.Digest }}
+
+ {{ .Digest }}
+
+ {{ if .HoldEndpoint }}
+
+ {{ end }}
{{ end }}
No tags available
{{ end }} @@ -225,10 +243,6 @@{{ .Manifest.Digest }}
No manifests available
{{ end }} diff --git a/pkg/hold/pds/did.go b/pkg/hold/pds/did.go index 13f98dc..816ba88 100644 --- a/pkg/hold/pds/did.go +++ b/pkg/hold/pds/did.go @@ -170,7 +170,10 @@ func LoadOrCreateDID(ctx context.Context, cfg DIDConfig) (string, error) { rotationKey, _ := parseOptionalMultibaseKey(cfg.RotationKey) if err := EnsurePLCCurrent(ctx, did, rotationKey, signingKey, cfg.PublicURL, cfg.PLCDirectoryURL); err != nil { - return "", fmt.Errorf("failed to ensure PLC identity is current: %w", err) + slog.Warn("Failed to verify PLC identity is current (will retry on next restart)", + "did", did, + "error", err, + ) } return did, nil