kubeconform/vendor/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/object.go
2023-01-24 16:34:14 +01:00

908 lines
28 KiB
Go

// Copyright 2022 Google LLC
//
// 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 fn
import (
"fmt"
"math"
"reflect"
"strconv"
"strings"
v1 "github.com/GoogleContainerTools/kpt-functions-sdk/go/api/kptfile/v1"
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/internal"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// KubeObject presents a k8s object.
type KubeObject struct {
SubObject
}
// ParseKubeObjects parses input byte slice to multiple KubeObjects.
func ParseKubeObjects(in []byte) ([]*KubeObject, error) {
doc, err := internal.ParseDoc(in)
if err != nil {
return nil, fmt.Errorf("failed to parse input bytes: %w", err)
}
objects, err := doc.Elements()
if err != nil {
return nil, fmt.Errorf("failed to extract objects: %w", err)
}
var kubeObjects []*KubeObject
for _, obj := range objects {
kubeObjects = append(kubeObjects, asKubeObject(obj))
}
return kubeObjects, nil
}
// ParseKubeObject parses input byte slice to a single KubeObject.
func ParseKubeObject(in []byte) (*KubeObject, error) {
objects, err := ParseKubeObjects(in)
if err != nil {
return nil, err
}
if len(objects) != 1 {
return nil, fmt.Errorf("expected exactly one object, got %d", len(objects))
}
obj := objects[0]
return obj, nil
}
// NestedBool returns the bool value, if the field exist and a potential error.
func (o *SubObject) NestedBool(fields ...string) (bool, bool, error) {
b, found, err := o.obj.GetNestedBool(fields...)
if err != nil {
var val bool
return val, found, NewErrUnmatchedField(*o, fields, val)
}
return b, found, nil
}
// NestedString returns the string value, if the field exist and a potential error.
func (o *SubObject) NestedString(fields ...string) (string, bool, error) {
s, found, err := o.obj.GetNestedString(fields...)
if err != nil {
var val string
return val, found, NewErrUnmatchedField(*o, fields, val)
}
return s, found, nil
}
// NestedFloat64 returns the float64 value, if the field exist and a potential error.
func (o *SubObject) NestedFloat64(fields ...string) (float64, bool, error) {
f, found, err := o.obj.GetNestedFloat(fields...)
if err != nil {
var val float64
return val, found, NewErrUnmatchedField(*o, fields, val)
}
return f, found, nil
}
// NestedInt64 returns the int64 value, if the field exist and a potential error.
func (o *SubObject) NestedInt64(fields ...string) (int64, bool, error) {
i, found, err := o.obj.GetNestedInt(fields...)
if err != nil {
var val int64
return val, found, NewErrUnmatchedField(*o, fields, val)
}
return int64(i), found, nil
}
// NestedInt returns the int64 value, if the field exist and a potential error.
func (o *SubObject) NestedInt(fields ...string) (int, bool, error) {
i, found, err := o.obj.GetNestedInt(fields...)
if err != nil {
var val int
return val, found, NewErrUnmatchedField(*o, fields, val)
}
return i, found, nil
}
// NestedSlice accepts a slice of `fields` which represents the path to the slice component and
// return a slice of SubObjects as the first return value; whether the component exists or
// not as the second return value, and errors as the third return value.
func (o *SubObject) NestedSlice(fields ...string) (SliceSubObjects, bool, error) {
// Expect a struct like SubObject.
var obj struct{}
var mapVariant *internal.MapVariant
if len(fields) > 1 {
m, found, err := o.obj.GetNestedMap(fields[:len(fields)-1]...)
if err != nil {
return nil, found, NewErrUnmatchedField(*o, fields, obj)
}
if !found {
return nil, found, nil
}
mapVariant = m
} else {
mapVariant = o.obj
}
sliceVal, found, err := mapVariant.GetNestedSlice(fields[len(fields)-1])
if err != nil {
return nil, found, NewErrUnmatchedField(*o, fields, obj)
}
if !found {
return nil, found, nil
}
objects, err := sliceVal.Elements()
if err != nil {
return nil, found, err
}
var val []*SubObject
for _, obj := range objects {
val = append(val, &SubObject{obj: obj})
}
return val, true, nil
}
// NestedMap returns a map[string]string value of a nested field, false if not found and an error if not a map[string]string type.
func (o *SubObject) NestedSubObject(fields ...string) (SubObject, bool, error) {
var variant SubObject
m, found, err := o.obj.GetNestedMap(fields...)
if err != nil {
return variant, found, NewErrUnmatchedField(*o, fields, variant)
}
if !found {
return variant, found, nil
}
err = m.Node().Decode(variant)
return variant, true, err
}
// NestedMap returns a map[string]string value of a nested field, false if not found and an error if not a map[string]string type.
func (o *SubObject) NestedResource(ptr interface{}, fields ...string) (bool, error) {
if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr {
return false, fmt.Errorf("ptr must be a pointer to an object")
}
k := reflect.TypeOf(ptr).Elem().Kind()
if k != reflect.Struct && k != reflect.Map {
return false, fmt.Errorf("expect struct or map, got %T", ptr)
}
m, found, err := o.obj.GetNestedMap(fields...)
if err != nil {
o.fieldpath = o.fieldpath + "." + strings.Join(fields, ".")
return found, NewErrUnmatchedField(*o, fields, ptr)
}
if !found {
return found, nil
}
err = m.Node().Decode(ptr)
return true, err
}
// NestedMap returns a map[string]string value of a nested field, false if not found and an error if not a map[string]string type.
func (o *SubObject) NestedStringMap(fields ...string) (map[string]string, bool, error) {
var variant map[string]string
m, found, err := o.obj.GetNestedMap(fields...)
if err != nil {
return variant, found, NewErrUnmatchedField(*o, fields, variant)
}
if !found {
return variant, false, nil
}
err = m.Node().Decode(&variant)
return variant, found, err
}
// NestedStringSlice returns a map[string]string value of a nested field, false if not found and an error if not a map[string]string type.
func (o *SubObject) NestedStringSlice(fields ...string) ([]string, bool, error) {
var variant []string
s, found, err := o.obj.GetNestedSlice(fields...)
if err != nil {
return variant, found, NewErrUnmatchedField(*o, fields, variant)
}
if !found {
return variant, false, nil
}
err = s.Node().Decode(&variant)
return variant, true, err
}
// RemoveNestedField removes the field located by fields if found. It returns if the field
// is found and a potential error.
func (o *SubObject) RemoveNestedField(fields ...string) (bool, error) {
found, err := func() (bool, error) {
if o == nil {
return false, fmt.Errorf("the object doesn't exist")
}
return o.obj.RemoveNestedField(fields...)
}()
if err != nil {
return found, fmt.Errorf("unable to remove fields %v with error: %w", fields, err)
}
return found, nil
}
// onLockedFields locks the SubObject fields which are expected for kpt internal use only.
func (o *SubObject) onLockedFields(val interface{}, fields ...string) error {
if o.hasUpstreamIdentifier(val, fields...) {
return ErrAttemptToTouchUpstreamIdentifier{}
}
return nil
}
// SetNestedField sets a nested field located by fields to the value provided as val. val
// should not be a yaml.RNode. If you want to deal with yaml.RNode, you should
// use Get method and modify the underlying yaml.Node.
func (o *SubObject) SetNestedField(val interface{}, fields ...string) error {
if err := o.onLockedFields(val, fields...); err != nil {
return err
}
err := func() error {
if val == nil {
return fmt.Errorf("the passed-in object must not be nil")
}
if o == nil {
return fmt.Errorf("the object doesn't exist")
}
if o.obj == nil {
o.obj = internal.NewMap(nil)
}
kind := reflect.ValueOf(val).Kind()
if kind == reflect.Ptr {
kind = reflect.TypeOf(val).Elem().Kind()
}
switch kind {
case reflect.Struct, reflect.Map:
m, err := internal.TypedObjectToMapVariant(val)
if err != nil {
return err
}
return o.obj.SetNestedMap(m, fields...)
case reflect.Slice:
s, err := internal.TypedObjectToSliceVariant(val)
if err != nil {
return err
}
return o.obj.SetNestedSlice(s, fields...)
case reflect.String:
var s string
switch val := val.(type) {
case string:
s = val
case *string:
s = *val
}
return o.obj.SetNestedString(s, fields...)
case reflect.Int, reflect.Int64:
var i int
switch val := val.(type) {
case int:
i = val
case *int:
i = *val
case int64:
i = int(val)
case *int64:
i = int(*val)
}
return o.obj.SetNestedInt(i, fields...)
case reflect.Float64:
var f float64
switch val := val.(type) {
case float64:
f = val
case *float64:
f = *val
}
return o.obj.SetNestedFloat(f, fields...)
case reflect.Bool:
var b bool
switch val := val.(type) {
case bool:
b = val
case *bool:
b = *val
}
return o.obj.SetNestedBool(b, fields...)
default:
return fmt.Errorf("unhandled kind %s", kind)
}
}()
if err != nil {
return fmt.Errorf("unable to set %v at fields %v with error: %w", val, fields, err)
}
return nil
}
// SetNestedInt sets the `fields` value to int `value`. It returns error if the fields type is not int.
func (o *SubObject) SetNestedInt(value int, fields ...string) error {
return o.SetNestedField(value, fields...)
}
// SetNestedBool sets the `fields` value to bool `value`. It returns error if the fields type is not bool.
func (o *SubObject) SetNestedBool(value bool, fields ...string) error {
return o.SetNestedField(value, fields...)
}
// SetNestedString sets the `fields` value to string `value`. It returns error if the fields type is not string.
func (o *SubObject) SetNestedString(value string, fields ...string) error {
return o.SetNestedField(value, fields...)
}
// SetNestedStringMap sets the `fields` value to map[string]string `value`. It returns error if the fields type is not map[string]string.
func (o *SubObject) SetNestedStringMap(value map[string]string, fields ...string) error {
return o.SetNestedField(value, fields...)
}
// SetNestedStringSlice sets the `fields` value to []string `value`. It returns error if the fields type is not []string.
func (o *SubObject) SetNestedStringSlice(value []string, fields ...string) error {
return o.SetNestedField(value, fields...)
}
// LineComment returns the line comment, if the target field exist and a
// potential error.
func (o *KubeObject) LineComment(fields ...string) (string, bool, error) {
rn, found, err := o.obj.GetRNode(fields...)
if !found || err != nil {
return "", found, err
}
return rn.YNode().LineComment, true, nil
}
// HeadComment returns the head comment, if the target field exist and a
// potential error.
func (o *KubeObject) HeadComment(fields ...string) (string, bool, error) {
rn, found, err := o.obj.GetRNode(fields...)
if !found || err != nil {
return "", found, err
}
return rn.YNode().HeadComment, true, nil
}
func (o *KubeObject) SetLineComment(comment string, fields ...string) error {
rn, found, err := o.obj.GetRNode(fields...)
if err != nil {
return err
}
if !found {
return fmt.Errorf("can't set line comment because the field doesn't exist")
}
rn.YNode().LineComment = comment
return nil
}
func (o *KubeObject) SetHeadComment(comment string, fields ...string) error {
rn, found, err := o.obj.GetRNode(fields...)
if err != nil {
return err
}
if !found {
return fmt.Errorf("can't set head comment because the field doesn't exist")
}
rn.YNode().HeadComment = comment
return nil
}
// As converts a KubeObject to the desired typed object. ptr must be
// a pointer to a typed object.
func (o *SubObject) As(ptr interface{}) error {
err := func() error {
if o == nil {
return fmt.Errorf("the object doesn't exist")
}
if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr {
return fmt.Errorf("ptr must be a pointer to an object")
}
return internal.MapVariantToTypedObject(o.obj, ptr)
}()
if err != nil {
return fmt.Errorf("unable to convert object to %T with error: %w", ptr, err)
}
return nil
}
// NewFromTypedObject construct a KubeObject from a typed object (e.g. corev1.Pod)
func NewFromTypedObject(v interface{}) (*KubeObject, error) {
kind := reflect.ValueOf(v).Kind()
if kind == reflect.Ptr {
kind = reflect.TypeOf(v).Elem().Kind()
}
var err error
var m *internal.MapVariant
switch kind {
case reflect.Struct, reflect.Map:
m, err = internal.TypedObjectToMapVariant(v)
case reflect.Slice:
return nil, fmt.Errorf(
"the typed object should be of a reflect.Struct or reflect.Map, got reflect.Slice")
}
if err != nil {
return nil, err
}
return asKubeObject(m), nil
}
// String serializes the object in yaml format.
func (o *SubObject) String() string {
doc := internal.NewDoc([]*yaml.Node{o.obj.Node()}...)
s, _ := doc.ToYAML()
return string(s)
}
// ShortString provides a human readable information for the KubeObject Identifier in the form of GVKNN.
func (o *KubeObject) ShortString() string {
return fmt.Sprintf("Resource(apiVersion=%v, kind=%v, namespace=%v, name=%v)",
o.GetAPIVersion(), o.GetKind(), o.GetNamespace(), o.GetName())
}
// resourceIdentifier returns the resource identifier including apiVersion, kind,
// namespace and name.
func (o *KubeObject) resourceIdentifier() *yaml.ResourceIdentifier {
apiVersion := o.GetAPIVersion()
kind := o.GetKind()
name := o.GetName()
ns := o.GetNamespace()
return &yaml.ResourceIdentifier{
TypeMeta: yaml.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
NameMeta: yaml.NameMeta{
Name: name,
Namespace: ns,
},
}
}
// GroupVersionKind returns the schema.GroupVersionKind for the specified object.
func (o *KubeObject) GroupVersionKind() schema.GroupVersionKind {
gv, err := schema.ParseGroupVersion(o.GetAPIVersion())
if err != nil {
return schema.GroupVersionKind{}
}
gvk := gv.WithKind(o.GetKind())
return gvk
}
// GroupKind returns the schema.GroupKind for the specified object.
func (o *KubeObject) GroupKind() schema.GroupKind {
return o.GroupVersionKind().GroupKind()
}
// IsGroupVersionKind compares the given group, version, and kind with KubeObject's apiVersion and Kind.
func (o *KubeObject) IsGroupVersionKind(gvk schema.GroupVersionKind) bool {
return o.GroupVersionKind() == gvk
}
// IsGroupKind compares the given group and kind with KubeObject's apiVersion and Kind.
func (o *KubeObject) IsGroupKind(gk schema.GroupKind) bool {
return o.GroupKind() == gk
}
// IsGVK compares the given group, version, and kind with KubeObject's apiVersion and Kind.
// It only matches on specified arguments, for example if the group is empty this will match any group.
// Deprecated: Prefer exact matching with IsGroupVersionKind or IsGroupKind
func (o *KubeObject) IsGVK(group, version, kind string) bool {
gvk := o.GroupVersionKind()
if gvk.Kind != "" && kind != "" && gvk.Kind != kind {
return false
}
if gvk.Group != "" && group != "" && gvk.Group != group {
return false
}
if gvk.Version != "" && version != "" && gvk.Version != version {
return false
}
return true
}
// IsLocalConfig checks the "config.kubernetes.io/local-config" field to tell
// whether a KRM resource will be skipped by `kpt live apply` or not.
func (o *KubeObject) IsLocalConfig() bool {
isLocalConfig := o.GetAnnotation(KptLocalConfig)
if isLocalConfig == "" || isLocalConfig == "false" {
return false
}
return true
}
// IsLocalConfig determines whether a KubeObject (or KRM resource) has the config.kubernetes.io/local-config: true annotation
func IsLocalConfig(o *KubeObject) bool {
return o.IsLocalConfig()
}
func (o *KubeObject) GetAPIVersion() string {
apiVersion, _, _ := o.obj.GetNestedString("apiVersion")
return apiVersion
}
func (o *KubeObject) SetAPIVersion(apiVersion string) error {
return o.obj.SetNestedString(apiVersion, "apiVersion")
}
func (o *KubeObject) GetKind() string {
kind, _, _ := o.obj.GetNestedString("kind")
return kind
}
func (o *KubeObject) SetKind(kind string) error {
return o.SetNestedField(kind, "kind")
}
func (o *KubeObject) GetName() string {
s, _, _ := o.obj.GetNestedString("metadata", "name")
return s
}
func (o *KubeObject) SetName(name string) error {
return o.SetNestedField(name, "metadata", "name")
}
func (o *KubeObject) GetNamespace() string {
s, _, _ := o.obj.GetNestedString("metadata", "namespace")
return s
}
// IsNamespaceScoped tells whether a k8s resource is namespace scoped. If the KubeObject resource is a customized, it
// determines the namespace scope by checking whether `metadata.namespace` is set.
func (o *KubeObject) IsNamespaceScoped() bool {
tm := yaml.TypeMeta{Kind: o.GetKind(), APIVersion: o.GetAPIVersion()}
if nsScoped, ok := internal.PrecomputedIsNamespaceScoped[tm]; ok {
return nsScoped
}
// TODO(yuwenma): parse the resource openapi schema to know its scope status.
return o.HasNamespace()
}
// IsClusterScoped tells whether a resource is cluster scoped.
func (o *KubeObject) IsClusterScoped() bool {
return !o.IsNamespaceScoped()
}
func (o *KubeObject) HasNamespace() bool {
_, found, _ := o.obj.GetNestedString("metadata", "namespace")
return found
}
func (o *KubeObject) SetNamespace(name string) error {
return o.SetNestedField(name, "metadata", "namespace")
}
func (o *KubeObject) SetAnnotation(k, v string) error {
// Keep upstream-identifier untouched from users
if k == UpstreamIdentifier {
return ErrAttemptToTouchUpstreamIdentifier{}
}
if err := o.SetNestedField(v, "metadata", "annotations", k); err != nil {
return fmt.Errorf("cannot set metadata annotations '%v': %v", k, err)
}
return nil
}
// GetAnnotations returns all annotations.
func (o *KubeObject) GetAnnotations() map[string]string {
v, _, _ := o.obj.GetNestedStringMap("metadata", "annotations")
return v
}
// GetAnnotation returns one annotation with key k.
func (o *KubeObject) GetAnnotation(k string) string {
v, _, _ := o.obj.GetNestedString("metadata", "annotations", k)
return v
}
// HasAnnotations returns whether the KubeObject has all the given annotations.
func (o *KubeObject) HasAnnotations(annotations map[string]string) bool {
kubeObjectLabels := o.GetAnnotations()
for k, v := range annotations {
kubeObjectValue, found := kubeObjectLabels[k]
if !found || kubeObjectValue != v {
return false
}
}
return true
}
// RemoveAnnotationsIfEmpty removes the annotations field when it has zero annotations.
func (o *KubeObject) RemoveAnnotationsIfEmpty() error {
annotations, found, err := o.obj.GetNestedStringMap("metadata", "annotations")
if err != nil {
return err
}
if found && len(annotations) == 0 {
_, err = o.obj.RemoveNestedField("metadata", "annotations")
return err
}
return nil
}
func (o *KubeObject) SetLabel(k, v string) error {
return o.SetNestedField(v, "metadata", "labels", k)
}
// Label returns one label with key k.
func (o *KubeObject) GetLabel(k string) string {
v, _, _ := o.obj.GetNestedString("metadata", "labels", k)
return v
}
// Labels returns all labels.
func (o *KubeObject) GetLabels() map[string]string {
v, _, _ := o.obj.GetNestedStringMap("metadata", "labels")
return v
}
// HasLabels returns whether the KubeObject has all the given labels
func (o *KubeObject) HasLabels(labels map[string]string) bool {
kubeObjectLabels := o.GetLabels()
for k, v := range labels {
kubeObjectValue, found := kubeObjectLabels[k]
if !found || kubeObjectValue != v {
return false
}
}
return true
}
func (o *KubeObject) PathAnnotation() string {
anno := o.GetAnnotation(kioutil.PathAnnotation)
return anno
}
// IndexAnnotation return -1 if not found.
func (o *KubeObject) IndexAnnotation() int {
anno := o.GetAnnotation(kioutil.IndexAnnotation)
if anno == "" {
return -1
}
i, _ := strconv.Atoi(anno)
return i
}
// IdAnnotation return -1 if not found.
func (o *KubeObject) IdAnnotation() int {
anno := o.GetAnnotation(kioutil.IdAnnotation)
if anno == "" {
return -1
}
i, _ := strconv.Atoi(anno)
return i
}
type KubeObjects []*KubeObject
func (o KubeObjects) Len() int { return len(o) }
func (o KubeObjects) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o KubeObjects) Less(i, j int) bool {
idi := o[i].resourceIdentifier()
idj := o[j].resourceIdentifier()
idStrI := fmt.Sprintf("%s %s %s %s", idi.GetAPIVersion(), idi.GetKind(), idi.GetNamespace(), idi.GetName())
idStrJ := fmt.Sprintf("%s %s %s %s", idj.GetAPIVersion(), idj.GetKind(), idj.GetNamespace(), idj.GetName())
return idStrI < idStrJ
}
func (o KubeObjects) String() string {
var elems []string
for _, obj := range o {
elems = append(elems, strings.TrimSpace(obj.String()))
}
return strings.Join(elems, "\n---\n")
}
// Where will return the subset of objects in KubeObjects such that f(object) returns 'true'.
func (o KubeObjects) Where(f func(*KubeObject) bool) KubeObjects {
var result KubeObjects
for _, obj := range o {
if f(obj) {
result = append(result, obj)
}
}
return result
}
// Not returns will return a function that returns the opposite of f(object), i.e. !f(object)
func Not(f func(*KubeObject) bool) func(o *KubeObject) bool {
return func(o *KubeObject) bool {
return !f(o)
}
}
// WhereNot will return the subset of objects in KubeObjects such that f(object) returns 'false'.
// This is a shortcut for Where(Not(f)).
func (o KubeObjects) WhereNot(f func(o *KubeObject) bool) KubeObjects {
return o.Where(Not(f))
}
// IsGVK returns a function that checks if a KubeObject has a certain GVK.
// Deprecated: Prefer exact matching with IsGroupVersionKind or IsGroupKind
func IsGVK(group, version, kind string) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.IsGVK(group, version, kind)
}
}
// IsGroupVersionKind returns a function that checks if a KubeObject has a certain GroupVersionKind.
func IsGroupVersionKind(gvk schema.GroupVersionKind) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.IsGroupVersionKind(gvk)
}
}
// IsGroupKind returns a function that checks if a KubeObject has a certain GroupKind.
func IsGroupKind(gk schema.GroupKind) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.IsGroupKind(gk)
}
}
// GetRootKptfile returns the root Kptfile. Nested kpt packages can have multiple Kptfile files of the same GVKNN.
func (o KubeObjects) GetRootKptfile() *KubeObject {
kptfiles := o.Where(IsGVK(v1.KptFileGroup, v1.KptFileVersion, v1.KptFileKind))
if len(kptfiles) == 0 {
return nil
}
minDepths := math.MaxInt32
var rootKptfile *KubeObject
for _, kf := range kptfiles {
path := kf.GetAnnotation(PathAnnotation)
depths := len(strings.Split(path, "/"))
if depths <= minDepths {
minDepths = depths
rootKptfile = kf
}
}
return rootKptfile
}
// IsName returns a function that checks if a KubeObject has a certain name.
func IsName(name string) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.GetName() == name
}
}
// IsNamespace returns a function that checks if a KubeObject has a certain namespace.
func IsNamespace(namespace string) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.GetNamespace() == namespace
}
}
// HasLabels returns a function that checks if a KubeObject has all the given labels.
func HasLabels(labels map[string]string) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.HasLabels(labels)
}
}
// HasAnnotations returns a function that checks if a KubeObject has all the given annotations.
func HasAnnotations(annotations map[string]string) func(*KubeObject) bool {
return func(o *KubeObject) bool {
return o.HasAnnotations(annotations)
}
}
// IsMetaResource returns a function that checks if a KubeObject is a meta resource. For now
// this just includes the Kptfile
func IsMetaResource() func(*KubeObject) bool {
return IsGVK("kpt.dev", "v1", "Kptfile")
}
func (o *KubeObject) IsEmpty() bool {
return yaml.IsYNodeEmptyMap(o.obj.Node())
}
func NewEmptyKubeObject() *KubeObject {
subObject := SubObject{parentGVK: schema.GroupVersionKind{}, obj: internal.NewMap(nil), fieldpath: ""}
return &KubeObject{subObject}
}
func asKubeObject(mapVariant *internal.MapVariant) *KubeObject {
group, _, _ := mapVariant.GetNestedString("group")
version, _, _ := mapVariant.GetNestedString("version")
kind, _, _ := mapVariant.GetNestedString("kind")
gvk := schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
return &KubeObject{SubObject{parentGVK: gvk, obj: mapVariant, fieldpath: ""}}
}
func (o *KubeObject) node() *internal.MapVariant {
return o.obj
}
func rnodeToKubeObject(rn *yaml.RNode) *KubeObject {
mapVariant := internal.NewMap(rn.YNode())
return asKubeObject(mapVariant)
}
// SubObject represents a map within a KubeObject
type SubObject struct {
parentGVK schema.GroupVersionKind
fieldpath string
obj *internal.MapVariant
}
func (o *SubObject) UpsertMap(k string) *SubObject {
m := o.obj.UpsertMap(k)
return &SubObject{obj: m, parentGVK: o.parentGVK, fieldpath: o.fieldpath + "." + k}
}
// GetMap accepts a single key `k` whose value is expected to be a map. It returns
// the map in the form of a SubObject pointer.
// It panic with ErrSubObjectFields error if the field cannot be represented as a SubObject.
func (o *SubObject) GetMap(k string) *SubObject {
var rn yaml.RNode
val, found, err := o.obj.GetNestedValue(k)
if err != nil || !found {
return nil
}
rn.SetYNode(val.Node())
return &SubObject{obj: internal.NewMap(rn.YNode()), parentGVK: o.parentGVK, fieldpath: o.fieldpath + "." + k}
}
// GetBool accepts a single key `k` whose value is expected to be a boolean. It returns
// the int value of the `k`. It panic with errSubObjectFields error if the
// field is not an integer type.
func (o *SubObject) GetBool(k string) bool {
val, _, _ := o.NestedBool(k)
return val
}
// GetInt accepts a single key `k` whose value is expected to be an integer. It returns
// the int value of the `k`. It panic with errSubObjectFields error if the
// field is not an integer type.
func (o *SubObject) GetInt(k string) int64 {
val, _, _ := o.NestedInt64(k)
return val
}
// GetString accepts a single key `k` whose value is expected to be a string. It returns
// the value of the `k`. It panic with errSubObjectFields error if the
// field is not a string type.
func (o *SubObject) GetString(k string) string {
val, _, _ := o.NestedString(k)
return val
}
// GetSlice accepts a single key `k` whose value is expected to be a slice. It returns
// the value as a slice of SubObject. It panic with errSubObjectFields error if the
// field is not a slice type.
func (o *SubObject) GetSlice(k string) SliceSubObjects {
val, _, _ := o.NestedSlice(k)
return val
}
// SetSlice sets the SliceSubObjects to the given field. It creates the field if not exists. If returns error if the field exists but not a slice type.
func (o *SubObject) SetSlice(objects SliceSubObjects, field string) error {
s := internal.NewSliceVariant()
for _, element := range objects {
s.Add(element.obj)
}
return o.obj.SetNestedSlice(s, field)
}
type SliceSubObjects []*SubObject
// MarshalJSON provides the custom encoding format for encode.json. This is used
// when KubeObject `Set` a slice of SubObjects.
func (s *SliceSubObjects) MarshalJSON() ([]byte, error) {
node := &yaml.Node{Kind: yaml.SequenceNode}
for _, subObject := range *s {
node.Content = append(node.Content, subObject.obj.Node())
}
return yaml.NewRNode(node).MarshalJSON()
}
// DEPRECATED: Please use type-aware functions instead.
// To parse struct object, please use `NestedResource`.
func (o *SubObject) Get(_ interface{}, _ ...string) (bool, error) {
return false, fmt.Errorf("unsupported")
}