Files
at-container-registry/pkg/appview/holdclient/config.go
2026-03-22 21:17:28 -05:00

70 lines
1.8 KiB
Go

package holdclient
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"atcr.io/pkg/atproto"
)
// OCIHistoryEntry represents a single entry in the OCI config history.
type OCIHistoryEntry struct {
Created string `json:"created"`
CreatedBy string `json:"created_by"`
EmptyLayer bool `json:"empty_layer"`
Comment string `json:"comment"`
}
// OCIConfig represents the parsed OCI image config with fields useful for display.
type OCIConfig struct {
History []OCIHistoryEntry `json:"history"`
}
// FetchImageConfig fetches the OCI image config record from the hold's
// getImageConfig XRPC endpoint. holdURL should be a resolved HTTP(S) URL.
// Returns the parsed OCI config with history entries.
func FetchImageConfig(ctx context.Context, holdURL, manifestDigest string) (*OCIConfig, error) {
reqURL := fmt.Sprintf("%s%s?digest=%s",
strings.TrimSuffix(holdURL, "/"),
atproto.HoldGetImageConfig,
url.QueryEscape(manifestDigest),
)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch image config: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("hold returned status %d for %s", resp.StatusCode, reqURL)
}
var record struct {
ConfigJSON string `json:"configJson"`
}
if err := json.NewDecoder(resp.Body).Decode(&record); err != nil {
return nil, fmt.Errorf("parse image config response: %w", err)
}
var config OCIConfig
if err := json.Unmarshal([]byte(record.ConfigJSON), &config); err != nil {
return nil, fmt.Errorf("parse OCI config JSON: %w", err)
}
return &config, nil
}