refactor: de-vendor tagcmp into its own repo

This commit is contained in:
Roxie Gibson 2021-08-13 12:49:46 +01:00
parent d5b893d9de
commit 98ec23761f
Signed by untrusted user: roxxers
GPG Key ID: 5D0140EDEE123F4D
5 changed files with 4 additions and 303 deletions

View File

@ -15,7 +15,7 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/client"
"coopcloud.tech/abra/config"
"coopcloud.tech/abra/tagcmp"
"coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference"

1
go.mod
View File

@ -3,6 +3,7 @@ module coopcloud.tech/abra
go 1.16
require (
coopcloud.tech/tagcmp v0.0.0-20210809131701-f81a7c03b97c
github.com/AlecAivazis/survey/v2 v2.2.15
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4
github.com/containerd/containerd v1.5.5 // indirect

2
go.sum
View File

@ -21,6 +21,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
coopcloud.tech/tagcmp v0.0.0-20210809131701-f81a7c03b97c h1:j4y6MBImeCU/nH7vFt9vIkFKJFcbtdSHk3OST79Mfj4=
coopcloud.tech/tagcmp v0.0.0-20210809131701-f81a7c03b97c/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.2.15 h1:6UNMnk+YGegYFiPfdTOyZDIN+m08x2nGnqOn15BWcEQ=
github.com/AlecAivazis/survey/v2 v2.2.15/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=

View File

@ -1,2 +0,0 @@
Will be taken out of our local source tree and referenced in go.mod once we
solve https://git.coopcloud.tech/coop-cloud/coopcloud.tech/issues/20

View File

@ -1,300 +0,0 @@
// Package tagcmp provides image tag comparison operations.
package tagcmp
import (
"fmt"
"regexp"
"strconv"
"strings"
)
type Tag struct {
Major string `json:",omitempty"` // major semver part
Minor string `json:",omitempty"` // minor semver part
MissingMinor bool // whether or not the minor semver part was left out
Patch string `json:",omitempty"` // patch semver part
MissingPatch bool // whether or not he patch semver part was left out
Suffix string // tag suffix (e.g. "-alpine")
UsesV bool // whether or not the tag uses the "v" prefix
}
// ByTag sorts tags in asc/desc order where the last element is the latest tag.
type ByTag []Tag
func (t ByTag) Len() int { return len(t) }
func (t ByTag) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t ByTag) Less(i, j int) bool {
return t[i].IsLessThan(t[j])
}
// IsGreaterThan tests if a tag is greater than another. There are some
// tag-isms to take into account here, shorter is bigger (i.e. 2.1 > 2.1.1 ==
// true, 2 > 2.1 == true).
func (t Tag) IsGreaterThan(tag Tag) bool {
// shorter is bigger, i.e. 2.1 > 2.1.1
if t.MissingPatch && !tag.MissingPatch || t.MissingMinor && !tag.MissingMinor {
return true
}
if tag.MissingPatch && !t.MissingPatch || tag.MissingMinor && !t.MissingMinor {
return false
}
// ignore errors since Parse already handled
mj1, _ := strconv.Atoi(t.Major)
mj2, _ := strconv.Atoi(tag.Major)
if mj1 > mj2 {
return true
}
if mj2 > mj1 {
return false
}
mn1, _ := strconv.Atoi(t.Minor)
mn2, _ := strconv.Atoi(tag.Minor)
if mn1 > mn2 {
return true
}
if mn2 > mn1 {
return false
}
p1, _ := strconv.Atoi(t.Patch)
p2, _ := strconv.Atoi(tag.Patch)
if p1 > p2 {
return true
}
if p2 > p1 {
return false
}
return false
}
// IsLessThan tests if a tag is less than another. There are some tag-isms to
// take into account here, shorter is bigger (i.e. 2.1 < 2.1.1 == false, 2 <
// 2.1 == false).
func (t Tag) IsLessThan(tag Tag) bool {
return !t.IsGreaterThan(tag)
}
// Equals tests Tag equality
func (t Tag) Equals(tag Tag) bool {
if t.MissingPatch && !tag.MissingPatch || t.MissingMinor && !tag.MissingMinor {
return false
}
if tag.MissingPatch && !t.MissingPatch || tag.MissingMinor && !t.MissingMinor {
return false
}
// ignore errors since Parse already handled
mj1, _ := strconv.Atoi(t.Major)
mj2, _ := strconv.Atoi(tag.Major)
if mj1 != mj2 {
return false
}
mn1, _ := strconv.Atoi(t.Minor)
mn2, _ := strconv.Atoi(tag.Minor)
if mn1 != mn2 {
return false
}
p1, _ := strconv.Atoi(t.Patch)
p2, _ := strconv.Atoi(tag.Patch)
return p1 == p2
}
// String formats a Tag correctly in string representation
func (t Tag) String() string {
var repr string
if t.UsesV {
repr += "v"
}
repr += t.Major
if !t.MissingMinor {
repr += fmt.Sprintf(".%s", t.Minor)
}
if !t.MissingPatch {
repr += fmt.Sprintf(".%s", t.Patch)
}
if t.Suffix != "" {
repr += fmt.Sprintf("-%s", t.Suffix)
}
return repr
}
// IsCompatible determines if two tags can be compared together
func (t Tag) IsCompatible(tag Tag) bool {
if t.UsesV && !tag.UsesV || tag.UsesV && !t.UsesV {
return false
}
if t.Suffix != "" && tag.Suffix == "" || t.Suffix == "" && tag.Suffix != "" {
return false
}
if t.Suffix != "" && tag.Suffix != "" {
if t.Suffix != tag.Suffix {
return false
}
}
if t.MissingMinor && !tag.MissingMinor || tag.MissingMinor && !t.MissingMinor {
return false
}
if t.MissingPatch && !tag.MissingPatch || tag.MissingPatch && !t.MissingPatch {
return false
}
return true
}
// CommitHashPattern matches commit-like hash tags
var CommitHashPattern = "^[a-f0-9]{7,40}$"
// DotPattern matches tags which contain multiple versions
var DotPattern = "([0-9]+)\\.([0-9]+)"
// EmptyPattern matches when tags are missing
var EmptyPattern = "^$"
// ParametrizedPattern matches when tags are parametrized
var ParametrizedPattern = "\\${.+}"
// StringPattern matches when tags are only made up of alphabetic characters
var StringPattern = "^[a-zA-Z]+$"
// patternMatches determines if a tag matches unsupported patterns
func patternMatches(tag string) error {
unsupported := []string{
CommitHashPattern,
EmptyPattern,
ParametrizedPattern,
StringPattern,
}
for _, pattern := range unsupported {
if match, _ := regexp.Match(pattern, []byte(tag)); match {
return fmt.Errorf("'%s' is not supported (%s)", tag, pattern)
}
}
return nil
}
// patternCounts determines if tags match unsupported patterns by counting occurences of matches
func patternCounts(tag string) error {
v := regexp.MustCompile(DotPattern)
if m := v.FindAllStringIndex(tag, -1); len(m) > 1 {
return fmt.Errorf("'%s' is not supported (%s)", tag, DotPattern)
}
return nil
}
// parseVersionPart converts a semver version part to an integer
func parseVersionPart(part string) (int, error) {
p, err := strconv.Atoi(part)
if err != nil {
return 0, err
}
return p, nil
}
// Parse converts an image tag into a structured data format. It aims to to
// support the general case of tags which are "semver-like" and/or stable and
// parseable by heuristics. Image tags follow no formal specification and
// therefore this is a best-effort implementation. Examples of tags this
// function can parse are: "5", "5.2", "v4", "v5.3.6", "4-alpine",
// "v3.2.1-debian".
func Parse(tag string) (Tag, error) {
if err := patternMatches(tag); err != nil {
return Tag{}, err
}
if err := patternCounts(tag); err != nil {
return Tag{}, err
}
usesV := false
if string(tag[0]) == "v" {
tag = strings.TrimPrefix(tag, "v")
usesV = true
}
var suffix string
splits := strings.SplitN(tag, "-", 2)
if len(splits) > 1 {
tag = splits[0]
suffix = splits[1]
}
var major, minor, patch string
var missingMinor, missingPatch bool
parts := strings.Split(tag, ".")
switch {
case len(parts) == 1:
if _, err := parseVersionPart(parts[0]); err != nil {
return Tag{}, fmt.Errorf("couldn't parse major part of '%s': '%s'", tag, parts[0])
}
major = parts[0]
missingMinor = true
missingPatch = true
case len(parts) == 2:
if _, err := parseVersionPart(parts[0]); err != nil {
return Tag{}, fmt.Errorf("couldn't parse major part of '%s': '%s'", tag, parts[0])
}
major = parts[0]
if _, err := parseVersionPart(parts[1]); err != nil {
return Tag{}, fmt.Errorf("couldn't parse minor part of '%s': '%s'", tag, parts[1])
}
minor = parts[1]
missingPatch = true
case len(parts) == 3:
if _, err := parseVersionPart(parts[0]); err != nil {
return Tag{}, fmt.Errorf("couldn't parse major part of '%s': '%s'", tag, parts[0])
}
major = parts[0]
if _, err := parseVersionPart(parts[1]); err != nil {
return Tag{}, fmt.Errorf("couldn't parse minor part of '%s': '%s'", tag, parts[1])
}
minor = parts[1]
if _, err := parseVersionPart(parts[2]); err != nil {
return Tag{}, fmt.Errorf("couldn't parse patch part of '%s': '%s'", tag, parts[2])
}
patch = parts[2]
default:
return Tag{}, fmt.Errorf("couldn't parse semver of '%s", tag)
}
parsedTag := Tag{
Major: major,
Minor: minor,
MissingMinor: missingMinor,
Patch: patch,
MissingPatch: missingPatch,
UsesV: usesV,
Suffix: suffix,
}
return parsedTag, nil
}
// IsParsable determines if a tag is supported by this library
func IsParsable(tag string) bool {
if _, err := Parse(tag); err != nil {
return false
}
return true
}