mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-30 09:51:23 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95f3303493 | ||
|
|
2faf4a491b | ||
|
|
e646bba1ff | ||
|
|
99a21b0a3c | ||
|
|
e7a22b6bc5 | ||
|
|
d783bbc0bc | ||
|
|
b7405f3872 | ||
|
|
abc419b5f9 | ||
|
|
336378b114 | ||
|
|
29959551da | ||
|
|
fc78917191 | ||
|
|
ffd95ef5a9 | ||
|
|
230090d790 | ||
|
|
8fa3861ba3 | ||
|
|
69c90e3427 | ||
|
|
5a73f345fd | ||
|
|
0e62d759f0 | ||
|
|
b2967d2f77 | ||
|
|
c23039c07a | ||
|
|
5747c417c4 | ||
|
|
8c53d77111 | ||
|
|
01667cabde | ||
|
|
f649b62629 | ||
|
|
3a4d025b5c | ||
|
|
99eb08eb1e | ||
|
|
d3f8c0d87f | ||
|
|
0bec7b996b | ||
|
|
dd5674fe6b | ||
|
|
33159c26df | ||
|
|
afc7dbebe5 | ||
|
|
f363acf839 | ||
|
|
96d5a7401d | ||
|
|
403fa20546 | ||
|
|
ba4d7ddca8 | ||
|
|
5116e2f210 | ||
|
|
9e0f198227 |
@@ -23,7 +23,9 @@ English | [简体中文](zh/README.md)
|
|||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
|
||||||
* [3.0](v3.0.0.md) - Late June 2019. Plugin developer release.
|
* [3.1](v3.1.0.md) - Late July 2019. Extended patches and improved resource matching.
|
||||||
|
|
||||||
|
* [3.0](v3.0.0.md) - Late June 2019. Plugin developer release.
|
||||||
|
|
||||||
* [2.1](v2.1.0.md) - 18 June 2019. Plugins, ordered resources, etc.
|
* [2.1](v2.1.0.md) - 18 June 2019. Plugins, ordered resources, etc.
|
||||||
|
|
||||||
|
|||||||
127
docs/v3.1.0.md
Normal file
127
docs/v3.1.0.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# kustomize 3.1.0
|
||||||
|
|
||||||
|
|
||||||
|
## Extended patches
|
||||||
|
Since this version, Kustomize allows applying one patch to multiple resources. This works for both Strategic Merge Patch and JSON Patch. Take a look at [patch multiple objects](../examples/patchMultipleObjects.md).
|
||||||
|
|
||||||
|
## Improved Resource Matching
|
||||||
|
|
||||||
|
Multiple improvements have been made to allow the user to leverage "namespace"
|
||||||
|
instead/or with "name suffix/prefix" to segregate resources.
|
||||||
|
|
||||||
|
### Patch resolution improvement
|
||||||
|
|
||||||
|
The following example demonstrates how using the namespace field in the patch definition,
|
||||||
|
will let the user define two different patches against two different Deployment having the
|
||||||
|
same "deploy1" name but in different namespaces in the same Kustomize context/folder.
|
||||||
|
Unless the `namespace:` field has been specified in the kustomization.yaml, no namespace
|
||||||
|
value will be handled as Kubernetes `default` namespace.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
namespace: main
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
env:
|
||||||
|
- name: ANOTHERENV
|
||||||
|
value: TESTVALUE
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
namespace: production
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: main
|
||||||
|
env:
|
||||||
|
- name: ANOTHERENV
|
||||||
|
value: PRODVALUE
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Variable resolution improvement
|
||||||
|
|
||||||
|
It is possible to add namespace field to the variable declaration. In the following example,
|
||||||
|
two `Service` objects with the same `elasticsearch` name have been declared.
|
||||||
|
Specifying the namespace in the objRef of the corresponding varriables, allows Kustomize to
|
||||||
|
resovlve thoses variables.
|
||||||
|
If the namespace is not specified, Kustomize will handle it has a "wildcard" value.
|
||||||
|
|
||||||
|
Extract of kustomization.yaml:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vars:
|
||||||
|
- name: elasticsearch-test-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
- name: elasticsearch-dev-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Simultaneous change of names and namespaces
|
||||||
|
|
||||||
|
Kustomize is now able to deal with simultaneous changes of name and namespace.
|
||||||
|
Special attention has been paid the handling of:
|
||||||
|
- ClusterRoleBinding/RoleBinding "subjects" field,
|
||||||
|
- ValidatingWebhookConfiguration "webhooks" field.
|
||||||
|
|
||||||
|
The user should be able to use a kustomization.yaml as shown in the example bellow
|
||||||
|
even if ClusterRoleBind,RoleBinding and ValidatingWebookConfiguration are part of the
|
||||||
|
resources he needs to declare.
|
||||||
|
|
||||||
|
Extract of kustomization.yaml:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
namePrefix: pfx-
|
||||||
|
nameSuffix: -sfx
|
||||||
|
namespace: testnamespace
|
||||||
|
|
||||||
|
resources:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resource and Kustomize Context matching.
|
||||||
|
|
||||||
|
Kustomize is now able to support more aggregation patterns.
|
||||||
|
|
||||||
|
If for instance, the top level of kustomization.yaml, is simply
|
||||||
|
combining sub-components, (as in the following example), Kustomize has improved
|
||||||
|
resource matching capabilities. This removes some of the constraints which were
|
||||||
|
present on the utilization of prefix/suffix and namespace transformers in the
|
||||||
|
individual components.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
resources:
|
||||||
|
- ../component1
|
||||||
|
- ../component2
|
||||||
|
- ../component3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other improvements
|
||||||
|
|
||||||
|
- Image transformation has been improved. This allows the user to update the sha256 of
|
||||||
|
an image with another sha256.
|
||||||
|
- Multiple default transformer configuration entries have been added, removing the need for the
|
||||||
|
user to add them as part of the `configurations:` section of the kustomization.yaml.
|
||||||
|
- `kustomize` help command has been tidied up.
|
||||||
@@ -33,6 +33,8 @@ Basic Usage
|
|||||||
|
|
||||||
* [json patch](jsonpatch.md) - Apply a json patch in a kustomization
|
* [json patch](jsonpatch.md) - Apply a json patch in a kustomization
|
||||||
|
|
||||||
|
* [patch multiple objects](patchMultipleObjects.md) - Apply a patch to multiple objects
|
||||||
|
|
||||||
Advanced Usage
|
Advanced Usage
|
||||||
|
|
||||||
- generator plugins:
|
- generator plugins:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
# before running it.
|
# before running it.
|
||||||
#
|
#
|
||||||
# At time of writing, its 'call point' was in
|
# At time of writing, its 'call point' was in
|
||||||
# https://github.com/kubernetes/test-infra/blob/master/jobs/config.json
|
# https://github.com/kubernetes/test-infra/blob/master/config/jobs/kubernetes-sigs/kustomize/kustomize-config.yaml
|
||||||
|
|
||||||
function exitWith {
|
function exitWith {
|
||||||
local msg=$1
|
local msg=$1
|
||||||
@@ -53,7 +53,7 @@ function setUpEnv {
|
|||||||
exitWith "Script must be run from $expectedRepo"
|
exitWith "Script must be run from $expectedRepo"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
GO111MODULE=on go install . || \
|
GO111MODULE=on go install ./cmd/kustomize || \
|
||||||
{ exitWith "Failed to install kustomize."; }
|
{ exitWith "Failed to install kustomize."; }
|
||||||
|
|
||||||
PATH=$GOPATH/bin:$PATH
|
PATH=$GOPATH/bin:$PATH
|
||||||
|
|||||||
188
examples/patchMultipleObjects.md
Normal file
188
examples/patchMultipleObjects.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
[Strategic Merge Patch]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
|
||||||
|
[JSON patches]: https://tools.ietf.org/html/rfc6902
|
||||||
|
[label selector]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||||
|
|
||||||
|
|
||||||
|
# Demo: applying a patch to multiple resources
|
||||||
|
|
||||||
|
A kustomization file supports customizing resources via both
|
||||||
|
[Strategic Merge Patch] and [JSON patches]. Now one patch can be
|
||||||
|
applied to multiple resources.
|
||||||
|
|
||||||
|
This can be done by specifying a patch and a target selector as follows:
|
||||||
|
```
|
||||||
|
patches:
|
||||||
|
- path: <PatchFile>
|
||||||
|
target:
|
||||||
|
group: <Group>
|
||||||
|
version: <Version>
|
||||||
|
kind: <Kind>
|
||||||
|
name: <Name>
|
||||||
|
namespace: <Namespace>
|
||||||
|
labelSelector: <LabelSelector>
|
||||||
|
annotationSelector: <AnnotationSelector>
|
||||||
|
```
|
||||||
|
Both `labelSelector` and `annotationSelector` should follow the convention in [label selector].
|
||||||
|
Kustomize selects the targets which match all the fields in `target` to apply the patch.
|
||||||
|
|
||||||
|
The example below shows how to inject a sidecar container for all deployment resources.
|
||||||
|
|
||||||
|
Make a `kustomization` containing a Deployment resource.
|
||||||
|
|
||||||
|
<!-- @createDeployment @test -->
|
||||||
|
```
|
||||||
|
DEMO_HOME=$(mktemp -d)
|
||||||
|
|
||||||
|
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||||
|
resources:
|
||||||
|
- deployments.yaml
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >$DEMO_HOME/deployments.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx
|
||||||
|
args:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy2
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
key: value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: busybox
|
||||||
|
image: busybox
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Declare a Strategic Merge Patch file to inject a sidecar container:
|
||||||
|
|
||||||
|
<!-- @addPatch @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >$DEMO_HOME/patch.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: not-important
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: istio-proxy
|
||||||
|
image: docker.io/istio/proxyv2
|
||||||
|
args:
|
||||||
|
- proxy
|
||||||
|
- sidecar
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply the patch by adding _patches_ field in kustomization.yaml
|
||||||
|
|
||||||
|
<!-- @applyPatch @test -->
|
||||||
|
```
|
||||||
|
cat <<EOF >>$DEMO_HOME/kustomization.yaml
|
||||||
|
patches:
|
||||||
|
- path: patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Running `kustomize build $DEMO_HOME`, in the output confirm that both Deployment resources are patched correctly.
|
||||||
|
|
||||||
|
<!-- @confirmPatch @test -->
|
||||||
|
```
|
||||||
|
test 2 == \
|
||||||
|
$(kustomize build $DEMO_HOME | grep "image: docker.io/istio/proxyv2" | wc -l); \
|
||||||
|
echo $?
|
||||||
|
```
|
||||||
|
|
||||||
|
The output is as follows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- proxy
|
||||||
|
- sidecar
|
||||||
|
image: docker.io/istio/proxyv2
|
||||||
|
name: istio-proxy
|
||||||
|
- args:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
image: nginx
|
||||||
|
name: nginx
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy2
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
key: value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- proxy
|
||||||
|
- sidecar
|
||||||
|
image: docker.io/istio/proxyv2
|
||||||
|
name: istio-proxy
|
||||||
|
- image: busybox
|
||||||
|
name: busybox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target selector
|
||||||
|
- Select resources with name matching `name*`
|
||||||
|
```yaml
|
||||||
|
target:
|
||||||
|
name: name*
|
||||||
|
```
|
||||||
|
- Select all Deployment resources
|
||||||
|
```yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
```
|
||||||
|
- Select resources matching label `app=hello`
|
||||||
|
```yaml
|
||||||
|
target:
|
||||||
|
labelSelector: app=hello
|
||||||
|
```
|
||||||
|
- Select resources matching annotation `app=hello`
|
||||||
|
```yaml
|
||||||
|
target:
|
||||||
|
annotationSelector: app=hello
|
||||||
|
```
|
||||||
|
- Select all Deployment resources matching label `app=hello`
|
||||||
|
```yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
labelSelector: app=hello
|
||||||
|
```
|
||||||
@@ -338,7 +338,11 @@ func (fs *UnstructAdapter) Patch(patch ifc.Kunstructured) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs.SetMap(merged)
|
fs.SetMap(merged)
|
||||||
fs.SetName(saveName)
|
if len(fs.Map()) != 0 {
|
||||||
|
// if the patch deletes the object
|
||||||
|
// don't reset the name
|
||||||
|
fs.SetName(saveName)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (jmp *jsonMergePatch) findConflict(
|
|||||||
if i == conflictingPatchIdx {
|
if i == conflictingPatchIdx {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !patches[conflictingPatchIdx].OrgId().GvknEquals(patch.OrgId()) {
|
if !patches[conflictingPatchIdx].OrgId().Equals(patch.OrgId()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conflict, err := mergepatch.HasConflicts(
|
conflict, err := mergepatch.HasConflicts(
|
||||||
@@ -105,7 +105,7 @@ func (smp *strategicMergePatch) findConflict(
|
|||||||
if i == conflictingPatchIdx {
|
if i == conflictingPatchIdx {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !patches[conflictingPatchIdx].OrgId().GvknEquals(patch.OrgId()) {
|
if !patches[conflictingPatchIdx].OrgId().Equals(patch.OrgId()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conflict, err := strategicpatch.MergingMapsHaveConflicts(
|
conflict, err := strategicpatch.MergingMapsHaveConflicts(
|
||||||
@@ -135,7 +135,7 @@ func MergePatches(patches []*resource.Resource,
|
|||||||
rc := resmap.New()
|
rc := resmap.New()
|
||||||
for ix, patch := range patches {
|
for ix, patch := range patches {
|
||||||
id := patch.OrgId()
|
id := patch.OrgId()
|
||||||
existing := rc.GetMatchingResourcesByOriginalId(id.GvknEquals)
|
existing := rc.GetMatchingResourcesByOriginalId(id.Equals)
|
||||||
if len(existing) == 0 {
|
if len(existing) == 0 {
|
||||||
rc.Append(patch)
|
rc.Append(patch)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -64,8 +64,14 @@ func (ra *ResAccumulator) GetTransformerConfig() *config.TransformerConfig {
|
|||||||
|
|
||||||
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
||||||
for _, v := range incoming {
|
for _, v := range incoming {
|
||||||
matched := ra.resMap.GetMatchingResourcesByOriginalId(
|
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
||||||
resid.NewResId(v.ObjRef.GVK(), v.ObjRef.Name).GvknEquals)
|
idMatcher := targetId.GvknEquals
|
||||||
|
if targetId.Namespace != "" || !targetId.IsNamespaceableKind() {
|
||||||
|
// Preserve backward compatibility. An empty namespace means
|
||||||
|
// wildcard search on the namespace hence we still use GvknEquals
|
||||||
|
idMatcher = targetId.Equals
|
||||||
|
}
|
||||||
|
matched := ra.resMap.GetMatchingResourcesByOriginalId(idMatcher)
|
||||||
if len(matched) > 1 {
|
if len(matched) > 1 {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"found %d resId matches for var %s "+
|
"found %d resId matches for var %s "+
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ func determineFieldOrder() []string {
|
|||||||
"CommonAnnotations",
|
"CommonAnnotations",
|
||||||
"PatchesStrategicMerge",
|
"PatchesStrategicMerge",
|
||||||
"PatchesJson6902",
|
"PatchesJson6902",
|
||||||
|
"Patches",
|
||||||
"ConfigMapGenerator",
|
"ConfigMapGenerator",
|
||||||
"SecretGenerator",
|
"SecretGenerator",
|
||||||
"GeneratorOptions",
|
"GeneratorOptions",
|
||||||
@@ -73,9 +74,7 @@ func determineFieldOrder() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add deprecated fields here.
|
// Add deprecated fields here.
|
||||||
deprecated := map[string]bool{
|
deprecated := map[string]bool{}
|
||||||
"Patches": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account for the inlined TypeMeta fields.
|
// Account for the inlined TypeMeta fields.
|
||||||
var result []string
|
var result []string
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func TestFieldOrder(t *testing.T) {
|
|||||||
"CommonAnnotations",
|
"CommonAnnotations",
|
||||||
"PatchesStrategicMerge",
|
"PatchesStrategicMerge",
|
||||||
"PatchesJson6902",
|
"PatchesJson6902",
|
||||||
|
"Patches",
|
||||||
"ConfigMapGenerator",
|
"ConfigMapGenerator",
|
||||||
"SecretGenerator",
|
"SecretGenerator",
|
||||||
"GeneratorOptions",
|
"GeneratorOptions",
|
||||||
@@ -264,3 +265,85 @@ generatorOptions:
|
|||||||
string(expected), string(bytes))
|
string(expected), string(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFixPatchesField(t *testing.T) {
|
||||||
|
kustomizationContentWithComments := []byte(`
|
||||||
|
patches:
|
||||||
|
- patch1.yaml
|
||||||
|
- patch2.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
expected := []byte(`
|
||||||
|
patchesStrategicMerge:
|
||||||
|
- patch1.yaml
|
||||||
|
- patch2.yaml
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
`)
|
||||||
|
fSys := fs.MakeFakeFS()
|
||||||
|
fSys.WriteTestKustomizationWith(kustomizationContentWithComments)
|
||||||
|
mf, err := NewKustomizationFile(fSys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kustomization, err := mf.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
if err = mf.Write(kustomization); err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
bytes, _ := fSys.ReadFile(mf.path)
|
||||||
|
|
||||||
|
if string(expected) != string(bytes) {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected =\n%s\n\nactual =\n%s\n",
|
||||||
|
string(expected), string(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixPatchesFieldForExtendedPatch(t *testing.T) {
|
||||||
|
kustomizationContentWithComments := []byte(`
|
||||||
|
patches:
|
||||||
|
- path: patch1.yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
- path: patch2.yaml
|
||||||
|
target:
|
||||||
|
kind: Service
|
||||||
|
`)
|
||||||
|
|
||||||
|
expected := []byte(`
|
||||||
|
patches:
|
||||||
|
- path: patch1.yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
- path: patch2.yaml
|
||||||
|
target:
|
||||||
|
kind: Service
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
`)
|
||||||
|
fSys := fs.MakeFakeFS()
|
||||||
|
fSys.WriteTestKustomizationWith(kustomizationContentWithComments)
|
||||||
|
mf, err := NewKustomizationFile(fSys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kustomization, err := mf.Read()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
if err = mf.Write(kustomization); err != nil {
|
||||||
|
t.Fatalf("Unexpected Error: %v", err)
|
||||||
|
}
|
||||||
|
bytes, _ := fSys.ReadFile(mf.path)
|
||||||
|
|
||||||
|
if string(expected) != string(bytes) {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected =\n%s\n\nactual =\n%s\n",
|
||||||
|
string(expected), string(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func (x Gvk) Equals(o Gvk) bool {
|
|||||||
// In some cases order just specified to provide determinism.
|
// In some cases order just specified to provide determinism.
|
||||||
var orderFirst = []string{
|
var orderFirst = []string{
|
||||||
"Namespace",
|
"Namespace",
|
||||||
|
"ResourceQuota",
|
||||||
"StorageClass",
|
"StorageClass",
|
||||||
"CustomResourceDefinition",
|
"CustomResourceDefinition",
|
||||||
"MutatingWebhookConfiguration",
|
"MutatingWebhookConfiguration",
|
||||||
|
|||||||
@@ -552,26 +552,17 @@ func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
|||||||
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
||||||
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||||
inputRes *resource.Resource) ResMap {
|
inputRes *resource.Resource) ResMap {
|
||||||
inputId := inputRes.OrgId()
|
|
||||||
if !inputId.IsNamespaceableKind() {
|
|
||||||
if inputRes.GetOutermostNamePrefix() == "" {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
result := New()
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
if r.GetOutermostNamePrefix() == inputRes.GetOutermostNamePrefix() &&
|
|
||||||
r.GetOutermostNameSuffix() == inputRes.GetOutermostNameSuffix() {
|
|
||||||
err := result.Append(r)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
result := New()
|
result := New()
|
||||||
|
inputId := inputRes.CurId()
|
||||||
|
isInputIdNamespaceable := inputId.IsNamespaceableKind()
|
||||||
|
rctxm := inputRes.PrefixesSuffixesEquals
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
if !r.OrgId().IsNamespaceableKind() || inputRes.InSameFuzzyNamespace(r) {
|
// Need to match more accuratly both at the time of selection and transformation.
|
||||||
|
// OutmostPrefixSuffixEquals is not accurate enough since it is only using
|
||||||
|
// the outer most suffix and the last prefix. Use PrefixedSuffixesEquals instead.
|
||||||
|
resId := r.CurId()
|
||||||
|
if (!isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId)) &&
|
||||||
|
r.InSameKustomizeCtx(rctxm) {
|
||||||
err := result.Append(r)
|
err := result.Append(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
|||||||
r4 := rf.FromMap(
|
r4 := rf.FromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "ConfigMap",
|
"kind": "Deployment",
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "charlie",
|
"name": "charlie",
|
||||||
"namespace": "happy",
|
"namespace": "happy",
|
||||||
@@ -365,7 +365,7 @@ func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
|||||||
r5 := rf.FromMap(
|
r5 := rf.FromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Deployment",
|
"kind": "ConfigMap",
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "charlie",
|
"name": "charlie",
|
||||||
"namespace": "happy",
|
"namespace": "happy",
|
||||||
@@ -408,12 +408,12 @@ func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
|||||||
"happy namespace no prefix": {
|
"happy namespace no prefix": {
|
||||||
filter: r3,
|
filter: r3,
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
AddR(r3).AddR(r4).AddR(r7).ResMap(),
|
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||||
},
|
},
|
||||||
"happy namespace with prefix": {
|
"happy namespace with prefix": {
|
||||||
filter: r5,
|
filter: r5,
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||||
},
|
},
|
||||||
"cluster level": {
|
"cluster level": {
|
||||||
filter: r7,
|
filter: r7,
|
||||||
|
|||||||
@@ -27,6 +27,22 @@ type Resource struct {
|
|||||||
nameSuffixes []string
|
nameSuffixes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResCtx is an interface describing the contextual added
|
||||||
|
// kept kustomize in the context of each Resource object.
|
||||||
|
// Currently mainly the name prefix and name suffix are added.
|
||||||
|
type ResCtx interface {
|
||||||
|
AddNamePrefix(p string)
|
||||||
|
AddNameSuffix(s string)
|
||||||
|
GetOutermostNamePrefix() string
|
||||||
|
GetOutermostNameSuffix() string
|
||||||
|
GetNamePrefixes() []string
|
||||||
|
GetNameSuffixes() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResCtxMatcher returns true if two Resources are being
|
||||||
|
// modified in the same kustomize context.
|
||||||
|
type ResCtxMatcher func(ResCtx) bool
|
||||||
|
|
||||||
// DeepCopy returns a new copy of resource
|
// DeepCopy returns a new copy of resource
|
||||||
func (r *Resource) DeepCopy() *Resource {
|
func (r *Resource) DeepCopy() *Resource {
|
||||||
rc := &Resource{
|
rc := &Resource{
|
||||||
@@ -104,14 +120,17 @@ func copyStringSlice(s []string) []string {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements ResCtx AddNamePrefix
|
||||||
func (r *Resource) AddNamePrefix(p string) {
|
func (r *Resource) AddNamePrefix(p string) {
|
||||||
r.namePrefixes = append(r.namePrefixes, p)
|
r.namePrefixes = append(r.namePrefixes, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements ResCtx AddNameSuffix
|
||||||
func (r *Resource) AddNameSuffix(s string) {
|
func (r *Resource) AddNameSuffix(s string) {
|
||||||
r.nameSuffixes = append(r.nameSuffixes, s)
|
r.nameSuffixes = append(r.nameSuffixes, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements ResCtx GetOutermostNamePrefix
|
||||||
func (r *Resource) GetOutermostNamePrefix() string {
|
func (r *Resource) GetOutermostNamePrefix() string {
|
||||||
if len(r.namePrefixes) == 0 {
|
if len(r.namePrefixes) == 0 {
|
||||||
return ""
|
return ""
|
||||||
@@ -119,6 +138,7 @@ func (r *Resource) GetOutermostNamePrefix() string {
|
|||||||
return r.namePrefixes[len(r.namePrefixes)-1]
|
return r.namePrefixes[len(r.namePrefixes)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements ResCtx GetOutermostNameSuffix
|
||||||
func (r *Resource) GetOutermostNameSuffix() string {
|
func (r *Resource) GetOutermostNameSuffix() string {
|
||||||
if len(r.nameSuffixes) == 0 {
|
if len(r.nameSuffixes) == 0 {
|
||||||
return ""
|
return ""
|
||||||
@@ -126,10 +146,51 @@ func (r *Resource) GetOutermostNameSuffix() string {
|
|||||||
return r.nameSuffixes[len(r.nameSuffixes)-1]
|
return r.nameSuffixes[len(r.nameSuffixes)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) InSameFuzzyNamespace(o *Resource) bool {
|
func sameBeginningSubarray(a, b []string) bool {
|
||||||
return r.CurId().IsNsEquals(o.CurId()) &&
|
maxlen := len(b)
|
||||||
r.GetOutermostNamePrefix() == o.GetOutermostNamePrefix() &&
|
if len(a) < len(b) {
|
||||||
r.GetOutermostNameSuffix() == o.GetOutermostNameSuffix()
|
maxlen = len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxlen == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range a[0 : maxlen-1] {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements ResCtx GetNamePrefixes
|
||||||
|
func (r *Resource) GetNamePrefixes() []string {
|
||||||
|
return r.namePrefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements ResCtx GetNameSuffixes
|
||||||
|
func (r *Resource) GetNameSuffixes() []string {
|
||||||
|
return r.nameSuffixes
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutermostPrefixSuffixEquals returns true if both resources
|
||||||
|
// outer suffix and prefix matches.
|
||||||
|
func (r *Resource) OutermostPrefixSuffixEquals(o ResCtx) bool {
|
||||||
|
return (r.GetOutermostNamePrefix() == o.GetOutermostNamePrefix()) && (r.GetOutermostNameSuffix() == o.GetOutermostNameSuffix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrefixesSuffixesEquals is conceptually doing the same task
|
||||||
|
// as OutermostPrefixSuffix but performs a deeper comparison
|
||||||
|
// of the the list of suffix and prefix.
|
||||||
|
func (r *Resource) PrefixesSuffixesEquals(o ResCtx) bool {
|
||||||
|
return sameBeginningSubarray(r.GetNamePrefixes(), o.GetNamePrefixes()) && sameBeginningSubarray(r.GetNameSuffixes(), o.GetNameSuffixes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used to compute if a referrer could potentially be impacted
|
||||||
|
// by the change of name of a referral.
|
||||||
|
func (r *Resource) InSameKustomizeCtx(rctxm ResCtxMatcher) bool {
|
||||||
|
return rctxm(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetOriginalName() string {
|
func (r *Resource) GetOriginalName() string {
|
||||||
|
|||||||
@@ -923,6 +923,107 @@ spec:
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtendedPatchWithoutTarget(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
||||||
|
makeCommonFileForExtendedPatchTest(th)
|
||||||
|
th.WriteK("/app/base", `
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
patches:
|
||||||
|
- path: patch.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/base/patch.yaml", `
|
||||||
|
apiVersion: apps/v1beta2
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: busybox
|
||||||
|
annotations:
|
||||||
|
new-key: new-value
|
||||||
|
`)
|
||||||
|
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Err: %v", err)
|
||||||
|
}
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1beta2
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp/ps
|
||||||
|
name: nginx-persistent-storage
|
||||||
|
volumes:
|
||||||
|
- emptyDir: {}
|
||||||
|
name: nginx-persistent-storage
|
||||||
|
- configMap:
|
||||||
|
name: configmap-in-base
|
||||||
|
name: configmap-in-base
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1beta2
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
new-key: new-value
|
||||||
|
labels:
|
||||||
|
app: busybox
|
||||||
|
name: busybox
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: busybox
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: busybox
|
||||||
|
name: busybox
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp/ps
|
||||||
|
name: busybox-persistent-storage
|
||||||
|
volumes:
|
||||||
|
- emptyDir: {}
|
||||||
|
name: busybox-persistent-storage
|
||||||
|
- configMap:
|
||||||
|
name: configmap-in-base
|
||||||
|
name: configmap-in-base
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
selector:
|
||||||
|
app: nginx
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: busybox
|
||||||
|
name: busybox
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
selector:
|
||||||
|
app: busybox
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtendedPatchNoMatchMultiplePatch(t *testing.T) {
|
func TestExtendedPatchNoMatchMultiplePatch(t *testing.T) {
|
||||||
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
||||||
makeCommonFileForExtendedPatchTest(th)
|
makeCommonFileForExtendedPatchTest(th)
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func (kt *KustTarget) configureBuiltinPatchTransformer(
|
|||||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
}
|
}
|
||||||
for _, patch := range kt.kustomization.Patches {
|
for _, patch := range kt.kustomization.Patches {
|
||||||
c.Target = &patch.Target
|
c.Target = patch.Target
|
||||||
c.Patch = patch.Patch
|
c.Patch = patch.Patch
|
||||||
c.Path = patch.Path
|
c.Path = patch.Path
|
||||||
p := builtin.NewPatchTransformerPlugin()
|
p := builtin.NewPatchTransformerPlugin()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package target_test
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -334,7 +333,10 @@ vars:
|
|||||||
t.Fatalf("unexpected size %d", len(vars))
|
t.Fatalf("unexpected size %d", len(vars))
|
||||||
}
|
}
|
||||||
for i := range vars[:2] {
|
for i := range vars[:2] {
|
||||||
if !reflect.DeepEqual(vars[i], someVars[i]) {
|
// By using Var.DeepEqual, we are protecting the code
|
||||||
|
// from a potential invocation of vars[i].ObjRef.GVK()
|
||||||
|
// during AccumulateTarget
|
||||||
|
if !vars[i].DeepEqual(someVars[i]) {
|
||||||
t.Fatalf("unexpected var[%d]:\n %v\n %v", i, vars[i], someVars[i])
|
t.Fatalf("unexpected var[%d]:\n %v\n %v", i, vars[i], someVars[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,7 +389,10 @@ resources:
|
|||||||
t.Fatalf("expected 4 vars, got %d", len(vars))
|
t.Fatalf("expected 4 vars, got %d", len(vars))
|
||||||
}
|
}
|
||||||
for i := range vars {
|
for i := range vars {
|
||||||
if !reflect.DeepEqual(vars[i], someVars[i]) {
|
// By using Var.DeepEqual, we are protecting the code
|
||||||
|
// from a potential invocation of vars[i].ObjRef.GVK()
|
||||||
|
// during AccumulateTarget
|
||||||
|
if !vars[i].DeepEqual(someVars[i]) {
|
||||||
t.Fatalf("unexpected var[%d]:\n %v\n %v", i, vars[i], someVars[i])
|
t.Fatalf("unexpected var[%d]:\n %v\n %v", i, vars[i], someVars[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package target_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,3 +94,508 @@ rules:
|
|||||||
- get
|
- get
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNameAndNsTransformation validates that NamespaceTransformer,
|
||||||
|
// PrefixSuffixTransformer and namereference transformers are
|
||||||
|
// able to deal with simultaneous change of namespace and name.
|
||||||
|
func TestNameAndNsTransformation(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/nameandns")
|
||||||
|
|
||||||
|
th.WriteK("/nameandns", `
|
||||||
|
namePrefix: p1-
|
||||||
|
nameSuffix: -s1
|
||||||
|
namespace: newnamespace
|
||||||
|
resources:
|
||||||
|
- resources.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteF("/nameandns/resources.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm2
|
||||||
|
namespace: ns1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc1
|
||||||
|
namespace: ns1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: svc2
|
||||||
|
namespace: ns1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: sa1
|
||||||
|
namespace: ns1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: sa2
|
||||||
|
namespace: ns1
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: manager-rolebinding
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: sa1
|
||||||
|
namespace: ns1
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: sa2
|
||||||
|
namespace: ns1
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: sa3
|
||||||
|
namespace: random
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: irrelevant
|
||||||
|
---
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
webhooks:
|
||||||
|
- name: example1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: svc1
|
||||||
|
namespace: ns1
|
||||||
|
- name: example2
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: svc2
|
||||||
|
namespace: ns1
|
||||||
|
- name: example3
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: svc3
|
||||||
|
namespace: random
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: crds.my.org
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: cr1
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: crb1
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: irrelevant
|
||||||
|
---
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: pv1
|
||||||
|
`)
|
||||||
|
|
||||||
|
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Err: %v", err)
|
||||||
|
}
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: p1-cm1-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: p1-cm2-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: p1-svc1-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: p1-svc2-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: p1-sa1-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: p1-sa2-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: p1-manager-rolebinding-s1
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: p1-sa1-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: p1-sa2-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: sa3
|
||||||
|
namespace: random
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: p1-example-s1
|
||||||
|
webhooks:
|
||||||
|
- clientConfig:
|
||||||
|
service:
|
||||||
|
name: p1-svc1-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
name: example1
|
||||||
|
- clientConfig:
|
||||||
|
service:
|
||||||
|
name: p1-svc2-s1
|
||||||
|
namespace: newnamespace
|
||||||
|
name: example2
|
||||||
|
- clientConfig:
|
||||||
|
service:
|
||||||
|
name: svc3
|
||||||
|
namespace: random
|
||||||
|
name: example3
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: crds.my.org
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: p1-cr1-s1
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: p1-crb1-s1
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: p1-pv1-s1
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This serie of constants is used to prove the need of
|
||||||
|
// the namespace field in the objref field of the var declaration.
|
||||||
|
// The following tests demonstrate that it creates umbiguous variable
|
||||||
|
// declaration if two entities of the kind with the same name
|
||||||
|
// but in different namespaces are declared.
|
||||||
|
// This is tracking the following issue:
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/issues/1298
|
||||||
|
const namespaceNeedInVarMyApp string = `
|
||||||
|
resources:
|
||||||
|
- elasticsearch-dev-service.yaml
|
||||||
|
- elasticsearch-test-service.yaml
|
||||||
|
vars:
|
||||||
|
- name: elasticsearch-test-service-name
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: elasticsearch-test-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
- name: elasticsearch-dev-service-name
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: elasticsearch-dev-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
`
|
||||||
|
|
||||||
|
const namespaceNeedInVarDevResources string = `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: elasticsearch
|
||||||
|
env:
|
||||||
|
- name: DISCOVERY_SERVICE
|
||||||
|
value: "$(elasticsearch-dev-service-name).monitoring.svc.cluster.local"
|
||||||
|
- name: DISCOVERY_PROTOCOL
|
||||||
|
value: "$(elasticsearch-dev-protocol)"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: transport
|
||||||
|
port: 9300
|
||||||
|
protocol: TCP
|
||||||
|
clusterIP: None
|
||||||
|
`
|
||||||
|
|
||||||
|
const namespaceNeedInVarTestResources string = `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: elasticsearch
|
||||||
|
env:
|
||||||
|
- name: DISCOVERY_SERVICE
|
||||||
|
value: "$(elasticsearch-test-service-name).monitoring.svc.cluster.local"
|
||||||
|
- name: DISCOVERY_PROTOCOL
|
||||||
|
value: "$(elasticsearch-test-protocol)"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: transport
|
||||||
|
port: 9300
|
||||||
|
protocol: UDP
|
||||||
|
clusterIP: None
|
||||||
|
`
|
||||||
|
|
||||||
|
const namespaceNeedInVarExpectedOutput string = `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: DISCOVERY_SERVICE
|
||||||
|
value: elasticsearch.monitoring.svc.cluster.local
|
||||||
|
- name: DISCOVERY_PROTOCOL
|
||||||
|
value: TCP
|
||||||
|
name: elasticsearch
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
ports:
|
||||||
|
- name: transport
|
||||||
|
port: 9300
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: DISCOVERY_SERVICE
|
||||||
|
value: elasticsearch.monitoring.svc.cluster.local
|
||||||
|
- name: DISCOVERY_PROTOCOL
|
||||||
|
value: UDP
|
||||||
|
name: elasticsearch
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
ports:
|
||||||
|
- name: transport
|
||||||
|
port: 9300
|
||||||
|
protocol: UDP
|
||||||
|
`
|
||||||
|
|
||||||
|
// TestVariablesAmbiguous demonstrates how two variables pointing at two different resources
|
||||||
|
// using the same name in different namespaces are treated as ambiguous if the namespace is
|
||||||
|
// not specified
|
||||||
|
func TestVariablesAmbiguous(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/namespaceNeedInVar/myapp")
|
||||||
|
th.WriteK("/namespaceNeedInVar/myapp", namespaceNeedInVarMyApp)
|
||||||
|
th.WriteF("/namespaceNeedInVar/myapp/elasticsearch-dev-service.yaml", namespaceNeedInVarDevResources)
|
||||||
|
th.WriteF("/namespaceNeedInVar/myapp/elasticsearch-test-service.yaml", namespaceNeedInVarTestResources)
|
||||||
|
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "unable to disambiguate") {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespaceNeedInVarDevFolder string = `
|
||||||
|
resources:
|
||||||
|
- elasticsearch-dev-service.yaml
|
||||||
|
vars:
|
||||||
|
- name: elasticsearch-dev-service-name
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: elasticsearch-dev-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
`
|
||||||
|
|
||||||
|
const namespaceNeedInVarTestFolder string = `
|
||||||
|
resources:
|
||||||
|
- elasticsearch-test-service.yaml
|
||||||
|
vars:
|
||||||
|
- name: elasticsearch-test-service-name
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: elasticsearch-test-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
`
|
||||||
|
|
||||||
|
// TestVariablesAmbiguousWorkaround demonstrates a possible workaround
|
||||||
|
// to TestVariablesAmbiguous problem. It requires to separate the variables
|
||||||
|
// and resources into multiple kustomization context/folders instead of one.
|
||||||
|
func TestVariablesAmbiguousWorkaround(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/namespaceNeedInVar/workaround")
|
||||||
|
th.WriteK("/namespaceNeedInVar/dev", namespaceNeedInVarDevFolder)
|
||||||
|
th.WriteF("/namespaceNeedInVar/dev/elasticsearch-dev-service.yaml", namespaceNeedInVarDevResources)
|
||||||
|
th.WriteK("/namespaceNeedInVar/test", namespaceNeedInVarTestFolder)
|
||||||
|
th.WriteF("/namespaceNeedInVar/test/elasticsearch-test-service.yaml", namespaceNeedInVarTestResources)
|
||||||
|
th.WriteK("/namespaceNeedInVar/workaround", `
|
||||||
|
resources:
|
||||||
|
- ../dev
|
||||||
|
- ../test
|
||||||
|
`)
|
||||||
|
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Err: %v", err)
|
||||||
|
}
|
||||||
|
th.AssertActualEqualsExpected(m, namespaceNeedInVarExpectedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespaceNeedInVarMyAppWithNamespace string = `
|
||||||
|
resources:
|
||||||
|
- elasticsearch-dev-service.yaml
|
||||||
|
- elasticsearch-test-service.yaml
|
||||||
|
vars:
|
||||||
|
- name: elasticsearch-test-service-name
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: elasticsearch-test-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: test
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
- name: elasticsearch-dev-service-name
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.name
|
||||||
|
- name: elasticsearch-dev-protocol
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
name: elasticsearch
|
||||||
|
namespace: dev
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: spec.ports[0].protocol
|
||||||
|
`
|
||||||
|
|
||||||
|
// TestVariablesDisambiguatedWithNamespace demonstrates that adding the namespace
|
||||||
|
// to the variable declarations allows to disambiguate the variables.
|
||||||
|
func TestVariablesDisambiguatedWithNamespace(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/namespaceNeedInVar/myapp")
|
||||||
|
th.WriteK("/namespaceNeedInVar/myapp", namespaceNeedInVarMyAppWithNamespace)
|
||||||
|
th.WriteF("/namespaceNeedInVar/myapp/elasticsearch-dev-service.yaml", namespaceNeedInVarDevResources)
|
||||||
|
th.WriteF("/namespaceNeedInVar/myapp/elasticsearch-test-service.yaml", namespaceNeedInVarTestResources)
|
||||||
|
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Err: %v", err)
|
||||||
|
}
|
||||||
|
th.AssertActualEqualsExpected(m, namespaceNeedInVarExpectedOutput)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package target_test
|
package target_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||||
@@ -16,6 +15,7 @@ resources:
|
|||||||
- serviceaccount.yaml
|
- serviceaccount.yaml
|
||||||
- rolebinding.yaml
|
- rolebinding.yaml
|
||||||
- clusterrolebinding.yaml
|
- clusterrolebinding.yaml
|
||||||
|
- clusterrole.yaml
|
||||||
namePrefix: pfx-
|
namePrefix: pfx-
|
||||||
nameSuffix: -sfx
|
nameSuffix: -sfx
|
||||||
`)
|
`)
|
||||||
@@ -32,7 +32,7 @@ metadata:
|
|||||||
name: rolebinding
|
name: rolebinding
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: role
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
@@ -45,11 +45,21 @@ metadata:
|
|||||||
name: rolebinding
|
name: rolebinding
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: role
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: serviceaccount
|
name: serviceaccount
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/base/clusterrole.yaml", `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: role
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "watch", "list"]
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,8 +107,8 @@ metadata:
|
|||||||
name: pfx-rolebinding-sfx
|
name: pfx-rolebinding-sfx
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: pfx-role-sfx
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: pfx-serviceaccount-sfx
|
name: pfx-serviceaccount-sfx
|
||||||
@@ -109,11 +119,25 @@ metadata:
|
|||||||
name: pfx-rolebinding-sfx
|
name: pfx-rolebinding-sfx
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: pfx-role-sfx
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: pfx-serviceaccount-sfx
|
name: pfx-serviceaccount-sfx
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: pfx-role-sfx
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +161,8 @@ metadata:
|
|||||||
name: a-pfx-rolebinding-sfx-suffixA
|
name: a-pfx-rolebinding-sfx-suffixA
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: a-pfx-role-sfx-suffixA
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: a-pfx-serviceaccount-sfx-suffixA
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
@@ -149,11 +173,25 @@ metadata:
|
|||||||
name: a-pfx-rolebinding-sfx-suffixA
|
name: a-pfx-rolebinding-sfx-suffixA
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: a-pfx-role-sfx-suffixA
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: a-pfx-serviceaccount-sfx-suffixA
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: a-pfx-role-sfx-suffixA
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,8 +215,8 @@ metadata:
|
|||||||
name: b-pfx-rolebinding-sfx-suffixB
|
name: b-pfx-rolebinding-sfx-suffixB
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: b-pfx-role-sfx-suffixB
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: b-pfx-serviceaccount-sfx-suffixB
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
@@ -189,11 +227,25 @@ metadata:
|
|||||||
name: b-pfx-rolebinding-sfx-suffixB
|
name: b-pfx-rolebinding-sfx-suffixB
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: b-pfx-role-sfx-suffixB
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: b-pfx-serviceaccount-sfx-suffixB
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: b-pfx-role-sfx-suffixB
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,8 +270,8 @@ metadata:
|
|||||||
name: a-pfx-rolebinding-sfx-suffixA
|
name: a-pfx-rolebinding-sfx-suffixA
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: a-pfx-role-sfx-suffixA
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: a-pfx-serviceaccount-sfx-suffixA
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
@@ -230,12 +282,26 @@ metadata:
|
|||||||
name: a-pfx-rolebinding-sfx-suffixA
|
name: a-pfx-rolebinding-sfx-suffixA
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: a-pfx-role-sfx-suffixA
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: a-pfx-serviceaccount-sfx-suffixA
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: a-pfx-role-sfx-suffixA
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
metadata:
|
metadata:
|
||||||
@@ -247,8 +313,8 @@ metadata:
|
|||||||
name: b-pfx-rolebinding-sfx-suffixB
|
name: b-pfx-rolebinding-sfx-suffixB
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: b-pfx-role-sfx-suffixB
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: b-pfx-serviceaccount-sfx-suffixB
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
@@ -259,11 +325,25 @@ metadata:
|
|||||||
name: b-pfx-rolebinding-sfx-suffixB
|
name: b-pfx-rolebinding-sfx-suffixB
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: ClusterRole
|
||||||
name: role
|
name: b-pfx-role-sfx-suffixB
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: b-pfx-serviceaccount-sfx-suffixB
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: b-pfx-role-sfx-suffixB
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,12 +369,100 @@ metadata:
|
|||||||
name: serviceaccount
|
name: serviceaccount
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected resource conflict.")
|
|
||||||
}
|
|
||||||
if !strings.Contains(
|
|
||||||
err.Error(), "multiple matches for ~G_v1_ServiceAccount") {
|
|
||||||
t.Fatalf("Unexpected err: %v", err)
|
t.Fatalf("Unexpected err: %v", err)
|
||||||
}
|
}
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: a-serviceaccount-suffixA
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: a-pfx-rolebinding-sfx-suffixA
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: a-pfx-role-sfx-suffixA
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: a-pfx-rolebinding-sfx-suffixA
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: a-pfx-role-sfx-suffixA
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: a-pfx-serviceaccount-sfx-suffixA
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: a-pfx-role-sfx-suffixA
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: b-pfx-rolebinding-sfx-suffixB
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: b-pfx-role-sfx-suffixB
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: b-pfx-rolebinding-sfx-suffixB
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: b-pfx-role-sfx-suffixB
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: b-pfx-serviceaccount-sfx-suffixB
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: b-pfx-role-sfx-suffixB
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- watch
|
||||||
|
- list
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,10 +272,10 @@ nameReference:
|
|||||||
- path: spec/service/name
|
- path: spec/service/name
|
||||||
kind: APIService
|
kind: APIService
|
||||||
group: apiregistration.k8s.io
|
group: apiregistration.k8s.io
|
||||||
- path: webhooks/clientConfig/service/name
|
- path: webhooks/clientConfig/service
|
||||||
kind: ValidatingWebhookConfiguration
|
kind: ValidatingWebhookConfiguration
|
||||||
group: admissionregistration.k8s.io
|
group: admissionregistration.k8s.io
|
||||||
- path: webhooks/clientConfig/service/name
|
- path: webhooks/clientConfig/service
|
||||||
kind: MutatingWebhookConfiguration
|
kind: MutatingWebhookConfiguration
|
||||||
group: admissionregistration.k8s.io
|
group: admissionregistration.k8s.io
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,9 @@ const (
|
|||||||
namespace:
|
namespace:
|
||||||
- path: metadata/namespace
|
- path: metadata/namespace
|
||||||
create: true
|
create: true
|
||||||
|
- path: subjects
|
||||||
|
kind: RoleBinding
|
||||||
|
- path: subjects
|
||||||
|
kind: ClusterRoleBinding
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -191,6 +191,11 @@ func (o *nameReferenceTransformer) getNameAndNsStruct(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newname == oldName) && (newnamespace == nil) {
|
||||||
|
// no candidate found.
|
||||||
|
return inMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
inMap["name"] = newname
|
inMap["name"] = newname
|
||||||
if newnamespace != "" {
|
if newnamespace != "" {
|
||||||
// We don't want value "" to replace value "default" since
|
// We don't want value "" to replace value "default" since
|
||||||
@@ -212,6 +217,12 @@ func (o *nameReferenceTransformer) getNewNameFunc(
|
|||||||
oldName, _ := in.(string)
|
oldName, _ := in.(string)
|
||||||
return o.getSimpleNameField(oldName, referrer, target,
|
return o.getSimpleNameField(oldName, referrer, target,
|
||||||
referralCandidates, referralCandidates.Resources())
|
referralCandidates, referralCandidates.Resources())
|
||||||
|
case map[string]interface{}:
|
||||||
|
// Kind: ValidatingWebhookConfiguration
|
||||||
|
// FieldSpec is webhooks/clientConfig/service
|
||||||
|
oldMap, _ := in.(map[string]interface{})
|
||||||
|
return o.getNameAndNsStruct(oldMap, referrer, target,
|
||||||
|
referralCandidates)
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
l, _ := in.([]interface{})
|
l, _ := in.([]interface{})
|
||||||
for idx, item := range l {
|
for idx, item := range l {
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).ResMap(),
|
}).ResMap(),
|
||||||
expectedErr: "is expected to be"},
|
expectedErr: "is expected to contain a name field"},
|
||||||
}
|
}
|
||||||
|
|
||||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||||
@@ -652,6 +652,7 @@ const (
|
|||||||
ns1 = "ns1"
|
ns1 = "ns1"
|
||||||
ns2 = "ns2"
|
ns2 = "ns2"
|
||||||
ns3 = "ns3"
|
ns3 = "ns3"
|
||||||
|
ns4 = "ns4"
|
||||||
|
|
||||||
orgname = "uniquename"
|
orgname = "uniquename"
|
||||||
prefixedname = "prefix-uniquename"
|
prefixedname = "prefix-uniquename"
|
||||||
@@ -808,6 +809,11 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
"name": orgname,
|
"name": orgname,
|
||||||
"namespace": ns2,
|
"namespace": ns2,
|
||||||
},
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"kind": "ServiceAccount",
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": "random",
|
||||||
|
},
|
||||||
}}).ResMap()
|
}}).ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||||
@@ -862,6 +868,11 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
"name": suffixedname,
|
"name": suffixedname,
|
||||||
"namespace": ns2,
|
"namespace": ns2,
|
||||||
},
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"kind": "ServiceAccount",
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": "random",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
@@ -890,6 +901,13 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
rf := resource.NewFactory(
|
rf := resource.NewFactory(
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
kunstruct.NewKunstructuredFactoryImpl())
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddWithNsAndName(ns4, orgname, map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Secret",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": ns4,
|
||||||
|
}}).
|
||||||
// Add ServiceAccount with the same org name in "ns1" namespaces
|
// Add ServiceAccount with the same org name in "ns1" namespaces
|
||||||
AddWithNsAndName(ns1, orgname, map[string]interface{}{
|
AddWithNsAndName(ns1, orgname, map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -934,6 +952,16 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
"name": orgname,
|
"name": orgname,
|
||||||
"namespace": ns3,
|
"namespace": ns3,
|
||||||
},
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"kind": "ServiceAccount",
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": "random",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"kind": "ServiceAccount",
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": ns4,
|
||||||
|
},
|
||||||
}}).ResMap()
|
}}).ResMap()
|
||||||
|
|
||||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||||
@@ -963,6 +991,16 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
"name": suffixedname,
|
"name": suffixedname,
|
||||||
"namespace": ns2,
|
"namespace": ns2,
|
||||||
},
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"kind": "ServiceAccount",
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": "random",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"kind": "ServiceAccount",
|
||||||
|
"name": orgname,
|
||||||
|
"namespace": ns4,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).ResMap()
|
}).ResMap()
|
||||||
|
|
||||||
|
|||||||
55
pkg/types/fix.go
Normal file
55
pkg/types/fix.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package types holds struct definitions that should find a better home.
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FixKustomizationPreUnmarshalling modies the raw data
|
||||||
|
// before marshalling - e.g. changes old field names to
|
||||||
|
// new field names.
|
||||||
|
func FixKustomizationPreUnmarshalling(data []byte) []byte {
|
||||||
|
deprecateFieldsMap := map[string]string{
|
||||||
|
"imageTags:": "images:",
|
||||||
|
}
|
||||||
|
for oldname, newname := range deprecateFieldsMap {
|
||||||
|
pattern := regexp.MustCompile(oldname)
|
||||||
|
data = pattern.ReplaceAll(data, []byte(newname))
|
||||||
|
}
|
||||||
|
|
||||||
|
if useLegacyPatch(data) {
|
||||||
|
pattern := regexp.MustCompile("patches:")
|
||||||
|
data = pattern.ReplaceAll(data, []byte("patchesStrategicMerge:"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func useLegacyPatch(data []byte) bool {
|
||||||
|
found := false
|
||||||
|
|
||||||
|
var object map[string]interface{}
|
||||||
|
err := yaml.Unmarshal(data, &object)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid content from %s\n", string(data))
|
||||||
|
}
|
||||||
|
if rawPatches, ok := object["patches"]; ok {
|
||||||
|
patches, ok := rawPatches.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
log.Fatalf("invalid patches from %v\n", rawPatches)
|
||||||
|
}
|
||||||
|
for _, p := range patches {
|
||||||
|
_, ok := p.(string)
|
||||||
|
if ok {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/image"
|
"sigs.k8s.io/kustomize/v3/pkg/image"
|
||||||
)
|
)
|
||||||
@@ -195,20 +193,6 @@ func (k *Kustomization) EnforceFields() []string {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixKustomizationPreUnmarshalling modies the raw data
|
|
||||||
// before marshalling - e.g. changes old field names to
|
|
||||||
// new field names.
|
|
||||||
func FixKustomizationPreUnmarshalling(data []byte) []byte {
|
|
||||||
deprecateFieldsMap := map[string]string{
|
|
||||||
"imageTags:": "images:",
|
|
||||||
}
|
|
||||||
for oldname, newname := range deprecateFieldsMap {
|
|
||||||
pattern := regexp.MustCompile(oldname)
|
|
||||||
data = pattern.ReplaceAll(data, []byte(newname))
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeneratorArgs contains arguments common to generators.
|
// GeneratorArgs contains arguments common to generators.
|
||||||
type GeneratorArgs struct {
|
type GeneratorArgs struct {
|
||||||
// Namespace for the configmap, optional
|
// Namespace for the configmap, optional
|
||||||
@@ -385,7 +369,7 @@ type Patch struct {
|
|||||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||||
|
|
||||||
// Target points to the resources that the patch is applied to
|
// Target points to the resources that the patch is applied to
|
||||||
Target Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
Target *Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selector specifies a set of resources.
|
// Selector specifies a set of resources.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ type Target struct {
|
|||||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||||
gvk.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
gvk.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FieldSelector contains the fieldPath to an object field.
|
// FieldSelector contains the fieldPath to an object field.
|
||||||
@@ -64,10 +66,22 @@ type FieldSelector struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// defaulting sets reference to field used by default.
|
// defaulting sets reference to field used by default.
|
||||||
func (v *Var) defaulting() {
|
func (v *Var) Defaulting() {
|
||||||
if v.FieldRef.FieldPath == "" {
|
if v.FieldRef.FieldPath == "" {
|
||||||
v.FieldRef.FieldPath = defaultFieldPath
|
v.FieldRef.FieldPath = defaultFieldPath
|
||||||
}
|
}
|
||||||
|
v.ObjRef.GVK()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepEqual returns true if var a and b are Equals.
|
||||||
|
// Note 1: The objects are unchanged by the VarEqual
|
||||||
|
// Note 2: Should be normalize be FieldPath before doing
|
||||||
|
// the DeepEqual. spec.a[b] is supposed to be the same
|
||||||
|
// as spec.a.b
|
||||||
|
func (v Var) DeepEqual(other Var) bool {
|
||||||
|
v.Defaulting()
|
||||||
|
other.Defaulting()
|
||||||
|
return reflect.DeepEqual(v, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VarSet is a set of Vars where no var.Name is repeated.
|
// VarSet is a set of Vars where no var.Name is repeated.
|
||||||
@@ -129,11 +143,52 @@ func (vs *VarSet) Merge(v Var) error {
|
|||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"var '%s' already encountered", v.Name)
|
"var '%s' already encountered", v.Name)
|
||||||
}
|
}
|
||||||
v.defaulting()
|
v.Defaulting()
|
||||||
vs.set[v.Name] = v
|
vs.set[v.Name] = v
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AbsorbSet absorbs other vars with error on (name,value) collision.
|
||||||
|
func (vs *VarSet) AbsorbSet(incoming VarSet) error {
|
||||||
|
for _, v := range incoming.set {
|
||||||
|
if err := vs.Absorb(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsorbSlice absorbs a Var slice with error on (name,value) collision.
|
||||||
|
// Empty fields in incoming vars are defaulted.
|
||||||
|
func (vs *VarSet) AbsorbSlice(incoming []Var) error {
|
||||||
|
for _, v := range incoming {
|
||||||
|
if err := vs.Absorb(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absorb absorbs another Var with error on (name,value) collision.
|
||||||
|
// Empty fields in incoming Var is defaulted.
|
||||||
|
func (vs *VarSet) Absorb(v Var) error {
|
||||||
|
conflicting := vs.Get(v.Name)
|
||||||
|
if conflicting == nil {
|
||||||
|
// no conflict. The var is valid.
|
||||||
|
v.Defaulting()
|
||||||
|
vs.set[v.Name] = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(v, *conflicting) {
|
||||||
|
// two vars with the same name are pointing at two
|
||||||
|
// different resources.
|
||||||
|
return fmt.Errorf(
|
||||||
|
"var '%s' already encountered", v.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Contains is true if the set has the other var.
|
// Contains is true if the set has the other var.
|
||||||
func (vs *VarSet) Contains(other Var) bool {
|
func (vs *VarSet) Contains(other Var) bool {
|
||||||
return vs.Get(other.Name) != nil
|
return vs.Get(other.Name) != nil
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func TestDefaulting(t *testing.T) {
|
|||||||
Name: "my-secret",
|
Name: "my-secret",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
v.defaulting()
|
v.Defaulting()
|
||||||
if v.FieldRef.FieldPath != defaultFieldPath {
|
if v.FieldRef.FieldPath != defaultFieldPath {
|
||||||
t.Fatalf("expected %s, got %v",
|
t.Fatalf("expected %s, got %v",
|
||||||
defaultFieldPath, v.FieldRef.FieldPath)
|
defaultFieldPath, v.FieldRef.FieldPath)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
package builtin
|
package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||||
@@ -36,23 +35,23 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
id := r.OrgId()
|
|
||||||
fs, ok := p.isSelected(id)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(r.Map()) == 0 {
|
if len(r.Map()) == 0 {
|
||||||
// Don't mutate empty objects?
|
// Don't mutate empty objects?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if doIt(id, fs) {
|
|
||||||
if err := p.changeNamespace(r, fs); err != nil {
|
id := r.OrgId()
|
||||||
|
applicableFs := p.applicableFieldSpecs(id)
|
||||||
|
|
||||||
|
for _, fs := range applicableFs {
|
||||||
|
err := transformers.MutateField(
|
||||||
|
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
||||||
|
p.changeNamespace(r))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.updateClusterRoleBinding(m)
|
|
||||||
p.updateServiceReference(m)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,104 +61,64 @@ const metaNamespace = "metadata/namespace"
|
|||||||
// all objects have it, even "ClusterKind" objects
|
// all objects have it, even "ClusterKind" objects
|
||||||
// that don't exist in a namespace (the Namespace
|
// that don't exist in a namespace (the Namespace
|
||||||
// object itself doesn't live in a namespace).
|
// object itself doesn't live in a namespace).
|
||||||
func doIt(id resid.ResId, fs *config.FieldSpec) bool {
|
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []config.FieldSpec {
|
||||||
return fs.Path != metaNamespace ||
|
res := []config.FieldSpec{}
|
||||||
(fs.Path == metaNamespace && id.IsNamespaceableKind())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) changeNamespace(
|
|
||||||
r *resource.Resource, fs *config.FieldSpec) error {
|
|
||||||
return transformers.MutateField(
|
|
||||||
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
|
||||||
func(_ interface{}) (interface{}, error) {
|
|
||||||
return p.Namespace, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) isSelected(
|
|
||||||
id resid.ResId) (*config.FieldSpec, bool) {
|
|
||||||
for _, fs := range p.FieldSpecs {
|
for _, fs := range p.FieldSpecs {
|
||||||
if id.IsSelected(&fs.Gvk) {
|
if id.IsSelected(&fs.Gvk) && (fs.Path != metaNamespace || (fs.Path == metaNamespace && id.IsNamespaceableKind())) {
|
||||||
return &fs, true
|
res = append(res, fs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) updateClusterRoleBinding(m resmap.ResMap) {
|
func (o *NamespaceTransformerPlugin) changeNamespace(
|
||||||
srvAccount := gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
|
referrer *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||||
saMap := map[string]bool{}
|
return func(in interface{}) (interface{}, error) {
|
||||||
for _, id := range m.AllIds() {
|
switch in.(type) {
|
||||||
if id.Gvk.Equals(srvAccount) {
|
case string:
|
||||||
saMap[id.Name] = true
|
// will happen when the metadata/namespace
|
||||||
}
|
// value is replaced
|
||||||
}
|
return o.Namespace, nil
|
||||||
|
case []interface{}:
|
||||||
for _, res := range m.Resources() {
|
l, _ := in.([]interface{})
|
||||||
if res.OrgId().Kind != "ClusterRoleBinding" &&
|
for idx, item := range l {
|
||||||
res.OrgId().Kind != "RoleBinding" {
|
switch item.(type) {
|
||||||
continue
|
case map[string]interface{}:
|
||||||
}
|
// Will happen when mutating the subjects
|
||||||
objMap := res.Map()
|
// field of ClusterRoleBinding and RoleBinding
|
||||||
subjects, ok := objMap["subjects"].([]interface{})
|
inMap, _ := item.(map[string]interface{})
|
||||||
if subjects == nil || !ok {
|
if _, ok := inMap["name"]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
for i := range subjects {
|
|
||||||
subject := subjects[i].(map[string]interface{})
|
|
||||||
kind, foundK := subject["kind"]
|
|
||||||
name, foundN := subject["name"]
|
|
||||||
if !foundK || !foundN || kind.(string) != srvAccount.Kind {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// a ServiceAccount named “default” exists in every active namespace
|
|
||||||
if name.(string) == "default" || saMap[name.(string)] {
|
|
||||||
subject := subjects[i].(map[string]interface{})
|
|
||||||
transformers.MutateField(
|
|
||||||
subject, []string{"namespace"},
|
|
||||||
true, func(_ interface{}) (interface{}, error) {
|
|
||||||
return p.Namespace, nil
|
|
||||||
})
|
|
||||||
subjects[i] = subject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
objMap["subjects"] = subjects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) updateServiceReference(m resmap.ResMap) {
|
|
||||||
svc := gvk.Gvk{Version: "v1", Kind: "Service"}
|
|
||||||
svcMap := map[string]bool{}
|
|
||||||
for _, id := range m.AllIds() {
|
|
||||||
if id.Gvk.Equals(svc) {
|
|
||||||
svcMap[id.Name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, res := range m.Resources() {
|
|
||||||
if res.OrgId().Kind != "ValidatingWebhookConfiguration" &&
|
|
||||||
res.OrgId().Kind != "MutatingWebhookConfiguration" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
objMap := res.Map()
|
|
||||||
webhooks, ok := objMap["webhooks"].([]interface{})
|
|
||||||
if webhooks == nil || !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i := range webhooks {
|
|
||||||
webhook := webhooks[i].(map[string]interface{})
|
|
||||||
transformers.MutateField(
|
|
||||||
webhook, []string{"clientConfig", "service"},
|
|
||||||
false, func(obj interface{}) (interface{}, error) {
|
|
||||||
svc := obj.(map[string]interface{})
|
|
||||||
svcName, foundN := svc["name"]
|
|
||||||
if foundN && svcMap[svcName.(string)] {
|
|
||||||
svc["namespace"] = p.Namespace
|
|
||||||
}
|
}
|
||||||
return svc, nil
|
name, ok := inMap["name"].(string)
|
||||||
})
|
if !ok {
|
||||||
webhooks[i] = webhook
|
continue
|
||||||
|
}
|
||||||
|
// The only case we need to force the namespace
|
||||||
|
// if for the "service account". "default" is
|
||||||
|
// kind of hardcoded here for right now.
|
||||||
|
if name != "default" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inMap["namespace"] = o.Namespace
|
||||||
|
l[idx] = inMap
|
||||||
|
default:
|
||||||
|
// nothing to do for right now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in, nil
|
||||||
|
case map[string]interface{}:
|
||||||
|
// Will happen if the createField=true
|
||||||
|
// when the namespace is added to the
|
||||||
|
// object
|
||||||
|
inMap := in.(map[string]interface{})
|
||||||
|
if len(inMap) == 0 {
|
||||||
|
return o.Namespace, nil
|
||||||
|
} else {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return in, nil
|
||||||
}
|
}
|
||||||
objMap["webhooks"] = webhooks
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// remove the resource from resmap
|
||||||
|
// when the patch is to $patch: delete that target
|
||||||
|
if len(target.Map()) == 0 {
|
||||||
|
err = m.Remove(target.CurId())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Target == nil {
|
if p.Target == nil {
|
||||||
|
|||||||
@@ -49,22 +49,40 @@ func (p *PrefixSuffixTransformerPlugin) Config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
if len(p.Prefix) == 0 && len(p.Suffix) == 0 {
|
|
||||||
return nil
|
// Even if both the Prefix and Suffix are empty we want
|
||||||
}
|
// to proceed with the transformation. This allows to add contextual
|
||||||
|
// information to the resources (AddNamePrefix and AddNameSuffix).
|
||||||
|
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
if p.shouldSkip(r.OrgId()) {
|
if p.shouldSkip(r.OrgId()) {
|
||||||
|
// Don't change the actual definition
|
||||||
|
// of a CRD.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
id := r.OrgId()
|
id := r.OrgId()
|
||||||
|
// current default configuration contains
|
||||||
|
// only one entry: "metadata/name" with no GVK
|
||||||
for _, path := range p.FieldSpecs {
|
for _, path := range p.FieldSpecs {
|
||||||
if !id.IsSelected(&path.Gvk) {
|
if !id.IsSelected(&path.Gvk) {
|
||||||
|
// With the currrent default configuration,
|
||||||
|
// because no Gvk is specified, so a wild
|
||||||
|
// card
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if smellsLikeANameChange(&path) {
|
if smellsLikeANameChange(&path) {
|
||||||
|
// "metadata/name" is the only field.
|
||||||
|
// this will add a prefix and a suffix
|
||||||
|
// to the resource even if those are
|
||||||
|
// empty
|
||||||
r.AddNamePrefix(p.Prefix)
|
r.AddNamePrefix(p.Prefix)
|
||||||
r.AddNameSuffix(p.Suffix)
|
r.AddNameSuffix(p.Suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the addPrefixSuffix method will not
|
||||||
|
// change the name if both the prefix and suffix
|
||||||
|
// are empty.
|
||||||
err := transformers.MutateField(
|
err := transformers.MutateField(
|
||||||
r.Map(),
|
r.Map(),
|
||||||
path.PathSlice(),
|
path.PathSlice(),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
||||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||||
@@ -37,23 +36,23 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
id := r.OrgId()
|
|
||||||
fs, ok := p.isSelected(id)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(r.Map()) == 0 {
|
if len(r.Map()) == 0 {
|
||||||
// Don't mutate empty objects?
|
// Don't mutate empty objects?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if doIt(id, fs) {
|
|
||||||
if err := p.changeNamespace(r, fs); err != nil {
|
id := r.OrgId()
|
||||||
|
applicableFs := p.applicableFieldSpecs(id)
|
||||||
|
|
||||||
|
for _, fs := range applicableFs {
|
||||||
|
err := transformers.MutateField(
|
||||||
|
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
||||||
|
p.changeNamespace(r))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.updateClusterRoleBinding(m)
|
|
||||||
p.updateServiceReference(m)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,104 +62,64 @@ const metaNamespace = "metadata/namespace"
|
|||||||
// all objects have it, even "ClusterKind" objects
|
// all objects have it, even "ClusterKind" objects
|
||||||
// that don't exist in a namespace (the Namespace
|
// that don't exist in a namespace (the Namespace
|
||||||
// object itself doesn't live in a namespace).
|
// object itself doesn't live in a namespace).
|
||||||
func doIt(id resid.ResId, fs *config.FieldSpec) bool {
|
func (p *plugin) applicableFieldSpecs(id resid.ResId) []config.FieldSpec {
|
||||||
return fs.Path != metaNamespace ||
|
res := []config.FieldSpec{}
|
||||||
(fs.Path == metaNamespace && id.IsNamespaceableKind())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) changeNamespace(
|
|
||||||
r *resource.Resource, fs *config.FieldSpec) error {
|
|
||||||
return transformers.MutateField(
|
|
||||||
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
|
||||||
func(_ interface{}) (interface{}, error) {
|
|
||||||
return p.Namespace, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) isSelected(
|
|
||||||
id resid.ResId) (*config.FieldSpec, bool) {
|
|
||||||
for _, fs := range p.FieldSpecs {
|
for _, fs := range p.FieldSpecs {
|
||||||
if id.IsSelected(&fs.Gvk) {
|
if id.IsSelected(&fs.Gvk) && (fs.Path != metaNamespace || (fs.Path == metaNamespace && id.IsNamespaceableKind())) {
|
||||||
return &fs, true
|
res = append(res, fs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *plugin) updateClusterRoleBinding(m resmap.ResMap) {
|
func (o *plugin) changeNamespace(
|
||||||
srvAccount := gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
|
referrer *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||||
saMap := map[string]bool{}
|
return func(in interface{}) (interface{}, error) {
|
||||||
for _, id := range m.AllIds() {
|
switch in.(type) {
|
||||||
if id.Gvk.Equals(srvAccount) {
|
case string:
|
||||||
saMap[id.Name] = true
|
// will happen when the metadata/namespace
|
||||||
}
|
// value is replaced
|
||||||
}
|
return o.Namespace, nil
|
||||||
|
case []interface{}:
|
||||||
for _, res := range m.Resources() {
|
l, _ := in.([]interface{})
|
||||||
if res.OrgId().Kind != "ClusterRoleBinding" &&
|
for idx, item := range l {
|
||||||
res.OrgId().Kind != "RoleBinding" {
|
switch item.(type) {
|
||||||
continue
|
case map[string]interface{}:
|
||||||
}
|
// Will happen when mutating the subjects
|
||||||
objMap := res.Map()
|
// field of ClusterRoleBinding and RoleBinding
|
||||||
subjects, ok := objMap["subjects"].([]interface{})
|
inMap, _ := item.(map[string]interface{})
|
||||||
if subjects == nil || !ok {
|
if _, ok := inMap["name"]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
for i := range subjects {
|
|
||||||
subject := subjects[i].(map[string]interface{})
|
|
||||||
kind, foundK := subject["kind"]
|
|
||||||
name, foundN := subject["name"]
|
|
||||||
if !foundK || !foundN || kind.(string) != srvAccount.Kind {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// a ServiceAccount named “default” exists in every active namespace
|
|
||||||
if name.(string) == "default" || saMap[name.(string)] {
|
|
||||||
subject := subjects[i].(map[string]interface{})
|
|
||||||
transformers.MutateField(
|
|
||||||
subject, []string{"namespace"},
|
|
||||||
true, func(_ interface{}) (interface{}, error) {
|
|
||||||
return p.Namespace, nil
|
|
||||||
})
|
|
||||||
subjects[i] = subject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
objMap["subjects"] = subjects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) updateServiceReference(m resmap.ResMap) {
|
|
||||||
svc := gvk.Gvk{Version: "v1", Kind: "Service"}
|
|
||||||
svcMap := map[string]bool{}
|
|
||||||
for _, id := range m.AllIds() {
|
|
||||||
if id.Gvk.Equals(svc) {
|
|
||||||
svcMap[id.Name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, res := range m.Resources() {
|
|
||||||
if res.OrgId().Kind != "ValidatingWebhookConfiguration" &&
|
|
||||||
res.OrgId().Kind != "MutatingWebhookConfiguration" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
objMap := res.Map()
|
|
||||||
webhooks, ok := objMap["webhooks"].([]interface{})
|
|
||||||
if webhooks == nil || !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i := range webhooks {
|
|
||||||
webhook := webhooks[i].(map[string]interface{})
|
|
||||||
transformers.MutateField(
|
|
||||||
webhook, []string{"clientConfig", "service"},
|
|
||||||
false, func(obj interface{}) (interface{}, error) {
|
|
||||||
svc := obj.(map[string]interface{})
|
|
||||||
svcName, foundN := svc["name"]
|
|
||||||
if foundN && svcMap[svcName.(string)] {
|
|
||||||
svc["namespace"] = p.Namespace
|
|
||||||
}
|
}
|
||||||
return svc, nil
|
name, ok := inMap["name"].(string)
|
||||||
})
|
if !ok {
|
||||||
webhooks[i] = webhook
|
continue
|
||||||
|
}
|
||||||
|
// The only case we need to force the namespace
|
||||||
|
// if for the "service account". "default" is
|
||||||
|
// kind of hardcoded here for right now.
|
||||||
|
if name != "default" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inMap["namespace"] = o.Namespace
|
||||||
|
l[idx] = inMap
|
||||||
|
default:
|
||||||
|
// nothing to do for right now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in, nil
|
||||||
|
case map[string]interface{}:
|
||||||
|
// Will happen if the createField=true
|
||||||
|
// when the namespace is added to the
|
||||||
|
// object
|
||||||
|
inMap := in.(map[string]interface{})
|
||||||
|
if len(inMap) == 0 {
|
||||||
|
return o.Namespace, nil
|
||||||
|
} else {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return in, nil
|
||||||
}
|
}
|
||||||
objMap["webhooks"] = webhooks
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ metadata:
|
|||||||
fieldSpecs:
|
fieldSpecs:
|
||||||
- path: metadata/namespace
|
- path: metadata/namespace
|
||||||
create: true
|
create: true
|
||||||
|
- path: subjects
|
||||||
|
kind: RoleBinding
|
||||||
|
group: rbac.authorization.k8s.io
|
||||||
|
- path: subjects
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
group: rbac.authorization.k8s.io
|
||||||
`, `
|
`, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
@@ -54,7 +60,7 @@ apiVersion: v1
|
|||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
metadata:
|
metadata:
|
||||||
name: default
|
name: default
|
||||||
namespace: system
|
namespace: test
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
@@ -99,6 +105,15 @@ metadata:
|
|||||||
name: crd
|
name: crd
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
// Import note: The namespace transformer is in charge of
|
||||||
|
// the metadata.namespace field. The namespace transformer SHOULD
|
||||||
|
// NOT modify neither the "namespace" subfield within the
|
||||||
|
// ClusterRoleBinding.subjects field nor the "namespace"
|
||||||
|
// subfield in the ValidatingWebhookConfiguration.webhooks field.
|
||||||
|
// This is the role of the namereference Transformer to handle
|
||||||
|
// object reference changes (prefix/suffix and namespace).
|
||||||
|
// For use cases involving simultaneous change of name and namespace,
|
||||||
|
// refer to namespaces tests in pkg/target test suites.
|
||||||
th.AssertActualEqualsExpected(rm, `
|
th.AssertActualEqualsExpected(rm, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
@@ -145,7 +160,7 @@ subjects:
|
|||||||
namespace: test
|
namespace: test
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: service-account
|
name: service-account
|
||||||
namespace: test
|
namespace: system
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: another
|
name: another
|
||||||
namespace: random
|
namespace: random
|
||||||
@@ -158,7 +173,7 @@ webhooks:
|
|||||||
- clientConfig:
|
- clientConfig:
|
||||||
service:
|
service:
|
||||||
name: svc1
|
name: svc1
|
||||||
namespace: test
|
namespace: system
|
||||||
name: example1
|
name: example1
|
||||||
- clientConfig:
|
- clientConfig:
|
||||||
service:
|
service:
|
||||||
@@ -213,6 +228,12 @@ metadata:
|
|||||||
fieldSpecs:
|
fieldSpecs:
|
||||||
- path: metadata/namespace
|
- path: metadata/namespace
|
||||||
create: true
|
create: true
|
||||||
|
- path: subjects
|
||||||
|
kind: RoleBinding
|
||||||
|
group: rbac.authorization.k8s.io
|
||||||
|
- path: subjects
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
group: rbac.authorization.k8s.io
|
||||||
`, noChangeExpected)
|
`, noChangeExpected)
|
||||||
|
|
||||||
th.AssertActualEqualsExpected(rm, noChangeExpected)
|
th.AssertActualEqualsExpected(rm, noChangeExpected)
|
||||||
|
|||||||
@@ -71,6 +71,14 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// remove the resource from resmap
|
||||||
|
// when the patch is to $patch: delete that target
|
||||||
|
if len(target.Map()) == 0 {
|
||||||
|
err = m.Remove(target.CurId())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package main_test
|
package main_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -533,3 +534,696 @@ paths:
|
|||||||
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
|
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// simple utility function to add an namespace in a resource
|
||||||
|
// used as base, patch or expected result. Simply looks
|
||||||
|
// for specs: in order to add namespace: xxxx before this line
|
||||||
|
func addNamespace(namespace string, base string) string {
|
||||||
|
res := strings.Replace(base,
|
||||||
|
"\nspec:\n",
|
||||||
|
"\n namespace: "+namespace+"\nspec:\n",
|
||||||
|
1)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// unExpectedError function handles unexpected error
|
||||||
|
func unExpectedError(t *testing.T, name string, err error) {
|
||||||
|
t.Fatalf("%q; - unexpected error %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareExpectedError compares the expectedError and the actualError return by GetFieldValue
|
||||||
|
func compareExpectedError(t *testing.T, name string, err error, errorMsg string) {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%q; - should return error, but no error returned", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), errorMsg) {
|
||||||
|
t.Fatalf("%q; - expected error: \"%s\", got error: \"%v\"",
|
||||||
|
name, errorMsg, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Deployment string = "Deployment"
|
||||||
|
const MyCRD string = "MyCRD"
|
||||||
|
|
||||||
|
// baseResource produces a base object which used to test
|
||||||
|
// patch transformation
|
||||||
|
// Also the structure is matching the Deployment syntax
|
||||||
|
// the kind can be replaced to allow testing using CRD
|
||||||
|
// without access to the schema
|
||||||
|
func baseResource(kind string) string {
|
||||||
|
|
||||||
|
res := `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx`
|
||||||
|
return fmt.Sprintf(res, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addContainerAndEnvPatch produces a patch object which adds
|
||||||
|
// an entry in the env slice of the first/nginx container
|
||||||
|
// as well as adding a label in the metadata
|
||||||
|
// Note that for SMP/WithSchema merge, the name:nginx entry
|
||||||
|
// is mandatory
|
||||||
|
func addLabelAndEnvPatch(kind string) string {
|
||||||
|
|
||||||
|
res := `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
some-label: some-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
env:
|
||||||
|
- name: SOMEENV
|
||||||
|
value: SOMEVALUE`
|
||||||
|
|
||||||
|
return fmt.Sprintf(res, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addContainerAndEnvPatch produces a patch object which adds
|
||||||
|
// an entry in the env slice of the first/nginx container
|
||||||
|
// as well as adding a second container in the container list
|
||||||
|
// Note that for SMP/WithSchema merge, the name:nginx entry
|
||||||
|
// is mandatory
|
||||||
|
func addContainerAndEnvPatch(kind string) string {
|
||||||
|
|
||||||
|
res := `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
env:
|
||||||
|
- name: ANOTHERENV
|
||||||
|
value: ANOTHERVALUE
|
||||||
|
- name: anothercontainer
|
||||||
|
image: anotherimage`
|
||||||
|
|
||||||
|
return fmt.Sprintf(res, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addContainerAndEnvPatch produces a patch object which replaces
|
||||||
|
// the value of the image field in the first/nginx container
|
||||||
|
// Note that for SMP/WithSchema merge, the name:nginx entry
|
||||||
|
// is mandatory
|
||||||
|
func changeImagePatch(kind string, newImage string) string {
|
||||||
|
|
||||||
|
res := `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: %s`
|
||||||
|
|
||||||
|
return fmt.Sprintf(res, kind, newImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility method building the expected output of a SMP
|
||||||
|
func expectedResultSMP() string {
|
||||||
|
|
||||||
|
return `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
some-label: some-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: SOMEENV
|
||||||
|
value: SOMEVALUE
|
||||||
|
image: nginx
|
||||||
|
name: nginx
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility method building the expected output of a JMP.
|
||||||
|
// imagename parameter allows to build a result consistent
|
||||||
|
// with the JMP behavior which basically overrides the
|
||||||
|
// entire "containers" list.
|
||||||
|
func expectedResultJMP(imagename string) string {
|
||||||
|
|
||||||
|
res := `apiVersion: apps/v1
|
||||||
|
kind: MyCRD
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
some-label: some-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: SOMEENV
|
||||||
|
value: SOMEVALUE
|
||||||
|
name: nginx
|
||||||
|
`
|
||||||
|
|
||||||
|
if imagename == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
res = `apiVersion: apps/v1
|
||||||
|
kind: MyCRD
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
some-label: some-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: %s
|
||||||
|
name: nginx
|
||||||
|
`
|
||||||
|
|
||||||
|
return fmt.Sprintf(res, imagename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility method to build the expected result of a multipatch
|
||||||
|
// the order of the patches still have influence especially
|
||||||
|
// in the insertion location within arrays.
|
||||||
|
func expectedResultMultiPatch(kind string, reversed bool) string {
|
||||||
|
|
||||||
|
res := `apiVersion: apps/v1
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
some-label: some-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: ANOTHERENV
|
||||||
|
value: ANOTHERVALUE
|
||||||
|
- name: SOMEENV
|
||||||
|
value: SOMEVALUE
|
||||||
|
image: nginx:latest
|
||||||
|
name: nginx
|
||||||
|
- image: anotherimage
|
||||||
|
name: anothercontainer
|
||||||
|
`
|
||||||
|
|
||||||
|
reversedres := `apiVersion: apps/v1
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: deploy1
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
old-label: old-value
|
||||||
|
some-label: some-value
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: SOMEENV
|
||||||
|
value: SOMEVALUE
|
||||||
|
- name: ANOTHERENV
|
||||||
|
value: ANOTHERVALUE
|
||||||
|
image: nginx:latest
|
||||||
|
name: nginx
|
||||||
|
- image: anotherimage
|
||||||
|
name: anothercontainer
|
||||||
|
`
|
||||||
|
|
||||||
|
if reversed {
|
||||||
|
return fmt.Sprintf(reversedres, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(res, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toConfig(patches ...string) string {
|
||||||
|
config := `
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: PatchStrategicMergeTransformer
|
||||||
|
metadata:
|
||||||
|
name: notImportantHere
|
||||||
|
paths:
|
||||||
|
`
|
||||||
|
for idx, _ := range patches {
|
||||||
|
config = fmt.Sprintf("%s\n- ./patch%d.yaml", config, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSinglePatch validates the single patch use cases
|
||||||
|
// regarless of the schema availibility, which in turns
|
||||||
|
// relies on StrategicMergePatch or simple JSON Patch.
|
||||||
|
func TestSinglePatch(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
base string
|
||||||
|
patch string
|
||||||
|
expected string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "withschema",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: addLabelAndEnvPatch(Deployment),
|
||||||
|
errorExpected: false,
|
||||||
|
expected: expectedResultSMP(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: addLabelAndEnvPatch(MyCRD),
|
||||||
|
errorExpected: false,
|
||||||
|
expected: expectedResultJMP(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := plugins_test.NewEnvForTest(t).Set()
|
||||||
|
defer tc.Reset()
|
||||||
|
tc.BuildGoPlugin(
|
||||||
|
"builtin", "", "PatchStrategicMergeTransformer")
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
th := kusttest_test.NewKustTestPluginHarness(t, fmt.Sprintf("/%s", test.name))
|
||||||
|
th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", test.name, 0), test.patch)
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
err := th.ErrorFromLoadAndRunTransformer(toConfig(test.patch), test.base)
|
||||||
|
compareExpectedError(t, test.name, err, test.errorMsg)
|
||||||
|
} else {
|
||||||
|
rm := th.LoadAndRunTransformer(toConfig(test.patch), test.base)
|
||||||
|
th.AssertActualEqualsExpected(rm, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMultiplePatches checks that the patches are applied
|
||||||
|
// properly, that the same result is obtained,
|
||||||
|
// regardless of the order of the patches and regardless
|
||||||
|
// of the schema availibility (SMP vs JSON)
|
||||||
|
func TestMultiplePatches(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
base string
|
||||||
|
patch []string
|
||||||
|
expected string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "withschema-label-image-container",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
addContainerAndEnvPatch(Deployment),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
expected: expectedResultMultiPatch(Deployment, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-image-container-label",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
addContainerAndEnvPatch(Deployment),
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
expected: expectedResultMultiPatch(Deployment, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-container-label-image",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
addContainerAndEnvPatch(Deployment),
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
expected: expectedResultMultiPatch(Deployment, true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-label-image-container",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
addContainerAndEnvPatch(MyCRD),
|
||||||
|
},
|
||||||
|
// This should work
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-image-container-label",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
addContainerAndEnvPatch(MyCRD),
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
},
|
||||||
|
// This should work
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-container-label-image",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
addContainerAndEnvPatch(MyCRD),
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
},
|
||||||
|
// This should work
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := plugins_test.NewEnvForTest(t).Set()
|
||||||
|
defer tc.Reset()
|
||||||
|
tc.BuildGoPlugin(
|
||||||
|
"builtin", "", "PatchStrategicMergeTransformer")
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
th := kusttest_test.NewKustTestPluginHarness(t, fmt.Sprintf("/%s", test.name))
|
||||||
|
for idx, patch := range test.patch {
|
||||||
|
th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", test.name, idx), patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
err := th.ErrorFromLoadAndRunTransformer(toConfig(test.patch...), test.base)
|
||||||
|
compareExpectedError(t, test.name, err, test.errorMsg)
|
||||||
|
} else {
|
||||||
|
rm := th.LoadAndRunTransformer(toConfig(test.patch...), test.base)
|
||||||
|
th.AssertActualEqualsExpected(rm, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMultiplePatchesWithConflict checks that the conflict are
|
||||||
|
// detected regardless of the order of the patches and regardless
|
||||||
|
// of the schema availibility (SMP vs JSON)
|
||||||
|
func TestMultiplePatchesWithConflict(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
base string
|
||||||
|
patch []string
|
||||||
|
expected string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "withschema-label-latest-1.7.9",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
changeImagePatch(Deployment, "nginx:1.7.9"),
|
||||||
|
},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-latest-label-1.7.9",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
changeImagePatch(Deployment, "nginx:1.7.9"),
|
||||||
|
},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-1.7.9-label-latest",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(Deployment, "nginx:1.7.9"),
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-1.7.9-latest-label",
|
||||||
|
base: baseResource(Deployment),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(Deployment, "nginx:1.7.9"),
|
||||||
|
changeImagePatch(Deployment, "nginx:latest"),
|
||||||
|
addLabelAndEnvPatch(Deployment),
|
||||||
|
changeImagePatch(Deployment, "nginx:nginx"),
|
||||||
|
},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-label-latest-1.7.9",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
changeImagePatch(MyCRD, "nginx:1.7.9"),
|
||||||
|
},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "conflict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-latest-label-1.7.9",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
changeImagePatch(MyCRD, "nginx:1.7.9"),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
// There is no conflict detected. It should
|
||||||
|
// be but the JMPConflictDector ignores it.
|
||||||
|
// See https://github.com/kubernetes-sigs/kustomize/issues/1370
|
||||||
|
expected: expectedResultJMP("nginx:1.7.9"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-1.7.9-label-latest",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(MyCRD, "nginx:1.7.9"),
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
// There is no conflict detected. It should
|
||||||
|
// be but the JMPConflictDector ignores it.
|
||||||
|
// See https://github.com/kubernetes-sigs/kustomize/issues/1370
|
||||||
|
expected: expectedResultJMP("nginx:latest"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-1.7.9-latest-label",
|
||||||
|
base: baseResource(MyCRD),
|
||||||
|
patch: []string{
|
||||||
|
changeImagePatch(MyCRD, "nginx:1.7.9"),
|
||||||
|
changeImagePatch(MyCRD, "nginx:latest"),
|
||||||
|
addLabelAndEnvPatch(MyCRD),
|
||||||
|
changeImagePatch(MyCRD, "nginx:nginx"),
|
||||||
|
},
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := plugins_test.NewEnvForTest(t).Set()
|
||||||
|
defer tc.Reset()
|
||||||
|
tc.BuildGoPlugin(
|
||||||
|
"builtin", "", "PatchStrategicMergeTransformer")
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
th := kusttest_test.NewKustTestPluginHarness(t, fmt.Sprintf("/%s", test.name))
|
||||||
|
for idx, patch := range test.patch {
|
||||||
|
th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", test.name, idx), patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
err := th.ErrorFromLoadAndRunTransformer(toConfig(test.patch...), test.base)
|
||||||
|
compareExpectedError(t, test.name, err, test.errorMsg)
|
||||||
|
} else {
|
||||||
|
rm := th.LoadAndRunTransformer(toConfig(test.patch...), test.base)
|
||||||
|
th.AssertActualEqualsExpected(rm, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMultipleNamespaces before the same patch
|
||||||
|
// on two objects have the same name but in a different namespaces
|
||||||
|
func TestMultipleNamespaces(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
base []string
|
||||||
|
patch []string
|
||||||
|
expected []string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "withschema-ns1-ns2",
|
||||||
|
base: []string{
|
||||||
|
addNamespace("ns1", baseResource(Deployment)),
|
||||||
|
addNamespace("ns2", baseResource(Deployment)),
|
||||||
|
},
|
||||||
|
patch: []string{
|
||||||
|
addNamespace("ns1", addLabelAndEnvPatch(Deployment)),
|
||||||
|
addNamespace("ns2", addLabelAndEnvPatch(Deployment)),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
expected: []string{
|
||||||
|
addNamespace("ns1", expectedResultSMP()),
|
||||||
|
addNamespace("ns2", expectedResultSMP()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-ns1-ns2",
|
||||||
|
base: []string{
|
||||||
|
addNamespace("ns1", baseResource(MyCRD)),
|
||||||
|
addNamespace("ns2", baseResource(MyCRD)),
|
||||||
|
},
|
||||||
|
patch: []string{
|
||||||
|
addNamespace("ns1", addLabelAndEnvPatch(MyCRD)),
|
||||||
|
addNamespace("ns2", addLabelAndEnvPatch(MyCRD)),
|
||||||
|
},
|
||||||
|
errorExpected: false,
|
||||||
|
expected: []string{
|
||||||
|
addNamespace("ns1", expectedResultJMP("")),
|
||||||
|
addNamespace("ns2", expectedResultJMP("")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-ns1-ns2",
|
||||||
|
base: []string{addNamespace("ns1", baseResource(Deployment))},
|
||||||
|
patch: []string{addNamespace("ns2", changeImagePatch(Deployment, "nginx:1.7.9"))},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "failed to find unique target for patch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-nil-ns2",
|
||||||
|
base: []string{baseResource(Deployment)},
|
||||||
|
patch: []string{addNamespace("ns2", changeImagePatch(Deployment, "nginx:1.7.9"))},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "failed to find unique target for patch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "withschema-ns1-nil",
|
||||||
|
base: []string{addNamespace("ns1", baseResource(Deployment))},
|
||||||
|
patch: []string{changeImagePatch(Deployment, "nginx:1.7.9")},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "failed to find unique target for patch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-ns1-ns2",
|
||||||
|
base: []string{addNamespace("ns1", baseResource(MyCRD))},
|
||||||
|
patch: []string{addNamespace("ns2", changeImagePatch(MyCRD, "nginx:1.7.9"))},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "failed to find unique target for patch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-nil-ns2",
|
||||||
|
base: []string{baseResource(MyCRD)},
|
||||||
|
patch: []string{addNamespace("ns2", changeImagePatch(MyCRD, "nginx:1.7.9"))},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "failed to find unique target for patch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noschema-ns1-nil",
|
||||||
|
base: []string{addNamespace("ns1", baseResource(MyCRD))},
|
||||||
|
patch: []string{changeImagePatch(MyCRD, "nginx:1.7.9")},
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "failed to find unique target for patch",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := plugins_test.NewEnvForTest(t).Set()
|
||||||
|
defer tc.Reset()
|
||||||
|
tc.BuildGoPlugin(
|
||||||
|
"builtin", "", "PatchStrategicMergeTransformer")
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
th := kusttest_test.NewKustTestPluginHarness(t, fmt.Sprintf("/%s", test.name))
|
||||||
|
for idx, patch := range test.patch {
|
||||||
|
th.WriteF(fmt.Sprintf("/%s/patch%d.yaml", test.name, idx), patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.errorExpected {
|
||||||
|
err := th.ErrorFromLoadAndRunTransformer(toConfig(test.patch...), strings.Join(test.base, "\n---\n"))
|
||||||
|
compareExpectedError(t, test.name, err, test.errorMsg)
|
||||||
|
} else {
|
||||||
|
rm := th.LoadAndRunTransformer(toConfig(test.patch...), strings.Join(test.base, "\n---\n"))
|
||||||
|
th.AssertActualEqualsExpected(rm, strings.Join(test.expected, "---\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPatchStrategicMergeTransformerPatchDelete(t *testing.T) {
|
||||||
|
tc := plugins_test.NewEnvForTest(t).Set()
|
||||||
|
defer tc.Reset()
|
||||||
|
|
||||||
|
tc.BuildGoPlugin(
|
||||||
|
"builtin", "", "PatchStrategicMergeTransformer")
|
||||||
|
|
||||||
|
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||||
|
|
||||||
|
th.WriteF("/app/patch.yaml", `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
kind: Deployment
|
||||||
|
$patch: delete
|
||||||
|
`)
|
||||||
|
|
||||||
|
rm := th.LoadAndRunTransformer(`
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: PatchStrategicMergeTransformer
|
||||||
|
metadata:
|
||||||
|
name: notImportantHere
|
||||||
|
paths:
|
||||||
|
- patch.yaml
|
||||||
|
`, target)
|
||||||
|
|
||||||
|
th.AssertActualEqualsExpected(rm, ``)
|
||||||
|
}
|
||||||
@@ -88,6 +88,7 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Target == nil {
|
if p.Target == nil {
|
||||||
|
|||||||
@@ -50,22 +50,40 @@ func (p *plugin) Config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *plugin) Transform(m resmap.ResMap) error {
|
func (p *plugin) Transform(m resmap.ResMap) error {
|
||||||
if len(p.Prefix) == 0 && len(p.Suffix) == 0 {
|
|
||||||
return nil
|
// Even if both the Prefix and Suffix are empty we want
|
||||||
}
|
// to proceed with the transformation. This allows to add contextual
|
||||||
|
// information to the resources (AddNamePrefix and AddNameSuffix).
|
||||||
|
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
if p.shouldSkip(r.OrgId()) {
|
if p.shouldSkip(r.OrgId()) {
|
||||||
|
// Don't change the actual definition
|
||||||
|
// of a CRD.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
id := r.OrgId()
|
id := r.OrgId()
|
||||||
|
// current default configuration contains
|
||||||
|
// only one entry: "metadata/name" with no GVK
|
||||||
for _, path := range p.FieldSpecs {
|
for _, path := range p.FieldSpecs {
|
||||||
if !id.IsSelected(&path.Gvk) {
|
if !id.IsSelected(&path.Gvk) {
|
||||||
|
// With the currrent default configuration,
|
||||||
|
// because no Gvk is specified, so a wild
|
||||||
|
// card
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if smellsLikeANameChange(&path) {
|
if smellsLikeANameChange(&path) {
|
||||||
|
// "metadata/name" is the only field.
|
||||||
|
// this will add a prefix and a suffix
|
||||||
|
// to the resource even if those are
|
||||||
|
// empty
|
||||||
r.AddNamePrefix(p.Prefix)
|
r.AddNamePrefix(p.Prefix)
|
||||||
r.AddNameSuffix(p.Suffix)
|
r.AddNameSuffix(p.Suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the addPrefixSuffix method will not
|
||||||
|
// change the name if both the prefix and suffix
|
||||||
|
// are empty.
|
||||||
err := transformers.MutateField(
|
err := transformers.MutateField(
|
||||||
r.Map(),
|
r.Map(),
|
||||||
path.PathSlice(),
|
path.PathSlice(),
|
||||||
|
|||||||
Reference in New Issue
Block a user