188 lines
5.6 KiB
Go
188 lines
5.6 KiB
Go
package image
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
)
|
|
|
|
var (
|
|
// Valid SemVer format (Major.Minor.Patch)
|
|
semVerPattern = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`)
|
|
// Matches tags like "RELEASE.2023-11-20T22-40-07Z"
|
|
releasePattern = regexp.MustCompile(`^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T`)
|
|
// Matches tags like "x64-1.2.3" and "arm64-1.2.3" followed by a numeric version
|
|
archPattern = regexp.MustCompile(`^[a-zA-Z0-9]+-[0-9]+\.[0-9]+`)
|
|
// Matches tags like "latest-2023-12-18"
|
|
prefixYearMonthDayPattern = regexp.MustCompile(`^[a-zA-Z0-9]+-[0-9]{4}-[0-9]{2}-[0-9]{2}$`)
|
|
// Matches dates like "2023-11-15" and "2022-04"
|
|
yearMonthDayPattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}(-[0-9]{2})?$`)
|
|
// Matches tags like "1.2.3.4" "1.2" and "1"
|
|
incompleteSemVerPattern = regexp.MustCompile(`^[0-9]+(\.[0-9]+)*$`)
|
|
// Matches tags like "something-abcdefg" (only chars before dash and exactly 7 characters after the dash)
|
|
shortCommitHashSuffixPattern = regexp.MustCompile(`^[a-zA-Z]+-[a-zA-Z0-9]{7}$`)
|
|
// Matches tags like "v1.2.3", "V1.2.3, #1.2.3, $1.2.3, etc"
|
|
leadingSymbolPattern = regexp.MustCompile(`^version|Version|[vV]|^[^a-zA-Z0-9]+`)
|
|
)
|
|
|
|
func CleanTag(tag string) (string, error) {
|
|
tag = strings.TrimSpace(tag)
|
|
|
|
if tag == "" {
|
|
return "", fmt.Errorf("tag is empty")
|
|
}
|
|
|
|
// Do basic cleaning
|
|
tag = cleanSha(tag)
|
|
tag = cleanLeadingSymbol(tag)
|
|
|
|
// Return early if the tag is already in SemVer format
|
|
if semVerPattern.MatchString(tag) {
|
|
return tag, nil
|
|
}
|
|
|
|
switch {
|
|
case releasePattern.MatchString(tag):
|
|
tag = cleanRelease(tag)
|
|
case archPattern.MatchString(tag):
|
|
tag = cleanArch(tag)
|
|
case prefixYearMonthDayPattern.MatchString(tag):
|
|
tag = cleanPrefixYearMonthDay(tag)
|
|
case yearMonthDayPattern.MatchString(tag):
|
|
tag = cleanYearMonthDay(tag)
|
|
case incompleteSemVerPattern.MatchString(tag):
|
|
tag = cleanIncompleteSemVer(tag)
|
|
case shortCommitHashSuffixPattern.MatchString(tag):
|
|
tag = keepShortCommitHashSuffix(tag)
|
|
case leadingSymbolPattern.MatchString(tag):
|
|
tag = cleanLeadingSymbol(tag)
|
|
}
|
|
|
|
// If string contains `-` the second part is usually
|
|
// either a commit hash or things like "debian" or "alpine"
|
|
// Make sure the first part is some kind of versioning and strip the rest
|
|
if strings.Contains(tag, "-") {
|
|
split := strings.Split(tag, "-")
|
|
switch {
|
|
case semVerPattern.MatchString(split[0]):
|
|
tag = split[0]
|
|
case incompleteSemVerPattern.MatchString(split[0]):
|
|
tag = split[0]
|
|
}
|
|
}
|
|
|
|
// Re-check for incomplete SemVer after cleaning
|
|
if incompleteSemVerPattern.MatchString(tag) {
|
|
tag = cleanIncompleteSemVer(tag)
|
|
}
|
|
|
|
if err := checkValidLabelValue(tag); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if !semVerPattern.MatchString(tag) {
|
|
log.Warn().Msgf("Could not produce a valid SemVer tag for tag [%s]", tag)
|
|
}
|
|
|
|
// Build and return the updated SemVer string
|
|
return tag, nil
|
|
}
|
|
|
|
func Clean(tag string) error {
|
|
|
|
newTag, err := CleanTag(tag)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msgf("Failed to clean tag [%s]", tag)
|
|
}
|
|
|
|
log.Info().Msgf("Tag [%s] cleaned to [%s]", tag, newTag)
|
|
return nil
|
|
}
|
|
|
|
func checkValidLabelValue(tag string) error {
|
|
if errs := validation.IsValidLabelValue(tag); len(errs) > 0 {
|
|
return fmt.Errorf("tag [%s] is not valid for label use. error: %s", tag, (strings.Join(errs, ", ")))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// keepShortCommitHashSuffix keeps the last 7 characters of a tag
|
|
// eg "something-abcdefg" -> "abcdefg"
|
|
func keepShortCommitHashSuffix(tag string) string {
|
|
return strings.Split(tag, "-")[1]
|
|
}
|
|
|
|
// cleanRelease Transforms release pattern format
|
|
// eg "RELEASE.2023-11-20T22-40-07Z" -> "2023.11.20"
|
|
func cleanRelease(tag string) string {
|
|
tag = strings.Split(tag, ".")[1]
|
|
tag = strings.Split(tag, "T")[0]
|
|
tag = strings.ReplaceAll(tag, "-", ".")
|
|
|
|
return tag
|
|
}
|
|
|
|
// cleanArch removes arch prefixes
|
|
// eg "x64-1.2.3" -> "1.2.3"
|
|
func cleanArch(tag string) string {
|
|
tag = strings.Split(tag, "-")[1]
|
|
|
|
return tag
|
|
}
|
|
|
|
// cleanYearMonthDay Transforms date versions
|
|
// eg "2023-11-15" -> "2023.11.15" and "2022-04" -> "2022.4"
|
|
func cleanYearMonthDay(tag string) string {
|
|
tag = strings.ReplaceAll(tag, "-", ".")
|
|
parts := strings.Split(tag, ".")
|
|
for idx := range parts {
|
|
parts[idx] = strings.TrimPrefix(parts[idx], "0")
|
|
}
|
|
for len(parts) < 3 {
|
|
parts = append(parts, "0")
|
|
}
|
|
tag = strings.Join(parts, ".")
|
|
|
|
return tag
|
|
}
|
|
|
|
// cleanIncompleteSemVer Transforms incomplete SemVer strings
|
|
// eg "1.2" -> "1.2.0" and "1" -> "1.0.0"
|
|
// versions with more parts are left as-is
|
|
func cleanIncompleteSemVer(tag string) string {
|
|
parts := strings.Split(tag, ".")
|
|
switch {
|
|
case len(parts) == 2:
|
|
tag = tag + ".0"
|
|
case len(parts) == 1:
|
|
tag = tag + ".0.0"
|
|
}
|
|
|
|
return tag
|
|
}
|
|
|
|
// cleanLeadingSymbol Trims leading 'v' or non-alphanumeric characters
|
|
// e.g "v1.2.3" -> "1.2.3"
|
|
func cleanLeadingSymbol(tag string) string {
|
|
return leadingSymbolPattern.ReplaceAllString(tag, "")
|
|
}
|
|
|
|
// cleanSha Strips everything after '@'
|
|
// e.g "v1.2.3@sha256:abc123" -> "v1.2.3"
|
|
func cleanSha(tag string) string {
|
|
return strings.Split(tag, "@")[0]
|
|
}
|
|
|
|
// cleanPrefixYearMonthDay Transforms date versions with prefix
|
|
// eg "latest-2023-12-18" -> "2023.12.18"
|
|
func cleanPrefixYearMonthDay(tag string) string {
|
|
calVer := strings.Split(tag, "-")[1:]
|
|
tag = strings.Join(calVer, ".")
|
|
|
|
return tag
|
|
}
|