Files
kustomize/kyaml/fn/framework/selector.go
Katrina Verey 5c4b5b1bf0 Improvements to kyaml fn framework
This commit creates a new version of the alpha configuration functions framework. Goals include:
- Make it easy to build multi-version APIs with the framework (not previously facilitated at all).
- Simplify the framework's APIs where redundant configuration options exist (leaving the most powerful, replacing others with helpers to maintain usability they provided).
- Make the Framework's APIs more consistent (e.g. between the various template types, usage of kio.Filter, field names)
- Decouple responsibilities (e.g. command creation, resource list processing, generation of templating functions).
- Make the framework even more powerfully pluggable (e.g. any kio.Filter can be a selector, and the selector the framework provides is itself a filter built from reusable abstractions).
- Improve documentation.
- Make container patches merge fields (notably list fields like `env`) correctly.
2021-03-03 08:27:19 -08:00

220 lines
7.4 KiB
Go

// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Selector matches resources. A resource matches if and only if ALL of the Selector fields
// match the resource. An empty Selector matches all resources.
type Selector struct {
// Names is a list of metadata.names to match. If empty match all names.
// e.g. Names: ["foo", "bar"] matches if `metadata.name` is either "foo" or "bar".
Names []string `json:"names" yaml:"names"`
// Namespaces is a list of metadata.namespaces to match. If empty match all namespaces.
// e.g. Namespaces: ["foo", "bar"] matches if `metadata.namespace` is either "foo" or "bar".
Namespaces []string `json:"namespaces" yaml:"namespaces"`
// Kinds is a list of kinds to match. If empty match all kinds.
// e.g. Kinds: ["foo", "bar"] matches if `kind` is either "foo" or "bar".
Kinds []string `json:"kinds" yaml:"kinds"`
// APIVersions is a list of apiVersions to match. If empty apply match all apiVersions.
// e.g. APIVersions: ["foo/v1", "bar/v1"] matches if `apiVersion` is either "foo/v1" or "bar/v1".
APIVersions []string `json:"apiVersions" yaml:"apiVersions"`
// Labels is a collection of labels to match. All labels must match exactly.
// e.g. Labels: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" labels match.
Labels map[string]string `json:"labels" yaml:"labels"`
// Annotations is a collection of annotations to match. All annotations must match exactly.
// e.g. Annotations: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" annotations match.
Annotations map[string]string `json:"annotations" yaml:"annotations"`
// ResourceMatcher is an arbitrary function used to match resources.
// Selector matches if the function returns true.
ResourceMatcher func(*yaml.RNode) bool
// TemplateData if present will cause the selector values to be parsed as templates
// and rendered using TemplateData before they are used.
TemplateData interface{}
// FailOnEmptyMatch makes the selector return an error when no items are selected.
FailOnEmptyMatch bool
}
// Filter implements kio.Filter, returning only those items from the list that the selector
// matches.
func (s *Selector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
andSel := AndSelector{TemplateData: s.TemplateData, FailOnEmptyMatch: s.FailOnEmptyMatch}
if s.Names != nil {
andSel.Matchers = append(andSel.Matchers, NameMatcher(s.Names...))
}
if s.Namespaces != nil {
andSel.Matchers = append(andSel.Matchers, NamespaceMatcher(s.Namespaces...))
}
if s.Kinds != nil {
andSel.Matchers = append(andSel.Matchers, KindMatcher(s.Kinds...))
}
if s.APIVersions != nil {
andSel.Matchers = append(andSel.Matchers, APIVersionMatcher(s.APIVersions...))
}
if s.Labels != nil {
andSel.Matchers = append(andSel.Matchers, LabelMatcher(s.Labels))
}
if s.Annotations != nil {
andSel.Matchers = append(andSel.Matchers, AnnotationMatcher(s.Annotations))
}
if s.ResourceMatcher != nil {
andSel.Matchers = append(andSel.Matchers, ResourceMatcherFunc(s.ResourceMatcher))
}
return andSel.Filter(items)
}
// MatchAll is a shorthand for building an AndSelector from a list of ResourceMatchers.
func MatchAll(matchers ...ResourceMatcher) *AndSelector {
return &AndSelector{Matchers: matchers}
}
// MatchAny is a shorthand for building an OrSelector from a list of ResourceMatchers.
func MatchAny(matchers ...ResourceMatcher) *OrSelector {
return &OrSelector{Matchers: matchers}
}
// OrSelector is a kio.Filter that selects resources when that match at least one of its embedded
// matchers.
type OrSelector struct {
// Matchers is the list of ResourceMatchers to try on the input resources.
Matchers []ResourceMatcher
// TemplateData, if present, is used to initialize any matchers that implement
// ResourceTemplateMatcher.
TemplateData interface{}
// FailOnEmptyMatch makes the selector return an error when no items are selected.
FailOnEmptyMatch bool
}
// Match implements ResourceMatcher so that OrSelectors can be composed
func (s *OrSelector) Match(item *yaml.RNode) bool {
for _, matcher := range s.Matchers {
if matcher.Match(item) {
return true
}
}
return false
}
// Filter implements kio.Filter, returning only those items from the list that the selector
// matches.
func (s *OrSelector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
if err := initMatcherTemplates(s.Matchers, s.TemplateData); err != nil {
return nil, err
}
var selectedItems []*yaml.RNode
for i := range items {
for _, matcher := range s.Matchers {
if matcher.Match(items[i]) {
selectedItems = append(selectedItems, items[i])
break
}
}
}
if s.FailOnEmptyMatch && len(selectedItems) == 0 {
return nil, errors.Errorf("selector did not select any items")
}
return selectedItems, nil
}
// DefaultTemplateData makes OrSelector a ResourceTemplateMatcher.
// Although it does not contain templates itself, this allows it to support ResourceTemplateMatchers
// when being used as a matcher itself.
func (s *OrSelector) DefaultTemplateData(data interface{}) {
if s.TemplateData == nil {
s.TemplateData = data
}
}
func (s *OrSelector) InitTemplates() error {
return initMatcherTemplates(s.Matchers, s.TemplateData)
}
func initMatcherTemplates(matchers []ResourceMatcher, data interface{}) error {
for _, matcher := range matchers {
if tm, ok := matcher.(ResourceTemplateMatcher); ok {
tm.DefaultTemplateData(data)
if err := tm.InitTemplates(); err != nil {
return err
}
}
}
return nil
}
var _ ResourceTemplateMatcher = &OrSelector{}
// OrSelector is a kio.Filter that selects resources when that match all of its embedded
// matchers.
type AndSelector struct {
// Matchers is the list of ResourceMatchers to try on the input resources.
Matchers []ResourceMatcher
// TemplateData, if present, is used to initialize any matchers that implement
// ResourceTemplateMatcher.
TemplateData interface{}
// FailOnEmptyMatch makes the selector return an error when no items are selected.
FailOnEmptyMatch bool
}
// Match implements ResourceMatcher so that AndSelectors can be composed
func (s *AndSelector) Match(item *yaml.RNode) bool {
for _, matcher := range s.Matchers {
if !matcher.Match(item) {
return false
}
}
return true
}
// Filter implements kio.Filter, returning only those items from the list that the selector
// matches.
func (s *AndSelector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
if err := initMatcherTemplates(s.Matchers, s.TemplateData); err != nil {
return nil, err
}
var selectedItems []*yaml.RNode
for i := range items {
isSelected := true
for _, matcher := range s.Matchers {
if !matcher.Match(items[i]) {
isSelected = false
break
}
}
if isSelected {
selectedItems = append(selectedItems, items[i])
}
}
if s.FailOnEmptyMatch && len(selectedItems) == 0 {
return nil, errors.Errorf("selector did not select any items")
}
return selectedItems, nil
}
// DefaultTemplateData makes AndSelector a ResourceTemplateMatcher.
// Although it does not contain templates itself, this allows it to support ResourceTemplateMatchers
// when being used as a matcher itself.
func (s *AndSelector) DefaultTemplateData(data interface{}) {
if s.TemplateData == nil {
s.TemplateData = data
}
}
func (s *AndSelector) InitTemplates() error {
return initMatcherTemplates(s.Matchers, s.TemplateData)
}
var _ ResourceTemplateMatcher = &AndSelector{}