package patchruntime import ( "errors" "fmt" "strings" "github.com/distribution/reference" "golang.org/x/mod/semver" ) // errImageRefNoTag reports that an image reference does not declare a // tag. The patch service maps it to `image_ref_not_semver` because a // digest-only or tagless reference cannot carry a semver-comparable // version. var errImageRefNoTag = errors.New("image reference is missing a tag") // extractSemverTag returns the canonical semver string ("v1.4.7") for // imageRef, ready to feed into golang.org/x/mod/semver. The leading "v" // is added when the underlying tag omits it. // // Errors returned by this function are pre-formatted for inclusion in // the patch service's `image_ref_not_semver` failure message. func extractSemverTag(imageRef string) (string, error) { parsed, err := reference.ParseNormalizedNamed(imageRef) if err != nil { return "", fmt.Errorf("parse image reference %q: %w", imageRef, err) } tagged, ok := parsed.(reference.NamedTagged) if !ok { return "", fmt.Errorf("%w: %q", errImageRefNoTag, imageRef) } tag := strings.TrimSpace(tagged.Tag()) if tag == "" { return "", fmt.Errorf("%w: %q", errImageRefNoTag, imageRef) } candidate := tag if !strings.HasPrefix(candidate, "v") { candidate = "v" + candidate } if !semver.IsValid(candidate) { return "", fmt.Errorf("tag %q on image reference %q is not a valid semver", tag, imageRef) } return candidate, nil } // samePatchSeries reports whether two canonical semver strings (with // the leading "v") share their major and minor components. The third // component (patch) and any pre-release / build metadata are ignored. func samePatchSeries(currentSemver, newSemver string) bool { return semver.MajorMinor(currentSemver) == semver.MajorMinor(newSemver) }