mirror of https://github.com/fluxcd/cli-utils.git
212 lines
5.5 KiB
Go
212 lines
5.5 KiB
Go
// Copyright 2021 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package object
|
|
|
|
import (
|
|
"hash/fnv"
|
|
"sort"
|
|
"strconv"
|
|
)
|
|
|
|
// ObjMetadataSet is an ordered list of ObjMetadata that acts like an unordered
|
|
// set for comparison purposes.
|
|
type ObjMetadataSet []ObjMetadata
|
|
|
|
// UnstructuredSetEquals returns true if the slice of objects in setA equals
|
|
// the slice of objects in setB.
|
|
func ObjMetadataSetEquals(setA []ObjMetadata, setB []ObjMetadata) bool {
|
|
return ObjMetadataSet(setA).Equal(ObjMetadataSet(setB))
|
|
}
|
|
|
|
// ObjMetadataSetFromMap constructs a set from a map
|
|
func ObjMetadataSetFromMap(mapA map[ObjMetadata]struct{}) ObjMetadataSet {
|
|
setA := make(ObjMetadataSet, 0, len(mapA))
|
|
for f := range mapA {
|
|
setA = append(setA, f)
|
|
}
|
|
return setA
|
|
}
|
|
|
|
// Equal returns true if the two sets contain equivalent objects. Duplicates are
|
|
// ignored.
|
|
// This function satisfies the cmp.Equal interface from github.com/google/go-cmp
|
|
func (setA ObjMetadataSet) Equal(setB ObjMetadataSet) bool {
|
|
mapA := make(map[ObjMetadata]struct{}, len(setA))
|
|
for _, a := range setA {
|
|
mapA[a] = struct{}{}
|
|
}
|
|
mapB := make(map[ObjMetadata]struct{}, len(setB))
|
|
for _, b := range setB {
|
|
mapB[b] = struct{}{}
|
|
}
|
|
if len(mapA) != len(mapB) {
|
|
return false
|
|
}
|
|
for b := range mapB {
|
|
if _, exists := mapA[b]; !exists {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Contains checks if the provided ObjMetadata exists in the set.
|
|
func (setA ObjMetadataSet) Contains(id ObjMetadata) bool {
|
|
for _, om := range setA {
|
|
if om == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Remove the object from the set and return the updated set.
|
|
func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet {
|
|
for i, a := range setA {
|
|
if a == obj {
|
|
setA[len(setA)-1], setA[i] = setA[i], setA[len(setA)-1]
|
|
return setA[:len(setA)-1]
|
|
}
|
|
}
|
|
return setA
|
|
}
|
|
|
|
// Intersection returns the set of unique objects in both set A and set B.
|
|
func (setA ObjMetadataSet) Intersection(setB ObjMetadataSet) ObjMetadataSet {
|
|
var maxlen int
|
|
if len(setA) > len(setB) {
|
|
maxlen = len(setA)
|
|
} else {
|
|
maxlen = len(setB)
|
|
}
|
|
mapI := make(map[ObjMetadata]struct{}, maxlen)
|
|
mapB := setB.ToMap()
|
|
for _, a := range setA {
|
|
if _, ok := mapB[a]; ok {
|
|
mapI[a] = struct{}{}
|
|
}
|
|
}
|
|
intersection := make(ObjMetadataSet, 0, len(mapI))
|
|
// Iterate over setA & setB to retain input order and have stable output
|
|
for _, id := range setA {
|
|
if _, ok := mapI[id]; ok {
|
|
intersection = append(intersection, id)
|
|
delete(mapI, id)
|
|
}
|
|
}
|
|
for _, id := range setB {
|
|
if _, ok := mapI[id]; ok {
|
|
intersection = append(intersection, id)
|
|
delete(mapI, id)
|
|
}
|
|
}
|
|
return intersection
|
|
}
|
|
|
|
// Union returns the set of unique objects from the merging of set A and set B.
|
|
func (setA ObjMetadataSet) Union(setB ObjMetadataSet) ObjMetadataSet {
|
|
m := make(map[ObjMetadata]struct{}, len(setA)+len(setB))
|
|
for _, a := range setA {
|
|
m[a] = struct{}{}
|
|
}
|
|
for _, b := range setB {
|
|
m[b] = struct{}{}
|
|
}
|
|
union := make(ObjMetadataSet, 0, len(m))
|
|
// Iterate over setA & setB to retain input order and have stable output
|
|
for _, id := range setA {
|
|
if _, ok := m[id]; ok {
|
|
union = append(union, id)
|
|
delete(m, id)
|
|
}
|
|
}
|
|
for _, id := range setB {
|
|
if _, ok := m[id]; ok {
|
|
union = append(union, id)
|
|
delete(m, id)
|
|
}
|
|
}
|
|
return union
|
|
}
|
|
|
|
// Diff returns the set of objects that exist in set A, but not in set B (A - B).
|
|
func (setA ObjMetadataSet) Diff(setB ObjMetadataSet) ObjMetadataSet {
|
|
// Create a map of the elements of A
|
|
m := make(map[ObjMetadata]struct{}, len(setA))
|
|
for _, a := range setA {
|
|
m[a] = struct{}{}
|
|
}
|
|
// Remove from A each element of B
|
|
for _, b := range setB {
|
|
delete(m, b) // OK to delete even if b not in m
|
|
}
|
|
// Create/return slice from the map of remaining items
|
|
diff := make(ObjMetadataSet, 0, len(m))
|
|
// Iterate over setA to retain input order and have stable output
|
|
for _, id := range setA {
|
|
if _, ok := m[id]; ok {
|
|
diff = append(diff, id)
|
|
delete(m, id)
|
|
}
|
|
}
|
|
return diff
|
|
}
|
|
|
|
// Unique returns the set with duplicates removed.
|
|
// Order may or may not remain consistent.
|
|
func (setA ObjMetadataSet) Unique() ObjMetadataSet {
|
|
return ObjMetadataSetFromMap(setA.ToMap())
|
|
}
|
|
|
|
// Hash the objects in the set by serializing, sorting, concatonating, and
|
|
// hashing the result with the 32-bit FNV-1a algorithm.
|
|
func (setA ObjMetadataSet) Hash() string {
|
|
objStrs := make([]string, 0, len(setA))
|
|
for _, obj := range setA {
|
|
objStrs = append(objStrs, obj.String())
|
|
}
|
|
sort.Strings(objStrs)
|
|
h := fnv.New32a()
|
|
for _, obj := range objStrs {
|
|
// Hash32.Write never returns an error
|
|
// https://pkg.go.dev/hash#pkg-types
|
|
_, _ = h.Write([]byte(obj))
|
|
}
|
|
return strconv.FormatUint(uint64(h.Sum32()), 16)
|
|
}
|
|
|
|
// ToMap returns the set as a map, with objMeta keys and empty struct values.
|
|
func (setA ObjMetadataSet) ToMap() map[ObjMetadata]struct{} {
|
|
m := make(map[ObjMetadata]struct{}, len(setA))
|
|
for _, objMeta := range setA {
|
|
m[objMeta] = struct{}{}
|
|
}
|
|
return m
|
|
}
|
|
|
|
// ToStringMap returns the set as a serializable map, with objMeta keys and
|
|
// empty string values.
|
|
func (setA ObjMetadataSet) ToStringMap() map[string]string {
|
|
stringMap := make(map[string]string, len(setA))
|
|
for _, objMeta := range setA {
|
|
stringMap[objMeta.String()] = ""
|
|
}
|
|
return stringMap
|
|
}
|
|
|
|
// FromStringMap returns a set from a serializable map, with objMeta keys and
|
|
// empty string values. Errors if parsing fails.
|
|
func FromStringMap(in map[string]string) (ObjMetadataSet, error) {
|
|
var set ObjMetadataSet
|
|
for s := range in {
|
|
objMeta, err := ParseObjMetadata(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
set = append(set, objMeta)
|
|
}
|
|
return set, nil
|
|
}
|