You've already forked civ
This commit is contained in:
352
pkg/usecases/verifyLabels.go
Normal file
352
pkg/usecases/verifyLabels.go
Normal file
@ -0,0 +1,352 @@
|
||||
package usecases
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.cryptic.systems/volker.raschek/civ/pkg/domain"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
// ContainerRuntime is an interface for different container runtimes to return labels
|
||||
// based on their full qualified container image name. For example:
|
||||
//
|
||||
// imageLabels, err := Load(ctx, "docker.io/library/alpine:latest")
|
||||
// imageLabels, err := Load(ctx, "docker.io/library/busybox:latest")
|
||||
type ContainerRuntime interface {
|
||||
GetImageLabels(ctx context.Context, name string) (map[string]string, error)
|
||||
}
|
||||
|
||||
type ConfigLoader interface {
|
||||
Load(ctx context.Context) (*domain.Config, error)
|
||||
}
|
||||
|
||||
type LabelVerifier struct {
|
||||
config *domain.Config
|
||||
labelLoader ContainerRuntime
|
||||
labelStore *labelStore
|
||||
}
|
||||
|
||||
// Run start the verification process based on the passed config.
|
||||
func (lv *LabelVerifier) Run(ctx context.Context) error {
|
||||
if err := lv.fillLabelStore(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(lv.config.Images))
|
||||
|
||||
for image := range lv.config.Images {
|
||||
go func(image string) {
|
||||
defer func() { wg.Done() }()
|
||||
lv.runLabelConstraints(image)
|
||||
}(image)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lv *LabelVerifier) runLabelConstraints(image string) {
|
||||
for labelKey, labelConstraint := range lv.config.Images[image].LabelConstraints {
|
||||
// fetch existing labels from store
|
||||
existingLabels := lv.labelStore.GetLabelsForImage(image)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(labelKey, "%") && strings.HasSuffix(labelKey, "%"):
|
||||
m := strings.TrimPrefix(strings.TrimSuffix(labelKey, "%"), "%")
|
||||
|
||||
re, err := regexp.Compile(fmt.Sprintf("^.*%v.*$", m))
|
||||
if err != nil {
|
||||
labelConstraint.CountResultMessage = err.Error()
|
||||
} else {
|
||||
state := labelCount(re, labelConstraint.Count, existingLabels)
|
||||
labelConstraint.CountResult = &state
|
||||
}
|
||||
case strings.HasPrefix(labelKey, "%"):
|
||||
m := strings.TrimPrefix(labelKey, "%")
|
||||
|
||||
re, err := regexp.Compile(fmt.Sprintf("^.*%v$", m))
|
||||
if err != nil {
|
||||
labelConstraint.CountResultMessage = err.Error()
|
||||
} else {
|
||||
state := labelCount(re, labelConstraint.Count, existingLabels)
|
||||
labelConstraint.CountResult = &state
|
||||
}
|
||||
case strings.HasSuffix(labelKey, "%"):
|
||||
m := strings.TrimSuffix(labelKey, "%")
|
||||
|
||||
re, err := regexp.Compile(fmt.Sprintf("^%v.*$", m))
|
||||
if err != nil {
|
||||
labelConstraint.CountResultMessage = err.Error()
|
||||
} else {
|
||||
state := labelCount(re, labelConstraint.Count, existingLabels)
|
||||
labelConstraint.CountResult = &state
|
||||
}
|
||||
default:
|
||||
// labelExists
|
||||
if labelConstraint.Exists != nil {
|
||||
state := lv.labelExists(labelKey, existingLabels)
|
||||
labelConstraint.ExistsResult = state
|
||||
if state {
|
||||
labelConstraint.ExistsResultMessage = "Label found"
|
||||
} else {
|
||||
labelConstraint.ExistsResultMessage = "Label not found"
|
||||
}
|
||||
}
|
||||
|
||||
// labelCompareSemver
|
||||
if labelConstraint.CompareSemver != nil {
|
||||
labelExistState := lv.labelExists(labelKey, existingLabels)
|
||||
if labelExistState {
|
||||
parsedSemVer, err := semver.NewVersion(existingLabels[labelKey])
|
||||
if err != nil {
|
||||
b := false
|
||||
labelConstraint.CompareSemverResult = &b
|
||||
labelConstraint.CompareSemverResultMessage = err.Error()
|
||||
}
|
||||
|
||||
state := labelCompareSemver(labelConstraint.CompareSemver, parsedSemVer)
|
||||
labelConstraint.CompareSemverResult = &state
|
||||
} else {
|
||||
labelConstraint.CompareSemverResult = &labelExistState
|
||||
labelConstraint.CompareSemverResultMessage = "Label found"
|
||||
}
|
||||
}
|
||||
|
||||
// labelCompareString
|
||||
if labelConstraint.CompareString != nil {
|
||||
state := labelCompareString(labelConstraint.CompareString, existingLabels[labelKey])
|
||||
labelConstraint.CompareStringResult = &state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func labelCompareSemver(compareSemver *domain.LabelConstraintCompareSemver, parsedSemVer *semver.Version) bool {
|
||||
var majorState bool
|
||||
|
||||
// Equal
|
||||
if compareSemver.Equal != "" {
|
||||
compareSemverEqualVersion, err := semver.NewVersion(compareSemver.Equal)
|
||||
if err != nil {
|
||||
compareSemver.EqualResultMessage = err.Error()
|
||||
} else {
|
||||
state := parsedSemVer.Equal(compareSemverEqualVersion)
|
||||
compareSemver.EqualResult = &state
|
||||
if state {
|
||||
compareSemver.EqualResultMessage = fmt.Sprintf("Version %s is equal to %s", parsedSemVer.String(), compareSemverEqualVersion.String())
|
||||
} else {
|
||||
compareSemver.EqualResultMessage = fmt.Sprintf("Version %s is not equal to %s", parsedSemVer.String(), compareSemverEqualVersion.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GreaterThan
|
||||
if compareSemver.GreaterThan != "" {
|
||||
compareSemverGreaterThanVersion, err := semver.NewVersion(compareSemver.GreaterThan)
|
||||
if err != nil {
|
||||
compareSemver.GreaterThanResultMessage = err.Error()
|
||||
} else {
|
||||
state := parsedSemVer.GreaterThan(compareSemverGreaterThanVersion)
|
||||
compareSemver.GreaterThanResult = &state
|
||||
if state {
|
||||
compareSemver.GreaterThanResultMessage = fmt.Sprintf("Version %s is greater than %s", parsedSemVer.String(), compareSemverGreaterThanVersion.String())
|
||||
} else {
|
||||
compareSemver.GreaterThanResultMessage = fmt.Sprintf("Version %s is not greater than %s", parsedSemVer.String(), compareSemverGreaterThanVersion.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LessThan
|
||||
if compareSemver.LessThan != "" {
|
||||
compareSemverLessThanVersion, err := semver.NewVersion(compareSemver.LessThan)
|
||||
if err != nil {
|
||||
compareSemver.LessThanResultMessage = err.Error()
|
||||
} else {
|
||||
state := parsedSemVer.LessThan(compareSemverLessThanVersion)
|
||||
compareSemver.LessThanResult = &state
|
||||
if state {
|
||||
compareSemver.LessThanResultMessage = fmt.Sprintf("Version %s is lower than %s", parsedSemVer.String(), compareSemverLessThanVersion.String())
|
||||
} else {
|
||||
compareSemver.LessThanResultMessage = fmt.Sprintf("Version %s is not lower than %s", parsedSemVer.String(), compareSemverLessThanVersion.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return majorState
|
||||
}
|
||||
|
||||
func labelCompareString(compareString *domain.LabelConstraintCompareString, labelValue string) bool {
|
||||
var majorState bool = true
|
||||
|
||||
// Equal
|
||||
if compareString.Equal != "" {
|
||||
state := compareString.Equal == labelValue
|
||||
if compareString.Equal == labelValue {
|
||||
compareString.EqualResult = &state
|
||||
compareString.EqualResultMessage = fmt.Sprintf("%s and %s are equal", labelValue, compareString.Equal)
|
||||
} else {
|
||||
compareString.EqualResult = &state
|
||||
compareString.EqualResultMessage = fmt.Sprintf("%s and %s are not equal", labelValue, compareString.Equal)
|
||||
}
|
||||
}
|
||||
|
||||
// hasPrefix
|
||||
if compareString.HasPrefix != "" {
|
||||
state := strings.HasPrefix(labelValue, compareString.HasPrefix)
|
||||
if state {
|
||||
compareString.HasPrefixResult = &state
|
||||
compareString.HasPrefixResultMessage = fmt.Sprintf("%s has prefix %s", labelValue, compareString.HasPrefix)
|
||||
} else {
|
||||
compareString.HasPrefixResult = &state
|
||||
compareString.HasPrefixResultMessage = fmt.Sprintf("%s has not prefix %s", labelValue, compareString.HasPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
// hasSuffix
|
||||
if compareString.HasSuffix != "" {
|
||||
state := strings.HasSuffix(labelValue, compareString.HasSuffix)
|
||||
if state {
|
||||
compareString.HasSuffixResult = &state
|
||||
compareString.HasSuffixResultMessage = fmt.Sprintf("%s has suffix %s", labelValue, compareString.HasSuffix)
|
||||
} else {
|
||||
compareString.HasSuffixResult = &state
|
||||
compareString.HasSuffixResultMessage = fmt.Sprintf("%s has not suffix %s", labelValue, compareString.HasSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
return majorState
|
||||
}
|
||||
|
||||
func labelCount(re *regexp.Regexp, labelConstraintCounter *domain.LabelConstraintCounter, labels map[string]string) bool {
|
||||
var majorState bool = true
|
||||
|
||||
var i uint = 0
|
||||
for key := range labels {
|
||||
if re.MatchString(key) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case labelConstraintCounter.Equal != nil:
|
||||
switch {
|
||||
case i == *labelConstraintCounter.Equal:
|
||||
state := true
|
||||
labelConstraintCounter.EqualResult = &state
|
||||
case i > *labelConstraintCounter.Equal:
|
||||
fallthrough
|
||||
case i < *labelConstraintCounter.Equal:
|
||||
state := false
|
||||
labelConstraintCounter.EqualResult = &state
|
||||
labelConstraintCounter.EqualResultMessage = fmt.Sprintf("%v is not equal %v", i, *labelConstraintCounter.Equal)
|
||||
majorState = false
|
||||
}
|
||||
case labelConstraintCounter.LessThan != nil:
|
||||
switch {
|
||||
case i < *labelConstraintCounter.LessThan:
|
||||
state := true
|
||||
labelConstraintCounter.LessThanResult = &state
|
||||
case i >= *labelConstraintCounter.LessThan:
|
||||
state := false
|
||||
labelConstraintCounter.LessThanResult = &state
|
||||
labelConstraintCounter.LessThanResultMessage = fmt.Sprintf("%v is not less than %v", i, *labelConstraintCounter.Equal)
|
||||
majorState = false
|
||||
}
|
||||
case labelConstraintCounter.GreaterThan != nil:
|
||||
switch {
|
||||
case i < *labelConstraintCounter.GreaterThan:
|
||||
state := true
|
||||
labelConstraintCounter.GreaterThanResult = &state
|
||||
case i >= *labelConstraintCounter.GreaterThan:
|
||||
state := false
|
||||
labelConstraintCounter.GreaterThanResult = &state
|
||||
labelConstraintCounter.GreaterThanResultMessage = fmt.Sprintf("%v is not greater than %v", i, *labelConstraintCounter.Equal)
|
||||
majorState = false
|
||||
}
|
||||
}
|
||||
|
||||
return majorState
|
||||
}
|
||||
|
||||
func (lv *LabelVerifier) labelExists(requiresLabelKey string, existingLabels map[string]string) bool {
|
||||
for existingLabelKey := range existingLabels {
|
||||
if existingLabelKey == requiresLabelKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fillLabelStore fills the label store with the labels of the defined images
|
||||
// from config.
|
||||
func (lv *LabelVerifier) fillLabelStore(ctx context.Context) error {
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(lv.config.Images))
|
||||
|
||||
errorChannel := make(chan error, len(lv.config.Images))
|
||||
|
||||
for image := range lv.config.Images {
|
||||
go func(image string) {
|
||||
defer wg.Done()
|
||||
labels, err := lv.labelLoader.GetImageLabels(ctx, image)
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
return
|
||||
}
|
||||
lv.labelStore.AddLabelsForImage(image, labels)
|
||||
}(image)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errorChannel)
|
||||
|
||||
for {
|
||||
err, open := <-errorChannel
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !open {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLabelVerifier(config *domain.Config, labelLoader ContainerRuntime) (*LabelVerifier, error) {
|
||||
return &LabelVerifier{
|
||||
config: config,
|
||||
labelLoader: labelLoader,
|
||||
labelStore: newLabelStore(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type labelStore struct {
|
||||
labels map[string]map[string]string
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
func (ls *labelStore) AddLabelsForImage(image string, labels map[string]string) {
|
||||
ls.mutex.Lock()
|
||||
defer func() { ls.mutex.Unlock() }()
|
||||
ls.labels[image] = labels
|
||||
}
|
||||
|
||||
func (ls *labelStore) GetLabelsForImage(image string) map[string]string {
|
||||
ls.mutex.RLock()
|
||||
defer func() { ls.mutex.RUnlock() }()
|
||||
return ls.labels[image]
|
||||
}
|
||||
|
||||
func newLabelStore() *labelStore {
|
||||
return &labelStore{
|
||||
labels: make(map[string]map[string]string),
|
||||
mutex: new(sync.RWMutex),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user