mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
225 lines
7.2 KiB
Go
225 lines
7.2 KiB
Go
/*
|
|
* Teleport
|
|
* Copyright (C) 2023 Gravitational, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package versioncontrol
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/mod/semver" //nolint:depguard // Usage precedes the x/mod/semver rule.
|
|
)
|
|
|
|
// NOTE: the contents of this file might be moving to the 'api' package in the future.
|
|
|
|
// isValidLabel checks if a string is a valid target label or value. this function is exposed
|
|
// as two separate helpers below in order to simplfiy things if we start having different
|
|
// criterea for keys and values in the future.
|
|
var isValidLabel = regexp.MustCompile(`^[a-z0-9\.\-\/]+$`).MatchString
|
|
|
|
// isValidLabelVal is the same as isValidLabel except that it also permits '|' which is interpreted
|
|
// as a list delmeter for some labels.
|
|
var isValidLabelVal = regexp.MustCompile(`^[a-z0-9\.\-\/|]+$`).MatchString
|
|
|
|
// IsValidTargetKey checks if a string is a valid installation target key.
|
|
func IsValidTargetKey(key string) bool {
|
|
return isValidLabel(key)
|
|
}
|
|
|
|
// IsValidTargetVal checks if a string is a valid installtion target value.
|
|
func IsValidTargetVal(val string) bool {
|
|
return isValidLabelVal(val)
|
|
}
|
|
|
|
// LabelSecurityPatch indicates that a release is a security patch when set to 'yes'.
|
|
const LabelSecurityPatch = "security-patch"
|
|
|
|
// LabelSecurityPatchAlts lists alternative versions that provide the same security fixes.
|
|
const LabelSecurityPatchAlts = "security-patch-alts"
|
|
|
|
// LabelVersion is the only required label for an installation target and must be
|
|
// valid go-style semver.
|
|
const LabelVersion = "version"
|
|
|
|
// TargetOption is a functional option for setting additional target fields during construction.
|
|
type TargetOption func(*targetOptions)
|
|
|
|
type targetOptions struct {
|
|
secPatch bool
|
|
secAlts []string
|
|
}
|
|
|
|
// SecurityPatch sets the security-patch=yes label if true.
|
|
func SecurityPatch(sec bool) TargetOption {
|
|
return func(opts *targetOptions) {
|
|
opts.secPatch = sec
|
|
}
|
|
}
|
|
|
|
// SecurityPatchAlts appends version strings to the security-patch-alts label.
|
|
func SecurityPatchAlts(alts ...string) TargetOption {
|
|
return func(opts *targetOptions) {
|
|
opts.secAlts = append(opts.secAlts, alts...)
|
|
}
|
|
}
|
|
|
|
// Target is a description of an available installation target. A given "release"
|
|
// (e.g. v1.2.3) may consist of one or more targets depending on how intallation
|
|
// targets are being modeled (e.g. TUF creates a separate target for each
|
|
// installation package for a given version, whereas the github releases scraper
|
|
// simply creates one target per version). Unknown keypairs should be ignored, and
|
|
// invalid values for expected labels should cause the target as a whole to be ignored.
|
|
// If the Target is being used in a manner that would cause it to be written to the backend,
|
|
// and therefore potentially be used later on by a newer version of teleport, then invalid
|
|
// keypairs should be preserved since they may have known meaning to newer versions.
|
|
type Target map[string]string
|
|
|
|
// NewTarget creates a new target with the specified version and options.
|
|
func NewTarget(version string, opts ...TargetOption) Target {
|
|
var options targetOptions
|
|
for _, opt := range opts {
|
|
opt(&options)
|
|
}
|
|
target := Target{
|
|
LabelVersion: version,
|
|
}
|
|
if options.secPatch {
|
|
target[LabelSecurityPatch] = "yes"
|
|
}
|
|
|
|
if len(options.secAlts) != 0 {
|
|
target[LabelSecurityPatchAlts] = strings.Join(options.secAlts, "|")
|
|
}
|
|
|
|
return target
|
|
}
|
|
|
|
// Ok checks if the target is valid. The only requirement for a target to be valid
|
|
// is for it to have a version field containing valid go-style semver. This must be
|
|
// checked prior to use of the target (this method also works as a nil check).
|
|
func (t Target) Ok() bool {
|
|
return semver.IsValid(t.Version())
|
|
}
|
|
|
|
// Version gets the version of this installation target. Note that the returned value is not
|
|
// necessarily canonicalized (i.e. two equivalent version strings may not compare as equal).
|
|
func (t Target) Version() string {
|
|
return t[LabelVersion]
|
|
}
|
|
|
|
// Major gets the major version of this target (e.g. if version=v1.2.3 then Major() returns v1).
|
|
func (t Target) Major() string {
|
|
return semver.Major(t.Version())
|
|
}
|
|
|
|
// NextMajor gets the next major version string (e.g. if version=v2.3.4 then NextMajor() returns v3).
|
|
func (t Target) NextMajor() string {
|
|
m := t.Major()
|
|
if len(m) < 2 {
|
|
return ""
|
|
}
|
|
n, err := strconv.ParseUint(m[1:], 10, 64)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("v%d", n+1)
|
|
}
|
|
|
|
// SecurityPatch checks for the special label 'security-patch=yes'.
|
|
func (t Target) SecurityPatch() bool {
|
|
return t[LabelSecurityPatch] == "yes"
|
|
}
|
|
|
|
// SecurityPatchAltOf performs a bidirectional check to see if this target and another
|
|
// are security patch alternates (i.e. whether or not they provide the same security fix).
|
|
func (t Target) SecurityPatchAltOf(other Target) bool {
|
|
if !t.Ok() || !other.Ok() {
|
|
return false
|
|
}
|
|
|
|
var alt bool
|
|
|
|
t.iterSecAlts(func(v string) {
|
|
if semver.Compare(v, other.Version()) == 0 {
|
|
alt = true
|
|
}
|
|
})
|
|
|
|
other.iterSecAlts(func(v string) {
|
|
if semver.Compare(v, t.Version()) == 0 {
|
|
alt = true
|
|
}
|
|
})
|
|
|
|
return alt
|
|
}
|
|
|
|
// iterSecAlts is a helper for iterating the valide values of the
|
|
// security-patch-alts label.
|
|
func (t Target) iterSecAlts(fn func(v string)) {
|
|
for alt := range strings.SplitSeq(t[LabelSecurityPatchAlts], "|") {
|
|
alt = strings.TrimSpace(alt)
|
|
if !semver.IsValid(alt) {
|
|
continue
|
|
}
|
|
|
|
fn(alt)
|
|
}
|
|
}
|
|
|
|
// Prerelease checks if this target represents a prerelease installation target
|
|
// (e.g. v1.2.3-alpha.1).
|
|
func (t Target) Prerelease() bool {
|
|
return semver.Prerelease(t.Version()) != ""
|
|
}
|
|
|
|
// NewerThan returns true if this target has a well-defined "newer" relationship to
|
|
// other. Returns false if other is older, equal, or incomparable (e.g. if one or both
|
|
// targets have invalid versions).
|
|
func (t Target) NewerThan(other Target) bool {
|
|
if !t.Ok() || !other.Ok() {
|
|
return false
|
|
}
|
|
|
|
return semver.Compare(t.Version(), other.Version()) == 1
|
|
}
|
|
|
|
// OlderThan returns true if this target has a well-defined "newer" relationship to
|
|
// other. Returns false if other is older, equal, or incomparable (e.g. if one or both
|
|
// targets have invalid versions).
|
|
func (t Target) OlderThan(other Target) bool {
|
|
if !t.Ok() || !other.Ok() {
|
|
return false
|
|
}
|
|
|
|
return semver.Compare(t.Version(), other.Version()) == -1
|
|
}
|
|
|
|
// VersionEquals returns true if this target has a well-defined equivalence relationship to the
|
|
// version of other. Returns false if the versons are not equal, or if they are incomparable
|
|
// (e.g. if one or both targets have invalid versions).
|
|
func (t Target) VersionEquals(other Target) bool {
|
|
if !t.Ok() || !other.Ok() {
|
|
return false
|
|
}
|
|
|
|
return semver.Compare(t.Version(), other.Version()) == 0
|
|
}
|