working commit

This commit is contained in:
2026-03-13 19:02:42 +02:00
parent bebbf79c7a
commit 5c1da77f4c
1329 changed files with 314708 additions and 39 deletions
@@ -0,0 +1,198 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package accumulator
import (
"encoding/json"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/yaml"
)
// OpenAPIDefinition describes single type.
// Normally these definitions are auto-generated using gen-openapi.
// Same as in k8s.io / kube-openapi / pkg / common.
type OpenAPIDefinition struct {
Schema spec.Schema
Dependencies []string
}
type myProperties = map[string]spec.Schema
type nameToApiMap map[string]OpenAPIDefinition
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
func LoadConfigFromCRDs(
ldr ifc.Loader, paths []string) (*builtinconfig.TransformerConfig, error) {
tc := builtinconfig.MakeEmptyConfig()
for _, path := range paths {
content, err := ldr.Load(path)
if err != nil {
return nil, err
}
m, err := makeNameToApiMap(content)
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to parse open API definition from '%s'", path)
}
otherTc, err := makeConfigFromApiMap(m)
if err != nil {
return nil, err
}
tc, err = tc.Merge(otherTc)
if err != nil {
return nil, err
}
}
return tc, nil
}
func makeNameToApiMap(content []byte) (result nameToApiMap, err error) {
if content[0] == '{' {
err = json.Unmarshal(content, &result)
} else {
err = yaml.Unmarshal(content, &result)
}
return
}
func makeConfigFromApiMap(m nameToApiMap) (*builtinconfig.TransformerConfig, error) {
result := builtinconfig.MakeEmptyConfig()
for name, api := range m {
if !looksLikeAk8sType(api.Schema.SchemaProps.Properties) {
continue
}
tc := builtinconfig.MakeEmptyConfig()
err := loadCrdIntoConfig(
tc, makeGvkFromTypeName(name), m, name, []string{})
if err != nil {
return result, err
}
result, err = result.Merge(tc)
if err != nil {
return result, err
}
}
return result, nil
}
// TODO: Get Group and Version for CRD from the
// openAPI definition once
// "x-kubernetes-group-version-kind" is available in CRD
func makeGvkFromTypeName(n string) resid.Gvk {
names := strings.Split(n, filesys.SelfDir)
kind := names[len(names)-1]
return resid.Gvk{Kind: kind}
}
func looksLikeAk8sType(properties myProperties) bool {
_, ok := properties["kind"]
if !ok {
return false
}
_, ok = properties["apiVersion"]
if !ok {
return false
}
_, ok = properties["metadata"]
return ok
}
const (
// "x-kubernetes-annotation": ""
xAnnotation = "x-kubernetes-annotation"
// "x-kubernetes-label-selector": ""
xLabelSelector = "x-kubernetes-label-selector"
// "x-kubernetes-identity": ""
xIdentity = "x-kubernetes-identity"
// "x-kubernetes-object-ref-api-version": <apiVersion name>
xVersion = "x-kubernetes-object-ref-api-version"
// "x-kubernetes-object-ref-kind": <kind name>
xKind = "x-kubernetes-object-ref-kind"
// "x-kubernetes-object-ref-name-key": "name"
// default is "name"
xNameKey = "x-kubernetes-object-ref-name-key"
)
// loadCrdIntoConfig loads a CRD spec into a TransformerConfig
func loadCrdIntoConfig(
theConfig *builtinconfig.TransformerConfig, theGvk resid.Gvk, theMap nameToApiMap,
typeName string, path []string) (err error) {
api, ok := theMap[typeName]
if !ok {
return nil
}
for propName, property := range api.Schema.SchemaProps.Properties {
_, annotate := property.Extensions.GetString(xAnnotation)
if annotate {
err = theConfig.AddAnnotationFieldSpec(
makeFs(theGvk, append(path, propName)))
if err != nil {
return
}
}
_, label := property.Extensions.GetString(xLabelSelector)
if label {
err = theConfig.AddCommonLabelsFieldSpec(
makeFs(theGvk, append(path, propName)))
if err != nil {
return
}
}
_, identity := property.Extensions.GetString(xIdentity)
if identity {
err = theConfig.AddPrefixFieldSpec(
makeFs(theGvk, append(path, propName)))
if err != nil {
return
}
}
version, ok := property.Extensions.GetString(xVersion)
if ok {
kind, ok := property.Extensions.GetString(xKind)
if ok {
nameKey, ok := property.Extensions.GetString(xNameKey)
if !ok {
nameKey = "name"
}
err = theConfig.AddNamereferenceFieldSpec(
builtinconfig.NameBackReferences{
Gvk: resid.Gvk{Kind: kind, Version: version},
Referrers: []types.FieldSpec{
makeFs(theGvk, append(path, propName, nameKey))},
})
if err != nil {
return
}
}
}
if property.Ref.GetURL() != nil {
err = loadCrdIntoConfig(
theConfig, theGvk, theMap,
property.Ref.String(), append(path, propName))
if err != nil {
return
}
}
}
return nil
}
func makeFs(in resid.Gvk, path []string) types.FieldSpec {
return types.FieldSpec{
CreateIfNotPresent: false,
Gvk: in,
Path: strings.Join(path, "/"),
}
}
@@ -0,0 +1,164 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package accumulator
import (
"fmt"
"log"
"sigs.k8s.io/kustomize/api/filters/nameref"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/kyaml/resid"
)
type nameReferenceTransformer struct {
backRefs []builtinconfig.NameBackReferences
}
const doDebug = false
var _ resmap.Transformer = &nameReferenceTransformer{}
type filterMap map[*resource.Resource][]nameref.Filter
// newNameReferenceTransformer constructs a nameReferenceTransformer
// with a given slice of NameBackReferences.
func newNameReferenceTransformer(
br []builtinconfig.NameBackReferences) resmap.Transformer {
if br == nil {
log.Fatal("backrefs not expected to be nil")
}
return &nameReferenceTransformer{backRefs: br}
}
// Transform updates name references in resource A that
// refer to resource B, given that B's name may have
// changed.
//
// For example, a HorizontalPodAutoscaler (HPA)
// necessarily refers to a Deployment, the thing that
// an HPA scales. In this case:
//
// - the HPA instance is the Referrer,
// - the Deployment instance is the ReferralTarget.
//
// If the Deployment's name changes, e.g. a prefix is added,
// then the HPA's reference to the Deployment must be fixed.
//
func (t *nameReferenceTransformer) Transform(m resmap.ResMap) error {
fMap := t.determineFilters(m.Resources())
debug(fMap)
for r, fList := range fMap {
c, err := m.SubsetThatCouldBeReferencedByResource(r)
if err != nil {
return err
}
for _, f := range fList {
f.Referrer = r
f.ReferralCandidates = c
if err := f.Referrer.ApplyFilter(f); err != nil {
return err
}
}
}
return nil
}
func debug(fMap filterMap) {
if !doDebug {
return
}
fmt.Printf("filterMap has %d entries:\n", len(fMap))
rCount := 0
for r, fList := range fMap {
yml, _ := r.AsYAML()
rCount++
fmt.Printf(`
---- %3d. possible referrer -------------
%s
---------`, rCount, string(yml),
)
for i, f := range fList {
fmt.Printf(`
%3d/%3d update: %s
from: %s
`, rCount, i+1, f.NameFieldToUpdate.Path, f.ReferralTarget,
)
}
}
}
// Produce a map from referrer resources that might need to be fixed
// to filters that might fix them. The keys to this map are potential
// referrers, so won't include resources like ConfigMap or Secret.
//
// In the inner loop over the resources below, say we
// encounter an HPA instance. Then, in scanning the set
// of all known backrefs, we encounter an entry like
//
// - kind: Deployment
// fieldSpecs:
// - kind: HorizontalPodAutoscaler
// path: spec/scaleTargetRef/name
//
// This entry says that an HPA, via its
// 'spec/scaleTargetRef/name' field, may refer to a
// Deployment.
//
// This means that a filter will need to hunt for the right Deployment,
// obtain it's new name, and write that name into the HPA's
// 'spec/scaleTargetRef/name' field. Return a filter that can do that.
func (t *nameReferenceTransformer) determineFilters(
resources []*resource.Resource) (fMap filterMap) {
// We cache the resource OrgId values because they don't change and otherwise are very visible in a memory pprof
resourceOrgIds := make([]resid.ResId, len(resources))
for i, resource := range resources {
resourceOrgIds[i] = resource.OrgId()
}
fMap = make(filterMap)
for _, backReference := range t.backRefs {
for _, referrerSpec := range backReference.Referrers {
for i, res := range resources {
if resourceOrgIds[i].IsSelected(&referrerSpec.Gvk) {
// If this is true, the res might be a referrer, and if
// so, the name reference it holds might need an update.
if resHasField(res, referrerSpec.Path) {
// Optimization - the referrer has the field
// that might need updating.
fMap[res] = append(fMap[res], nameref.Filter{
// Name field to write in the Referrer.
// If the path specified here isn't found in
// the Referrer, nothing happens (no error,
// no field creation).
NameFieldToUpdate: referrerSpec,
// Specification of object class to read from.
// Always read from metadata/name field.
ReferralTarget: backReference.Gvk,
})
}
}
}
}
}
return fMap
}
// TODO: check res for field existence here to avoid extra work.
// res.GetFieldValue, which uses yaml.Lookup under the hood, doesn't know
// how to parse fieldspec-style paths that make no distinction
// between maps and sequences. This means it cannot lookup commonly
// used "indeterminate" paths like
// spec/containers/env/valueFrom/configMapKeyRef/name
// ('containers' is a list, not a map).
// However, the fieldspec filter does know how to handle this;
// extract that code and call it here?
func resHasField(res *resource.Resource, path string) bool {
return true
// fld := strings.Join(utils.PathSplitter(path), ".")
// _, e := res.GetFieldValue(fld)
// return e == nil
}
@@ -0,0 +1,57 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package accumulator
import (
"sigs.k8s.io/kustomize/api/filters/refvar"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
)
type refVarTransformer struct {
varMap map[string]interface{}
replacementCounts map[string]int
fieldSpecs []types.FieldSpec
}
// newRefVarTransformer returns a new refVarTransformer
// that replaces $(VAR) style variables with values.
// The fieldSpecs are the places to look for occurrences of $(VAR).
func newRefVarTransformer(
varMap map[string]interface{}, fs []types.FieldSpec) *refVarTransformer {
return &refVarTransformer{
varMap: varMap,
fieldSpecs: fs,
}
}
// UnusedVars returns slice of Var names that were unused
// after a Transform run.
func (rv *refVarTransformer) UnusedVars() []string {
var unused []string
for k := range rv.varMap {
if _, ok := rv.replacementCounts[k]; !ok {
unused = append(unused, k)
}
}
return unused
}
// Transform replaces $(VAR) style variables with values.
func (rv *refVarTransformer) Transform(m resmap.ResMap) error {
rv.replacementCounts = make(map[string]int)
mf := refvar.MakePrimitiveReplacer(rv.replacementCounts, rv.varMap)
for _, res := range m.Resources() {
for _, fieldSpec := range rv.fieldSpecs {
err := res.ApplyFilter(refvar.Filter{
MappingFunc: mf,
FieldSpec: fieldSpec,
})
if err != nil {
return err
}
}
}
return nil
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package accumulator
import (
"fmt"
"log"
"strings"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
)
// ResAccumulator accumulates resources and the rules
// used to customize those resources. It's a ResMap
// plus stuff needed to modify the ResMap.
type ResAccumulator struct {
resMap resmap.ResMap
tConfig *builtinconfig.TransformerConfig
varSet types.VarSet
}
func MakeEmptyAccumulator() *ResAccumulator {
ra := &ResAccumulator{}
ra.resMap = resmap.New()
ra.tConfig = &builtinconfig.TransformerConfig{}
ra.varSet = types.NewVarSet()
return ra
}
// ResMap returns a copy of the internal resMap.
func (ra *ResAccumulator) ResMap() resmap.ResMap {
return ra.resMap.ShallowCopy()
}
// Vars returns a copy of underlying vars.
func (ra *ResAccumulator) Vars() []types.Var {
return ra.varSet.AsSlice()
}
func (ra *ResAccumulator) AppendAll(resources resmap.ResMap) error {
return ra.resMap.AppendAll(resources)
}
func (ra *ResAccumulator) AbsorbAll(resources resmap.ResMap) error {
return ra.resMap.AbsorbAll(resources)
}
func (ra *ResAccumulator) MergeConfig(
tConfig *builtinconfig.TransformerConfig) (err error) {
ra.tConfig, err = ra.tConfig.Merge(tConfig)
return err
}
func (ra *ResAccumulator) GetTransformerConfig() *builtinconfig.TransformerConfig {
return ra.tConfig
}
// MergeVars accumulates vars into ResAccumulator.
// A Var is a tuple of name, object reference and field reference.
// This func takes a list of vars from the current kustomization file and
// annotates the accumulated resources with the names of the vars that match
// those resources. E.g. if there's a var named "sam" that wants to get
// its data from a ConfigMap named "james", and the resource list contains a
// ConfigMap named "james", then that ConfigMap will be annotated with the
// var name "sam". Later this annotation is used to find the data for "sam"
// by digging into a particular fieldpath of "james".
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
for _, v := range incoming {
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
idMatcher := targetId.GvknEquals
if targetId.Namespace != "" || targetId.IsClusterScoped() {
// Preserve backward compatibility. An empty namespace means
// wildcard search on the namespace hence we still use GvknEquals
idMatcher = targetId.Equals
}
matched := ra.resMap.GetMatchingResourcesByAnyId(idMatcher)
if len(matched) > 1 {
return fmt.Errorf(
"found %d resId matches for var %s "+
"(unable to disambiguate)",
len(matched), v)
}
if len(matched) == 1 {
matched[0].AppendRefVarName(v)
}
}
return ra.varSet.MergeSlice(incoming)
}
func (ra *ResAccumulator) MergeAccumulator(other *ResAccumulator) (err error) {
err = ra.AppendAll(other.resMap)
if err != nil {
return err
}
err = ra.MergeConfig(other.tConfig)
if err != nil {
return err
}
return ra.varSet.MergeSet(other.varSet)
}
func (ra *ResAccumulator) findVarValueFromResources(v types.Var) (interface{}, error) {
for _, res := range ra.resMap.Resources() {
for _, varName := range res.GetRefVarNames() {
if varName == v.Name {
s, err := res.GetFieldValue(v.FieldRef.FieldPath)
if err != nil {
return "", fmt.Errorf(
"field specified in var '%v' "+
"not found in corresponding resource", v)
}
return s, nil
}
}
}
return "", fmt.Errorf(
"var '%v' cannot be mapped to a field "+
"in the set of known resources", v)
}
// makeVarReplacementMap returns a map of Var names to
// their final values. The values are strings intended
// for substitution wherever the $(var.Name) occurs.
func (ra *ResAccumulator) makeVarReplacementMap() (map[string]interface{}, error) {
result := map[string]interface{}{}
for _, v := range ra.Vars() {
s, err := ra.findVarValueFromResources(v)
if err != nil {
return nil, err
}
result[v.Name] = s
}
return result, nil
}
func (ra *ResAccumulator) Transform(t resmap.Transformer) error {
return t.Transform(ra.resMap)
}
func (ra *ResAccumulator) ResolveVars() error {
replacementMap, err := ra.makeVarReplacementMap()
if err != nil {
return err
}
if len(replacementMap) == 0 {
return nil
}
t := newRefVarTransformer(
replacementMap, ra.tConfig.VarReference)
err = ra.Transform(t)
if len(t.UnusedVars()) > 0 {
log.Printf(
"well-defined vars that were never replaced: %s\n",
strings.Join(t.UnusedVars(), ","))
}
return err
}
func (ra *ResAccumulator) FixBackReferences() (err error) {
if ra.tConfig.NameReference == nil {
return nil
}
return ra.Transform(
newNameReferenceTransformer(ra.tConfig.NameReference))
}
// Intersection drops the resources which "other" does not have.
func (ra *ResAccumulator) Intersection(other resmap.ResMap) error {
otherIds := other.AllIds() //nolint:revive
for _, curId := range ra.resMap.AllIds() {
toDelete := true
for _, otherId := range otherIds {
if otherId == curId {
toDelete = false
break
}
}
if toDelete {
err := ra.resMap.Remove(curId)
if err != nil {
return err
}
}
}
return nil
}
@@ -0,0 +1,36 @@
// Code generated by pluginator on AnnotationsTransformer; DO NOT EDIT.
package builtins
import (
"sigs.k8s.io/kustomize/api/filters/annotations"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
// Add the given annotations to the given field specifications.
type AnnotationsTransformerPlugin struct {
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
func (p *AnnotationsTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Annotations = nil
p.FieldSpecs = nil
return yaml.Unmarshal(c, p)
}
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
if len(p.Annotations) == 0 {
return nil
}
return m.ApplyFilter(annotations.Filter{
Annotations: p.Annotations,
FsSlice: p.FieldSpecs,
})
}
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
return &AnnotationsTransformerPlugin{}
}
@@ -0,0 +1,37 @@
// Code generated by pluginator on ConfigMapGenerator; DO NOT EDIT.
package builtins
import (
"sigs.k8s.io/kustomize/api/kv"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
type ConfigMapGeneratorPlugin struct {
h *resmap.PluginHelpers
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
types.ConfigMapArgs
}
func (p *ConfigMapGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
p.ConfigMapArgs = types.ConfigMapArgs{}
err = yaml.Unmarshal(config, p)
if p.ConfigMapArgs.Name == "" {
p.ConfigMapArgs.Name = p.Name
}
if p.ConfigMapArgs.Namespace == "" {
p.ConfigMapArgs.Namespace = p.Namespace
}
p.h = h
return
}
func (p *ConfigMapGeneratorPlugin) Generate() (resmap.ResMap, error) {
return p.h.ResmapFactory().FromConfigMapArgs(
kv.NewLoader(p.h.Loader(), p.h.Validator()), p.ConfigMapArgs)
}
func NewConfigMapGeneratorPlugin() resmap.GeneratorPlugin {
return &ConfigMapGeneratorPlugin{}
}
+38
View File
@@ -0,0 +1,38 @@
// Code generated by pluginator on HashTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/resmap"
)
type HashTransformerPlugin struct {
hasher ifc.KustHasher
}
func (p *HashTransformerPlugin) Config(
h *resmap.PluginHelpers, _ []byte) (err error) {
p.hasher = h.ResmapFactory().RF().Hasher()
return nil
}
// Transform appends hash to generated resources.
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
for _, res := range m.Resources() {
if res.NeedHashSuffix() {
h, err := res.Hash(p.hasher)
if err != nil {
return err
}
res.StorePreviousId()
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
}
}
return nil
}
func NewHashTransformerPlugin() resmap.TransformerPlugin {
return &HashTransformerPlugin{}
}
@@ -0,0 +1,396 @@
// Code generated by pluginator on HelmChartInflationGenerator; DO NOT EDIT.
package builtins
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
"sigs.k8s.io/yaml"
)
// Generate resources from a remote or local helm chart.
type HelmChartInflationGeneratorPlugin struct {
h *resmap.PluginHelpers
types.HelmGlobals
types.HelmChart
tmpDir string
}
const (
valuesMergeOptionMerge = "merge"
valuesMergeOptionOverride = "override"
valuesMergeOptionReplace = "replace"
)
var legalMergeOptions = []string{
valuesMergeOptionMerge,
valuesMergeOptionOverride,
valuesMergeOptionReplace,
}
// Config uses the input plugin configurations `config` to setup the generator
// options
func (p *HelmChartInflationGeneratorPlugin) Config(
h *resmap.PluginHelpers, config []byte) (err error) {
if h.GeneralConfig() == nil {
return fmt.Errorf("unable to access general config")
}
if !h.GeneralConfig().HelmConfig.Enabled {
return fmt.Errorf("must specify --enable-helm")
}
if h.GeneralConfig().HelmConfig.Command == "" {
return fmt.Errorf("must specify --helm-command")
}
// CLI args takes precedence
if h.GeneralConfig().HelmConfig.KubeVersion != "" {
p.HelmChart.KubeVersion = h.GeneralConfig().HelmConfig.KubeVersion
}
if len(h.GeneralConfig().HelmConfig.ApiVersions) != 0 {
p.HelmChart.ApiVersions = h.GeneralConfig().HelmConfig.ApiVersions
}
if h.GeneralConfig().HelmConfig.Debug {
p.HelmChart.Debug = h.GeneralConfig().HelmConfig.Debug
}
p.h = h
if err = yaml.Unmarshal(config, p); err != nil {
return
}
return p.validateArgs()
}
// This uses the real file system since tmpDir may be used
// by the helm subprocess. Cannot use a chroot jail or fake
// filesystem since we allow the user to use previously
// downloaded charts. This is safe since this plugin is
// owned by kustomize.
func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
if p.tmpDir != "" {
// already done.
return nil
}
p.tmpDir, err = os.MkdirTemp("", "kustomize-helm-")
return err
}
func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) {
if p.Name == "" {
return fmt.Errorf("chart name cannot be empty")
}
// ChartHome might be consulted by the plugin (to read
// values files below it), so it must be located under
// the loader root (unless root restrictions are
// disabled, in which case this can be an absolute path).
if p.ChartHome == "" {
p.ChartHome = types.HelmDefaultHome
}
// The ValuesFile(s) may be consulted by the plugin, so it must
// be under the loader root (unless root restrictions are
// disabled).
if p.ValuesFile == "" {
p.ValuesFile = filepath.Join(p.absChartHome(), p.Name, "values.yaml")
}
for i, file := range p.AdditionalValuesFiles {
// use Load() to enforce root restrictions
if _, err := p.h.Loader().Load(file); err != nil {
return errors.WrapPrefixf(err, "could not load additionalValuesFile")
}
// the additional values filepaths must be relative to the kust root
p.AdditionalValuesFiles[i] = filepath.Join(p.h.Loader().Root(), file)
}
if err = p.errIfIllegalValuesMerge(); err != nil {
return err
}
// ConfigHome is not loaded by the plugin, and can be located anywhere.
if p.ConfigHome == "" {
if err = p.establishTmpDir(); err != nil {
return errors.WrapPrefixf(
err, "unable to create tmp dir for HELM_CONFIG_HOME")
}
p.ConfigHome = filepath.Join(p.tmpDir, "helm")
}
return nil
}
func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
if p.ValuesMerge == "" {
// Use the default.
p.ValuesMerge = valuesMergeOptionOverride
return nil
}
for _, opt := range legalMergeOptions {
if p.ValuesMerge == opt {
return nil
}
}
return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions)
}
func (p *HelmChartInflationGeneratorPlugin) absChartHome() string {
var chartHome string
if filepath.IsAbs(p.ChartHome) {
chartHome = p.ChartHome
} else {
chartHome = filepath.Join(p.h.Loader().Root(), p.ChartHome)
}
if p.Version != "" && p.Repo != "" {
return filepath.Join(chartHome, fmt.Sprintf("%s-%s", p.Name, p.Version))
}
return chartHome
}
func (p *HelmChartInflationGeneratorPlugin) runHelmCommand(
args []string) ([]byte, error) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
env := []string{
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome),
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome),
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)}
cmd.Env = append(os.Environ(), env...)
err := cmd.Run()
errorOutput := stderr.String()
if slices.Contains(args, "--debug") {
errorOutput = " Helm stack trace:\n" + errorOutput + "\nHelm template:\n" + stdout.String() + "\n"
}
if err != nil {
helm := p.h.GeneralConfig().HelmConfig.Command
err = errors.WrapPrefixf(
fmt.Errorf(
"unable to run: '%s %s' with env=%s (is '%s' installed?): %w",
helm, strings.Join(args, " "), env, helm, err),
"%s", errorOutput,
)
}
return stdout.Bytes(), err
}
// createNewMergedValuesFile replaces/merges original values file with ValuesInline.
func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() (
path string, err error) {
if p.ValuesMerge == valuesMergeOptionMerge ||
p.ValuesMerge == valuesMergeOptionOverride {
if err = p.replaceValuesInline(); err != nil {
return "", err
}
}
var b []byte
b, err = yaml.Marshal(p.ValuesInline)
if err != nil {
return "", err
}
return p.writeValuesBytes(b)
}
func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error {
pValues, err := p.h.Loader().Load(p.ValuesFile)
if err != nil {
return err
}
chValues, err := kyaml.Parse(string(pValues))
if err != nil {
return errors.WrapPrefixf(err, "could not parse values file into rnode")
}
inlineValues, err := kyaml.FromMap(p.ValuesInline)
if err != nil {
return errors.WrapPrefixf(err, "could not parse values inline into rnode")
}
var outValues *kyaml.RNode
switch p.ValuesMerge {
// Function `merge2.Merge` overrides values in dest with values from src.
// To achieve override or merge behavior, we pass parameters in different order.
// Object passed as dest will be modified, so we copy it just in case someone
// decides to use it after this is called.
case valuesMergeOptionOverride:
outValues, err = merge2.Merge(inlineValues, chValues.Copy(), kyaml.MergeOptions{})
case valuesMergeOptionMerge:
outValues, err = merge2.Merge(chValues, inlineValues.Copy(), kyaml.MergeOptions{})
}
if err != nil {
return errors.WrapPrefixf(err, "could not merge values")
}
mapValues, err := outValues.Map()
if err != nil {
return errors.WrapPrefixf(err, "could not parse merged values into map")
}
p.ValuesInline = mapValues
return err
}
// copyValuesFile to avoid branching. TODO: get rid of this.
func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) {
b, err := p.h.Loader().Load(p.ValuesFile)
if err != nil {
return "", err
}
return p.writeValuesBytes(b)
}
// Write a absolute path file in the tmp file system.
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(
b []byte) (string, error) {
if err := p.establishTmpDir(); err != nil {
return "", fmt.Errorf("cannot create tmp dir to write helm values")
}
path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml")
return path, errors.WrapPrefixf(os.WriteFile(path, b, 0644), "failed to write values file")
}
func (p *HelmChartInflationGeneratorPlugin) cleanup() {
if p.tmpDir != "" {
os.RemoveAll(p.tmpDir)
}
}
// Generate implements generator
func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
defer p.cleanup()
if err = p.checkHelmVersion(); err != nil {
return nil, err
}
if path, exists := p.chartExistsLocally(); !exists {
if p.Repo == "" {
return nil, fmt.Errorf(
"no repo specified for pull, no chart found at '%s'", path)
}
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
return nil, err
}
}
if len(p.ValuesInline) > 0 {
p.ValuesFile, err = p.createNewMergedValuesFile()
} else {
p.ValuesFile, err = p.copyValuesFile()
}
if err != nil {
return nil, err
}
var stdout []byte
stdout, err = p.runHelmCommand(p.AsHelmArgs(p.absChartHome()))
if err != nil {
return nil, err
}
rm, resMapErr := p.h.ResmapFactory().NewResMapFromBytes(stdout)
if resMapErr == nil {
if err := p.markHelmGeneratedResources(rm); err != nil {
return nil, err
}
return rm, nil
}
// try to remove the contents before first "---" because
// helm may produce messages to stdout before it
r := &kio.ByteReader{Reader: bytes.NewBuffer(stdout), OmitReaderAnnotations: true}
nodes, err := r.Read()
if err != nil {
return nil, fmt.Errorf("error reading helm output: %w", err)
}
if len(nodes) != 0 {
rm, err = p.h.ResmapFactory().NewResMapFromRNodeSlice(nodes)
if err != nil {
return nil, fmt.Errorf("could not parse rnode slice into resource map: %w", err)
}
if err := p.markHelmGeneratedResources(rm); err != nil {
return nil, err
}
return rm, nil
}
return nil, fmt.Errorf("could not parse bytes into resource map: %w", resMapErr)
}
func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
args := []string{
"pull",
"--untar",
"--untardir", p.absChartHome(),
}
switch {
case strings.HasPrefix(p.Repo, "oci://"):
args = append(args, strings.TrimSuffix(p.Repo, "/")+"/"+p.Name)
case p.Repo != "":
args = append(args, "--repo", p.Repo)
fallthrough
default:
args = append(args, p.Name)
}
if p.Version != "" {
args = append(args, "--version", p.Version)
}
if p.Devel {
args = append(args, "--devel")
}
return args
}
// chartExistsLocally will return true if the chart does exist in
// local chart home.
func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
path := filepath.Join(p.absChartHome(), p.Name)
s, err := os.Stat(path)
if err != nil {
return "", false
}
return path, s.IsDir()
}
func (p *HelmChartInflationGeneratorPlugin) markHelmGeneratedResources(rm resmap.ResMap) error {
for _, r := range rm.Resources() {
if err := r.RNode.PipeE(kyaml.SetAnnotation(konfig.HelmGeneratedAnnotation, "true")); err != nil {
return fmt.Errorf("failed to set helm annotation: %w", err)
}
}
return nil
}
// checkHelmVersion will return an error if the helm version is not V3 or V4
func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
stdout, err := p.runHelmCommand([]string{"version", "--short"})
if err != nil {
return err
}
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
if err != nil {
return err
}
v := r.FindString(string(stdout))
if v == "" {
return fmt.Errorf("cannot find version string in %s", string(stdout))
}
if v[0] == 'v' {
v = v[1:]
}
majorVersion := strings.Split(v, ".")[0]
if majorVersion != "3" && majorVersion != "4" {
return fmt.Errorf("this plugin requires helm V3 or V4 but got v%s", v)
}
return nil
}
func NewHelmChartInflationGeneratorPlugin() resmap.GeneratorPlugin {
return &HelmChartInflationGeneratorPlugin{}
}
@@ -0,0 +1,31 @@
// Code generated by pluginator on IAMPolicyGenerator; DO NOT EDIT.
package builtins
import (
"sigs.k8s.io/kustomize/api/filters/iampolicygenerator"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
type IAMPolicyGeneratorPlugin struct {
types.IAMPolicyGeneratorArgs
}
func (p *IAMPolicyGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
p.IAMPolicyGeneratorArgs = types.IAMPolicyGeneratorArgs{}
err = yaml.Unmarshal(config, p)
return
}
func (p *IAMPolicyGeneratorPlugin) Generate() (resmap.ResMap, error) {
r := resmap.New()
err := r.ApplyFilter(iampolicygenerator.Filter{
IAMPolicyGenerator: p.IAMPolicyGeneratorArgs,
})
return r, err
}
func NewIAMPolicyGeneratorPlugin() resmap.GeneratorPlugin {
return &IAMPolicyGeneratorPlugin{}
}
@@ -0,0 +1,39 @@
// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT.
package builtins
import (
"sigs.k8s.io/kustomize/api/filters/imagetag"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
// Find matching image declarations and replace
// the name, tag and/or digest.
type ImageTagTransformerPlugin struct {
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
func (p *ImageTagTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.ImageTag = types.Image{}
p.FieldSpecs = nil
return yaml.Unmarshal(c, p)
}
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
if err := m.ApplyFilter(imagetag.LegacyFilter{
ImageTag: p.ImageTag,
}); err != nil {
return err
}
return m.ApplyFilter(imagetag.Filter{
ImageTag: p.ImageTag,
FsSlice: p.FieldSpecs,
})
}
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
return &ImageTagTransformerPlugin{}
}
+36
View File
@@ -0,0 +1,36 @@
// Code generated by pluginator on LabelTransformer; DO NOT EDIT.
package builtins
import (
"sigs.k8s.io/kustomize/api/filters/labels"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
// Add the given labels to the given field specifications.
type LabelTransformerPlugin struct {
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
func (p *LabelTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Labels = nil
p.FieldSpecs = nil
return yaml.Unmarshal(c, p)
}
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
if len(p.Labels) == 0 {
return nil
}
return m.ApplyFilter(labels.Filter{
Labels: p.Labels,
FsSlice: p.FieldSpecs,
})
}
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
return &LabelTransformerPlugin{}
}
@@ -0,0 +1,79 @@
// Code generated by pluginator on NamespaceTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"sigs.k8s.io/kustomize/api/filters/namespace"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/yaml"
)
// Change or set the namespace of non-cluster level resources.
//
//nolint:tagalign
type NamespaceTransformerPlugin struct {
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
UnsetOnly bool `json:"unsetOnly" yaml:"unsetOnly"`
SetRoleBindingSubjects namespace.RoleBindingSubjectMode `json:"setRoleBindingSubjects" yaml:"setRoleBindingSubjects"`
}
func (p *NamespaceTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Namespace = ""
p.FieldSpecs = nil
if err := yaml.Unmarshal(c, p); err != nil {
return errors.WrapPrefixf(err, "unmarshalling NamespaceTransformer config")
}
switch p.SetRoleBindingSubjects {
case namespace.AllServiceAccountSubjects, namespace.DefaultSubjectsOnly, namespace.NoSubjects:
// valid
case namespace.SubjectModeUnspecified:
p.SetRoleBindingSubjects = namespace.DefaultSubjectsOnly
default:
return errors.Errorf("invalid value %q for setRoleBindingSubjects: "+
"must be one of %q, %q or %q", p.SetRoleBindingSubjects,
namespace.DefaultSubjectsOnly, namespace.NoSubjects, namespace.AllServiceAccountSubjects)
}
return nil
}
func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
if len(p.Namespace) == 0 {
return nil
}
for _, r := range m.Resources() {
if r.IsNilOrEmpty() {
// Don't mutate empty objects?
continue
}
if annotations := r.GetAnnotations(konfig.HelmGeneratedAnnotation); annotations[konfig.HelmGeneratedAnnotation] == "true" {
// Don't apply namespace on Helm generated manifest. Helm should take care of it.
continue
}
r.StorePreviousId()
if err := r.ApplyFilter(namespace.Filter{
Namespace: p.Namespace,
FsSlice: p.FieldSpecs,
SetRoleBindingSubjects: p.SetRoleBindingSubjects,
UnsetOnly: p.UnsetOnly,
}); err != nil {
return err
}
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
if len(matches) != 1 {
return fmt.Errorf(
"namespace transformation produces ID conflict: %+v", matches)
}
}
return nil
}
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
return &NamespaceTransformerPlugin{}
}
@@ -0,0 +1,103 @@
// Code generated by pluginator on PatchJson6902Transformer; DO NOT EDIT.
package builtins
import (
"fmt"
jsonpatch "gopkg.in/evanphx/json-patch.v4"
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/yaml"
)
type PatchJson6902TransformerPlugin struct {
ldr ifc.Loader
decodedPatch jsonpatch.Patch
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
}
func (p *PatchJson6902TransformerPlugin) Config(
h *resmap.PluginHelpers, c []byte) (err error) {
p.ldr = h.Loader()
err = yaml.Unmarshal(c, p)
if err != nil {
return err
}
if p.Target.Name == "" {
return fmt.Errorf("must specify the target name")
}
if p.Path == "" && p.JsonOp == "" {
return fmt.Errorf("empty file path and empty jsonOp")
}
if p.Path != "" {
if p.JsonOp != "" {
return fmt.Errorf("must specify a file path or jsonOp, not both")
}
rawOp, err := p.ldr.Load(p.Path)
if err != nil {
return err
}
p.JsonOp = string(rawOp)
if p.JsonOp == "" {
return fmt.Errorf("patch file '%s' empty seems to be empty", p.Path)
}
}
if p.JsonOp[0] != '[' {
// if it doesn't seem to be JSON, imagine
// it is YAML, and convert to JSON.
op, err := yaml.YAMLToJSON([]byte(p.JsonOp))
if err != nil {
return err
}
p.JsonOp = string(op)
}
p.decodedPatch, err = jsonpatch.DecodePatch([]byte(p.JsonOp))
if err != nil {
return errors.WrapPrefixf(err, "decoding %s", p.JsonOp)
}
if len(p.decodedPatch) == 0 {
return fmt.Errorf(
"patch appears to be empty; file=%s, JsonOp=%s", p.Path, p.JsonOp)
}
return err
}
func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error {
if p.Target == nil {
return fmt.Errorf("must specify a target for patch %s", p.JsonOp)
}
resources, err := m.Select(*p.Target)
if err != nil {
return err
}
for _, res := range resources {
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
err = res.ApplyFilter(patchjson6902.Filter{
Patch: p.JsonOp,
})
if err != nil {
return err
}
annotations := res.GetAnnotations()
for key, value := range internalAnnotations {
annotations[key] = value
}
err = res.SetAnnotations(annotations)
if err != nil {
return err
}
}
return nil
}
func NewPatchJson6902TransformerPlugin() resmap.TransformerPlugin {
return &PatchJson6902TransformerPlugin{}
}
@@ -0,0 +1,87 @@
// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
type PatchStrategicMergeTransformerPlugin struct {
loadedPatches []*resource.Resource
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
}
func (p *PatchStrategicMergeTransformerPlugin) Config(
h *resmap.PluginHelpers, c []byte) (err error) {
err = yaml.Unmarshal(c, p)
if err != nil {
return err
}
if len(p.Paths) == 0 && p.Patches == "" {
return fmt.Errorf("empty file path and empty patch content")
}
if len(p.Paths) != 0 {
patches, err := loadFromPaths(h, p.Paths)
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, patches...)
}
if p.Patches != "" {
patches, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, patches...)
}
if len(p.loadedPatches) == 0 {
return fmt.Errorf(
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
}
return nil
}
func loadFromPaths(
h *resmap.PluginHelpers,
paths []types.PatchStrategicMerge) (
result []*resource.Resource, err error) {
var patches []*resource.Resource
for _, path := range paths {
// For legacy reasons, attempt to treat the path string as
// actual patch content.
patches, err = h.ResmapFactory().RF().SliceFromBytes([]byte(path))
if err != nil {
// Failing that, treat it as a file path.
patches, err = h.ResmapFactory().RF().SliceFromPatches(
h.Loader(), []types.PatchStrategicMerge{path})
if err != nil {
return
}
}
result = append(result, patches...)
}
return
}
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
for _, patch := range p.loadedPatches {
target, err := m.GetById(patch.OrgId())
if err != nil {
return err
}
if err = m.ApplySmPatch(
resource.MakeIdSet([]*resource.Resource{target}), patch); err != nil {
return err
}
}
return nil
}
func NewPatchStrategicMergeTransformerPlugin() resmap.TransformerPlugin {
return &PatchStrategicMergeTransformerPlugin{}
}
+179
View File
@@ -0,0 +1,179 @@
// Code generated by pluginator on PatchTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"strings"
jsonpatch "gopkg.in/evanphx/json-patch.v4"
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/yaml"
)
type PatchTransformerPlugin struct {
smPatches []*resource.Resource // strategic-merge patches
jsonPatches jsonpatch.Patch // json6902 patch
// patchText is pure patch text created by Path or Patch
patchText string
// patchSource is patch source message
patchSource string
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
Options *types.PatchArgs `json:"options,omitempty" yaml:"options,omitempty"`
}
func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error {
if err := yaml.Unmarshal(c, p); err != nil {
return err
}
p.Patch = strings.TrimSpace(p.Patch)
switch {
case p.Patch == "" && p.Path == "":
return fmt.Errorf("must specify one of patch and path in\n%s", string(c))
case p.Patch != "" && p.Path != "":
return fmt.Errorf("patch and path can't be set at the same time\n%s", string(c))
case p.Patch != "":
p.patchText = p.Patch
p.patchSource = fmt.Sprintf("[patch: %q]", p.patchText)
case p.Path != "":
loaded, err := h.Loader().Load(p.Path)
if err != nil {
return fmt.Errorf("failed to get the patch file from path(%s): %w", p.Path, err)
}
p.patchText = string(loaded)
p.patchSource = fmt.Sprintf("[path: %q]", p.Path)
}
patchesSM, errSM := h.ResmapFactory().RF().SliceFromBytes([]byte(p.patchText))
patchesJson, errJson := jsonPatchFromBytes([]byte(p.patchText))
if ((errSM == nil && errJson == nil) ||
(patchesSM != nil && patchesJson != nil)) &&
(len(patchesSM) > 0 && len(patchesJson) > 0) {
return fmt.Errorf(
"illegally qualifies as both an SM and JSON patch: %s",
p.patchSource)
}
if errSM != nil && errJson != nil {
return fmt.Errorf(
"unable to parse SM or JSON patch from %s", p.patchSource)
}
if errSM == nil {
p.smPatches = patchesSM
for _, loadedPatch := range p.smPatches {
if p.Options == nil {
continue
}
if p.Options.AllowNameChange {
loadedPatch.AllowNameChange()
}
if p.Options.AllowKindChange {
loadedPatch.AllowKindChange()
}
}
} else {
p.jsonPatches = patchesJson
}
return nil
}
func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
if p.smPatches != nil {
return p.transformStrategicMerge(m)
}
if p.jsonPatches != nil {
return p.transformJson6902(m)
}
return nil
}
// transformStrategicMerge applies each loaded strategic merge patch
// to the resource in the ResMap that matches the identifier of the patch.
// If only one patch is specified, the Target can be used instead.
func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap) error {
if p.Target != nil {
if len(p.smPatches) > 1 {
// detail: https://github.com/kubernetes-sigs/kustomize/issues/5049#issuecomment-1440604403
return fmt.Errorf("Multiple Strategic-Merge Patches in one `patches` entry is not allowed to set `patches.target` field: %s", p.patchSource)
}
// single patch
patch := p.smPatches[0]
selected, err := m.Select(*p.Target)
if err != nil {
return fmt.Errorf("unable to find patch target %q in `resources`: %w", p.Target, err)
}
return errors.Wrap(m.ApplySmPatch(resource.MakeIdSet(selected), patch))
}
for _, patch := range p.smPatches {
target, err := m.GetById(patch.OrgId())
if err != nil {
return fmt.Errorf("no resource matches strategic merge patch %q: %w", patch.OrgId(), err)
}
if err := target.ApplySmPatch(patch); err != nil {
return errors.Wrap(err)
}
}
return nil
}
// transformJson6902 applies json6902 Patch to all the resources in the ResMap that match Target.
func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap) error {
if p.Target == nil {
return fmt.Errorf("must specify a target for JSON patch %s", p.patchSource)
}
resources, err := m.Select(*p.Target)
if err != nil {
return err
}
for _, res := range resources {
res.StorePreviousId()
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
err = res.ApplyFilter(patchjson6902.Filter{
Patch: p.patchText,
})
if err != nil {
return err
}
annotations := res.GetAnnotations()
for key, value := range internalAnnotations {
annotations[key] = value
}
err = res.SetAnnotations(annotations)
}
return nil
}
// jsonPatchFromBytes loads a Json 6902 patch from a bytes input
func jsonPatchFromBytes(in []byte) (jsonpatch.Patch, error) {
ops := string(in)
if ops == "" {
return nil, fmt.Errorf("empty json patch operations")
}
if ops[0] != '[' {
// TODO(5049):
// In the case of multiple yaml documents, return error instead of ignoring all but first.
// Details: https://github.com/kubernetes-sigs/kustomize/pull/5194#discussion_r1256686728
jsonOps, err := yaml.YAMLToJSON(in)
if err != nil {
return nil, err
}
ops = string(jsonOps)
}
return jsonpatch.DecodePatch([]byte(ops))
}
func NewPatchTransformerPlugin() resmap.TransformerPlugin {
return &PatchTransformerPlugin{}
}
@@ -0,0 +1,94 @@
// Code generated by pluginator on PrefixTransformer; DO NOT EDIT.
package builtins
import (
"errors"
"sigs.k8s.io/kustomize/api/filters/prefix"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Add the given prefix to the field
type PrefixTransformerPlugin struct {
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
FieldSpecs types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
// TODO: Make this gvk skip list part of the config.
var prefixFieldSpecsToSkip = types.FsSlice{
{Gvk: resid.Gvk{Kind: "CustomResourceDefinition"}},
{Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"}},
{Gvk: resid.Gvk{Kind: "Namespace"}},
}
func (p *PrefixTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Prefix = ""
p.FieldSpecs = nil
err = yaml.Unmarshal(c, p)
if err != nil {
return
}
if p.FieldSpecs == nil {
return errors.New("fieldSpecs is not expected to be nil")
}
return
}
func (p *PrefixTransformerPlugin) Transform(m resmap.ResMap) error {
// Even if the Prefix is empty we want to proceed with the
// transformation. This allows to add contextual information
// to the resources (AddNamePrefix).
for _, r := range m.Resources() {
// TODO: move this test into the filter (i.e. make a better filter)
if p.shouldSkip(r.OrgId()) {
continue
}
id := r.OrgId()
// current default configuration contains
// only one entry: "metadata/name" with no GVK
for _, fs := range p.FieldSpecs {
// TODO: this is redundant to filter (but needed for now)
if !id.IsSelected(&fs.Gvk) {
continue
}
// TODO: move this test into the filter.
if fs.Path == "metadata/name" {
// "metadata/name" is the only field.
// this will add a prefix to the resource
// even if it is empty
r.AddNamePrefix(p.Prefix)
if p.Prefix != "" {
// TODO: There are multiple transformers that can change a resource's name, and each makes a call to
// StorePreviousID(). We should make it so that we only call StorePreviousID once per kustomization layer
// to avoid storing intermediate names between transformations, to prevent intermediate name conflicts.
r.StorePreviousId()
}
}
if err := r.ApplyFilter(prefix.Filter{
Prefix: p.Prefix,
FieldSpec: fs,
}); err != nil {
return err
}
}
}
return nil
}
func (p *PrefixTransformerPlugin) shouldSkip(id resid.ResId) bool {
for _, path := range prefixFieldSpecsToSkip {
if id.IsSelected(&path.Gvk) {
return true
}
}
return false
}
func NewPrefixTransformerPlugin() resmap.TransformerPlugin {
return &PrefixTransformerPlugin{}
}
@@ -0,0 +1,76 @@
// Code generated by pluginator on ReplacementTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"reflect"
"sigs.k8s.io/kustomize/api/filters/replacement"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
// Replace values in targets with values from a source
type ReplacementTransformerPlugin struct {
ReplacementList []types.ReplacementField `json:"replacements,omitempty" yaml:"replacements,omitempty"`
replacements []types.Replacement
}
func (p *ReplacementTransformerPlugin) Config(
h *resmap.PluginHelpers, c []byte) (err error) {
p.ReplacementList = []types.ReplacementField{}
if err := yaml.Unmarshal(c, p); err != nil {
return err
}
for _, r := range p.ReplacementList {
if r.Path != "" && (r.Source != nil || len(r.Targets) != 0) {
return fmt.Errorf("cannot specify both path and inline replacement")
}
if r.Path != "" {
// load the replacement from the path
content, err := h.Loader().Load(r.Path)
if err != nil {
return err
}
// find if the path contains a a list of replacements or a single replacement
var replacement interface{}
err = yaml.Unmarshal(content, &replacement)
if err != nil {
return err
}
items := reflect.ValueOf(replacement)
switch items.Kind() {
case reflect.Slice:
repl := []types.Replacement{}
if err := yaml.Unmarshal(content, &repl); err != nil {
return err
}
p.replacements = append(p.replacements, repl...)
case reflect.Map:
repl := types.Replacement{}
if err := yaml.Unmarshal(content, &repl); err != nil {
return err
}
p.replacements = append(p.replacements, repl)
default:
return fmt.Errorf("unsupported replacement type encountered within replacement path: %v", items.Kind())
}
} else {
// replacement information is already loaded
p.replacements = append(p.replacements, r.Replacement)
}
}
return nil
}
func (p *ReplacementTransformerPlugin) Transform(m resmap.ResMap) (err error) {
return m.ApplyFilter(replacement.Filter{
Replacements: p.replacements,
})
}
func NewReplacementTransformerPlugin() resmap.TransformerPlugin {
return &ReplacementTransformerPlugin{}
}
@@ -0,0 +1,71 @@
// Code generated by pluginator on ReplicaCountTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"sigs.k8s.io/kustomize/api/filters/replicacount"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/yaml"
)
// Find matching replicas declarations and replace the count.
// Eases the kustomization configuration of replica changes.
type ReplicaCountTransformerPlugin struct {
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
func (p *ReplicaCountTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Replica = types.Replica{}
p.FieldSpecs = nil
return yaml.Unmarshal(c, p)
}
func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
found := false
for _, fs := range p.FieldSpecs {
matcher := p.createMatcher(fs)
resList := m.GetMatchingResourcesByAnyId(matcher)
if len(resList) > 0 {
found = true
for _, r := range resList {
// There are redundant checks in the filter
// that we'll live with until resolution of
// https://github.com/kubernetes-sigs/kustomize/issues/2506
err := r.ApplyFilter(replicacount.Filter{
Replica: p.Replica,
FieldSpec: fs,
})
if err != nil {
return err
}
}
}
}
if !found {
gvks := make([]string, len(p.FieldSpecs))
for i, replicaSpec := range p.FieldSpecs {
gvks[i] = replicaSpec.Gvk.String()
}
return fmt.Errorf("resource with name %s does not match a config with the following GVK %v",
p.Replica.Name, gvks)
}
return nil
}
// Match Replica.Name and FieldSpec
func (p *ReplicaCountTransformerPlugin) createMatcher(fs types.FieldSpec) resmap.IdMatcher {
return func(r resid.ResId) bool {
return r.Name == p.Replica.Name && r.Gvk.IsSelected(&fs.Gvk)
}
}
func NewReplicaCountTransformerPlugin() resmap.TransformerPlugin {
return &ReplicaCountTransformerPlugin{}
}
+37
View File
@@ -0,0 +1,37 @@
// Code generated by pluginator on SecretGenerator; DO NOT EDIT.
package builtins
import (
"sigs.k8s.io/kustomize/api/kv"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
type SecretGeneratorPlugin struct {
h *resmap.PluginHelpers
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
types.SecretArgs
}
func (p *SecretGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
p.SecretArgs = types.SecretArgs{}
err = yaml.Unmarshal(config, p)
if p.SecretArgs.Name == "" {
p.SecretArgs.Name = p.Name
}
if p.SecretArgs.Namespace == "" {
p.SecretArgs.Namespace = p.Namespace
}
p.h = h
return
}
func (p *SecretGeneratorPlugin) Generate() (resmap.ResMap, error) {
return p.h.ResmapFactory().FromSecretArgs(
kv.NewLoader(p.h.Loader(), p.h.Validator()), p.SecretArgs)
}
func NewSecretGeneratorPlugin() resmap.GeneratorPlugin {
return &SecretGeneratorPlugin{}
}
@@ -0,0 +1,236 @@
// Code generated by pluginator on SortOrderTransformer; DO NOT EDIT.
package builtins
import (
"sort"
"strings"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/yaml"
)
// Sort the resources using a customizable ordering based of Kind.
// Defaults to the ordering of the GVK struct, which puts cluster-wide basic
// resources with no dependencies (like Namespace, StorageClass, etc.) first,
// and resources with a high number of dependencies
// (like ValidatingWebhookConfiguration) last.
type SortOrderTransformerPlugin struct {
SortOptions *types.SortOptions `json:"sortOptions,omitempty" yaml:"sortOptions,omitempty"`
}
func (p *SortOrderTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) error {
return errors.WrapPrefixf(yaml.Unmarshal(c, p), "Failed to unmarshal SortOrderTransformer config")
}
func (p *SortOrderTransformerPlugin) applyDefaults() {
// Default to FIFO sort, aka no-op.
if p.SortOptions == nil {
p.SortOptions = &types.SortOptions{
Order: types.FIFOSortOrder,
}
}
// If legacy sort is selected and no options are given, default to
// hardcoded order.
if p.SortOptions.Order == types.LegacySortOrder && p.SortOptions.LegacySortOptions == nil {
p.SortOptions.LegacySortOptions = &types.LegacySortOptions{
OrderFirst: defaultOrderFirst,
OrderLast: defaultOrderLast,
}
}
}
func (p *SortOrderTransformerPlugin) validate() error {
// Check valid values for SortOrder
if p.SortOptions.Order != types.FIFOSortOrder && p.SortOptions.Order != types.LegacySortOrder {
return errors.Errorf("the field 'sortOptions.order' must be one of [%s, %s]",
types.FIFOSortOrder, types.LegacySortOrder)
}
// Validate that the only options set are the ones corresponding to the
// selected sort order.
if p.SortOptions.Order == types.FIFOSortOrder &&
p.SortOptions.LegacySortOptions != nil {
return errors.Errorf("the field 'sortOptions.legacySortOptions' is"+
" set but the selected sort order is '%v', not 'legacy'",
p.SortOptions.Order)
}
return nil
}
func (p *SortOrderTransformerPlugin) Transform(m resmap.ResMap) (err error) {
p.applyDefaults()
err = p.validate()
if err != nil {
return err
}
// Sort
if p.SortOptions.Order == types.LegacySortOrder {
s := newLegacyIDSorter(m.Resources(), p.SortOptions.LegacySortOptions)
sort.Sort(s)
// Clear the map and re-add the resources in the sorted order.
m.Clear()
for _, r := range s.resources {
err := m.Append(r)
if err != nil {
return errors.WrapPrefixf(err, "SortOrderTransformer: Failed to append to resources")
}
}
}
return nil
}
// Code for legacy sorting.
// Legacy sorting is a "fixed" order sorting maintained for backwards
// compatibility.
// legacyIDSorter sorts resources based on two priority lists:
// - orderFirst: Resources that should be placed in the start, in the given order.
// - orderLast: Resources that should be placed in the end, in the given order.
type legacyIDSorter struct {
// resids only stores the metadata of the object. This is an optimization as
// it's expensive to compute these again and again during ordering.
resids []resid.ResId
// Initially, we sorted the metadata (ResId) of each object and then called GetByCurrentId on each to construct the final list.
// The problem is that GetByCurrentId is inefficient and does a linear scan in a list every time we do that.
// So instead, we sort resources alongside the ResIds.
resources []*resource.Resource
typeOrders map[string]int
}
func newLegacyIDSorter(
resources []*resource.Resource,
options *types.LegacySortOptions) *legacyIDSorter {
// Precalculate a resource ranking based on the priority lists.
var typeOrders = func() map[string]int {
m := map[string]int{}
for i, n := range options.OrderFirst {
m[n] = -len(options.OrderFirst) + i
}
for i, n := range options.OrderLast {
m[n] = 1 + i
}
return m
}()
ret := &legacyIDSorter{typeOrders: typeOrders}
for _, res := range resources {
ret.resids = append(ret.resids, res.CurId())
ret.resources = append(ret.resources, res)
}
return ret
}
var _ sort.Interface = legacyIDSorter{}
func (a legacyIDSorter) Len() int { return len(a.resids) }
func (a legacyIDSorter) Swap(i, j int) {
a.resids[i], a.resids[j] = a.resids[j], a.resids[i]
a.resources[i], a.resources[j] = a.resources[j], a.resources[i]
}
func (a legacyIDSorter) Less(i, j int) bool {
if !a.resids[i].Gvk.Equals(a.resids[j].Gvk) {
return gvkLessThan(a.resids[i].Gvk, a.resids[j].Gvk, a.typeOrders)
}
return legacyResIDSortString(a.resids[i]) < legacyResIDSortString(a.resids[j])
}
func gvkLessThan(gvk1, gvk2 resid.Gvk, typeOrders map[string]int) bool {
index1 := typeOrders[gvk1.Kind]
index2 := typeOrders[gvk2.Kind]
if index1 != index2 {
return index1 < index2
}
if (gvk1.Kind == types.NamespaceKind && gvk2.Kind == types.NamespaceKind) && (gvk1.Group == "" || gvk2.Group == "") {
return legacyGVKSortString(gvk1) > legacyGVKSortString(gvk2)
}
return legacyGVKSortString(gvk1) < legacyGVKSortString(gvk2)
}
// legacyGVKSortString returns a string representation of given GVK used for
// stable sorting.
func legacyGVKSortString(x resid.Gvk) string {
legacyNoGroup := "~G"
legacyNoVersion := "~V"
legacyNoKind := "~K"
legacyFieldSeparator := "_"
g := x.Group
if g == "" {
g = legacyNoGroup
}
v := x.Version
if v == "" {
v = legacyNoVersion
}
k := x.Kind
if k == "" {
k = legacyNoKind
}
return strings.Join([]string{g, v, k}, legacyFieldSeparator)
}
// legacyResIDSortString returns a string representation of given ResID used for
// stable sorting.
func legacyResIDSortString(id resid.ResId) string {
legacyNoNamespace := "~X"
legacyNoName := "~N"
legacySeparator := "|"
ns := id.Namespace
if ns == "" {
ns = legacyNoNamespace
}
nm := id.Name
if nm == "" {
nm = legacyNoName
}
return strings.Join(
[]string{id.Gvk.String(), ns, nm}, legacySeparator)
}
// DO NOT CHANGE!
// Final legacy ordering provided as a default by kustomize.
// Originally an attempt to apply resources in the correct order, an effort
// which later proved impossible as not all types are known beforehand.
// See: https://github.com/kubernetes-sigs/kustomize/issues/3913
var defaultOrderFirst = []string{ //nolint:gochecknoglobals
"Namespace",
"ResourceQuota",
"StorageClass",
"CustomResourceDefinition",
"ServiceAccount",
"PodSecurityPolicy",
"Role",
"ClusterRole",
"RoleBinding",
"ClusterRoleBinding",
"ConfigMap",
"Secret",
"Endpoints",
"Service",
"LimitRange",
"PriorityClass",
"PersistentVolume",
"PersistentVolumeClaim",
"Deployment",
"StatefulSet",
"CronJob",
"PodDisruptionBudget",
}
var defaultOrderLast = []string{ //nolint:gochecknoglobals
"MutatingWebhookConfiguration",
"ValidatingWebhookConfiguration",
}
func NewSortOrderTransformerPlugin() resmap.TransformerPlugin {
return &SortOrderTransformerPlugin{}
}
@@ -0,0 +1,94 @@
// Code generated by pluginator on SuffixTransformer; DO NOT EDIT.
package builtins
import (
"errors"
"sigs.k8s.io/kustomize/api/filters/suffix"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Add the given suffix to the field
type SuffixTransformerPlugin struct {
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
FieldSpecs types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
// TODO: Make this gvk skip list part of the config.
var suffixFieldSpecsToSkip = types.FsSlice{
{Gvk: resid.Gvk{Kind: "CustomResourceDefinition"}},
{Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"}},
{Gvk: resid.Gvk{Kind: "Namespace"}},
}
func (p *SuffixTransformerPlugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Suffix = ""
p.FieldSpecs = nil
err = yaml.Unmarshal(c, p)
if err != nil {
return
}
if p.FieldSpecs == nil {
return errors.New("fieldSpecs is not expected to be nil")
}
return
}
func (p *SuffixTransformerPlugin) Transform(m resmap.ResMap) error {
// Even if the Suffix is empty we want to proceed with the
// transformation. This allows to add contextual information
// to the resources (AddNameSuffix).
for _, r := range m.Resources() {
// TODO: move this test into the filter (i.e. make a better filter)
if p.shouldSkip(r.OrgId()) {
continue
}
id := r.OrgId()
// current default configuration contains
// only one entry: "metadata/name" with no GVK
for _, fs := range p.FieldSpecs {
// TODO: this is redundant to filter (but needed for now)
if !id.IsSelected(&fs.Gvk) {
continue
}
// TODO: move this test into the filter.
if fs.Path == "metadata/name" {
// "metadata/name" is the only field.
// this will add a suffix to the resource
// even if it is empty
r.AddNameSuffix(p.Suffix)
if p.Suffix != "" {
// TODO: There are multiple transformers that can change a resource's name, and each makes a call to
// StorePreviousID(). We should make it so that we only call StorePreviousID once per kustomization layer
// to avoid storing intermediate names between transformations, to prevent intermediate name conflicts.
r.StorePreviousId()
}
}
if err := r.ApplyFilter(suffix.Filter{
Suffix: p.Suffix,
FieldSpec: fs,
}); err != nil {
return err
}
}
}
return nil
}
func (p *SuffixTransformerPlugin) shouldSkip(id resid.ResId) bool {
for _, path := range suffixFieldSpecsToSkip {
if id.IsSelected(&path.Gvk) {
return true
}
}
return false
}
func NewSuffixTransformerPlugin() resmap.TransformerPlugin {
return &SuffixTransformerPlugin{}
}
@@ -0,0 +1,139 @@
// Code generated by pluginator on ValueAddTransformer; DO NOT EDIT.
package builtins
import (
"fmt"
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/api/filters/namespace"
"sigs.k8s.io/kustomize/api/filters/valueadd"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
// An 'Add' transformer inspired by the IETF RFC 6902 JSON spec Add operation.
type ValueAddTransformerPlugin struct {
// Value is the value to add.
// Defaults to base name of encompassing kustomization root.
Value string `json:"value,omitempty" yaml:"value,omitempty"`
// Targets is a slice of targets that should have the value added.
Targets []Target `json:"targets,omitempty" yaml:"targets,omitempty"`
// TargetFilePath is a file path. If specified, the file will be parsed into
// a slice of Target, and appended to anything that was specified in the
// Targets field. This is just a means to share common target specifications.
TargetFilePath string `json:"targetFilePath,omitempty" yaml:"targetFilePath,omitempty"`
}
// Target describes where to put the value.
type Target struct {
// Selector selects the resources to modify.
Selector *types.Selector `json:"selector,omitempty" yaml:"selector,omitempty"`
// NotSelector selects the resources to exclude
// from those included by overly broad selectors.
// TODO: implement this?
// NotSelector *types.Selector `json:"notSelector,omitempty" yaml:"notSelector,omitempty"`
// FieldPath is a JSON-style path to the field intended to hold the value.
FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
// FilePathPosition is passed to the filter directly. Look there for doc.
FilePathPosition int `json:"filePathPosition,omitempty" yaml:"filePathPosition,omitempty"`
}
func (p *ValueAddTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error {
err := yaml.Unmarshal(c, p)
if err != nil {
return err
}
p.Value = strings.TrimSpace(p.Value)
if p.Value == "" {
p.Value = filepath.Base(h.Loader().Root())
}
if p.TargetFilePath != "" {
bytes, err := h.Loader().Load(p.TargetFilePath)
if err != nil {
return err
}
var targets struct {
Targets []Target `json:"targets,omitempty" yaml:"targets,omitempty"`
}
err = yaml.Unmarshal(bytes, &targets)
if err != nil {
return err
}
p.Targets = append(p.Targets, targets.Targets...)
}
if len(p.Targets) == 0 {
return fmt.Errorf("must specify at least one target")
}
for _, target := range p.Targets {
if err = validateSelector(target.Selector); err != nil {
return err
}
// TODO: call validateSelector(target.NotSelector) if field added.
if err = validateJsonFieldPath(target.FieldPath); err != nil {
return err
}
if target.FilePathPosition < 0 {
return fmt.Errorf(
"value of FilePathPosition (%d) cannot be negative",
target.FilePathPosition)
}
}
return nil
}
// TODO: implement
func validateSelector(_ *types.Selector) error {
return nil
}
// TODO: Enforce RFC 6902?
func validateJsonFieldPath(p string) error {
if len(p) == 0 {
return fmt.Errorf("fieldPath cannot be empty")
}
return nil
}
func (p *ValueAddTransformerPlugin) Transform(m resmap.ResMap) (err error) {
for _, t := range p.Targets {
var resources []*resource.Resource
if t.Selector == nil {
resources = m.Resources()
} else {
resources, err = m.Select(*t.Selector)
if err != nil {
return err
}
}
// TODO: consider t.NotSelector if implemented
for _, res := range resources {
if t.FieldPath == types.MetadataNamespacePath {
err = res.ApplyFilter(namespace.Filter{
Namespace: p.Value,
})
} else {
err = res.ApplyFilter(valueadd.Filter{
Value: p.Value,
FieldPath: t.FieldPath,
FilePathPosition: t.FilePathPosition,
})
}
if err != nil {
return err
}
}
}
return nil
}
func NewValueAddTransformerPlugin() resmap.TransformerPlugin {
return &ValueAddTransformerPlugin{}
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package builtins holds code generated from the builtin plugins.
// The "builtin" plugins are written as normal plugins and can
// be used as such, but they are also used to generate the code
// in this package so they can be statically linked to client code.
package builtins
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package generators
import (
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// MakeConfigMap makes a configmap.
//
// ConfigMap: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#configmap-v1-core
//
// ConfigMaps and Secrets are similar.
//
// Both objects have a `data` field, which contains a map from keys to
// values that must be UTF-8 valid strings. Such data might be simple text,
// or whoever made the data may have done so by performing a base64 encoding
// on binary data. Regardless, k8s has no means to know this, so it treats
// the data field as a string.
//
// The ConfigMap has an additional field `binaryData`, also a map, but its
// values are _intended_ to be interpreted as a base64 encoding of []byte,
// by whatever makes use of the ConfigMap.
//
// In a ConfigMap, any key used in `data` cannot also be used in `binaryData`
// and vice-versa. A key must be unique across both maps.
func MakeConfigMap(
ldr ifc.KvLoader, args *types.ConfigMapArgs) (rn *yaml.RNode, err error) {
rn, err = makeBaseNode("ConfigMap", args.Name, args.Namespace)
if err != nil {
return nil, err
}
m, err := makeValidatedDataMap(ldr, args.Name, args.KvPairSources)
if err != nil {
return nil, err
}
if err = rn.LoadMapIntoConfigMapData(m); err != nil {
return nil, err
}
err = copyLabelsAndAnnotations(rn, args.Options)
if err != nil {
return nil, err
}
err = setImmutable(rn, args.Options)
if err != nil {
return nil, err
}
return rn, nil
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package generators
import (
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// MakeSecret makes a kubernetes Secret.
//
// Secret: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#secret-v1-core
//
// ConfigMaps and Secrets are similar.
//
// Like a ConfigMap, a Secret has a `data` field, but unlike a ConfigMap it has
// no `binaryData` field.
//
// All of a Secret's data is assumed to be opaque in nature, and assumed to be
// base64 encoded from its original representation, regardless of whether the
// original data was UTF-8 text or binary.
//
// This encoding provides no secrecy. It's just a neutral, common means to
// represent opaque text and binary data. Beneath the base64 encoding
// is presumably further encoding under control of the Secret's consumer.
//
// A Secret has string field `type` which holds an identifier, used by the
// client, to choose the algorithm to interpret the `data` field. Kubernetes
// cannot make use of this data; it's up to a controller or some pod's service
// to interpret the value, using `type` as a clue as to how to do this.
func MakeSecret(
ldr ifc.KvLoader, args *types.SecretArgs) (rn *yaml.RNode, err error) {
rn, err = makeBaseNode("Secret", args.Name, args.Namespace)
if err != nil {
return nil, err
}
t := "Opaque"
if args.Type != "" {
t = args.Type
}
if _, err := rn.Pipe(
yaml.FieldSetter{
Name: "type",
Value: yaml.NewStringRNode(t)}); err != nil {
return nil, err
}
m, err := makeValidatedDataMap(ldr, args.Name, args.KvPairSources)
if err != nil {
return nil, err
}
if err = rn.LoadMapIntoSecretData(m); err != nil {
return nil, err
}
copyLabelsAndAnnotations(rn, args.Options)
setImmutable(rn, args.Options)
return rn, nil
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package generators
import (
"fmt"
"path"
"strings"
"github.com/go-errors/errors"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func makeBaseNode(kind, name, namespace string) (*yaml.RNode, error) {
rn, err := yaml.Parse(fmt.Sprintf(`
apiVersion: v1
kind: %s
`, kind))
if err != nil {
return nil, err
}
if name == "" {
return nil, errors.Errorf("a configmap must have a name")
}
if _, err := rn.Pipe(yaml.SetK8sName(name)); err != nil {
return nil, err
}
if namespace != "" {
if _, err := rn.Pipe(yaml.SetK8sNamespace(namespace)); err != nil {
return nil, err
}
}
return rn, nil
}
func makeValidatedDataMap(
ldr ifc.KvLoader, name string, sources types.KvPairSources) (map[string]string, error) {
pairs, err := ldr.Load(sources)
if err != nil {
return nil, errors.WrapPrefix(err, "loading KV pairs", 0)
}
knownKeys := make(map[string]string)
for _, p := range pairs {
// legal key: alphanumeric characters, '-', '_' or '.'
if err := ldr.Validator().ErrIfInvalidKey(p.Key); err != nil {
return nil, err
}
if _, ok := knownKeys[p.Key]; ok {
return nil, errors.Errorf(
"configmap %s illegally repeats the key `%s`", name, p.Key)
}
knownKeys[p.Key] = p.Value
}
return knownKeys, nil
}
// copyLabelsAndAnnotations copies labels and annotations from
// GeneratorOptions into the given object.
func copyLabelsAndAnnotations(
rn *yaml.RNode, opts *types.GeneratorOptions) error {
if opts == nil {
return nil
}
for _, k := range yaml.SortedMapKeys(opts.Labels) {
v := opts.Labels[k]
if _, err := rn.Pipe(yaml.SetLabel(k, v)); err != nil {
return err
}
}
for _, k := range yaml.SortedMapKeys(opts.Annotations) {
v := opts.Annotations[k]
if _, err := rn.Pipe(yaml.SetAnnotation(k, v)); err != nil {
return err
}
}
return nil
}
func setImmutable(
rn *yaml.RNode, opts *types.GeneratorOptions) error {
if opts == nil {
return nil
}
if opts.Immutable {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Value: "true",
Tag: yaml.NodeTagBool,
}
if _, err := rn.Pipe(yaml.FieldSetter{Name: "immutable", Value: yaml.NewRNode(n)}); err != nil {
return err
}
}
return nil
}
// ParseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func ParseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", errors.Errorf("missing key name for file path %q in source %q", strings.TrimPrefix(source, "="), source)
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", errors.Errorf("missing file path for key name %q in source %q", strings.TrimSuffix(source, "="), source)
case numSeparators > 1:
return "", "", errors.Errorf("source %q key name or file path contains '='", source)
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package git
import (
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// Cloner is a function that can clone a git repo.
type Cloner func(repoSpec *RepoSpec) error
// ClonerUsingGitExec uses a local git install, as opposed
// to say, some remote API, to obtain a local clone of
// a remote repo.
func ClonerUsingGitExec(repoSpec *RepoSpec) error {
r, err := newCmdRunner(repoSpec.Timeout)
if err != nil {
return err
}
repoSpec.Dir = r.dir
if err = r.run("init"); err != nil {
return err
}
// git relative submodule need origin, see https://github.com/kubernetes-sigs/kustomize/issues/5131
if err = r.run("remote", "add", "origin", repoSpec.CloneSpec()); err != nil {
return err
}
ref := "HEAD"
if repoSpec.Ref != "" {
ref = repoSpec.Ref
}
// we use repoSpec.CloneSpec() instead of origin because on error,
// the prior prints the actual repo url for the user.
if err = r.run("fetch", "--depth=1", repoSpec.CloneSpec(), ref); err != nil {
return err
}
if err = r.run("checkout", "FETCH_HEAD"); err != nil {
return err
}
if repoSpec.Submodules {
return r.run("submodule", "update", "--init", "--recursive")
}
return nil
}
// DoNothingCloner returns a cloner that only sets
// cloneDir field in the repoSpec. It's assumed that
// the cloneDir is associated with some fake filesystem
// used in a test.
func DoNothingCloner(dir filesys.ConfirmedDir) Cloner {
return func(rs *RepoSpec) error {
rs.Dir = dir
return nil
}
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package git
import (
"os/exec"
"time"
"sigs.k8s.io/kustomize/api/internal/utils"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// gitRunner runs the external git binary.
type gitRunner struct {
gitProgram string
duration time.Duration
dir filesys.ConfirmedDir
}
// newCmdRunner returns a gitRunner if it can find the binary.
// It also creats a temp directory for cloning repos.
func newCmdRunner(timeout time.Duration) (*gitRunner, error) {
gitProgram, err := exec.LookPath("git")
if err != nil {
return nil, errors.WrapPrefixf(err, "no 'git' program on path")
}
dir, err := filesys.NewTmpConfirmedDir()
if err != nil {
return nil, err
}
return &gitRunner{
gitProgram: gitProgram,
duration: timeout,
dir: dir,
}, nil
}
// run a command with a timeout.
func (r gitRunner) run(args ...string) error {
//nolint: gosec
cmd := exec.Command(r.gitProgram, args...)
cmd.Dir = r.dir.String()
return utils.TimedCall(
cmd.String(),
r.duration,
func() error {
out, err := cmd.CombinedOutput()
if err != nil {
return errors.WrapPrefixf(err, "failed to run '%s': %s", cmd.String(), string(out))
}
return err
})
}
+387
View File
@@ -0,0 +1,387 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package git
import (
"fmt"
"log"
"net/url"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// Used as a temporary non-empty occupant of the cloneDir
// field, as something distinguishable from the empty string
// in various outputs (especially tests). Not using an
// actual directory name here, as that's a temporary directory
// with a unique name that isn't created until clone time.
const notCloned = filesys.ConfirmedDir("/notCloned")
// RepoSpec specifies a git repository and a branch and path therein.
type RepoSpec struct {
// Raw, original spec, used to look for cycles.
// TODO(monopole): Drop raw, use processed fields instead.
raw string
// Host, e.g. https://github.com/
Host string
// RepoPath name (Path to repository),
// e.g. kubernetes-sigs/kustomize
RepoPath string
// Dir is where the repository is cloned to.
Dir filesys.ConfirmedDir
// Relative path in the repository, and in the cloneDir,
// to a Kustomization.
KustRootPath string
// Branch or tag reference.
Ref string
// Submodules indicates whether or not to clone git submodules.
Submodules bool
// Timeout is the maximum duration allowed for execing git commands.
Timeout time.Duration
}
// CloneSpec returns a string suitable for "git clone {spec}".
func (x *RepoSpec) CloneSpec() string {
return x.Host + x.RepoPath
}
func (x *RepoSpec) CloneDir() filesys.ConfirmedDir {
return x.Dir
}
func (x *RepoSpec) Raw() string {
return x.raw
}
func (x *RepoSpec) AbsPath() string {
return x.Dir.Join(x.KustRootPath)
}
func (x *RepoSpec) Cleaner(fSys filesys.FileSystem) func() error {
return func() error { return fSys.RemoveAll(x.Dir.String()) }
}
const (
refQuery = "?ref="
gitSuffix = ".git"
gitRootDelimiter = "_git/"
pathSeparator = "/" // do not use filepath.Separator, as this is a URL
)
// NewRepoSpecFromURL parses git-like urls.
// From strings like git@github.com:someOrg/someRepo.git or
// https://github.com/someOrg/someRepo?ref=someHash, extract
// the different parts of URL, set into a RepoSpec object and return RepoSpec object.
// It MUST return an error if the input is not a git-like URL, as this is used by some code paths
// to distinguish between local and remote paths.
//
// In particular, NewRepoSpecFromURL separates the URL used to clone the repo from the
// elements Kustomize uses for other purposes (e.g. query params that turn into args, and
// the path to the kustomization root within the repo).
func NewRepoSpecFromURL(n string) (*RepoSpec, error) {
repoSpec := &RepoSpec{raw: n, Dir: notCloned, Timeout: defaultTimeout, Submodules: defaultSubmodules}
if filepath.IsAbs(n) {
return nil, fmt.Errorf("uri looks like abs path: %s", n)
}
// Parse the query first. This is safe because according to rfc3986 "?" is only allowed in the
// query and is not recognized %-encoded.
// Note that parseQuery returns default values for empty parameters.
n, query, _ := strings.Cut(n, "?")
repoSpec.Ref, repoSpec.Timeout, repoSpec.Submodules = parseQuery(query)
var err error
// Parse the host (e.g. scheme, username, domain) segment.
repoSpec.Host, n, err = extractHost(n)
if err != nil {
return nil, err
}
// In some cases, we're given a path to a git repo + a path to the kustomization root within
// that repo. We need to split them so that we can ultimately give the repo only to the cloner.
repoSpec.RepoPath, repoSpec.KustRootPath, err = parsePathParts(n, defaultRepoPathLength(repoSpec.Host))
if err != nil {
return nil, err
}
return repoSpec, nil
}
const allSegments = -999999
const orgRepoSegments = 2
func defaultRepoPathLength(host string) int {
if strings.HasPrefix(host, fileScheme) {
return allSegments
}
return orgRepoSegments
}
// parsePathParts splits the repo path that will ultimately be passed to git to clone the
// repo from the kustomization root path, which Kustomize will execute the build in after the repo
// is cloned.
//
// We first try to do this based on explicit markers in the URL (e.g. _git, .git or //).
// If none are present, we try to apply a historical default repo path length that is derived from
// Github URLs. If there aren't enough segments, we have historically considered the URL invalid.
func parsePathParts(n string, defaultSegmentLength int) (string, string, error) {
repoPath, kustRootPath, success := tryExplicitMarkerSplit(n)
if !success {
repoPath, kustRootPath, success = tryDefaultLengthSplit(n, defaultSegmentLength)
}
// Validate the result
if !success || len(repoPath) == 0 {
return "", "", fmt.Errorf("failed to parse repo path segment")
}
if kustRootPathExitsRepo(kustRootPath) {
return "", "", fmt.Errorf("url path exits repo: %s", n)
}
return repoPath, strings.TrimPrefix(kustRootPath, pathSeparator), nil
}
func tryExplicitMarkerSplit(n string) (string, string, bool) {
// Look for the _git delimiter, which by convention is expected to be ONE directory above the repo root.
// If found, split on the NEXT path element, which is the repo root.
// Example: https://username@dev.azure.com/org/project/_git/repo/path/to/kustomization/root
if gitRootIdx := strings.Index(n, gitRootDelimiter); gitRootIdx >= 0 {
gitRootPath := n[:gitRootIdx+len(gitRootDelimiter)]
subpathSegments := strings.Split(n[gitRootIdx+len(gitRootDelimiter):], pathSeparator)
return gitRootPath + subpathSegments[0], strings.Join(subpathSegments[1:], pathSeparator), true
// Look for a double-slash in the path, which if present separates the repo root from the kust path.
// It is a convention, not a real path element, so do not preserve it in the returned value.
// Example: https://github.com/org/repo//path/to/kustomozation/root
} else if repoRootIdx := strings.Index(n, "//"); repoRootIdx >= 0 {
return n[:repoRootIdx], n[repoRootIdx+2:], true
// Look for .git in the path, which if present is part of the directory name of the git repo.
// This means we want to grab everything up to and including that suffix
// Example: https://github.com/org/repo.git/path/to/kustomozation/root
} else if gitSuffixIdx := strings.Index(n, gitSuffix); gitSuffixIdx >= 0 {
upToGitSuffix := n[:gitSuffixIdx+len(gitSuffix)]
afterGitSuffix := n[gitSuffixIdx+len(gitSuffix):]
return upToGitSuffix, afterGitSuffix, true
}
return "", "", false
}
func tryDefaultLengthSplit(n string, defaultSegmentLength int) (string, string, bool) {
// If the default is to take all segments, do so.
if defaultSegmentLength == allSegments {
return n, "", true
// If the default is N segments, make sure we have at least that many and take them if so.
// If we have less than N, we have historically considered the URL invalid.
} else if segments := strings.Split(n, pathSeparator); len(segments) >= defaultSegmentLength {
firstNSegments := strings.Join(segments[:defaultSegmentLength], pathSeparator)
rest := strings.Join(segments[defaultSegmentLength:], pathSeparator)
return firstNSegments, rest, true
}
return "", "", false
}
func kustRootPathExitsRepo(kustRootPath string) bool {
cleanedPath := filepath.Clean(strings.TrimPrefix(kustRootPath, string(filepath.Separator)))
pathElements := strings.Split(cleanedPath, string(filepath.Separator))
return len(pathElements) > 0 &&
pathElements[0] == filesys.ParentDir
}
// Clone git submodules by default.
const defaultSubmodules = true
// Arbitrary, but non-infinite, timeout for running commands.
const defaultTimeout = 27 * time.Second
func parseQuery(query string) (string, time.Duration, bool) {
values, err := url.ParseQuery(query)
// in event of parse failure, return defaults
if err != nil {
return "", defaultTimeout, defaultSubmodules
}
// ref is the desired git ref to target. Can be specified by in a git URL
// with ?ref=<string> or ?version=<string>, although ref takes precedence.
ref := values.Get("version")
if queryValue := values.Get("ref"); queryValue != "" {
ref = queryValue
}
// depth is the desired git exec timeout. Can be specified by in a git URL
// with ?timeout=<duration>.
duration := defaultTimeout
if queryValue := values.Get("timeout"); queryValue != "" {
// Attempt to first parse as a number of integer seconds (like "61"),
// and then attempt to parse as a suffixed duration (like "61s").
if intValue, err := strconv.Atoi(queryValue); err == nil && intValue > 0 {
duration = time.Duration(intValue) * time.Second
} else if durationValue, err := time.ParseDuration(queryValue); err == nil && durationValue > 0 {
duration = durationValue
}
}
// submodules indicates if git submodule cloning is desired. Can be
// specified by in a git URL with ?submodules=<bool>.
submodules := defaultSubmodules
if queryValue := values.Get("submodules"); queryValue != "" {
if boolValue, err := strconv.ParseBool(queryValue); err == nil {
submodules = boolValue
}
}
return ref, duration, submodules
}
func extractHost(n string) (string, string, error) {
n = ignoreForcedGitProtocol(n)
scheme, n := extractScheme(n)
username, n := extractUsername(n)
stdGithub := isStandardGithubHost(n)
acceptSCP := acceptSCPStyle(scheme, username, stdGithub)
// Validate the username and scheme before attempting host/path parsing, because if the parsing
// so far has not succeeded, we will not be able to extract the host and path correctly.
if err := validateScheme(scheme, acceptSCP); err != nil {
return "", "", err
}
// Now that we have extracted a valid scheme+username, we can parse host itself.
// The file protocol specifies an absolute path to a local git repo.
// Everything after the scheme (including any 'username' we found) is actually part of that path.
if scheme == fileScheme {
return scheme, username + n, nil
}
var host, rest = n, ""
if sepIndex := findPathSeparator(n, acceptSCP); sepIndex >= 0 {
host, rest = n[:sepIndex+1], n[sepIndex+1:]
}
// Github URLs are strictly normalized in a way that may discard scheme and username components.
if stdGithub {
scheme, username, host = normalizeGithubHostParts(scheme, username)
}
// Host is required, so do not concat the scheme and username if we didn't find one.
if host == "" {
return "", "", errors.Errorf("failed to parse host segment")
}
return scheme + username + host, rest, nil
}
// ignoreForcedGitProtocol strips the "git::" prefix from URLs.
// We used to use go-getter to handle our urls: https://github.com/hashicorp/go-getter.
// The git:: prefix signaled go-getter to use the git protocol to fetch the url's contents.
// We silently strip this prefix to allow these go-getter-style urls to continue to work,
// although the git protocol (which is insecure and unsupported on many platforms, including Github)
// will not actually be used as intended.
func ignoreForcedGitProtocol(n string) string {
n, found := trimPrefixIgnoreCase(n, "git::")
if found {
log.Println("Warning: Forcing the git protocol using the 'git::' URL prefix is not supported. " +
"Kustomize currently strips this invalid prefix, but will stop doing so in a future release. " +
"Please remove the 'git::' prefix from your configuration.")
}
return n
}
// acceptSCPStyle returns true if the scheme and username indicate potential use of an SCP-style URL.
// With this style, the scheme is not explicit and the path is delimited by a colon.
// Strictly speaking the username is optional in SCP-like syntax, but Kustomize has always
// required it for non-Github URLs.
// Example: user@host.xz:path/to/repo.git/
func acceptSCPStyle(scheme, username string, isGithubURL bool) bool {
return scheme == "" && (username != "" || isGithubURL)
}
func validateScheme(scheme string, acceptSCPStyle bool) error {
// see https://git-scm.com/docs/git-fetch#_git_urls for info relevant to these validations
switch scheme {
case "":
// Empty scheme is only ok if it's a Github URL or if it looks like SCP-style syntax
if !acceptSCPStyle {
return fmt.Errorf("failed to parse scheme")
}
case sshScheme, fileScheme, httpsScheme, httpScheme:
// These are all supported schemes
default:
// At time of writing, we should never end up here because we do not parse out
// unsupported schemes to begin with.
return fmt.Errorf("unsupported scheme %q", scheme)
}
return nil
}
const fileScheme = "file://"
const httpScheme = "http://"
const httpsScheme = "https://"
const sshScheme = "ssh://"
func extractScheme(s string) (string, string) {
for _, prefix := range []string{sshScheme, httpsScheme, httpScheme, fileScheme} {
if rest, found := trimPrefixIgnoreCase(s, prefix); found {
return prefix, rest
}
}
return "", s
}
func extractUsername(s string) (string, string) {
var userRegexp = regexp.MustCompile(`^([a-zA-Z][a-zA-Z0-9-]*)@`)
if m := userRegexp.FindStringSubmatch(s); m != nil {
username := m[1] + "@"
return username, s[len(username):]
}
return "", s
}
func isStandardGithubHost(s string) bool {
lowerCased := strings.ToLower(s)
return strings.HasPrefix(lowerCased, "github.com/") ||
strings.HasPrefix(lowerCased, "github.com:")
}
// trimPrefixIgnoreCase returns the rest of s and true if prefix, ignoring case, prefixes s.
// Otherwise, trimPrefixIgnoreCase returns s and false.
func trimPrefixIgnoreCase(s, prefix string) (string, bool) {
if len(prefix) <= len(s) && strings.ToLower(s[:len(prefix)]) == prefix {
return s[len(prefix):], true
}
return s, false
}
func findPathSeparator(hostPath string, acceptSCP bool) int {
sepIndex := strings.Index(hostPath, pathSeparator)
if acceptSCP {
colonIndex := strings.Index(hostPath, ":")
// The colon acts as a delimiter in scp-style ssh URLs only if not prefixed by '/'.
if sepIndex == -1 || (colonIndex > 0 && colonIndex < sepIndex) {
sepIndex = colonIndex
}
}
return sepIndex
}
func normalizeGithubHostParts(scheme, username string) (string, string, string) {
if strings.HasPrefix(scheme, sshScheme) || username != "" {
return "", username, "github.com:"
}
return httpsScheme, "", "github.com/"
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package image
import (
"regexp"
"strings"
)
// IsImageMatched returns true if the value of t is identical to the
// image name in the full image name and tag as given by s.
func IsImageMatched(s, t string) bool {
// Tag values are limited to [a-zA-Z0-9_.{}-].
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.{}-]*)?(@sha256:[a-zA-Z0-9_.{}-]*)?$")
return pattern.MatchString(s)
}
// Split separates and returns the name and tag parts
// from the image string using either colon `:` or at `@` separators.
// image reference pattern: [[host[:port]/]component/]component[:tag][@digest]
func Split(imageName string) (name string, tag string, digest string) {
// check if image name contains a domain
// if domain is present, ignore domain and check for `:`
searchName := imageName
slashIndex := strings.Index(imageName, "/")
if slashIndex > 0 {
searchName = imageName[slashIndex:]
} else {
slashIndex = 0
}
id := strings.Index(searchName, "@")
ic := strings.Index(searchName, ":")
// no tag or digest
if ic < 0 && id < 0 {
return imageName, "", ""
}
// digest only
if id >= 0 && (id < ic || ic < 0) {
id += slashIndex
name = imageName[:id]
digest = strings.TrimPrefix(imageName[id:], "@")
return name, "", digest
}
// tag and digest
if id >= 0 && ic >= 0 {
id += slashIndex
ic += slashIndex
name = imageName[:ic]
tag = strings.TrimPrefix(imageName[ic:id], ":")
digest = strings.TrimPrefix(imageName[id:], "@")
return name, tag, digest
}
// tag only
ic += slashIndex
name = imageName[:ic]
tag = strings.TrimPrefix(imageName[ic:], ":")
return name, tag, ""
}
@@ -0,0 +1,47 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const commonAnnotationFieldSpecs = `
commonAnnotations:
- path: metadata/annotations
create: true
- path: spec/template/metadata/annotations
create: true
version: v1
kind: ReplicationController
- path: spec/template/metadata/annotations
create: true
kind: Deployment
- path: spec/template/metadata/annotations
create: true
kind: ReplicaSet
- path: spec/template/metadata/annotations
create: true
kind: DaemonSet
- path: spec/template/metadata/annotations
create: true
kind: StatefulSet
- path: spec/template/metadata/annotations
create: true
group: batch
kind: Job
- path: spec/jobTemplate/metadata/annotations
create: true
group: batch
kind: CronJob
- path: spec/jobTemplate/spec/template/metadata/annotations
create: true
group: batch
kind: CronJob
`
@@ -0,0 +1,113 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const commonLabelFieldSpecs = `
commonLabels:
- path: spec/selector
create: true
version: v1
kind: Service
- path: spec/selector
create: true
version: v1
kind: ReplicationController
- path: spec/selector/matchLabels
create: true
kind: Deployment
- path: spec/template/spec/affinity/podAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels
create: false
group: apps
kind: Deployment
- path: spec/template/spec/affinity/podAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels
create: false
group: apps
kind: Deployment
- path: spec/template/spec/affinity/podAntiAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels
create: false
group: apps
kind: Deployment
- path: spec/template/spec/affinity/podAntiAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels
create: false
group: apps
kind: Deployment
- path: spec/template/spec/topologySpreadConstraints/labelSelector/matchLabels
create: false
group: apps
kind: Deployment
- path: spec/selector/matchLabels
create: true
kind: ReplicaSet
- path: spec/selector/matchLabels
create: true
kind: DaemonSet
- path: spec/selector/matchLabels
create: true
group: apps
kind: StatefulSet
- path: spec/template/spec/affinity/podAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels
create: false
group: apps
kind: StatefulSet
- path: spec/template/spec/affinity/podAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels
create: false
group: apps
kind: StatefulSet
- path: spec/template/spec/affinity/podAntiAffinity/preferredDuringSchedulingIgnoredDuringExecution/podAffinityTerm/labelSelector/matchLabels
create: false
group: apps
kind: StatefulSet
- path: spec/template/spec/affinity/podAntiAffinity/requiredDuringSchedulingIgnoredDuringExecution/labelSelector/matchLabels
create: false
group: apps
kind: StatefulSet
- path: spec/template/spec/topologySpreadConstraints/labelSelector/matchLabels
create: false
group: apps
kind: StatefulSet
- path: spec/selector/matchLabels
create: false
group: batch
kind: Job
- path: spec/jobTemplate/spec/selector/matchLabels
create: false
group: batch
kind: CronJob
- path: spec/selector/matchLabels
create: false
group: policy
kind: PodDisruptionBudget
- path: spec/podSelector/matchLabels
create: false
group: networking.k8s.io
kind: NetworkPolicy
- path: spec/ingress/from/podSelector/matchLabels
create: false
group: networking.k8s.io
kind: NetworkPolicy
- path: spec/egress/to/podSelector/matchLabels
create: false
group: networking.k8s.io
kind: NetworkPolicy
` + metadataLabelsFieldSpecs
@@ -0,0 +1,42 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
import (
"bytes"
)
// GetDefaultFieldSpecs returns default fieldSpecs.
func GetDefaultFieldSpecs() []byte {
configData := [][]byte{
[]byte(namePrefixFieldSpecs),
[]byte(nameSuffixFieldSpecs),
[]byte(commonLabelFieldSpecs),
[]byte(templateLabelFieldSpecs),
[]byte(commonAnnotationFieldSpecs),
[]byte(namespaceFieldSpecs),
[]byte(varReferenceFieldSpecs),
[]byte(nameReferenceFieldSpecs),
[]byte(imagesFieldSpecs),
[]byte(replicasFieldSpecs),
}
return bytes.Join(configData, []byte("\n"))
}
// GetDefaultFieldSpecsAsMap returns default fieldSpecs
// as a string->string map.
func GetDefaultFieldSpecsAsMap() map[string]string {
result := make(map[string]string)
result["nameprefix"] = namePrefixFieldSpecs
result["namesuffix"] = nameSuffixFieldSpecs
result["commonlabels"] = commonLabelFieldSpecs
result["templatelabels"] = templateLabelFieldSpecs
result["commonannotations"] = commonAnnotationFieldSpecs
result["namespace"] = namespaceFieldSpecs
result["varreference"] = varReferenceFieldSpecs
result["namereference"] = nameReferenceFieldSpecs
result["images"] = imagesFieldSpecs
result["replicas"] = replicasFieldSpecs
return result
}
@@ -0,0 +1,8 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package builtinpluginconsts provides builtin plugin
// configuration data. Builtin plugins can also be
// configured individually with plugin config files,
// in which case the constants in this package are ignored.
package builtinpluginconsts
@@ -0,0 +1,22 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const (
imagesFieldSpecs = `
images:
- path: spec/containers[]/image
create: true
- path: spec/initContainers[]/image
create: true
- path: spec/volumes[]/image/reference
create: true
- path: spec/template/spec/containers[]/image
create: true
- path: spec/template/spec/initContainers[]/image
create: true
- path: spec/template/spec/volumes[]/image/reference
create: true
`
)
@@ -0,0 +1,51 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const metadataLabelsFieldSpecs = `
- path: metadata/labels
create: true
- path: spec/template/metadata/labels
create: true
version: v1
kind: ReplicationController
- path: spec/template/metadata/labels
create: true
kind: Deployment
- path: spec/template/metadata/labels
create: true
kind: ReplicaSet
- path: spec/template/metadata/labels
create: true
kind: DaemonSet
- path: spec/template/metadata/labels
create: true
group: apps
kind: StatefulSet
- path: spec/volumeClaimTemplates[]/metadata/labels
create: true
group: apps
kind: StatefulSet
- path: spec/template/metadata/labels
create: true
group: batch
kind: Job
- path: spec/jobTemplate/metadata/labels
create: true
group: batch
kind: CronJob
- path: spec/jobTemplate/spec/template/metadata/labels
create: true
group: batch
kind: CronJob
`
@@ -0,0 +1,11 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const (
namePrefixFieldSpecs = `
namePrefix:
- path: metadata/name
`
)
@@ -0,0 +1,434 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
// LINT.IfChange
const (
nameReferenceFieldSpecs = `
nameReference:
- kind: Deployment
fieldSpecs:
- path: spec/scaleTargetRef/name
kind: HorizontalPodAutoscaler
- kind: ReplicationController
fieldSpecs:
- path: spec/scaleTargetRef/name
kind: HorizontalPodAutoscaler
- kind: ReplicaSet
fieldSpecs:
- path: spec/scaleTargetRef/name
kind: HorizontalPodAutoscaler
- kind: StatefulSet
fieldSpecs:
- path: spec/scaleTargetRef/name
kind: HorizontalPodAutoscaler
- kind: ConfigMap
version: v1
fieldSpecs:
- path: spec/volumes/configMap/name
version: v1
kind: Pod
- path: spec/containers/env/valueFrom/configMapKeyRef/name
version: v1
kind: Pod
- path: spec/initContainers/env/valueFrom/configMapKeyRef/name
version: v1
kind: Pod
- path: spec/containers/envFrom/configMapRef/name
version: v1
kind: Pod
- path: spec/initContainers/envFrom/configMapRef/name
version: v1
kind: Pod
- path: spec/volumes/projected/sources/configMap/name
version: v1
kind: Pod
- path: template/spec/volumes/configMap/name
kind: PodTemplate
- path: template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: PodTemplate
- path: template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: PodTemplate
- path: template/spec/containers/envFrom/configMapRef/name
kind: PodTemplate
- path: template/spec/initContainers/envFrom/configMapRef/name
kind: PodTemplate
- path: template/spec/volumes/projected/sources/configMap/name
kind: PodTemplate
- path: spec/template/spec/volumes/configMap/name
kind: Deployment
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: Deployment
- path: spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: Deployment
- path: spec/template/spec/containers/envFrom/configMapRef/name
kind: Deployment
- path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: Deployment
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: Deployment
- path: spec/template/spec/volumes/configMap/name
kind: ReplicaSet
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: ReplicaSet
- path: spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: ReplicaSet
- path: spec/template/spec/containers/envFrom/configMapRef/name
kind: ReplicaSet
- path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: ReplicaSet
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: ReplicaSet
- path: spec/template/spec/volumes/configMap/name
kind: DaemonSet
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: DaemonSet
- path: spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: DaemonSet
- path: spec/template/spec/containers/envFrom/configMapRef/name
kind: DaemonSet
- path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: DaemonSet
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: DaemonSet
- path: spec/template/spec/volumes/configMap/name
kind: StatefulSet
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: StatefulSet
- path: spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: StatefulSet
- path: spec/template/spec/containers/envFrom/configMapRef/name
kind: StatefulSet
- path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: StatefulSet
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: StatefulSet
- path: spec/template/spec/volumes/configMap/name
kind: Job
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: Job
- path: spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: Job
- path: spec/template/spec/containers/envFrom/configMapRef/name
kind: Job
- path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: Job
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: Job
- path: spec/jobTemplate/spec/template/spec/volumes/configMap/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/configMap/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/envFrom/configMapRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/envFrom/configMapRef/name
kind: CronJob
- path: spec/configSource/configMap
kind: Node
- path: rules/resourceNames
kind: Role
- path: rules/resourceNames
kind: ClusterRole
- path: metadata/annotations/nginx.ingress.kubernetes.io\/fastcgi-params-configmap
kind: Ingress
- kind: Secret
version: v1
fieldSpecs:
- path: spec/volumes/secret/secretName
version: v1
kind: Pod
- path: spec/containers/env/valueFrom/secretKeyRef/name
version: v1
kind: Pod
- path: spec/initContainers/env/valueFrom/secretKeyRef/name
version: v1
kind: Pod
- path: spec/containers/envFrom/secretRef/name
version: v1
kind: Pod
- path: spec/initContainers/envFrom/secretRef/name
version: v1
kind: Pod
- path: spec/imagePullSecrets/name
version: v1
kind: Pod
- path: spec/volumes/projected/sources/secret/name
version: v1
kind: Pod
- path: template/spec/volumes/secret/secretName
kind: PodTemplate
- path: template/spec/containers/env/valueFrom/secretKeyRef/name
kind: PodTemplate
- path: template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: PodTemplate
- path: template/spec/containers/envFrom/secretRef/name
kind: PodTemplate
- path: template/spec/initContainers/envFrom/secretRef/name
kind: PodTemplate
- path: template/spec/imagePullSecrets/name
kind: PodTemplate
- path: template/spec/volumes/projected/sources/secret/name
kind: PodTemplate
- path: spec/template/spec/volumes/secret/secretName
kind: Deployment
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: Deployment
- path: spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: Deployment
- path: spec/template/spec/containers/envFrom/secretRef/name
kind: Deployment
- path: spec/template/spec/initContainers/envFrom/secretRef/name
kind: Deployment
- path: spec/template/spec/imagePullSecrets/name
kind: Deployment
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: Deployment
- path: spec/template/spec/volumes/secret/secretName
kind: ReplicaSet
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: ReplicaSet
- path: spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: ReplicaSet
- path: spec/template/spec/containers/envFrom/secretRef/name
kind: ReplicaSet
- path: spec/template/spec/initContainers/envFrom/secretRef/name
kind: ReplicaSet
- path: spec/template/spec/imagePullSecrets/name
kind: ReplicaSet
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: ReplicaSet
- path: spec/template/spec/volumes/secret/secretName
kind: DaemonSet
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: DaemonSet
- path: spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: DaemonSet
- path: spec/template/spec/containers/envFrom/secretRef/name
kind: DaemonSet
- path: spec/template/spec/initContainers/envFrom/secretRef/name
kind: DaemonSet
- path: spec/template/spec/imagePullSecrets/name
kind: DaemonSet
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: DaemonSet
- path: spec/template/spec/volumes/secret/secretName
kind: StatefulSet
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: StatefulSet
- path: spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: StatefulSet
- path: spec/template/spec/containers/envFrom/secretRef/name
kind: StatefulSet
- path: spec/template/spec/initContainers/envFrom/secretRef/name
kind: StatefulSet
- path: spec/template/spec/imagePullSecrets/name
kind: StatefulSet
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: StatefulSet
- path: spec/template/spec/volumes/secret/secretName
kind: Job
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: Job
- path: spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: Job
- path: spec/template/spec/containers/envFrom/secretRef/name
kind: Job
- path: spec/template/spec/initContainers/envFrom/secretRef/name
kind: Job
- path: spec/template/spec/imagePullSecrets/name
kind: Job
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: Job
- path: spec/jobTemplate/spec/template/spec/volumes/secret/secretName
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/secret/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/envFrom/secretRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/envFrom/secretRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/imagePullSecrets/name
kind: CronJob
- path: spec/tls/secretName
kind: Ingress
- path: metadata/annotations/ingress.kubernetes.io\/auth-secret
kind: Ingress
- path: metadata/annotations/nginx.ingress.kubernetes.io\/auth-secret
kind: Ingress
- path: metadata/annotations/nginx.ingress.kubernetes.io\/auth-tls-secret
kind: Ingress
- path: spec/tls/secretName
kind: Ingress
- path: imagePullSecrets/name
kind: ServiceAccount
- path: parameters/secretName
kind: StorageClass
- path: parameters/adminSecretName
kind: StorageClass
- path: parameters/userSecretName
kind: StorageClass
- path: parameters/secretRef
kind: StorageClass
- path: rules/resourceNames
kind: Role
- path: rules/resourceNames
kind: ClusterRole
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: Service
group: serving.knative.dev
version: v1
- path: spec/azureFile/secretName
kind: PersistentVolume
- kind: Service
version: v1
fieldSpecs:
- path: spec/serviceName
kind: StatefulSet
group: apps
- path: spec/rules/http/paths/backend/serviceName
kind: Ingress
- path: spec/backend/serviceName
kind: Ingress
- path: spec/rules/http/paths/backend/service/name
kind: Ingress
- path: spec/defaultBackend/service/name
kind: Ingress
- path: spec/service/name
kind: APIService
group: apiregistration.k8s.io
- path: webhooks/clientConfig/service
kind: ValidatingWebhookConfiguration
group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service
kind: MutatingWebhookConfiguration
group: admissionregistration.k8s.io
- kind: Role
group: rbac.authorization.k8s.io
fieldSpecs:
- path: roleRef/name
kind: RoleBinding
group: rbac.authorization.k8s.io
- kind: ClusterRole
group: rbac.authorization.k8s.io
fieldSpecs:
- path: roleRef/name
kind: RoleBinding
group: rbac.authorization.k8s.io
- path: roleRef/name
kind: ClusterRoleBinding
group: rbac.authorization.k8s.io
- kind: ServiceAccount
version: v1
fieldSpecs:
- path: subjects
kind: RoleBinding
group: rbac.authorization.k8s.io
- path: subjects
kind: ClusterRoleBinding
group: rbac.authorization.k8s.io
- path: spec/serviceAccountName
kind: Pod
- path: spec/template/spec/serviceAccountName
kind: StatefulSet
- path: spec/template/spec/serviceAccountName
kind: Deployment
- path: spec/template/spec/serviceAccountName
kind: ReplicationController
- path: spec/jobTemplate/spec/template/spec/serviceAccountName
kind: CronJob
- path: spec/template/spec/serviceAccountName
kind: Job
- path: spec/template/spec/serviceAccountName
kind: DaemonSet
- kind: PersistentVolumeClaim
version: v1
fieldSpecs:
- path: spec/volumes/persistentVolumeClaim/claimName
kind: Pod
- path: spec/template/spec/volumes/persistentVolumeClaim/claimName
kind: StatefulSet
- path: spec/template/spec/volumes/persistentVolumeClaim/claimName
kind: Deployment
- path: spec/template/spec/volumes/persistentVolumeClaim/claimName
kind: ReplicationController
- path: spec/jobTemplate/spec/template/spec/volumes/persistentVolumeClaim/claimName
kind: CronJob
- path: spec/template/spec/volumes/persistentVolumeClaim/claimName
kind: Job
- path: spec/template/spec/volumes/persistentVolumeClaim/claimName
kind: DaemonSet
- kind: PersistentVolume
version: v1
fieldSpecs:
- path: spec/volumeName
kind: PersistentVolumeClaim
- path: rules/resourceNames
kind: ClusterRole
- kind: StorageClass
version: v1
group: storage.k8s.io
fieldSpecs:
- path: spec/storageClassName
kind: PersistentVolume
- path: spec/storageClassName
kind: PersistentVolumeClaim
- path: spec/volumeClaimTemplates/spec/storageClassName
kind: StatefulSet
- kind: PriorityClass
version: v1
group: scheduling.k8s.io
fieldSpecs:
- path: spec/priorityClassName
kind: Pod
- path: spec/template/spec/priorityClassName
kind: StatefulSet
- path: spec/template/spec/priorityClassName
kind: Deployment
- path: spec/template/spec/priorityClassName
kind: ReplicationController
- path: spec/jobTemplate/spec/template/spec/priorityClassName
kind: CronJob
- path: spec/template/spec/priorityClassName
kind: Job
- path: spec/template/spec/priorityClassName
kind: DaemonSet
- kind: IngressClass
version: v1
group: networking.k8s.io/v1
fieldSpecs:
- path: spec/ingressClassName
kind: Ingress
- kind: ValidatingAdmissionPolicy
group: admissionregistration.k8s.io
fieldSpecs:
- path: spec/policyName
kind: ValidatingAdmissionPolicyBinding
group: admissionregistration.k8s.io
`
)
// LINT.ThenChange(/examples/transformerconfigs/README.md)
@@ -0,0 +1,20 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const (
namespaceFieldSpecs = `
namespace:
- path: metadata/name
kind: Namespace
create: true
- path: spec/service/namespace
group: apiregistration.k8s.io
kind: APIService
create: true
- path: spec/conversion/webhook/clientConfig/service/namespace
group: apiextensions.k8s.io
kind: CustomResourceDefinition
`
)
@@ -0,0 +1,11 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const (
nameSuffixFieldSpecs = `
nameSuffix:
- path: metadata/name
`
)
@@ -0,0 +1,23 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const replicasFieldSpecs = `
replicas:
- path: spec/replicas
create: true
kind: Deployment
- path: spec/replicas
create: true
kind: ReplicationController
- path: spec/replicas
create: true
kind: ReplicaSet
- path: spec/replicas
create: true
kind: StatefulSet
`
@@ -0,0 +1,8 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const templateLabelFieldSpecs = `
templateLabels:
` + metadataLabelsFieldSpecs
@@ -0,0 +1,223 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinpluginconsts
const (
varReferenceFieldSpecs = `
varReference:
- path: spec/jobTemplate/spec/template/spec/containers/args
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/command
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/env/value
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/volumeMounts/mountPath
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/args
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/command
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/env/value
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/volumeMounts/mountPath
kind: CronJob
- path: spec/jobTemplate/spec/template/volumes/nfs/server
kind: CronJob
- path: spec/template/spec/containers/args
kind: DaemonSet
- path: spec/template/spec/containers/command
kind: DaemonSet
- path: spec/template/spec/containers/env/value
kind: DaemonSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: DaemonSet
- path: spec/template/spec/initContainers/args
kind: DaemonSet
- path: spec/template/spec/initContainers/command
kind: DaemonSet
- path: spec/template/spec/initContainers/env/value
kind: DaemonSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: DaemonSet
- path: spec/template/spec/volumes/nfs/server
kind: DaemonSet
- path: spec/template/spec/containers/args
kind: Deployment
- path: spec/template/spec/containers/command
kind: Deployment
- path: spec/template/spec/containers/env/value
kind: Deployment
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: Deployment
- path: spec/template/spec/initContainers/args
kind: Deployment
- path: spec/template/spec/initContainers/command
kind: Deployment
- path: spec/template/spec/initContainers/env/value
kind: Deployment
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: Deployment
- path: spec/template/spec/volumes/nfs/server
kind: Deployment
- path: spec/template/metadata/annotations
kind: Deployment
- path: spec/rules/host
kind: Ingress
- path: spec/tls/hosts
kind: Ingress
- path: spec/tls/secretName
kind: Ingress
- path: spec/template/spec/containers/args
kind: Job
- path: spec/template/spec/containers/command
kind: Job
- path: spec/template/spec/containers/env/value
kind: Job
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: Job
- path: spec/template/spec/initContainers/args
kind: Job
- path: spec/template/spec/initContainers/command
kind: Job
- path: spec/template/spec/initContainers/env/value
kind: Job
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: Job
- path: spec/template/spec/volumes/nfs/server
kind: Job
- path: spec/containers/args
kind: Pod
- path: spec/containers/command
kind: Pod
- path: spec/containers/env/value
kind: Pod
- path: spec/containers/volumeMounts/mountPath
kind: Pod
- path: spec/initContainers/args
kind: Pod
- path: spec/initContainers/command
kind: Pod
- path: spec/initContainers/env/value
kind: Pod
- path: spec/initContainers/volumeMounts/mountPath
kind: Pod
- path: spec/volumes/nfs/server
kind: Pod
- path: spec/template/spec/containers/args
kind: ReplicaSet
- path: spec/template/spec/containers/command
kind: ReplicaSet
- path: spec/template/spec/containers/env/value
kind: ReplicaSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: ReplicaSet
- path: spec/template/spec/initContainers/args
kind: ReplicaSet
- path: spec/template/spec/initContainers/command
kind: ReplicaSet
- path: spec/template/spec/initContainers/env/value
kind: ReplicaSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: ReplicaSet
- path: spec/template/spec/volumes/nfs/server
kind: ReplicaSet
- path: spec/ports/port
kind: Service
- path: spec/ports/targetPort
kind: Service
- path: spec/template/spec/containers/args
kind: StatefulSet
- path: spec/template/spec/containers/command
kind: StatefulSet
- path: spec/template/spec/containers/env/value
kind: StatefulSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: StatefulSet
- path: spec/template/spec/initContainers/args
kind: StatefulSet
- path: spec/template/spec/initContainers/command
kind: StatefulSet
- path: spec/template/spec/initContainers/env/value
kind: StatefulSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: StatefulSet
- path: spec/volumeClaimTemplates/spec/nfs/server
kind: StatefulSet
- path: spec/nfs/server
kind: PersistentVolume
- path: metadata/labels
- path: metadata/annotations
`
)
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package error has contextual error types.
package kusterr
import (
"fmt"
"strings"
)
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
type YamlFormatError struct {
Path string
ErrorMsg string
}
func (e YamlFormatError) Error() string {
return fmt.Sprintf("YAML file [%s] encounters a format error.\n%s\n", e.Path, e.ErrorMsg)
}
// MalformedYamlError represents an error that occurred while trying to decode a given YAML.
type MalformedYamlError struct {
Path string
ErrorMsg string
}
func (e MalformedYamlError) Error() string {
return fmt.Sprintf("%s in File: %s", e.ErrorMsg, e.Path)
}
// Handler handles YamlFormatError
func Handler(e error, path string) error {
if isYAMLSyntaxError(e) {
return YamlFormatError{
Path: path,
ErrorMsg: e.Error(),
}
}
if IsMalformedYAMLError(e) {
return MalformedYamlError{
Path: path,
ErrorMsg: e.Error(),
}
}
return e
}
func isYAMLSyntaxError(e error) bool {
return strings.Contains(e.Error(), "error converting YAML to JSON") || strings.Contains(e.Error(), "error unmarshaling JSON")
}
func IsMalformedYAMLError(e error) bool {
return strings.Contains(e.Error(), "MalformedYAMLError")
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package loader
import "sigs.k8s.io/kustomize/kyaml/errors"
var (
ErrHTTP = errors.Errorf("HTTP Error")
ErrRtNotDir = errors.Errorf("must build at directory")
)
+338
View File
@@ -0,0 +1,338 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package loader
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/git"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// IsRemoteFile returns whether path has a url scheme that kustomize allows for
// remote files. See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md
func IsRemoteFile(path string) bool {
u, err := url.Parse(path)
return err == nil && (u.Scheme == "http" || u.Scheme == "https")
}
// FileLoader is a kustomization's interface to files.
//
// The directory in which a kustomization file sits
// is referred to below as the kustomization's _root_.
//
// An instance of fileLoader has an immutable root,
// and offers a `New` method returning a new loader
// with a new root.
//
// A kustomization file refers to two kinds of files:
//
// * supplemental data paths
//
// `Load` is used to visit these paths.
//
// These paths refer to resources, patches,
// data for ConfigMaps and Secrets, etc.
//
// The loadRestrictor may disallow certain paths
// or classes of paths.
//
// * bases (other kustomizations)
//
// `New` is used to load bases.
//
// A base can be either a remote git repo URL, or
// a directory specified relative to the current
// root. In the former case, the repo is locally
// cloned, and the new loader is rooted on a path
// in that clone.
//
// As loaders create new loaders, a root history
// is established, and used to disallow:
//
// - A base that is a repository that, in turn,
// specifies a base repository seen previously
// in the loading stack (a cycle).
//
// - An overlay depending on a base positioned at
// or above it. I.e. '../foo' is OK, but '.',
// '..', '../..', etc. are disallowed. Allowing
// such a base has no advantages and encourages
// cycles, particularly if some future change
// were to introduce globbing to file
// specifications in the kustomization file.
//
// These restrictions assure that kustomizations
// are self-contained and relocatable, and impose
// some safety when relying on remote kustomizations,
// e.g. a remotely loaded ConfigMap generator specified
// to read from /etc/passwd will fail.
type FileLoader struct {
// Loader that spawned this loader.
// Used to avoid cycles.
referrer *FileLoader
// An absolute, cleaned path to a directory.
// The Load function will read non-absolute
// paths relative to this directory.
root filesys.ConfirmedDir
// Restricts behavior of Load function.
loadRestrictor LoadRestrictorFunc
// If this is non-nil, the files were
// obtained from the given repository.
repoSpec *git.RepoSpec
// File system utilities.
fSys filesys.FileSystem
// Used to load from HTTP
http *http.Client
// Used to clone repositories.
cloner git.Cloner
// Used to clean up, as needed.
cleaner func() error
}
// Repo returns the absolute path to the repo that contains Root if this fileLoader was created from a url
// or the empty string otherwise.
func (fl *FileLoader) Repo() string {
if fl.repoSpec != nil {
return fl.repoSpec.Dir.String()
}
return ""
}
// Root returns the absolute path that is prepended to any
// relative paths used in Load.
func (fl *FileLoader) Root() string {
return fl.root.String()
}
func NewLoaderOrDie(
lr LoadRestrictorFunc,
fSys filesys.FileSystem, path string) *FileLoader {
root, err := filesys.ConfirmDir(fSys, path)
if err != nil {
log.Fatalf("unable to make loader at '%s'; %v", path, err)
}
return newLoaderAtConfirmedDir(
lr, root, fSys, nil, git.ClonerUsingGitExec)
}
// newLoaderAtConfirmedDir returns a new FileLoader with given root.
func newLoaderAtConfirmedDir(
lr LoadRestrictorFunc,
root filesys.ConfirmedDir, fSys filesys.FileSystem,
referrer *FileLoader, cloner git.Cloner) *FileLoader {
return &FileLoader{
loadRestrictor: lr,
root: root,
referrer: referrer,
fSys: fSys,
cloner: cloner,
cleaner: func() error { return nil },
}
}
// New returns a new Loader, rooted relative to current loader,
// or rooted in a temp directory holding a git repo clone.
func (fl *FileLoader) New(path string) (ifc.Loader, error) {
if path == "" {
return nil, errors.Errorf("new root cannot be empty")
}
repoSpec, err := git.NewRepoSpecFromURL(path)
if err == nil {
// Treat this as git repo clone request.
if err = fl.errIfRepoCycle(repoSpec); err != nil {
return nil, err
}
return newLoaderAtGitClone(
repoSpec, fl.fSys, fl, fl.cloner)
}
if filepath.IsAbs(path) {
return nil, fmt.Errorf("new root '%s' cannot be absolute", path)
}
root, err := filesys.ConfirmDir(fl.fSys, fl.root.Join(path))
if err != nil {
return nil, errors.WrapPrefixf(err, "%s", ErrRtNotDir.Error())
}
if err = fl.errIfGitContainmentViolation(root); err != nil {
return nil, err
}
if err = fl.errIfArgEqualOrHigher(root); err != nil {
return nil, err
}
return newLoaderAtConfirmedDir(
fl.loadRestrictor, root, fl.fSys, fl, fl.cloner), nil
}
// newLoaderAtGitClone returns a new Loader pinned to a temporary
// directory holding a cloned git repo.
func newLoaderAtGitClone(
repoSpec *git.RepoSpec, fSys filesys.FileSystem,
referrer *FileLoader, cloner git.Cloner) (ifc.Loader, error) {
cleaner := repoSpec.Cleaner(fSys)
err := cloner(repoSpec)
if err != nil {
cleaner()
return nil, err
}
root, f, err := fSys.CleanedAbs(repoSpec.AbsPath())
if err != nil {
cleaner()
return nil, err
}
// We don't know that the path requested in repoSpec
// is a directory until we actually clone it and look
// inside. That just happened, hence the error check
// is here.
if f != "" {
cleaner()
return nil, fmt.Errorf(
"'%s' refers to file '%s'; expecting directory",
repoSpec.AbsPath(), f)
}
// Path in repo can contain symlinks that exit repo. We can only
// check for this after cloning repo.
if !root.HasPrefix(repoSpec.CloneDir()) {
_ = cleaner()
return nil, fmt.Errorf("%q refers to directory outside of repo %q", repoSpec.AbsPath(),
repoSpec.CloneDir())
}
return &FileLoader{
// Clones never allowed to escape root.
loadRestrictor: RestrictionRootOnly,
root: root,
referrer: referrer,
repoSpec: repoSpec,
fSys: fSys,
cloner: cloner,
cleaner: cleaner,
}, nil
}
func (fl *FileLoader) errIfGitContainmentViolation(
base filesys.ConfirmedDir) error {
containingRepo := fl.containingRepo()
if containingRepo == nil {
return nil
}
if !base.HasPrefix(containingRepo.CloneDir()) {
return fmt.Errorf(
"security; bases in kustomizations found in "+
"cloned git repos must be within the repo, "+
"but base '%s' is outside '%s'",
base, containingRepo.CloneDir())
}
return nil
}
// Looks back through referrers for a git repo, returning nil
// if none found.
func (fl *FileLoader) containingRepo() *git.RepoSpec {
if fl.repoSpec != nil {
return fl.repoSpec
}
if fl.referrer == nil {
return nil
}
return fl.referrer.containingRepo()
}
// errIfArgEqualOrHigher tests whether the argument,
// is equal to or above the root of any ancestor.
func (fl *FileLoader) errIfArgEqualOrHigher(
candidateRoot filesys.ConfirmedDir) error {
if fl.root.HasPrefix(candidateRoot) {
return fmt.Errorf(
"cycle detected: candidate root '%s' contains visited root '%s'",
candidateRoot, fl.root)
}
if fl.referrer == nil {
return nil
}
return fl.referrer.errIfArgEqualOrHigher(candidateRoot)
}
// TODO(monopole): Distinguish branches?
// I.e. Allow a distinction between git URI with
// path foo and tag bar and a git URI with the same
// path but a different tag?
func (fl *FileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error {
// TODO(monopole): Use parsed data instead of Raw().
if fl.repoSpec != nil &&
strings.HasPrefix(fl.repoSpec.Raw(), newRepoSpec.Raw()) {
return fmt.Errorf(
"cycle detected: URI '%s' referenced by previous URI '%s'",
newRepoSpec.Raw(), fl.repoSpec.Raw())
}
if fl.referrer == nil {
return nil
}
return fl.referrer.errIfRepoCycle(newRepoSpec)
}
// Load returns the content of file at the given path,
// else an error. Relative paths are taken relative
// to the root.
func (fl *FileLoader) Load(path string) ([]byte, error) {
if IsRemoteFile(path) {
return fl.httpClientGetContent(path)
}
if !filepath.IsAbs(path) {
path = fl.root.Join(path)
}
path, err := fl.loadRestrictor(fl.fSys, fl.root, path)
if err != nil {
return nil, err
}
return fl.fSys.ReadFile(path)
}
func (fl *FileLoader) httpClientGetContent(path string) ([]byte, error) {
var hc *http.Client
if fl.http != nil {
hc = fl.http
} else {
hc = &http.Client{}
}
parsedURL, err := url.ParseRequestURI(path)
if err != nil {
return nil, errors.Wrap(err)
}
resp, err := hc.Get(parsedURL.String())
if err != nil {
return nil, errors.Wrap(err)
}
defer resp.Body.Close()
// response unsuccessful
if resp.StatusCode < 200 || resp.StatusCode > 299 {
_, err = git.NewRepoSpecFromURL(path)
if err == nil {
return nil, errors.Errorf("URL is a git repository")
}
return nil, fmt.Errorf("%w: status code %d (%s)", ErrHTTP, resp.StatusCode, http.StatusText(resp.StatusCode))
}
content, err := io.ReadAll(resp.Body)
return content, errors.Wrap(err)
}
// Cleanup runs the cleaner.
func (fl *FileLoader) Cleanup() error {
return fl.cleaner()
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package loader has a data loading interface and various implementations.
package loader
import (
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/git"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// NewLoader returns a Loader pointed at the given target.
// If the target is remote, the loader will be restricted
// to the root and below only. If the target is local, the
// loader will have the restrictions passed in. Regardless,
// if a local target attempts to transitively load remote bases,
// the remote bases will all be root-only restricted.
func NewLoader(
lr LoadRestrictorFunc,
target string, fSys filesys.FileSystem) (ifc.Loader, error) {
repoSpec, err := git.NewRepoSpecFromURL(target)
if err == nil {
// The target qualifies as a remote git target.
return newLoaderAtGitClone(
repoSpec, fSys, nil, git.ClonerUsingGitExec)
}
root, err := filesys.ConfirmDir(fSys, target)
if err != nil {
return nil, errors.WrapPrefixf(err, "%s", ErrRtNotDir.Error())
}
return newLoaderAtConfirmedDir(
lr, root, fSys, nil, git.ClonerUsingGitExec), nil
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package loader
import (
"fmt"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
type LoadRestrictorFunc func(
filesys.FileSystem, filesys.ConfirmedDir, string) (string, error)
func RestrictionRootOnly(
fSys filesys.FileSystem, root filesys.ConfirmedDir, path string) (string, error) {
d, f, err := fSys.CleanedAbs(path)
if err != nil {
return "", err
}
if f == "" {
return "", fmt.Errorf("'%s' must resolve to a file", path)
}
if !d.HasPrefix(root) {
return "", fmt.Errorf(
"security; file '%s' is not in or below '%s'",
path, root)
}
return d.Join(f), nil
}
func RestrictionNone(
_ filesys.FileSystem, _ filesys.ConfirmedDir, path string) (string, error) {
return path, nil
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package builtinconfig provides legacy methods for
// configuring builtin plugins from a common config file.
// As a user, its best to configure plugins individually
// with plugin config files specified in the `transformers:`
// or `generators:` field, than to use this legacy
// configuration technique.
package builtinconfig
@@ -0,0 +1,42 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinconfig
import (
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/yaml"
)
// loadDefaultConfig returns a TranformerConfig
// object from a list of files.
func loadDefaultConfig(
ldr ifc.Loader, paths []string) (*TransformerConfig, error) {
result := &TransformerConfig{}
for _, path := range paths {
data, err := ldr.Load(path)
if err != nil {
return nil, err
}
t, err := makeTransformerConfigFromBytes(data)
if err != nil {
return nil, err
}
result, err = result.Merge(t)
if err != nil {
return nil, err
}
}
return result, nil
}
// makeTransformerConfigFromBytes returns a TransformerConfig object from bytes
func makeTransformerConfigFromBytes(data []byte) (*TransformerConfig, error) {
var t TransformerConfig
err := yaml.UnmarshalStrict(data, &t)
if err != nil {
return nil, err
}
t.sortFields()
return &t, nil
}
@@ -0,0 +1,112 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinconfig
import (
"strings"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
)
// NameBackReferences is an association between a gvk.GVK (a ReferralTarget)
// and a list of Referrers that could refer to it.
//
// It is used to handle name changes, and can be thought of as a
// a contact list. If you change your own contact info (name,
// phone number, etc.), you must tell your contacts or they won't
// know about the change.
//
// For example, ConfigMaps can be used by Pods and everything that
// contains a Pod; Deployment, Job, StatefulSet, etc.
// The ConfigMap is the ReferralTarget, the others are Referrers.
//
// If the name of a ConfigMap instance changed from 'alice' to 'bob',
// one must
// - visit all objects that could refer to the ConfigMap (the Referrers)
// - see if they mention 'alice',
// - if so, change the Referrer's name reference to 'bob'.
//
// The NameBackReferences instance to aid in this could look like
// {
// kind: ConfigMap
// version: v1
// fieldSpecs:
// - kind: Pod
// version: v1
// path: spec/volumes/configMap/name
// - kind: Deployment
// path: spec/template/spec/volumes/configMap/name
// - kind: Job
// path: spec/template/spec/volumes/configMap/name
// (etc.)
// }
type NameBackReferences struct {
resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
// TODO: rename json 'fieldSpecs' to 'referrers' for clarity.
// This will, however, break anyone using a custom config.
Referrers types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
// Note: If any new pointer based members are added, DeepCopy needs to be updated
}
func (n NameBackReferences) String() string {
var r []string
for _, f := range n.Referrers {
r = append(r, f.String())
}
return n.Gvk.String() + ": (\n" +
strings.Join(r, "\n") + "\n)"
}
type nbrSlice []NameBackReferences
func (s nbrSlice) Len() int { return len(s) }
func (s nbrSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s nbrSlice) Less(i, j int) bool {
return s[i].Gvk.IsLessThan(s[j].Gvk)
}
// DeepCopy returns a new copy of nbrSlice
func (s nbrSlice) DeepCopy() nbrSlice {
ret := make(nbrSlice, len(s))
copy(ret, s)
for i, slice := range ret {
ret[i].Referrers = slice.Referrers.DeepCopy()
}
return ret
}
func (s nbrSlice) mergeAll(o nbrSlice) (result nbrSlice, err error) {
result = s
for _, r := range o {
result, err = result.mergeOne(r)
if err != nil {
return nil, err
}
}
return result, nil
}
func (s nbrSlice) mergeOne(other NameBackReferences) (nbrSlice, error) {
var result nbrSlice
var err error
found := false
for _, c := range s {
if c.Gvk.Equals(other.Gvk) {
c.Referrers, err = c.Referrers.MergeAll(other.Referrers)
if err != nil {
return nil, err
}
found = true
}
result = append(result, c)
}
if !found {
result = append(result, other)
}
return result, nil
}
@@ -0,0 +1,202 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinconfig
import (
"log"
"sort"
"sync"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/konfig/builtinpluginconsts"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
)
// TransformerConfig holds the data needed to perform transformations.
//
//nolint:tagalign
type TransformerConfig struct {
// if any fields are added, update the DeepCopy implementation
NamePrefix types.FsSlice `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
NameSuffix types.FsSlice `json:"nameSuffix,omitempty" yaml:"nameSuffix,omitempty"`
NameSpace types.FsSlice `json:"namespace,omitempty" yaml:"namespace,omitempty"`
CommonLabels types.FsSlice `json:"commonLabels,omitempty" yaml:"commonLabels,omitempty"`
Labels types.FsSlice `json:"labels,omitempty" yaml:"labels,omitempty"`
TemplateLabels types.FsSlice `json:"templateLabels,omitempty" yaml:"templateLabels,omitempty"`
CommonAnnotations types.FsSlice `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
NameReference nbrSlice `json:"nameReference,omitempty" yaml:"nameReference,omitempty"`
VarReference types.FsSlice `json:"varReference,omitempty" yaml:"varReference,omitempty"`
Images types.FsSlice `json:"images,omitempty" yaml:"images,omitempty"`
Replicas types.FsSlice `json:"replicas,omitempty" yaml:"replicas,omitempty"`
}
// MakeEmptyConfig returns an empty TransformerConfig object
func MakeEmptyConfig() *TransformerConfig {
return &TransformerConfig{}
}
// DeepCopy returns a new copy of TransformerConfig
func (t *TransformerConfig) DeepCopy() *TransformerConfig {
return &TransformerConfig{
NamePrefix: t.NamePrefix.DeepCopy(),
NameSuffix: t.NameSuffix.DeepCopy(),
NameSpace: t.NameSpace.DeepCopy(),
CommonLabels: t.CommonLabels.DeepCopy(),
Labels: t.Labels.DeepCopy(),
TemplateLabels: t.TemplateLabels.DeepCopy(),
CommonAnnotations: t.CommonAnnotations.DeepCopy(),
NameReference: t.NameReference.DeepCopy(),
VarReference: t.VarReference.DeepCopy(),
Images: t.Images.DeepCopy(),
Replicas: t.Replicas.DeepCopy(),
}
}
// the default transformer config is initialized by MakeDefaultConfig,
// and must only be accessed via that function.
var (
initDefaultConfig sync.Once //nolint:gochecknoglobals
defaultConfig *TransformerConfig //nolint:gochecknoglobals
)
// MakeDefaultConfig returns a default TransformerConfig.
func MakeDefaultConfig() *TransformerConfig {
// parsing is expensive when having a large tree with many kustomization modules, so only do it once
initDefaultConfig.Do(func() {
var err error
defaultConfig, err = makeTransformerConfigFromBytes(
builtinpluginconsts.GetDefaultFieldSpecs())
if err != nil {
log.Fatalf("Unable to make default transformconfig: %v", err)
}
})
// return a copy to avoid any mutations to protect the reference copy
return defaultConfig.DeepCopy()
}
// MakeTransformerConfig returns a merger of custom config,
// if any, with default config.
func MakeTransformerConfig(
ldr ifc.Loader, paths []string) (*TransformerConfig, error) {
t1 := MakeDefaultConfig()
if len(paths) == 0 {
return t1, nil
}
t2, err := loadDefaultConfig(ldr, paths)
if err != nil {
return nil, err
}
return t1.Merge(t2)
}
// sortFields provides determinism in logging, tests, etc.
func (t *TransformerConfig) sortFields() {
sort.Sort(t.NamePrefix)
sort.Sort(t.NameSuffix)
sort.Sort(t.NameSpace)
sort.Sort(t.CommonLabels)
sort.Sort(t.Labels)
sort.Sort(t.TemplateLabels)
sort.Sort(t.CommonAnnotations)
sort.Sort(t.NameReference)
sort.Sort(t.VarReference)
sort.Sort(t.Images)
sort.Sort(t.Replicas)
}
// AddPrefixFieldSpec adds a FieldSpec to NamePrefix
func (t *TransformerConfig) AddPrefixFieldSpec(fs types.FieldSpec) (err error) {
t.NamePrefix, err = t.NamePrefix.MergeOne(fs)
return err
}
// AddSuffixFieldSpec adds a FieldSpec to NameSuffix
func (t *TransformerConfig) AddSuffixFieldSpec(fs types.FieldSpec) (err error) {
t.NameSuffix, err = t.NameSuffix.MergeOne(fs)
return err
}
// AddCommonLabelsFieldSpec adds a FieldSpec to CommonLabels
func (t *TransformerConfig) AddCommonLabelsFieldSpec(fs types.FieldSpec) (err error) {
t.CommonLabels, err = t.CommonLabels.MergeOne(fs)
return err
}
// AddLabelsFieldSpec adds a FieldSpec to Labels
func (t *TransformerConfig) AddLabelsFieldSpec(fs types.FieldSpec) (err error) {
t.Labels, err = t.Labels.MergeOne(fs)
return err //nolint:wrapcheck
}
// AddAnnotationFieldSpec adds a FieldSpec to CommonAnnotations
func (t *TransformerConfig) AddAnnotationFieldSpec(fs types.FieldSpec) (err error) {
t.CommonAnnotations, err = t.CommonAnnotations.MergeOne(fs)
return err
}
// AddNamereferenceFieldSpec adds a NameBackReferences to NameReference
func (t *TransformerConfig) AddNamereferenceFieldSpec(
nbrs NameBackReferences) (err error) {
t.NameReference, err = t.NameReference.mergeOne(nbrs)
return err
}
// Merge merges two TransformerConfigs objects into
// a new TransformerConfig object
func (t *TransformerConfig) Merge(input *TransformerConfig) (
merged *TransformerConfig, err error) {
if input == nil {
return t, nil
}
merged = &TransformerConfig{}
merged.NamePrefix, err = t.NamePrefix.MergeAll(input.NamePrefix)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge NamePrefix fieldSpec")
}
merged.NameSuffix, err = t.NameSuffix.MergeAll(input.NameSuffix)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge NameSuffix fieldSpec")
}
merged.NameSpace, err = t.NameSpace.MergeAll(input.NameSpace)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge NameSpace fieldSpec")
}
merged.CommonAnnotations, err = t.CommonAnnotations.MergeAll(
input.CommonAnnotations)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge CommonAnnotations fieldSpec")
}
merged.CommonLabels, err = t.CommonLabels.MergeAll(input.CommonLabels)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge CommonLabels fieldSpec")
}
merged.Labels, err = t.Labels.MergeAll(input.Labels)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge Labels fieldSpec")
}
merged.TemplateLabels, err = t.TemplateLabels.MergeAll(input.TemplateLabels)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge TemplateLabels fieldSpec")
}
merged.VarReference, err = t.VarReference.MergeAll(input.VarReference)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge VarReference fieldSpec")
}
merged.NameReference, err = t.NameReference.mergeAll(input.NameReference)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge NameReference fieldSpec")
}
merged.Images, err = t.Images.MergeAll(input.Images)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge Images fieldSpec")
}
merged.Replicas, err = t.Replicas.MergeAll(input.Replicas)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge Replicas fieldSpec")
}
merged.sortFields()
return merged, nil
}
@@ -0,0 +1,41 @@
// Code generated by "stringer -type=BuiltinPluginType"; DO NOT EDIT.
package builtinhelpers
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Unknown-0]
_ = x[AnnotationsTransformer-1]
_ = x[ConfigMapGenerator-2]
_ = x[IAMPolicyGenerator-3]
_ = x[HashTransformer-4]
_ = x[ImageTagTransformer-5]
_ = x[LabelTransformer-6]
_ = x[NamespaceTransformer-7]
_ = x[PatchJson6902Transformer-8]
_ = x[PatchStrategicMergeTransformer-9]
_ = x[PatchTransformer-10]
_ = x[PrefixSuffixTransformer-11]
_ = x[PrefixTransformer-12]
_ = x[SuffixTransformer-13]
_ = x[ReplicaCountTransformer-14]
_ = x[SecretGenerator-15]
_ = x[ValueAddTransformer-16]
_ = x[HelmChartInflationGenerator-17]
_ = x[ReplacementTransformer-18]
}
const _BuiltinPluginType_name = "UnknownAnnotationsTransformerConfigMapGeneratorIAMPolicyGeneratorHashTransformerImageTagTransformerLabelTransformerNamespaceTransformerPatchJson6902TransformerPatchStrategicMergeTransformerPatchTransformerPrefixSuffixTransformerPrefixTransformerSuffixTransformerReplicaCountTransformerSecretGeneratorValueAddTransformerHelmChartInflationGeneratorReplacementTransformer"
var _BuiltinPluginType_index = [...]uint16{0, 7, 29, 47, 65, 80, 99, 115, 135, 159, 189, 205, 228, 245, 262, 285, 300, 319, 346, 368}
func (i BuiltinPluginType) String() string {
if i < 0 || i >= BuiltinPluginType(len(_BuiltinPluginType_index)-1) {
return "BuiltinPluginType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BuiltinPluginType_name[_BuiltinPluginType_index[i]:_BuiltinPluginType_index[i+1]]
}
@@ -0,0 +1,115 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package builtinhelpers
import (
"sigs.k8s.io/kustomize/api/internal/builtins"
"sigs.k8s.io/kustomize/api/resmap"
)
//go:generate stringer -type=BuiltinPluginType
type BuiltinPluginType int
const (
Unknown BuiltinPluginType = iota
AnnotationsTransformer
ConfigMapGenerator
IAMPolicyGenerator
HashTransformer
ImageTagTransformer
LabelTransformer
NamespaceTransformer
PatchJson6902Transformer
PatchStrategicMergeTransformer
PatchTransformer
PrefixSuffixTransformer
PrefixTransformer
SuffixTransformer
ReplicaCountTransformer
SecretGenerator
ValueAddTransformer
HelmChartInflationGenerator
ReplacementTransformer
)
var stringToBuiltinPluginTypeMap map[string]BuiltinPluginType
func init() { //nolint:gochecknoinits
stringToBuiltinPluginTypeMap = makeStringToBuiltinPluginTypeMap()
}
func makeStringToBuiltinPluginTypeMap() (result map[string]BuiltinPluginType) {
result = make(map[string]BuiltinPluginType, 23)
for k := range GeneratorFactories {
result[k.String()] = k
}
for k := range TransformerFactories {
result[k.String()] = k
}
return
}
func GetBuiltinPluginType(n string) BuiltinPluginType {
result, ok := stringToBuiltinPluginTypeMap[n]
if ok {
return result
}
return Unknown
}
var GeneratorFactories = map[BuiltinPluginType]func() resmap.GeneratorPlugin{
ConfigMapGenerator: builtins.NewConfigMapGeneratorPlugin,
IAMPolicyGenerator: builtins.NewIAMPolicyGeneratorPlugin,
SecretGenerator: builtins.NewSecretGeneratorPlugin,
HelmChartInflationGenerator: builtins.NewHelmChartInflationGeneratorPlugin,
}
type MultiTransformer struct {
transformers []resmap.TransformerPlugin
}
func (t *MultiTransformer) Transform(m resmap.ResMap) error {
for _, transformer := range t.transformers {
if err := transformer.Transform(m); err != nil {
return err
}
}
return nil
}
func (t *MultiTransformer) Config(h *resmap.PluginHelpers, b []byte) error {
for _, transformer := range t.transformers {
if err := transformer.Config(h, b); err != nil {
return err
}
}
return nil
}
func NewMultiTransformer() resmap.TransformerPlugin {
return &MultiTransformer{[]resmap.TransformerPlugin{
builtins.NewPrefixTransformerPlugin(),
builtins.NewSuffixTransformerPlugin(),
}}
}
var TransformerFactories = map[BuiltinPluginType]func() resmap.TransformerPlugin{
AnnotationsTransformer: builtins.NewAnnotationsTransformerPlugin,
HashTransformer: builtins.NewHashTransformerPlugin,
ImageTagTransformer: builtins.NewImageTagTransformerPlugin,
LabelTransformer: builtins.NewLabelTransformerPlugin,
NamespaceTransformer: builtins.NewNamespaceTransformerPlugin,
PatchJson6902Transformer: builtins.NewPatchJson6902TransformerPlugin,
PatchStrategicMergeTransformer: builtins.NewPatchStrategicMergeTransformerPlugin,
PatchTransformer: builtins.NewPatchTransformerPlugin,
PrefixSuffixTransformer: NewMultiTransformer,
PrefixTransformer: builtins.NewPrefixTransformerPlugin,
SuffixTransformer: builtins.NewSuffixTransformerPlugin,
ReplacementTransformer: builtins.NewReplacementTransformerPlugin,
ReplicaCountTransformer: builtins.NewReplicaCountTransformerPlugin,
ValueAddTransformer: builtins.NewValueAddTransformerPlugin,
// Do not wired SortOrderTransformer as a builtin plugin.
// We only want it to be available in the top-level kustomization.
// See: https://github.com/kubernetes-sigs/kustomize/issues/3913
}
@@ -0,0 +1,206 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package execplugin
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"runtime"
"strings"
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/yaml"
)
const (
tmpConfigFilePrefix = "kust-plugin-config-"
maxArgStringLength = 131071
)
// ExecPlugin record the name and args of an executable
// It triggers the executable generator and transformer
type ExecPlugin struct {
// absolute path of the executable
path string
// Optional command line arguments to the executable
// pulled from specially named fields in cfg.
// This is for executables that don't want to parse YAML.
args []string
// Plugin configuration data.
cfg []byte
// PluginHelpers
h *resmap.PluginHelpers
}
func NewExecPlugin(p string) *ExecPlugin {
return &ExecPlugin{path: p}
}
func (p *ExecPlugin) ErrIfNotExecutable() error {
f, err := os.Stat(p.path)
if err != nil {
return err
}
// In Windows, it is not possible to determine whether a
// file is executable through file mode.
// TODO: provide for setting the executable FileMode bit on Windows
// The (fs *fileStat) Mode() (m FileMode) {} function in
// https://golang.org/src/os/types_windows.go
// lacks the ability to set the FileMode executable bit in response
// to file data on Windows.
if f.Mode()&0111 == 0000 && runtime.GOOS != "windows" {
return fmt.Errorf("unexecutable plugin at: %s", p.path)
}
return nil
}
func (p *ExecPlugin) Path() string {
return p.path
}
func (p *ExecPlugin) Args() []string {
return p.args
}
func (p *ExecPlugin) Cfg() []byte {
return p.cfg
}
func (p *ExecPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
p.h = h
p.cfg = config
return p.processOptionalArgsFields()
}
type argsConfig struct {
ArgsOneLiner string `json:"argsOneLiner,omitempty" yaml:"argsOneLiner,omitempty"`
ArgsFromFile string `json:"argsFromFile,omitempty" yaml:"argsFromFile,omitempty"`
}
func (p *ExecPlugin) processOptionalArgsFields() error {
var c argsConfig
err := yaml.Unmarshal(p.cfg, &c)
if err != nil {
return err
}
if c.ArgsOneLiner != "" {
argsTolenSlice, err := ShlexSplit(c.ArgsOneLiner)
if err != nil {
return fmt.Errorf("failed to parse argsOneLiner: %w", err)
}
p.args = argsTolenSlice
}
if c.ArgsFromFile != "" {
content, err := p.h.Loader().Load(c.ArgsFromFile)
if err != nil {
return err
}
for _, x := range strings.Split(string(content), "\n") {
x := strings.TrimLeft(x, " ")
if x != "" {
p.args = append(p.args, x)
}
}
}
return nil
}
func (p *ExecPlugin) Generate() (resmap.ResMap, error) {
output, err := p.invokePlugin(nil)
if err != nil {
return nil, err
}
rm, err := p.h.ResmapFactory().NewResMapFromBytes(output)
if err != nil {
return nil, err
}
return utils.UpdateResourceOptions(rm)
}
func (p *ExecPlugin) Transform(rm resmap.ResMap) error {
// add ResIds as annotations to all objects so that we can add them back
inputRM, err := utils.GetResMapWithIDAnnotation(rm)
if err != nil {
return err
}
// encode the ResMap so it can be fed to the plugin
resources, err := inputRM.AsYaml()
if err != nil {
return err
}
// invoke the plugin with resources as the input
output, err := p.invokePlugin(resources)
if err != nil {
return fmt.Errorf("%v %s", err, string(output))
}
// update the original ResMap based on the output
return utils.UpdateResMapValues(p.path, p.h, output, rm)
}
// invokePlugin writes plugin config to a temp file, then
// passes the full temp file path as the first arg to a process
// running the plugin binary. Process output is returned.
func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) {
f, err := os.CreateTemp("", tmpConfigFilePrefix)
if err != nil {
return nil, errors.WrapPrefixf(
err, "creating tmp plugin config file")
}
_, err = f.Write(p.cfg)
if err != nil {
return nil, errors.WrapPrefixf(
err, "writing plugin config to %s", f.Name())
}
err = f.Close()
if err != nil {
return nil, errors.WrapPrefixf(
err, "closing plugin config file %s", f.Name())
}
cmd := exec.Command(
p.path, append([]string{f.Name()}, p.args...)...)
cmd.Env = p.getEnv()
cmd.Stdin = bytes.NewReader(input)
var stdErr bytes.Buffer
cmd.Stderr = &stdErr
if _, err := os.Stat(p.h.Loader().Root()); err == nil {
cmd.Dir = p.h.Loader().Root()
}
result, err := cmd.Output()
if err != nil {
return nil, errors.WrapPrefixf(
fmt.Errorf("failure in plugin configured via %s; %w", f.Name(), err),
"%s", stdErr.String())
}
return result, os.Remove(f.Name())
}
func (p *ExecPlugin) getEnv() []string {
env := os.Environ()
pluginConfigString := "KUSTOMIZE_PLUGIN_CONFIG_STRING=" + string(p.cfg)
if len(pluginConfigString) <= maxArgStringLength {
env = append(env, pluginConfigString)
} else {
log.Printf("KUSTOMIZE_PLUGIN_CONFIG_STRING exceeds hard limit of %d characters, the environment variable "+
"will be omitted", maxArgStringLength)
}
pluginConfigRoot := "KUSTOMIZE_PLUGIN_CONFIG_ROOT=" + p.h.Loader().Root()
if len(pluginConfigRoot) <= maxArgStringLength {
env = append(env, pluginConfigRoot)
} else {
log.Printf("KUSTOMIZE_PLUGIN_CONFIG_ROOT exceeds hard limit of %d characters, the environment variable "+
"will be omitted", maxArgStringLength)
}
return env
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package execplugin
import (
"fmt"
"strings"
"unicode"
)
// ShlexSplit splits a string into a slice of strings using shell-style rules for quoting and commenting
// Similar to Python's shlex.split with comments enabled
func ShlexSplit(s string) ([]string, error) {
return shlexSplit(s)
}
func shlexSplit(s string) ([]string, error) {
result := []string{}
// noQuote is used to track if we are not in a quoted
const noQuote = 0
var current strings.Builder
var quote rune = noQuote
var escaped bool
for _, r := range s {
switch {
case escaped:
current.WriteRune(r)
escaped = false
case r == '\\' && quote != '\'':
escaped = true
case (r == '\'' || r == '"') && quote == noQuote:
quote = r
case r == quote:
quote = noQuote
case r == '#' && quote == noQuote:
// Comment starts, ignore the rest of the line
if current.Len() > 0 {
result = append(result, current.String())
}
return result, nil
case unicode.IsSpace(r) && quote == noQuote:
if current.Len() > 0 {
result = append(result, current.String())
current.Reset()
}
default:
current.WriteRune(r)
}
}
if quote != noQuote {
return nil, fmt.Errorf("unclosed quote in string")
}
if current.Len() > 0 {
result = append(result, current.String())
}
return result, nil
}
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package fnplugin
import (
"bytes"
"fmt"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
"sigs.k8s.io/kustomize/kyaml/runfn"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// FnPlugin is the struct to hold function information
type FnPlugin struct {
// Function runner
runFns runfn.RunFns
// Plugin configuration data.
cfg []byte
// Plugin name cache for error output
pluginName string
// PluginHelpers
h *resmap.PluginHelpers
}
func bytesToRNode(yml []byte) (*yaml.RNode, error) {
rnode, err := yaml.Parse(string(yml))
if err != nil {
return nil, err
}
return rnode, nil
}
func resourceToRNode(res *resource.Resource) (*yaml.RNode, error) {
yml, err := res.AsYAML()
if err != nil {
return nil, err
}
return bytesToRNode(yml)
}
// GetFunctionSpec return function spec is there is. Otherwise return nil
func GetFunctionSpec(res *resource.Resource) (*runtimeutil.FunctionSpec, error) {
rnode, err := resourceToRNode(res)
if err != nil {
return nil, fmt.Errorf("could not convert resource to RNode: %w", err)
}
functionSpec, err := runtimeutil.GetFunctionSpec(rnode)
if err != nil {
return nil, fmt.Errorf("failed to get FunctionSpec: %w", err)
}
return functionSpec, nil
}
func toStorageMounts(mounts []string) []runtimeutil.StorageMount {
var sms []runtimeutil.StorageMount
for _, mount := range mounts {
sms = append(sms, runtimeutil.StringToStorageMount(mount))
}
return sms
}
// NewFnPlugin creates a FnPlugin struct
func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin {
return &FnPlugin{
runFns: runfn.RunFns{
Functions: []*yaml.RNode{},
Network: o.Network,
EnableExec: o.EnableExec,
StorageMounts: toStorageMounts(o.Mounts),
Env: o.Env,
AsCurrentUser: o.AsCurrentUser,
WorkingDir: o.WorkingDir,
},
}
}
// Cfg returns function config
func (p *FnPlugin) Cfg() []byte {
return p.cfg
}
// Config is called by kustomize to pass-in config information
func (p *FnPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
p.h = h
p.cfg = config
fn, err := bytesToRNode(p.cfg)
if err != nil {
return err
}
meta, err := fn.GetMeta()
if err != nil {
return err
}
p.pluginName = fmt.Sprintf("api: %s, kind: %s, name: %s",
meta.APIVersion, meta.Kind, meta.Name)
return nil
}
// Generate is called when run as generator
func (p *FnPlugin) Generate() (resmap.ResMap, error) {
output, err := p.invokePlugin(nil)
if err != nil {
return nil, err
}
rm, err := p.h.ResmapFactory().NewResMapFromBytes(output)
if err != nil {
return nil, err
}
return utils.UpdateResourceOptions(rm)
}
// Transform is called when run as transformer
func (p *FnPlugin) Transform(rm resmap.ResMap) error {
// add ResIds as annotations to all objects so that we can add them back
inputRM, err := utils.GetResMapWithIDAnnotation(rm)
if err != nil {
return err
}
// encode the ResMap so it can be fed to the plugin
resources, err := inputRM.AsYaml()
if err != nil {
return err
}
// invoke the plugin with resources as the input
output, err := p.invokePlugin(resources)
if err != nil {
return fmt.Errorf("%v %s", err, string(output))
}
// update the original ResMap based on the output
return utils.UpdateResMapValues(p.pluginName, p.h, output, rm)
}
func injectAnnotation(input *yaml.RNode, k, v string) error {
err := input.PipeE(yaml.SetAnnotation(k, v))
if err != nil {
return err
}
return nil
}
// invokePlugin uses Function runner to run function as plugin
func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) {
// get function config rnode
functionConfig, err := bytesToRNode(p.cfg)
if err != nil {
return nil, err
}
// This annotation will let kustomize ingnore this item in output
err = injectAnnotation(functionConfig, "config.kubernetes.io/local-config", "true")
if err != nil {
return nil, err
}
// we need to add config as input for generators. Some of them don't work with FunctionConfig
// and in addition kio.Pipeline won't create anything if there are no objects
// see https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/kio/kio.go#L93
// Since we added `local-config` annotation so it will be ignored in generator output
// TODO(donnyxia): This is actually not used by generator and only used to bypass a kio limitation.
// Need better solution.
if input == nil {
yml, err := functionConfig.String()
if err != nil {
return nil, err
}
input = []byte(yml)
}
// Configure and Execute Fn. We don't need to convert resources to ResourceList here
// because function runtime will do that. See kyaml/fn/runtime/runtimeutil/runtimeutil.go
var ouputBuffer bytes.Buffer
p.runFns.Input = bytes.NewReader(input)
p.runFns.Functions = append(p.runFns.Functions, functionConfig)
p.runFns.Output = &ouputBuffer
err = p.runFns.Execute()
if err != nil {
return nil, errors.WrapPrefixf(
err, "couldn't execute function")
}
return ouputBuffer.Bytes(), nil
}
@@ -0,0 +1,62 @@
// Copyright 2024 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//go:build !kustomize_disable_go_plugin_support
package loader
import (
"fmt"
"log"
"plugin"
"reflect"
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/resid"
)
// registry is a means to avoid trying to load the same .so file
// into memory more than once, which results in an error.
// Each test makes its own loader, and tries to load its own plugins,
// but the loaded .so files are in shared memory, so one will get
// "this plugin already loaded" errors if the registry is maintained
// as a Loader instance variable. So make it a package variable.
var registry = make(map[string]resmap.Configurable) //nolint:gochecknoglobals
func copyPlugin(c resmap.Configurable) resmap.Configurable {
indirect := reflect.Indirect(reflect.ValueOf(c))
newIndirect := reflect.New(indirect.Type())
newIndirect.Elem().Set(reflect.ValueOf(indirect.Interface()))
newNamed := newIndirect.Interface()
return newNamed.(resmap.Configurable) //nolint:forcetypeassert
}
func (l *Loader) loadGoPlugin(id resid.ResId, absPath string) (resmap.Configurable, error) {
regId := relativePluginPath(id)
if c, ok := registry[regId]; ok {
return copyPlugin(c), nil
}
if !utils.FileExists(absPath) {
return nil, fmt.Errorf(
"expected file with Go object code at: %s", absPath)
}
log.Printf("Attempting plugin load from %q", absPath)
p, err := plugin.Open(absPath)
if err != nil {
return nil, errors.WrapPrefixf(err, "plugin %s fails to load", absPath)
}
symbol, err := p.Lookup(konfig.PluginSymbol)
if err != nil {
return nil, errors.WrapPrefixf(
err, "plugin %s doesn't have symbol %s",
regId, konfig.PluginSymbol)
}
c, ok := symbol.(resmap.Configurable)
if !ok {
return nil, fmt.Errorf("plugin %q not configurable", regId)
}
registry[regId] = c
return copyPlugin(c), nil
}
@@ -0,0 +1,25 @@
// Copyright 2024 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// The build tag "kustomize_disable_go_plugin_support" is used to deactivate the
// kustomize API's dependency on the "plugins" package. This is beneficial for
// applications that need to embed it but do not have requirements for dynamic
// Go plugins.
// Including plugins as a dependency can lead to an increase in binary size due
// to the population of ELF's sections such as .dynsym and .dynstr.
// By utilizing this flag, applications have the flexibility to exclude the
// import if they do not require support for dynamic Go plugins.
//go:build kustomize_disable_go_plugin_support
package loader
import (
"fmt"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/kyaml/resid"
)
func (l *Loader) loadGoPlugin(_ resid.ResId, _ string) (resmap.Configurable, error) {
return nil, fmt.Errorf("plugin load is disabled")
}
+290
View File
@@ -0,0 +1,290 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package loader
import (
"fmt"
"os"
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
"sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
"sigs.k8s.io/kustomize/api/internal/plugins/fnplugin"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/resid"
)
// Loader loads plugins using a file loader (a different loader).
type Loader struct {
pc *types.PluginConfig
rf *resmap.Factory
fs filesys.FileSystem
// absolutePluginHome caches the location of a valid plugin root directory.
// It should only be set once the directory's existence has been confirmed.
absolutePluginHome string
}
func NewLoader(
pc *types.PluginConfig, rf *resmap.Factory, fs filesys.FileSystem,
) *Loader {
return &Loader{pc: pc, rf: rf, fs: fs}
}
// LoaderWithWorkingDir returns loader after setting its working directory.
// NOTE: This is not really a new loader since some of the Loader struct fields are pointers.
func (l *Loader) LoaderWithWorkingDir(wd string) *Loader {
lpc := &types.PluginConfig{
PluginRestrictions: l.pc.PluginRestrictions,
BpLoadingOptions: l.pc.BpLoadingOptions,
FnpLoadingOptions: l.pc.FnpLoadingOptions,
HelmConfig: l.pc.HelmConfig,
}
lpc.FnpLoadingOptions.WorkingDir = wd
return &Loader{pc: lpc, rf: l.rf, fs: l.fs}
}
// Config provides the global (not plugin specific) PluginConfig data.
func (l *Loader) Config() *types.PluginConfig {
return l.pc
}
func (l *Loader) LoadGenerators(
ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap) (
result []*resmap.GeneratorWithProperties, err error,
) {
for _, res := range rm.Resources() {
g, err := l.LoadGenerator(ldr, v, res)
if err != nil {
return nil, fmt.Errorf("failed to load generator: %w", err)
}
generatorOrigin, err := resource.OriginFromCustomPlugin(res)
if err != nil {
return nil, fmt.Errorf("failed to get origin from CustomPlugin: %w", err)
}
result = append(result, &resmap.GeneratorWithProperties{Generator: g, Origin: generatorOrigin})
}
return result, nil
}
func (l *Loader) LoadGenerator(
ldr ifc.Loader, v ifc.Validator, res *resource.Resource,
) (resmap.Generator, error) {
c, err := l.loadAndConfigurePlugin(ldr, v, res)
if err != nil {
return nil, err
}
g, ok := c.(resmap.Generator)
if !ok {
return nil, fmt.Errorf("plugin %s not a generator", res.OrgId())
}
return g, nil
}
func (l *Loader) LoadTransformers(
ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap,
) ([]*resmap.TransformerWithProperties, error) {
var result []*resmap.TransformerWithProperties
for _, res := range rm.Resources() {
t, err := l.LoadTransformer(ldr, v, res)
if err != nil {
return nil, err
}
transformerOrigin, err := resource.OriginFromCustomPlugin(res)
if err != nil {
return nil, err
}
result = append(result, &resmap.TransformerWithProperties{Transformer: t, Origin: transformerOrigin})
}
return result, nil
}
func (l *Loader) LoadTransformer(
ldr ifc.Loader, v ifc.Validator, res *resource.Resource,
) (*resmap.TransformerWithProperties, error) {
c, err := l.loadAndConfigurePlugin(ldr, v, res)
if err != nil {
return nil, err
}
t, ok := c.(resmap.Transformer)
if !ok {
return nil, fmt.Errorf("plugin %s not a transformer", res.OrgId())
}
return &resmap.TransformerWithProperties{Transformer: t}, nil
}
func relativePluginPath(id resid.ResId) string {
return filepath.Join(
id.Group,
id.Version,
strings.ToLower(id.Kind))
}
func (l *Loader) AbsolutePluginPath(id resid.ResId) (string, error) {
pluginHome, err := l.absPluginHome()
if err != nil {
return "", err
}
return filepath.Join(pluginHome, relativePluginPath(id), id.Kind), nil
}
// absPluginHome is the home of kustomize Exec and Go plugins.
// Kustomize plugin configuration files are k8s-style objects
// containing the fields 'apiVersion' and 'kind', e.g.
//
// apiVersion: apps/v1
// kind: Deployment
//
// kustomize reads plugin configuration data from a file path
// specified in the 'generators:' or 'transformers:' field of a
// kustomization file. For Exec and Go plugins, kustomize
// uses this data to both locate the plugin and configure it.
// Each Exec or Go plugin (its code, its tests, its supporting data
// files, etc.) must be housed in its own directory at
//
// ${absPluginHome}/${pluginApiVersion}/LOWERCASE(${pluginKind})
//
// where
// - ${absPluginHome} is an absolute path, defined below.
// - ${pluginApiVersion} is taken from the plugin config file.
// - ${pluginKind} is taken from the plugin config file.
func (l *Loader) absPluginHome() (string, error) {
// External plugins are disabled--return the dummy plugin root.
if l.pc.PluginRestrictions != types.PluginRestrictionsNone {
return konfig.NoPluginHomeSentinal, nil
}
// We've already determined plugin home--use the cached value.
if l.absolutePluginHome != "" {
return l.absolutePluginHome, nil
}
// Check default locations for a valid plugin root, and cache it if found.
dir, err := konfig.DefaultAbsPluginHome(l.fs)
if err != nil {
return "", err
}
l.absolutePluginHome = dir
return l.absolutePluginHome, nil
}
func isBuiltinPlugin(res *resource.Resource) bool {
// TODO: the special string should appear in Group, not Version.
return res.GetGvk().Group == "" &&
res.GetGvk().Version == konfig.BuiltinPluginApiVersion
}
func (l *Loader) loadAndConfigurePlugin(
ldr ifc.Loader,
v ifc.Validator,
res *resource.Resource,
) (c resmap.Configurable, err error) {
if isBuiltinPlugin(res) {
switch l.pc.BpLoadingOptions {
case types.BploLoadFromFileSys:
c, err = l.loadPlugin(res)
case types.BploUseStaticallyLinked:
// Instead of looking for and loading a .so file,
// instantiate the plugin from a generated factory
// function (see "pluginator"). Being able to do this
// is what makes a plugin "builtin".
c, err = l.makeBuiltinPlugin(res.GetGvk())
default:
err = fmt.Errorf(
"unknown plugin loader behavior specified: %s %v", res.GetGvk().String(),
l.pc.BpLoadingOptions)
}
} else {
switch l.pc.PluginRestrictions {
case types.PluginRestrictionsNone:
c, err = l.loadPlugin(res)
case types.PluginRestrictionsBuiltinsOnly:
err = types.NewErrOnlyBuiltinPluginsAllowed(res.OrgId().Kind)
default:
err = fmt.Errorf(
"unknown plugin restriction specified: %v",
l.pc.PluginRestrictions)
}
}
if err != nil {
return nil, err
}
yaml, err := res.AsYAML()
if err != nil {
return nil, errors.WrapPrefixf(err, "marshalling yaml from res %s", res.OrgId())
}
err = c.Config(resmap.NewPluginHelpers(ldr, v, l.rf, l.pc), yaml)
if err != nil {
return nil, errors.WrapPrefixf(
err, "plugin %s fails configuration", res.OrgId())
}
return c, nil
}
func (l *Loader) makeBuiltinPlugin(r resid.Gvk) (resmap.Configurable, error) {
bpt := builtinhelpers.GetBuiltinPluginType(r.Kind)
if f, ok := builtinhelpers.GeneratorFactories[bpt]; ok {
return f(), nil
}
if f, ok := builtinhelpers.TransformerFactories[bpt]; ok {
return f(), nil
}
return nil, errors.Errorf("unable to load builtin %s", r)
}
func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error) {
spec, err := fnplugin.GetFunctionSpec(res)
if err != nil {
return nil, fmt.Errorf("loader: %w", err)
}
if spec != nil {
// validation check that function mounts are under the current kustomization directory
for _, mount := range spec.Container.StorageMounts {
if filepath.IsAbs(mount.Src) {
return nil, errors.Errorf("plugin %s with mount path '%s' is not permitted; "+
"mount paths must be relative to the current kustomization directory", res.OrgId(), mount.Src)
}
if strings.HasPrefix(filepath.Clean(mount.Src), "..") {
return nil, errors.Errorf("plugin %s with mount path '%s' is not permitted; "+
"mount paths must be under the current kustomization directory", res.OrgId(), mount.Src)
}
}
return fnplugin.NewFnPlugin(&l.pc.FnpLoadingOptions), nil
}
return l.loadExecOrGoPlugin(res.OrgId())
}
func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, error) {
absPluginPath, err := l.AbsolutePluginPath(resId)
if err != nil {
return nil, err
}
// First try to load the plugin as an executable.
p := execplugin.NewExecPlugin(absPluginPath)
if err = p.ErrIfNotExecutable(); err == nil {
return p, nil
}
if !os.IsNotExist(err) {
// The file exists, but something else is wrong,
// likely it's not executable.
// Assume the user forgot to set the exec bit,
// and return an error, rather than adding ".so"
// to the name and attempting to load it as a Go
// plugin, which will likely fail and result
// in an obscure message.
return nil, err
}
// Failing the above, try loading it as a Go plugin.
c, err := l.loadGoPlugin(resId, absPluginPath+".so")
if err != nil {
return nil, err
}
return c, nil
}
+240
View File
@@ -0,0 +1,240 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"time"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/yaml"
)
const (
idAnnotation = "kustomize.config.k8s.io/id"
HashAnnotation = "kustomize.config.k8s.io/needs-hash"
BehaviorAnnotation = "kustomize.config.k8s.io/behavior"
)
func GoBin() string {
return filepath.Join(runtime.GOROOT(), "bin", "go")
}
// DeterminePluginSrcRoot guesses where the user
// has her ${g}/${v}/$lower(${k})/${k}.go files.
func DeterminePluginSrcRoot(fSys filesys.FileSystem) (string, error) {
return konfig.FirstDirThatExistsElseError(
"plugin src root", fSys, []konfig.NotedFunc{
{
Note: "relative to unit test",
F: func() string {
return filepath.Clean(
filepath.Join(
os.Getenv("PWD"),
"..", "..",
konfig.RelPluginHome))
},
},
{
Note: "relative to unit test (internal pkg)",
F: func() string {
return filepath.Clean(
filepath.Join(
os.Getenv("PWD"),
"..", "..", "..", "..",
konfig.RelPluginHome))
},
},
{
Note: "relative to api package",
F: func() string {
return filepath.Clean(
filepath.Join(
os.Getenv("PWD"),
"..", "..", "..",
konfig.RelPluginHome))
},
},
{
Note: "old style $GOPATH",
F: func() string {
return filepath.Join(
os.Getenv("GOPATH"),
"src", konfig.DomainName,
konfig.ProgramName, konfig.RelPluginHome)
},
},
{
Note: "HOME with literal 'gopath'",
F: func() string {
return filepath.Join(
konfig.HomeDir(), "gopath",
"src", konfig.DomainName,
konfig.ProgramName, konfig.RelPluginHome)
},
},
{
Note: "home directory",
F: func() string {
return filepath.Join(
konfig.HomeDir(), konfig.DomainName,
konfig.ProgramName, konfig.RelPluginHome)
},
},
})
}
// FileYoungerThan returns true if the file both exists and has an
// age is <= the Duration argument.
func FileYoungerThan(path string, d time.Duration) bool {
fi, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false
}
}
return time.Since(fi.ModTime()) <= d
}
// FileModifiedAfter returns true if the file both exists and was
// modified after the given time..
func FileModifiedAfter(path string, t time.Time) bool {
fi, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false
}
}
return fi.ModTime().After(t)
}
func FileExists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// GetResMapWithIDAnnotation returns a new copy of the given ResMap with the ResIds annotated in each Resource
func GetResMapWithIDAnnotation(rm resmap.ResMap) (resmap.ResMap, error) {
inputRM := rm.DeepCopy()
for _, r := range inputRM.Resources() {
idString, err := yaml.Marshal(r.CurId())
if err != nil {
return nil, err
}
annotations := r.GetAnnotations()
annotations[idAnnotation] = string(idString)
if err = r.SetAnnotations(annotations); err != nil {
return nil, err
}
}
return inputRM, nil
}
// UpdateResMapValues updates the Resource value in the given ResMap
// with the emitted Resource values in output.
func UpdateResMapValues(pluginName string, h *resmap.PluginHelpers, output []byte, rm resmap.ResMap) error {
mapFactory := h.ResmapFactory()
resFactory := mapFactory.RF()
resources, err := resFactory.SliceFromBytes(output)
if err != nil {
return err
}
// Don't use resources here, or error message will be unfriendly to plugin builders
newMap, err := mapFactory.NewResMapFromBytes([]byte{})
if err != nil {
return err
}
for _, r := range resources {
// stale--not manipulated by plugin transformers
if err = removeIDAnnotation(r); err != nil {
return err
}
// Add to the new map, checking for duplicates
if err := newMap.Append(r); err != nil {
prettyID, err := json.Marshal(r.CurId())
if err != nil {
prettyID = []byte(r.CurId().String())
}
return fmt.Errorf("plugin %s generated duplicate resource: %s", pluginName, prettyID)
}
// Add to or update the old map
oldIdx, err := rm.GetIndexOfCurrentId(r.CurId())
if err != nil {
return err
}
if oldIdx != -1 {
rm.GetByIndex(oldIdx).ResetRNode(r)
} else {
if err := rm.Append(r); err != nil {
return err
}
}
}
// Remove items the transformer deleted from the old map
for _, id := range rm.AllIds() {
newIdx, _ := newMap.GetIndexOfCurrentId(id)
if newIdx == -1 {
if err = rm.Remove(id); err != nil {
return err
}
}
}
return nil
}
func removeIDAnnotation(r *resource.Resource) error {
// remove the annotation set by Kustomize to track the resource
annotations := r.GetAnnotations()
delete(annotations, idAnnotation)
return r.SetAnnotations(annotations)
}
// UpdateResourceOptions updates the generator options for each resource in the
// given ResMap based on plugin provided annotations.
func UpdateResourceOptions(rm resmap.ResMap) (resmap.ResMap, error) {
for _, r := range rm.Resources() {
// Disable name hashing by default and require plugin to explicitly
// request it for each resource.
annotations := r.GetAnnotations()
behavior := annotations[BehaviorAnnotation]
var needsHash bool
if val, ok := annotations[HashAnnotation]; ok {
b, err := strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf(
"the annotation %q contains an invalid value (%q)",
HashAnnotation, val)
}
needsHash = b
}
delete(annotations, HashAnnotation)
delete(annotations, BehaviorAnnotation)
if err := r.SetAnnotations(annotations); err != nil {
return nil, err
}
if needsHash {
r.EnableHashSuffix()
}
r.SetBehavior(types.NewGenerationBehavior(behavior))
}
return rm, nil
}
@@ -0,0 +1,44 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package target
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/kyaml/errors"
)
type errMissingKustomization struct {
path string
}
func (e *errMissingKustomization) Error() string {
return fmt.Sprintf(
"unable to find one of %v in directory '%s'",
commaOr(quoted(konfig.RecognizedKustomizationFileNames())),
e.path)
}
func IsMissingKustomizationFileError(err error) bool {
e := &errMissingKustomization{}
return errors.As(err, &e)
}
func NewErrMissingKustomization(p string) *errMissingKustomization {
return &errMissingKustomization{path: p}
}
func quoted(l []string) []string {
r := make([]string, len(l))
for i, v := range l {
r[i] = "'" + v + "'"
}
return r
}
func commaOr(q []string) string {
return strings.Join(q[:len(q)-1], ", ") + " or " + q[len(q)-1]
}
+593
View File
@@ -0,0 +1,593 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package target
import (
"encoding/json"
"fmt"
"os"
"strings"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/accumulator"
"sigs.k8s.io/kustomize/api/internal/builtins"
"sigs.k8s.io/kustomize/api/internal/kusterr"
load "sigs.k8s.io/kustomize/api/internal/loader"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
"sigs.k8s.io/kustomize/api/internal/plugins/loader"
"sigs.k8s.io/kustomize/api/internal/utils"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/yaml"
)
// KustTarget encapsulates the entirety of a kustomization build.
type KustTarget struct {
kustomization *types.Kustomization
kustFileName string
ldr ifc.Loader
validator ifc.Validator
rFactory *resmap.Factory
pLdr *loader.Loader
origin *resource.Origin
helmRootNamespace string // namespace inherited from parent kustomization for HelmCharts
}
// NewKustTarget returns a new instance of KustTarget.
func NewKustTarget(
ldr ifc.Loader,
validator ifc.Validator,
rFactory *resmap.Factory,
pLdr *loader.Loader) *KustTarget {
return &KustTarget{
ldr: ldr,
validator: validator,
rFactory: rFactory,
pLdr: pLdr.LoaderWithWorkingDir(ldr.Root()),
}
}
// Load attempts to load the target's kustomization file.
func (kt *KustTarget) Load() error {
content, kustFileName, err := LoadKustFile(kt.ldr)
if err != nil {
return err
}
var k types.Kustomization
if err := k.Unmarshal(content); err != nil {
return err
}
// show warning message when using deprecated fields.
if warningMessages := k.CheckDeprecatedFields(); warningMessages != nil {
for _, msg := range *warningMessages {
fmt.Fprintf(os.Stderr, "%v\n", msg)
}
}
k.FixKustomization()
// check that Kustomization is empty
if err := k.CheckEmpty(); err != nil {
return err
}
errs := k.EnforceFields()
if len(errs) > 0 {
return fmt.Errorf(
"Failed to read kustomization file under %s:\n"+
strings.Join(errs, "\n"), kt.ldr.Root())
}
kt.kustomization = &k
kt.kustFileName = kustFileName
return nil
}
// Kustomization returns a copy of the immutable, internal kustomization object.
func (kt *KustTarget) Kustomization() types.Kustomization {
var result types.Kustomization
b, _ := json.Marshal(*kt.kustomization)
json.Unmarshal(b, &result)
return result
}
func LoadKustFile(ldr ifc.Loader) ([]byte, string, error) {
var content []byte
match := 0
var kustFileName string
for _, kf := range konfig.RecognizedKustomizationFileNames() {
c, err := ldr.Load(kf)
if err == nil {
match += 1
content = c
kustFileName = kf
}
}
switch match {
case 0:
return nil, "", NewErrMissingKustomization(ldr.Root())
case 1:
return content, kustFileName, nil
default:
return nil, "", fmt.Errorf(
"Found multiple kustomization files under: %s\n", ldr.Root())
}
}
// MakeCustomizedResMap creates a fully customized ResMap
// per the instructions contained in its kustomization instance.
func (kt *KustTarget) MakeCustomizedResMap() (resmap.ResMap, error) {
return kt.makeCustomizedResMap()
}
func (kt *KustTarget) makeCustomizedResMap() (resmap.ResMap, error) {
var origin *resource.Origin
if len(kt.kustomization.BuildMetadata) != 0 {
origin = &resource.Origin{}
}
kt.origin = origin
ra, err := kt.AccumulateTarget()
if err != nil {
return nil, err
}
// The following steps must be done last, not as part of
// the recursion implicit in AccumulateTarget.
err = kt.addHashesToNames(ra)
if err != nil {
return nil, err
}
// Given that names have changed (prefixs/suffixes added),
// fix all the back references to those names.
err = ra.FixBackReferences()
if err != nil {
return nil, err
}
// With all the back references fixed, it's OK to resolve Vars.
err = ra.ResolveVars()
if err != nil {
return nil, err
}
err = kt.IgnoreLocal(ra)
if err != nil {
return nil, err
}
return ra.ResMap(), nil
}
func (kt *KustTarget) addHashesToNames(
ra *accumulator.ResAccumulator) error {
p := builtins.NewHashTransformerPlugin()
err := kt.configureBuiltinPlugin(p, nil, builtinhelpers.HashTransformer)
if err != nil {
return err
}
return ra.Transform(p)
}
// AccumulateTarget returns a new ResAccumulator,
// holding customized resources and the data/rules used
// to do so. The name back references and vars are
// not yet fixed.
// The origin parameter is used through the recursive calls
// to annotate each resource with information about where
// the resource came from, e.g. the file and/or the repository
// it originated from.
// As an entrypoint, one can pass an empty resource.Origin object to
// AccumulateTarget. As AccumulateTarget moves recursively
// through kustomization directories, it updates `origin.path`
// accordingly. When a remote base is found, it updates `origin.repo`
// and `origin.ref` accordingly.
func (kt *KustTarget) AccumulateTarget() (
ra *accumulator.ResAccumulator, err error) {
return kt.accumulateTarget(accumulator.MakeEmptyAccumulator())
}
// ra should be empty when this KustTarget is a Kustomization, or the ra of the parent if this KustTarget is a Component
// (or empty if the Component does not have a parent).
func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator) (
resRa *accumulator.ResAccumulator, err error) {
ra, err = kt.accumulateResources(ra, kt.kustomization.Resources)
if err != nil {
return nil, errors.WrapPrefixf(err, "accumulating resources")
}
tConfig, err := builtinconfig.MakeTransformerConfig(
kt.ldr, kt.kustomization.Configurations)
if err != nil {
return nil, err
}
err = ra.MergeConfig(tConfig)
if err != nil {
return nil, errors.WrapPrefixf(
err, "merging config %v", tConfig)
}
crdTc, err := accumulator.LoadConfigFromCRDs(kt.ldr, kt.kustomization.Crds)
if err != nil {
return nil, errors.WrapPrefixf(
err, "loading CRDs %v", kt.kustomization.Crds)
}
err = ra.MergeConfig(crdTc)
if err != nil {
return nil, errors.WrapPrefixf(
err, "merging CRDs %v", crdTc)
}
err = kt.runGenerators(ra)
if err != nil {
return nil, err
}
// components are expected to execute after reading resources and adding generators ,before applying transformers and validation.
// https://github.com/kubernetes-sigs/kustomize/pull/5170#discussion_r1212101287
ra, err = kt.accumulateComponents(ra, kt.kustomization.Components)
if err != nil {
return nil, errors.WrapPrefixf(err, "accumulating components")
}
err = kt.runTransformers(ra)
if err != nil {
return nil, err
}
err = kt.runValidators(ra)
if err != nil {
return nil, err
}
err = ra.MergeVars(kt.kustomization.Vars)
if err != nil {
return nil, errors.WrapPrefixf(
err, "merging vars %v", kt.kustomization.Vars)
}
return ra, nil
}
// IgnoreLocal drops the local resource by checking the annotation "config.kubernetes.io/local-config".
func (kt *KustTarget) IgnoreLocal(ra *accumulator.ResAccumulator) error {
rf := kt.rFactory.RF()
if rf.IncludeLocalConfigs {
return nil
}
remainRes, err := rf.DropLocalNodes(ra.ResMap().ToRNodeSlice())
if err != nil {
return err
}
return ra.Intersection(kt.rFactory.FromResourceSlice(remainRes))
}
func (kt *KustTarget) runGenerators(
ra *accumulator.ResAccumulator) error {
var generators []*resmap.GeneratorWithProperties
gs, err := kt.configureBuiltinGenerators()
if err != nil {
return err
}
generators = append(generators, gs...)
gs, err = kt.configureExternalGenerators()
if err != nil {
return errors.WrapPrefixf(err, "loading generator plugins")
}
generators = append(generators, gs...)
for i, g := range generators {
resMap, err := g.Generate()
if err != nil {
return err
}
if resMap != nil {
err = resMap.AddOriginAnnotation(generators[i].Origin)
if err != nil {
return errors.WrapPrefixf(err, "adding origin annotations for generator %v", g)
}
}
err = ra.AbsorbAll(resMap)
if err != nil {
return errors.WrapPrefixf(err, "merging from generator %v", g)
}
}
return nil
}
func (kt *KustTarget) configureExternalGenerators() (
[]*resmap.GeneratorWithProperties, error) {
ra := accumulator.MakeEmptyAccumulator()
var generatorPaths []string
for _, p := range kt.kustomization.Generators {
// handle inline generators
rm, err := kt.rFactory.NewResMapFromBytes([]byte(p))
if err != nil {
// not an inline config
generatorPaths = append(generatorPaths, p)
continue
}
// inline config, track the origin
if kt.origin != nil {
resources := rm.Resources()
for _, r := range resources {
r.SetOrigin(kt.origin.Append(kt.kustFileName))
rm.Replace(r)
}
}
if err = ra.AppendAll(rm); err != nil {
return nil, errors.WrapPrefixf(err, "configuring external generator")
}
}
ra, err := kt.accumulateResources(ra, generatorPaths)
if err != nil {
return nil, err
}
return kt.pLdr.LoadGenerators(kt.ldr, kt.validator, ra.ResMap())
}
func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error {
var r []*resmap.TransformerWithProperties
tConfig := ra.GetTransformerConfig()
lts, err := kt.configureBuiltinTransformers(tConfig)
if err != nil {
return err
}
r = append(r, lts...)
lts, err = kt.configureExternalTransformers(kt.kustomization.Transformers)
if err != nil {
return err
}
r = append(r, lts...)
return ra.Transform(newMultiTransformer(r))
}
func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]*resmap.TransformerWithProperties, error) {
ra := accumulator.MakeEmptyAccumulator()
var transformerPaths []string
for _, p := range transformers {
// handle inline transformers
rm, err := kt.rFactory.NewResMapFromBytes([]byte(p))
if err != nil {
// not an inline config
transformerPaths = append(transformerPaths, p)
continue
}
// inline config, track the origin
if kt.origin != nil {
resources := rm.Resources()
for _, r := range resources {
r.SetOrigin(kt.origin.Append(kt.kustFileName))
rm.Replace(r)
}
}
if err = ra.AppendAll(rm); err != nil {
return nil, errors.WrapPrefixf(err, "configuring external transformer")
}
}
ra, err := kt.accumulateResources(ra, transformerPaths)
if err != nil {
return nil, err
}
return kt.pLdr.LoadTransformers(kt.ldr, kt.validator, ra.ResMap())
}
func (kt *KustTarget) runValidators(ra *accumulator.ResAccumulator) error {
validators, err := kt.configureExternalTransformers(kt.kustomization.Validators)
if err != nil {
return err
}
for _, v := range validators {
// Validators shouldn't modify the resource map
orignal := ra.ResMap().DeepCopy()
err = v.Transform(ra.ResMap())
if err != nil {
return err
}
newMap := ra.ResMap().DeepCopy()
if err = kt.removeValidatedByLabel(newMap); err != nil {
return err
}
if err = orignal.ErrorIfNotEqualSets(newMap); err != nil {
return fmt.Errorf("validator shouldn't modify the resource map: %v", err)
}
}
return nil
}
func (kt *KustTarget) removeValidatedByLabel(rm resmap.ResMap) error {
resources := rm.Resources()
for _, r := range resources {
labels := r.GetLabels()
if _, found := labels[konfig.ValidatedByLabelKey]; !found {
continue
}
delete(labels, konfig.ValidatedByLabelKey)
if err := r.SetLabels(labels); err != nil {
return err
}
}
return nil
}
// accumulateResources fills the given resourceAccumulator
// with resources read from the given list of paths.
func (kt *KustTarget) accumulateResources(
ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) {
for _, path := range paths {
// try loading resource as file then as base (directory or git repository)
if errF := kt.accumulateFile(ra, path); errF != nil {
// not much we can do if the error is an HTTP error so we bail out
if errors.Is(errF, load.ErrHTTP) {
return nil, errF
}
ldr, err := kt.ldr.New(path)
if err != nil {
// If accumulateFile found malformed YAML and there was a failure
// loading the resource as a base, then the resource is likely a
// file. The loader failure message is unnecessary, and could be
// confusing. Report only the file load error.
//
// However, a loader timeout implies there is a git repo at the
// path. In that case, both errors could be important.
if kusterr.IsMalformedYAMLError(errF) && !utils.IsErrTimeout(err) {
return nil, errF
}
return nil, errors.WrapPrefixf(
err, "accumulation err='%s'", errF.Error())
}
// store the origin, we'll need it later
origin := kt.origin.Copy()
if kt.origin != nil {
kt.origin = kt.origin.Append(path)
ra, err = kt.accumulateDirectory(ra, ldr, false)
// after we are done recursing through the directory, reset the origin
kt.origin = &origin
} else {
ra, err = kt.accumulateDirectory(ra, ldr, false)
}
if err != nil {
return nil, errors.WrapPrefixf(
err, "accumulation err='%s'", errF.Error())
}
}
}
return ra, nil
}
// accumulateComponents fills the given resourceAccumulator
// with resources read from the given list of paths.
func (kt *KustTarget) accumulateComponents(
ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) {
for _, path := range paths {
// Components always refer to directories
ldr, errL := kt.ldr.New(path)
if errL != nil {
return nil, fmt.Errorf("loader.New %q", errL)
}
var errD error
// store the origin, we'll need it later
origin := kt.origin.Copy()
if kt.origin != nil {
kt.origin = kt.origin.Append(path)
ra, errD = kt.accumulateDirectory(ra, ldr, true)
// after we are done recursing through the directory, reset the origin
kt.origin = &origin
} else {
ra, errD = kt.accumulateDirectory(ra, ldr, true)
}
if errD != nil {
return nil, fmt.Errorf("accumulateDirectory: %q", errD)
}
}
return ra, nil
}
func (kt *KustTarget) accumulateDirectory(
ra *accumulator.ResAccumulator, ldr ifc.Loader, isComponent bool) (*accumulator.ResAccumulator, error) {
defer ldr.Cleanup()
subKt := NewKustTarget(ldr, kt.validator, kt.rFactory, kt.pLdr)
err := subKt.Load()
if err != nil {
return nil, errors.WrapPrefixf(
err, "couldn't make target for path '%s'", ldr.Root())
}
subKt.kustomization.BuildMetadata = kt.kustomization.BuildMetadata
subKt.origin = kt.origin
// Propagate namespace to child kustomization's helmRootNamespace for HelmCharts
// This ensures Helm charts in base kustomizations inherit namespace from overlays
// without affecting other transformers like patches
// Fixes https://github.com/kubernetes-sigs/kustomize/issues/6031
// Fixes https://github.com/kubernetes-sigs/kustomize/issues/6027
if kt.kustomization.Namespace != "" {
subKt.helmRootNamespace = kt.kustomization.Namespace
} else if kt.helmRootNamespace != "" {
subKt.helmRootNamespace = kt.helmRootNamespace
}
var bytes []byte
if openApiPath, exists := subKt.Kustomization().OpenAPI["path"]; exists {
bytes, err = ldr.Load(openApiPath)
if err != nil {
return nil, err
}
}
err = openapi.SetSchema(subKt.Kustomization().OpenAPI, bytes, false)
if err != nil {
return nil, err
}
if isComponent && subKt.kustomization.Kind != types.ComponentKind {
return nil, fmt.Errorf(
"expected kind '%s' for path '%s' but got '%s'", types.ComponentKind, ldr.Root(), subKt.kustomization.Kind)
} else if !isComponent && subKt.kustomization.Kind == types.ComponentKind {
return nil, fmt.Errorf(
"expected kind != '%s' for path '%s'", types.ComponentKind, ldr.Root())
}
var subRa *accumulator.ResAccumulator
if isComponent {
// Components don't create a new accumulator: the kustomization directives are added to the current accumulator
subRa, err = subKt.accumulateTarget(ra)
ra = accumulator.MakeEmptyAccumulator()
} else {
// Child Kustomizations create a new accumulator which resolves their kustomization directives, which will later
// be merged into the current accumulator.
subRa, err = subKt.AccumulateTarget()
}
if err != nil {
return nil, errors.WrapPrefixf(
err, "recursed accumulation of path '%s'", ldr.Root())
}
err = ra.MergeAccumulator(subRa)
if err != nil {
return nil, errors.WrapPrefixf(
err, "recursed merging from path '%s'", ldr.Root())
}
return ra, nil
}
func (kt *KustTarget) accumulateFile(
ra *accumulator.ResAccumulator, path string) error {
resources, err := kt.rFactory.FromFile(kt.ldr, path)
if err != nil {
return errors.WrapPrefixf(err, "accumulating resources from '%s'", path)
}
if kt.origin != nil {
originAnno, err := kt.origin.Append(path).String()
if err != nil {
return errors.WrapPrefixf(err, "cannot add path annotation for '%s'", path)
}
err = resources.AnnotateAll(utils.OriginAnnotationKey, originAnno)
if err != nil || originAnno == "" {
return errors.WrapPrefixf(err, "cannot add path annotation for '%s'", path)
}
}
err = ra.AppendAll(resources)
if err != nil {
return errors.WrapPrefixf(err, "merging resources from '%s'", path)
}
return nil
}
func (kt *KustTarget) configureBuiltinPlugin(
p resmap.Configurable, c interface{}, bpt builtinhelpers.BuiltinPluginType) (err error) {
var y []byte
if c != nil {
y, err = yaml.Marshal(c)
if err != nil {
return errors.WrapPrefixf(
err, "builtin %s marshal", bpt)
}
}
err = p.Config(
resmap.NewPluginHelpers(
kt.ldr, kt.validator, kt.rFactory, kt.pLdr.Config()),
y)
if err != nil {
return errors.WrapPrefixf(
err, "trouble configuring builtin %s with config: `\n%s`", bpt, string(y))
}
return nil
}
@@ -0,0 +1,467 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package target
import (
"fmt"
"path/filepath"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Functions dedicated to configuring the builtin
// transformer and generator plugins using config data
// read from a kustomization file and from the
// config.TransformerConfig, whose data may be a
// mix of hardcoded values and data read from file.
//
// Non-builtin plugins will get their configuration
// from their own dedicated structs and YAML files.
//
// There are some loops in the functions below because
// the kustomization file would, say, allow someone to
// request multiple secrets be made, or run multiple
// image tag transforms. In these cases, we'll need
// N plugin instances with differing configurations.
func (kt *KustTarget) configureBuiltinGenerators() (
result []*resmap.GeneratorWithProperties, err error) {
for _, bpt := range []builtinhelpers.BuiltinPluginType{
builtinhelpers.ConfigMapGenerator,
builtinhelpers.SecretGenerator,
builtinhelpers.HelmChartInflationGenerator,
} {
r, err := generatorConfigurators[bpt](
kt, bpt, builtinhelpers.GeneratorFactories[bpt])
if err != nil {
return nil, err
}
var generatorOrigin *resource.Origin
if kt.origin != nil {
generatorOrigin = &resource.Origin{
Repo: kt.origin.Repo,
Ref: kt.origin.Ref,
ConfiguredIn: filepath.Join(kt.origin.Path, kt.kustFileName),
ConfiguredBy: yaml.ResourceIdentifier{
TypeMeta: yaml.TypeMeta{
APIVersion: "builtin",
Kind: bpt.String(),
},
},
}
}
for i := range r {
result = append(result, &resmap.GeneratorWithProperties{Generator: r[i], Origin: generatorOrigin})
}
}
return result, nil
}
func (kt *KustTarget) configureBuiltinTransformers(
tc *builtinconfig.TransformerConfig) (
result []*resmap.TransformerWithProperties, err error) {
for _, bpt := range []builtinhelpers.BuiltinPluginType{
builtinhelpers.PatchStrategicMergeTransformer,
builtinhelpers.PatchTransformer,
builtinhelpers.NamespaceTransformer,
builtinhelpers.PrefixTransformer,
builtinhelpers.SuffixTransformer,
builtinhelpers.LabelTransformer,
builtinhelpers.AnnotationsTransformer,
builtinhelpers.PatchJson6902Transformer,
builtinhelpers.ReplicaCountTransformer,
builtinhelpers.ImageTagTransformer,
builtinhelpers.ReplacementTransformer,
} {
r, err := transformerConfigurators[bpt](
kt, bpt, builtinhelpers.TransformerFactories[bpt], tc)
if err != nil {
return nil, err
}
var transformerOrigin *resource.Origin
if kt.origin != nil {
transformerOrigin = &resource.Origin{
Repo: kt.origin.Repo,
Ref: kt.origin.Ref,
ConfiguredIn: filepath.Join(kt.origin.Path, kt.kustFileName),
ConfiguredBy: yaml.ResourceIdentifier{
TypeMeta: yaml.TypeMeta{
APIVersion: "builtin",
Kind: bpt.String(),
},
},
}
}
for i := range r {
result = append(result, &resmap.TransformerWithProperties{Transformer: r[i], Origin: transformerOrigin})
}
}
return result, nil
}
type gFactory func() resmap.GeneratorPlugin
var generatorConfigurators = map[builtinhelpers.BuiltinPluginType]func(
kt *KustTarget,
bpt builtinhelpers.BuiltinPluginType,
factory gFactory) (result []resmap.Generator, err error){
builtinhelpers.SecretGenerator: func(kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
result []resmap.Generator, err error) {
var c struct {
types.SecretArgs
}
for _, args := range kt.kustomization.SecretGenerator {
c.SecretArgs = args
c.SecretArgs.Options = types.MergeGlobalOptionsIntoLocal(
c.SecretArgs.Options, kt.kustomization.GeneratorOptions)
p := f()
err := kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
return
},
builtinhelpers.ConfigMapGenerator: func(kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
result []resmap.Generator, err error) {
var c struct {
types.ConfigMapArgs
}
for _, args := range kt.kustomization.ConfigMapGenerator {
c.ConfigMapArgs = args
c.ConfigMapArgs.Options = types.MergeGlobalOptionsIntoLocal(
c.ConfigMapArgs.Options, kt.kustomization.GeneratorOptions)
p := f()
err := kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
return
},
builtinhelpers.HelmChartInflationGenerator: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
result []resmap.Generator, err error) {
var c struct {
types.HelmGlobals
types.HelmChart
}
var globals types.HelmGlobals
if kt.kustomization.HelmGlobals != nil {
globals = *kt.kustomization.HelmGlobals
}
for _, chart := range kt.kustomization.HelmCharts {
c.HelmGlobals = globals
c.HelmChart = chart
// Pass kustomize namespace to helm
// Fixes https://github.com/kubernetes-sigs/kustomize/issues/5566
// Also propagate parent namespace for multi-level kustomization hierarchies
if c.HelmChart.Namespace == "" {
if kt.kustomization.Namespace != "" {
c.HelmChart.Namespace = kt.kustomization.Namespace
} else if kt.helmRootNamespace != "" {
c.HelmChart.Namespace = kt.helmRootNamespace
}
}
p := f()
if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil {
return nil, err
}
result = append(result, p)
}
return
},
}
type tFactory func() resmap.TransformerPlugin
var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func(
kt *KustTarget,
bpt builtinhelpers.BuiltinPluginType,
f tFactory,
tc *builtinconfig.TransformerConfig) (result []resmap.Transformer, err error){
builtinhelpers.NamespaceTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if kt.kustomization.Namespace == "" {
return
}
var c struct {
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
FieldSpecs []types.FieldSpec
}
c.Namespace = kt.kustomization.Namespace
c.FieldSpecs = tc.NameSpace
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return
},
builtinhelpers.PatchJson6902Transformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
var c struct {
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
}
for _, args := range kt.kustomization.PatchesJson6902 {
c.Target = args.Target
c.Path = args.Path
c.JsonOp = args.Patch
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
return
},
builtinhelpers.PatchStrategicMergeTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if len(kt.kustomization.PatchesStrategicMerge) == 0 {
return
}
var c struct {
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
}
c.Paths = kt.kustomization.PatchesStrategicMerge
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return
},
builtinhelpers.PatchTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if len(kt.kustomization.Patches) == 0 {
return
}
var c struct {
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
Options *types.PatchArgs `json:"options,omitempty" yaml:"options,omitempty"`
}
for _, pc := range kt.kustomization.Patches {
c.Target = pc.Target
c.Patch = pc.Patch
c.Path = pc.Path
c.Options = pc.Options
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
return
},
builtinhelpers.LabelTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if len(kt.kustomization.Labels) == 0 && len(kt.kustomization.CommonLabels) == 0 {
return
}
type labelStruct struct {
Labels map[string]string
FieldSpecs []types.FieldSpec
}
for _, label := range kt.kustomization.Labels {
var c labelStruct
c.Labels = label.Pairs
fss := types.FsSlice(label.FieldSpecs)
// merge labels specified in the label section of transformer configs
// these apply to selectors and templates
fss, err := fss.MergeAll(tc.Labels)
if err != nil {
return nil, fmt.Errorf("failed to merge labels: %w", err)
}
// merge the custom fieldSpecs with the default
if label.IncludeSelectors {
fss, err = fss.MergeAll(tc.CommonLabels)
} else {
// merge spec/template/metadata fieldSpecs if includeTemplate flag is true
if label.IncludeTemplates {
fss, err = fss.MergeAll(tc.TemplateLabels)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to merge template fieldSpec")
}
}
// only add to metadata by default
fss, err = fss.MergeOne(types.FieldSpec{Path: "metadata/labels", CreateIfNotPresent: true})
}
if err != nil {
return nil, fmt.Errorf("failed to merge labels: %w", err)
}
c.FieldSpecs = fss
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
var c labelStruct
c.Labels = kt.kustomization.CommonLabels
c.FieldSpecs = tc.CommonLabels
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return
},
builtinhelpers.AnnotationsTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if len(kt.kustomization.CommonAnnotations) == 0 {
return
}
var c struct {
Annotations map[string]string
FieldSpecs []types.FieldSpec
}
c.Annotations = kt.kustomization.CommonAnnotations
c.FieldSpecs = tc.CommonAnnotations
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return
},
builtinhelpers.PrefixTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if kt.kustomization.NamePrefix == "" {
return
}
var c struct {
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
c.Prefix = kt.kustomization.NamePrefix
c.FieldSpecs = tc.NamePrefix
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return
},
builtinhelpers.SuffixTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if kt.kustomization.NameSuffix == "" {
return
}
var c struct {
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}
c.Suffix = kt.kustomization.NameSuffix
c.FieldSpecs = tc.NameSuffix
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return
},
builtinhelpers.ImageTagTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
var c struct {
ImageTag types.Image
FieldSpecs []types.FieldSpec
}
for _, args := range kt.kustomization.Images {
c.ImageTag = args
c.FieldSpecs = tc.Images
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
return
},
builtinhelpers.ReplacementTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, _ *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
if len(kt.kustomization.Replacements) == 0 {
return
}
var c struct {
Replacements []types.ReplacementField
}
c.Replacements = kt.kustomization.Replacements
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
return result, nil
},
builtinhelpers.ReplicaCountTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
var c struct {
Replica types.Replica
FieldSpecs []types.FieldSpec
}
for _, args := range kt.kustomization.Replicas {
c.Replica = args
c.FieldSpecs = tc.Replicas
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
return
},
// No kustomization file keyword for this yet.
builtinhelpers.ValueAddTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
return nil, fmt.Errorf("valueadd keyword not yet defined")
},
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package target
import (
"sigs.k8s.io/kustomize/api/resmap"
)
// multiTransformer contains a list of transformers.
type multiTransformer struct {
transformers []*resmap.TransformerWithProperties
}
var _ resmap.Transformer = &multiTransformer{}
// newMultiTransformer constructs a multiTransformer.
func newMultiTransformer(t []*resmap.TransformerWithProperties) resmap.Transformer {
r := &multiTransformer{
transformers: make([]*resmap.TransformerWithProperties, len(t)),
}
copy(r.transformers, t)
return r
}
// Transform applies the member transformers in order to the resources,
// optionally detecting and erroring on commutation conflict.
func (o *multiTransformer) Transform(m resmap.ResMap) error {
for _, t := range o.transformers {
if err := t.Transform(m); err != nil {
return err
}
if t.Origin != nil {
if err := m.AddTransformerAnnotation(t.Origin); err != nil {
return err
}
}
m.DropEmpties()
}
return nil
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package utils
import "sigs.k8s.io/kustomize/api/konfig"
const (
// build annotations
BuildAnnotationPreviousKinds = konfig.ConfigAnnoDomain + "/previousKinds"
BuildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames"
BuildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes"
BuildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes"
BuildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces"
BuildAnnotationsRefBy = konfig.ConfigAnnoDomain + "/refBy"
BuildAnnotationsGenBehavior = konfig.ConfigAnnoDomain + "/generatorBehavior"
BuildAnnotationsGenAddHashSuffix = konfig.ConfigAnnoDomain + "/needsHashSuffix"
// the following are only for patches, to specify whether they can change names
// and kinds of their targets
BuildAnnotationAllowNameChange = konfig.ConfigAnnoDomain + "/allowNameChange"
BuildAnnotationAllowKindChange = konfig.ConfigAnnoDomain + "/allowKindChange"
// for keeping track of origin and transformer data
OriginAnnotationKey = "config.kubernetes.io/origin"
TransformerAnnotationKey = "alpha.config.kubernetes.io/transformations"
Enabled = "enabled"
)
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"fmt"
"time"
"sigs.k8s.io/kustomize/kyaml/errors"
)
type errTimeOut struct {
duration time.Duration
cmd string
}
func NewErrTimeOut(d time.Duration, c string) *errTimeOut {
return &errTimeOut{duration: d, cmd: c}
}
func (e *errTimeOut) Error() string {
return fmt.Sprintf("hit %s timeout running '%s'", e.duration, e.cmd)
}
func IsErrTimeout(err error) bool {
e := &errTimeOut{}
return errors.As(err, &e)
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// MakeResIds returns all of an RNode's current and previous Ids
func MakeResIds(n *yaml.RNode) ([]resid.ResId, error) {
var result []resid.ResId
apiVersion := n.Field(yaml.APIVersionField)
var group, version string
if apiVersion != nil {
group, version = resid.ParseGroupVersion(yaml.GetValue(apiVersion.Value))
}
result = append(result, resid.NewResIdWithNamespace(
resid.Gvk{Group: group, Version: version, Kind: n.GetKind()}, n.GetName(), n.GetNamespace()),
)
prevIds, err := PrevIds(n)
if err != nil {
return nil, err
}
result = append(result, prevIds...)
return result, nil
}
// PrevIds returns all of an RNode's previous Ids
func PrevIds(n *yaml.RNode) ([]resid.ResId, error) {
var ids []resid.ResId
// TODO: merge previous names and namespaces into one list of
// pairs on one annotation so there is no chance of error
annotations := n.GetAnnotations(
BuildAnnotationPreviousNames,
BuildAnnotationPreviousNamespaces,
BuildAnnotationPreviousKinds)
if _, ok := annotations[BuildAnnotationPreviousNames]; !ok {
return nil, nil
}
names := strings.Split(annotations[BuildAnnotationPreviousNames], ",")
ns := strings.Split(annotations[BuildAnnotationPreviousNamespaces], ",")
kinds := strings.Split(annotations[BuildAnnotationPreviousKinds], ",")
// This should never happen
if len(names) != len(ns) || len(names) != len(kinds) {
return nil, fmt.Errorf(
"number of previous names, " +
"number of previous namespaces, " +
"number of previous kinds not equal")
}
apiVersion := n.GetApiVersion()
group, version := resid.ParseGroupVersion(apiVersion)
ids = make([]resid.ResId, 0, len(names))
for i := range names {
gvk := resid.Gvk{
Group: group,
Version: version,
Kind: kinds[i],
}
ids = append(ids, resid.NewResIdWithNamespace(
gvk, names[i], ns[i]))
}
return ids, nil
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package utils
// StringSliceIndex returns the index of the str, else -1.
func StringSliceIndex(slice []string, str string) int {
for i := range slice {
if slice[i] == str {
return i
}
}
return -1
}
// StringSliceContains returns true if the slice has the string.
func StringSliceContains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
// SameEndingSubSlice returns true if the slices end the same way, e.g.
// {"a", "b", "c"}, {"b", "c"} => true
// {"a", "b", "c"}, {"a", "b"} => false
// If one slice is empty and the other is not, return false.
func SameEndingSubSlice(shortest, longest []string) bool {
if len(shortest) > len(longest) {
longest, shortest = shortest, longest
}
diff := len(longest) - len(shortest)
if len(shortest) == 0 {
return diff == 0
}
for i := len(shortest) - 1; i >= 0; i-- {
if longest[i+diff] != shortest[i] {
return false
}
}
return true
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"time"
)
// TimedCall runs fn, failing if it doesn't complete in the given duration.
// The description is used in the timeout error message.
func TimedCall(description string, d time.Duration, fn func() error) error {
done := make(chan error, 1)
timer := time.NewTimer(d)
defer timer.Stop()
go func() { done <- fn() }()
select {
case err := <-done:
return err
case <-timer.C:
return NewErrTimeOut(d, description)
}
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package validate
import (
"sigs.k8s.io/kustomize/api/ifc"
)
// FieldValidator implements ifc.Validator to check
// the values of various KRM string fields,
// e.g. labels, annotations, names, namespaces.
//
// TODO: Have this use kyaml/yaml/internal/k8sgen/pkg/labels
// which has label and annotation validation code, but is internal
// so this impl would need to move to kyaml (a fine idea).
type FieldValidator struct {
}
var _ ifc.Validator = (*FieldValidator)(nil)
func NewFieldValidator() *FieldValidator {
return &FieldValidator{}
}
// TODO(#FieldValidator): implement MakeAnnotationValidator
func (f FieldValidator) MakeAnnotationValidator() func(map[string]string) error {
return func(x map[string]string) error {
return nil
}
}
// TODO(#FieldValidator): implement MakeAnnotationNameValidator
func (f FieldValidator) MakeAnnotationNameValidator() func([]string) error {
return func(x []string) error {
return nil
}
}
// TODO(#FieldValidator): implement MakeLabelValidator
func (f FieldValidator) MakeLabelValidator() func(map[string]string) error {
return func(x map[string]string) error {
return nil
}
}
// TODO(#FieldValidator): implement MakeLabelNameValidator
func (f FieldValidator) MakeLabelNameValidator() func([]string) error {
return func(x []string) error {
return nil
}
}
// TODO(#FieldValidator): implement ValidateNamespace
func (f FieldValidator) ValidateNamespace(s string) []string {
var errs []string
return errs
}
// TODO(#FieldValidator): implement ErrIfInvalidKey
func (f FieldValidator) ErrIfInvalidKey(s string) error {
return nil
}
// TODO(#FieldValidator): implement IsEnvVarName
func (f FieldValidator) IsEnvVarName(k string) error {
return nil
}