working commit

This commit is contained in:
2026-03-13 19:02:42 +02:00
parent bebbf79c7a
commit 5c1da77f4c
1329 changed files with 314708 additions and 39 deletions
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package object
import (
"fmt"
)
// InvalidAnnotationError represents an invalid annotation.
// Fields are exposed to allow callers to perform introspection.
type InvalidAnnotationError struct {
Annotation string
Cause error
}
func (iae InvalidAnnotationError) Error() string {
return fmt.Sprintf("invalid %q annotation: %v",
iae.Annotation, iae.Cause)
}
func (iae InvalidAnnotationError) Unwrap() error {
return iae.Cause
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package object
import (
"fmt"
"regexp"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// NestedField gets a value from a KRM map, if it exists, otherwise nil.
// Fields can be string (map key) or int (array index).
func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{}, bool, error) {
var val interface{} = obj
for i, field := range fields {
if val == nil {
return nil, false, nil
}
switch typedField := field.(type) {
case string:
if m, ok := val.(map[string]interface{}); ok {
val, ok = m[typedField]
if !ok {
// not in map
return nil, false, nil
}
} else {
return nil, false, InvalidType(fields[:i+1], val, "map[string]interface{}")
}
case int:
if s, ok := val.([]interface{}); ok {
if typedField >= len(s) {
// index out of range
return nil, false, nil
}
val = s[typedField]
} else {
return nil, false, InvalidType(fields[:i+1], val, "[]interface{}")
}
default:
return nil, false, InvalidType(fields[:i+1], val, "string or int")
}
}
return val, true, nil
}
// InvalidType returns a *Error indicating "invalid value type". This is used
// to report malformed values (e.g. found int, expected string).
func InvalidType(fieldPath []interface{}, value interface{}, validTypes string) *field.Error {
return Invalid(fieldPath, value,
fmt.Sprintf("found type %T, expected %s", value, validTypes))
}
// Invalid returns a *Error indicating "invalid value". This is used
// to report malformed values (e.g. failed regex match, too long, out of bounds).
func Invalid(fieldPath []interface{}, value interface{}, detail string) *field.Error {
return &field.Error{
Type: field.ErrorTypeInvalid,
Field: FieldPath(fieldPath),
BadValue: value,
Detail: detail,
}
}
// NotFound returns a *Error indicating "value not found". This is
// used to report failure to find a requested value (e.g. looking up an ID).
func NotFound(fieldPath []interface{}, value interface{}) *field.Error {
return &field.Error{
Type: field.ErrorTypeNotFound,
Field: FieldPath(fieldPath),
BadValue: value,
Detail: "",
}
}
// FieldPath formats a list of KRM field keys as a JSONPath expression.
// The only valid field keys in KRM are strings (map keys) and ints (list keys).
// Simple strings (see isSimpleString) will be delimited with a period.
// Complex strings will be wrapped with square brackets and double quotes.
// Integers will be wrapped with square brackets.
// All other types will be formatted best-effort within square brackets.
func FieldPath(fieldPath []interface{}) string {
var sb strings.Builder
for _, field := range fieldPath {
switch typedField := field.(type) {
case string:
if isSimpleString(typedField) {
_, _ = fmt.Fprintf(&sb, ".%s", typedField)
} else {
_, _ = fmt.Fprintf(&sb, "[%q]", typedField)
}
case int:
_, _ = fmt.Fprintf(&sb, "[%d]", typedField)
default:
// invalid type. try anyway...
_, _ = fmt.Fprintf(&sb, "[%#v]", typedField)
}
}
return sb.String()
}
var simpleStringRegex = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$`)
// isSimpleString returns true if the input follows the following rules:
// - contains only alphanumeric characters, '_' or '-'
// - starts with an alphabetic character
// - ends with an alphanumeric character
func isSimpleString(s string) bool {
return simpleStringRegex.FindString(s) != ""
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package object
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
)
// InfosToObjMetas returns object metadata (ObjMetadata) for the
// passed objects (infos); returns an error if one occurs.
func InfosToObjMetas(infos []*resource.Info) ([]ObjMetadata, error) {
objMetas := make([]ObjMetadata, 0, len(infos))
for _, info := range infos {
objMeta, err := InfoToObjMeta(info)
if err != nil {
return nil, err
}
objMetas = append(objMetas, objMeta)
}
return objMetas, nil
}
// InfoToObjMeta takes information from the provided info and
// returns an ObjMetadata that identifies the resource.
func InfoToObjMeta(info *resource.Info) (ObjMetadata, error) {
if info == nil || info.Object == nil {
return ObjMetadata{}, fmt.Errorf("attempting to transform info, but it is empty")
}
id := ObjMetadata{
Namespace: info.Namespace,
Name: info.Name,
GroupKind: info.Object.GetObjectKind().GroupVersionKind().GroupKind(),
}
return id, nil
}
// InfoToUnstructured transforms the passed info object into unstructured format.
func InfoToUnstructured(info *resource.Info) *unstructured.Unstructured {
return info.Object.(*unstructured.Unstructured)
}
// UnstructuredToInfo transforms the passed Unstructured object into Info format,
// or an error if one occurs.
func UnstructuredToInfo(obj *unstructured.Unstructured) (*resource.Info, error) {
// make a copy of the input object to avoid modifying the input
obj = obj.DeepCopy()
annos := obj.GetAnnotations()
source := "unstructured"
path, ok := annos[kioutil.PathAnnotation]
if ok {
source = path
}
StripKyamlAnnotations(obj)
return &resource.Info{
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
Source: source,
Object: obj,
}, nil
}
// InfosToUnstructureds transforms the passed objects in Info format to Unstructured.
func InfosToUnstructureds(infos []*resource.Info) []*unstructured.Unstructured {
var objs []*unstructured.Unstructured
for _, info := range infos {
objs = append(objs, InfoToUnstructured(info))
}
return objs
}
// UnstructuredsToInfos transforms the passed Unstructured objects into Info format
// or an error if one occurs.
func UnstructuredsToInfos(objs []*unstructured.Unstructured) ([]*resource.Info, error) {
var infos []*resource.Info
for _, obj := range objs {
inf, err := UnstructuredToInfo(obj)
if err != nil {
return infos, err
}
infos = append(infos, inf)
}
return infos, nil
}
+147
View File
@@ -0,0 +1,147 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
// ObjMetadata is the minimal set of information to
// uniquely identify an object. The four fields are:
//
// Group/Kind (NOTE: NOT version)
// Namespace
// Name
//
// We specifically do not use the "version", because
// the APIServer does not recognize a version as a
// different resource. This metadata is used to identify
// resources for pruning and teardown.
package object
import (
"fmt"
"strings"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const (
// Separates inventory fields. This string is allowable as a
// ConfigMap key, but it is not allowed as a character in
// resource name.
fieldSeparator = "_"
// Transform colons in the RBAC resource names to double
// underscore.
colonTranscoded = "__"
)
var (
NilObjMetadata = ObjMetadata{}
)
// RBACGroupKind is a map of the RBAC resources. Needed since name validation
// is different than other k8s resources.
var RBACGroupKind = map[schema.GroupKind]bool{
{Group: rbacv1.GroupName, Kind: "Role"}: true,
{Group: rbacv1.GroupName, Kind: "ClusterRole"}: true,
{Group: rbacv1.GroupName, Kind: "RoleBinding"}: true,
{Group: rbacv1.GroupName, Kind: "ClusterRoleBinding"}: true,
}
// ObjMetadata organizes and stores the indentifying information
// for an object. This struct (as a string) is stored in a
// inventory object to keep track of sets of applied objects.
type ObjMetadata struct {
Namespace string
Name string
GroupKind schema.GroupKind
}
// ParseObjMetadata takes a string, splits it into its four fields,
// and returns an ObjMetadata struct storing the four fields.
// Example inventory string:
//
// test-namespace_test-name_apps_ReplicaSet
//
// Returns an error if unable to parse and create the ObjMetadata struct.
//
// NOTE: name field can contain double underscore (__), which represents
// a colon. RBAC resources can have this additional character (:) in their name.
func ParseObjMetadata(s string) (ObjMetadata, error) {
// Parse first field namespace
index := strings.Index(s, fieldSeparator)
if index == -1 {
return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s)
}
namespace := s[:index]
s = s[index+1:]
// Next, parse last field kind
index = strings.LastIndex(s, fieldSeparator)
if index == -1 {
return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s)
}
kind := s[index+1:]
s = s[:index]
// Next, parse next to last field group
index = strings.LastIndex(s, fieldSeparator)
if index == -1 {
return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s)
}
group := s[index+1:]
// Finally, second field name. Name may contain colon transcoded as double underscore.
name := s[:index]
name = strings.ReplaceAll(name, colonTranscoded, ":")
// Check that there are no extra fields by search for fieldSeparator.
if strings.Contains(name, fieldSeparator) {
return NilObjMetadata, fmt.Errorf("too many fields within: %s", s)
}
// Create the ObjMetadata object from the four parsed fields.
id := ObjMetadata{
Namespace: namespace,
Name: name,
GroupKind: schema.GroupKind{
Group: group,
Kind: kind,
},
}
return id, nil
}
// Equals compares two ObjMetadata and returns true if they are equal. This does
// not contain any special treatment for the extensions API group.
func (o *ObjMetadata) Equals(other *ObjMetadata) bool {
if other == nil {
return false
}
return *o == *other
}
// String create a string version of the ObjMetadata struct. For RBAC resources,
// the "name" field transcodes ":" into double underscore for valid storing
// as the label of a ConfigMap.
func (o ObjMetadata) String() string {
name := o.Name
if _, exists := RBACGroupKind[o.GroupKind]; exists {
name = strings.ReplaceAll(name, ":", colonTranscoded)
}
return fmt.Sprintf("%s%s%s%s%s%s%s",
o.Namespace, fieldSeparator,
name, fieldSeparator,
o.GroupKind.Group, fieldSeparator,
o.GroupKind.Kind)
}
// RuntimeToObjMeta extracts the object metadata information from a
// runtime.Object and returns it as ObjMetadata.
func RuntimeToObjMeta(obj runtime.Object) (ObjMetadata, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return NilObjMetadata, err
}
id := ObjMetadata{
Namespace: accessor.GetNamespace(),
Name: accessor.GetName(),
GroupKind: obj.GetObjectKind().GroupVersionKind().GroupKind(),
}
return id, nil
}
+211
View File
@@ -0,0 +1,211 @@
// 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
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package object
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
)
// YamlStringer delays YAML marshalling for logging until String() is called.
type YamlStringer struct {
O *unstructured.Unstructured
}
// String marshals the wrapped object to a YAML string. If serializing errors,
// the error string will be returned instead. This is primarily for use with
// verbose logging.
func (ys YamlStringer) String() string {
yamlBytes, err := yaml.Marshal(ys.O)
if err != nil {
return fmt.Sprintf("<<failed to serialize as yaml: %s>>", err)
}
return string(yamlBytes)
}
+214
View File
@@ -0,0 +1,214 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package object
import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
)
var (
namespaceGK = schema.GroupKind{Group: "", Kind: "Namespace"}
crdGK = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}
)
// UnstructuredSetToObjMetadataSet converts a UnstructuredSet to a ObjMetadataSet.
func UnstructuredSetToObjMetadataSet(objs UnstructuredSet) ObjMetadataSet {
objMetas := make([]ObjMetadata, len(objs))
for i, obj := range objs {
objMetas[i] = UnstructuredToObjMetadata(obj)
}
return objMetas
}
// UnstructuredToObjMetadata extracts the identifying information from an
// Unstructured object and returns it as ObjMetadata object.
func UnstructuredToObjMetadata(obj *unstructured.Unstructured) ObjMetadata {
return ObjMetadata{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
GroupKind: obj.GroupVersionKind().GroupKind(),
}
}
// IsKindNamespace returns true if the passed Unstructured object is
// GroupKind == Core/Namespace (no version checked); false otherwise.
func IsKindNamespace(u *unstructured.Unstructured) bool {
if u == nil {
return false
}
gvk := u.GroupVersionKind()
return namespaceGK == gvk.GroupKind()
}
// IsNamespaced returns true if the passed Unstructured object
// is namespace-scoped (not cluster-scoped); false otherwise.
func IsNamespaced(u *unstructured.Unstructured) bool {
if u == nil {
return false
}
return u.GetNamespace() != ""
}
// IsNamespace returns true if the passed Unstructured object
// is Namespace in the core (empty string) group.
func IsNamespace(u *unstructured.Unstructured) bool {
if u == nil {
return false
}
gvk := u.GroupVersionKind()
// core group, any version
return gvk.Group == "" && gvk.Kind == "Namespace"
}
// IsCRD returns true if the passed Unstructured object has
// GroupKind == Extensions/CustomResourceDefinition; false otherwise.
func IsCRD(u *unstructured.Unstructured) bool {
if u == nil {
return false
}
gvk := u.GroupVersionKind()
return crdGK == gvk.GroupKind()
}
// GetCRDGroupKind returns the GroupKind stored in the passed
// Unstructured CustomResourceDefinition and true if the passed object
// is a CRD.
func GetCRDGroupKind(u *unstructured.Unstructured) (schema.GroupKind, bool) {
emptyGroupKind := schema.GroupKind{Group: "", Kind: ""}
if u == nil {
return emptyGroupKind, false
}
group, found, err := unstructured.NestedString(u.Object, "spec", "group")
if found && err == nil {
kind, found, err := unstructured.NestedString(u.Object, "spec", "names", "kind")
if found && err == nil {
return schema.GroupKind{Group: group, Kind: kind}, true
}
}
return emptyGroupKind, false
}
// UnknownTypeError captures information about a type for which no information
// could be found in the cluster or among the known CRDs.
type UnknownTypeError struct {
GroupVersionKind schema.GroupVersionKind
}
func (e *UnknownTypeError) Error() string {
return fmt.Sprintf("unknown resource type: %q", e.GroupVersionKind.String())
}
// LookupResourceScope tries to look up the scope of the type of the provided
// resource, looking at both the types known to the cluster (through the
// RESTMapper) and the provided CRDs. If no information about the type can
// be found, an UnknownTypeError wil be returned.
func LookupResourceScope(u *unstructured.Unstructured, crds []*unstructured.Unstructured, mapper meta.RESTMapper) (meta.RESTScope, error) {
gvk := u.GroupVersionKind()
// First see if we can find the type (and the scope) in the cluster through
// the RESTMapper.
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err == nil {
// If we find the type in the cluster, we just look up the scope there.
return mapping.Scope, nil
}
// Not finding a match is not an error here, so only error out for other
// error types.
if !meta.IsNoMatchError(err) {
return nil, err
}
// If we couldn't find the type in the cluster, check if we find a
// match in any of the provided CRDs.
for _, crd := range crds {
group, found, err := NestedField(crd.Object, "spec", "group")
if err != nil {
return nil, err
}
if !found || group == "" {
return nil, NotFound([]interface{}{"spec", "group"}, group)
}
kind, found, err := NestedField(crd.Object, "spec", "names", "kind")
if err != nil {
return nil, err
}
if !found || kind == "" {
return nil, NotFound([]interface{}{"spec", "kind"}, group)
}
if gvk.Kind != kind || gvk.Group != group {
continue
}
versionDefined, err := crdDefinesVersion(crd, gvk.Version)
if err != nil {
return nil, err
}
if !versionDefined {
return nil, &UnknownTypeError{
GroupVersionKind: gvk,
}
}
scopeName, _, err := NestedField(crd.Object, "spec", "scope")
if err != nil {
return nil, err
}
switch scopeName {
case "Namespaced":
return meta.RESTScopeNamespace, nil
case "Cluster":
return meta.RESTScopeRoot, nil
default:
return nil, Invalid([]interface{}{"spec", "scope"}, scopeName,
"expected Namespaced or Cluster")
}
}
return nil, &UnknownTypeError{
GroupVersionKind: gvk,
}
}
func crdDefinesVersion(crd *unstructured.Unstructured, version string) (bool, error) {
versions, found, err := NestedField(crd.Object, "spec", "versions")
if err != nil {
return false, err
}
if !found {
return false, NotFound([]interface{}{"spec", "versions"}, versions)
}
versionsSlice, ok := versions.([]interface{})
if !ok {
return false, InvalidType([]interface{}{"spec", "versions"}, versions, "[]interface{}")
}
if len(versionsSlice) == 0 {
return false, Invalid([]interface{}{"spec", "versions"}, versionsSlice, "must not be empty")
}
for i := range versionsSlice {
name, found, err := NestedField(crd.Object, "spec", "versions", i, "name")
if err != nil {
return false, err
}
if !found {
return false, NotFound([]interface{}{"spec", "versions", i, "name"}, name)
}
if name == version {
return true, nil
}
}
return false, nil
}
// StripKyamlAnnotations removes any path and index annotations from the
// unstructured resource.
func StripKyamlAnnotations(u *unstructured.Unstructured) {
annos := u.GetAnnotations()
delete(annos, kioutil.PathAnnotation)
delete(annos, kioutil.LegacyPathAnnotation) //nolint:staticcheck
delete(annos, kioutil.IndexAnnotation)
delete(annos, kioutil.LegacyIndexAnnotation) //nolint:staticcheck
u.SetAnnotations(annos)
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package object
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// UnstructuredSet is an ordered list of Unstructured that acts like an
// unordered set for comparison purposes.
type UnstructuredSet []*unstructured.Unstructured
// UnstructuredSetEquals returns true if the slice of objects in setA equals
// the slice of objects in setB.
func UnstructuredSetEquals(setA []*unstructured.Unstructured, setB []*unstructured.Unstructured) bool {
return UnstructuredSet(setA).Equal(UnstructuredSet(setB))
}
func (setA UnstructuredSet) Equal(setB UnstructuredSet) bool {
mapA := make(map[string]string, len(setA))
for _, a := range setA {
jsonBytes, err := a.MarshalJSON()
if err != nil {
mapA[string(jsonBytes)] = err.Error()
} else {
mapA[string(jsonBytes)] = ""
}
}
mapB := make(map[string]string, len(setB))
for _, b := range setB {
jsonBytes, err := b.MarshalJSON()
if err != nil {
mapB[string(jsonBytes)] = err.Error()
} else {
mapB[string(jsonBytes)] = ""
}
}
if len(mapA) != len(mapB) {
return false
}
for b, errB := range mapB {
if errA, exists := mapA[b]; !exists {
if !exists {
return false
}
if errA != errB {
return false
}
}
}
return true
}