Compare commits

..

55 Commits

Author SHA1 Message Date
Kubernetes Prow Robot
f374a12f24 Merge pull request #3759 from phanimarupaka/NoKptfileInTreeOutput
Don't print Krmfile file in tree output
2021-03-30 09:37:57 -07:00
Kubernetes Prow Robot
c7156d0586 Merge pull request #3733 from RyanSquared/add-crd-webhook-namespace-transformer
Add CustomResourceDefinition webhook namespace transformer
2021-03-26 11:50:43 -07:00
Jeff Regan
d3b7d3ab70 Merge pull request #3754 from webwurst/patch-3
Fix indentation
2021-03-26 11:37:36 -07:00
Jeff Regan
197bb9d9e3 Merge pull request #3744 from KnVerey/plugin-dir-error
Do no require exec/go plugin home to use fn plugins
2021-03-26 11:26:48 -07:00
Phani Teja Marupaka
fa96878cfc Don't print Kustomization file in tree output 2021-03-25 17:01:30 -07:00
Kubernetes Prow Robot
558995536d Merge pull request #3748 from phanimarupaka/AvoidSetPanic
Check if value is provided
2021-03-25 10:23:30 -07:00
Katrina Verey
3255c73c71 Loader FS and empty env fix 2021-03-24 18:44:29 -07:00
Tobias Bradtke
fd486c1f23 Fix indentation 2021-03-24 22:29:31 +01:00
Kubernetes Prow Robot
b0a40e2752 Merge pull request #3743 from Shell32-Natsu/label
Add labels field to kustomization
2021-03-24 12:25:28 -07:00
Donny Xia
ccb95ab269 add conflict check 2021-03-24 10:41:37 -07:00
Jeff Regan
4a6ec9063d Merge pull request #3747 from natasha41575/RemoveOldReplacementTransformer
remove old untested replacement transformer
2021-03-23 16:47:16 -07:00
Phani Teja Marupaka
c3beadacd9 Check if value is provided 2021-03-23 15:40:35 -07:00
Natasha Sarkar
5f3bd4b4c2 remove old untested replacement transformer 2021-03-23 14:28:53 -07:00
Katrina Verey
e77c284924 Do no require exec/go plugin home to use fn plugins 2021-03-23 10:47:16 -07:00
Donny Xia
5ed2067be9 Add labels field to kustomization 2021-03-22 17:01:45 -07:00
Jeff Regan
7b38ce4ef2 Merge pull request #3740 from monopole/fixNitsIn3731
Fix nits in 3731
2021-03-20 07:31:28 -07:00
monopole
700a112b28 Fix nits in 3731 2021-03-20 07:05:56 -07:00
Jeff Regan
e05ce0f05b Merge pull request #3731 from dfsdevops/test-validating-webhook
added test showing broken namespace reference when combining resource…
2021-03-20 06:40:08 -07:00
Jeff Regan
b8cfa3ca9b Merge pull request #3738 from phanimarupaka/MakeDirOptionalForTree
Make dir optional for tree command
2021-03-20 06:38:35 -07:00
Phani Teja Marupaka
26a8455717 Make dir optional for tree command 2021-03-19 16:58:16 -07:00
Jeff Regan
710db98dbf Merge pull request #3685 from natasha41575/PatchServicePort
fixed disappearing port issue
2021-03-17 11:00:39 -07:00
Dylan Schultz
0d152c4784 added comment referring test to issue 3732 2021-03-17 10:52:41 -07:00
Kubernetes Prow Robot
1729c95135 Merge pull request #3734 from monopole/removeMergeConflictCode
Remove dead merge conflict code.
2021-03-17 09:51:58 -07:00
monopole
6f6d41f17f Remove dead merge conflict code. 2021-03-17 06:35:30 -07:00
RyanSquared
3ff5263ff6 api/konfig/builtinpluginconsts/namespace: Add CustomResourceDefinition webhook namespace transformer 2021-03-16 19:35:18 -05:00
Dylan Schultz
5bb668533f added test showing broken namespace reference when combining resources from two different bases 2021-03-16 17:01:39 -07:00
Jeff Regan
eb48b1b718 Merge pull request #3730 from monopole/typeAlias
Type alias for spec.Schema
2021-03-16 16:54:41 -07:00
monopole
1f837fdfec Type alias for spec.Schema 2021-03-16 16:18:10 -07:00
Kubernetes Prow Robot
1301384670 Merge pull request #3729 from Shell32-Natsu/helm-deps
handle helm output properly
2021-03-16 15:12:56 -07:00
Donny Xia
a9c20a2eb7 handle helm output properly 2021-03-16 13:09:04 -07:00
Kubernetes Prow Robot
297bdc3825 Merge pull request #3727 from monopole/fix3394
Allow patch removal of emptyDir {}
2021-03-16 10:06:35 -07:00
Jeff Regan
831f99c95b Merge pull request #3715 from natasha41575/SpeedUpCustomParsing
Speed up custom parsing
2021-03-15 22:26:27 -07:00
Jeff Regan
5247aa5750 Update resource.go 2021-03-15 22:22:43 -07:00
monopole
74d5646526 Allow patch removal of emptyDir {} 2021-03-15 18:34:36 -07:00
Natasha Sarkar
2f6a611e62 add example of using custom schema 2021-03-15 18:08:12 -07:00
Kubernetes Prow Robot
d0dbc3e87b Merge pull request #3721 from monopole/removeWrappyLayer
Remove the wrappy package in service of 3588
2021-03-15 16:54:34 -07:00
monopole
235101a614 Drop uses of 'unstructured' terminology. 2021-03-15 15:59:06 -07:00
monopole
123a5d6e56 Remove the wrappy layer. 2021-03-15 15:59:06 -07:00
Jeff Regan
e4bbd04a43 Merge pull request #3644 from natasha41575/AllowGeneralNameChanges
allow general name and kind changes via an options field in patches
2021-03-15 15:49:04 -07:00
Jeff Regan
75120b2a92 Merge pull request #3708 from saschagrunert/test-version
Take provenance version into account for build test
2021-03-15 13:16:29 -07:00
Jeff Regan
cb423ad300 Merge pull request #3725 from JohanWork/bug-demo-components-link
Fix broken link kep 1802
2021-03-15 12:40:31 -07:00
Johan Hansson
c81b5bd3c2 updated formatting 2021-03-14 21:49:23 +01:00
Kubernetes Prow Robot
b3cec39c25 Merge pull request #3720 from Shell32-Natsu/helm-version
support new version string in helm
2021-03-12 15:33:03 -08:00
Donny Xia
5fc6cab49f support new version string in helm 2021-03-12 12:01:02 -08:00
Donny Xia
c636ee616b Merge pull request #3713 from jkroepke/patch-1
HelmChartInflationGenerator: inherit environment variables to helm command
2021-03-12 10:10:54 -08:00
Natasha Sarkar
f96ac2d61e allow general name and kind changes via an options field in patches 2021-03-10 14:58:58 -08:00
Natasha Sarkar
a513c56d88 parse custom schema only once when necessary 2021-03-10 13:45:53 -08:00
Natasha Sarkar
ed3ab9f532 updated krusty tests for custom schema 2021-03-10 13:45:34 -08:00
Jeff Regan
bab8c34c1f Merge pull request #3709 from monopole/removeKyamlBranching
Remove branching on kyaml enablement
2021-03-09 14:45:26 -08:00
monopole
839fd2b971 Remove branching on kyaml enablement 2021-03-09 14:29:27 -08:00
Jeff Regan
26e9b8b3b8 Merge pull request #3714 from monopole/backToDev
Unpin all modules (back to dev mode).
2021-03-09 14:17:36 -08:00
monopole
ddfb4ff02d Unpin all modules (back to dev mode). 2021-03-09 14:01:26 -08:00
Jan-Otto Kröpke
a5e6295923 HelmChartInflationGenerator: inherit environment variables to helm command 2021-03-09 19:44:53 +01:00
Sascha Grunert
e2e495027d Take provenance version into account for build test
The result of the test will be different depending on how it has being
build via `-ldflags=-X sigs.k8s.io/kustomize/api/provenance.version=…`.

We now additionally query this version within the test to make it more
robust.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
2021-03-09 09:52:45 +01:00
Natasha Sarkar
397744f436 fixed disappearing ports issue 2021-03-08 17:03:22 -08:00
175 changed files with 3458 additions and 96693 deletions

View File

@@ -26,8 +26,7 @@ verify-kustomize: \
lint-kustomize \
test-unit-kustomize-all \
test-examples-kustomize-against-HEAD \
test-examples-kustomize-against-4.0 \
test-examples-kustomize-against-3.10
test-examples-kustomize-against-4.0
# The following target referenced by a file in
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
@@ -39,8 +38,7 @@ prow-presubmit-check: \
test-unit-cmd-all \
test-go-mod \
test-examples-kustomize-against-HEAD \
test-examples-kustomize-against-4.0 \
test-examples-kustomize-against-3.10
test-examples-kustomize-against-4.0
.PHONY: verify-kustomize-e2e
verify-kustomize-e2e: test-examples-e2e-kustomize
@@ -282,11 +280,7 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
.PHONY:
test-examples-kustomize-against-4.0: $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh v4@v4.0.1
.PHONY:
test-examples-kustomize-against-3.10: $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh v3@v3.10.0
./hack/testExamplesAgainstKustomize.sh v4@v4.0.5
# linux only.
# This is for testing an example plugin that

View File

@@ -11,7 +11,7 @@ import (
)
type HashTransformerPlugin struct {
hasher ifc.KunstructuredHasher
hasher ifc.KustHasher
}
func (p *HashTransformerPlugin) Config(
@@ -24,7 +24,7 @@ func (p *HashTransformerPlugin) Config(
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
for _, res := range m.Resources() {
if res.NeedHashSuffix() {
h, err := p.hasher.Hash(res)
h, err := res.Hash(p.hasher)
if err != nil {
return err
}

View File

@@ -76,7 +76,7 @@ func (p *HelmChartInflationGeneratorPlugin) Config(h *resmap.PluginHelpers, conf
cmd := exec.Command(p.HelmBin, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Env = append(cmd.Env,
cmd.Env = append(os.Environ(),
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.HelmHome),
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.HelmHome),
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.HelmHome),
@@ -218,7 +218,17 @@ func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) {
return nil, err
}
return p.h.ResmapFactory().NewResMapFromBytes(stdout)
rm, rmfErr := p.h.ResmapFactory().NewResMapFromBytes(stdout)
if rmfErr == nil {
return rm, nil
}
// try to remove the contents before first "---" because
// helm may produce messages to stdout before it
stdoutStr := string(stdout)
if idx := strings.Index(stdoutStr, "---"); idx != -1 {
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:]))
}
return nil, rmfErr
}
func (p *HelmChartInflationGeneratorPlugin) getTemplateCommandArgs() []string {
@@ -270,11 +280,17 @@ func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
if err != nil {
return err
}
r, err := regexp.Compile(`v\d+(\.\d+)+`)
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
if err != nil {
return err
}
v := string(r.Find(stdout))[1:]
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" {
return fmt.Errorf("this plugin requires helm V3 but got v%s", v)

View File

@@ -30,20 +30,15 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
return nil
}
for _, r := range m.Resources() {
empty, err := r.IsEmpty()
if err != nil {
return err
}
if empty {
if r.IsEmpty() {
// Don't mutate empty objects?
continue
}
r.StorePreviousId()
err = r.ApplyFilter(namespace.Filter{
if err := r.ApplyFilter(namespace.Filter{
Namespace: p.Namespace,
FsSlice: p.FieldSpecs,
})
if err != nil {
}); err != nil {
return err
}
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)

View File

@@ -28,45 +28,48 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
return fmt.Errorf("empty file path and empty patch content")
}
if len(p.Paths) != 0 {
for _, onePath := range p.Paths {
// The following oddly attempts to interpret a path string as an
// actual patch (instead of as a path to a file containing a patch).
// All tests pass if this code is commented out. This code should
// be deleted; the user should use the Patches field which
// exists for this purpose (inline patch declaration).
res, err := h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
if err == nil {
p.loadedPatches = append(p.loadedPatches, res...)
continue
}
res, err = h.ResmapFactory().RF().SliceFromPatches(
h.Loader(), []types.PatchStrategicMerge{onePath})
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, res...)
}
}
if p.Patches != "" {
res, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
patches, err := loadFromPaths(h, p.Paths)
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, res...)
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)
}
// Merge the patches, looking for conflicts.
_, err = h.ResmapFactory().ConflatePatches(p.loadedPatches)
if err != nil {
return err
}
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())

View File

