mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-19 01:47:02 +00:00
908 lines
28 KiB
Go
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")
|
|
}
|