195
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/loadconfigfromcrds.go
generated
vendored
Normal file
195
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/loadconfigfromcrds.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package accumulator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"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.Wrapf(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.AddLabelFieldSpec(
|
||||
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 {
|
||||
loadCrdIntoConfig(
|
||||
theConfig, theGvk, theMap,
|
||||
property.Ref.String(), append(path, propName))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFs(in resid.Gvk, path []string) types.FieldSpec {
|
||||
return types.FieldSpec{
|
||||
CreateIfNotPresent: false,
|
||||
Gvk: in,
|
||||
Path: strings.Join(path, "/"),
|
||||
}
|
||||
}
|
||||
154
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/namereferencetransformer.go
generated
vendored
Normal file
154
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/namereferencetransformer.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
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 := m.SubsetThatCouldBeReferencedByResource(r)
|
||||
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) {
|
||||
fMap = make(filterMap)
|
||||
for _, backReference := range t.backRefs {
|
||||
for _, referrerSpec := range backReference.Referrers {
|
||||
for _, res := range resources {
|
||||
if res.OrgId().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
|
||||
}
|
||||
57
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/refvartransformer.go
generated
vendored
Normal file
57
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/refvartransformer.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
169
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/resaccumulator.go
generated
vendored
Normal file
169
vendor/sigs.k8s.io/kustomize/api/internal/accumulator/resaccumulator.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// 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/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// 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.IsNamespaceableKind() {
|
||||
// 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))
|
||||
}
|
||||
Reference in New Issue
Block a user