working commit
This commit is contained in:
+24
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user