mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 10:30:59 +00:00
Merge pull request #3863 from monopole/simplifyGvk
Simplify gvk, speed up cluster-scoped checks.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,8 +46,8 @@ type Filter struct {
|
|||||||
|
|
||||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||||
// check if the FieldSpec applies to the object
|
// check if the FieldSpec applies to the object
|
||||||
if match, err := isMatchGVK(fltr.FieldSpec, obj); !match || err != nil {
|
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
|
||||||
return obj, errors.Wrap(err)
|
return obj, nil
|
||||||
}
|
}
|
||||||
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path)
|
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path)
|
||||||
err := fltr.filter(obj)
|
err := fltr.filter(obj)
|
||||||
@@ -158,28 +159,24 @@ func isSequenceField(name string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
||||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) (bool, error) {
|
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) bool {
|
||||||
meta, err := obj.GetMeta()
|
if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if fs.Kind != "" && fs.Kind != meta.Kind {
|
|
||||||
// kind doesn't match
|
// kind doesn't match
|
||||||
return false, err
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the group and version from the apiVersion field
|
// parse the group and version from the apiVersion field
|
||||||
group, version := parseGV(meta.APIVersion)
|
group, version := resid.ParseGroupVersion(obj.GetApiVersion())
|
||||||
|
|
||||||
if fs.Group != "" && fs.Group != group {
|
if fs.Group != "" && fs.Group != group {
|
||||||
// group doesn't match
|
// group doesn't match
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.Version != "" && fs.Version != version {
|
if fs.Version != "" && fs.Version != version {
|
||||||
// version doesn't match
|
// version doesn't match
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ xxx:
|
|||||||
"empty path": {
|
"empty path": {
|
||||||
fieldSpec: `
|
fieldSpec: `
|
||||||
group: foo
|
group: foo
|
||||||
|
version: v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
`,
|
`,
|
||||||
input: `
|
input: `
|
||||||
apiVersion: foo
|
apiVersion: foo/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
xxx:
|
xxx:
|
||||||
`,
|
`,
|
||||||
@@ -59,7 +60,7 @@ kind: Bar
|
|||||||
xxx:
|
xxx:
|
||||||
`,
|
`,
|
||||||
error: `considering field '' of object
|
error: `considering field '' of object
|
||||||
apiVersion: foo
|
apiVersion: foo/v1
|
||||||
kind: Bar
|
kind: Bar
|
||||||
xxx:
|
xxx:
|
||||||
: cannot set or create an empty field name`,
|
: cannot set or create an empty field name`,
|
||||||
@@ -195,11 +196,14 @@ kind: Bar
|
|||||||
input: `
|
input: `
|
||||||
a:
|
a:
|
||||||
b: c
|
b: c
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
a:
|
||||||
|
b: c
|
||||||
`,
|
`,
|
||||||
filter: fieldspec.Filter{
|
filter: fieldspec.Filter{
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
SetValue: filtersutil.SetScalar("e"),
|
||||||
},
|
},
|
||||||
error: "missing Resource metadata",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"miss-match-type": {
|
"miss-match-type": {
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
package fieldspec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return true for 'v' followed by a 1 or 2, and don't look at rest.
|
|
||||||
// I.e. 'v1', 'v1beta1', 'v2', would return true.
|
|
||||||
func looksLikeACoreApiVersion(s string) bool {
|
|
||||||
if len(s) < 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[0:1] != "v" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s[1:2] == "1" || s[1:2] == "2"
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGV parses apiVersion field into group and version.
|
|
||||||
func parseGV(apiVersion string) (group, version string) {
|
|
||||||
// parse the group and version from the apiVersion field
|
|
||||||
parts := strings.SplitN(apiVersion, "/", 2)
|
|
||||||
group = parts[0]
|
|
||||||
if len(parts) > 1 {
|
|
||||||
version = parts[1]
|
|
||||||
}
|
|
||||||
// Special case the original "apiVersion" of what
|
|
||||||
// we now call the "core" (empty) group.
|
|
||||||
if version == "" && looksLikeACoreApiVersion(group) {
|
|
||||||
version = group
|
|
||||||
group = ""
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGVK parses the metadata into a GVK
|
|
||||||
func GetGVK(meta yaml.ResourceMeta) resid.Gvk {
|
|
||||||
group, version := parseGV(meta.APIVersion)
|
|
||||||
return resid.Gvk{
|
|
||||||
Group: group,
|
|
||||||
Version: version,
|
|
||||||
Kind: meta.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
package fieldspec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseGV(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expectedGroup string
|
|
||||||
expectedVersion string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: "",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "",
|
|
||||||
},
|
|
||||||
"certSigning": {
|
|
||||||
input: "certificates.k8s.io/v1beta1",
|
|
||||||
expectedGroup: "certificates.k8s.io",
|
|
||||||
expectedVersion: "v1beta1",
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
input: "extensions/v1beta1",
|
|
||||||
expectedGroup: "extensions",
|
|
||||||
expectedVersion: "v1beta1",
|
|
||||||
},
|
|
||||||
"normal": {
|
|
||||||
input: "apps/v1",
|
|
||||||
expectedGroup: "apps",
|
|
||||||
expectedVersion: "v1",
|
|
||||||
},
|
|
||||||
"justApps": {
|
|
||||||
input: "apps",
|
|
||||||
expectedGroup: "apps",
|
|
||||||
expectedVersion: "",
|
|
||||||
},
|
|
||||||
"coreV1": {
|
|
||||||
input: "v1",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v1",
|
|
||||||
},
|
|
||||||
"coreV2": {
|
|
||||||
input: "v2",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v2",
|
|
||||||
},
|
|
||||||
"coreV2Beta1": {
|
|
||||||
input: "v2beta1",
|
|
||||||
expectedGroup: "",
|
|
||||||
expectedVersion: "v2beta1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
group, version := parseGV(tc.input)
|
|
||||||
if !assert.Equal(t, tc.expectedGroup, group) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, tc.expectedVersion, version) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetGVK(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expected resid.Gvk
|
|
||||||
parseError string
|
|
||||||
metaError string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
input: `
|
|
||||||
`,
|
|
||||||
parseError: "EOF",
|
|
||||||
},
|
|
||||||
"junk": {
|
|
||||||
input: `
|
|
||||||
congress: effective
|
|
||||||
`,
|
|
||||||
metaError: "missing Resource metadata",
|
|
||||||
},
|
|
||||||
"normal": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyWithSlash": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "v1", Kind: ""},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyNoSlash1": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "apps", Version: "", Kind: ""},
|
|
||||||
},
|
|
||||||
"apiVersionOnlyNoSlash2": {
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
`,
|
|
||||||
expected: resid.Gvk{Group: "", Version: "v1", Kind: ""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
obj, err := yaml.Parse(tc.input)
|
|
||||||
if len(tc.parseError) != 0 {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected parse error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tc.parseError) {
|
|
||||||
t.Errorf("expected parse err '%s', got '%v'", tc.parseError, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
meta, err := obj.GetMeta()
|
|
||||||
if len(tc.metaError) != 0 {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected meta error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), tc.metaError) {
|
|
||||||
t.Errorf("expected meta err '%s', got '%v'", tc.metaError, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
gvk := GetGVK(meta)
|
|
||||||
if !assert.Equal(t, tc.expected, gvk) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -257,8 +257,7 @@ func previousIdSelectedByGvk(gvk *resid.Gvk) sieveFunc {
|
|||||||
|
|
||||||
// If the we are updating a 'roleRef/name' field, the 'apiGroup' and 'kind'
|
// If the we are updating a 'roleRef/name' field, the 'apiGroup' and 'kind'
|
||||||
// fields in the same 'roleRef' map must be considered.
|
// fields in the same 'roleRef' map must be considered.
|
||||||
// If either object is cluster-scoped (!IsNamespaceableKind), there
|
// If either object is cluster-scoped, there can be a referral.
|
||||||
// can be a referral.
|
|
||||||
// E.g. a RoleBinding (which exists in a namespace) can refer
|
// E.g. a RoleBinding (which exists in a namespace) can refer
|
||||||
// to a ClusterRole (cluster-scoped) object.
|
// to a ClusterRole (cluster-scoped) object.
|
||||||
// https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole
|
// https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole
|
||||||
@@ -285,12 +284,12 @@ func prefixSuffixEquals(other resource.ResCtx) sieveFunc {
|
|||||||
|
|
||||||
func (f Filter) sameCurrentNamespaceAsReferrer() sieveFunc {
|
func (f Filter) sameCurrentNamespaceAsReferrer() sieveFunc {
|
||||||
referrerCurId := f.Referrer.CurId()
|
referrerCurId := f.Referrer.CurId()
|
||||||
if !referrerCurId.IsNamespaceableKind() {
|
if referrerCurId.IsClusterScoped() {
|
||||||
// If the referrer is cluster-scoped, let anything through.
|
// If the referrer is cluster-scoped, let anything through.
|
||||||
return acceptAll
|
return acceptAll
|
||||||
}
|
}
|
||||||
return func(r *resource.Resource) bool {
|
return func(r *resource.Resource) bool {
|
||||||
if !r.CurId().IsNamespaceableKind() {
|
if r.CurId().IsClusterScoped() {
|
||||||
// Allow cluster-scoped through.
|
// Allow cluster-scoped through.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
package namespace
|
package namespace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,16 +54,11 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
|||||||
// hacks applies the namespace transforms that are hardcoded rather
|
// hacks applies the namespace transforms that are hardcoded rather
|
||||||
// than specified through FieldSpecs.
|
// than specified through FieldSpecs.
|
||||||
func (ns Filter) hacks(obj *yaml.RNode) error {
|
func (ns Filter) hacks(obj *yaml.RNode) error {
|
||||||
meta, err := obj.GetMeta()
|
gvk := resid.GvkFromNode(obj)
|
||||||
if err != nil {
|
if err := ns.metaNamespaceHack(obj, gvk); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return ns.roleBindingHack(obj, gvk)
|
||||||
if err := ns.metaNamespaceHack(obj, meta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ns.roleBindingHack(obj, meta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// metaNamespaceHack is a hack for implementing the namespace transform
|
// metaNamespaceHack is a hack for implementing the namespace transform
|
||||||
@@ -74,9 +69,8 @@ func (ns Filter) hacks(obj *yaml.RNode) error {
|
|||||||
// This hack should be updated to allow individual resources to specify
|
// This hack should be updated to allow individual resources to specify
|
||||||
// if they are cluster scoped through either an annotation on the resources,
|
// if they are cluster scoped through either an annotation on the resources,
|
||||||
// or through inlined OpenAPI on the resource as a YAML comment.
|
// or through inlined OpenAPI on the resource as a YAML comment.
|
||||||
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, meta yaml.ResourceMeta) error {
|
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||||
gvk := fieldspec.GetGVK(meta)
|
if gvk.IsClusterScoped() {
|
||||||
if !gvk.IsNamespaceableKind() {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
f := fsslice.Filter{
|
f := fsslice.Filter{
|
||||||
@@ -104,8 +98,8 @@ func (ns Filter) metaNamespaceHack(obj *yaml.RNode, meta yaml.ResourceMeta) erro
|
|||||||
// ...
|
// ...
|
||||||
// - name: "something-else" # this will not have the namespace set
|
// - name: "something-else" # this will not have the namespace set
|
||||||
// ...
|
// ...
|
||||||
func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error {
|
func (ns Filter) roleBindingHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||||
if meta.Kind != roleBindingKind && meta.Kind != clusterRoleBindingKind {
|
if gvk.Kind != roleBindingKind && gvk.Kind != clusterRoleBindingKind {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -885,9 +885,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
clusterRoleId := resid.NewResId(
|
clusterRoleId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"), modifiedname)
|
||||||
clusterRoleBindingId := resid.NewResId(
|
clusterRoleBindingId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"), modifiedname)
|
||||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||||
|
|
||||||
@@ -1012,9 +1012,11 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
clusterRoleId := resid.NewResId(
|
clusterRoleId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"),
|
||||||
|
modifiedname)
|
||||||
clusterRoleBindingId := resid.NewResId(
|
clusterRoleBindingId := resid.NewResId(
|
||||||
resid.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
resid.NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
|
||||||
|
modifiedname)
|
||||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
|||||||
for _, v := range incoming {
|
for _, v := range incoming {
|
||||||
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
||||||
idMatcher := targetId.GvknEquals
|
idMatcher := targetId.GvknEquals
|
||||||
if targetId.Namespace != "" || !targetId.IsNamespaceableKind() {
|
if targetId.Namespace != "" || targetId.IsClusterScoped() {
|
||||||
// Preserve backward compatibility. An empty namespace means
|
// Preserve backward compatibility. An empty namespace means
|
||||||
// wildcard search on the namespace hence we still use GvknEquals
|
// wildcard search on the namespace hence we still use GvknEquals
|
||||||
idMatcher = targetId.Equals
|
idMatcher = targetId.Equals
|
||||||
|
|||||||
@@ -168,8 +168,7 @@ type ResMap interface {
|
|||||||
|
|
||||||
// GroupedByCurrentNamespace returns a map of namespace
|
// GroupedByCurrentNamespace returns a map of namespace
|
||||||
// to a slice of *Resource in that namespace.
|
// to a slice of *Resource in that namespace.
|
||||||
// Resources for whom IsNamespaceableKind is false are
|
// Cluster-scoped Resources are not included (see ClusterScoped).
|
||||||
// are not included at all (see NonNamespaceable).
|
|
||||||
// Resources with an empty namespace are placed
|
// Resources with an empty namespace are placed
|
||||||
// in the resid.DefaultNamespace entry.
|
// in the resid.DefaultNamespace entry.
|
||||||
GroupedByCurrentNamespace() map[string][]*resource.Resource
|
GroupedByCurrentNamespace() map[string][]*resource.Resource
|
||||||
@@ -179,10 +178,10 @@ type ResMap interface {
|
|||||||
// one to perform the grouping.
|
// one to perform the grouping.
|
||||||
GroupedByOriginalNamespace() map[string][]*resource.Resource
|
GroupedByOriginalNamespace() map[string][]*resource.Resource
|
||||||
|
|
||||||
// NonNamespaceable returns a slice of resources that
|
// ClusterScoped returns a slice of resources that
|
||||||
// cannot be placed in a namespace, e.g.
|
// cannot be placed in a namespace, e.g.
|
||||||
// Node, ClusterRole, Namespace itself, etc.
|
// Node, ClusterRole, Namespace itself, etc.
|
||||||
NonNamespaceable() []*resource.Resource
|
ClusterScoped() []*resource.Resource
|
||||||
|
|
||||||
// AllIds returns all CurrentIds.
|
// AllIds returns all CurrentIds.
|
||||||
AllIds() []resid.ResId
|
AllIds() []resid.ResId
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resourc
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
// NonNamespaceable implements ResMap.NonNamespaceable
|
// ClusterScoped implements ResMap.ClusterScoped
|
||||||
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
func (m *resWrangler) ClusterScoped() []*resource.Resource {
|
||||||
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
|||||||
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||||
referrer *resource.Resource) ResMap {
|
referrer *resource.Resource) ResMap {
|
||||||
referrerId := referrer.CurId()
|
referrerId := referrer.CurId()
|
||||||
if !referrerId.IsNamespaceableKind() {
|
if referrerId.IsClusterScoped() {
|
||||||
// A cluster scoped resource can refer to anything.
|
// A cluster scoped resource can refer to anything.
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -396,7 +396,7 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
|||||||
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
||||||
for _, possibleTarget := range m.rList {
|
for _, possibleTarget := range m.rList {
|
||||||
id := possibleTarget.CurId()
|
id := possibleTarget.CurId()
|
||||||
if !id.IsNamespaceableKind() {
|
if id.IsClusterScoped() {
|
||||||
// A cluster-scoped resource can be referred to by anything.
|
// A cluster-scoped resource can be referred to by anything.
|
||||||
result.append(possibleTarget)
|
result.append(possibleTarget)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ metadata:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
||||||
cmap := resid.Gvk{Version: "v1", Kind: "ConfigMap"}
|
cmap := resid.NewGvk("", "v1", "ConfigMap")
|
||||||
|
|
||||||
r1 := rf.FromMap(
|
r1 := rf.FromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
|
|||||||
@@ -87,12 +87,7 @@ func (r *Resource) GetBinaryDataMap() map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetGvk() resid.Gvk {
|
func (r *Resource) GetGvk() resid.Gvk {
|
||||||
meta, err := r.node.GetMeta()
|
return resid.GvkFromNode(r.node)
|
||||||
if err != nil {
|
|
||||||
return resid.GvkFromString("")
|
|
||||||
}
|
|
||||||
g, v := resid.ParseGroupVersion(meta.APIVersion)
|
|
||||||
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Hash(h ifc.KustHasher) (string, error) {
|
func (r *Resource) Hash(h ifc.KustHasher) (string, error) {
|
||||||
|
|||||||
@@ -87,11 +87,13 @@ func TestResourceId(t *testing.T) {
|
|||||||
{
|
{
|
||||||
in: testConfigMap,
|
in: testConfigMap,
|
||||||
id: resid.NewResIdWithNamespace(
|
id: resid.NewResIdWithNamespace(
|
||||||
resid.Gvk{Version: "v1", Kind: "ConfigMap"}, "winnie", "hundred-acre-wood"),
|
resid.NewGvk("", "v1", "ConfigMap"),
|
||||||
|
"winnie", "hundred-acre-wood"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: testDeployment,
|
in: testDeployment,
|
||||||
id: resid.NewResId(resid.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}, "pooh"),
|
id: resid.NewResId(
|
||||||
|
resid.NewGvk("apps", "v1", "Deployment"), "pooh"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (w Writer) WriteIndividualFiles(dirPath string, m resmap.ResMap) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, res := range m.NonNamespaceable() {
|
for _, res := range m.ClusterScoped() {
|
||||||
err := w.write(dirPath, fileName(res), res)
|
err := w.write(dirPath, fileName(res), res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -273,6 +273,15 @@ func IsNamespaceScoped(typeMeta yaml.TypeMeta) (bool, bool) {
|
|||||||
return isNamespaceScoped, found
|
return isNamespaceScoped, found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsCertainlyClusterScoped returns true for Node, Namespace, etc. and
|
||||||
|
// false for Pod, Deployment, etc. and kinds that aren't recognized in the
|
||||||
|
// openapi data. See:
|
||||||
|
// https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces
|
||||||
|
func IsCertainlyClusterScoped(typeMeta yaml.TypeMeta) bool {
|
||||||
|
nsScoped, found := IsNamespaceScoped(typeMeta)
|
||||||
|
return found && !nsScoped
|
||||||
|
}
|
||||||
|
|
||||||
// SuppressBuiltInSchemaUse can be called to prevent using the built-in Kubernetes
|
// SuppressBuiltInSchemaUse can be called to prevent using the built-in Kubernetes
|
||||||
// schema as part of the global schema.
|
// schema as part of the global schema.
|
||||||
// Must be called before the schema is used.
|
// Must be called before the schema is used.
|
||||||
|
|||||||
@@ -16,13 +16,26 @@ type Gvk struct {
|
|||||||
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
||||||
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
// isClusterScoped is true if the object is known, per the openapi
|
||||||
|
// data in use, to be cluster scoped, and false otherwise.
|
||||||
|
isClusterScoped bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGvk(g, v, k string) Gvk {
|
||||||
|
result := Gvk{Group: g, Version: v, Kind: k}
|
||||||
|
result.isClusterScoped =
|
||||||
|
openapi.IsCertainlyClusterScoped(result.AsTypeMeta())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func GvkFromNode(r *yaml.RNode) Gvk {
|
||||||
|
g, v := ParseGroupVersion(r.GetApiVersion())
|
||||||
|
return NewGvk(g, v, r.GetKind())
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromKind makes a Gvk with only the kind specified.
|
// FromKind makes a Gvk with only the kind specified.
|
||||||
func FromKind(k string) Gvk {
|
func FromKind(k string) Gvk {
|
||||||
return Gvk{
|
return NewGvk("", "", k)
|
||||||
Kind: k,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseGroupVersion parses a KRM metadata apiVersion field.
|
// ParseGroupVersion parses a KRM metadata apiVersion field.
|
||||||
@@ -56,11 +69,7 @@ func GvkFromString(s string) Gvk {
|
|||||||
if k == noKind {
|
if k == noKind {
|
||||||
k = ""
|
k = ""
|
||||||
}
|
}
|
||||||
return Gvk{
|
return NewGvk(g, v, k)
|
||||||
Group: g,
|
|
||||||
Version: v,
|
|
||||||
Kind: k,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values that are brief but meaningful in logs.
|
// Values that are brief but meaningful in logs.
|
||||||
@@ -90,10 +99,13 @@ func (x Gvk) String() string {
|
|||||||
|
|
||||||
// ApiVersion returns the combination of Group and Version
|
// ApiVersion returns the combination of Group and Version
|
||||||
func (x Gvk) ApiVersion() string {
|
func (x Gvk) ApiVersion() string {
|
||||||
if x.Group == "" {
|
var sb strings.Builder
|
||||||
return x.Version
|
if x.Group != "" {
|
||||||
|
sb.WriteString(x.Group)
|
||||||
|
sb.WriteString("/")
|
||||||
}
|
}
|
||||||
return x.Group + "/" + x.Version
|
sb.WriteString(x.Version)
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringWoEmptyField returns a string representation of the GVK. Non-exist
|
// StringWoEmptyField returns a string representation of the GVK. Non-exist
|
||||||
@@ -207,26 +219,16 @@ func (x Gvk) IsSelected(selector *Gvk) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// toKyamlTypeMeta returns a yaml.TypeMeta from x's information.
|
// AsTypeMeta returns a yaml.TypeMeta from x's information.
|
||||||
func (x Gvk) toKyamlTypeMeta() yaml.TypeMeta {
|
func (x Gvk) AsTypeMeta() yaml.TypeMeta {
|
||||||
var apiVersion strings.Builder
|
|
||||||
if x.Group != "" {
|
|
||||||
apiVersion.WriteString(x.Group)
|
|
||||||
apiVersion.WriteString("/")
|
|
||||||
}
|
|
||||||
apiVersion.WriteString(x.Version)
|
|
||||||
return yaml.TypeMeta{
|
return yaml.TypeMeta{
|
||||||
APIVersion: apiVersion.String(),
|
APIVersion: x.ApiVersion(),
|
||||||
Kind: x.Kind,
|
Kind: x.Kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNamespaceableKind returns true if x is a namespaceable Gvk,
|
// IsClusterScoped returns true if the Gvk is certainly cluster scoped
|
||||||
// e.g. instances of Pod and Deployment are namespaceable,
|
// with respect to the available openapi data.
|
||||||
// but instances of Node and Namespace are not namespaceable.
|
func (x Gvk) IsClusterScoped() bool {
|
||||||
// Alternative name for this method: IsNotClusterScoped.
|
return x.isClusterScoped
|
||||||
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
|
||||||
func (x Gvk) IsNamespaceableKind() bool {
|
|
||||||
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())
|
|
||||||
return !found || isNamespaceScoped
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,30 +259,45 @@ func TestSelectByGVK(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsNamespaceableKind(t *testing.T) {
|
func TestIsClusterScoped(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
gvk Gvk
|
gvk Gvk
|
||||||
expected bool
|
isClusterScoped bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"namespaceable resource",
|
"deployment is namespaceable",
|
||||||
Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
NewGvk("apps", "v1", "Deployment"),
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"clusterscoped resource",
|
|
||||||
Gvk{Group: "", Version: "v1", Kind: "Namespace"},
|
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unknown resource (should default to namespaceable)",
|
"clusterscoped resource",
|
||||||
Gvk{Group: "example1.com", Version: "v1", Kind: "Bar"},
|
NewGvk("", "v1", "Namespace"),
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unknown resource (should default to namespaceable)",
|
"unknown resource (should default to namespaceable)",
|
||||||
Gvk{Group: "apps", Version: "v1", Kind: "ClusterRoleBinding"},
|
NewGvk("example1.com", "v1", "BoatyMcBoatface"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node is cluster scoped",
|
||||||
|
NewGvk("", "v1", "Node"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Role is namespace scoped",
|
||||||
|
NewGvk("rbac.authorization.k8s.io", "v1", "Role"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ClusterRole is cluster scoped",
|
||||||
|
NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRole"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ClusterRoleBinding is cluster scoped",
|
||||||
|
NewGvk("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -290,8 +305,7 @@ func TestIsNamespaceableKind(t *testing.T) {
|
|||||||
for i := range testCases {
|
for i := range testCases {
|
||||||
test := testCases[i]
|
test := testCases[i]
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
isNamespaceable := test.gvk.IsNamespaceableKind()
|
assert.Equal(t, test.isClusterScoped, test.gvk.IsClusterScoped())
|
||||||
assert.Equal(t, test.expected, isNamespaceable)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,10 @@ type ResId struct {
|
|||||||
// Gvk of the resource.
|
// Gvk of the resource.
|
||||||
Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
|
|
||||||
// Name of the resource before transformation.
|
// Name of the resource.
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
|
||||||
// Namespace the resource belongs to.
|
// Namespace the resource belongs to, if it can belong to a namespace.
|
||||||
// An untransformed resource has no namespace.
|
|
||||||
// A fully transformed resource has the namespace
|
|
||||||
// from the top most overlay.
|
|
||||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,12 +27,12 @@ func NewResIdWithNamespace(k Gvk, n, ns string) ResId {
|
|||||||
|
|
||||||
// NewResId creates new ResId.
|
// NewResId creates new ResId.
|
||||||
func NewResId(k Gvk, n string) ResId {
|
func NewResId(k Gvk, n string) ResId {
|
||||||
return ResId{Gvk: k, Name: n}
|
return NewResIdWithNamespace(k, n, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResIdKindOnly creates a new ResId.
|
// NewResIdKindOnly creates a new ResId.
|
||||||
func NewResIdKindOnly(k string, n string) ResId {
|
func NewResIdKindOnly(k string, n string) ResId {
|
||||||
return ResId{Gvk: FromKind(k), Name: n}
|
return NewResId(FromKind(k), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -113,7 +110,7 @@ func (id ResId) IsNsEquals(o ResId) bool {
|
|||||||
// ResId and the Namespace is either not set or set
|
// ResId and the Namespace is either not set or set
|
||||||
// to DefaultNamespace.
|
// to DefaultNamespace.
|
||||||
func (id ResId) IsInDefaultNs() bool {
|
func (id ResId) IsInDefaultNs() bool {
|
||||||
return id.IsNamespaceableKind() && id.isPutativelyDefaultNs()
|
return !id.IsClusterScoped() && id.isPutativelyDefaultNs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (id ResId) isPutativelyDefaultNs() bool {
|
func (id ResId) isPutativelyDefaultNs() bool {
|
||||||
@@ -124,7 +121,7 @@ func (id ResId) isPutativelyDefaultNs() bool {
|
|||||||
// namespace for use in reporting and equality tests.
|
// namespace for use in reporting and equality tests.
|
||||||
func (id ResId) EffectiveNamespace() string {
|
func (id ResId) EffectiveNamespace() string {
|
||||||
// The order of these checks matters.
|
// The order of these checks matters.
|
||||||
if !id.IsNamespaceableKind() {
|
if id.IsClusterScoped() {
|
||||||
return TotallyNotANamespace
|
return TotallyNotANamespace
|
||||||
}
|
}
|
||||||
if id.isPutativelyDefaultNs() {
|
if id.isPutativelyDefaultNs() {
|
||||||
|
|||||||
@@ -464,42 +464,42 @@ func TestFromString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEffectiveNamespace(t *testing.T) {
|
func TestEffectiveNamespace(t *testing.T) {
|
||||||
var test = []struct {
|
var testCases = map[string]struct {
|
||||||
id ResId
|
id ResId
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
"tst1": {
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Gvk: Gvk{Group: "", Version: "v1", Kind: "Node"},
|
Gvk: NewGvk("", "v1", "Node"),
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
expected: TotallyNotANamespace,
|
expected: TotallyNotANamespace,
|
||||||
},
|
},
|
||||||
{
|
"tst2": {
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Gvk: Gvk{Group: "", Version: "v1", Kind: "Node"},
|
Gvk: NewGvk("", "v1", "Node"),
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
expected: TotallyNotANamespace,
|
expected: TotallyNotANamespace,
|
||||||
},
|
},
|
||||||
{
|
"tst3": {
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
|
Gvk: NewGvk("g", "v", "k"),
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
expected: "foo",
|
expected: "foo",
|
||||||
},
|
},
|
||||||
{
|
"tst4": {
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Namespace: "",
|
Namespace: "",
|
||||||
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
|
Gvk: NewGvk("g", "v", "k"),
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
expected: DefaultNamespace,
|
expected: DefaultNamespace,
|
||||||
},
|
},
|
||||||
{
|
"tst5": {
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
|
Gvk: Gvk{Group: "g", Version: "v", Kind: "k"},
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
@@ -508,10 +508,12 @@ func TestEffectiveNamespace(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tst := range test {
|
for n, tst := range testCases {
|
||||||
if actual := tst.id.EffectiveNamespace(); actual != tst.expected {
|
t.Run(n, func(t *testing.T) {
|
||||||
t.Fatalf("EffectiveNamespace was %s, expected %s",
|
if actual := tst.id.EffectiveNamespace(); actual != tst.expected {
|
||||||
actual, tst.expected)
|
t.Fatalf("EffectiveNamespace was %s, expected %s",
|
||||||
}
|
actual, tst.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,13 +337,34 @@ func (rn *RNode) SetYNode(node *yaml.Node) {
|
|||||||
*rn.value = *node
|
*rn.value = *node
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKind returns the kind.
|
// GetKind returns the kind, if it exists, else empty string.
|
||||||
func (rn *RNode) GetKind() string {
|
func (rn *RNode) GetKind() string {
|
||||||
node, err := rn.Pipe(FieldMatcher{Name: KindField})
|
if node := rn.getMapFieldValue(KindField); node != nil {
|
||||||
if err != nil {
|
return node.Value
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
return GetValue(node)
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApiVersion returns the apiversion, if it exists, else empty string.
|
||||||
|
func (rn *RNode) GetApiVersion() string {
|
||||||
|
if node := rn.getMapFieldValue(APIVersionField); node != nil {
|
||||||
|
return node.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMapFieldValue returns the value (*yaml.Node) of a mapping field.
|
||||||
|
// The value might be nil. Also, the function returns nil, not an error,
|
||||||
|
// if this node is not a mapping node, or if this node does not have the
|
||||||
|
// given field, so this function cannot be used to make distinctions
|
||||||
|
// between these cases.
|
||||||
|
func (rn *RNode) getMapFieldValue(field string) *yaml.Node {
|
||||||
|
for i := 0; i < len(rn.Content()); i = IncrementFieldIndex(i) {
|
||||||
|
if rn.Content()[i].Value == field {
|
||||||
|
return rn.Content()[i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name.
|
// GetName returns the name.
|
||||||
|
|||||||
Reference in New Issue
Block a user