@@ -21,6 +21,7 @@ type PatchTransformerPlugin 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 map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
}
func (p *PatchTransformerPlugin) Config(
@@ -60,6 +61,12 @@ func (p *PatchTransformerPlugin) Config(
}
if errSM == nil {
p.loadedPatch = patchSM
if p.Options["allowNameChange"] {
p.loadedPatch.SetAllowNameChange("true")
}
if p.Options["allowKindChange"] {
p.loadedPatch.SetAllowKindChange("true")
}
} else {
p.decodedPatch = patchJson
}

View File

@@ -1,7 +1,6 @@
package nameref
import (
"encoding/json"
"fmt"
"strings"
@@ -11,7 +10,6 @@ import (
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filtersutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -186,11 +184,7 @@ func (f Filter) recordTheReferral(referral *resource.Resource) {
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
n, err := filtersutil.GetRNode(res)
if err != nil {
return nil, err
}
func getRoleRefGvk(n *yaml.RNode) (*resid.Gvk, error) {
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
if err != nil {
return nil, err
@@ -276,7 +270,7 @@ func (f Filter) roleRefFilter() sieveFunc {
if !strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name") {
return acceptAll
}
roleRefGvk, err := getRoleRefGvk(f.Referrer)
roleRefGvk, err := getRoleRefGvk(f.Referrer.AsRNode())
if err != nil {
return acceptAll
}

View File

@@ -233,7 +233,7 @@ map:
}
tc.filter.Referrer = referrer
resMapFactory := resmap.NewFactory(factory, nil)
resMapFactory := resmap.NewFactory(factory)
candidatesRes, err := factory.SliceFromBytesWithNames(
tc.originalNames, []byte(tc.candidates))
if err != nil {
@@ -334,7 +334,7 @@ metadata:
}
tc.filter.Referrer = referrer
resMapFactory := resmap.NewFactory(factory, nil)
resMapFactory := resmap.NewFactory(factory)
candidatesRes, err := factory.SliceFromBytesWithNames(
tc.originalNames, []byte(tc.candidates))
if err != nil {
@@ -751,7 +751,7 @@ ref:
}
tc.filter.Referrer = referrer
resMapFactory := resmap.NewFactory(factory, nil)
resMapFactory := resmap.NewFactory(factory)
candidatesRes, err := factory.SliceFromBytesWithNames(
tc.originalNames, []byte(tc.candidates))
if err != nil {

View File

@@ -118,7 +118,6 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
// add the namespace to each "subject" with name: default
err = obj.VisitElements(func(o *yaml.RNode) error {
// copied from kunstruct based kustomize NamespaceTransformer plugin
// The only case we need to force the namespace
// if for the "service account". "default" is
// kind of hardcoded here for right now.

View File

@@ -4,7 +4,6 @@
package patchstrategicmerge
import (
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
@@ -29,7 +28,7 @@ func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
if err != nil {
return nil, err
}
if !konfig.FlagEnableKyamlDefaultValue || r != nil {
if r != nil {
result = append(result, r)
}
}

View File

@@ -723,12 +723,12 @@ spec:
- name: consul
image: "dashicorp/consul:1.9.1"
ports:
- containerPort: 8500
name: http
- containerPort: 8301
protocol: "TCP"
- containerPort: 8301
protocol: "UDP"
- containerPort: 8500
name: http
`,
},
}

View File

@@ -6,13 +6,13 @@ require (
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/go-errors/errors v1.0.1
github.com/go-openapi/spec v0.19.5
github.com/google/go-cmp v0.4.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/imdario/mergo v0.3.5
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.4.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
sigs.k8s.io/kustomize/kyaml v0.10.15
sigs.k8s.io/yaml v1.2.0
)
replace sigs.k8s.io/kustomize/kyaml => ../kyaml

View File

@@ -242,7 +242,6 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -265,7 +264,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -20,12 +20,12 @@ func SortArrayAndComputeHash(s []string) (string, error) {
if err != nil {
return "", err
}
return Encode(Hash(string(data)))
return encode(hex256(string(data)))
}
// Copied from https://github.com/kubernetes/kubernetes
// /blob/master/pkg/kubectl/util/hash/hash.go
func Encode(hex string) (string, error) {
func encode(hex string) (string, error) {
if len(hex) < 10 {
return "", fmt.Errorf(
"input length must be at least 10")
@@ -48,23 +48,18 @@ func Encode(hex string) (string, error) {
return string(enc), nil
}
// Hash returns the hex form of the sha256 of the argument.
func Hash(data string) string {
// hex256 returns the hex form of the sha256 of the argument.
func hex256(data string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
}
// HashRNode returns the hash value of input RNode
func HashRNode(node *yaml.RNode) (string, error) {
// get node kind
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
if err != nil {
return "", err
}
kind := kindNode.YNode().Value
// Hasher computes the hash of an RNode.
type Hasher struct{}
// calculate hash for different kinds
encoded := ""
switch kind {
// Hash returns a hash of the argument.
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
var encoded string
switch node.GetKind() {
case "ConfigMap":
encoded, err = encodeConfigMap(node)
case "Secret":
@@ -77,10 +72,11 @@ func HashRNode(node *yaml.RNode) (string, error) {
if err != nil {
return "", err
}
return Encode(Hash(encoded))
return encode(hex256(encoded))
}
func getNodeValues(node *yaml.RNode, paths []string) (map[string]interface{}, error) {
func getNodeValues(
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
values := make(map[string]interface{})
for _, p := range paths {
vn, err := node.Pipe(yaml.Lookup(p))
@@ -117,8 +113,11 @@ func encodeConfigMap(node *yaml.RNode) (string, error) {
if err != nil {
return "", err
}
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
"data": values["data"]}
m := map[string]interface{}{
"kind": "ConfigMap",
"name": values["metadata/name"],
"data": values["data"],
}
if _, ok := values["binaryData"].(map[string]interface{}); ok {
m["binaryData"] = values["binaryData"]
}

View File

@@ -32,10 +32,10 @@ func TestSortArrayAndComputeHash(t *testing.T) {
}
}
func TestHash(t *testing.T) {
func Test_hex256(t *testing.T) {
// hash the empty string to be sure that sha256 is being used
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
sum := Hash("")
sum := hex256("")
if expect != sum {
t.Errorf("expected hash %q but got %q", expect, sum)
}
@@ -56,7 +56,7 @@ kind: ConfigMap`, "6ct58987ht", ""},
{"one key", `
apiVersion: v1
kind: ConfigMap
data:
data:
one: ""`, "9g67k2htb6", ""},
// three keys (tests sorting order)
{"three keys", `
@@ -93,17 +93,17 @@ data:
binaryData:
two: ""`, "698h7c7t9m", ""},
}
h := &Hasher{}
for _, c := range cases {
node, err := yaml.Parse(c.cmYaml)
if err != nil {
t.Fatal(err)
}
h, err := HashRNode(node)
hashed, err := h.Hash(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
if c.hash != hashed {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
@@ -154,35 +154,34 @@ type: my-type
data:
one: ""`, "74bd68bm66", ""},
}
h := &Hasher{}
for _, c := range cases {
node, err := yaml.Parse(c.secretYaml)
if err != nil {
t.Fatal(err)
}
h, err := HashRNode(node)
hashed, err := h.Hash(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
if c.hash != hashed {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestUnstructuredHash(t *testing.T) {
cases := []struct {
desc string
unstructured string
hash string
err string
func TestBasicHash(t *testing.T) {
cases := map[string]struct {
res string
hash string
err string
}{
{"minimal", `
"minimal": {`
apiVersion: test/v1
kind: TestResource
metadata:
name: my-resource`, "244782mkb7", ""},
{"with spec", `
"with spec": {`
apiVersion: test/v1
kind: TestResource
metadata:
@@ -191,19 +190,22 @@ spec:
foo: 1
bar: abc`, "59m2mdccg4", ""},
}
for _, c := range cases {
node, err := yaml.Parse(c.unstructured)
if err != nil {
t.Fatal(err)
}
h, err := HashRNode(node)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
h := &Hasher{}
for n := range cases {
c := cases[n]
t.Run(n, func(t *testing.T) {
node, err := yaml.Parse(c.res)
if err != nil {
t.Fatal(err)
}
hashed, err := h.Hash(node)
if SkipRest(t, n, err, c.err) {
return
}
if c.hash != hashed {
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
}
})
}
}
@@ -222,7 +224,7 @@ kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
{"one key", `
apiVersion: v1
kind: ConfigMap
data:
data:
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
// three keys (tests sorting order)
{"three keys", `
@@ -334,9 +336,10 @@ data:
}
}
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
// and logs the appropriate error on the test object.
// The return value indicates whether we should skip the rest of the test case due to the error result.
// SkipRest returns true if there was a non-nil error or if we expected an
// error that didn't happen, and logs the appropriate error on the test object.
// The return value indicates whether we should skip the rest of the test case
// due to the error result.
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
if err != nil {
if len(contains) == 0 {

View File

@@ -5,8 +5,8 @@
package ifc
import (
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Validator provides functions to validate annotations and labels
@@ -38,92 +38,10 @@ type Loader interface {
Cleanup() error
}
// Kunstructured represents a Kubernetes Resource Model object.
type Kunstructured interface {
// Several uses.
Copy() Kunstructured
// GetAnnotations returns the k8s annotations.
GetAnnotations() map[string]string
// GetData returns a top-level "data" field, as in a ConfigMap.
GetDataMap() map[string]string
// GetData returns a top-level "binaryData" field, as in a ConfigMap.
GetBinaryDataMap() map[string]string
// Used by ResAccumulator and ReplacementTransformer.
GetFieldValue(string) (interface{}, error)
// Used by Resource.OrgId
GetGvk() resid.Gvk
// Used by resource.Factory.SliceFromBytes
GetKind() string
// GetLabels returns the k8s labels.
GetLabels() map[string]string
// Used by Resource.CurId and resource factory.
GetName() string
// Used by special case code in
// ResMap.SubsetThatCouldBeReferencedByResource
GetSlice(path string) ([]interface{}, error)
// GetString returns the value of a string field.
// Used by Resource.GetNamespace
GetString(string) (string, error)
// Several uses.
Map() (map[string]interface{}, error)
// Used by Resource.AsYAML and Resource.String
MarshalJSON() ([]byte, error)
// Used by resWrangler.Select
MatchesAnnotationSelector(selector string) (bool, error)
// Used by resWrangler.Select
MatchesLabelSelector(selector string) (bool, error)
// SetAnnotations replaces the k8s annotations.
SetAnnotations(map[string]string)
// SetDataMap sets a top-level "data" field, as in a ConfigMap.
SetDataMap(map[string]string)
// SetDataMap sets a top-level "binaryData" field, as in a ConfigMap.
SetBinaryDataMap(map[string]string)
// Used by PatchStrategicMergeTransformer.
SetGvk(resid.Gvk)
// SetLabels replaces the k8s labels.
SetLabels(map[string]string)
// SetName changes the name.
SetName(string)
// SetNamespace changes the namespace.
SetNamespace(string)
// Needed, for now, by kyaml/filtersutil.ApplyToJSON.
UnmarshalJSON([]byte) error
}
// KunstructuredFactory makes instances of Kunstructured.
type KunstructuredFactory interface {
SliceFromBytes([]byte) ([]Kunstructured, error)
FromMap(m map[string]interface{}) Kunstructured
Hasher() KunstructuredHasher
MakeConfigMap(kvLdr KvLoader, args *types.ConfigMapArgs) (Kunstructured, error)
MakeSecret(kvLdr KvLoader, args *types.SecretArgs) (Kunstructured, error)
}
// KunstructuredHasher returns a hash of the argument
// KustHasher returns a hash of the argument
// or an error.
type KunstructuredHasher interface {
Hash(Kunstructured) (string, error)
type KustHasher interface {
Hash(*yaml.RNode) (string, error)
}
// See core.v1.SecretTypeOpaque

View File

@@ -25,7 +25,7 @@ type OpenAPIDefinition struct {
Dependencies []string
}
type myProperties map[string]spec.Schema
type myProperties = map[string]spec.Schema
type nameToApiMap map[string]OpenAPIDefinition
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig

View File

@@ -8,7 +8,6 @@ import (
"testing"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/provider"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
@@ -521,17 +520,9 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
},
},
}).ResMap(),
// TODO(#3304): DECISION - kyaml better; not a bug.
expectedErr: konfig.IfApiMachineryElseKyaml(
`updating name reference in 'rules/resourceNames' field of `+
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'`+
`: considering field 'rules/resourceNames' of object
{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "ClusterRole", "metadata": {
"name": "cr"}, "rules": [{"resourceNames": {"foo": "bar"}, "resources": ["secrets"]}]}
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`,
`updating name reference in 'rules/resourceNames' field of `+
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'`+
`: considering field 'rules/resourceNames' of object
expectedErr: `updating name reference in 'rules/resourceNames' field of ` +
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'` +
`: considering field 'rules/resourceNames' of object
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
@@ -541,7 +532,7 @@ rules:
foo: bar
resources:
- secrets
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`)},
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`},
}
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)

View File

@@ -7,7 +7,6 @@ import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
@@ -121,12 +120,7 @@ func TestRefVarTransformer(t *testing.T) {
"slice": []interface{}{5}, // noticeably *not* a []string
}}).ResMap(),
},
// TODO(#3304): DECISION - kyaml better; not a bug.
errMessage: konfig.IfApiMachineryElseKyaml(
`considering field 'data/slice' of object
{"apiVersion": "v1", "data": {"slice": [5]}, "kind": "ConfigMap", "metadata": {"name": "cm1"}}
: invalid value type expect a string`,
`considering field 'data/slice' of object
errMessage: `considering field 'data/slice' of object
apiVersion: v1
data:
slice:
@@ -135,7 +129,6 @@ kind: ConfigMap
metadata:
name: cm1
: invalid value type expect a string`,
),
},
"var replacement in nil": {
given: given{

View File

@@ -352,6 +352,7 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
"metadata": map[string]interface{}{
"name": "sub-backendOne",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "Service",
"config.kubernetes.io/previousNames": "backendOne",
"config.kubernetes.io/previousNamespaces": "default",
"config.kubernetes.io/prefixes": "sub-",

View File

@@ -1,23 +0,0 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package conflict
import (
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resource"
)
type cdFactory struct{}
var _ resource.ConflictDetectorFactory = &cdFactory{}
// NewFactory returns a new conflict detector factory.
func NewFactory() resource.ConflictDetectorFactory {
return &cdFactory{}
}
// New returns an instance of smPatchMergeOnlyDetector.
func (c cdFactory) New(_ resid.Gvk) (resource.ConflictDetector, error) {
return &smPatchMergeOnlyDetector{}, nil
}

View File

@@ -1,33 +0,0 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package conflict
import (
"sigs.k8s.io/kustomize/api/resource"
)
// smPatchMergeOnlyDetector ignores conflicts,
// but does real strategic merge patching.
// This is part of an effort to eliminate dependence on
// apimachinery package to allow kustomize integration
// into kubectl (#2506 and #1500)
type smPatchMergeOnlyDetector struct{}
var _ resource.ConflictDetector = &smPatchMergeOnlyDetector{}
func (c *smPatchMergeOnlyDetector) HasConflict(
_, _ *resource.Resource) (bool, error) {
return false, nil
}
// There's at least one case that doesn't work. Suppose one has a
// Deployment with a volume with the bizarre "emptyDir: {}" entry.
// If you want to get rid of this entry via a patch containing
// the entry "emptyDir: null", then the following won't work,
// because null entries are eliminated.
func (c *smPatchMergeOnlyDetector) MergePatches(
r, patch *resource.Resource) (*resource.Resource, error) {
err := r.ApplySmPatch(patch)
return r, err
}

View File

@@ -13,10 +13,10 @@ import (
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/konfig"
fLdr "sigs.k8s.io/kustomize/api/loader"
"sigs.k8s.io/kustomize/api/provider"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
)
func TestExecPluginConfig(t *testing.T) {
@@ -32,8 +32,7 @@ s/$BAR/bar baz/g
t.Fatal(err)
}
pvd := provider.NewDefaultDepProvider()
rf := resmap.NewFactory(
pvd.GetResourceFactory(), pvd.GetConflictDetectorFactory())
rf := resmap.NewFactory(pvd.GetResourceFactory())
pluginConfig := rf.RF().FromMap(
map[string]interface{}{
"apiVersion": "someteam.example.com/v1",
@@ -46,10 +45,12 @@ s/$BAR/bar baz/g
})
pluginConfig.RemoveBuildAnnotations()
p := NewExecPlugin(
pLdr.AbsolutePluginPath(
konfig.DisabledPluginConfig(),
pluginConfig.OrgId()))
loader := pLdr.NewLoader(types.DisabledPluginConfig(), rf, fSys)
pluginPath, err := loader.AbsolutePluginPath(pluginConfig.OrgId())
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
p := NewExecPlugin(pluginPath)
// Not checking to see if the plugin is executable,
// because this test does not run it.
// This tests only covers sending configuration

View File

@@ -13,6 +13,7 @@ import (
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
"sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
@@ -29,11 +30,16 @@ import (
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) *Loader {
return &Loader{pc: pc, rf: rf}
pc *types.PluginConfig, rf *resmap.Factory, fs filesys.FileSystem) *Loader {
return &Loader{pc: pc, rf: rf, fs: fs}
}
func (l *Loader) LoadGenerators(
@@ -95,13 +101,47 @@ func relativePluginPath(id resid.ResId) string {
strings.ToLower(id.Kind))
}
func AbsolutePluginPath(pc *types.PluginConfig, id resid.ResId) string {
return filepath.Join(
pc.AbsPluginHome, relativePluginPath(id), 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
}
func (l *Loader) absolutePluginPath(id resid.ResId) string {
return AbsolutePluginPath(l.pc, id)
// 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 {
@@ -176,10 +216,13 @@ func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error)
}
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(l.absolutePluginPath(resId))
err := p.ErrIfNotExecutable()
if err == nil {
p := execplugin.NewExecPlugin(absPluginPath)
if err = p.ErrIfNotExecutable(); err == nil {
return p, nil
}
if !os.IsNotExist(err) {
@@ -193,7 +236,7 @@ func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, err
return nil, err
}
// Failing the above, try loading it as a Go plugin.
c, err := l.loadGoPlugin(resId)
c, err := l.loadGoPlugin(resId, absPluginPath+".so")
if err != nil {
return nil, err
}
@@ -208,12 +251,11 @@ func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, err
// as a Loader instance variable. So make it a package variable.
var registry = make(map[string]resmap.Configurable)
func (l *Loader) loadGoPlugin(id resid.ResId) (resmap.Configurable, error) {
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
}
absPath := l.absolutePluginPath(id) + ".so"
if !utils.FileExists(absPath) {
return nil, fmt.Errorf(
"expected file with Go object code at: %s", absPath)

View File

@@ -8,7 +8,6 @@ import (
"sigs.k8s.io/kustomize/api/filesys"
. "sigs.k8s.io/kustomize/api/internal/plugins/loader"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/loader"
"sigs.k8s.io/kustomize/api/provider"
"sigs.k8s.io/kustomize/api/resmap"
@@ -51,11 +50,11 @@ func TestLoader(t *testing.T) {
BuildGoPlugin("someteam.example.com", "v1", "SomeServiceGenerator")
defer th.Reset()
p := provider.NewDefaultDepProvider()
rmF := resmap.NewFactory(
p.GetResourceFactory(), p.GetConflictDetectorFactory())
rmF := resmap.NewFactory(p.GetResourceFactory())
fsys := filesys.MakeFsInMemory()
fLdr, err := loader.NewLoader(
loader.RestrictionRootOnly,
filesys.Separator, filesys.MakeFsInMemory())
filesys.Separator, fsys)
if err != nil {
t.Fatal(err)
}
@@ -67,11 +66,8 @@ func TestLoader(t *testing.T) {
for _, behavior := range []types.BuiltinPluginLoadingOptions{
/* types.BploUseStaticallyLinked,
types.BploLoadFromFileSys */} {
c, err := konfig.EnabledPluginConfig(behavior)
if err != nil {
t.Fatal(err)
}
pLdr := NewLoader(c, rmF)
c := types.EnabledPluginConfig(behavior)
pLdr := NewLoader(c, rmF, fsys)
if pLdr == nil {
t.Fatal("expect non-nil loader")
}

View File

@@ -34,7 +34,7 @@ func GoBin() string {
// has her ${g}/${v}/$lower(${k})/${k}.go files.
func DeterminePluginSrcRoot(fSys filesys.FileSystem) (string, error) {
return konfig.FirstDirThatExistsElseError(
"source directory", fSys, []konfig.NotedFunc{
"plugin src root", fSys, []konfig.NotedFunc{
{
Note: "relative to unit test",
F: func() string {

View File

@@ -201,14 +201,16 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func(
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"`
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 map[string]bool `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 {
@@ -221,6 +223,31 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func(
builtinhelpers.LabelTransformer: func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
result []resmap.Transformer, err error) {
for _, label := range kt.kustomization.Labels {
var c struct {
Labels map[string]string
FieldSpecs []types.FieldSpec
}
c.Labels = label.Pairs
fss := types.FsSlice(label.FieldSpecs)
// merge the custom fieldSpecs with the default
if label.IncludeSelectors {
fss, err = fss.MergeAll(tc.CommonLabels)
} else {
// only add to metadata by default
fss, err = fss.MergeOne(types.FieldSpec{Path: "metadata/labels", CreateIfNotPresent: true})
}
if err != nil {
return nil, err
}
c.FieldSpecs = fss
p := f()
err = kt.configureBuiltinPlugin(p, c, bpt)
if err != nil {
return nil, err
}
result = append(result, p)
}
var c struct {
Labels map[string]string
FieldSpecs []types.FieldSpec

View File

@@ -9,11 +9,11 @@ import (
"sigs.k8s.io/kustomize/api/filesys"
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
"sigs.k8s.io/kustomize/api/internal/target"
"sigs.k8s.io/kustomize/api/konfig"
fLdr "sigs.k8s.io/kustomize/api/loader"
"sigs.k8s.io/kustomize/api/provider"
"sigs.k8s.io/kustomize/api/resmap"
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
"sigs.k8s.io/kustomize/api/types"
)
func makeAndLoadKustTarget(
@@ -36,12 +36,11 @@ func makeKustTargetWithRf(
if err != nil {
t.Fatal(err)
}
rf := resmap.NewFactory(
pvd.GetResourceFactory(), pvd.GetConflictDetectorFactory())
pc := konfig.DisabledPluginConfig()
rf := resmap.NewFactory(pvd.GetResourceFactory())
pc := types.DisabledPluginConfig()
return target.NewKustTarget(
ldr,
valtest_test.MakeFakeValidator(),
rf,
pLdr.NewLoader(pc, rf))
pLdr.NewLoader(pc, rf, fSys))
}

View File

@@ -4,15 +4,12 @@
package target
import (
"fmt"
"sigs.k8s.io/kustomize/api/resmap"
)
// multiTransformer contains a list of transformers.
type multiTransformer struct {
transformers []resmap.Transformer
checkConflictEnabled bool
transformers []resmap.Transformer
}
var _ resmap.Transformer = &multiTransformer{}
@@ -20,8 +17,8 @@ var _ resmap.Transformer = &multiTransformer{}
// newMultiTransformer constructs a multiTransformer.
func newMultiTransformer(t []resmap.Transformer) resmap.Transformer {
r := &multiTransformer{
transformers: make([]resmap.Transformer, len(t)),
checkConflictEnabled: false}
transformers: make([]resmap.Transformer, len(t)),
}
copy(r.transformers, t)
return r
}
@@ -29,57 +26,11 @@ func newMultiTransformer(t []resmap.Transformer) resmap.Transformer {
// 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 {
if o.checkConflictEnabled {
return o.transformWithCheckConflict(m)
}
return o.transform(m)
}
func (o *multiTransformer) transform(m resmap.ResMap) error {
for _, t := range o.transformers {
err := t.Transform(m)
if err != nil {
if err := t.Transform(m); err != nil {
return err
}
}
for _, r := range m.Resources() {
empty, err := r.IsEmpty()
if err != nil {
return err
}
if empty {
err := m.Remove(r.CurId())
if err != nil {
return err
}
}
m.DropEmpties()
}
return nil
}
// Of the len(o.transformers)! possible transformer orderings, compare to a reversed order.
// A spot check to perform when the transformations are supposed to be commutative.
// Fail if there's a difference in the result.
func (o *multiTransformer) transformWithCheckConflict(m resmap.ResMap) error {
mcopy := m.DeepCopy()
err := o.transform(m)
if err != nil {
return err
}
o.reverseTransformers()
err = o.transform(mcopy)
if err != nil {
return err
}
err = m.ErrorIfNotEqualSets(mcopy)
if err != nil {
return fmt.Errorf("found conflict between different patches\n%v", err)
}
return nil
}
func (o *multiTransformer) reverseTransformers() {
for i, j := 0, len(o.transformers)-1; i < j; i, j = i+1, j-1 {
o.transformers[i], o.transformers[j] = o.transformers[j], o.transformers[i]
}
}

View File

@@ -1,108 +0,0 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package wrappy
import (
"fmt"
"sigs.k8s.io/kustomize/api/hasher"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/generators"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filtersutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// WNodeFactory makes instances of WNode.
//
// These instances in turn adapt
// sigs.k8s.io/kustomize/kyaml/yaml.RNode
// to implement ifc.Unstructured.
// This factory is meant to implement ifc.KunstructuredFactory.
//
// This implementation should be thin, as both WNode and WNodeFactory must be
// factored away (deleted) along with ifc.Kunstructured in favor of direct use
// of RNode methods upon completion of
// https://github.com/kubernetes-sigs/kustomize/issues/2506.
//
// See also api/krusty/internal/provider/depprovider.go
type WNodeFactory struct {
}
var _ ifc.KunstructuredFactory = (*WNodeFactory)(nil)
func (k *WNodeFactory) SliceFromBytes(bs []byte) ([]ifc.Kunstructured, error) {
yamlRNodes, err := kio.FromBytes(bs)
if err != nil {
return nil, err
}
var result []ifc.Kunstructured
for i := range yamlRNodes {
rn := yamlRNodes[i]
meta, err := rn.GetValidatedMetadata()
if err != nil {
return nil, err
}
if !shouldDropObject(meta) {
if foundNil, path := rn.HasNilEntryInList(); foundNil {
return nil, fmt.Errorf("empty item at %v in object %v", path, rn)
}
result = append(result, FromRNode(rn))
}
}
return result, nil
}
// shouldDropObject returns true if the resource should not be accumulated.
func shouldDropObject(m yaml.ResourceMeta) bool {
_, y := m.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
return y
}
func (k *WNodeFactory) FromMap(m map[string]interface{}) ifc.Kunstructured {
rn, err := FromMap(m)
if err != nil {
// TODO(#WNodeFactory): handle or bubble error"
panic(err)
}
return rn
}
// kustHash computes a hash of an unstructured object.
type kustHash struct{}
// Hash returns a hash of the given object
func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
node, err := filtersutil.GetRNode(m)
if err != nil {
return "", err
}
return hasher.HashRNode(node)
}
func (k *WNodeFactory) Hasher() ifc.KunstructuredHasher {
return &kustHash{}
}
// MakeConfigMap makes a wrapped configmap.
func (k *WNodeFactory) MakeConfigMap(
ldr ifc.KvLoader, args *types.ConfigMapArgs) (ifc.Kunstructured, error) {
rn, err := generators.MakeConfigMap(ldr, args)
if err != nil {
return nil, err
}
return FromRNode(rn), nil
}
// MakeSecret makes a wrapped secret.
func (k *WNodeFactory) MakeSecret(
ldr ifc.KvLoader, args *types.SecretArgs) (ifc.Kunstructured, error) {
rn, err := generators.MakeSecret(ldr, args)
if err != nil {
return nil, err
}
return FromRNode(rn), nil
}

View File

@@ -1,324 +0,0 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package wrappy_test
import (
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
. "sigs.k8s.io/kustomize/api/internal/wrappy"
)
func TestHasher(t *testing.T) {
input := `
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
one: ""
binaryData:
two: ""
`
expect := "698h7c7t9m"
factory := &WNodeFactory{}
k, err := factory.SliceFromBytes([]byte(input))
if err != nil {
t.Fatal(err)
}
hasher := factory.Hasher()
result, err := hasher.Hash(k[0])
if err != nil {
t.Fatal(err)
}
if result != expect {
t.Fatalf("expect %s but got %s", expect, result)
}
}
func TestSliceFromBytes(t *testing.T) {
factory := &WNodeFactory{}
testConfigMap :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
}
testConfigMapList :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMapList",
"items": []interface{}{
testConfigMap,
testConfigMap,
},
}
testDeploymentSpec := map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"hostAliases": []interface{}{
map[string]interface{}{
"hostnames": []interface{}{
"a.example.com",
},
"ip": "8.8.8.8",
},
},
},
},
}
testDeploymentA := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-a",
},
"spec": testDeploymentSpec,
}
testDeploymentB := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-b",
},
"spec": testDeploymentSpec,
}
testDeploymentList :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "DeploymentList",
"items": []interface{}{
testDeploymentA,
testDeploymentB,
},
}
type expected struct {
out []map[string]interface{}
isErr bool
}
testCases := map[string]struct {
input []byte
exp expected
}{
"garbage": {
input: []byte("garbageIn: garbageOut"),
exp: expected{
isErr: true,
},
},
"noBytes": {
input: []byte{},
exp: expected{
out: []map[string]interface{}{},
},
},
"goodJson": {
input: []byte(`
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
},
},
"goodYaml1": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
},
},
"goodYaml2": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap, testConfigMap},
},
},
"localConfigYaml": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
# this annotation causes the Resource to be ignored by kustomize
config.kubernetes.io/local-config: ""
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
},
},
"garbageInOneOfTwoObjects": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
`),
exp: expected{
isErr: true,
},
},
"emptyObjects": {
input: []byte(`
---
#a comment
---
`),
exp: expected{
out: []map[string]interface{}{},
},
},
"Missing .metadata.name in object": {
input: []byte(`
apiVersion: v1
kind: Namespace
metadata:
annotations:
foo: bar
`),
exp: expected{
isErr: true,
},
},
"nil value in list": {
input: []byte(`
apiVersion: builtin
kind: ConfigMapGenerator
metadata:
name: kube100-site
labels:
app: web
testList:
- testA
-
`),
exp: expected{
isErr: true,
},
},
"List": {
input: []byte(`
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{
testConfigMap,
testConfigMap},
},
},
"ConfigMapList": {
input: []byte(`
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMapList},
},
},
"listWithAnchors": {
input: []byte(`
apiVersion: v1
kind: DeploymentList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-b
spec:
<<: *hostAliases
`),
exp: expected{
out: []map[string]interface{}{testDeploymentList},
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rs, err := factory.SliceFromBytes(tc.input)
if err != nil {
assert.True(t, tc.exp.isErr)
return
}
assert.False(t, tc.exp.isErr)
assert.Equal(t, len(tc.exp.out), len(rs))
for i := range rs {
rsMap, err := rs[i].Map()
assert.NoError(t, err)
assert.Equal(
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
if n != "listWithAnchors" {
// https://github.com/kubernetes-sigs/kustomize/issues/3271
m, _ := rs[i].Map()
if !reflect.DeepEqual(tc.exp.out[i], m) {
t.Fatalf("%s:\nexpected: %v\n actual: %v",
n, tc.exp.out[i], m)
}
}
}
})
}
}

View File

@@ -1,292 +0,0 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package wrappy
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// WNode implements ifc.Kunstructured using yaml.RNode.
//
// It exists only to help manage a switch from
// kunstruct.UnstructAdapter to yaml.RNode as the core
// representation of KRM objects in kustomize.
//
// It's got a silly name because we don't want it around for long,
// and want its use to be obvious.
type WNode struct {
node *yaml.RNode
}
var _ ifc.Kunstructured = (*WNode)(nil)
func NewWNode() *WNode {
return FromRNode(yaml.NewRNode(nil))
}
func FromMap(m map[string]interface{}) (*WNode, error) {
n, err := yaml.FromMap(m)
if err != nil {
return nil, err
}
return FromRNode(n), nil
}
func FromRNode(node *yaml.RNode) *WNode {
return &WNode{node: node}
}
func (wn *WNode) AsRNode() *yaml.RNode {
return wn.node
}
func (wn *WNode) demandMetaData(label string) yaml.ResourceMeta {
meta, err := wn.node.GetMeta()
if err != nil {
// Log and die since interface doesn't allow error.
log.Fatalf("for %s', expected valid resource: %v", label, err)
}
return meta
}
// Copy implements ifc.Kunstructured.
func (wn *WNode) Copy() ifc.Kunstructured {
return &WNode{node: wn.node.Copy()}
}
// GetAnnotations implements ifc.Kunstructured.
func (wn *WNode) GetAnnotations() map[string]string {
return wn.demandMetaData("GetAnnotations").Annotations
}
// convertSliceIndex traverses the items in `fields` and find
// if there is a slice index in the item and change it to a
// valid Lookup field path. For example, 'ports[0]' will be
// converted to 'ports' and '0'.
func convertSliceIndex(fields []string) []string {
var res []string
for _, s := range fields {
if !strings.HasSuffix(s, "]") {
res = append(res, s)
continue
}
re := regexp.MustCompile(`^(.*)\[(\d+)\]$`)
groups := re.FindStringSubmatch(s)
if len(groups) == 0 {
// no match, add to result
res = append(res, s)
continue
}
if groups[1] != "" {
res = append(res, groups[1])
}
res = append(res, groups[2])
}
return res
}
// GetFieldValue implements ifc.Kunstructured.
func (wn *WNode) GetFieldValue(path string) (interface{}, error) {
fields := convertSliceIndex(strings.Split(path, "."))
rn, err := wn.node.Pipe(yaml.Lookup(fields...))
if err != nil {
return nil, err
}
if rn == nil {
return nil, NoFieldError{path}
}
yn := rn.YNode()
// If this is an alias node, resolve it
if yn.Kind == yaml.AliasNode {
yn = yn.Alias
}
// Return value as map for DocumentNode and MappingNode kinds
if yn.Kind == yaml.DocumentNode || yn.Kind == yaml.MappingNode {
var result map[string]interface{}
if err := yn.Decode(&result); err != nil {
return nil, err
}
return result, err
}
// Return value as slice for SequenceNode kind
if yn.Kind == yaml.SequenceNode {
var result []interface{}
if err := yn.Decode(&result); err != nil {
return nil, err
}
return result, nil
}
if yn.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("expected ScalarNode, got Kind=%d", yn.Kind)
}
// TODO: When doing kustomize var replacement, which is likely a
// a primary use of this function and the reason it returns interface{}
// rather than string, we do conversion from Nodes to Go types and back
// to nodes. We should figure out how to do replacement using raw nodes,
// assuming we keep the var feature in kustomize.
// The other end of this is: refvar.go:updateNodeValue.
switch yn.Tag {
case yaml.NodeTagString:
return yn.Value, nil
case yaml.NodeTagInt:
return strconv.Atoi(yn.Value)
case yaml.NodeTagFloat:
return strconv.ParseFloat(yn.Value, 64)
case yaml.NodeTagBool:
return strconv.ParseBool(yn.Value)
default:
// Possibly this should be an error or log.
return yn.Value, nil
}
}
// GetGvk implements ifc.Kunstructured.
func (wn *WNode) GetGvk() resid.Gvk {
meta := wn.demandMetaData("GetGvk")
g, v := resid.ParseGroupVersion(meta.APIVersion)
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
}
// GetDataMap implements ifc.Kunstructured.
func (wn *WNode) GetDataMap() map[string]string {
return wn.node.GetDataMap()
}
// SetDataMap implements ifc.Kunstructured.
func (wn *WNode) SetDataMap(m map[string]string) {
wn.node.SetDataMap(m)
}
// GetBinaryDataMap implements ifc.Kunstructured.
func (wn *WNode) GetBinaryDataMap() map[string]string {
return wn.node.GetBinaryDataMap()
}
// SetBinaryDataMap implements ifc.Kunstructured.
func (wn *WNode) SetBinaryDataMap(m map[string]string) {
wn.node.SetBinaryDataMap(m)
}
// GetKind implements ifc.Kunstructured.
func (wn *WNode) GetKind() string {
return wn.demandMetaData("GetKind").Kind
}
// GetLabels implements ifc.Kunstructured.
func (wn *WNode) GetLabels() map[string]string {
return wn.demandMetaData("GetLabels").Labels
}
// GetName implements ifc.Kunstructured.
func (wn *WNode) GetName() string {
return wn.demandMetaData("GetName").Name
}
// GetSlice implements ifc.Kunstructured.
func (wn *WNode) GetSlice(path string) ([]interface{}, error) {
value, err := wn.GetFieldValue(path)
if err != nil {
return nil, err
}
if sliceValue, ok := value.([]interface{}); ok {
return sliceValue, nil
}
return nil, fmt.Errorf("node %s is not a slice", path)
}
// GetSlice implements ifc.Kunstructured.
func (wn *WNode) GetString(path string) (string, error) {
value, err := wn.GetFieldValue(path)
if err != nil {
return "", err
}
if v, ok := value.(string); ok {
return v, nil
}
return "", fmt.Errorf("node %s is not a string: %v", path, value)
}
// Map implements ifc.Kunstructured.
func (wn *WNode) Map() (map[string]interface{}, error) {
return wn.node.Map()
}
// MarshalJSON implements ifc.Kunstructured.
func (wn *WNode) MarshalJSON() ([]byte, error) {
return wn.node.MarshalJSON()
}
// MatchesAnnotationSelector implements ifc.Kunstructured.
func (wn *WNode) MatchesAnnotationSelector(selector string) (bool, error) {
return wn.node.MatchesAnnotationSelector(selector)
}
// MatchesLabelSelector implements ifc.Kunstructured.
func (wn *WNode) MatchesLabelSelector(selector string) (bool, error) {
return wn.node.MatchesLabelSelector(selector)
}
// SetAnnotations implements ifc.Kunstructured.
func (wn *WNode) SetAnnotations(annotations map[string]string) {
if err := wn.node.SetAnnotations(annotations); err != nil {
log.Fatal(err) // interface doesn't allow error.
}
}
// SetGvk implements ifc.Kunstructured.
func (wn *WNode) SetGvk(gvk resid.Gvk) {
wn.setMapField(yaml.NewScalarRNode(gvk.Kind), yaml.KindField)
wn.setMapField(yaml.NewScalarRNode(gvk.ApiVersion()), yaml.APIVersionField)
}
// SetLabels implements ifc.Kunstructured.
func (wn *WNode) SetLabels(labels map[string]string) {
if err := wn.node.SetLabels(labels); err != nil {
log.Fatal(err) // interface doesn't allow error.
}
}
// SetName implements ifc.Kunstructured.
func (wn *WNode) SetName(name string) {
wn.setMapField(yaml.NewScalarRNode(name), yaml.MetadataField, yaml.NameField)
}
// SetNamespace implements ifc.Kunstructured.
func (wn *WNode) SetNamespace(ns string) {
if err := wn.node.SetNamespace(ns); err != nil {
log.Fatal(err) // interface doesn't allow error.
}
}
func (wn *WNode) setMapField(value *yaml.RNode, path ...string) {
if err := wn.node.SetMapField(value, path...); err != nil {
// Log and die since interface doesn't allow error.
log.Fatalf("failed to set field %v: %v", path, err)
}
}
// UnmarshalJSON implements ifc.Kunstructured.
func (wn *WNode) UnmarshalJSON(data []byte) error {
return wn.node.UnmarshalJSON(data)
}
type NoFieldError struct {
Field string
}
func (e NoFieldError) Error() string {
return fmt.Sprintf("no field named '%s'", e.Field)
}

View File

@@ -1,670 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package wrappy
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"sigs.k8s.io/kustomize/api/resid"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
)
const (
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
`"metadata":{"name":"homer","namespace":"simpsons"}}`
deploymentBiggerJson = `
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "homer",
"namespace": "simpsons",
"labels": {
"fruit": "apple",
"veggie": "carrot"
},
"annotations": {
"area": "51",
"greeting": "Take me to your leader."
}
},
"spec": {
"template": {
"spec": {
"containers": [
{
"env": [
{
"name": "CM_FOO",
"valueFrom": {
"configMapKeyRef": {
"key": "somekey",
"name": "myCm"
}
}
},
{
"name": "SECRET_FOO",
"valueFrom": {
"secretKeyRef": {
"key": "someKey",
"name": "mySecret"
}
}
}
],
"image": "nginx:1.7.9",
"name": "nginx"
}
]
}
}
}
}
`
bigMapYaml = `Kind: Service
complextree:
- field1:
- boolfield: true
floatsubfield: 1.01
intsubfield: 1010
stringsubfield: idx1010
- boolfield: false
floatsubfield: 1.011
intsubfield: 1011
stringsubfield: idx1011
field2:
- boolfield: true
floatsubfield: 1.02
intsubfield: 1020
stringsubfield: idx1020
- boolfield: false
floatsubfield: 1.021
intsubfield: 1021
stringsubfield: idx1021
- field1:
- boolfield: true
floatsubfield: 1.11
intsubfield: 1110
stringsubfield: idx1110
- boolfield: false
floatsubfield: 1.111
intsubfield: 1111
stringsubfield: idx1111
field2:
- boolfield: true
floatsubfield: 1.112
intsubfield: 1120
stringsubfield: idx1120
- boolfield: false
floatsubfield: 1.1121
intsubfield: 1121
stringsubfield: idx1121
metadata:
labels:
app: application-name
name: service-name
spec:
ports:
port: 80
that:
- idx0
- idx1
- idx2
- idx3
these:
- field1:
- idx010
- idx011
field2:
- idx020
- idx021
- field1:
- idx110
- idx111
field2:
- idx120
- idx121
- field1:
- idx210
- idx211
field2:
- idx220
- idx221
this:
is:
aBool: true
aFloat: 1.001
aNilValue: null
aNumber: 1000
anEmptyMap: {}
anEmptySlice: []
those:
- field1: idx0foo
field2: idx0bar
- field1: idx1foo
field2: idx1bar
- field1: idx2foo
field2: idx2bar
`
)
func makeBigMap() map[string]interface{} {
return map[string]interface{}{
"Kind": "Service",
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "application-name",
},
"name": "service-name",
},
"spec": map[string]interface{}{
"ports": map[string]interface{}{
"port": int64(80),
},
},
"this": map[string]interface{}{
"is": map[string]interface{}{
"aNumber": int64(1000),
"aFloat": float64(1.001),
"aNilValue": nil,
"aBool": true,
"anEmptyMap": map[string]interface{}{},
"anEmptySlice": []interface{}{},
/*
TODO: test for unrecognizable (e.g. a function)
"unrecognizable": testing.InternalExample{
Name: "fooBar",
},
*/
},
},
"that": []interface{}{
"idx0",
"idx1",
"idx2",
"idx3",
},
"those": []interface{}{
map[string]interface{}{
"field1": "idx0foo",
"field2": "idx0bar",
},
map[string]interface{}{
"field1": "idx1foo",
"field2": "idx1bar",
},
map[string]interface{}{
"field1": "idx2foo",
"field2": "idx2bar",
},
},
"these": []interface{}{
map[string]interface{}{
"field1": []interface{}{"idx010", "idx011"},
"field2": []interface{}{"idx020", "idx021"},
},
map[string]interface{}{
"field1": []interface{}{"idx110", "idx111"},
"field2": []interface{}{"idx120", "idx121"},
},
map[string]interface{}{
"field1": []interface{}{"idx210", "idx211"},
"field2": []interface{}{"idx220", "idx221"},
},
},
"complextree": []interface{}{
map[string]interface{}{
"field1": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1010",
"intsubfield": int64(1010),
"floatsubfield": float64(1.010),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1011",
"intsubfield": int64(1011),
"floatsubfield": float64(1.011),
"boolfield": false,
},
},
"field2": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1020",
"intsubfield": int64(1020),
"floatsubfield": float64(1.020),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1021",
"intsubfield": int64(1021),
"floatsubfield": float64(1.021),
"boolfield": false,
},
},
},
map[string]interface{}{
"field1": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1110",
"intsubfield": int64(1110),
"floatsubfield": float64(1.110),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1111",
"intsubfield": int64(1111),
"floatsubfield": float64(1.111),
"boolfield": false,
},
},
"field2": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1120",
"intsubfield": int64(1120),
"floatsubfield": float64(1.1120),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1121",
"intsubfield": int64(1121),
"floatsubfield": float64(1.1121),
"boolfield": false,
},
},
},
},
}
}
func TestBasicYamlOperationFromMap(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
if string(bytes) != bigMapYaml {
t.Fatalf("unexpected string equality")
}
rNode, err := kyaml.Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNodeString := rNode.MustString()
// The result from MustString has more indentation
// than bigMapYaml.
rNodeStrings := strings.Split(rNodeString, "\n")
bigMapStrings := strings.Split(bigMapYaml, "\n")
if len(rNodeStrings) != len(bigMapStrings) {
t.Fatalf("line count mismatch")
}
for i := range rNodeStrings {
s1 := strings.TrimSpace(rNodeStrings[i])
s2 := strings.TrimSpace(bigMapStrings[i])
if s1 != s2 {
t.Fatalf("expected '%s'=='%s'", s1, s2)
}
}
}
func TestRoundTripJSON(t *testing.T) {
wn := NewWNode()
err := wn.UnmarshalJSON([]byte(deploymentLittleJson))
if err != nil {
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
}
data, err := wn.MarshalJSON()
if err != nil {
t.Fatalf("unexpected MarshalJSON err: %v", err)
}
actual := string(data)
if actual != deploymentLittleJson {
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
}
}
func TestGettingFields(t *testing.T) {
wn := NewWNode()
err := wn.UnmarshalJSON([]byte(deploymentBiggerJson))
if err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
gvk := wn.GetGvk()
expected := "apps"
actual := gvk.Group
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "v1"
actual = gvk.Version
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "Deployment"
actual = gvk.Kind
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
actual = wn.GetKind()
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "homer"
actual = wn.GetName()
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
actualMap := wn.GetLabels()
v, ok := actualMap["fruit"]
if !ok || v != "apple" {
t.Fatalf("unexpected labels '%v'", actualMap)
}
actualMap = wn.GetAnnotations()
v, ok = actualMap["greeting"]
if !ok || v != "Take me to your leader." {
t.Fatalf("unexpected annotations '%v'", actualMap)
}
}
func TestGetFieldValueReturnsMap(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"fruit": "apple",
"veggie": "carrot",
}
actual, err := wn.GetFieldValue("metadata.labels")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestGetFieldValueReturnsStuff(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := []interface{}{
map[string]interface{}{
"env": []interface{}{
map[string]interface{}{
"name": "CM_FOO",
"valueFrom": map[string]interface{}{
"configMapKeyRef": map[string]interface{}{
"key": "somekey",
"name": "myCm",
},
},
},
map[string]interface{}{
"name": "SECRET_FOO",
"valueFrom": map[string]interface{}{
"secretKeyRef": map[string]interface{}{
"key": "someKey",
"name": "mySecret",
},
},
},
},
"image": string("nginx:1.7.9"),
"name": string("nginx"),
},
}
actual, err := wn.GetFieldValue("spec.template.spec.containers")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
// Cannot go deeper yet.
_, err = wn.GetFieldValue("spec.template.spec.containers.env")
if err == nil {
t.Fatalf("expected err %v", err)
}
}
func TestGetFieldValueReturnsSlice(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNode, err := kyaml.Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
wn := FromRNode(rNode)
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
actual, err := wn.GetFieldValue("that")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNode, err := kyaml.Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
wn := FromRNode(rNode)
expected := []interface{}{
map[string]interface{}{
"field1": "idx0foo",
"field2": "idx0bar",
},
map[string]interface{}{
"field1": "idx1foo",
"field2": "idx1bar",
},
map[string]interface{}{
"field1": "idx2foo",
"field2": "idx2bar",
},
}
actual, err := wn.GetFieldValue("those")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestGetFieldValueReturnsString(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
actual, err := wn.GetFieldValue("metadata.labels.fruit")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
v, ok := actual.(string)
if !ok || v != "apple" {
t.Fatalf("unexpected value '%v'", actual)
}
}
func TestGetFieldValueResolvesAlias(t *testing.T) {
yamlWithAlias := `
foo: &a theValue
bar: *a
`
rNode, err := kyaml.Parse(yamlWithAlias)
if err != nil {
t.Fatalf("unexpected yaml parse error: %v", err)
}
wn := FromRNode(rNode)
actual, err := wn.GetFieldValue("bar")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
v, ok := actual.(string)
if !ok || v != "theValue" {
t.Fatalf("unexpected value '%v'", actual)
}
}
func TestGetString(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := "carrot"
actual, err := wn.GetString("metadata.labels.veggie")
if err != nil {
t.Fatalf("error getting string: %v", err)
}
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestGetSlice(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNode, err := kyaml.Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
wn := FromRNode(rNode)
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
actual, err := wn.GetSlice("that")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestMapEmpty(t *testing.T) {
newNodeMap, err := NewWNode().Map()
assert.NoError(t, err)
assert.Equal(t, 0, len(newNodeMap))
}
func TestMap(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "homer",
"namespace": "simpsons",
},
}
actual, err := wn.Map()
assert.NoError(t, err)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestSetName(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
wn.SetName("marge")
if expected, actual := "marge", wn.GetName(); expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetNamespace(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
wn.SetNamespace("flanders")
meta, _ := wn.node.GetMeta()
if expected, actual := "flanders", meta.Namespace; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetLabels(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
wn.SetLabels(map[string]string{
"label1": "foo",
"label2": "bar",
})
labels := wn.GetLabels()
if expected, actual := 2, len(labels); expected != actual {
t.Fatalf("expected '%d', got '%d'", expected, actual)
}
if expected, actual := "foo", labels["label1"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "bar", labels["label2"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestGetAnnotations(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
wn.SetAnnotations(map[string]string{
"annotation1": "foo",
"annotation2": "bar",
})
annotations := wn.GetAnnotations()
if expected, actual := 2, len(annotations); expected != actual {
t.Fatalf("expected '%d', got '%d'", expected, actual)
}
if expected, actual := "foo", annotations["annotation1"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "bar", annotations["annotation2"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetGvk(t *testing.T) {
wn := NewWNode()
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
wn.SetGvk(resid.GvkFromString("grp_ver_knd"))
gvk := wn.GetGvk()
if expected, actual := "grp", gvk.Group; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "ver", gvk.Version; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "knd", gvk.Kind; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}

View File

@@ -19,5 +19,8 @@ namespace:
group: apiregistration.k8s.io
kind: APIService
create: true
- path: spec/conversion/webhook/clientConfig/service/namespace
group: apiextensions.k8s.io
kind: CustomResourceDefinition
`
)

View File

@@ -19,31 +19,7 @@ func DefaultKustomizationFileName() string {
return RecognizedKustomizationFileNames()[0]
}
// IfApiMachineryElseKyaml returns true if executing the apimachinery code
// path, else we're executing the kyaml code paths.
func IfApiMachineryElseKyaml(s1, s2 string) string {
if !FlagEnableKyamlDefaultValue {
return s1
}
return s2
}
const (
// FlagEnableKyamlDefaultValue is the default value for the --enable_kyaml
// flag. This value is also used in unit tests. See provider.DepProvider.
//
// TODO(#3588): Delete this constant.
//
// All tests should pass for either true or false values
// of this constant, without having to check its value.
// In the cases where there's a different outcome, either decide
// that the difference is acceptable, or make the difference go away.
//
// Historically, tests passed for enable_kyaml == false, i.e. using
// apimachinery libs. This doesn't mean the code was better, it just
// means regression tests preserved those outcomes.
FlagEnableKyamlDefaultValue = true
// An environment variable to consult for kustomization
// configuration data. See:
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

View File

@@ -41,32 +41,6 @@ const (
NoPluginHomeSentinal = "/No/non-builtin/plugins!"
)
func EnabledPluginConfig(b types.BuiltinPluginLoadingOptions) (*types.PluginConfig, error) {
dir, err := DefaultAbsPluginHome(filesys.MakeFsOnDisk())
if err != nil {
return nil, err
}
return MakePluginConfig(types.PluginRestrictionsNone, b, dir), nil
}
func DisabledPluginConfig() *types.PluginConfig {
return MakePluginConfig(
types.PluginRestrictionsBuiltinsOnly,
types.BploUseStaticallyLinked,
NoPluginHomeSentinal)
}
func MakePluginConfig(
pr types.PluginRestrictions,
b types.BuiltinPluginLoadingOptions,
home string) *types.PluginConfig {
return &types.PluginConfig{
PluginRestrictions: pr,
AbsPluginHome: home,
BpLoadingOptions: b,
}
}
type NotedFunc struct {
Note string
F func() string
@@ -77,7 +51,7 @@ type NotedFunc struct {
// the home of kustomize plugins.
func DefaultAbsPluginHome(fSys filesys.FileSystem) (string, error) {
return FirstDirThatExistsElseError(
"plugin home directory", fSys, []NotedFunc{
"plugin root", fSys, []NotedFunc{
{
Note: "homed in $" + KustomizePluginHomeEnv,
F: func() string {
@@ -87,9 +61,11 @@ func DefaultAbsPluginHome(fSys filesys.FileSystem) (string, error) {
{
Note: "homed in $" + XdgConfigHomeEnv,
F: func() string {
return filepath.Join(
os.Getenv(XdgConfigHomeEnv),
ProgramName, RelPluginHome)
if root := os.Getenv(XdgConfigHomeEnv); root != "" {
return filepath.Join(root, ProgramName, RelPluginHome)
}
// do not look in "kustomize/plugin" if XdgConfigHomeEnv is unset
return ""
},
},
{
@@ -118,11 +94,14 @@ func FirstDirThatExistsElseError(
pathFuncs []NotedFunc) (string, error) {
var nope []types.Pair
for _, dt := range pathFuncs {
dir := dt.F()
if fSys.Exists(dir) {
return dir, nil
if dir := dt.F(); dir != "" {
if fSys.Exists(dir) {
return dir, nil
}
nope = append(nope, types.Pair{Key: dt.Note, Value: dir})
} else {
nope = append(nope, types.Pair{Key: dt.Note, Value: "<no value>"})
}
nope = append(nope, types.Pair{Key: dt.Note, Value: dir})
}
return "", types.NewErrUnableToFind(what, nope)
}

View File

@@ -8,6 +8,8 @@ import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/types"
)
@@ -28,6 +30,34 @@ func TestDefaultAbsPluginHome_NoKustomizePluginHomeEnv(t *testing.T) {
if !types.IsErrUnableToFind(err) {
t.Fatalf("unexpected err: %v", err)
}
for _, expectedMsg := range []string{
"unable to find plugin root - tried:",
"('<no value>'; homed in $KUSTOMIZE_PLUGIN_HOME)",
"; homed in $XDG_CONFIG_HOME)",
"/.config/kustomize/plugin'; homed in default value of $XDG_CONFIG_HOME)",
"/kustomize/plugin'; homed in home directory)",
} {
assert.Contains(t, err.Error(), expectedMsg)
}
}
func TestDefaultAbsPluginHome_EmptyKustomizePluginHomeEnv(t *testing.T) {
keep, isSet := os.LookupEnv(KustomizePluginHomeEnv)
os.Setenv(KustomizePluginHomeEnv, "")
_, err := DefaultAbsPluginHome(filesys.MakeFsInMemory())
if !isSet {
_ = os.Unsetenv(KustomizePluginHomeEnv)
} else {
_ = os.Setenv(KustomizePluginHomeEnv, keep)
}
if err == nil {
t.Fatalf("expected err")
}
if !types.IsErrUnableToFind(err) {
t.Fatalf("unexpected err: %v", err)
}
assert.Contains(t, err.Error(), "('<no value>'; homed in $KUSTOMIZE_PLUGIN_HOME)")
}
func TestDefaultAbsPluginHome_WithKustomizePluginHomeEnv(t *testing.T) {
@@ -89,6 +119,25 @@ func TestDefaultAbsPluginHomeNoConfig(t *testing.T) {
}
}
func TestDefaultAbsPluginHomeEmptyXdgConfig(t *testing.T) {
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
os.Setenv(XdgConfigHomeEnv, "")
if isSet {
_ = os.Unsetenv(XdgConfigHomeEnv)
}
_, err := DefaultAbsPluginHome(filesys.MakeFsInMemory())
if isSet {
os.Setenv(XdgConfigHomeEnv, keep)
}
if err == nil {
t.Fatalf("expected err")
}
if !types.IsErrUnableToFind(err) {
t.Fatalf("unexpected err: %v", err)
}
assert.Contains(t, err.Error(), "('<no value>'; homed in $XDG_CONFIG_HOME)")
}
func TestDefaultAbsPluginHomeNoXdgWithDotConfig(t *testing.T) {
fSys := filesys.MakeFsInMemory()
configDir := filepath.Join(

View File

@@ -11,26 +11,6 @@ import (
func TestBasicIO_1(t *testing.T) {
th := kusttest_test.MakeHarness(t)
opts := th.MakeDefaultOptions()
if !opts.UseKyaml {
// This test won't pass under apimachinery, because in the bowels of
// that code (see GetAnnotations in v0.17.0 of
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go)
// an error returned from NestedStringMap is discarded, and an
// empty annotation map is silently returned, making this test fail
// The swallowed error arises from code like:
// var v interface{}
// v = true
// if str, ok := v.(string); ok {
// save the value in a map (doesn't happen)
// } else {
// return an error (that is then ignored by GetAnnotations)
// }
// The error happens when any annotation value can be interpreted as
// a boolean or number. Such annotations cannot be successfully applied
// to an object in a cluster unless they are quoted.
t.SkipNow()
}
th.WriteK(".", `
resources:
- service.yaml
@@ -47,7 +27,7 @@ metadata:
spec:
clusterIP: None
`)
m := th.Run(".", opts)
m := th.Run(".", th.MakeDefaultOptions())
// The annotations are sorted by key, hence the order change.
// Quotes are added intentionally.
th.AssertActualEqualsExpected(

View File

@@ -4,7 +4,6 @@
package krusty_test
import (
"fmt"
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
@@ -176,7 +175,9 @@ weak nuclear
`)
opts := th.MakeDefaultOptions()
m := th.Run(".", opts)
expFmt := `apiVersion: v1
th.AssertActualEqualsExpected(
m, `
apiVersion: v1
data:
BIRD: falcon
MOUNTAIN: everest
@@ -212,32 +213,19 @@ data:
BIRD: ZmFsY29u
MOUNTAIN: ZXZlcmVzdA==
OCEAN: cGFjaWZpYw==
forces.txt: %s
forces.txt: |
CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
VjbGVhcgo=
fruit: YXBwbGU=
passphrase: %s
passphrase: |
CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=
vegetable: YnJvY2NvbGk=
kind: Secret
metadata:
name: blah-bob-%s
name: blah-bob-58g62h555c
type: Opaque
`
th.AssertActualEqualsExpected(
m,
// TODO(#3304): DECISION - kyaml better; not a bug.
opts.IfApiMachineryElseKyaml(
fmt.Sprintf(
expFmt,
`CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
VjbGVhcgo=`,
`CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`,
`ftht6hfgmb`),
fmt.Sprintf(
expFmt, `|
CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
VjbGVhcgo=`, `|
CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`, `58g62h555c`)))
`)
}
// TODO: These should be errors instead.

View File

@@ -3,6 +3,7 @@ package krusty_test
import (
"testing"
"github.com/stretchr/testify/assert"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
@@ -35,5 +36,8 @@ spec:
- name: PODINFO_UI_COLOR
value: "#34577c"
`)
th.RunWithErr(".", th.MakeDefaultOptions())
m := th.Run(".", th.MakeDefaultOptions())
_, err := m.AsYaml()
assert.Error(t, err)
assert.Contains(t, err.Error(), "mapping key \"env\" already defined")
}

View File

@@ -8,8 +8,8 @@ import (
)
func TestFnExecGenerator(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
// Function plugins should not need the env setup done by MakeEnhancedHarness
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
@@ -92,8 +92,8 @@ func skipIfNoDocker(t *testing.T) {
func TestFnContainerGenerator(t *testing.T) {
skipIfNoDocker(t)
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
// Function plugins should not need the env setup done by MakeEnhancedHarness
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
@@ -308,8 +308,8 @@ spec:
func TestFnContainerTransformer(t *testing.T) {
skipIfNoDocker(t)
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
// Function plugins should not need the env setup done by MakeEnhancedHarness
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
@@ -408,8 +408,8 @@ spec:
func TestFnContainerTransformerWithConfig(t *testing.T) {
skipIfNoDocker(t)
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
// Function plugins should not need the env setup done by MakeEnhancedHarness
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
@@ -468,8 +468,8 @@ metadata:
func TestFnContainerEnvVars(t *testing.T) {
skipIfNoDocker(t)
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
// Function plugins should not need the env setup done by MakeEnhancedHarness
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
generators:

View File

@@ -14,7 +14,6 @@ import (
// GVKN shouldn't change with default options
func TestKeepOriginalGVKN(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -27,14 +26,12 @@ spec:
- name: nginx
image: nginx
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: StatefulSet
metadata:
name: new-name
`)
th.WriteK(".", `
resources:
- deployment.yaml
@@ -44,7 +41,6 @@ patches:
target:
kind: Deployment
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
@@ -64,7 +60,6 @@ spec:
// These tests document behavior that will change
func TestChangeName(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -77,14 +72,12 @@ spec:
- name: nginx
image: nginx
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: Deployment
metadata:
name: new-name
`)
th.WriteK(".", `
resources:
- deployment.yaml
@@ -93,18 +86,16 @@ patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// name should become `new-name`
m := th.Run(".", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: old-name
name: new-name
spec:
template:
spec:
@@ -116,7 +107,6 @@ spec:
func TestChangeKind(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -129,32 +119,27 @@ spec:
- name: nginx
image: nginx
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: StatefulSet
metadata:
name: old-name
`)
th.WriteK(".", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowKindChange: true
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// kind should become `StatefulSet`
m := th.Run(".", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
kind: StatefulSet
metadata:
name: old-name
spec:
@@ -168,7 +153,6 @@ spec:
func TestChangeNameAndKind(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -181,35 +165,30 @@ spec:
- name: nginx
image: nginx
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: StatefulSet
metadata:
name: new-name
`)
th.WriteK(".", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
allowKindChange: true
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// kind should become `StatefulSet`
// name should become `new-name`
m := th.Run(".", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
kind: StatefulSet
metadata:
name: old-name
name: new-name
spec:
template:
spec:
@@ -219,12 +198,10 @@ spec:
`)
}
// https://github.com/kubernetes-sigs/kustomize/issues/3280
// Should be able to refer to a resource with either its
// original GVKN or its current one
func TestPatchOriginalName(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("base/deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -246,13 +223,13 @@ metadata:
th.WriteK("base", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
`)
th.WriteK("overlay", `
resources:
- ../base
@@ -267,17 +244,13 @@ metadata:
spec:
replicas: 999
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// name should become `new-name`
m := th.Run("overlay", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: old-name
name: new-name
spec:
replicas: 999
template:
@@ -290,7 +263,6 @@ spec:
func TestPatchNewName(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("base/deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -312,13 +284,13 @@ metadata:
th.WriteK("base", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
`)
th.WriteK("overlay", `
resources:
- ../base
@@ -333,18 +305,25 @@ metadata:
spec:
replicas: 999
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// depPatch cannot find target with the name `new-name`
// because base/patch.yaml can't yet edit the name
assert.Error(t, th.RunWithErr("overlay", options))
m := th.Run("overlay", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: new-name
spec:
replicas: 999
template:
spec:
containers:
- image: nginx
name: nginx
`)
}
func TestPatchOriginalNameAndKind(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("base/deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -366,13 +345,14 @@ metadata:
th.WriteK("base", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
allowKindChange: true
`)
th.WriteK("overlay", `
resources:
- ../base
@@ -387,18 +367,13 @@ metadata:
spec:
replicas: 999
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// kind should become `StatefulSet`
// name should become `new-name`
m := th.Run("overlay", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
kind: StatefulSet
metadata:
name: old-name
name: new-name
spec:
replicas: 999
template:
@@ -411,7 +386,6 @@ spec:
func TestPatchNewNameAndKind(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("base/deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -433,13 +407,14 @@ metadata:
th.WriteK("base", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
allowKindChange: true
`)
th.WriteK("overlay", `
resources:
- ../base
@@ -454,20 +429,27 @@ metadata:
spec:
replicas: 999
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// depPatch cannot find target with kind `StatefulSet` and name `new-name`
// because base/patch.yaml can't yet edit the kind or name
assert.Error(t, th.RunWithErr("overlay", options))
m := th.Run("overlay", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: StatefulSet
metadata:
name: new-name
spec:
replicas: 999
template:
spec:
containers:
- image: nginx
name: nginx
`)
}
// Use original name, but new kind
// Should fail, even after #3280 is done, because this ID is invalid
func TestPatchOriginalNameAndNewKind(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("base/deployment.yaml", `
apiVersion: v1
kind: Deployment
@@ -489,13 +471,14 @@ metadata:
th.WriteK("base", `
resources:
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
options:
allowNameChange: true
allowKindChange: true
`)
th.WriteK("overlay", `
resources:
- ../base
@@ -510,10 +493,7 @@ metadata:
spec:
replicas: 999
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// depPatch cannot find target with kind `Deployment` and name `new-name`
// because the resource never had this GVKN
assert.Error(t, th.RunWithErr("overlay", options))
@@ -552,12 +532,11 @@ spec:
func TestBaseReuseNameAndKindConflict(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("/app/shared", `
th.WriteK("shared", `
resources:
- deployment.yaml
`)
th.WriteF("/app/shared/deployment.yaml", `
th.WriteF("shared/deployment.yaml", `
apiVersion: v1
kind: Deployment
metadata:
@@ -570,53 +549,139 @@ spec:
image: nginx
`)
th.WriteK("/app/component1/base", `
th.WriteK("component1/base", `
resources:
- ../../shared
`)
th.WriteK("/app/component1/overlay", `
th.WriteK("component1/overlay", `
resources:
- ../base
namePrefix: overlay-
`)
th.WriteK("/app/component2/base", `
th.WriteK("component2/base", `
resources:
- ../../shared
patches:
- path: patch.yaml
target:
kind: Deployment
name: my-deploy
options:
allowNameChange: true
allowKindChange: true
`)
th.WriteF("/app/component2/base/patch.yaml", `
th.WriteF("component2/base/patch.yaml", `
apiVersion: v1
kind: StatefulSet
metadata:
name: my-stateful-set
`)
th.WriteK("/app/component2/overlay", `
th.WriteK("component2/overlay", `
resources:
- ../base
namePrefix: overlay-
`)
th.WriteK("/app", `
th.WriteK(".", `
resources:
- component1/overlay
- component2/overlay
`)
options := th.MakeDefaultOptions()
options.AllowResourceIdChanges = true
// Error occurs when app/component2/base tries to load the shared resources
// because the kind is not (yet) allowed to change yet
// so it loads a second Deployment with the name my-deploy
// instead of a StatefulSet as specified by the patch.
// Will be fixed by #3280.
assert.Error(t, th.RunWithErr("overlay", options))
m := th.Run(".", options)
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: overlay-my-deploy
spec:
template:
spec:
containers:
- image: nginx
name: nginx
---
apiVersion: v1
kind: StatefulSet
metadata:
name: overlay-my-stateful-set
spec:
template:
spec:
containers:
- image: nginx
name: nginx
`)
}
func TestNameReferenceAfterGvknChange(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("configMap.yaml", `
apiVersion: v1
kind: ConfigMap
metadata:
name: old-name
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: ConfigMap
metadata:
name: new-name
`)
th.WriteF("deployment.yaml", `
apiVersion: v1
kind: Deployment
metadata:
name: deploy
spec:
template:
spec:
containers:
- env:
- valueFrom:
configMapKeyRef:
name: old-name
key: somekey
envFrom:
- configMapRef:
name: old-name
key: somekey
`)
th.WriteK(".", `
resources:
- configMap.yaml
- deployment.yaml
patches:
- path: patch.yaml
target:
kind: ConfigMap
options:
allowNameChange: true
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: ConfigMap
metadata:
name: new-name
---
apiVersion: v1
kind: Deployment
metadata:
name: deploy
spec:
template:
spec:
containers:
- env:
- valueFrom:
configMapKeyRef:
key: somekey
name: new-name
envFrom:
- configMapRef:
key: somekey
name: new-name
`)
}

View File

@@ -0,0 +1,93 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
const resources string = `apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
template:
spec:
containers:
- name: my-deployment
livenessProbe:
httpGet:
path: /healthz
port: 8080
---
apiVersion: example.dev/v1
kind: MyCRD
metadata:
name: crd
`
func TestKustomizationLabels(t *testing.T) {
th := kusttest_test.MakeHarness(t)
makeResourcesForPatchTest(th)
th.WriteK("/app", `
resources:
- deployment.yaml
labels:
- pairs:
foo: bar
- pairs:
a: b
includeSelectors: true
- pairs:
c: d
fields:
- path: spec/selector
group: example.dev
version: v1
kind: MyCRD
create: true
`)
th.WriteF("/app/deployment.yaml", resources)
m := th.Run("/app", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
a: b
c: d
foo: bar
name: my-deployment
spec:
selector:
matchLabels:
a: b
template:
metadata:
labels:
a: b
spec:
containers:
- livenessProbe:
httpGet:
path: /healthz
port: 8080
name: my-deployment
---
apiVersion: example.dev/v1
kind: MyCRD
metadata:
labels:
a: b
c: d
foo: bar
name: crd
spec:
selector:
c: d
`)
}

View File

@@ -36,7 +36,7 @@ type Kustomizer struct {
func MakeKustomizer(o *Options) *Kustomizer {
return &Kustomizer{
options: o,
depProvider: provider.NewDepProvider(o.UseKyaml),
depProvider: provider.NewDepProvider(),
}
}
@@ -52,9 +52,7 @@ func MakeKustomizer(o *Options) *Kustomizer {
// and Run can be called on each of them).
func (b *Kustomizer) Run(
fSys filesys.FileSystem, path string) (resmap.ResMap, error) {
resmapFactory := resmap.NewFactory(
b.depProvider.GetResourceFactory(),
b.depProvider.GetConflictDetectorFactory())
resmapFactory := resmap.NewFactory(b.depProvider.GetResourceFactory())
lr := fLdr.RestrictionNone
if b.options.LoadRestrictions == types.LoadRestrictionsRootOnly {
lr = fLdr.RestrictionRootOnly
@@ -68,7 +66,8 @@ func (b *Kustomizer) Run(
ldr,
b.depProvider.GetFieldValidator(),
resmapFactory,
pLdr.NewLoader(b.options.PluginConfig, resmapFactory),
// The plugin configs are always located on disk, regardless of the fSys passed in
pLdr.NewLoader(b.options.PluginConfig, resmapFactory, filesys.MakeFsOnDisk()),
)
err = kt.Load()
if err != nil {

View File

@@ -4,8 +4,6 @@
package krusty_test
import (
"fmt"
"strings"
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
@@ -237,6 +235,7 @@ metadata:
`)
}
// Goal is to remove " emptyDir: {}" with a patch.
func TestRemoveEmptyDirWithPatchesAtSameLevel(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
@@ -300,7 +299,9 @@ spec:
`)
opts := th.MakeDefaultOptions()
m := th.Run("overlay", opts)
expFmt := `apiVersion: apps/v1
th.AssertActualEqualsExpected(
m, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
@@ -315,21 +316,11 @@ spec:
name: nginx
- image: sidecar:latest
name: sidecar
volumes:%s
name: nginx-persistent-storage
`
th.AssertActualEqualsExpected(
// TODO(#3394): Should be possible to delete emptyDir with a patch.
// TODO(#3304): DECISION - still a bug, emptyDir should be deleted.
m, opts.IfApiMachineryElseKyaml(
fmt.Sprintf(expFmt, `
volumes:
- gcePersistentDisk:
pdName: nginx-persistent-storage`),
fmt.Sprintf(expFmt, `
- emptyDir: {}
gcePersistentDisk:
pdName: nginx-persistent-storage`),
))
pdName: nginx-persistent-storage
name: nginx-persistent-storage
`)
}
func TestSimpleMultiplePatches(t *testing.T) {
@@ -715,7 +706,7 @@ metadata:
`)
}
func TestMultiplePatchesWithConflict(t *testing.T) {
func TestNonCommutablePatches(t *testing.T) {
th := kusttest_test.MakeHarness(t)
makeCommonFilesForMultiplePatchTests(th)
th.WriteF("overlay/staging/deployment-patch1.yaml", `
@@ -753,12 +744,10 @@ spec:
- name: ENABLE_FEATURE_FOO
value: FALSE
`)
opts := th.MakeDefaultOptions()
if opts.UseKyaml {
// kyaml doesn't try to detect conflicts in patches
// (so ENABLE_FEATURE_FOO FALSE wins).
m := th.Run("overlay/staging", opts)
th.AssertActualEqualsExpected(m, `
// kyaml doesn't try to detect conflicts in patches
// (so ENABLE_FEATURE_FOO FALSE wins).
m := th.Run("overlay/staging", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -852,16 +841,6 @@ metadata:
env: staging
name: staging-configmap-in-overlay-dc6fm46dhm
`)
} else {
err := th.RunWithErr("overlay/staging", opts)
if err == nil {
t.Fatalf("expected conflict")
}
if !strings.Contains(
err.Error(), "conflict between ") {
t.Fatalf("Unexpected err: %v", err)
}
}
}
func TestMultiplePatchesWithOnePatchDeleteDirective(t *testing.T) {
@@ -889,25 +868,23 @@ spec:
- $patch: delete
name: sidecar
`
cases := []struct {
name string
cases := map[string]struct {
patch1 string
patch2 string
expectError bool
}{
{
name: "Patch with delete directive first",
"Patch with delete directive first": {
patch1: deletePatch,
patch2: additivePatch,
},
{
name: "Patch with delete directive second",
"Patch with delete directive second": {
patch1: additivePatch,
patch2: deletePatch,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
for name := range cases {
c := cases[name]
t.Run(name, func(t *testing.T) {
th := kusttest_test.MakeHarness(t)
makeCommonFilesForMultiplePatchTests(th)
th.WriteF("overlay/staging/deployment-patch1.yaml", c.patch1)
@@ -1029,12 +1006,10 @@ spec:
- $patch: delete
name: nginx
`)
opt := th.MakeDefaultOptions()
if opt.UseKyaml {
// kyaml doesn't fail on conflicts in patches; both containers
// (nginx and sidecar) are deleted per this patching instruction.
m := th.Run("overlay/staging", opt)
th.AssertActualEqualsExpected(m, `
// kyaml doesn't fail on conflicts in patches; both containers
// (nginx and sidecar) are deleted per this patching instruction.
m := th.Run("overlay/staging", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -1112,17 +1087,6 @@ metadata:
env: staging
name: staging-configmap-in-overlay-dc6fm46dhm
`)
} else {
// No kyaml means error on a patch conflict.
err := th.RunWithErr("overlay/staging", opt)
if err == nil {
t.Fatalf("Expected error")
}
if !strings.Contains(
err.Error(), "both containing ") {
t.Fatalf("Unexpected err: %v", err)
}
}
}
// test for #3513
@@ -1191,26 +1155,26 @@ spec:
- image: dashicorp/consul:1.9.1
name: consul
ports:
- containerPort: 8500
name: http
- containerPort: 8501
name: https
- containerPort: 8301
name: serflan-tcp
protocol: TCP
- containerPort: 8301
name: serflan-udp
protocol: UDP
- containerPort: 8302
name: serfwan
- containerPort: 8300
name: server
- containerPort: 8600
name: dns-tcp
protocol: TCP
- containerPort: 8600
name: dns-udp
protocol: UDP
- containerPort: 8500
name: http
- containerPort: 8501
name: https
- containerPort: 8302
name: serfwan
- containerPort: 8300
name: server
`)
}
@@ -1262,3 +1226,113 @@ spec:
- name: rabbitmq.rules
`)
}
// test for #3620
func TestPatchPortHasNoProtocol(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
- service.yaml
patchesStrategicMerge:
- patch.yaml
`)
th.WriteF("service.yaml", `
apiVersion: v1
kind: Service
metadata:
name: web
spec:
ports:
- port: 30900
targetPort: 30900
protocol: TCP
type: NodePort
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: Service
metadata:
name: web
labels:
service: web
spec:
ports:
- port: 30900
targetPort: 30900
selector:
service: web
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Service
metadata:
labels:
service: web
name: web
spec:
ports:
- port: 30900
protocol: TCP
targetPort: 30900
selector:
service: web
type: NodePort
`)
}
// test for #3620
func TestPatchAddNewServicePort(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
- service.yaml
patchesStrategicMerge:
- patch.yaml
`)
th.WriteF("service.yaml", `
apiVersion: v1
kind: Service
metadata:
name: web
spec:
ports:
- port: 30900
targetPort: 30900
protocol: TCP
type: NodePort
`)
th.WriteF("patch.yaml", `
apiVersion: v1
kind: Service
metadata:
name: web
labels:
service: web
spec:
ports:
- port: 30901
targetPort: 30901
selector:
service: web
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Service
metadata:
labels:
service: web
name: web
spec:
ports:
- port: 30901
targetPort: 30901
- port: 30900
protocol: TCP
targetPort: 30900
selector:
service: web
type: NodePort
`)
}

View File

@@ -1,7 +1,6 @@
package krusty_test
import (
"fmt"
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
@@ -265,9 +264,9 @@ kind: ServiceAccount
metadata:
name: external-dns
`)
opts := th.MakeDefaultOptions()
m := th.Run(".", opts)
expFmt := `
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(
m, `
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
@@ -353,7 +352,7 @@ spec:
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file-%s
secretName: azure-config-file-66cc4224mm
---
apiVersion: v1
kind: ServiceAccount
@@ -366,13 +365,18 @@ metadata:
---
apiVersion: v1
data:
azure.json: %s
azure.json: |
ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCS
JzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIs
CgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW
50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhY
WFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==
kind: Secret
metadata:
labels:
app: external-dns
instance: public
name: azure-config-file-%s
name: azure-config-file-66cc4224mm
namespace: kube-system
type: Opaque
---
@@ -461,7 +465,7 @@ spec:
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file-private-%s
secretName: azure-config-file-private-66cc4224mm
---
apiVersion: v1
kind: ServiceAccount
@@ -474,42 +478,21 @@ metadata:
---
apiVersion: v1
data:
azure.json: %s
azure.json: |
ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCS
JzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIs
CgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW
50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhY
WFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==
kind: Secret
metadata:
labels:
app: external-dns
instance: private
name: azure-config-file-private-%s
name: azure-config-file-private-66cc4224mm
namespace: kube-system
type: Opaque
`
const (
nameHashKyaml = "66cc4224mm"
contentKyaml = `|
ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCS
JzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIs
CgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW
50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhY
WFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==`
nameHashApiMach = "g2k4bkgt4d"
// nolint: lll
contentApiMach = `ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCSJzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIsCgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==`
)
th.AssertActualEqualsExpected(
m,
// TODO(#3304): DECISION - kyaml better; not a bug.
opts.IfApiMachineryElseKyaml(
fmt.Sprintf(expFmt,
nameHashApiMach,
contentApiMach, nameHashApiMach,
nameHashApiMach,
contentApiMach, nameHashApiMach),
fmt.Sprintf(expFmt,
nameHashKyaml,
contentKyaml, nameHashKyaml,
nameHashKyaml,
contentKyaml, nameHashKyaml)))
`)
}
func TestEmptyFieldSpecValue(t *testing.T) {

View File

@@ -306,6 +306,19 @@ kind: CustomResourceDefinition
metadata:
name: crds.my.org
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: namespace.crds.my.org
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: crd-svc
namespace: random
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
@@ -407,6 +420,19 @@ kind: CustomResourceDefinition
metadata:
name: crds.my.org
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: namespace.crds.my.org
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: crd-svc
namespace: newnamespace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:

View File

@@ -1,3 +1,6 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (
@@ -183,6 +186,7 @@ openapi:
func TestCustomOpenApiFieldOverlayTakesPrecedence(t *testing.T) {
th := kusttest_test.MakeHarness(t)
openapi.ResetOpenAPI()
th.WriteK("base", `
resources:
- mycrd.yaml

View File

@@ -1,3 +1,6 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (

View File

@@ -5,7 +5,6 @@ package krusty
import (
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/types"
)
@@ -33,37 +32,19 @@ type Options struct {
// Options related to kustomize plugins.
PluginConfig *types.PluginConfig
// TODO(#3588): Delete this field (it's always true).
// When true, use kyaml/ packages to manipulate KRM yaml.
// When false, use k8sdeps/ instead (uses k8s.io/api* packages).
UseKyaml bool
// When true, allow name and kind changing via a patch
// When false, patch name/kind don't overwrite target name/kind
AllowResourceIdChanges bool
}
// MakeDefaultOptions returns a default instance of Options.
func MakeDefaultOptions() *Options {
return &Options{
DoLegacyResourceSort: false,
AddManagedbyLabel: false,
LoadRestrictions: types.LoadRestrictionsRootOnly,
DoPrune: false,
PluginConfig: konfig.DisabledPluginConfig(),
UseKyaml: konfig.FlagEnableKyamlDefaultValue,
AllowResourceIdChanges: false,
DoLegacyResourceSort: false,
AddManagedbyLabel: false,
LoadRestrictions: types.LoadRestrictionsRootOnly,
DoPrune: false,
PluginConfig: types.DisabledPluginConfig(),
}
}
func (o Options) IfApiMachineryElseKyaml(s1, s2 string) string {
if !o.UseKyaml {
return s1
}
return s2
}
// GetBuiltinPluginNames returns a list of builtin plugin names
func GetBuiltinPluginNames() []string {
var ret []string

View File

@@ -4,7 +4,6 @@
package krusty_test
import (
"fmt"
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
@@ -57,7 +56,6 @@ spec:
func TestPodDisruptionBudgetMerging(t *testing.T) {
th := kusttest_test.MakeHarness(t)
opts := th.MakeDefaultOptions()
th.WriteF("pdb-patch.yaml", `
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
@@ -97,26 +95,7 @@ patches:
resources:
- my_file.yaml
`)
m := th.Run(".", opts)
expFmt := `
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
labels:
faceit-pdb: default
name: championships-api
spec:
maxUnavailable: %s
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
labels:
faceit-pdb: default
name: championships-api-2
spec:
maxUnavailable: %s
`
m := th.Run(".", th.MakeDefaultOptions())
// In a PodDisruptionBudget, the fields maxUnavailable
// minAvailable are mutually exclusive, and both can hold
// either an integer, i.e. 10, or string that has to be
@@ -126,9 +105,23 @@ spec:
// the percent sign, quotes can be added and the API server will
// accept it, but they don't have to be added.
th.AssertActualEqualsExpected(
m,
// TODO(#3304): DECISION - kyaml better; not a bug.
opts.IfApiMachineryElseKyaml(
fmt.Sprintf(expFmt, `"1"`, `"1"`),
fmt.Sprintf(expFmt, `1`, `1`)))
m, `
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
labels:
faceit-pdb: default
name: championships-api
spec:
maxUnavailable: 1
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
labels:
faceit-pdb: default
name: championships-api-2
spec:
maxUnavailable: 1
`)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
// Reproduce issue #3732
func TestValidatingWebhookCombinedNamespaces(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- service.yaml
- validatingwebhook.yaml
`)
th.WriteF("base/service.yaml", `
apiVersion: v1
kind: Service
metadata:
name: admission
namespace: base-namespace
spec:
type: ClusterIP
ports:
- name: https-webhook
port: 443
targetPort: webhook
`)
th.WriteF("base/validatingwebhook.yaml", `
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validatingwebhook
webhooks:
- name: validate
matchPolicy: Equivalent
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- ingresses
failurePolicy: Fail
sideEffects: None
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
namespace: base-namespace
name: admission
path: /networking/v1beta1/ingresses
`)
th.WriteK("overlay", `
namespace: merge-namespace
resources:
- ../base
patchesStrategicMerge:
- validatingwebhookdelete.yaml
`)
th.WriteF("overlay/validatingwebhookdelete.yaml", `
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validatingwebhook
$patch: delete
`)
th.WriteK("combined", `
resources:
- ../base
- ../overlay
`)
m := th.Run("combined", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Service
metadata:
name: admission
namespace: base-namespace
spec:
ports:
- name: https-webhook
port: 443
targetPort: webhook
type: ClusterIP
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validatingwebhook
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: admission
namespace: merge-namespace
path: /networking/v1beta1/ingresses
failurePolicy: Fail
matchPolicy: Equivalent
name: validate
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- ingresses
sideEffects: None
---
apiVersion: v1
kind: Service
metadata:
name: admission
namespace: merge-namespace
spec:
ports:
- name: https-webhook
port: 443
targetPort: webhook
type: ClusterIP
`)
}

View File

@@ -4,198 +4,39 @@
package provider
import (
"log"
"sigs.k8s.io/kustomize/api/hasher"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/conflict"
"sigs.k8s.io/kustomize/api/internal/validate"
"sigs.k8s.io/kustomize/api/internal/wrappy"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resource"
)
// DepProvider is a dependency provider.
//
// The instances it returns are either
// - old implementations backed by k8sdeps code,
// - new implementations backed by kyaml code.
//
// History:
//
// kubectl depends on k8s.io code, and at the time of writing, so
// does kustomize. Code that imports k8s.io/api* cannot be imported
// back into k8s.io/*, yet kustomize appears inside k8s.io/kubectl.
//
// To allow kustomize to appear inside kubectl, yet still be developed
// outside kubectl, the kustomize code was divided into the following
// packages
//
// api/
// k8sdeps/ (and internal/ks8deps/)
// ifc/
// krusty/
// everythingElse/
//
// with the following rules:
//
// - Only k8sdeps/ may import k8s.io/api*.
//
// - Only krusty/ (and its internals) may import k8sdeps/.
// I.e., ifc/ and everythingElse/ must not
// import k8sdeps/ or k8s.io/api*.
//
// - Code in krusty/ may use code in k8sdeps/ to create
// objects then inject said objects into
// everythingElse/ behind dependency neutral interfaces.
//
// The idea was to periodically copy, not import, the large k8sdeps/
// tree (plus a snippet from krusty/kustomizer.go) into the kubectl
// codebase via a large PR, and have kubectl depend on the rest via
// normal importing.
//
// Over 2019, however, kubectl underwent large changes including
// a switch to Go modules, and a concerted attempt to extract kubectl
// from the k8s repo. This made large kustomize integration PRs too
// intrusive to review.
//
// In 2020, kubectl is based on Go modules, and almost entirely
// extracted from the k8s.io repositories, and further the kyaml
// library has a appeared as a viable replacement to k8s.io/api*
// KRM manipulation code.
//
// The new plan is to eliminate k8sdeps/ entirely, along with its
// k8s.io/api* dependence, allowing kustomize code to be imported
// into kubectl via normal Go module imports. Then the kustomize API
// code can then move into the github.com/kubernetes-sigs/cli-utils
// repo. The kustomize CLI in github.com/kubernetes-sigs/kustomize
// and the kubectl CLI can then both depend on the kustomize API.
//
// So, all code that depends on k8sdeps must go behind interfaces,
// and kustomize must be factored to choose the implementation.
//
// That problem has been reduced to three interfaces, each having
// two implementations. (1) is k8sdeps-based, (2) is kyaml-based.
//
// - ifc.Kunstructured
//
// 1) api/k8sdeps/kunstruct.UnstructAdapter
//
// This adapts structs in
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
// to ifc.Kunstructured.
//
// 2) api/wrappy.WNode
//
// This adapts sigs.k8s.io/kustomize/kyaml/yaml.RNode
// to ifc.Unstructured.
//
// At time of writing, implementation started.
// Further reducing the size of ifc.Kunstructed
// would really reduce the work
// (e.g. drop Vars, drop ReplacementTranformer).
//
// - resource.ConflictDetector
//
// 1) api/internal/k8sdeps/conflict.conflictDetectorJson
// api/internal/k8sdeps/conflict.conflictDetectorSm
//
// Uses k8s.io/apimachinery/pkg/util/strategicpatch,
// apimachinery/pkg/util/mergepatch, etc. to merge
// resource.Resource instances.
//
// 2) api/internal/conflict.smPatchMergeOnlyDetector
//
// At time of writing, this doesn't report conflicts,
// but it does know how to merge patches. Conflict
// reporting isn't vital to kustomize function. It's
// rare that a person would configure one transformer
// with many patches, much less so many that it became
// hard to spot conflicts. In the case of an undetected
// conflict, the last patch applied wins, likely what
// the user wants anyway. Regardless, the effect of this
// is plainly visible and usable in the output, even if
// a conflict happened but wasn't reported as an error.
//
// - ifc.Validator
//
// 1) api/k8sdeps/validator.KustValidator
//
// Uses k8s.io/apimachinery/pkg/api/validation and
// friends to validate strings.
//
// 2) api/internal/validate.FieldValidator
//
// See TODO inside the validator for status.
// At time of writing, this is a do-nothing
// validator as it's not critical to kustomize function.
//
// Proposed plan:
// [x] Ship kustomize with the ability to switch from 1 to 2 via
// an --enable_kyaml flag.
// [x] Make --enable_kyaml true by default.
// [x] When 2 is not noticeably more buggy than 1, delete 1.
// I.e. delete k8sdeps/, transitively deleting all k8s.io/api* deps.
// This DepProvider should be left in place to retain these
// comments, but it will have only one choice.
// [x] The way is now clear to reintegrate into kubectl.
// This should be done ASAP; the last step is cleanup.
// [ ] Cleanup. With only one impl of Kunstructure remaining,
// that interface and WNode can be deleted, along with this
// DepProvider. The other two interfaces could be dropped too.
//
// When the above is done, kustomize will use yaml.RNode and/or
// KRM Config Functions directly and exclusively.
// If you're reading this, plan not done.
//
// DepProvider is a dependency provider, injecting different
// implementations depending on the context.
type DepProvider struct {
kFactory ifc.KunstructuredFactory
resourceFactory *resource.Factory
conflictDectectorFactory resource.ConflictDetectorFactory
fieldValidator ifc.Validator
resourceFactory *resource.Factory
// implemented by api/internal/validate.FieldValidator
// See TODO inside the validator for status.
// At time of writing, this is a do-nothing
// validator as it's not critical to kustomize function.
fieldValidator ifc.Validator
}
// The dependencies this method needs have been deleted -
// see comments above. This method will be deleted
// along with DepProvider in the final step.
func makeK8sdepBasedInstances() *DepProvider {
log.Fatal("This binary cannot use k8s.io code; it must use kyaml.")
return nil
}
func makeKyamlBasedInstances() *DepProvider {
kf := &wrappy.WNodeFactory{}
rf := resource.NewFactory(kf)
func NewDepProvider() *DepProvider {
rf := resource.NewFactory(&hasher.Hasher{})
return &DepProvider{
kFactory: kf,
resourceFactory: rf,
conflictDectectorFactory: conflict.NewFactory(),
fieldValidator: validate.NewFieldValidator(),
resourceFactory: rf,
fieldValidator: validate.NewFieldValidator(),
}
}
func NewDepProvider(useKyaml bool) *DepProvider {
if useKyaml {
return makeKyamlBasedInstances()
}
return makeK8sdepBasedInstances()
}
func NewDefaultDepProvider() *DepProvider {
return NewDepProvider(konfig.FlagEnableKyamlDefaultValue)
}
func (dp *DepProvider) GetKunstructuredFactory() ifc.KunstructuredFactory {
return dp.kFactory
return NewDepProvider()
}
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
return dp.resourceFactory
}
func (dp *DepProvider) GetConflictDetectorFactory() resource.ConflictDetectorFactory {
return dp.conflictDectectorFactory
}
func (dp *DepProvider) GetFieldValidator() ifc.Validator {
return dp.fieldValidator
}

View File

@@ -36,6 +36,14 @@ func ParseGroupVersion(apiVersion string) (group, version string) {
// GvkFromString makes a Gvk from the output of Gvk.String().
func GvkFromString(s string) Gvk {
values := strings.Split(s, fieldSep)
if len(values) != 3 {
// ...then the string didn't come from Gvk.String().
return Gvk{
Group: noGroup,
Version: noVersion,
Kind: noKind,
}
}
g := values[0]
if g == noGroup {
g = ""
@@ -213,7 +221,10 @@ func (x Gvk) toKyamlTypeMeta() yaml.TypeMeta {
}
}
// IsNamespaceableKind returns true if x is a namespaceable Gvk
// IsNamespaceableKind returns true if x is a namespaceable Gvk,
// e.g. instances of Pod and Deployment are namespaceable,
// but instances of Node and Namespace are not namespaceable.
// Alternative name for this method: IsNotClusterScoped.
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
func (x Gvk) IsNamespaceableKind() bool {
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())

View File

@@ -103,6 +103,12 @@ func TestString(t *testing.T) {
}
}
func TestGvkFromString(t *testing.T) {
for _, hey := range stringTests {
assert.Equal(t, hey.x, GvkFromString(hey.s))
}
}
func TestApiVersion(t *testing.T) {
for _, hey := range []struct {
x Gvk

View File

@@ -16,14 +16,11 @@ import (
type Factory struct {
// Makes resources.
resF *resource.Factory
// Makes ConflictDetectors.
cdf resource.ConflictDetectorFactory
}
// NewFactory returns a new resmap.Factory.
func NewFactory(
rf *resource.Factory, cdf resource.ConflictDetectorFactory) *Factory {
return &Factory{resF: rf, cdf: cdf}
func NewFactory(rf *resource.Factory) *Factory {
return &Factory{resF: rf}
}
// RF returns a resource.Factory.
@@ -126,13 +123,6 @@ func (rmF *Factory) FromSecretArgs(
return rmF.FromResource(res), nil
}
// ConflatePatches creates a new ResMap containing a merger of the
// incoming patches.
// Error if conflict found.
func (rmF *Factory) ConflatePatches(patches []*resource.Resource) (ResMap, error) {
return (&merginator{cdf: rmF.cdf}).ConflatePatches(patches)
}
func newResMapFromResourceSlice(
resources []*resource.Resource) (ResMap, error) {
result := New()
@@ -146,18 +136,10 @@ func newResMapFromResourceSlice(
}
// NewResMapFromRNodeSlice returns a ResMap from a slice of RNodes
func (rmF *Factory) NewResMapFromRNodeSlice(rnodes []*yaml.RNode) (ResMap, error) {
var resources []*resource.Resource
for _, rnode := range rnodes {
s, err := rnode.String()
if err != nil {
return nil, err
}
r, err := rmF.resF.SliceFromBytes([]byte(s))
if err != nil {
return nil, err
}
resources = append(resources, r...)
func (rmF *Factory) NewResMapFromRNodeSlice(s []*yaml.RNode) (ResMap, error) {
rs, err := rmF.resF.ResourcesFromRNodes(s)
if err != nil {
return nil, err
}
return newResMapFromResourceSlice(resources)
return newResMapFromResourceSlice(rs)
}

View File

@@ -10,11 +10,9 @@ import (
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/kv"
"sigs.k8s.io/kustomize/api/loader"
. "sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
"sigs.k8s.io/kustomize/api/types"
@@ -337,82 +335,17 @@ metadata:
expected: resmaptest_test.NewRmBuilder(t, rf).ResMap(),
},
}
for name, tc := range testcases {
rnodes := []*yaml.RNode{
yaml.MustParse(tc.input),
}
rm, err := rmF.NewResMapFromRNodeSlice(rnodes)
if err != nil {
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
}
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
t.Fatalf("error in test case [%s]: %s", name, err)
}
for name := range testcases {
tc := testcases[name]
t.Run(name, func(t *testing.T) {
rm, err := rmF.NewResMapFromRNodeSlice(
[]*yaml.RNode{yaml.MustParse(tc.input)})
if err != nil {
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
}
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
t.Fatalf("error in test case [%s]: %s", name, err)
}
})
}
}
func TestConflatePatches_Empty(t *testing.T) {
rm, err := rmF.ConflatePatches([]*resource.Resource{})
assert.NoError(t, err)
assert.Equal(t, 0, rm.Size())
}
func TestConflatePatches(t *testing.T) {
var (
err error
yml []byte
r1, r2 *resource.Resource
)
r1, err = rf.FromBytes([]byte(`apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
B:
C: Z
`))
assert.NoError(t, err)
r2, err = rf.FromBytes([]byte(`apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: Z
D: W
baz:
hello: world
`))
assert.NoError(t, err)
rm, err := rmF.ConflatePatches([]*resource.Resource{r1, r2})
assert.NoError(t, err)
yml, err = rm.AsYaml()
assert.NoError(t, err)
// TODO(#3304): DECISION - kyaml better; not a bug.
assert.Equal(t, konfig.IfApiMachineryElseKyaml(`apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
B: null
C: Z
D: W
baz:
hello: world
`, `apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: Z
D: W
baz:
hello: world
`), string(yml))
}

View File

@@ -1,123 +0,0 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package resmap
import (
"fmt"
"sigs.k8s.io/kustomize/api/resource"
)
// merginator coordinates merging the resources in incoming to the result.
type merginator struct {
incoming []*resource.Resource
cdf resource.ConflictDetectorFactory
result ResMap
}
func (m *merginator) ConflatePatches(in []*resource.Resource) (ResMap, error) {
m.result = New()
m.incoming = in
for index := range m.incoming {
alreadyInResult, err := m.appendIfNoMatch(index)
if err != nil {
return nil, err
}
if alreadyInResult != nil {
// The resource at index has the same resId as a previously
// considered resource.
//
// If they conflict with each other (e.g. they both want to change
// the image name in a Deployment, but to different values),
// return an error.
//
// If they don't conflict, then merge them into a single resource,
// since they both target the same item, and we want cumulative
// behavior. E.g. say both patches modify a map. Without a merge,
// the last patch wins, replacing the entire map.
err = m.mergeWithExisting(index, alreadyInResult)
if err != nil {
return nil, err
}
}
}
return m.result, nil
}
func (m *merginator) appendIfNoMatch(index int) (*resource.Resource, error) {
candidate := m.incoming[index]
matchedResources := m.result.GetMatchingResourcesByAnyId(
candidate.OrgId().Equals)
if len(matchedResources) == 0 {
m.result.Append(candidate)
return nil, nil
}
if len(matchedResources) > 1 {
return nil, fmt.Errorf("multiple resources targeted by patch")
}
return matchedResources[0], nil
}
func (m *merginator) mergeWithExisting(
index int, alreadyInResult *resource.Resource) error {
candidate := m.incoming[index]
cd, err := m.cdf.New(candidate.OrgId().Gvk)
if err != nil {
return err
}
hasConflict, err := cd.HasConflict(candidate, alreadyInResult)
if err != nil {
return err
}
if hasConflict {
return m.makeError(cd, index)
}
merged, err := cd.MergePatches(alreadyInResult, candidate)
if err != nil {
return err
}
_, err = m.result.Replace(merged)
return err
}
// Make an error message describing the conflict.
func (m *merginator) makeError(cd resource.ConflictDetector, index int) error {
conflict, err := m.findConflict(cd, index)
if err != nil {
return err
}
if conflict == nil {
return fmt.Errorf("expected conflict for %s", m.incoming[index].OrgId())
}
conflictMap, _ := conflict.Map()
incomingIndexMap, _ := m.incoming[index].Map()
return fmt.Errorf(
"conflict between %#v at index %d and %#v",
incomingIndexMap,
index,
conflictMap,
)
}
// findConflict looks for a conflict in a resource slice.
// It returns the first conflict between the resource at index
// and some other resource. Two resources can only conflict if
// they have the same original ResId.
func (m *merginator) findConflict(
cd resource.ConflictDetector, index int) (*resource.Resource, error) {
targetId := m.incoming[index].OrgId()
for i, p := range m.incoming {
if i == index || !targetId.Equals(p.OrgId()) {
continue
}
conflict, err := cd.HasConflict(p, m.incoming[index])
if err != nil {
return nil, err
}
if conflict {
return p, nil
}
}
return nil, nil
}

View File

@@ -80,6 +80,9 @@ type TransformerPlugin interface {
// resource to transform, try the OrgId first, and if this
// fails or finds too many, it might make sense to then try
// the CurrId. Depends on the situation.
//
// TODO: get rid of this interface (use bare resWrangler).
// There aren't multiple implementations any more.
type ResMap interface {
// Size reports the number of resources.
Size() int
@@ -189,6 +192,9 @@ type ResMap interface {
// Clear removes all resources and Ids.
Clear()
// DropEmpties drops empty resources from the ResMap.
DropEmpties()
// SubsetThatCouldBeReferencedByResource returns a ResMap subset
// of self with resources that could be referenced by the
// resource argument.
@@ -231,9 +237,8 @@ type ResMap interface {
// are selected by a Selector
Select(types.Selector) ([]*resource.Resource, error)
// ToRNodeSlice converts the resources in the resmp
// to a list of RNodes
ToRNodeSlice() ([]*yaml.RNode, error)
// ToRNodeSlice returns a copy of the resources as RNodes.
ToRNodeSlice() []*yaml.RNode
// ApplySmPatch applies a strategic-merge patch to the
// selected set of resources.

View File

@@ -6,14 +6,12 @@ package resmap
import (
"bytes"
"fmt"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
kyaml_yaml "sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/yaml"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
)
// resWrangler implements ResMap.
@@ -38,6 +36,18 @@ func (m *resWrangler) Clear() {
m.rList = nil
}
// DropEmpties quickly drops empty resources.
// It doesn't use Append, which checks for Id collisions.
func (m *resWrangler) DropEmpties() {
var rList []*resource.Resource
for _, r := range m.rList {
if !r.IsEmpty() {
rList = append(rList, r)
}
}
m.rList = rList
}
// Size implements ResMap.
func (m *resWrangler) Size() int {
return len(m.rList)
@@ -66,22 +76,27 @@ func (m *resWrangler) Append(res *resource.Resource) error {
return fmt.Errorf(
"may not add resource with an already registered id: %s", id)
}
m.rList = append(m.rList, res)
m.append(res)
return nil
}
// append appends without performing an Id check
func (m *resWrangler) append(res *resource.Resource) {
m.rList = append(m.rList, res)
}
// Remove implements ResMap.
func (m *resWrangler) Remove(adios resid.ResId) error {
tmp := newOne()
var rList []*resource.Resource
for _, r := range m.rList {
if r.CurId() != adios {
tmp.Append(r)
rList = append(rList, r)
}
}
if tmp.Size() != m.Size()-1 {
if len(rList) != m.Size()-1 {
return fmt.Errorf("id %s not found in removal", adios)
}
m.rList = tmp.rList
m.rList = rList
return nil
}
@@ -118,16 +133,7 @@ func (m *resWrangler) Debug(title string) {
} else {
fmt.Println("---")
}
fmt.Printf("# %d %s\n", i, r.OrgId())
m, err := r.Map()
if err != nil {
panic(err)
}
blob, err := yaml.Marshal(m)
if err != nil {
panic(err)
}
fmt.Println(string(blob))
fmt.Printf("# %d %s\n%s\n", i, r.OrgId(), r.String())
}
}
@@ -273,7 +279,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
firstObj := true
var b []byte
buf := bytes.NewBuffer(b)
for _, res := range m.Resources() {
for _, res := range m.rList {
out, err := res.AsYAML()
if err != nil {
m, _ := res.Map()
@@ -297,7 +303,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
m2, ok := other.(*resWrangler)
if !ok {
panic("bad cast")
return fmt.Errorf("bad cast to resWrangler 1")
}
if m.Size() != m2.Size() {
return fmt.Errorf(
@@ -317,9 +323,9 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
"id in self matches %d in other; id: %s", len(others), id)
}
r2 := others[0]
if !r1.KunstructEqual(r2) {
if !r1.NodeEqual(r2) {
return fmt.Errorf(
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
"nodes unequal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
r1, r2, r1, r2)
}
seen[m2.indexOfResource(r2)] = true
@@ -334,7 +340,7 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
m2, ok := other.(*resWrangler)
if !ok {
panic("bad cast")
return fmt.Errorf("bad cast to resWrangler 2")
}
if m.Size() != m2.Size() {
return fmt.Errorf(
@@ -388,7 +394,7 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
}
result := newOne()
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
for _, possibleTarget := range m.Resources() {
for _, possibleTarget := range m.rList {
id := possibleTarget.CurId()
if !id.IsNamespaceableKind() {
// A cluster-scoped resource can be referred to by anything.
@@ -435,16 +441,21 @@ func getNamespacesForRoleBinding(r *resource.Resource) map[string]bool {
return result
}
func (m *resWrangler) append(res *resource.Resource) {
m.rList = append(m.rList, res)
}
// AppendAll implements ResMap.
func (m *resWrangler) AppendAll(other ResMap) error {
if other == nil {
return nil
}
for _, res := range other.Resources() {
m2, ok := other.(*resWrangler)
if !ok {
return fmt.Errorf("bad cast to resWrangler 3")
}
return m.appendAll(m2.rList)
}
// appendAll appends all the resources, error on Id collision.
func (m *resWrangler) appendAll(list []*resource.Resource) error {
for _, res := range list {
if err := m.Append(res); err != nil {
return err
}
@@ -457,7 +468,11 @@ func (m *resWrangler) AbsorbAll(other ResMap) error {
if other == nil {
return nil
}
for _, r := range other.Resources() {
m2, ok := other.(*resWrangler)
if !ok {
return fmt.Errorf("bad cast to resWrangler 4")
}
for _, r := range m2.rList {
err := m.appendReplaceOrMerge(r)
if err != nil {
return err
@@ -522,7 +537,7 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
if err != nil {
return nil, err
}
for _, r := range m.Resources() {
for _, r := range m.rList {
curId := r.CurId()
orgId := r.OrgId()
@@ -567,77 +582,39 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
return result, nil
}
// ToRNodeSlice converts the resources in the resmp
// to a list of RNodes
func (m *resWrangler) ToRNodeSlice() ([]*kyaml_yaml.RNode, error) {
var rnodes []*kyaml_yaml.RNode
for _, r := range m.Resources() {
s, err := r.AsYAML()
if err != nil {
return nil, err
}
rnode, err := kyaml_yaml.Parse(string(s))
if err != nil {
return nil, err
}
rnodes = append(rnodes, rnode)
// ToRNodeSlice returns a copy of the resources as RNodes.
func (m *resWrangler) ToRNodeSlice() []*kyaml.RNode {
result := make([]*kyaml.RNode, len(m.rList))
for i := range m.rList {
result[i] = m.rList[i].AsRNode()
}
return rnodes, nil
return result
}
// ApplySmPatch applies the patch, and errors on Id collisions.
func (m *resWrangler) ApplySmPatch(
selectedSet *resource.IdSet, patch *resource.Resource) error {
newRm := New()
for _, res := range m.Resources() {
if !selectedSet.Contains(res.CurId()) {
newRm.Append(res)
continue
}
patchCopy := patch.DeepCopy()
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
patchCopy.SetGvk(res.GetGvk())
err := res.ApplySmPatch(patchCopy)
if err != nil {
// Check for an error string from UnmarshalJSON that's indicative
// of an object that's missing basic KRM fields, and thus may have been
// entirely deleted (an acceptable outcome). This error handling should
// be deleted along with use of ResMap and apimachinery functions like
// UnmarshalJSON.
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
// Some unknown error, let it through.
var list []*resource.Resource
for _, res := range m.rList {
if selectedSet.Contains(res.CurId()) {
patchCopy := patch.DeepCopy()
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
patchCopy.SetGvk(res.GetGvk())
patchCopy.SetKind(patch.GetKind())
if err := res.ApplySmPatch(patchCopy); err != nil {
return err
}
empty, err := res.IsEmpty()
if err != nil {
return err
}
if !empty {
m, _ := res.Map()
return errors.Wrapf(
err, "with unexpectedly non-empty object map of size %d",
len(m))
}
// Fall through to handle deleted object.
}
empty, err := res.IsEmpty()
if err != nil {
return err
}
if !empty {
// IsEmpty means all fields have been removed from the object.
// This can happen if a patch required deletion of the
// entire resource (not just a part of it). This means
// the overall resmap must shrink by one.
newRm.Append(res)
if !res.IsEmpty() {
list = append(list, res)
}
}
m.Clear()
m.AppendAll(newRm)
return nil
return m.appendAll(list)
}
func (m *resWrangler) RemoveBuildAnnotations() {
for _, r := range m.Resources() {
for _, r := range m.rList {
r.RemoveBuildAnnotations()
}
}

View File

@@ -21,7 +21,7 @@ import (
var depProvider = provider.NewDefaultDepProvider()
var rf = depProvider.GetResourceFactory()
var rmF = NewFactory(rf, depProvider.GetConflictDetectorFactory())
var rmF = NewFactory(rf)
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
err := w.Append(r)
@@ -339,6 +339,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"metadata": map[string]interface{}{
"name": "new-alice",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap",
"config.kubernetes.io/previousNames": "alice",
"config.kubernetes.io/previousNamespaces": "default",
},
@@ -351,6 +352,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"metadata": map[string]interface{}{
"name": "new-bob",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap,ConfigMap",
"config.kubernetes.io/previousNames": "bob,bob2",
"config.kubernetes.io/previousNamespaces": "default,default",
},
@@ -364,6 +366,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"name": "new-bob",
"namespace": "new-happy",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap",
"config.kubernetes.io/previousNames": "bob",
"config.kubernetes.io/previousNamespaces": "happy",
},
@@ -377,6 +380,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"name": "charlie",
"namespace": "happy",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap",
"config.kubernetes.io/previousNames": "charlie",
"config.kubernetes.io/previousNamespaces": "default",
},
@@ -873,13 +877,8 @@ rules:
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
rnodes, err := rm.ToRNodeSlice()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
b := bytes.NewBufferString("")
for i, n := range rnodes {
for i, n := range rm.ToRNodeSlice() {
if i != 0 {
b.WriteString("---\n")
}

View File

@@ -1,20 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package resource
import "sigs.k8s.io/kustomize/api/resid"
// ConflictDetector detects conflicts between resources.
type ConflictDetector interface {
// HasConflict returns true if the given resources have a conflict.
HasConflict(patch1, patch2 *Resource) (bool, error)
// Merge two resources into one.
MergePatches(patch1, patch2 *Resource) (*Resource, error)
}
// ConflictDetectorFactory makes instances of ConflictDetector that know
// how to handle the given Group, Version, Kind tuple.
type ConflictDetectorFactory interface {
New(gvk resid.Gvk) (ConflictDetector, error)
}

View File

@@ -10,28 +10,33 @@ import (
"strings"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/generators"
"sigs.k8s.io/kustomize/api/internal/kusterr"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Factory makes instances of Resource.
type Factory struct {
kf ifc.KunstructuredFactory
hasher ifc.KustHasher
}
// NewFactory makes an instance of Factory.
func NewFactory(kf ifc.KunstructuredFactory) *Factory {
return &Factory{kf: kf}
func NewFactory(h ifc.KustHasher) *Factory {
return &Factory{hasher: h}
}
func (rf *Factory) Hasher() ifc.KunstructuredHasher {
return rf.kf.Hasher()
// Hasher returns an ifc.KustHasher
func (rf *Factory) Hasher() ifc.KustHasher {
return rf.hasher
}
// FromMap returns a new instance of Resource.
func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
return rf.makeOne(rf.kf.FromMap(m), nil)
return rf.FromMapAndOption(m, nil)
}
// FromMapWithName returns a new instance with the given "original" name.
@@ -41,34 +46,30 @@ func (rf *Factory) FromMapWithName(n string, m map[string]interface{}) *Resource
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
return rf.makeOne(rf.kf.FromMap(m), nil).setPreviousNamespaceAndName(ns, n)
r := rf.FromMapAndOption(m, nil)
return r.setPreviousId(ns, n, r.GetKind())
}
// FromMapAndOption returns a new instance of Resource with given options.
func (rf *Factory) FromMapAndOption(
m map[string]interface{}, args *types.GeneratorArgs) *Resource {
return rf.makeOne(rf.kf.FromMap(m), types.NewGenArgs(args))
}
// FromKunstructured returns a new instance of Resource.
func (rf *Factory) FromKunstructured(u ifc.Kunstructured) *Resource {
return rf.makeOne(u, nil)
n, err := yaml.FromMap(m)
if err != nil {
// TODO: return err instead of log.
log.Fatal(err)
}
return rf.makeOne(n, types.NewGenArgs(args))
}
// makeOne returns a new instance of Resource.
func (rf *Factory) makeOne(
u ifc.Kunstructured, o *types.GenArgs) *Resource {
if u == nil {
log.Fatal("unstruct ifc must not be null")
func (rf *Factory) makeOne(rn *yaml.RNode, o *types.GenArgs) *Resource {
if rn == nil {
log.Fatal("RNode must not be null")
}
if o == nil {
o = types.NewGenArgs(nil)
}
r := &Resource{
kunStr: u,
options: o,
}
return r
return &Resource{node: rn, options: o}
}
// SliceFromPatches returns a slice of resources given a patch path
@@ -105,47 +106,135 @@ func (rf *Factory) FromBytes(in []byte) (*Resource, error) {
// SliceFromBytes unmarshals bytes into a Resource slice.
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
kunStructs, err := rf.kf.SliceFromBytes(in)
nodes, err := rf.RNodesFromBytes(in)
if err != nil {
return nil, err
}
var result []*Resource
for len(kunStructs) > 0 {
u := kunStructs[0]
kunStructs = kunStructs[1:]
if strings.HasSuffix(u.GetKind(), "List") {
m, err := u.Map()
if err != nil {
return nil, err
return rf.resourcesFromRNodes(nodes), nil
}
// ResourcesFromRNodes converts RNodes to Resources.
func (rf *Factory) ResourcesFromRNodes(
nodes []*yaml.RNode) (result []*Resource, err error) {
nodes, err = rf.dropBadNodes(nodes)
if err != nil {
return nil, err
}
return rf.resourcesFromRNodes(nodes), nil
}
// resourcesFromRNode assumes all nodes are good.
func (rf *Factory) resourcesFromRNodes(
nodes []*yaml.RNode) (result []*Resource) {
for _, n := range nodes {
result = append(result, rf.makeOne(n, nil))
}
return
}
func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
nodes, err := kio.FromBytes(b)
if err != nil {
return nil, err
}
nodes, err = rf.dropBadNodes(nodes)
if err != nil {
return nil, err
}
for len(nodes) > 0 {
n0 := nodes[0]
nodes = nodes[1:]
kind := n0.GetKind()
if !strings.HasSuffix(kind, "List") {
result = append(result, n0)
continue
}
// Convert a FooList into a slice of Foo.
var m map[string]interface{}
m, err = n0.Map()
if err != nil {
return nil, err
}
items, ok := m["items"]
if !ok {
// treat as an empty list
continue
}
slice, ok := items.([]interface{})
if !ok {
if items == nil {
// an empty list
continue
}
items := m["items"]
itemsSlice, ok := items.([]interface{})
if !ok {
if items == nil {
// an empty list
continue
}
return nil, fmt.Errorf("items in List is type %T, expected array", items)
}
for _, item := range itemsSlice {
itemJSON, err := json.Marshal(item)
if err != nil {
return nil, err
}
innerU, err := rf.kf.SliceFromBytes(itemJSON)
if err != nil {
return nil, err
}
// append innerU to kunStructs so nested Lists can be handled
kunStructs = append(kunStructs, innerU...)
}
} else {
result = append(result, rf.FromKunstructured(u))
return nil, fmt.Errorf(
"expected array in %s/items, but found %T", kind, items)
}
innerNodes, err := rf.convertObjectSliceToNodeSlice(slice)
if err != nil {
return nil, err
}
nodes = append(nodes, innerNodes...)
}
return result, nil
}
// convertObjectSlice converts a list of objects to a list of RNode.
func (rf *Factory) convertObjectSliceToNodeSlice(
objects []interface{}) (result []*yaml.RNode, err error) {
var bytes []byte
var nodes []*yaml.RNode
for _, obj := range objects {
bytes, err = json.Marshal(obj)
if err != nil {
return
}
nodes, err = kio.FromBytes(bytes)
if err != nil {
return
}
nodes, err = rf.dropBadNodes(nodes)
if err != nil {
return
}
result = append(result, nodes...)
}
return
}
// dropBadNodes may drop some nodes from its input argument.
func (rf *Factory) dropBadNodes(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
var result []*yaml.RNode
for _, n := range nodes {
ignore, err := rf.shouldIgnore(n)
if err != nil {
return nil, err
}
if !ignore {
result = append(result, n)
}
}
return result, nil
}
// shouldIgnore returns true if there's some reason to ignore the node.
func (rf *Factory) shouldIgnore(n *yaml.RNode) (bool, error) {
if n.IsNilOrEmpty() {
return true, nil
}
md, err := n.GetValidatedMetadata()
if err != nil {
return true, err
}
_, ignore := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
if ignore {
return true, nil
}
if foundNil, path := n.HasNilEntryInList(); foundNil {
return true, fmt.Errorf("empty item at %v in object %v", path, n)
}
return false, nil
}
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
// name.
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {
@@ -157,25 +246,25 @@ func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resour
return nil, fmt.Errorf("number of names doesn't match number of resources")
}
for i, res := range result {
res.setPreviousNamespaceAndName(resid.DefaultNamespace, names[i])
res.setPreviousId(resid.DefaultNamespace, names[i], res.GetKind())
}
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)
rn, err := generators.MakeConfigMap(kvLdr, args)
if err != nil {
return nil, err
}
return rf.makeOne(u, types.NewGenArgs(&args.GeneratorArgs)), nil
return rf.makeOne(rn, types.NewGenArgs(&args.GeneratorArgs)), nil
}
// MakeSecret makes an instance of Resource for Secret
func (rf *Factory) MakeSecret(kvLdr ifc.KvLoader, args *types.SecretArgs) (*Resource, error) {
u, err := rf.kf.MakeSecret(kvLdr, args)
rn, err := generators.MakeSecret(kvLdr, args)
if err != nil {
return nil, err
}
return rf.makeOne(u, types.NewGenArgs(&args.GeneratorArgs)), nil
return rf.makeOne(rn, types.NewGenArgs(&args.GeneratorArgs)), nil
}

View File

@@ -5,10 +5,9 @@ package resource_test
import (
"fmt"
"reflect"
"testing"
"sigs.k8s.io/kustomize/api/konfig"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/loader"
@@ -153,35 +152,33 @@ spec:
for name := range testCases {
tc := testCases[name]
t.Run(name, func(t *testing.T) {
result, err := factory.SliceFromBytes([]byte(tc.input))
result, err := factory.RNodesFromBytes([]byte(tc.input))
if err != nil {
t.Fatalf("%v: fails with err: %v", name, err)
}
if len(result) != len(tc.expected) {
for i := range result {
bytes, err := result[i].AsYAML()
str, err := result[i].String()
if err != nil {
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
}
tmp := string(bytes)
t.Logf("--- %d:\n%s", i, tmp)
t.Logf("--- %d:\n%s", i, str)
}
t.Fatalf(
"%v: actual len %d != expected len %d",
name, len(result), len(tc.expected))
}
for i := range tc.expected {
bytes, err := result[i].AsYAML()
str, err := result[i].String()
if err != nil {
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
}
tmp := string(bytes)
if tmp != tc.expected[i] {
if str != tc.expected[i] {
t.Fatalf(
"%v: string mismatch in item %d\n"+
"actual:\n-----\n%s\n-----\n"+
"expected:\n-----\n%s\n-----\n",
name, i, tmp, tc.expected[i])
name, i, str, tc.expected[i])
}
}
})
@@ -309,77 +306,376 @@ kind: List
t.Fatal(err)
}
tests := []struct {
name string
tests := map[string]struct {
input []types.PatchStrategicMerge
expectedOut []*Resource
expectedErr bool
}{
{
name: "happy",
"happy": {
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
expectedOut: []*Resource{testDeployment, testConfigMap},
expectedErr: false,
},
{
name: "badFileName",
"badFileName": {
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
expectedOut: []*Resource{},
expectedErr: true,
},
{
name: "badData",
"badData": {
input: []types.PatchStrategicMerge{patchGood1, patchBad},
expectedOut: []*Resource{},
expectedErr: true,
},
{
name: "listOfPatches",
"listOfPatches": {
input: []types.PatchStrategicMerge{patchList},
expectedOut: []*Resource{testDeployment, testConfigMap},
expectedErr: false,
},
{
name: "listWithAnchorReference",
"listWithAnchorReference": {
input: []types.PatchStrategicMerge{patchList2},
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
// The error using kyaml is:
// json: unsupported type: map[interface {}]interface {}
// maybe arising from too many conversions between
// yaml, json, Resource, RNode, Unstructured etc.
// yaml, json, Resource, RNode, etc.
// These conversions go away after closing #3506
// TODO(#3271) This shouldn't have an error, but does when kyaml is used.
// TODO(#3304): DECISION - still a bug, but not a blocker to #3304 or #2506
expectedErr: konfig.FlagEnableKyamlDefaultValue,
expectedErr: true,
},
{
name: "listWithNoEntries",
"listWithNoEntries": {
input: []types.PatchStrategicMerge{patchList3},
expectedOut: []*Resource{},
expectedErr: false,
},
{
name: "listWithNoItems",
"listWithNoItems": {
input: []types.PatchStrategicMerge{patchList4},
expectedOut: []*Resource{},
expectedErr: false,
},
}
for _, test := range tests {
rs, err := factory.SliceFromPatches(ldr, test.input)
if err != nil {
assert.True(t, test.expectedErr,
fmt.Sprintf("in test %s, got unexpected error: %v", test.name, err))
continue
}
assert.False(t, test.expectedErr, "expected no error in "+test.name)
assert.Equal(t, len(test.expectedOut), len(rs))
for i := range rs {
expYaml, err := test.expectedOut[i].AsYAML()
assert.NoError(t, err)
actYaml, err := rs[i].AsYAML()
assert.NoError(t, err)
assert.Equal(t, expYaml, actYaml)
}
for n, test := range tests {
t.Run(n, func(t *testing.T) {
rs, err := factory.SliceFromPatches(ldr, test.input)
if err != nil {
assert.True(t, test.expectedErr,
fmt.Sprintf("in test %s, got unexpected error: %v", n, err))
return
}
assert.False(t, test.expectedErr, "expected no error in "+n)
assert.Equal(t, len(test.expectedOut), len(rs))
for i := range rs {
expYaml, err := test.expectedOut[i].AsYAML()
assert.NoError(t, err)
actYaml, err := rs[i].AsYAML()
assert.NoError(t, err)
assert.Equal(t, expYaml, actYaml)
}
})
}
}
func TestHash(t *testing.T) {
input := `
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
one: ""
binaryData:
two: ""
`
expect := "698h7c7t9m"
k, err := factory.SliceFromBytes([]byte(input))
if err != nil {
t.Fatal(err)
}
result, err := k[0].Hash(factory.Hasher())
if err != nil {
t.Fatal(err)
}
if result != expect {
t.Fatalf("expect %s but got %s", expect, result)
}
}
func TestSliceFromBytesMore(t *testing.T) {
testConfigMap :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
}
testDeploymentSpec := map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"hostAliases": []interface{}{
map[string]interface{}{
"hostnames": []interface{}{
"a.example.com",
},
"ip": "8.8.8.8",
},
},
},
},
}
testDeploymentA := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-a",
},
"spec": testDeploymentSpec,
}
testDeploymentB := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-b",
},
"spec": testDeploymentSpec,
}
testDeploymentList :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "DeploymentList",
"items": []interface{}{
testDeploymentA,
testDeploymentB,
},
}
type expected struct {
out []map[string]interface{}
isErr bool
}
testCases := map[string]struct {
input []byte
exp expected
}{
"garbage": {
input: []byte("garbageIn: garbageOut"),
exp: expected{
isErr: true,
},
},
"noBytes": {
input: []byte{},
exp: expected{
out: []map[string]interface{}{},
},
},
"goodJson": {
input: []byte(`
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
},
},
"goodYaml1": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
},
},
"goodYaml2": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap, testConfigMap},
},
},
"localConfigYaml": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
# this annotation causes the Resource to be ignored by kustomize
config.kubernetes.io/local-config: ""
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
},
},
"garbageInOneOfTwoObjects": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
`),
exp: expected{
isErr: true,
},
},
"emptyObjects": {
input: []byte(`
---
#a comment
---
`),
exp: expected{
out: []map[string]interface{}{},
},
},
"Missing .metadata.name in object": {
input: []byte(`
apiVersion: v1
kind: Namespace
metadata:
annotations:
foo: bar
`),
exp: expected{
isErr: true,
},
},
"nil value in list": {
input: []byte(`
apiVersion: builtin
kind: ConfigMapGenerator
metadata:
name: kube100-site
labels:
app: web
testList:
- testA
-
`),
exp: expected{
isErr: true,
},
},
"List": {
input: []byte(`
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{
testConfigMap,
testConfigMap},
},
},
"ConfigMapList": {
input: []byte(`
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{
testConfigMap,
testConfigMap,
},
},
},
"listWithAnchors": {
input: []byte(`
apiVersion: v1
kind: DeploymentList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-b
spec:
<<: *hostAliases
`),
exp: expected{
// TODO(3271): This should work.
// https://github.com/kubernetes-sigs/kustomize/issues/3271
// json.Marshal(obj) fails on the 2nd list item.
// The value of the 1st list item's first spec field is
// map[string]interface{}
// The value of the 2nd list item's first spec field is
// map[interface{}]interface{}
// which causes a encoding/json.UnsupportedTypeError.
isErr: true,
out: []map[string]interface{}{testDeploymentList},
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rs, err := factory.RNodesFromBytes(tc.input)
if err != nil {
assert.True(t, tc.exp.isErr)
return
}
assert.False(t, tc.exp.isErr)
assert.Equal(t, len(tc.exp.out), len(rs))
for i := range rs {
rsMap, err := rs[i].Map()
assert.NoError(t, err)
assert.Equal(
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
m, _ := rs[i].Map()
if !reflect.DeepEqual(tc.exp.out[i], m) {
t.Fatalf("%s:\nexpected: %v\n actual: %v",
n, tc.exp.out[i], m)
}
}
})
}
}

View File

@@ -12,153 +12,184 @@ import (
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/wrappy"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filtersutil"
"sigs.k8s.io/kustomize/kyaml/kio"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/yaml"
)
// Resource is a representation of a Kubernetes Resource Model (KRM) object
// Resource is an RNode, representing a Kubernetes Resource Model object,
// paired with metadata used by kustomize.
// For more history, see sigs.k8s.io/kustomize/api/ifc.Unstructured
type Resource struct {
kunStr ifc.Kunstructured
// TODO: Inline RNode, dropping complexity. Resource is just a decorator.
node *kyaml.RNode
options *types.GenArgs
refBy []resid.ResId
refVarNames []string
}
const (
buildAnnotationPreviousKinds = konfig.ConfigAnnoDomain + "/previousKinds"
buildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames"
buildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes"
buildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes"
buildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces"
// 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"
)
var buildAnnotations = []string{
buildAnnotationPreviousKinds,
buildAnnotationPreviousNames,
buildAnnotationPrefixes,
buildAnnotationSuffixes,
buildAnnotationPreviousNamespaces,
buildAnnotationAllowNameChange,
buildAnnotationAllowKindChange,
}
func (r *Resource) AsRNode() *kyaml.RNode {
return r.node.Copy()
}
func (r *Resource) ResetPrimaryData(incoming *Resource) {
r.kunStr = incoming.Copy()
r.node = incoming.node.Copy()
}
func (r *Resource) GetAnnotations() map[string]string {
annotations := r.kunStr.GetAnnotations()
if annotations == nil {
annotations, err := r.node.GetAnnotations()
if err != nil || annotations == nil {
return make(map[string]string)
}
return annotations
}
func (r *Resource) Copy() ifc.Kunstructured {
return r.kunStr.Copy()
}
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
return r.kunStr.GetFieldValue(f)
//nolint:staticcheck
return r.node.GetFieldValue(f)
}
func (r *Resource) GetDataMap() map[string]string {
return r.kunStr.GetDataMap()
return r.node.GetDataMap()
}
func (r *Resource) GetBinaryDataMap() map[string]string {
return r.kunStr.GetBinaryDataMap()
return r.node.GetBinaryDataMap()
}
func (r *Resource) GetGvk() resid.Gvk {
return r.kunStr.GetGvk()
meta, err := r.node.GetMeta()
if err != nil {
return resid.GvkFromString("")
}
g, v := resid.ParseGroupVersion(meta.APIVersion)
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
}
func (r *Resource) Hash(h ifc.KustHasher) (string, error) {
return h.Hash(r.node)
}
func (r *Resource) GetKind() string {
return r.kunStr.GetKind()
return r.node.GetKind()
}
func (r *Resource) GetLabels() map[string]string {
return r.kunStr.GetLabels()
l, err := r.node.GetLabels()
if err != nil {
return map[string]string{}
}
return l
}
func (r *Resource) GetName() string {
return r.kunStr.GetName()
return r.node.GetName()
}
func (r *Resource) GetSlice(p string) ([]interface{}, error) {
return r.kunStr.GetSlice(p)
//nolint:staticcheck
return r.node.GetSlice(p)
}
func (r *Resource) GetString(p string) (string, error) {
return r.kunStr.GetString(p)
//nolint:staticcheck
return r.node.GetString(p)
}
func (r *Resource) IsEmpty() (bool, error) {
m, err := r.kunStr.Map()
return len(m) == 0, err
func (r *Resource) IsEmpty() bool {
return r.node.IsNilOrEmpty()
}
func (r *Resource) Map() (map[string]interface{}, error) {
return r.kunStr.Map()
return r.node.Map()
}
func (r *Resource) MarshalJSON() ([]byte, error) {
return r.kunStr.MarshalJSON()
return r.node.MarshalJSON()
}
func (r *Resource) MatchesLabelSelector(selector string) (bool, error) {
return r.kunStr.MatchesLabelSelector(selector)
return r.node.MatchesLabelSelector(selector)
}
func (r *Resource) MatchesAnnotationSelector(selector string) (bool, error) {
return r.kunStr.MatchesAnnotationSelector(selector)
return r.node.MatchesAnnotationSelector(selector)
}
func (r *Resource) SetAnnotations(m map[string]string) {
if len(m) == 0 {
// Force field erasure.
r.kunStr.SetAnnotations(nil)
r.node.SetAnnotations(nil)
return
}
r.kunStr.SetAnnotations(m)
r.node.SetAnnotations(m)
}
func (r *Resource) SetDataMap(m map[string]string) {
r.kunStr.SetDataMap(m)
r.node.SetDataMap(m)
}
func (r *Resource) SetBinaryDataMap(m map[string]string) {
r.kunStr.SetBinaryDataMap(m)
r.node.SetBinaryDataMap(m)
}
func (r *Resource) SetGvk(gvk resid.Gvk) {
r.kunStr.SetGvk(gvk)
r.node.SetMapField(
kyaml.NewScalarRNode(gvk.Kind), kyaml.KindField)
r.node.SetMapField(
kyaml.NewScalarRNode(gvk.ApiVersion()), kyaml.APIVersionField)
}
func (r *Resource) SetLabels(m map[string]string) {
if len(m) == 0 {
// Force field erasure.
r.kunStr.SetLabels(nil)
r.node.SetLabels(nil)
return
}
r.kunStr.SetLabels(m)
r.node.SetLabels(m)
}
func (r *Resource) SetName(n string) {
r.kunStr.SetName(n)
r.node.SetName(n)
}
func (r *Resource) SetNamespace(n string) {
r.kunStr.SetNamespace(n)
r.node.SetNamespace(n)
}
func (r *Resource) SetKind(k string) {
gvk := r.GetGvk()
gvk.Kind = k
r.SetGvk(gvk)
}
func (r *Resource) UnmarshalJSON(s []byte) error {
return r.kunStr.UnmarshalJSON(s)
return r.node.UnmarshalJSON(s)
}
// ResCtx is an interface describing the contextual added
@@ -178,14 +209,14 @@ type ResCtxMatcher func(ResCtx) bool
// DeepCopy returns a new copy of resource
func (r *Resource) DeepCopy() *Resource {
rc := &Resource{
kunStr: r.Copy(),
node: r.node.Copy(),
}
rc.copyOtherFields(r)
return rc
}
// CopyMergeMetaDataFields copies everything but the non-metadata in
// the ifc.Kunstructured map, merging labels and annotations.
// the resource.
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) {
r.SetLabels(mergeStringMaps(other.GetLabels(), r.GetLabels()))
r.SetAnnotations(
@@ -251,8 +282,10 @@ func (r *Resource) ReferencesEqual(other *Resource) bool {
return len(setSelf) == len(setOther)
}
func (r *Resource) KunstructEqual(o *Resource) bool {
return reflect.DeepEqual(r.kunStr, o.kunStr)
// NodeEqual returns true if the resource's nodes are
// equal, ignoring ancillary information like genargs, refby, etc.
func (r *Resource) NodeEqual(o *Resource) bool {
return reflect.DeepEqual(r.node, o.node)
}
func (r *Resource) copyRefBy() []resid.ResId {
@@ -351,12 +384,41 @@ func (r *Resource) RemoveBuildAnnotations() {
r.SetAnnotations(annotations)
}
func (r *Resource) setPreviousNamespaceAndName(ns string, n string) *Resource {
func (r *Resource) setPreviousId(ns string, n string, k string) *Resource {
r.appendCsvAnnotation(buildAnnotationPreviousNames, n)
r.appendCsvAnnotation(buildAnnotationPreviousNamespaces, ns)
r.appendCsvAnnotation(buildAnnotationPreviousKinds, k)
return r
}
func (r *Resource) SetAllowNameChange(value string) {
annotations := r.GetAnnotations()
annotations[buildAnnotationAllowNameChange] = value
r.SetAnnotations(annotations)
}
func (r *Resource) NameChangeAllowed() bool {
annotations := r.GetAnnotations()
if allowed, set := annotations[buildAnnotationAllowNameChange]; set && allowed == "true" {
return true
}
return false
}
func (r *Resource) SetAllowKindChange(value string) {
annotations := r.GetAnnotations()
annotations[buildAnnotationAllowKindChange] = value
r.SetAnnotations(annotations)
}
func (r *Resource) KindChangeAllowed() bool {
annotations := r.GetAnnotations()
if allowed, set := annotations[buildAnnotationAllowKindChange]; set && allowed == "true" {
return true
}
return false
}
// String returns resource as JSON.
func (r *Resource) String() string {
bs, err := r.MarshalJSON()
@@ -430,14 +492,19 @@ func (r *Resource) PrevIds() []resid.ResId {
// pairs on one annotation so there is no chance of error
names := r.getCsvAnnotation(buildAnnotationPreviousNames)
ns := r.getCsvAnnotation(buildAnnotationPreviousNamespaces)
if len(names) != len(ns) {
kinds := r.getCsvAnnotation(buildAnnotationPreviousKinds)
if len(names) != len(ns) || len(names) != len(kinds) {
panic(errors.New(
"number of previous names not equal to " +
"number of previous namespaces"))
"number of previous names, " +
"number of previous namespaces, " +
"number of previous kinds not equal"))
}
for i := range names {
k := kinds[i]
gvk := r.GetGvk()
gvk.Kind = k
ids = append(ids, resid.NewResIdWithNamespace(
r.GetGvk(), names[i], ns[i]))
gvk, names[i], ns[i]))
}
return ids
}
@@ -445,7 +512,7 @@ func (r *Resource) PrevIds() []resid.ResId {
// StorePreviousId stores the resource's current ID via build annotations.
func (r *Resource) StorePreviousId() {
id := r.CurId()
r.setPreviousNamespaceAndName(id.EffectiveNamespace(), id.Name)
r.setPreviousId(id.EffectiveNamespace(), id.Name, id.Kind)
}
// CurId returns a ResId for the resource using the
@@ -478,38 +545,35 @@ func (r *Resource) AppendRefVarName(variable types.Var) {
// ApplySmPatch applies the provided strategic merge patch.
func (r *Resource) ApplySmPatch(patch *Resource) error {
node, err := filtersutil.GetRNode(patch)
if err != nil {
n, ns, k := r.GetName(), r.GetNamespace(), r.GetKind()
if patch.NameChangeAllowed() || patch.KindChangeAllowed() {
r.StorePreviousId()
}
if err := r.ApplyFilter(patchstrategicmerge.Filter{
Patch: patch.node,
}); err != nil {
return err
}
n, ns := r.GetName(), r.GetNamespace()
err = r.ApplyFilter(patchstrategicmerge.Filter{
Patch: node,
})
if err != nil {
return err
if r.IsEmpty() {
return nil
}
empty, err := r.IsEmpty()
if err != nil {
return err
if !patch.KindChangeAllowed() {
r.SetKind(k)
}
if !empty {
if !patch.NameChangeAllowed() {
r.SetName(n)
r.SetNamespace(ns)
}
return err
r.SetNamespace(ns)
return nil
}
func (r *Resource) ApplyFilter(f kio.Filter) error {
if wn, ok := r.kunStr.(*wrappy.WNode); ok {
l, err := f.Filter([]*kyaml.RNode{wn.AsRNode()})
if len(l) == 0 {
// Hack to deal with deletion.
r.kunStr = wrappy.NewWNode()
}
return err
l, err := f.Filter([]*kyaml.RNode{r.node})
if len(l) == 0 {
// The node was deleted. The following makes r.IsEmpty() true.
r.node = nil
}
return filtersutil.ApplyToJSON(f, r)
return err
}
func mergeStringMaps(maps ...map[string]string) map[string]string {

View File

@@ -695,7 +695,7 @@ spec:
}
}
func TestResource_StorePreviousId(t *testing.T) {
func TestResourceStorePreviousId(t *testing.T) {
tests := map[string]struct {
input string
newName string
@@ -714,6 +714,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
name: newName
@@ -725,6 +726,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
name: oldName2
@@ -735,6 +737,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret,Secret
config.kubernetes.io/previousNames: oldName,oldName2
config.kubernetes.io/previousNamespaces: default,default
name: newName
@@ -746,6 +749,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
name: oldName2
@@ -757,6 +761,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret,Secret
config.kubernetes.io/previousNames: oldName,oldName2
config.kubernetes.io/previousNamespaces: default,oldNamespace
name: newName
@@ -806,6 +811,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
name: newName
@@ -824,6 +830,7 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret,Secret
config.kubernetes.io/previousNames: oldName,oldName2
config.kubernetes.io/previousNamespaces: default,oldNamespace
name: newName
@@ -1072,3 +1079,54 @@ func TestSameEndingSubarray(t *testing.T) {
})
}
}
func TestGetGvk(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
assert.NoError(t, err)
gvk := r.GetGvk()
expected := "apps"
actual := gvk.Group
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "v1"
actual = gvk.Version
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "Deployment"
actual = gvk.Kind
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetGvk(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
assert.NoError(t, err)
r.SetGvk(resid.GvkFromString("grp_ver_knd"))
gvk := r.GetGvk()
if expected, actual := "grp", gvk.Group; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "ver", gvk.Version; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "knd", gvk.Kind; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}

View File

@@ -5,7 +5,6 @@ package kusttest_test
import (
"path/filepath"
"strings"
"testing"
"sigs.k8s.io/kustomize/api/filesys"
@@ -77,15 +76,7 @@ func (th Harness) MakeOptionsPluginsDisabled() krusty.Options {
// Enables use of non-builtin plugins.
func (th Harness) MakeOptionsPluginsEnabled() krusty.Options {
pc, err := konfig.EnabledPluginConfig(types.BploLoadFromFileSys)
if err != nil {
if strings.Contains(err.Error(), "unable to find plugin root") {
th.t.Log(
"Tests that want to run with plugins enabled must be " +
"bookended by calls to MakeEnhancedHarness(), Reset().")
}
th.t.Fatal(err)
}
pc := types.EnabledPluginConfig(types.BploLoadFromFileSys)
o := *krusty.MakeDefaultOptions()
o.PluginConfig = pc
return o

View File

@@ -40,21 +40,18 @@ type HarnessEnhanced struct {
func MakeEnhancedHarness(t *testing.T) *HarnessEnhanced {
pte := newPluginTestEnv(t).set()
pc, err := konfig.EnabledPluginConfig(types.BploLoadFromFileSys)
pc := types.EnabledPluginConfig(types.BploLoadFromFileSys)
pc.FnpLoadingOptions.EnableStar = true
if err != nil {
t.Fatal(err)
}
p := provider.NewDefaultDepProvider()
resourceFactory := p.GetResourceFactory()
resmapFactory := resmap.NewFactory(
resourceFactory, p.GetConflictDetectorFactory())
resmapFactory := resmap.NewFactory(resourceFactory)
result := &HarnessEnhanced{
Harness: MakeHarness(t),
pte: pte,
rf: resmapFactory,
pl: pLdr.NewLoader(pc, resmapFactory)}
// The plugin configs are always located on disk, regardless of the test harness's FS
pl: pLdr.NewLoader(pc, resmapFactory, filesys.MakeFsOnDisk())}
// Point the file loader to the root ('/') of the in-memory file system.
result.ResetLoaderRoot(filesys.Separator)

View File

@@ -23,7 +23,7 @@ func (e *errUnableToFind) Error() string {
m = append(m, "('"+p.Value+"'; "+p.Key+")")
}
return fmt.Sprintf(
"unable to find plugin root - tried: %s", strings.Join(m, ", "))
"unable to find %s - tried: %s", e.what, strings.Join(m, ", "))
}
func NewErrUnableToFind(w string, a []Pair) *errUnableToFind {

View File

@@ -9,8 +9,7 @@ import (
"sigs.k8s.io/kustomize/api/resid"
)
// FieldSpec completely specifies a kustomizable field in
// an unstructured representation of a k8s API object.
// FieldSpec completely specifies a kustomizable field in a k8s API object.
// It helps define the operands of transformations.
//
// For example, a directive to add a common label to objects

View File

@@ -6,6 +6,7 @@ package types
import (
"bytes"
"encoding/json"
"fmt"
"sigs.k8s.io/yaml"
)
@@ -46,6 +47,9 @@ type Kustomization struct {
// CommonLabels to add to all objects and selectors.
CommonLabels map[string]string `json:"commonLabels,omitempty" yaml:"commonLabels,omitempty"`
// Labels to add to all objects but not selectors.
Labels []Label `json:"labels,omitempty" yaml:"labels,omitempty"`
// CommonAnnotations to add to all objects.
CommonAnnotations map[string]string `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
@@ -186,10 +190,28 @@ func (k *Kustomization) FixKustomizationPostUnmarshalling() {
// FixKustomizationPreMarshalling fixes things
// that should occur after the kustomization file
// has been processed.
func (k *Kustomization) FixKustomizationPreMarshalling() {
func (k *Kustomization) FixKustomizationPreMarshalling() error {
// PatchesJson6902 should be under the Patches field.
k.Patches = append(k.Patches, k.PatchesJson6902...)
k.PatchesJson6902 = nil
// this fix is not in FixKustomizationPostUnmarshalling because
// it will break some commands like `create` and `add`. those
// commands depend on 'commonLabels' field
if cl := labelFromCommonLabels(k.CommonLabels); cl != nil {
// check conflicts between commonLabels and labels
for _, l := range k.Labels {
for k := range l.Pairs {
if _, exist := cl.Pairs[k]; exist {
return fmt.Errorf("label name '%s' exists in both commonLabels and labels", k)
}
}
}
k.Labels = append(k.Labels, *cl)
k.CommonLabels = nil
}
return nil
}
func (k *Kustomization) EnforceFields() []string {

View File

@@ -22,6 +22,9 @@ func TestFixKustomizationPostUnmarshalling(t *testing.T) {
EnvSource: "c",
},
}}}
k.CommonLabels = map[string]string{
"foo": "bar",
}
k.FixKustomizationPostUnmarshalling()
expected := Kustomization{
@@ -35,6 +38,9 @@ func TestFixKustomizationPostUnmarshalling(t *testing.T) {
EnvSources: []string{"a", "b", "c"},
},
}}},
CommonLabels: map[string]string{
"foo": "bar",
},
}
if !reflect.DeepEqual(k, expected) {
t.Fatalf("unexpected output: %v", k)

25
api/types/labels.go Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package types
type Label struct {
// Pairs contains the key-value pairs for labels to add
Pairs map[string]string `json:"pairs,omitempty" yaml:"pairs,omitempty"`
// IncludeSelectors inidicates should transformer include the
// fieldSpecs for selectors. Custom fieldSpecs specified by
// FieldSpecs will be merged with builtin fieldSpecs if this
// is true.
IncludeSelectors bool `json:"includeSelectors,omitempty" yaml:"includeSelectors,omitempty"`
FieldSpecs []FieldSpec `json:"fields,omitempty" yaml:"fields,omitempty"`
}
func labelFromCommonLabels(commonLabels map[string]string) *Label {
if len(commonLabels) == 0 {
return nil
}
return &Label{
Pairs: commonLabels,
IncludeSelectors: true,
}
}

View File

@@ -3,6 +3,8 @@
package types
import "reflect"
// Patch represent either a Strategic Merge Patch or a JSON patch
// and its targets.
// The content of the patch can either be from a file
@@ -16,6 +18,9 @@ type Patch struct {
// Target points to the resources that the patch is applied to
Target *Selector `json:"target,omitempty" yaml:"target,omitempty"`
// Options is a list of options for the patch
Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
}
// Equals return true if p equals o.
@@ -24,5 +29,6 @@ func (p *Patch) Equals(o Patch) bool {
(p.Target != nil && o.Target != nil && *p.Target == *o.Target)
return p.Path == o.Path &&
p.Patch == o.Patch &&
targetEqual
targetEqual &&
reflect.DeepEqual(p.Options, o.Options)
}

View File

@@ -5,25 +5,6 @@ package types
// PluginConfig holds plugin configuration.
type PluginConfig struct {
// AbsPluginHome is the home of kustomize 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. kustomize must then use this data to both
// locate the plugin and configure it.
// Every kustomize 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.
// The value of AbsPluginHome can be any absolute path.
AbsPluginHome string
// PluginRestrictions distinguishes plugin restrictions.
PluginRestrictions PluginRestrictions
@@ -33,3 +14,21 @@ type PluginConfig struct {
// FnpLoadingOptions sets the way function-based plugin behaviors.
FnpLoadingOptions FnPluginLoadingOptions
}
func EnabledPluginConfig(b BuiltinPluginLoadingOptions) *PluginConfig {
return MakePluginConfig(PluginRestrictionsNone, b)
}
func DisabledPluginConfig() *PluginConfig {
return MakePluginConfig(
PluginRestrictionsBuiltinsOnly,
BploUseStaticallyLinked)
}
func MakePluginConfig(pr PluginRestrictions,
b BuiltinPluginLoadingOptions) *PluginConfig {
return &PluginConfig{
PluginRestrictions: pr,
BpLoadingOptions: b,
}
}

View File

@@ -18,3 +18,5 @@ require (
gopkg.in/inf.v0 v0.9.1
sigs.k8s.io/kustomize/kyaml v0.10.15
)
replace sigs.k8s.io/kustomize/kyaml => ../../kyaml

View File

@@ -290,5 +290,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=

View File

@@ -71,6 +71,11 @@ func (r *SetRunner) preRunE(c *cobra.Command, args []string) error {
return errors.Errorf("value should set either from flag or arg")
}
// make sure that the value is provided either through values flag or as an arg
if !valueFlagSet && len(args) < 3 {
return errors.Errorf("value must be provided either from flag or arg")
}
r.Name = args[1]
if valueFlagSet {
r.Value = r.Values[0]

View File

@@ -603,6 +603,54 @@ spec:
errMsg: `value should set either from flag or arg`,
},
{
name: "value must be provided from either flag or arg",
args: []string{"replicas", "--description", "hi there"},
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hello world
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hello world
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
errMsg: `value must be provided either from flag or arg`,
},
{
name: "openAPI list values set by flag success",
args: []string{"list", "--values", "10", "--values", "11"},

View File

@@ -20,7 +20,7 @@ import (
func GetTreeRunner(name string) *TreeRunner {
r := &TreeRunner{}
c := &cobra.Command{
Use: "tree DIR",
Use: "tree [DIR]",
Short: commands.TreeShort,
Long: commands.TreeLong,
Example: commands.TreeExamples,
@@ -75,19 +75,17 @@ type TreeRunner struct {
structure string
}
func (r *TreeRunner) getMatchFilesGlob() []string {
matchFilesGlob := append([]string{ext.KRMFileName()}, kio.DefaultMatch...)
return append(matchFilesGlob, "Kustomization")
}
func (r *TreeRunner) runE(c *cobra.Command, args []string) error {
var input kio.Reader
var root = "."
if len(args) == 1 {
root = filepath.Clean(args[0])
input = kio.LocalPackageReader{PackagePath: args[0], MatchFilesGlob: r.getMatchFilesGlob()}
} else {
if len(args) == 0 {
args = append(args, root)
}
if args[0] == "-" {
input = &kio.ByteReader{Reader: c.InOrStdin()}
} else {
root = filepath.Clean(args[0])
input = kio.LocalPackageReader{PackagePath: args[0]}
}
var fields []kio.TreeWriterField

View File

@@ -15,6 +15,84 @@ import (
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
func TestTreeCommandDefaultCurDir_files(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-tree-test")
defer os.RemoveAll(d)
if !assert.NoError(t, err) {
return
}
err = os.Chdir(d)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/reconciler:v1
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 1
---
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetTreeRunner("")
r.Command.SetArgs([]string{})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, fmt.Sprintf(`.
├── [f1.yaml] Deployment foo
├── [f1.yaml] Service foo
└── [f2.yaml] Deployment bar
`), b.String()) {
return
}
}
// TestCmd_files verifies fmt reads the files and filters them
func TestTreeCommand_files(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-tree-test")
@@ -130,7 +208,6 @@ resources:
}
if !assert.Equal(t, fmt.Sprintf(`%s
├── [Kustomization] Kustomization
└── [f2.yaml] Deployment bar
`, d), b.String()) {
return
@@ -229,11 +306,9 @@ openAPI:
}
if !assert.Equal(t, fmt.Sprintf(`%s
├── [Krmfile] Krmfile mainpkg
├── [f1.yaml] Deployment foo
├── [f1.yaml] Service foo
└── Pkg: subpkg
├── [Krmfile] Krmfile subpkg
└── [f2.yaml] Deployment bar
`, d), b.String()) {
return
@@ -244,7 +319,7 @@ func TestTreeCommand_stdin(t *testing.T) {
// fmt the files
b := &bytes.Buffer{}
r := commands.GetTreeRunner("")
r.Command.SetArgs([]string{})
r.Command.SetArgs([]string{"-"})
r.Command.SetIn(bytes.NewBufferString(`apiVersion: extensions/v1
kind: Deployment
metadata:

View File

@@ -9,3 +9,7 @@ require (
sigs.k8s.io/kustomize/api v0.8.5
sigs.k8s.io/kustomize/kyaml v0.10.15
)
replace sigs.k8s.io/kustomize/api => ../../api
replace sigs.k8s.io/kustomize/kyaml => ../../kyaml

View File

@@ -267,9 +267,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ=
sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -15,8 +15,7 @@ import (
func main() {
var plugin resmap.Configurable
p := provider.NewDefaultDepProvider()
resmapFactory := resmap.NewFactory(
p.GetResourceFactory(), p.GetConflictDetectorFactory())
resmapFactory := resmap.NewFactory(p.GetResourceFactory())
pluginHelpers := resmap.NewPluginHelpers(
nil, p.GetFieldValidator(), resmapFactory)
@@ -46,10 +45,7 @@ func main() {
}
}
resourceList.Items, err = resMap.ToRNodeSlice()
if err != nil {
return err
}
resourceList.Items = resMap.ToRNodeSlice()
return nil
})
if err := framework.Execute(&processor, nil); err != nil {

View File

@@ -1,7 +1,7 @@
# Demo: Components
For more details regarding this feature you can read the
[Kustomize Components KEP](https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/1802-kustomize-components.md).
[Kustomize Components KEP](https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/1802-kustomize-components/README.md).
_This example requires Kustomize ``v3.7.0`` or newer_

View File

@@ -0,0 +1,244 @@
# Using a Custom OpenAPI schema
For more details regarding this feature you can read the
[Kustomize OpenAPI Features KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2206-openapi-features-in-kustomize).
A kustomization file supports adding your own
OpenAPI schema to define merge keys and patch
strategy.
Make a place to work:
<!-- @placeToWork @testAgainstLatestRelease -->
```
DEMO_HOME=$(mktemp -d)
```
We'll be editing our own [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/).
<!-- @customOpenAPI @testAgainstLatestRelease -->
```
cat <<EOF >$DEMO_HOME/my_resource.yaml
apiVersion: example.com/v1alpha1
kind: MyResource
metadata:
name: service
spec:
template:
spec:
containers:
- name: server
image: server
command: example
ports:
- name: grpc
protocol: TCP
containerPort: 8080
EOF
```
This resource has an image field. Let's change its value from server
to nginx with a patch.
Kustomize gets its merge key information from the OpenAPI data
provided by the kubernetes API server. It doesn't have information
about custom resources, so we will have to provide our own
schema file.
Note: CRDs support declarative validation using an OpenAPI v3 schema.
See https://book.kubebuilder.io/reference/generating-crd.html#validation.
You can get an OpenAPI document like this by fetching the OpenAPI
document from your locally favored cluster with the command
`kustomize openapi fetch`. Kustomize will use the OpenAPI extensions
`x-kubernetes-patch-merge-key` and `x-kubernetes-patch-strategy` to
perform a strategic merge. `x-kubernetes-patch-strategy` should be set
to "merge", and you can set your merge key to whatever you like. Below,
our custom resource inherits merge keys from `PodTemplateSpec`.
<!-- @addCustomSchema @testAgainstLatestRelease -->
```
cat <<EOF >>$DEMO_HOME/mycr_schema.json
{
"definitions": {
"v1alpha1.MyResource": {
"properties": {
"apiVersion": {
"type": "string"
},
"kind": {
"type": "string"
},
"metadata": {
"type": "object"
},
"spec": {
"properties": {
"template": {
"\$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
}
},
"type": "object"
},
"status": {
"properties": {
"success": {
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object",
"x-kubernetes-group-version-kind": [
{
"group": "example.com",
"kind": "MyResource",
"version": "v1alpha1"
}
]
},
"io.k8s.api.core.v1.PodTemplateSpec": {
"properties": {
"metadata": {
"\$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"\$ref": "#/definitions/io.k8s.api.core.v1.PodSpec"
}
},
"type": "object"
},
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": {
"properties": {
"name": {
"type": "string"
}
},
"type": "object"
},
"io.k8s.api.core.v1.PodSpec": {
"properties": {
"containers": {
"items": {
"\$ref": "#/definitions/io.k8s.api.core.v1.Container"
},
"type": "array",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
}
},
"type": "object"
},
"io.k8s.api.core.v1.Container": {
"properties": {
"command": {
"items": {
"type": "string"
},
"type": "array"
},
"image": {
"type": "string"
},
"name": {
"type": "string"
},
"ports": {
"items": {
"\$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort"
},
"type": "array",
"x-kubernetes-list-map-keys": [
"containerPort",
"protocol"
],
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "containerPort",
"x-kubernetes-patch-strategy": "merge"
}
},
"type": "object"
},
"io.k8s.api.core.v1.ContainerPort": {
"properties": {
"containerPort": {
"format": "int32",
"type": "integer"
},
"name": {
"type": "string"
},
"protocol": {
"type": "string"
}
},
"type": "object"
}
}
}
EOF
```
We'll of course need a `kustomization` file
referring to the custom resource, and containing our patch:
<!-- @openAPIkustomization @testAgainstLatestRelease -->
```
cat <<EOF >$DEMO_HOME/kustomization.yaml
resources:
- my_resource.yaml
openapi:
path: mycr_schema.json
patchesStrategicMerge:
- |-
apiVersion: example.com/v1alpha1
kind: MyResource
metadata:
name: service
spec:
template:
spec:
containers:
- name: server
image: nginx
EOF
```
Define the expected output:
<!-- @expected @testAgainstLatestRelease -->
```
cat <<EOF >$DEMO_HOME/out_expected.yaml
apiVersion: example.com/v1alpha1
kind: MyResource
metadata:
name: service
spec:
template:
spec:
containers:
- command: example
image: nginx
name: server
ports:
- containerPort: 8080
name: grpc
protocol: TCP
EOF
```
Run the build:
<!-- @runExample @testAgainstLatestRelease -->
```
kustomize build $DEMO_HOME >$DEMO_HOME/out_actual.yaml
```
Confirm they match:
<!-- @diffShouldBeEmpty @testAgainstLatestRelease -->
```
diff $DEMO_HOME/out_actual.yaml $DEMO_HOME/out_expected.yaml
```

View File

@@ -6,7 +6,6 @@ package build
import (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
@@ -22,9 +21,8 @@ var theArgs struct {
var theFlags struct {
outputPath string
enable struct {
resourceIdChanges bool
plugins bool
managedByLabel bool
plugins bool
managedByLabel bool
}
loadRestrictor string
reorderOutput string
@@ -104,7 +102,6 @@ func NewCmdBuild(
AddFlagEnablePlugins(cmd.Flags())
AddFlagReorderOutput(cmd.Flags())
AddFlagEnableManagedbyLabel(cmd.Flags())
AddFlagAllowResourceIdChanges(cmd.Flags())
return cmd
}
@@ -132,14 +129,10 @@ func HonorKustomizeFlags(kOpts *krusty.Options) *krusty.Options {
kOpts.DoLegacyResourceSort = getFlagReorderOutput() == legacy
kOpts.LoadRestrictions = getFlagLoadRestrictorValue()
if theFlags.enable.plugins {
c, err := konfig.EnabledPluginConfig(types.BploUseStaticallyLinked)
if err != nil {
log.Fatal(err)
}
c := types.EnabledPluginConfig(types.BploUseStaticallyLinked)
c.FnpLoadingOptions = theFlags.fnOptions
kOpts.PluginConfig = c
}
kOpts.AddManagedbyLabel = isManagedByLabelEnabled()
kOpts.AllowResourceIdChanges = theFlags.enable.resourceIdChanges
return kOpts
}

View File

@@ -5,11 +5,13 @@ package build_test
import (
"bytes"
"fmt"
"strings"
"testing"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/provenance"
. "sigs.k8s.io/kustomize/kustomize/v4/commands/build"
)
@@ -157,16 +159,17 @@ func TestBuildWithShardedOutput(t *testing.T) {
"someDir/v1_namespace_ns1.yaml"); err != nil {
t.Fatal(err)
}
expected := `apiVersion: v1
version := provenance.GetProvenance().Version
expected := fmt.Sprintf(`apiVersion: v1
kind: Namespace
metadata:
annotations:
note: This is a test annotation
labels:
app: nginx
app.kubernetes.io/managed-by: kustomize-unknown
app.kubernetes.io/managed-by: kustomize-%s
name: ns1
`
`, version)
if string(data) != expected {
t.Fatalf("Expected:\n%s\nBut got:\n%s\n", expected, string(data))
}
@@ -174,7 +177,7 @@ metadata:
"someDir/v1_secret_foo-secret-bar-82c2g5f8f6.yaml"); err != nil {
t.Fatal(err)
}
expected = `apiVersion: v1
expected = fmt.Sprintf(`apiVersion: v1
data:
DB_PASSWORD: c29tZXB3
DB_USERNAME: YWRtaW4=
@@ -184,11 +187,11 @@ metadata:
note: This is a test annotation
labels:
app: nginx
app.kubernetes.io/managed-by: kustomize-unknown
app.kubernetes.io/managed-by: kustomize-%s
name: foo-secret-bar-82c2g5f8f6
namespace: ns1
type: Opaque
`
`, version)
if string(data) != expected {
t.Fatalf("Expected:\n%s\nBut got:\n%s\n", expected, string(data))
}

View File

@@ -1,16 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package build
import (
"github.com/spf13/pflag"
)
func AddFlagAllowResourceIdChanges(set *pflag.FlagSet) {
set.BoolVar(
&theFlags.enable.resourceIdChanges,
"allow-id-changes",
false,
`enable changes to a resourceId`)
}

View File

@@ -49,8 +49,8 @@ See https://sigs.k8s.io/kustomize
completion.NewCommand(),
makeBuildCommand(fSys, stdOut),
edit.NewCmdEdit(
fSys, pvd.GetFieldValidator(), pvd.GetKunstructuredFactory()),
create.NewCmdCreate(fSys, pvd.GetKunstructuredFactory()),
fSys, pvd.GetFieldValidator(), pvd.GetResourceFactory()),
create.NewCmdCreate(fSys, pvd.GetResourceFactory()),
version.NewCmdVersion(stdOut),
openapi.NewCmdOpenAPI(stdOut),
)

View File

@@ -11,9 +11,9 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/loader"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/util"
)
@@ -31,7 +31,7 @@ type createFlags struct {
}
// NewCmdCreate returns an instance of 'create' subcommand.
func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.Command {
func NewCmdCreate(fSys filesys.FileSystem, rf *resource.Factory) *cobra.Command {
opts := createFlags{path: filesys.SelfDir}
c := &cobra.Command{
Use: "create",
@@ -49,7 +49,7 @@ func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.C
kustomize create --resources deployment.yaml,service.yaml,../base --namespace staging --nameprefix acme-
`,
RunE: func(cmd *cobra.Command, args []string) error {
return runCreate(opts, fSys, uf)
return runCreate(opts, fSys, rf)
},
}
c.Flags().StringVar(
@@ -95,7 +95,7 @@ func NewCmdCreate(fSys filesys.FileSystem, uf ifc.KunstructuredFactory) *cobra.C
return c
}
func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFactory) error {
func runCreate(opts createFlags, fSys filesys.FileSystem, rf *resource.Factory) error {
var resources []string
var err error
if opts.resources != "" {
@@ -108,7 +108,7 @@ func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFa
return fmt.Errorf("kustomization file already exists")
}
if opts.detectResources {
detected, err := detectResources(fSys, uf, opts.path, opts.detectRecursive)
detected, err := detectResources(fSys, rf, opts.path, opts.detectRecursive)
if err != nil {
return err
}
@@ -149,7 +149,7 @@ func runCreate(opts createFlags, fSys filesys.FileSystem, uf ifc.KunstructuredFa
return mf.Write(m)
}
func detectResources(fSys filesys.FileSystem, uf ifc.KunstructuredFactory, base string, recursive bool) ([]string, error) {
func detectResources(fSys filesys.FileSystem, rf *resource.Factory, base string, recursive bool) ([]string, error) {
var paths []string
err := fSys.Walk(base, func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -176,7 +176,7 @@ func detectResources(fSys filesys.FileSystem, uf ifc.KunstructuredFactory, base
if err != nil {
return err
}
if _, err := uf.SliceFromBytes(fContents); err != nil {
if _, err := rf.SliceFromBytes(fContents); err != nil {
return nil
}
paths = append(paths, path)

View File

@@ -13,7 +13,7 @@ import (
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
)
var factory = provider.NewDefaultDepProvider().GetKunstructuredFactory()
var factory = provider.NewDefaultDepProvider().GetResourceFactory()
func readKustomizationFS(t *testing.T, fSys filesys.FileSystem) *types.Kustomization {
kf, err := kustfile.NewKustomizationFile(fSys)

View File

@@ -7,13 +7,14 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/resource"
)
// NewCmdAdd returns an instance of 'add' subcommand.
func NewCmdAdd(
fSys filesys.FileSystem,
ldr ifc.KvLoader,
kf ifc.KunstructuredFactory) *cobra.Command {
rf *resource.Factory) *cobra.Command {
c := &cobra.Command{
Use: "add",
Short: "Adds an item to the kustomization file.",
@@ -53,8 +54,8 @@ func NewCmdAdd(
newCmdAddResource(fSys),
newCmdAddPatch(fSys),
newCmdAddComponent(fSys),
newCmdAddSecret(fSys, ldr, kf),
newCmdAddConfigMap(fSys, ldr, kf),
newCmdAddSecret(fSys, ldr, rf),
newCmdAddConfigMap(fSys, ldr, rf),
newCmdAddBase(fSys),
newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()),
newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()),

View File

@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
)
@@ -15,7 +16,7 @@ import (
func newCmdAddConfigMap(
fSys filesys.FileSystem,
ldr ifc.KvLoader,
kf ifc.KunstructuredFactory) *cobra.Command {
rf *resource.Factory) *cobra.Command {
var flags flagsAndArgs
cmd := &cobra.Command{
Use: "configmap NAME [--behavior={create|merge|replace}] [--from-file=[key=]source] [--from-literal=key1=value1]",
@@ -57,7 +58,7 @@ func newCmdAddConfigMap(
}
// Add the flagsAndArgs map to the kustomization file.
err = addConfigMap(ldr, kustomization, flags, kf)
err = addConfigMap(ldr, kustomization, flags, rf)
if err != nil {
return err
}
@@ -106,13 +107,13 @@ func newCmdAddConfigMap(
func addConfigMap(
ldr ifc.KvLoader,
k *types.Kustomization,
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
flags flagsAndArgs, rf *resource.Factory) error {
args := findOrMakeConfigMapArgs(k, flags.Name)
mergeFlagsIntoCmArgs(args, flags)
// Validate by trying to create corev1.configmap.
args.Options = types.MergeGlobalOptionsIntoLocal(
args.Options, k.GeneratorOptions)
_, err := kf.MakeConfigMap(ldr, args)
_, err := rf.MakeConfigMap(ldr, args)
return err
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
)
@@ -15,7 +16,7 @@ import (
func newCmdAddSecret(
fSys filesys.FileSystem,
ldr ifc.KvLoader,
kf ifc.KunstructuredFactory) *cobra.Command {
rf *resource.Factory) *cobra.Command {
var flags flagsAndArgs
cmd := &cobra.Command{
Use: "secret NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--type=Opaque|kubernetes.io/tls]",
@@ -54,7 +55,7 @@ func newCmdAddSecret(
}
// Add the flagsAndArgs map to the kustomization file.
err = addSecret(ldr, kustomization, flags, kf)
err = addSecret(ldr, kustomization, flags, rf)
if err != nil {
return err
}
@@ -106,13 +107,13 @@ func newCmdAddSecret(
func addSecret(
ldr ifc.KvLoader,
k *types.Kustomization,
flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
flags flagsAndArgs, rf *resource.Factory) error {
args := findOrMakeSecretArgs(k, flags.Name, flags.Namespace, flags.Type)
mergeFlagsIntoGeneratorArgs(&args.GeneratorArgs, flags)
// Validate by trying to create corev1.secret.
args.Options = types.MergeGlobalOptionsIntoLocal(
args.Options, k.GeneratorOptions)
_, err := kf.MakeSecret(ldr, args)
_, err := rf.MakeSecret(ldr, args)
return err
}

View File

@@ -9,6 +9,7 @@ import (
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/kv"
"sigs.k8s.io/kustomize/api/loader"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/add"
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/fix"
"sigs.k8s.io/kustomize/kustomize/v4/commands/edit/listbuiltin"
@@ -18,7 +19,7 @@ import (
// NewCmdEdit returns an instance of 'edit' subcommand.
func NewCmdEdit(
fSys filesys.FileSystem, v ifc.Validator, kf ifc.KunstructuredFactory) *cobra.Command {
fSys filesys.FileSystem, v ifc.Validator, rf *resource.Factory) *cobra.Command {
c := &cobra.Command{
Use: "edit",
Short: "Edits a kustomization file",
@@ -40,7 +41,7 @@ func NewCmdEdit(
add.NewCmdAdd(
fSys,
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
kf),
rf),
set.NewCmdSet(
fSys,
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),

View File

@@ -39,6 +39,9 @@ func RunFix(fSys filesys.FileSystem) error {
return err
}
m.FixKustomizationPreMarshalling()
err = m.FixKustomizationPreMarshalling()
if err != nil {
return err
}
return mf.Write(m)
}

View File

@@ -133,3 +133,68 @@ kind: Kustomization
t.Errorf("Mismatch (-expected, +actual):\n%s", diff)
}
}
func TestFixOutdatedCommonLabels(t *testing.T) {
kustomizationContentWithOutdatedCommonLabels := []byte(`
commonLabels:
foo: bar
labels:
- pairs:
a: b
`)
expected := []byte(`
labels:
- pairs:
a: b
- includeSelectors: true
pairs:
foo: bar
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
`)
fSys := filesys.MakeFsInMemory()
testutils_test.WriteTestKustomizationWith(fSys, kustomizationContentWithOutdatedCommonLabels)
cmd := NewCmdFix(fSys)
err := cmd.RunE(cmd, nil)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := testutils_test.ReadTestKustomization(fSys)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if !strings.Contains(string(content), "apiVersion: ") {
t.Errorf("expected apiVersion in kustomization")
}
if !strings.Contains(string(content), "kind: Kustomization") {
t.Errorf("expected kind in kustomization")
}
if diff := cmp.Diff(expected, content); diff != "" {
t.Errorf("Mismatch (-expected, +actual):\n%s", diff)
}
}
func TestFixOutdatedCommonLabelsDuplicate(t *testing.T) {
kustomizationContentWithOutdatedCommonLabels := []byte(`
commonLabels:
foo: bar
labels:
- pairs:
foo: baz
a: b
`)
fSys := filesys.MakeFsInMemory()
testutils_test.WriteTestKustomizationWith(fSys, kustomizationContentWithOutdatedCommonLabels)
cmd := NewCmdFix(fSys)
err := cmd.RunE(cmd, nil)
if err == nil {
t.Fatalf("expect error")
}
expectedErr := "label name 'foo' exists in both commonLabels and labels"
if err.Error() != expectedErr {
t.Fatalf("error message '%s' doesn't match expected", err.Error())
}
}

View File

@@ -85,7 +85,7 @@ func (o *removePatchOptions) RunRemovePatch(fSys filesys.FileSystem) error {
}
}
if len(patches) == len(m.Patches) {
log.Printf("patch %s doesn't exist in kustomization file", o.Patch)
log.Printf("patch %s doesn't exist in kustomization file", o.Patch.Patch)
return nil
}
m.Patches = patches

Some files were not shown because too many files have changed in this diff Show More