...

Source file src/sigs.k8s.io/kustomize/api/filters/namespace/namespace.go

Documentation: sigs.k8s.io/kustomize/api/filters/namespace

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package namespace
     5  
     6  import (
     7  	"sigs.k8s.io/kustomize/api/filters/filtersutil"
     8  	"sigs.k8s.io/kustomize/api/filters/fsslice"
     9  	"sigs.k8s.io/kustomize/api/types"
    10  	"sigs.k8s.io/kustomize/kyaml/errors"
    11  	"sigs.k8s.io/kustomize/kyaml/kio"
    12  	"sigs.k8s.io/kustomize/kyaml/resid"
    13  	"sigs.k8s.io/kustomize/kyaml/yaml"
    14  )
    15  
    16  type Filter struct {
    17  	// Namespace is the namespace to apply to the inputs
    18  	Namespace string `yaml:"namespace,omitempty"`
    19  
    20  	// FsSlice contains the FieldSpecs to locate the namespace field
    21  	FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
    22  
    23  	// UnsetOnly means only blank namespace fields will be set
    24  	UnsetOnly bool `json:"unsetOnly" yaml:"unsetOnly"`
    25  
    26  	// SetRoleBindingSubjects determines which subject fields in RoleBinding and ClusterRoleBinding
    27  	// objects will have their namespace fields set. Overrides field specs provided for these types, if any.
    28  	// - defaultOnly (default): namespace will be set only on subjects named "default".
    29  	// - allServiceAccounts: namespace will be set on all subjects with "kind: ServiceAccount"
    30  	// - none: all subjects will be skipped.
    31  	SetRoleBindingSubjects RoleBindingSubjectMode `json:"setRoleBindingSubjects" yaml:"setRoleBindingSubjects"`
    32  
    33  	trackableSetter filtersutil.TrackableSetter
    34  }
    35  
    36  type RoleBindingSubjectMode string
    37  
    38  const (
    39  	DefaultSubjectsOnly       RoleBindingSubjectMode = "defaultOnly"
    40  	SubjectModeUnspecified    RoleBindingSubjectMode = ""
    41  	AllServiceAccountSubjects RoleBindingSubjectMode = "allServiceAccounts"
    42  	NoSubjects                RoleBindingSubjectMode = "none"
    43  )
    44  
    45  var _ kio.Filter = Filter{}
    46  var _ kio.TrackableFilter = &Filter{}
    47  
    48  // WithMutationTracker registers a callback which will be invoked each time a field is mutated
    49  func (ns *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) {
    50  	ns.trackableSetter.WithMutationTracker(callback)
    51  }
    52  
    53  func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
    54  	return kio.FilterAll(yaml.FilterFunc(ns.run)).Filter(nodes)
    55  }
    56  
    57  // Run runs the filter on a single node rather than a slice
    58  func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
    59  	// Special handling for metadata.namespace and metadata.name -- :(
    60  	// never let SetEntry handle metadata.namespace--it will incorrectly include cluster-scoped resources
    61  	// only update metadata.name if api version is expected one--so-as it leaves other resources of kind namespace alone
    62  	apiVersion := node.GetApiVersion()
    63  	ns.FsSlice = ns.removeUnneededMetaFieldSpecs(apiVersion, ns.FsSlice)
    64  	gvk := resid.GvkFromNode(node)
    65  	if err := ns.metaNamespaceHack(node, gvk); err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	// Special handling for (cluster) role binding subjects -- :(
    70  	if isRoleBinding(gvk.Kind) {
    71  		ns.FsSlice = ns.removeRoleBindingSubjectFieldSpecs(ns.FsSlice)
    72  		if err := ns.roleBindingHack(node); err != nil {
    73  			return nil, err
    74  		}
    75  	}
    76  
    77  	// transformations based on data -- :)
    78  	err := node.PipeE(fsslice.Filter{
    79  		FsSlice:    ns.FsSlice,
    80  		SetValue:   ns.fieldSetter(),
    81  		CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
    82  		CreateTag:  yaml.NodeTagString,
    83  	})
    84  	invalidKindErr := &yaml.InvalidNodeKindError{}
    85  	if err != nil && errors.As(err, &invalidKindErr) && invalidKindErr.ActualNodeKind() != yaml.ScalarNode {
    86  		return nil, errors.WrapPrefixf(err, "namespace field specs must target scalar nodes")
    87  	}
    88  	return node, errors.WrapPrefixf(err, "namespace transformation failed")
    89  }
    90  
    91  // metaNamespaceHack is a hack for implementing the namespace transform
    92  // for the metadata.namespace field on namespace scoped resources.
    93  func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
    94  	if gvk.IsClusterScoped() {
    95  		return nil
    96  	}
    97  	f := fsslice.Filter{
    98  		FsSlice: []types.FieldSpec{
    99  			{Path: types.MetadataNamespacePath, CreateIfNotPresent: true},
   100  		},
   101  		SetValue:   ns.fieldSetter(),
   102  		CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
   103  	}
   104  	_, err := f.Filter(obj)
   105  	return err
   106  }
   107  
   108  // roleBindingHack is a hack for implementing the transformer's SetRoleBindingSubjects option
   109  // for RoleBinding and ClusterRoleBinding resource types.
   110  //
   111  // In NoSubjects mode, it does nothing.
   112  //
   113  // In AllServiceAccountSubjects mode, it sets the namespace on subjects with "kind: ServiceAccount".
   114  //
   115  // In DefaultSubjectsOnly mode (default mode), RoleBinding and ClusterRoleBinding have namespace set on
   116  // elements of the "subjects" field if and only if the subject elements
   117  // "name" is "default".  Otherwise the namespace is not set.
   118  // Example:
   119  //
   120  // kind: RoleBinding
   121  // subjects:
   122  // - name: "default" # this will have the namespace set
   123  //   ...
   124  // - name: "something-else" # this will not have the namespace set
   125  //   ...
   126  func (ns Filter) roleBindingHack(obj *yaml.RNode) error {
   127  	var visitor filtersutil.SetFn
   128  	switch ns.SetRoleBindingSubjects {
   129  	case NoSubjects:
   130  		return nil
   131  	case DefaultSubjectsOnly, SubjectModeUnspecified:
   132  		visitor = ns.setSubjectsNamedDefault
   133  	case AllServiceAccountSubjects:
   134  		visitor = ns.setServiceAccountNamespaces
   135  	default:
   136  		return errors.Errorf("invalid value %q for setRoleBindingSubjects: "+
   137  			"must be one of %q, %q or %q", ns.SetRoleBindingSubjects,
   138  			DefaultSubjectsOnly, NoSubjects, AllServiceAccountSubjects)
   139  	}
   140  
   141  	// Lookup the subjects field on all elements.
   142  	obj, err := obj.Pipe(yaml.Lookup(subjectsField))
   143  	if err != nil || yaml.IsMissingOrNull(obj) {
   144  		return err
   145  	}
   146  	// Use the appropriate visitor to set the namespace field on the correct subset of subjects
   147  	return errors.WrapPrefixf(obj.VisitElements(visitor), "setting namespace on (cluster)role binding subjects")
   148  }
   149  
   150  func isRoleBinding(kind string) bool {
   151  	return kind == roleBindingKind || kind == clusterRoleBindingKind
   152  }
   153  
   154  func (ns Filter) setServiceAccountNamespaces(o *yaml.RNode) error {
   155  	name, err := o.Pipe(yaml.Lookup("kind"), yaml.Match("ServiceAccount"))
   156  	if err != nil || yaml.IsMissingOrNull(name) {
   157  		return errors.WrapPrefixf(err, "looking up kind on (cluster)role binding subject")
   158  	}
   159  	return setNamespaceField(o, ns.fieldSetter())
   160  }
   161  
   162  func (ns Filter) setSubjectsNamedDefault(o *yaml.RNode) error {
   163  	name, err := o.Pipe(yaml.Lookup("name"), yaml.Match("default"))
   164  	if err != nil || yaml.IsMissingOrNull(name) {
   165  		return errors.WrapPrefixf(err, "looking up name on (cluster)role binding subject")
   166  	}
   167  	return setNamespaceField(o, ns.fieldSetter())
   168  }
   169  
   170  func setNamespaceField(node *yaml.RNode, setter filtersutil.SetFn) error {
   171  	node, err := node.Pipe(yaml.LookupCreate(yaml.ScalarNode, "namespace"))
   172  	if err != nil {
   173  		return errors.WrapPrefixf(err, "setting namespace field on (cluster)role binding subject")
   174  	}
   175  	return setter(node)
   176  }
   177  
   178  // removeRoleBindingSubjectFieldSpecs removes from the list fieldspecs that
   179  // have hardcoded implementations
   180  func (ns Filter) removeRoleBindingSubjectFieldSpecs(fs types.FsSlice) types.FsSlice {
   181  	var val types.FsSlice
   182  	for i := range fs {
   183  		if isRoleBinding(fs[i].Kind) && fs[i].Path == subjectsNamespacePath {
   184  			continue
   185  		}
   186  		val = append(val, fs[i])
   187  	}
   188  	return val
   189  }
   190  
   191  func (ns Filter) removeUnneededMetaFieldSpecs(apiVersion string, fs types.FsSlice) types.FsSlice {
   192  	var val types.FsSlice
   193  	for i := range fs {
   194  		if fs[i].Path == types.MetadataNamespacePath {
   195  			continue
   196  		}
   197  		if apiVersion != types.MetadataNamespaceApiVersion && fs[i].Path == types.MetadataNamePath {
   198  			continue
   199  		}
   200  		val = append(val, fs[i])
   201  	}
   202  	return val
   203  }
   204  
   205  func (ns *Filter) fieldSetter() filtersutil.SetFn {
   206  	if ns.UnsetOnly {
   207  		return ns.trackableSetter.SetEntryIfEmpty("", ns.Namespace, yaml.NodeTagString)
   208  	}
   209  	return ns.trackableSetter.SetEntry("", ns.Namespace, yaml.NodeTagString)
   210  }
   211  
   212  const (
   213  	subjectsField          = "subjects"
   214  	subjectsNamespacePath  = "subjects/namespace"
   215  	roleBindingKind        = "RoleBinding"
   216  	clusterRoleBindingKind = "ClusterRoleBinding"
   217  )
   218  

View as plain text