Files
teleport/lib/versioncontrol/target.go
T

165 lines
5.5 KiB
Go

/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioncontrol
import (
"fmt"
"regexp"
"strconv"
"golang.org/x/mod/semver"
)
// 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
// 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 isValidLabel(val)
}
// LabelSecurityPatch indicates that a release is a security patch when set to 'yes'.
const LabelSecurityPatch = "security-patch"
// 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
}
// SecurityPatch sets the security-patch=yes label if true.
func SecurityPatch(sec bool) TargetOption {
return func(opts *targetOptions) {
opts.secPatch = sec
}
}
// 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"
}
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"
}
// 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
}