diff --git a/api/filters/nameref/doc.go b/api/filters/nameref/doc.go new file mode 100644 index 000000000..b78499d51 --- /dev/null +++ b/api/filters/nameref/doc.go @@ -0,0 +1,3 @@ +// Package nameref contains a kio.Filter implementation of the kustomize +// name reference transformer. +package nameref diff --git a/api/filters/nameref/nameref.go b/api/filters/nameref/nameref.go new file mode 100644 index 000000000..1da3396fd --- /dev/null +++ b/api/filters/nameref/nameref.go @@ -0,0 +1,214 @@ +package nameref + +import ( + "fmt" + + "sigs.k8s.io/kustomize/api/filters/fieldspec" + "sigs.k8s.io/kustomize/api/filters/filtersutil" + "sigs.k8s.io/kustomize/api/resid" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// Filter will update the name reference +type Filter struct { + FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"` + Referrer *resource.Resource + Target resid.Gvk + ReferralCandidates resmap.ResMap +} + +func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { + return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes) +} + +func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) { + err := node.PipeE(fieldspec.Filter{ + FieldSpec: f.FieldSpec, + SetValue: f.set, + }) + return node, err +} + +func (f Filter) set(node *yaml.RNode) error { + if yaml.IsMissingOrNull(node) { + return nil + } + switch node.YNode().Kind { + case yaml.ScalarNode: + return f.setScalar(node) + case yaml.MappingNode: + // Kind: ValidatingWebhookConfiguration + // FieldSpec is webhooks/clientConfig/service + return f.setMapping(node) + case yaml.SequenceNode: + return f.setSequence(node) + default: + return fmt.Errorf( + "node is expected to be either a string or a slice of string or a map of string") + } +} + +func (f Filter) setSequence(node *yaml.RNode) error { + return applyFilterToSeq(seqFilter{ + setScalarFn: f.setScalar, + setMappingFn: f.setMapping, + }, node) +} + +func (f Filter) setMapping(node *yaml.RNode) error { + return setNameAndNs( + node, + f.Referrer, + f.Target, + f.ReferralCandidates, + ) +} + +func (f Filter) setScalar(node *yaml.RNode) error { + newValue, err := getSimpleNameField( + node.YNode().Value, + f.Referrer, + f.Target, + f.ReferralCandidates, + f.ReferralCandidates.Resources(), + ) + if err != nil { + return err + } + err = filtersutil.SetScalar(newValue)(node) + if err != nil { + return err + } + return nil +} + +// selectReferral picks the referral among a subset of candidates. +// It returns the current name and namespace of the selected candidate. +// Note that the content of the referricalCandidateSubset slice is most of the time +// identical to the referralCandidates resmap. Still in some cases, such +// as ClusterRoleBinding, the subset only contains the resources of a specific +// namespace. +func selectReferral( + oldName string, + referrer *resource.Resource, + target resid.Gvk, + referralCandidates resmap.ResMap, + referralCandidateSubset []*resource.Resource) (string, string, error) { + + for _, res := range referralCandidateSubset { + id := res.OrgId() + if id.IsSelected(&target) && res.GetOriginalName() == oldName { + matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals) + // If there's more than one match, there's no way + // to know which one to pick, so emit error. + if len(matches) > 1 { + return "", "", fmt.Errorf( + "multiple matches for %s:\n %v", + id, getIds(matches)) + } + // In the resource, note that it is referenced + // by the referrer. + res.AppendRefBy(referrer.CurId()) + // Return transformed name of the object, + // complete with prefixes, hashes, etc. + return res.GetName(), res.GetNamespace(), nil + } + } + + return oldName, "", nil +} + +// utility function to replace a simple string by the new name +func getSimpleNameField( + oldName string, + referrer *resource.Resource, + target resid.Gvk, + referralCandidates resmap.ResMap, + referralCandidateSubset []*resource.Resource) (string, error) { + + newName, _, err := selectReferral(oldName, referrer, target, + referralCandidates, referralCandidateSubset) + + return newName, err +} + +func getIds(rs []*resource.Resource) []string { + var result []string + for _, r := range rs { + result = append(result, r.CurId().String()+"\n") + } + return result +} + +// utility function to replace name field within a map RNode +// and leverage the namespace field. +func setNameAndNs( + in *yaml.RNode, + referrer *resource.Resource, + target resid.Gvk, + referralCandidates resmap.ResMap) error { + + if in.YNode().Kind != yaml.MappingNode { + return fmt.Errorf("expect a mapping node") + } + + // Get name field + nameNode, err := in.Pipe(yaml.FieldMatcher{ + Name: "name", + }) + if err != nil || nameNode == nil { + return fmt.Errorf("cannot find field 'name' in node") + } + + // Get namespace field + namespaceNode, err := in.Pipe(yaml.FieldMatcher{ + Name: "namespace", + }) + if err != nil { + return fmt.Errorf("error when find field 'namespace'") + } + + // check is namespace matched + // name will bot be updated if the namespace doesn't match + subset := referralCandidates.Resources() + if namespaceNode != nil { + namespace := namespaceNode.YNode().Value + bynamespace := referralCandidates.GroupedByOriginalNamespace() + if _, ok := bynamespace[namespace]; !ok { + return nil + } + subset = bynamespace[namespace] + } + + oldName := nameNode.YNode().Value + newname, newnamespace, err := selectReferral(oldName, referrer, target, + referralCandidates, subset) + if err != nil { + return err + } + + if (newname == oldName) && (newnamespace == "") { + // no candidate found. + return nil + } + + // set name + in.Pipe(yaml.FieldSetter{ + Name: "name", + StringValue: newname, + }) + if newnamespace != "" { + // We don't want value "" to replace value "default" since + // the empty string is handled as a wild card here not default namespace + // by kubernetes. + in.Pipe(yaml.FieldSetter{ + Name: "namespace", + StringValue: newnamespace, + }) + } + return nil +} diff --git a/api/filters/nameref/nameref_test.go b/api/filters/nameref/nameref_test.go new file mode 100644 index 000000000..951ff73eb --- /dev/null +++ b/api/filters/nameref/nameref_test.go @@ -0,0 +1,333 @@ +package nameref + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" + "sigs.k8s.io/kustomize/api/resid" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" + filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest" + "sigs.k8s.io/kustomize/api/types" +) + +func TestNamerefFilter(t *testing.T) { + testCases := map[string]struct { + input string + candidates string + expected string + filter Filter + originalNames []string + }{ + "simple scalar": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +ref: + name: oldName +`, + candidates: ` +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName +--- +apiVersion: apps/v1 +kind: NotSecret +metadata: + name: newName2 +`, + originalNames: []string{"oldName", ""}, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +ref: + name: newName +`, + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "ref/name"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + "sequence": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +seq: +- oldName1 +- oldName2 +`, + candidates: ` +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName +--- +apiVersion: apps/v1 +kind: NotSecret +metadata: + name: newName2 +`, + originalNames: []string{"oldName1", ""}, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +seq: +- newName +- oldName2 +`, + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "seq"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + "mapping": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +map: + name: oldName +`, + candidates: ` +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName +--- +apiVersion: apps/v1 +kind: NotSecret +metadata: + name: newName2 +`, + originalNames: []string{"oldName", ""}, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +map: + name: newName +`, + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "map"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + "mapping with namespace": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +map: + name: oldName + namespace: oldNs +`, + candidates: ` +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName + namespace: oldNs +--- +apiVersion: apps/v1 +kind: NotSecret +metadata: + name: newName2 +`, + originalNames: []string{"oldName", ""}, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +map: + name: newName + namespace: oldNs +`, + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "map"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) + referrer, err := factory.FromBytes([]byte(tc.input)) + if err != nil { + t.Fatal(err) + } + tc.filter.Referrer = referrer + + resMapFactory := resmap.NewFactory(factory, nil) + candidatesRes, err := factory.SliceFromBytesWithNames( + tc.originalNames, []byte(tc.candidates)) + if err != nil { + t.Fatal(err) + } + + candidates := resMapFactory.FromResourceSlice(candidatesRes) + tc.filter.ReferralCandidates = candidates + + if !assert.Equal(t, + strings.TrimSpace(tc.expected), + strings.TrimSpace( + filtertest_test.RunFilter(t, tc.input, tc.filter))) { + t.FailNow() + } + }) + } +} + +func TestNamerefFilterUnhappy(t *testing.T) { + testCases := map[string]struct { + input string + candidates string + expected string + filter Filter + originalNames []string + }{ + "invalid node type": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +ref: + name: null +`, + candidates: "", + originalNames: []string{}, + expected: "obj '' at path 'ref/name': node is expected to be either a string or a slice of string or a map of string", + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "ref/name"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + "multiple match": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +ref: + name: oldName +`, + candidates: ` +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName +--- +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName2 +`, + originalNames: []string{"oldName", "oldName"}, + expected: "", + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "ref/name"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + "no name": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dep +ref: + notName: oldName +`, + candidates: ` +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName +--- +apiVersion: apps/v1 +kind: Secret +metadata: + name: newName2 +`, + originalNames: []string{"oldName", "oldName"}, + expected: "", + filter: Filter{ + FieldSpec: types.FieldSpec{Path: "ref"}, + Target: resid.Gvk{ + Group: "apps", + Version: "v1", + Kind: "Secret", + }, + }, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) + referrer, err := factory.FromBytes([]byte(tc.input)) + if err != nil { + t.Fatal(err) + } + tc.filter.Referrer = referrer + + resMapFactory := resmap.NewFactory(factory, nil) + candidatesRes, err := factory.SliceFromBytesWithNames( + tc.originalNames, []byte(tc.candidates)) + if err != nil { + t.Fatal(err) + } + + candidates := resMapFactory.FromResourceSlice(candidatesRes) + tc.filter.ReferralCandidates = candidates + + _, err = filtertest_test.RunFilterE(t, tc.input, tc.filter) + if err == nil { + t.Fatalf("expect an error") + } + if tc.expected != "" && !assert.EqualError(t, err, tc.expected) { + t.FailNow() + } + }) + } +} diff --git a/api/filters/nameref/seqfilter.go b/api/filters/nameref/seqfilter.go new file mode 100644 index 000000000..c880694d1 --- /dev/null +++ b/api/filters/nameref/seqfilter.go @@ -0,0 +1,57 @@ +package nameref + +import ( + "fmt" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +type setFn func(*yaml.RNode) error + +type seqFilter struct { + setScalarFn setFn + setMappingFn setFn +} + +func (sf seqFilter) Filter(node *yaml.RNode) (*yaml.RNode, error) { + if yaml.IsMissingOrNull(node) { + return node, nil + } + switch node.YNode().Kind { + case yaml.ScalarNode: + // Kind: Role/ClusterRole + // FieldSpec is rules.resourceNames + err := sf.setScalarFn(node) + return node, err + case yaml.MappingNode: + // Kind: RoleBinding/ClusterRoleBinding + // FieldSpec is subjects + // Note: The corresponding fieldSpec had been changed from + // from path: subjects/name to just path: subjects. This is + // what get mutatefield to request the mapping of the whole + // map containing namespace and name instead of just a simple + // string field containing the name + err := sf.setMappingFn(node) + return node, err + default: + return node, fmt.Errorf( + "%#v is expected to be either a string or a map of string", node) + } +} + +// applyFilterToSeq will apply the filter to each element in the sequence node +func applyFilterToSeq(filter yaml.Filter, node *yaml.RNode) error { + if node.YNode().Kind != yaml.SequenceNode { + return fmt.Errorf("expect a sequence node but got %v", node.YNode().Kind) + } + + for _, elem := range node.Content() { + rnode := yaml.NewRNode(elem) + err := rnode.PipeE(filter) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/filters/nameref/seqfilter_test.go b/api/filters/nameref/seqfilter_test.go new file mode 100644 index 000000000..2dbf43c7d --- /dev/null +++ b/api/filters/nameref/seqfilter_test.go @@ -0,0 +1,80 @@ +package nameref + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func SeqFilter(node *yaml.RNode) (*yaml.RNode, error) { + if node.YNode().Value == "aaa" { + node.YNode().SetString("ccc") + } + return node, nil +} + +func TestApplyFilterToSeq(t *testing.T) { + fltr := yaml.FilterFunc(SeqFilter) + + testCases := map[string]struct { + input string + expect string + }{ + "replace in seq": { + input: ` +- aaa +- bbb`, + expect: ` +- ccc +- bbb`, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + node, err := yaml.Parse(tc.input) + if err != nil { + t.Fatal(err) + } + err = applyFilterToSeq(fltr, node) + if err != nil { + t.Fatal(err) + } + if !assert.Equal(t, + strings.TrimSpace(tc.expect), + strings.TrimSpace(node.MustString())) { + t.Fatalf("expect:\n%s\nactual:\n%s", + strings.TrimSpace(tc.expect), + strings.TrimSpace(node.MustString())) + } + }) + } +} + +func TestApplyFilterToSeqUnhappy(t *testing.T) { + fltr := yaml.FilterFunc(SeqFilter) + + testCases := map[string]struct { + input string + }{ + "replace in seq": { + input: ` +aaa`, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + node, err := yaml.Parse(tc.input) + if err != nil { + t.Fatal(err) + } + err = applyFilterToSeq(fltr, node) + if err == nil { + t.Fatalf("expect an error") + } + }) + } +} diff --git a/api/internal/accumulator/namereferencetransformer.go b/api/internal/accumulator/namereferencetransformer.go index 6dbd73c1a..59497312a 100644 --- a/api/internal/accumulator/namereferencetransformer.go +++ b/api/internal/accumulator/namereferencetransformer.go @@ -4,14 +4,12 @@ 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/resid" "sigs.k8s.io/kustomize/api/resmap" - "sigs.k8s.io/kustomize/api/resource" - "sigs.k8s.io/kustomize/api/transform" + "sigs.k8s.io/kustomize/kyaml/filtersutil" ) type nameReferenceTransformer struct { @@ -86,16 +84,12 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error { if candidates == nil { candidates = m.SubsetThatCouldBeReferencedByResource(referrer) } - err := transform.MutateField( - referrer.Map(), - fSpec.PathSlice(), - fSpec.CreateIfNotPresent, - o.getNewNameFunc( - // referrer could be an HPA instance, - // target could be Gvk for Deployment, - // candidate a list of resources "reachable" - // from the HPA. - referrer, target.Gvk, candidates)) + err := filtersutil.ApplyToJSON(nameref.Filter{ + FieldSpec: fSpec, + Referrer: referrer, + Target: target.Gvk, + ReferralCandidates: candidates, + }, referrer) if err != nil { return err } @@ -105,165 +99,3 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error { } return nil } - -// selectReferral picks the referral among a subset of candidates. -// It returns the current name and namespace of the selected candidate. -// Note that the content of the referricalCandidateSubset slice is most of the time -// identical to the referralCandidates resmap. Still in some cases, such -// as ClusterRoleBinding, the subset only contains the resources of a specific -// namespace. -func (o *nameReferenceTransformer) selectReferral( - oldName string, - referrer *resource.Resource, - target resid.Gvk, - referralCandidates resmap.ResMap, - referralCandidateSubset []*resource.Resource) (interface{}, interface{}, error) { - - for _, res := range referralCandidateSubset { - id := res.OrgId() - if id.IsSelected(&target) && res.GetOriginalName() == oldName { - matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals) - // If there's more than one match, there's no way - // to know which one to pick, so emit error. - if len(matches) > 1 { - return nil, nil, fmt.Errorf( - "multiple matches for %s:\n %v", - id, getIds(matches)) - } - // In the resource, note that it is referenced - // by the referrer. - res.AppendRefBy(referrer.CurId()) - // Return transformed name of the object, - // complete with prefixes, hashes, etc. - return res.GetName(), res.GetNamespace(), nil - } - } - - return oldName, nil, nil -} - -// utility function to replace a simple string by the new name -func (o *nameReferenceTransformer) getSimpleNameField( - oldName string, - referrer *resource.Resource, - target resid.Gvk, - referralCandidates resmap.ResMap, - referralCandidateSubset []*resource.Resource) (interface{}, error) { - - newName, _, err := o.selectReferral(oldName, referrer, target, - referralCandidates, referralCandidateSubset) - - return newName, err -} - -// utility function to replace name field within a map[string]interface{} -// and leverage the namespace field. -func (o *nameReferenceTransformer) getNameAndNsStruct( - inMap map[string]interface{}, - referrer *resource.Resource, - target resid.Gvk, - referralCandidates resmap.ResMap) (interface{}, error) { - - // Example: - if _, ok := inMap["name"]; !ok { - return nil, fmt.Errorf( - "%#v is expected to contain a name field", inMap) - } - oldName, ok := inMap["name"].(string) - if !ok { - return nil, fmt.Errorf( - "%#v is expected to contain a name field of type string", oldName) - } - - subset := referralCandidates.Resources() - if namespacevalue, ok := inMap["namespace"]; ok { - namespace := namespacevalue.(string) - bynamespace := referralCandidates.GroupedByOriginalNamespace() - if _, ok := bynamespace[namespace]; !ok { - return inMap, nil - } - subset = bynamespace[namespace] - } - - newname, newnamespace, err := o.selectReferral(oldName, referrer, target, - referralCandidates, subset) - if err != nil { - return nil, err - } - - if (newname == oldName) && (newnamespace == nil) { - // no candidate found. - return inMap, nil - } - - inMap["name"] = newname - if newnamespace != "" { - // We don't want value "" to replace value "default" since - // the empty string is handled as a wild card here not default namespace - // by kubernetes. - inMap["namespace"] = newnamespace - } - return inMap, nil - -} - -func (o *nameReferenceTransformer) getNewNameFunc( - referrer *resource.Resource, - target resid.Gvk, - referralCandidates resmap.ResMap) func(in interface{}) (interface{}, error) { - return func(in interface{}) (interface{}, error) { - switch thing := in.(type) { - case string: - return o.getSimpleNameField(thing, referrer, target, - referralCandidates, referralCandidates.Resources()) - case map[string]interface{}: - // Kind: ValidatingWebhookConfiguration - // FieldSpec is webhooks/clientConfig/service - return o.getNameAndNsStruct(thing, referrer, target, - referralCandidates) - case []interface{}: - for idx, item := range thing { - switch value := item.(type) { - case string: - // Kind: Role/ClusterRole - // FieldSpec is rules.resourceNames - newName, err := o.getSimpleNameField(value, referrer, target, - referralCandidates, referralCandidates.Resources()) - if err != nil { - return nil, err - } - thing[idx] = newName - case map[string]interface{}: - // Kind: RoleBinding/ClusterRoleBinding - // FieldSpec is subjects - // Note: The corresponding fieldSpec had been changed from - // from path: subjects/name to just path: subjects. This is - // what get mutatefield to request the mapping of the whole - // map containing namespace and name instead of just a simple - // string field containing the name - newMap, err := o.getNameAndNsStruct(value, referrer, target, - referralCandidates) - if err != nil { - return nil, err - } - thing[idx] = newMap - default: - return nil, fmt.Errorf( - "%#v is expected to be either a []string or a []map[string]interface{}", in) - } - } - return in, nil - default: - return nil, fmt.Errorf( - "%#v is expected to be either a string or a []interface{}", in) - } - } -} - -func getIds(rs []*resource.Resource) []string { - var result []string - for _, r := range rs { - result = append(result, r.CurId().String()+"\n") - } - return result -} diff --git a/api/internal/accumulator/namereferencetransformer_test.go b/api/internal/accumulator/namereferencetransformer_test.go index 49a117ea9..d6acbae2a 100644 --- a/api/internal/accumulator/namereferencetransformer_test.go +++ b/api/internal/accumulator/namereferencetransformer_test.go @@ -520,7 +520,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) { }, }, }).ResMap(), - expectedErr: "is expected to contain a name field"}, + expectedErr: "cannot find field 'name' in node"}, } nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference) diff --git a/api/resmap/factory.go b/api/resmap/factory.go index b779bb67f..90ee58c37 100644 --- a/api/resmap/factory.go +++ b/api/resmap/factory.go @@ -40,6 +40,15 @@ func (rmF *Factory) FromResource(res *resource.Resource) ResMap { return m } +// FromResourceSlice returns a ResMap with a slice of resources. +func (rmF *Factory) FromResourceSlice(ress []*resource.Resource) ResMap { + m, err := newResMapFromResourceSlice(ress) + if err != nil { + panic(err) + } + return m +} + // FromFile returns a ResMap given a resource path. func (rmF *Factory) FromFile( loader ifc.Loader, path string) (ResMap, error) { diff --git a/api/resource/factory.go b/api/resource/factory.go index 67fc020a8..2064475ce 100644 --- a/api/resource/factory.go +++ b/api/resource/factory.go @@ -146,6 +146,22 @@ func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) { return result, nil } +// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original +// name. +func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) { + result, err := rf.SliceFromBytes(in) + if err != nil { + return nil, err + } + if len(names) != len(result) { + return nil, fmt.Errorf("number of names doesn't match number of resources") + } + for i, res := range result { + res.originalName = names[i] + } + return result, nil +} + // MakeConfigMap makes an instance of Resource for ConfigMap func (rf *Factory) MakeConfigMap(kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (*Resource, error) { u, err := rf.kf.MakeConfigMap(kvLdr, args)