mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-04-20 16:40:29 +00:00
124 lines
3.0 KiB
Go
124 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// ImageRef is a parsed container image reference
|
|
type ImageRef struct {
|
|
Host string
|
|
Identity string
|
|
Repo string
|
|
Tag string
|
|
Raw string
|
|
}
|
|
|
|
// detectImageRef walks the process tree looking for an image reference
|
|
// that matches the given registry host. It starts from the parent process
|
|
// and walks up to 5 ancestors to handle wrapper scripts (make, bash -c, etc.).
|
|
//
|
|
// Returns nil if no matching image reference is found — callers should
|
|
// fall back to the active account.
|
|
func detectImageRef(registryHost string) *ImageRef {
|
|
// Normalize the registry host for matching
|
|
matchHost := strings.TrimPrefix(registryHost, "https://")
|
|
matchHost = strings.TrimPrefix(matchHost, "http://")
|
|
matchHost = strings.TrimSuffix(matchHost, "/")
|
|
|
|
pid := os.Getppid()
|
|
for depth := 0; depth < 5; depth++ {
|
|
args, err := getProcessArgs(pid)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
for _, arg := range args {
|
|
if ref := parseImageRef(arg, matchHost); ref != nil {
|
|
return ref
|
|
}
|
|
}
|
|
|
|
ppid, err := getParentPID(pid)
|
|
if err != nil || ppid == pid || ppid <= 1 {
|
|
break
|
|
}
|
|
pid = ppid
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseImageRef tries to parse a string as a container image reference.
|
|
// Expected format: host/identity/repo:tag or host/identity/repo
|
|
//
|
|
// Handles:
|
|
// - docker:// and oci:// transport prefixes (skopeo)
|
|
// - Flags (- prefix), paths (/ or . prefix), shell artifacts (|, &, ;)
|
|
// - Optional tag (defaults to "latest")
|
|
// - Host must look like a domain (contains ., or is localhost, or has :port)
|
|
// - If matchHost is non-empty, only returns refs matching that host
|
|
func parseImageRef(s string, matchHost string) *ImageRef {
|
|
// Skip flags, absolute paths, relative paths
|
|
if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "/") || strings.HasPrefix(s, ".") {
|
|
return nil
|
|
}
|
|
|
|
// Strip docker:// or oci:// transport prefixes (skopeo)
|
|
s = strings.TrimPrefix(s, "docker://")
|
|
s = strings.TrimPrefix(s, "oci://")
|
|
|
|
// Skip other transport schemes
|
|
if strings.Contains(s, "://") {
|
|
return nil
|
|
}
|
|
// Must contain at least one slash
|
|
if !strings.Contains(s, "/") {
|
|
return nil
|
|
}
|
|
// Skip things that look like shell commands
|
|
if strings.ContainsAny(s, " |&;") {
|
|
return nil
|
|
}
|
|
|
|
// Split off tag
|
|
tag := "latest"
|
|
refPart := s
|
|
if atIdx := strings.LastIndex(s, ":"); atIdx != -1 {
|
|
lastSlash := strings.LastIndex(s, "/")
|
|
if atIdx > lastSlash {
|
|
tag = s[atIdx+1:]
|
|
refPart = s[:atIdx]
|
|
}
|
|
}
|
|
|
|
parts := strings.Split(refPart, "/")
|
|
|
|
// ATCR pattern requires host/identity/repo (3+ parts)
|
|
if len(parts) < 3 {
|
|
return nil
|
|
}
|
|
|
|
host := parts[0]
|
|
identity := parts[1]
|
|
repo := strings.Join(parts[2:], "/")
|
|
|
|
// Host must look like a domain
|
|
if !strings.Contains(host, ".") && host != "localhost" && !strings.Contains(host, ":") {
|
|
return nil
|
|
}
|
|
|
|
// If a specific host was requested, enforce it
|
|
if matchHost != "" && host != matchHost {
|
|
return nil
|
|
}
|
|
|
|
return &ImageRef{
|
|
Host: host,
|
|
Identity: identity,
|
|
Repo: repo,
|
|
Tag: tag,
|
|
Raw: s,
|
|
}
|
|
}
|