mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 18:40:55 +00:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c464fb0a81 | ||
|
|
694cf23df8 | ||
|
|
e66656aa7f | ||
|
|
eaae7af5fe | ||
|
|
f9fe138114 | ||
|
|
2a2a889c37 | ||
|
|
34287e511f | ||
|
|
e6fffc8ba4 | ||
|
|
86f221611e | ||
|
|
b4d6e89fa2 | ||
|
|
5937bd0259 | ||
|
|
ed3c29be12 | ||
|
|
3d2e956b19 | ||
|
|
dd9d1f95e9 | ||
|
|
a279c08f7d | ||
|
|
a798109161 | ||
|
|
bafd6b5423 | ||
|
|
963913f9ef | ||
|
|
46905588ac | ||
|
|
5426888df4 | ||
|
|
35481ec6d9 | ||
|
|
6c92c30e94 | ||
|
|
02f6b3ec98 | ||
|
|
a9848f2738 | ||
|
|
b4038a6cd2 | ||
|
|
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 | ||
|
|
30b378a924 | ||
|
|
3a843f1eca | ||
|
|
9b40f8ab47 | ||
|
|
dc6dcd8150 | ||
|
|
3cb6c7f1f4 | ||
|
|
7632839bc8 | ||
|
|
c3ea109b59 | ||
|
|
579995dc8a | ||
|
|
b43bd5440d | ||
|
|
c4d899f7f3 | ||
|
|
7998ee7036 | ||
|
|
878960d7b1 | ||
|
|
ed0cfc685b | ||
|
|
b0a7345123 | ||
|
|
580963ea76 | ||
|
|
0707deae95 | ||
|
|
fb44880b8c | ||
|
|
e5ebca6604 | ||
|
|
f5fc9acb84 | ||
|
|
28d1bad3cb | ||
|
|
6f74419628 | ||
|
|
8121467c1e | ||
|
|
a85f297f31 | ||
|
|
76a7816aeb | ||
|
|
7872405379 | ||
|
|
6c17a3409f | ||
|
|
f1dbab9dee | ||
|
|
bfafbbf47f | ||
|
|
08d7c35da7 | ||
|
|
f12704f6c1 | ||
|
|
0edab60b30 | ||
|
|
3c05e2d664 | ||
|
|
095333ffb1 | ||
|
|
0d8d9e2f2b | ||
|
|
120ba6b870 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/kustomize
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
28
Makefile
Normal file
28
Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
BIN_NAME=kustomize
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
all: test build
|
||||
|
||||
test: generate-code test-lint test-go
|
||||
|
||||
test-go:
|
||||
go test -v ./...
|
||||
|
||||
test-lint:
|
||||
golangci-lint run ./...
|
||||
|
||||
generate-code:
|
||||
./plugin/generateBuiltins.sh $(GOPATH)
|
||||
|
||||
build:
|
||||
go build -o $(BIN_NAME) cmd/kustomize/main.go
|
||||
|
||||
install:
|
||||
go install $(PWD)/cmd/kustomize
|
||||
|
||||
clean:
|
||||
go clean
|
||||
rm -f $(BIN_NAME)
|
||||
|
||||
.PHONY: test build install clean generate-code test-go test-lint
|
||||
16
README.md
16
README.md
@@ -1,6 +1,6 @@
|
||||
# kustomize
|
||||
|
||||
_[v3.0.0](https://github.com/kubernetes-sigs/kustomize/releases/tag/v3.0.0) is the latest release._
|
||||
_[v3.0.2](https://github.com/kubernetes-sigs/kustomize/releases/tag/v3.0.2) is the latest release._
|
||||
|
||||
`kustomize` lets you customize raw, template-free YAML
|
||||
files for multiple purposes, leaving the original YAML
|
||||
@@ -24,8 +24,16 @@ these [instructions](docs/INSTALL.md).
|
||||
Browse the [docs](docs) or jump right into the
|
||||
tested [examples](examples).
|
||||
|
||||
kustomize [v2.0.3] is available in [kubectl v1.15][kubectl].
|
||||
## kubectl integration
|
||||
|
||||
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
|
||||
|
||||
| kubectl version | kustomize version |
|
||||
|---------|--------|
|
||||
| v1.15.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||
| v1.14.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||
|
||||
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -161,7 +169,9 @@ is governed by the [Kubernetes Code of Conduct].
|
||||
[imageBase]: docs/images/base.jpg
|
||||
[imageOverlay]: docs/images/overlay.jpg
|
||||
[kind/feature]: https://github.com/kubernetes-sigs/kustomize/labels/kind%2Ffeature
|
||||
[kubectl]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/pages/app_customization/introduction.html
|
||||
[kubernetes documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
|
||||
28
docs/FAQ.md
28
docs/FAQ.md
@@ -28,3 +28,31 @@ To disable this, use v3, and the `load_restrictor` flag:
|
||||
```
|
||||
kustomize build --load_restrictor none $target
|
||||
```
|
||||
|
||||
## Some field is not transformed by kustomize
|
||||
|
||||
Example: [#1319](https://github.com/kubernetes-sigs/kustomize/issues/1319), [#1322](https://github.com/kubernetes-sigs/kustomize/issues/1322), [#1347](https://github.com/kubernetes-sigs/kustomize/issues/1347) and etc.
|
||||
|
||||
The fields transformed by kustomize is configured explicitly in [defaultconfig](https://github.com/kubernetes-sigs/kustomize/tree/master/pkg/transformers/config/defaultconfig). The configuration itself can be customized by including `configurations` in `kustomization.yaml`, e.g.
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
```
|
||||
|
||||
The configuration directive allows customization of the following transformers:
|
||||
|
||||
```yaml
|
||||
commonAnnotations: []
|
||||
commonLabels: []
|
||||
nameprefix: []
|
||||
namespace: []
|
||||
varreference: []
|
||||
namereference: []
|
||||
images: []
|
||||
replicas: []
|
||||
```
|
||||
|
||||
To persist the changes to default configuration, submit a PR like [#1338](https://github.com/kubernetes-sigs/kustomize/pull/1338), [#1348](https://github.com/kubernetes-sigs/kustomize/pull/1348) and etc.
|
||||
|
||||
@@ -23,7 +23,9 @@ English | [简体中文](zh/README.md)
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ What transformations (customizations) should be applied?
|
||||
| [namePrefix](#nameprefix) | string | Prepends value to the names of all resources |
|
||||
| [nameSuffix](#namesuffix) | string | The value is appended to the names of all resources. |
|
||||
| [replicas](#replicas) | list | Replicas modifies the number of replicas of a resource. |
|
||||
| [patches](#patches) | list | Each entry should resolve to a patch that can be applied to multiple targets. |
|
||||
|[patchesStrategicMerge](#patchesstrategicmerge)| list |Each entry in this list should resolve to a partial or complete resource definition file.|
|
||||
|[patchesJson6902](#patchesjson6902)| list |Each entry in this list should resolve to a kubernetes object and a JSON patch that will be applied to the object.|
|
||||
|[transformers](#transformers)|list|[plugin](plugins) configuration files|
|
||||
@@ -263,11 +264,47 @@ resource type is ConfigMap or Secret.
|
||||
nameSuffix: -v2
|
||||
```
|
||||
|
||||
### patches
|
||||
|
||||
Each entry in this list should resolve to an Patch object,
|
||||
which includes a patch and a target selector.
|
||||
The patch can be either a strategic merge patch or a JSON patch.
|
||||
it can be either a patch file or an inline string.
|
||||
The target selects
|
||||
resources by group, version, kind, name, namespace,
|
||||
labelSelector and annotationSelector. A resource
|
||||
which matches all the specified fields is selected
|
||||
to apply the patch.
|
||||
|
||||
```
|
||||
patches:
|
||||
- path: patch.yaml
|
||||
target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: deploy.*
|
||||
labelSelector: "env=dev"
|
||||
annotationSelector: "zone=west"
|
||||
- patch: |-
|
||||
- op: replace
|
||||
path: some/existing/path
|
||||
value: new value
|
||||
target:
|
||||
kind: MyKind
|
||||
labelSelector: "env=dev"
|
||||
```
|
||||
|
||||
The `name` and `namespace` fields of the patch target selector are
|
||||
automatically anchored regular expressions. This means that the value `myapp`
|
||||
is equivalent to `^myapp$`.
|
||||
|
||||
### patchesStrategicMerge
|
||||
|
||||
Each entry in this list should be a relative path
|
||||
Each entry in this list should be either a relative
|
||||
file path or an inline content
|
||||
resolving to a partial or complete resource
|
||||
definition file.
|
||||
definition.
|
||||
|
||||
The names in these (possibly partial) resource
|
||||
files must match names already loaded via the
|
||||
@@ -286,6 +323,22 @@ patchesStrategicMerge:
|
||||
- deployment_increase_memory.yaml
|
||||
```
|
||||
|
||||
The patch content can be a inline string as well.
|
||||
```
|
||||
patchesStrategicMerge:
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nignx:latest
|
||||
```
|
||||
|
||||
### patchesJson6902
|
||||
|
||||
Each entry in this list should resolve to
|
||||
@@ -330,6 +383,23 @@ patchesJson6902:
|
||||
path: add_service_annotation.yaml
|
||||
```
|
||||
|
||||
The patch content can be an inline string as well:
|
||||
|
||||
```
|
||||
patchesJson6902:
|
||||
- target:
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: my-deployment
|
||||
patch: |-
|
||||
- op: add
|
||||
path: /some/new/path
|
||||
value: value
|
||||
- op: replace
|
||||
path: /some/existing/path
|
||||
value: "new value"
|
||||
```
|
||||
|
||||
### replicas
|
||||
|
||||
Replicas modified the number of replicas for a resource.
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
[Go plugin caveats]: goPluginCaveats.md
|
||||
|
||||
This is a (no reading allowed!) 60 second copy/paste guided
|
||||
example.
|
||||
example.
|
||||
|
||||
Full plugin docs [here](README.md).
|
||||
Be sure to read the [Go plugin caveats].
|
||||
|
||||
|
||||
This demo uses a Go plugin, `SopsEncodedSecrets`,
|
||||
that lives in the [sopsencodedsecrets repository].
|
||||
This is an inprocess [Go plugin], not an
|
||||
@@ -22,14 +21,20 @@ current setup.
|
||||
|
||||
#### requirements
|
||||
|
||||
* linux, git, curl, Go 1.12
|
||||
* Google cloud (gcloud) install
|
||||
* a Google account (will use Google kms -
|
||||
volunteers needed to convert to a GPG example).
|
||||
* linux, git, curl, Go 1.12
|
||||
|
||||
For encryption
|
||||
|
||||
* gpg
|
||||
|
||||
Or
|
||||
|
||||
* Google cloud (gcloud) install
|
||||
* a Google account with KMS permission
|
||||
|
||||
## Make a place to work
|
||||
|
||||
```
|
||||
```shell
|
||||
# Keeping these separate to avoid cluttering the DEMO dir.
|
||||
DEMO=$(mktemp -d)
|
||||
tmpGoPath=$(mktemp -d)
|
||||
@@ -40,7 +45,7 @@ tmpGoPath=$(mktemp -d)
|
||||
Need v3.0.0 for what follows, and you must _compile_
|
||||
it (not download the binary from the release page):
|
||||
|
||||
```
|
||||
```shell
|
||||
GOPATH=$tmpGoPath go install sigs.k8s.io/kustomize/v3/cmd/kustomize
|
||||
```
|
||||
|
||||
@@ -62,7 +67,7 @@ The kustomize program reads the config file
|
||||
kustomization file), then locates the Go plugin's
|
||||
object code at the following location:
|
||||
|
||||
> ```
|
||||
> ```shell
|
||||
> $XGD_CONFIG_HOME/kustomize/plugin/$apiVersion/$lKind/$kind.so
|
||||
> ```
|
||||
|
||||
@@ -82,7 +87,7 @@ left to plugins to find their own config.
|
||||
This demo will house the plugin it uses at the
|
||||
ephemeral directory
|
||||
|
||||
```
|
||||
```shell
|
||||
PLUGIN_ROOT=$DEMO/kustomize/plugin
|
||||
```
|
||||
|
||||
@@ -105,10 +110,10 @@ to a plugin.
|
||||
This demo uses a plugin called _SopsEncodedSecrets_,
|
||||
and it lives in the [SopsEncodedSecrets repository].
|
||||
|
||||
Somewhat arbitrarily, we'll chose to install
|
||||
Somewhat arbitrarily, we'll chose to install
|
||||
this plugin with
|
||||
|
||||
```
|
||||
```shell
|
||||
apiVersion=mygenerators
|
||||
kind=SopsEncodedSecrets
|
||||
```
|
||||
@@ -119,7 +124,7 @@ By convention, the ultimate home of the plugin
|
||||
code and supplemental data, tests, documentation,
|
||||
etc. is the lowercase form of its kind.
|
||||
|
||||
```
|
||||
```shell
|
||||
lKind=$(echo $kind | awk '{print tolower($0)}')
|
||||
```
|
||||
|
||||
@@ -129,7 +134,7 @@ In this case, the repo name matches the lowercase
|
||||
kind already, so we just clone the repo and get
|
||||
the proper directory name automatically:
|
||||
|
||||
```
|
||||
```shell
|
||||
mkdir -p $PLUGIN_ROOT/${apiVersion}
|
||||
cd $PLUGIN_ROOT/${apiVersion}
|
||||
git clone git@github.com:monopole/sopsencodedsecrets.git
|
||||
@@ -137,7 +142,7 @@ git clone git@github.com:monopole/sopsencodedsecrets.git
|
||||
|
||||
Remember this directory:
|
||||
|
||||
```
|
||||
```shell
|
||||
MY_PLUGIN_DIR=$PLUGIN_ROOT/${apiVersion}/${lKind}
|
||||
```
|
||||
|
||||
@@ -146,14 +151,14 @@ MY_PLUGIN_DIR=$PLUGIN_ROOT/${apiVersion}/${lKind}
|
||||
Plugins may come with their own tests.
|
||||
This one does, and it hopefully passes:
|
||||
|
||||
```
|
||||
```shell
|
||||
cd $MY_PLUGIN_DIR
|
||||
go test SopsEncodedSecrets_test.go
|
||||
```
|
||||
|
||||
Build the object code for use by kustomize:
|
||||
|
||||
```
|
||||
```shell
|
||||
cd $MY_PLUGIN_DIR
|
||||
GOPATH=$tmpGoPath go build -buildmode plugin -o ${kind}.so ${kind}.go
|
||||
```
|
||||
@@ -171,7 +176,7 @@ On load failure
|
||||
version of Go (_go1.12_) on the same `$GOOS`
|
||||
(_linux_) and `$GOARCH` (_amd64_) used to build
|
||||
the kustomize being [used in this demo].
|
||||
|
||||
|
||||
* change the plugin's dependencies in its `go.mod`
|
||||
to match the versions used by kustomize (check
|
||||
kustomize's `go.mod` used in its tagged commit).
|
||||
@@ -188,11 +193,11 @@ reusable instead of bizarrely woven throughout the
|
||||
code as a individual special cases.
|
||||
|
||||
## Create a kustomization
|
||||
|
||||
|
||||
Make a kustomization directory to
|
||||
hold all your config:
|
||||
|
||||
```
|
||||
```shell
|
||||
MYAPP=$DEMO/myapp
|
||||
mkdir -p $MYAPP
|
||||
```
|
||||
@@ -202,7 +207,7 @@ Make a config file for the SopsEncodedSecrets plugin.
|
||||
Its `apiVersion` and `kind` allow the plugin to be
|
||||
found:
|
||||
|
||||
```
|
||||
```shell
|
||||
cat <<EOF >$MYAPP/secGenerator.yaml
|
||||
apiVersion: ${apiVersion}
|
||||
kind: ${kind}
|
||||
@@ -223,7 +228,7 @@ This plugin expects to find more data in
|
||||
Make a kustomization file referencing the plugin
|
||||
config:
|
||||
|
||||
```
|
||||
```shell
|
||||
cat <<EOF >$MYAPP/kustomization.yaml
|
||||
commonLabels:
|
||||
app: hello
|
||||
@@ -232,31 +237,46 @@ generators:
|
||||
EOF
|
||||
```
|
||||
|
||||
Now for the hard part. Generate the real encrypted data.
|
||||
Now generate the real encrypted data.
|
||||
|
||||
### Assure you have an encryption tool installed
|
||||
|
||||
### Assure you have a Google Cloud sops key ring.
|
||||
We're going to use [sops](https://github.com/mozilla/sops) to encode a file. Choose either GPG or Google Cloud KMS as the secret provider to continue.
|
||||
|
||||
We're going to use [sops](https://github.com/mozilla/sops) to encode a file.
|
||||
#### GPG
|
||||
|
||||
Try this:
|
||||
|
||||
```shell
|
||||
gpg --list-keys
|
||||
```
|
||||
|
||||
If it returns a list, presumably you've already created keys. If not, try import test keys from sops for dev.
|
||||
|
||||
```shell
|
||||
curl https://raw.githubusercontent.com/mozilla/sops/master/pgp/sops_functional_tests_key.asc | gpg --import
|
||||
SOPS_PGP_FP="1022470DE3F0BC54BC6AB62DE05550BC07FB1A0A"
|
||||
```
|
||||
|
||||
#### Google Cloude KMS
|
||||
|
||||
Try this:
|
||||
|
||||
```shell
|
||||
gcloud kms keys list --location global --keyring sops
|
||||
```
|
||||
|
||||
If it succeeds, presumably you've already
|
||||
created keys and placed them in a keyring called `sops`.
|
||||
If not, do this:
|
||||
If it succeeds, presumably you've already created keys and placed them in a keyring called sops. If not, do this:
|
||||
|
||||
```
|
||||
```shell
|
||||
gcloud kms keyrings create sops --location global
|
||||
gcloud kms keys create sops-key --location global \
|
||||
--keyring sops --purpose encryption
|
||||
```
|
||||
|
||||
Extract your keyLocation for use below:
|
||||
```
|
||||
|
||||
```shell
|
||||
keyLocation=$(\
|
||||
gcloud kms keys list --location global --keyring sops |\
|
||||
grep GOOGLE | cut -d " " -f1)
|
||||
@@ -265,14 +285,15 @@ echo $keyLocation
|
||||
|
||||
### Install `sops`
|
||||
|
||||
```
|
||||
```shell
|
||||
GOPATH=$tmpGoPath go install go.mozilla.org/sops/cmd/sops
|
||||
```
|
||||
|
||||
### Create data encrypted with your Google Cloud key
|
||||
### Create data encrypted with your private key
|
||||
|
||||
Create raw data to encrypt:
|
||||
```
|
||||
|
||||
```shell
|
||||
cat <<EOF >$MYAPP/myClearData.yaml
|
||||
VEGETABLE: carrot
|
||||
ROCKET: saturn-v
|
||||
@@ -283,21 +304,31 @@ EOF
|
||||
|
||||
Encrypt the data into file the plugin wants to read:
|
||||
|
||||
With PGP
|
||||
|
||||
```shell
|
||||
$tmpGoPath/bin/sops --encrypt \
|
||||
--pgp $SOPS_PGP_FP \
|
||||
$MYAPP/myClearData.yaml >$MYAPP/myEncryptedData.yaml
|
||||
```
|
||||
|
||||
Or GCP KMS
|
||||
|
||||
```shell
|
||||
$tmpGoPath/bin/sops --encrypt \
|
||||
--gcp-kms $keyLocation \
|
||||
$MYAPP/myClearData.yaml >$MYAPP/myEncryptedData.yaml
|
||||
```
|
||||
|
||||
|
||||
Review the files
|
||||
```
|
||||
|
||||
```shell
|
||||
tree $DEMO
|
||||
```
|
||||
|
||||
This should look something like:
|
||||
|
||||
> ```
|
||||
> ```shell
|
||||
> /tmp/tmp.0kIE9VclPt
|
||||
> ├── kustomize
|
||||
> │ └── plugin
|
||||
@@ -319,7 +350,7 @@ This should look something like:
|
||||
|
||||
## Build your app, using the plugin:
|
||||
|
||||
```
|
||||
```shell
|
||||
XDG_CONFIG_HOME=$DEMO $tmpGoPath/bin/kustomize build --enable_alpha_plugins $MYAPP
|
||||
```
|
||||
|
||||
@@ -328,10 +359,9 @@ encrypted data for the names `ROCKET` and `CAR`.
|
||||
|
||||
Above, if you had set
|
||||
|
||||
> ```
|
||||
> ```shell
|
||||
> PLUGIN_ROOT=$HOME/.config/kustomize/plugin
|
||||
> ```
|
||||
|
||||
there would be no need to use `XDG_CONFIG_HOME` in the
|
||||
_kustomize_ command above.
|
||||
|
||||
|
||||
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.
|
||||
@@ -232,11 +232,11 @@ moment forward.
|
||||
[beta-level rules]: https://github.com/kubernetes/community/blob/master/contributors/devel/api_changes.md#alpha-beta-and-stable-versions
|
||||
[changes]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md
|
||||
[adapt]: https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/types/kustomization.go#L166
|
||||
[special]: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#resources
|
||||
[special]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
[k8s API]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
|
||||
[conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
|
||||
[release process]: ../releasing/README.md
|
||||
[kustomization]: glossary.md#kustomization
|
||||
[`kind`]: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#types-kinds
|
||||
[`kind`]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
[`apiVersion`]: https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning
|
||||
[semantic versioning]: https://semver.org
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
## 发行说明
|
||||
|
||||
* [3.1](../v3.1.0.md) - 2019年7月下旬,扩展 patches 和改进的资源匹配。
|
||||
|
||||
* [3.0](../v3.0.0.md) - 2019年6月下旬,插件开发者发布。
|
||||
|
||||
* [2.1](../v2.1.0.md) - 2019年6月18日
|
||||
|
||||
@@ -33,6 +33,8 @@ Basic Usage
|
||||
|
||||
* [json patch](jsonpatch.md) - Apply a json patch in a kustomization
|
||||
|
||||
* [patch multiple objects](patchMultipleObjects.md) - Apply a patch to multiple objects
|
||||
|
||||
Advanced Usage
|
||||
|
||||
- generator plugins:
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
# before running it.
|
||||
#
|
||||
# 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 {
|
||||
local msg=$1
|
||||
@@ -53,7 +53,7 @@ function setUpEnv {
|
||||
exitWith "Script must be run from $expectedRepo"
|
||||
fi
|
||||
|
||||
GO111MODULE=on go install . || \
|
||||
GO111MODULE=on go install ./cmd/kustomize || \
|
||||
{ exitWith "Failed to install kustomize."; }
|
||||
|
||||
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
|
||||
```
|
||||
@@ -26,6 +26,8 @@ go get sigs.k8s.io/kustomize/v3/cmd/kustomize
|
||||
|
||||
* [json patch](jsonpatch.md) -在 kustomization 中应用 json patch 。
|
||||
|
||||
* [patch multiple objects](patchMultipleObjects.md) - 通过一个patch来修改多个资源。
|
||||
|
||||
高级用法
|
||||
|
||||
- generator 插件:
|
||||
@@ -34,6 +36,10 @@ go get sigs.k8s.io/kustomize/v3/cmd/kustomize
|
||||
|
||||
* [secret generation](../secretGeneratorPlugin.md) - 生成 Secret。
|
||||
|
||||
- transformer 插件:
|
||||
|
||||
* [validation transformer](../validationTransformer/README.md) - 通过 transformer 验证资源。
|
||||
|
||||
- 定制内建 transformer 配置
|
||||
|
||||
* [transformer configs](../transformerconfigs/README.md) - 自定义 transformer 配置。
|
||||
|
||||
1
go.sum
1
go.sum
@@ -149,7 +149,6 @@ k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c=
|
||||
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco=
|
||||
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
||||
@@ -69,6 +69,11 @@ func (fs *UnstructAdapter) GetGvk() gvk.Gvk {
|
||||
}
|
||||
}
|
||||
|
||||
// SetGvk set the Gvk of the object to the input Gvk
|
||||
func (fs *UnstructAdapter) SetGvk(g gvk.Gvk) {
|
||||
fs.SetGroupVersionKind(toSchemaGvk(g))
|
||||
}
|
||||
|
||||
// Copy provides a copy behind an interface.
|
||||
func (fs *UnstructAdapter) Copy() ifc.Kunstructured {
|
||||
return &UnstructAdapter{*fs.DeepCopy()}
|
||||
@@ -333,7 +338,11 @@ func (fs *UnstructAdapter) Patch(patch ifc.Kunstructured) error {
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func (jmp *jsonMergePatch) findConflict(
|
||||
if i == conflictingPatchIdx {
|
||||
continue
|
||||
}
|
||||
if !patches[conflictingPatchIdx].OrgId().GvknEquals(patch.OrgId()) {
|
||||
if !patches[conflictingPatchIdx].OrgId().Equals(patch.OrgId()) {
|
||||
continue
|
||||
}
|
||||
conflict, err := mergepatch.HasConflicts(
|
||||
@@ -105,7 +105,7 @@ func (smp *strategicMergePatch) findConflict(
|
||||
if i == conflictingPatchIdx {
|
||||
continue
|
||||
}
|
||||
if !patches[conflictingPatchIdx].OrgId().GvknEquals(patch.OrgId()) {
|
||||
if !patches[conflictingPatchIdx].OrgId().Equals(patch.OrgId()) {
|
||||
continue
|
||||
}
|
||||
conflict, err := strategicpatch.MergingMapsHaveConflicts(
|
||||
@@ -135,7 +135,7 @@ func MergePatches(patches []*resource.Resource,
|
||||
rc := resmap.New()
|
||||
for ix, patch := range patches {
|
||||
id := patch.OrgId()
|
||||
existing := rc.GetMatchingResourcesByOriginalId(id.GvknEquals)
|
||||
existing := rc.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
if len(existing) == 0 {
|
||||
rc.Append(patch)
|
||||
continue
|
||||
|
||||
@@ -64,8 +64,14 @@ func (ra *ResAccumulator) GetTransformerConfig() *config.TransformerConfig {
|
||||
|
||||
func (ra *ResAccumulator) MergeVars(incoming []types.Var) error {
|
||||
for _, v := range incoming {
|
||||
matched := ra.resMap.GetMatchingResourcesByOriginalId(
|
||||
resid.NewResId(v.ObjRef.GVK(), v.ObjRef.Name).GvknEquals)
|
||||
targetId := resid.NewResIdWithNamespace(v.ObjRef.GVK(), v.ObjRef.Name, v.ObjRef.Namespace)
|
||||
idMatcher := targetId.GvknEquals
|
||||
if targetId.Namespace != "" || !targetId.IsNamespaceableKind() {
|
||||
// Preserve backward compatibility. An empty namespace means
|
||||
// wildcard search on the namespace hence we still use GvknEquals
|
||||
idMatcher = targetId.Equals
|
||||
}
|
||||
matched := ra.resMap.GetMatchingResourcesByOriginalId(idMatcher)
|
||||
if len(matched) > 1 {
|
||||
return fmt.Errorf(
|
||||
"found %d resId matches for var %s "+
|
||||
|
||||
@@ -201,7 +201,7 @@ func NewCmdBuildPrune(
|
||||
|
||||
func writeIndividualFiles(
|
||||
fSys fs.FileSystem, folderPath string, m resmap.ResMap) error {
|
||||
byNamespace := m.GroupedByNamespace()
|
||||
byNamespace := m.GroupedByCurrentNamespace()
|
||||
for namespace, resList := range byNamespace {
|
||||
for _, res := range resList {
|
||||
fName := fileName(res)
|
||||
|
||||
@@ -61,11 +61,13 @@ func determineFieldOrder() []string {
|
||||
"CommonAnnotations",
|
||||
"PatchesStrategicMerge",
|
||||
"PatchesJson6902",
|
||||
"Patches",
|
||||
"ConfigMapGenerator",
|
||||
"SecretGenerator",
|
||||
"GeneratorOptions",
|
||||
"Vars",
|
||||
"Images",
|
||||
"Replicas",
|
||||
"Configurations",
|
||||
"Generators",
|
||||
"Transformers",
|
||||
@@ -73,9 +75,7 @@ func determineFieldOrder() []string {
|
||||
}
|
||||
|
||||
// Add deprecated fields here.
|
||||
deprecated := map[string]bool{
|
||||
"Patches": true,
|
||||
}
|
||||
deprecated := map[string]bool{}
|
||||
|
||||
// Account for the inlined TypeMeta fields.
|
||||
var result []string
|
||||
|
||||
@@ -40,11 +40,13 @@ func TestFieldOrder(t *testing.T) {
|
||||
"CommonAnnotations",
|
||||
"PatchesStrategicMerge",
|
||||
"PatchesJson6902",
|
||||
"Patches",
|
||||
"ConfigMapGenerator",
|
||||
"SecretGenerator",
|
||||
"GeneratorOptions",
|
||||
"Vars",
|
||||
"Images",
|
||||
"Replicas",
|
||||
"Configurations",
|
||||
"Generators",
|
||||
"Transformers",
|
||||
@@ -264,3 +266,85 @@ generatorOptions:
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +35,14 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "no 'git' program on path")
|
||||
}
|
||||
repoSpec.cloneDir, err = fs.NewTmpConfirmedDir()
|
||||
repoSpec.Dir, err = fs.NewTmpConfirmedDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(
|
||||
gitProgram,
|
||||
"init",
|
||||
repoSpec.cloneDir.String())
|
||||
repoSpec.Dir.String())
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
@@ -50,7 +50,7 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
return errors.Wrapf(
|
||||
err,
|
||||
"trouble initializing empty git repo in %s",
|
||||
repoSpec.cloneDir.String())
|
||||
repoSpec.Dir.String())
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
@@ -60,7 +60,7 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
"origin",
|
||||
repoSpec.CloneSpec())
|
||||
cmd.Stdout = &out
|
||||
cmd.Dir = repoSpec.cloneDir.String()
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
@@ -68,20 +68,20 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
"trouble adding remote %s",
|
||||
repoSpec.CloneSpec())
|
||||
}
|
||||
if repoSpec.ref == "" {
|
||||
repoSpec.ref = "master"
|
||||
if repoSpec.Ref == "" {
|
||||
repoSpec.Ref = "master"
|
||||
}
|
||||
cmd = exec.Command(
|
||||
gitProgram,
|
||||
"fetch",
|
||||
"--depth=1",
|
||||
"origin",
|
||||
repoSpec.ref)
|
||||
repoSpec.Ref)
|
||||
cmd.Stdout = &out
|
||||
cmd.Dir = repoSpec.cloneDir.String()
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "trouble fetching %s", repoSpec.ref)
|
||||
return errors.Wrapf(err, "trouble fetching %s", repoSpec.Ref)
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
@@ -90,11 +90,11 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
"--hard",
|
||||
"FETCH_HEAD")
|
||||
cmd.Stdout = &out
|
||||
cmd.Dir = repoSpec.cloneDir.String()
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "trouble hard resetting empty repository to %s", repoSpec.ref)
|
||||
err, "trouble hard resetting empty repository to %s", repoSpec.Ref)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
// used in a test.
|
||||
func DoNothingCloner(dir fs.ConfirmedDir) Cloner {
|
||||
return func(rs *RepoSpec) error {
|
||||
rs.cloneDir = dir
|
||||
rs.Dir = dir
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,36 +39,36 @@ type RepoSpec struct {
|
||||
raw string
|
||||
|
||||
// Host, e.g. github.com
|
||||
host string
|
||||
Host string
|
||||
|
||||
// orgRepo name (organization/repoName),
|
||||
// e.g. kubernetes-sigs/kustomize
|
||||
orgRepo string
|
||||
OrgRepo string
|
||||
|
||||
// ConfirmedDir where the orgRepo is cloned to.
|
||||
cloneDir fs.ConfirmedDir
|
||||
// Dir where the orgRepo is cloned to.
|
||||
Dir fs.ConfirmedDir
|
||||
|
||||
// Relative path in the repository, and in the cloneDir,
|
||||
// to a Kustomization.
|
||||
path string
|
||||
Path string
|
||||
|
||||
// Branch or tag reference.
|
||||
ref string
|
||||
Ref string
|
||||
|
||||
// e.g. .git or empty in case of _git is present
|
||||
gitSuffix string
|
||||
GitSuffix string
|
||||
}
|
||||
|
||||
// CloneSpec returns a string suitable for "git clone {spec}".
|
||||
func (x *RepoSpec) CloneSpec() string {
|
||||
if isAzureHost(x.host) || isAWSHost(x.host) {
|
||||
return x.host + x.orgRepo
|
||||
if isAzureHost(x.Host) || isAWSHost(x.Host) {
|
||||
return x.Host + x.OrgRepo
|
||||
}
|
||||
return x.host + x.orgRepo + x.gitSuffix
|
||||
return x.Host + x.OrgRepo + x.GitSuffix
|
||||
}
|
||||
|
||||
func (x *RepoSpec) CloneDir() fs.ConfirmedDir {
|
||||
return x.cloneDir
|
||||
return x.Dir
|
||||
}
|
||||
|
||||
func (x *RepoSpec) Raw() string {
|
||||
@@ -76,11 +76,11 @@ func (x *RepoSpec) Raw() string {
|
||||
}
|
||||
|
||||
func (x *RepoSpec) AbsPath() string {
|
||||
return x.cloneDir.Join(x.path)
|
||||
return x.Dir.Join(x.Path)
|
||||
}
|
||||
|
||||
func (x *RepoSpec) Cleaner(fSys fs.FileSystem) func() error {
|
||||
return func() error { return fSys.RemoveAll(x.cloneDir.String()) }
|
||||
return func() error { return fSys.RemoveAll(x.Dir.String()) }
|
||||
}
|
||||
|
||||
// From strings like git@github.com:someOrg/someRepo.git or
|
||||
@@ -98,8 +98,8 @@ func NewRepoSpecFromUrl(n string) (*RepoSpec, error) {
|
||||
return nil, fmt.Errorf("url lacks host: %s", n)
|
||||
}
|
||||
return &RepoSpec{
|
||||
raw: n, host: host, orgRepo: orgRepo,
|
||||
cloneDir: notCloned, path: path, ref: gitRef, gitSuffix: gitSuffix}, nil
|
||||
raw: n, Host: host, OrgRepo: orgRepo,
|
||||
Dir: notCloned, Path: path, Ref: gitRef, GitSuffix: gitSuffix}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -72,17 +72,17 @@ func TestNewRepoSpecFromUrl(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("problem %v", err)
|
||||
}
|
||||
if rs.host != hostSpec {
|
||||
bad = append(bad, []string{"host", uri, rs.host, hostSpec})
|
||||
if rs.Host != hostSpec {
|
||||
bad = append(bad, []string{"host", uri, rs.Host, hostSpec})
|
||||
}
|
||||
if rs.orgRepo != orgRepo {
|
||||
bad = append(bad, []string{"orgRepo", uri, rs.orgRepo, orgRepo})
|
||||
if rs.OrgRepo != orgRepo {
|
||||
bad = append(bad, []string{"orgRepo", uri, rs.OrgRepo, orgRepo})
|
||||
}
|
||||
if rs.path != pathName {
|
||||
bad = append(bad, []string{"path", uri, rs.path, pathName})
|
||||
if rs.Path != pathName {
|
||||
bad = append(bad, []string{"path", uri, rs.Path, pathName})
|
||||
}
|
||||
if rs.ref != hrefArg {
|
||||
bad = append(bad, []string{"ref", uri, rs.ref, hrefArg})
|
||||
if rs.Ref != hrefArg {
|
||||
bad = append(bad, []string{"ref", uri, rs.Ref, hrefArg})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,9 +201,9 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) {
|
||||
t.Errorf("AbsPath expected to be %v, but got %v on %s",
|
||||
testcase.absPath, rs.AbsPath(), testcase.input)
|
||||
}
|
||||
if rs.ref != testcase.ref {
|
||||
if rs.Ref != testcase.ref {
|
||||
t.Errorf("ref expected to be %v, but got %v on %s",
|
||||
testcase.ref, rs.ref, testcase.input)
|
||||
testcase.ref, rs.Ref, testcase.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ func (x Gvk) Equals(o Gvk) bool {
|
||||
// In some cases order just specified to provide determinism.
|
||||
var orderFirst = []string{
|
||||
"Namespace",
|
||||
"ResourceQuota",
|
||||
"StorageClass",
|
||||
"CustomResourceDefinition",
|
||||
"MutatingWebhookConfiguration",
|
||||
|
||||
@@ -54,6 +54,7 @@ type Kunstructured interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
UnmarshalJSON([]byte) error
|
||||
GetGvk() gvk.Gvk
|
||||
SetGvk(gvk.Gvk)
|
||||
GetKind() string
|
||||
GetName() string
|
||||
SetName(string)
|
||||
|
||||
@@ -138,11 +138,16 @@ func (th *KustTestHarness) ErrorFromLoadAndRunTransformer(
|
||||
|
||||
func (th *KustTestHarness) RunTransformer(
|
||||
config, input string) (resmap.ResMap, error) {
|
||||
transConfig, err := th.rf.RF().FromBytes([]byte(config))
|
||||
resMap, err := th.rf.NewResMapFromBytes([]byte(input))
|
||||
if err != nil {
|
||||
th.t.Fatalf("Err: %v", err)
|
||||
}
|
||||
resMap, err := th.rf.NewResMapFromBytes([]byte(input))
|
||||
return th.RunTransformerFromResMap(config, resMap)
|
||||
}
|
||||
|
||||
func (th *KustTestHarness) RunTransformerFromResMap(
|
||||
config string, resMap resmap.ResMap) (resmap.ResMap, error) {
|
||||
transConfig, err := th.rf.RF().FromBytes([]byte(config))
|
||||
if err != nil {
|
||||
th.t.Fatalf("Err: %v", err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sigs.k8s.io/kustomize/v3/internal/loadertest"
|
||||
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
|
||||
. "sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
||||
)
|
||||
@@ -41,7 +42,7 @@ port: "12345"
|
||||
)
|
||||
|
||||
func TestLoader(t *testing.T) {
|
||||
tc := NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package plugins
|
||||
package test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -12,13 +12,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
)
|
||||
|
||||
// EnvForTest manages the plugin test environment.
|
||||
// It sets/resets XDG_CONFIG_HOME, makes/removes a temp objRoot.
|
||||
type EnvForTest struct {
|
||||
t *testing.T
|
||||
compiler *Compiler
|
||||
compiler *plugins.Compiler
|
||||
workDir string
|
||||
oldXdg string
|
||||
wasSet bool
|
||||
@@ -61,7 +62,7 @@ func (x *EnvForTest) BuildExecPlugin(g, v, k string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *EnvForTest) makeCompiler() *Compiler {
|
||||
func (x *EnvForTest) makeCompiler() *plugins.Compiler {
|
||||
// The plugin loader wants to find object code under
|
||||
// $XDG_CONFIG_HOME/kustomize/plugins
|
||||
// and the compiler writes object code to
|
||||
@@ -73,11 +74,11 @@ func (x *EnvForTest) makeCompiler() *Compiler {
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
srcRoot, err := DefaultSrcRoot()
|
||||
srcRoot, err := plugins.DefaultSrcRoot()
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
return NewCompiler(srcRoot, objRoot)
|
||||
return plugins.NewCompiler(srcRoot, objRoot)
|
||||
}
|
||||
|
||||
func (x *EnvForTest) createWorkDir() {
|
||||
@@ -112,13 +112,18 @@ type ResMap interface {
|
||||
// match.
|
||||
GetById(resid.ResId) (*resource.Resource, error)
|
||||
|
||||
// GroupedByNamespace returns a map of namespace
|
||||
// GroupedByCurrentNamespace returns a map of namespace
|
||||
// to a slice of *Resource in that namespace.
|
||||
// Resources for whom IsNamespaceableKind is false are
|
||||
// are not included at all (see NonNamespaceable).
|
||||
// Resources with an empty namespace are placed
|
||||
// in the resid.DefaultNamespace entry.
|
||||
GroupedByNamespace() map[string][]*resource.Resource
|
||||
GroupedByCurrentNamespace() map[string][]*resource.Resource
|
||||
|
||||
// GroupByOrginalNamespace performs as GroupByNamespace
|
||||
// but use the original namespace instead of the current
|
||||
// one to perform the grouping.
|
||||
GroupedByOriginalNamespace() map[string][]*resource.Resource
|
||||
|
||||
// NonNamespaceable returns a slice of resources that
|
||||
// cannot be placed in a namespace, e.g.
|
||||
@@ -390,19 +395,19 @@ func demandOneMatch(
|
||||
return nil, fmt.Errorf("no matches for %sId %s", s, id)
|
||||
}
|
||||
|
||||
// GroupedByNamespace implements ResMap.GroupByNamespace
|
||||
func (m *resWrangler) GroupedByNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByNamespace()
|
||||
// GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
|
||||
func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByCurrentNamespace()
|
||||
delete(items, resid.TotallyNotANamespace)
|
||||
return items
|
||||
}
|
||||
|
||||
// NonNamespaceable implements ResMap.NonNamespaceable
|
||||
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
||||
return m.groupedByNamespace()[resid.TotallyNotANamespace]
|
||||
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
||||
}
|
||||
|
||||
func (m *resWrangler) groupedByNamespace() map[string][]*resource.Resource {
|
||||
func (m *resWrangler) groupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||
byNamespace := make(map[string][]*resource.Resource)
|
||||
for _, res := range m.rList {
|
||||
namespace := res.CurId().EffectiveNamespace()
|
||||
@@ -414,6 +419,25 @@ func (m *resWrangler) groupedByNamespace() map[string][]*resource.Resource {
|
||||
return byNamespace
|
||||
}
|
||||
|
||||
// GroupedByNamespace implements ResMap.GroupByOrginalNamespace
|
||||
func (m *resWrangler) GroupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByOriginalNamespace()
|
||||
delete(items, resid.TotallyNotANamespace)
|
||||
return items
|
||||
}
|
||||
|
||||
func (m *resWrangler) groupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||
byNamespace := make(map[string][]*resource.Resource)
|
||||
for _, res := range m.rList {
|
||||
namespace := res.OrgId().EffectiveNamespace()
|
||||
if _, found := byNamespace[namespace]; !found {
|
||||
byNamespace[namespace] = []*resource.Resource{}
|
||||
}
|
||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||
}
|
||||
return byNamespace
|
||||
}
|
||||
|
||||
// AsYaml implements ResMap.
|
||||
func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||
firstObj := true
|
||||
@@ -528,35 +552,27 @@ func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
||||
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
||||
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||
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 := newOne()
|
||||
inputId := inputRes.CurId()
|
||||
isInputIdNamespaceable := inputId.IsNamespaceableKind()
|
||||
rctxm := inputRes.PrefixesSuffixesEquals
|
||||
for _, r := range m.Resources() {
|
||||
if !r.OrgId().IsNamespaceableKind() || inputRes.InSameFuzzyNamespace(r) {
|
||||
err := result.Append(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// 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) {
|
||||
result.append(r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *resWrangler) append(res *resource.Resource) {
|
||||
m.rList = append(m.rList, res)
|
||||
}
|
||||
|
||||
// AppendAll implements ResMap.
|
||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||
if other == nil {
|
||||
@@ -635,11 +651,18 @@ func (m *resWrangler) appendReplaceOrMerge(
|
||||
return nil
|
||||
}
|
||||
|
||||
func anchorRegex(pattern string) string {
|
||||
if pattern == "" {
|
||||
return pattern
|
||||
}
|
||||
return "^" + pattern + "$"
|
||||
}
|
||||
|
||||
// Select returns a list of resources that
|
||||
// are selected by a Selector
|
||||
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
||||
ns := regexp.MustCompile(s.Namespace)
|
||||
nm := regexp.MustCompile(s.Name)
|
||||
ns := regexp.MustCompile(anchorRegex(s.Namespace))
|
||||
nm := regexp.MustCompile(anchorRegex(s.Name))
|
||||
var result []*resource.Resource
|
||||
for _, r := range m.Resources() {
|
||||
curId := r.CurId()
|
||||
|
||||
@@ -356,7 +356,7 @@ func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
||||
r4 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
@@ -365,7 +365,7 @@ func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
||||
r5 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
@@ -408,12 +408,12 @@ func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
||||
"happy namespace no prefix": {
|
||||
filter: r3,
|
||||
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": {
|
||||
filter: r5,
|
||||
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": {
|
||||
filter: r7,
|
||||
|
||||
@@ -41,6 +41,12 @@ metadata:
|
||||
app: name3
|
||||
annotations:
|
||||
bar: baz
|
||||
---
|
||||
apiVersion: group1/v1
|
||||
kind: Kind2
|
||||
metadata:
|
||||
name: x-name1
|
||||
namespace: x-default
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -56,13 +62,13 @@ func TestFindPatchTargets(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
target: types.Selector{
|
||||
Name: "name*",
|
||||
Name: "name.*",
|
||||
},
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Name: "name*",
|
||||
Name: "name.*",
|
||||
AnnotationSelector: "foo=bar",
|
||||
},
|
||||
count: 2,
|
||||
@@ -78,7 +84,7 @@ func TestFindPatchTargets(t *testing.T) {
|
||||
Gvk: gvk.Gvk{
|
||||
Kind: "Kind1",
|
||||
},
|
||||
Name: "name*",
|
||||
Name: "name.*",
|
||||
},
|
||||
count: 2,
|
||||
},
|
||||
@@ -92,7 +98,7 @@ func TestFindPatchTargets(t *testing.T) {
|
||||
target: types.Selector{
|
||||
Name: "",
|
||||
},
|
||||
count: 3,
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
@@ -104,18 +110,60 @@ func TestFindPatchTargets(t *testing.T) {
|
||||
target: types.Selector{
|
||||
Namespace: "",
|
||||
},
|
||||
count: 3,
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Namespace: "default",
|
||||
Name: "name*",
|
||||
Name: "name.*",
|
||||
Gvk: gvk.Gvk{
|
||||
Kind: "Kind1",
|
||||
},
|
||||
},
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Name: "^name.*",
|
||||
},
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Name: "name.*$",
|
||||
},
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Name: "^name.*$",
|
||||
},
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Namespace: "^def.*",
|
||||
},
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Namespace: "def.*$",
|
||||
},
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Namespace: "^def.*$",
|
||||
},
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
target: types.Selector{
|
||||
Namespace: "default",
|
||||
},
|
||||
count: 2,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
actual, err := rm.Select(testcase.target)
|
||||
|
||||
@@ -62,6 +62,14 @@ func (rm *rmBuilder) AddWithNs(ns string, m map[string]interface{}) *rmBuilder {
|
||||
return rm
|
||||
}
|
||||
|
||||
func (rm *rmBuilder) AddWithNsAndName(ns string, n string, m map[string]interface{}) *rmBuilder {
|
||||
err := rm.m.Append(rm.rf.FromMapWithNamespaceAndName(ns, n, m))
|
||||
if err != nil {
|
||||
rm.t.Fatalf("test setup failure: %v", err)
|
||||
}
|
||||
return rm
|
||||
}
|
||||
|
||||
func (rm *rmBuilder) ReplaceResource(m map[string]interface{}) *rmBuilder {
|
||||
r := rm.rf.FromMap(m)
|
||||
_, err := rm.m.Replace(r)
|
||||
|
||||
@@ -43,6 +43,11 @@ func (rf *Factory) FromMapWithNamespace(n string, m map[string]interface{}) *Res
|
||||
return rf.makeOne(rf.kf.FromMap(m), nil).setOriginalNs(n)
|
||||
}
|
||||
|
||||
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
|
||||
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
|
||||
return rf.makeOne(rf.kf.FromMap(m), nil).setOriginalNs(ns).setOriginalName(n)
|
||||
}
|
||||
|
||||
// FromMapAndOption returns a new instance of Resource with given options.
|
||||
func (rf *Factory) FromMapAndOption(
|
||||
m map[string]interface{}, args *types.GeneratorArgs, option *types.GeneratorOptions) *Resource {
|
||||
|
||||
@@ -27,6 +27,22 @@ type Resource struct {
|
||||
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
|
||||
func (r *Resource) DeepCopy() *Resource {
|
||||
rc := &Resource{
|
||||
@@ -104,14 +120,17 @@ func copyStringSlice(s []string) []string {
|
||||
return c
|
||||
}
|
||||
|
||||
// Implements ResCtx AddNamePrefix
|
||||
func (r *Resource) AddNamePrefix(p string) {
|
||||
r.namePrefixes = append(r.namePrefixes, p)
|
||||
}
|
||||
|
||||
// Implements ResCtx AddNameSuffix
|
||||
func (r *Resource) AddNameSuffix(s string) {
|
||||
r.nameSuffixes = append(r.nameSuffixes, s)
|
||||
}
|
||||
|
||||
// Implements ResCtx GetOutermostNamePrefix
|
||||
func (r *Resource) GetOutermostNamePrefix() string {
|
||||
if len(r.namePrefixes) == 0 {
|
||||
return ""
|
||||
@@ -119,6 +138,7 @@ func (r *Resource) GetOutermostNamePrefix() string {
|
||||
return r.namePrefixes[len(r.namePrefixes)-1]
|
||||
}
|
||||
|
||||
// Implements ResCtx GetOutermostNameSuffix
|
||||
func (r *Resource) GetOutermostNameSuffix() string {
|
||||
if len(r.nameSuffixes) == 0 {
|
||||
return ""
|
||||
@@ -126,10 +146,51 @@ func (r *Resource) GetOutermostNameSuffix() string {
|
||||
return r.nameSuffixes[len(r.nameSuffixes)-1]
|
||||
}
|
||||
|
||||
func (r *Resource) InSameFuzzyNamespace(o *Resource) bool {
|
||||
return r.GetNamespace() == o.GetNamespace() &&
|
||||
r.GetOutermostNamePrefix() == o.GetOutermostNamePrefix() &&
|
||||
r.GetOutermostNameSuffix() == o.GetOutermostNameSuffix()
|
||||
func sameBeginningSubarray(a, b []string) bool {
|
||||
maxlen := len(b)
|
||||
if len(a) < len(b) {
|
||||
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 {
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
package target_test
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
kusttest_test "sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
// This is an example of using a helm chart as a base,
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
// TODO: Download and inflate the chart, and check that
|
||||
// in for the test.
|
||||
func TestChartInflatorPlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin(
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
kusttest_test "sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
const patchAddProbe = `
|
||||
@@ -340,7 +340,7 @@ patchesStrategicMerge:
|
||||
}
|
||||
|
||||
func TestIssue1251_Plugins_ProdVsDev(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -380,7 +380,7 @@ transformers:
|
||||
}
|
||||
|
||||
func TestIssue1251_Plugins_Local(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -430,7 +430,7 @@ jsonOp: '%s'
|
||||
|
||||
// Remote in the sense that they are bundled in a different kustomization.
|
||||
func TestIssue1251_Plugins_Bundled(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
1248
pkg/target/extendedpatch_test.go
Normal file
1248
pkg/target/extendedpatch_test.go
Normal file
File diff suppressed because it is too large
Load Diff
243
pkg/target/inlinepatch_test.go
Normal file
243
pkg/target/inlinepatch_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package target_test
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeResourcesForPatchTest(th *kusttest_test.KustTestHarness) {
|
||||
th.WriteF("/app/base/deployment.yaml", `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumeMounts:
|
||||
- name: nginx-persistent-storage
|
||||
mountPath: /tmp/ps
|
||||
volumes:
|
||||
- name: nginx-persistent-storage
|
||||
emptyDir: {}
|
||||
- configMap:
|
||||
name: configmap-in-base
|
||||
name: configmap-in-base
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStrategicMergePatchInline(t *testing.T) {
|
||||
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
||||
makeResourcesForPatchTest(th)
|
||||
th.WriteK("/app/base", `
|
||||
resources:
|
||||
- deployment.yaml
|
||||
|
||||
patchesStrategicMerge:
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: image1
|
||||
`)
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
th.AssertActualEqualsExpected(m, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: image1
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
func TestJSONPatchInline(t *testing.T) {
|
||||
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
||||
makeResourcesForPatchTest(th)
|
||||
th.WriteK("/app/base", `
|
||||
resources:
|
||||
- deployment.yaml
|
||||
|
||||
patchesJson6902:
|
||||
- target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: nginx
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/image
|
||||
value: image1
|
||||
`)
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
th.AssertActualEqualsExpected(m, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: image1
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
func TestExtendedPatchInlineJSON(t *testing.T) {
|
||||
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
||||
makeResourcesForPatchTest(th)
|
||||
th.WriteK("/app/base", `
|
||||
resources:
|
||||
- deployment.yaml
|
||||
|
||||
patches:
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: nginx
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/image
|
||||
value: image1
|
||||
`)
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
th.AssertActualEqualsExpected(m, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: image1
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
func TestExtendedPatchInlineYAML(t *testing.T) {
|
||||
th := kusttest_test.NewKustTestHarness(t, "/app/base")
|
||||
makeResourcesForPatchTest(th)
|
||||
th.WriteK("/app/base", `
|
||||
resources:
|
||||
- deployment.yaml
|
||||
|
||||
patches:
|
||||
- target:
|
||||
kind: Deployment
|
||||
name: nginx
|
||||
patch: |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: image1
|
||||
`)
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
th.AssertActualEqualsExpected(m, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: image1
|
||||
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
|
||||
`)
|
||||
}
|
||||
@@ -64,6 +64,7 @@ func (kt *KustTarget) configureBuiltinTransformers(
|
||||
// - patch SMP
|
||||
configurators := []transformerConfigurator{
|
||||
kt.configureBuiltinPatchStrategicMergeTransformer,
|
||||
kt.configureBuiltinPatchTransformer,
|
||||
kt.configureBuiltinNamespaceTransformer,
|
||||
kt.configureBuiltinNameTransformer,
|
||||
kt.configureBuiltinLabelTransformer,
|
||||
@@ -155,7 +156,7 @@ func (kt *KustTarget) configureBuiltinPatchJson6902Transformer(
|
||||
for _, args := range kt.kustomization.PatchesJson6902 {
|
||||
c.Target = *args.Target
|
||||
c.Path = args.Path
|
||||
c.JsonOp = "" // Not implemented for kustomization file yet.
|
||||
c.JsonOp = args.Patch
|
||||
p := builtin.NewPatchJson6902TransformerPlugin()
|
||||
err = kt.configureBuiltinPlugin(p, c, "patchJson6902")
|
||||
if err != nil {
|
||||
@@ -170,7 +171,6 @@ func (kt *KustTarget) configureBuiltinPatchStrategicMergeTransformer(
|
||||
tConfig *config.TransformerConfig) (
|
||||
result []transformers.Transformer, err error) {
|
||||
if len(kt.kustomization.PatchesStrategicMerge) == 0 {
|
||||
result = append(result, transformers.NewNoOpTransformer())
|
||||
return
|
||||
}
|
||||
var c struct {
|
||||
@@ -178,7 +178,6 @@ func (kt *KustTarget) configureBuiltinPatchStrategicMergeTransformer(
|
||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
||||
}
|
||||
c.Paths = kt.kustomization.PatchesStrategicMerge
|
||||
c.Patches = "" // Not implemented for kustomization file yet
|
||||
p := builtin.NewPatchStrategicMergeTransformerPlugin()
|
||||
err = kt.configureBuiltinPlugin(p, c, "patchStrategicMerge")
|
||||
if err != nil {
|
||||
@@ -188,6 +187,31 @@ func (kt *KustTarget) configureBuiltinPatchStrategicMergeTransformer(
|
||||
return
|
||||
}
|
||||
|
||||
func (kt *KustTarget) configureBuiltinPatchTransformer(
|
||||
tConfig *config.TransformerConfig) (
|
||||
result []transformers.Transformer, err error) {
|
||||
if len(kt.kustomization.Patches) == 0 {
|
||||
return
|
||||
}
|
||||
var c struct {
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
}
|
||||
for _, patch := range kt.kustomization.Patches {
|
||||
c.Target = patch.Target
|
||||
c.Patch = patch.Patch
|
||||
c.Path = patch.Path
|
||||
p := builtin.NewPatchTransformerPlugin()
|
||||
err = kt.configureBuiltinPlugin(p, c, "patch")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (kt *KustTarget) configureBuiltinLabelTransformer(
|
||||
tConfig *config.TransformerConfig) (
|
||||
result []transformers.Transformer, err error) {
|
||||
|
||||
@@ -6,7 +6,6 @@ package target_test
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -334,7 +333,10 @@ vars:
|
||||
t.Fatalf("unexpected size %d", len(vars))
|
||||
}
|
||||
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])
|
||||
}
|
||||
}
|
||||
@@ -387,7 +389,10 @@ resources:
|
||||
t.Fatalf("expected 4 vars, got %d", len(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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ spec:
|
||||
volumeMounts:
|
||||
- name: nginx-persistent-storage
|
||||
mountPath: /tmp/ps
|
||||
- name: sidecar
|
||||
image: sidecar:latest
|
||||
volumes:
|
||||
- name: nginx-persistent-storage
|
||||
emptyDir: {}
|
||||
@@ -138,8 +140,6 @@ spec:
|
||||
env:
|
||||
- name: ANOTHERENV
|
||||
value: FOO
|
||||
- name: sidecar
|
||||
image: sidecar
|
||||
volumes:
|
||||
- name: nginx-persistent-storage
|
||||
`)
|
||||
@@ -187,7 +187,7 @@ spec:
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/ps
|
||||
name: nginx-persistent-storage
|
||||
- image: sidecar
|
||||
- image: sidecar:latest
|
||||
name: sidecar
|
||||
volumes:
|
||||
- gcePersistentDisk:
|
||||
@@ -293,3 +293,258 @@ spec:
|
||||
t.Fatalf("Unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiplePatchesWithPatchDeleteIgnored demonstrates that if the
|
||||
// patch containing the $patch:delete directive is second in the list,
|
||||
// the behavior of kustomize is incorrect, and the sidecar container is
|
||||
// not removed from the final output. No conflict, nor error is reported
|
||||
// even so the patch is ignored. See issue #1354
|
||||
func TestMultiplePatchesWithPatchDeleteIgnored(t *testing.T) {
|
||||
th := kusttest_test.NewKustTestHarness(t, "/app/overlay/staging")
|
||||
makeCommonFileForMultiplePatchTest(th)
|
||||
th.WriteF("/app/overlay/staging/deployment-patch1.yaml", `
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: SOME_NAME
|
||||
value: somevalue
|
||||
`)
|
||||
th.WriteF("/app/overlay/staging/deployment-patch2.yaml", `
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- $patch: delete
|
||||
name: sidecar
|
||||
`)
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
th.AssertActualEqualsExpected(m, `apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
name: staging-team-foo-nginx
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: SOME_NAME
|
||||
value: somevalue
|
||||
image: nginx
|
||||
name: nginx
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/ps
|
||||
name: nginx-persistent-storage
|
||||
- image: sidecar:latest
|
||||
name: sidecar
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: nginx-persistent-storage
|
||||
- configMap:
|
||||
name: staging-team-foo-configmap-in-base-g7k6gt2889
|
||||
name: configmap-in-base
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
name: staging-team-foo-nginx
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
name: staging-team-foo-configmap-in-base-g7k6gt2889
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
hello: world
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
env: staging
|
||||
name: staging-configmap-in-overlay-k7cbc75tg8
|
||||
`)
|
||||
}
|
||||
|
||||
// TestMultiplePatchesWithPatchDeleteApplied demonstrates that if the
|
||||
// patch containing the $patch:delete directive is first in the list,
|
||||
// the behavior of kustomize is correct, and the sidecar container
|
||||
// is removed from the final output. See issue #1354
|
||||
func TestMultiplePatchesWithPatchDeleteApplied(t *testing.T) {
|
||||
th := kusttest_test.NewKustTestHarness(t, "/app/overlay/staging")
|
||||
makeCommonFileForMultiplePatchTest(th)
|
||||
th.WriteF("/app/overlay/staging/deployment-patch1.yaml", `
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- $patch: delete
|
||||
name: sidecar
|
||||
`)
|
||||
th.WriteF("/app/overlay/staging/deployment-patch2.yaml", `
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: SOME_NAME
|
||||
value: somevalue
|
||||
`)
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
th.AssertActualEqualsExpected(m, `apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
name: staging-team-foo-nginx
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: SOME_NAME
|
||||
value: somevalue
|
||||
image: nginx
|
||||
name: nginx
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/ps
|
||||
name: nginx-persistent-storage
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: nginx-persistent-storage
|
||||
- configMap:
|
||||
name: staging-team-foo-configmap-in-base-g7k6gt2889
|
||||
name: configmap-in-base
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
name: staging-team-foo-nginx
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations:
|
||||
note: This is a test annotation
|
||||
labels:
|
||||
app: mynginx
|
||||
env: staging
|
||||
org: example.com
|
||||
team: foo
|
||||
name: staging-team-foo-configmap-in-base-g7k6gt2889
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
hello: world
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
env: staging
|
||||
name: staging-configmap-in-overlay-k7cbc75tg8
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -51,19 +51,551 @@ resources:
|
||||
- role.yaml
|
||||
`)
|
||||
|
||||
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
// TODO: Fix #1044
|
||||
// This should not be an error -
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
// This validates Fix #1444. This should not be an error anymore -
|
||||
// the secrets have the same name but are in different namespaces.
|
||||
// The ClusterRole (by def) is not in a namespace,
|
||||
// an in this case applies to *any* Secret resource
|
||||
// named "dummy"
|
||||
if err == nil {
|
||||
t.Fatalf("unexpected lack of error")
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"slice case - multiple matches for ~G_v1_Secret|default|dummy") {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
th.AssertActualEqualsExpected(m, `
|
||||
apiVersion: v1
|
||||
data:
|
||||
dummy: ""
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: dummy
|
||||
namespace: default
|
||||
type: Opaque
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
dummy: ""
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: dummy
|
||||
namespace: kube-system
|
||||
type: Opaque
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: dummy
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resourceNames:
|
||||
- dummy
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- 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)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
kusttest_test "sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/loader"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/target"
|
||||
@@ -22,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPluginDir(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin(
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package target_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
@@ -16,6 +15,7 @@ resources:
|
||||
- serviceaccount.yaml
|
||||
- rolebinding.yaml
|
||||
- clusterrolebinding.yaml
|
||||
- clusterrole.yaml
|
||||
namePrefix: pfx-
|
||||
nameSuffix: -sfx
|
||||
`)
|
||||
@@ -32,7 +32,7 @@ metadata:
|
||||
name: rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
kind: ClusterRole
|
||||
name: role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
@@ -45,11 +45,21 @@ metadata:
|
||||
name: rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
kind: ClusterRole
|
||||
name: role
|
||||
subjects:
|
||||
- kind: 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
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
kind: ClusterRole
|
||||
name: pfx-role-sfx
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: pfx-serviceaccount-sfx
|
||||
@@ -109,11 +119,25 @@ metadata:
|
||||
name: pfx-rolebinding-sfx
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
kind: ClusterRole
|
||||
name: pfx-role-sfx
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
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
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
kind: ClusterRole
|
||||
name: a-pfx-role-sfx-suffixA
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: a-pfx-serviceaccount-sfx-suffixA
|
||||
@@ -149,11 +173,25 @@ metadata:
|
||||
name: a-pfx-rolebinding-sfx-suffixA
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -177,8 +215,8 @@ metadata:
|
||||
name: b-pfx-rolebinding-sfx-suffixB
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
kind: ClusterRole
|
||||
name: b-pfx-role-sfx-suffixB
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: b-pfx-serviceaccount-sfx-suffixB
|
||||
@@ -189,11 +227,25 @@ metadata:
|
||||
name: b-pfx-rolebinding-sfx-suffixB
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -218,8 +270,8 @@ metadata:
|
||||
name: a-pfx-rolebinding-sfx-suffixA
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
kind: ClusterRole
|
||||
name: a-pfx-role-sfx-suffixA
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: a-pfx-serviceaccount-sfx-suffixA
|
||||
@@ -230,12 +282,26 @@ metadata:
|
||||
name: a-pfx-rolebinding-sfx-suffixA
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
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:
|
||||
@@ -247,8 +313,8 @@ metadata:
|
||||
name: b-pfx-rolebinding-sfx-suffixB
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
kind: ClusterRole
|
||||
name: b-pfx-role-sfx-suffixB
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: b-pfx-serviceaccount-sfx-suffixB
|
||||
@@ -259,11 +325,25 @@ metadata:
|
||||
name: b-pfx-rolebinding-sfx-suffixB
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: role
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -289,12 +369,100 @@ metadata:
|
||||
name: serviceaccount
|
||||
`)
|
||||
|
||||
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected resource conflict.")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(), "multiple matches for ~G_v1_ServiceAccount") {
|
||||
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
kusttest_test "sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func writeDeployment(th *kusttest_test.KustTestHarness, path string) {
|
||||
@@ -50,7 +50,7 @@ metadata:
|
||||
}
|
||||
|
||||
func TestOrderedTransformers(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -96,7 +96,7 @@ spec:
|
||||
}
|
||||
|
||||
func TestPluginsNotEnabled(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -119,7 +119,7 @@ transformers:
|
||||
}
|
||||
|
||||
func TestSedTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin(
|
||||
@@ -187,7 +187,7 @@ metadata:
|
||||
}
|
||||
|
||||
func TestTransformedTransformers(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -272,10 +272,10 @@ nameReference:
|
||||
- path: spec/service/name
|
||||
kind: APIService
|
||||
group: apiregistration.k8s.io
|
||||
- path: webhooks/clientConfig/service/name
|
||||
- path: webhooks/clientConfig/service
|
||||
kind: ValidatingWebhookConfiguration
|
||||
group: admissionregistration.k8s.io
|
||||
- path: webhooks/clientConfig/service/name
|
||||
- path: webhooks/clientConfig/service
|
||||
kind: MutatingWebhookConfiguration
|
||||
group: admissionregistration.k8s.io
|
||||
|
||||
@@ -299,10 +299,10 @@ nameReference:
|
||||
- kind: ServiceAccount
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- path: subjects/name
|
||||
- path: subjects
|
||||
kind: RoleBinding
|
||||
group: rbac.authorization.k8s.io
|
||||
- path: subjects/name
|
||||
- path: subjects
|
||||
kind: ClusterRoleBinding
|
||||
group: rbac.authorization.k8s.io
|
||||
- path: spec/serviceAccountName
|
||||
@@ -343,6 +343,8 @@ nameReference:
|
||||
fieldSpecs:
|
||||
- path: spec/volumeName
|
||||
kind: PersistentVolumeClaim
|
||||
- path: rules/resourceNames
|
||||
kind: ClusterRole
|
||||
|
||||
- kind: StorageClass
|
||||
version: v1
|
||||
@@ -352,5 +354,7 @@ nameReference:
|
||||
kind: PersistentVolume
|
||||
- path: spec/storageClassName
|
||||
kind: PersistentVolumeClaim
|
||||
- path: spec/volumeClaimTemplates/spec/storageClassName
|
||||
kind: StatefulSet
|
||||
`
|
||||
)
|
||||
|
||||
@@ -21,5 +21,9 @@ const (
|
||||
namespace:
|
||||
- path: metadata/namespace
|
||||
create: true
|
||||
- path: subjects
|
||||
kind: RoleBinding
|
||||
- path: subjects
|
||||
kind: ClusterRoleBinding
|
||||
`
|
||||
)
|
||||
|
||||
@@ -106,6 +106,107 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// selectReferral picks the referral among a subset of candidates.
|
||||
// It returns the current name and namespace of the selected candidate.
|
||||
// Note that the content of the referricalCandidateSubset slice is most of the time
|
||||
// identical to the referralCandidates resmap. Still in some cases, such
|
||||
// as ClusterRoleBinding, the subset only contains the resources of a specific
|
||||
// namespace.
|
||||
func (o *nameReferenceTransformer) selectReferral(
|
||||
oldName string,
|
||||
referrer *resource.Resource,
|
||||
target gvk.Gvk,
|
||||
referralCandidates resmap.ResMap,
|
||||
referralCandidateSubset []*resource.Resource) (interface{}, interface{}, error) {
|
||||
|
||||
for _, res := range referralCandidateSubset {
|
||||
id := res.OrgId()
|
||||
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
// If there's more than one match, there's no way
|
||||
// to know which one to pick, so emit error.
|
||||
if len(matches) > 1 {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"multiple matches for %s:\n %v",
|
||||
id, getIds(matches))
|
||||
}
|
||||
// In the resource, note that it is referenced
|
||||
// by the referrer.
|
||||
res.AppendRefBy(referrer.CurId())
|
||||
// Return transformed name of the object,
|
||||
// complete with prefixes, hashes, etc.
|
||||
return res.GetName(), res.GetNamespace(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return oldName, nil, nil
|
||||
}
|
||||
|
||||
// utility function to replace a simple string by the new name
|
||||
func (o *nameReferenceTransformer) getSimpleNameField(
|
||||
oldName string,
|
||||
referrer *resource.Resource,
|
||||
target gvk.Gvk,
|
||||
referralCandidates resmap.ResMap,
|
||||
referralCandidateSubset []*resource.Resource) (interface{}, error) {
|
||||
|
||||
newName, _, err := o.selectReferral(oldName, referrer, target,
|
||||
referralCandidates, referralCandidateSubset)
|
||||
|
||||
return newName, err
|
||||
}
|
||||
|
||||
// utility function to replace name field within a map[string]interface{}
|
||||
// and leverage the namespace field.
|
||||
func (o *nameReferenceTransformer) getNameAndNsStruct(
|
||||
inMap map[string]interface{},
|
||||
referrer *resource.Resource,
|
||||
target gvk.Gvk,
|
||||
referralCandidates resmap.ResMap) (interface{}, error) {
|
||||
|
||||
// Example:
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to contain a name field", inMap)
|
||||
}
|
||||
oldName, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to contain a name field of type string", oldName)
|
||||
}
|
||||
|
||||
subset := referralCandidates.Resources()
|
||||
if namespacevalue, ok := inMap["namespace"]; ok {
|
||||
namespace := namespacevalue.(string)
|
||||
bynamespace := referralCandidates.GroupedByOriginalNamespace()
|
||||
if _, ok := bynamespace[namespace]; !ok {
|
||||
return inMap, nil
|
||||
}
|
||||
subset = bynamespace[namespace]
|
||||
}
|
||||
|
||||
newname, newnamespace, err := o.selectReferral(oldName, referrer, target,
|
||||
referralCandidates, subset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (newname == oldName) && (newnamespace == nil) {
|
||||
// no candidate found.
|
||||
return inMap, nil
|
||||
}
|
||||
|
||||
inMap["name"] = newname
|
||||
if newnamespace != "" {
|
||||
// We don't want value "" to replace value "default" since
|
||||
// the empty string is handled as a wild card here not default namespace
|
||||
// by kubernetes.
|
||||
inMap["namespace"] = newnamespace
|
||||
}
|
||||
return inMap, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *nameReferenceTransformer) getNewNameFunc(
|
||||
referrer *resource.Resource,
|
||||
target gvk.Gvk,
|
||||
@@ -114,52 +215,46 @@ func (o *nameReferenceTransformer) getNewNameFunc(
|
||||
switch in.(type) {
|
||||
case string:
|
||||
oldName, _ := in.(string)
|
||||
for _, res := range referralCandidates.Resources() {
|
||||
id := res.OrgId()
|
||||
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.GvknEquals)
|
||||
// If there's more than one match, there's no way
|
||||
// to know which one to pick, so emit error.
|
||||
if len(matches) > 1 {
|
||||
return nil, fmt.Errorf(
|
||||
"string case - multiple matches for %s:\n %v",
|
||||
id, getIds(matches))
|
||||
}
|
||||
// In the resource, note that it is referenced
|
||||
// by the referrer.
|
||||
res.AppendRefBy(referrer.CurId())
|
||||
// Return transformed name of the object,
|
||||
// complete with prefixes, hashes, etc.
|
||||
return res.GetName(), nil
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
return o.getSimpleNameField(oldName, referrer, target,
|
||||
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{}:
|
||||
l, _ := in.([]interface{})
|
||||
var names []string
|
||||
for _, item := range l {
|
||||
name, ok := item.(string)
|
||||
if !ok {
|
||||
for idx, item := range l {
|
||||
switch item.(type) {
|
||||
case string:
|
||||
// Kind: Role/ClusterRole
|
||||
// FieldSpec is rules.resourceNames
|
||||
oldName, _ := item.(string)
|
||||
newName, err := o.getSimpleNameField(oldName, referrer, target,
|
||||
referralCandidates, referralCandidates.Resources())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l[idx] = newName
|
||||
case map[string]interface{}:
|
||||
// Kind: RoleBinding/ClusterRoleBinding
|
||||
// FieldSpec is subjects
|
||||
// Note: The corresponding fieldSpec had been changed from
|
||||
// from path: subjects/name to just path: subjects. This is
|
||||
// what get mutatefield to request the mapping of the whole
|
||||
// map containing namespace and name instead of just a simple
|
||||
// string field containing the name
|
||||
oldMap, _ := item.(map[string]interface{})
|
||||
newMap, err := o.getNameAndNsStruct(oldMap, referrer, target,
|
||||
referralCandidates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l[idx] = newMap
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"%#v is expected to be %T", item, name)
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
for _, res := range referralCandidates.Resources() {
|
||||
indexes := indexOf(res.GetOriginalName(), names)
|
||||
id := res.OrgId()
|
||||
if id.IsSelected(&target) && len(indexes) > 0 {
|
||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.GvknEquals)
|
||||
if len(matches) > 1 {
|
||||
return nil, fmt.Errorf(
|
||||
"slice case - multiple matches for %s:\n %v",
|
||||
id, getIds(matches))
|
||||
}
|
||||
for _, index := range indexes {
|
||||
l[index] = res.GetName()
|
||||
}
|
||||
res.AppendRefBy(referrer.CurId())
|
||||
return l, nil
|
||||
"%#v is expected to be either a []string or a []map[string]interface{}", in)
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmaptest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
||||
@@ -466,6 +468,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
@@ -497,7 +500,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}).ResMap(),
|
||||
expectedErr: "is expected to be string"},
|
||||
expectedErr: "is expected to be"},
|
||||
{
|
||||
resMap: resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
@@ -517,7 +520,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}).ResMap(),
|
||||
expectedErr: "is expected to be either a string or a []interface{}"},
|
||||
expectedErr: "is expected to contain a name field"},
|
||||
}
|
||||
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
@@ -590,3 +593,469 @@ func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// utility map to create a deployment object
|
||||
// with (metadatanamespace, metadataname) as key
|
||||
// and pointing to "refname" secret and configmap
|
||||
func deploymentMap(metadatanamespace string, metadataname string,
|
||||
configmapref string, secretref string) map[string]interface{} {
|
||||
deployment := map[string]interface{}{
|
||||
"group": "apps",
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": metadataname,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:1.7.9",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "CM_FOO",
|
||||
"valueFrom": map[string]interface{}{
|
||||
"configMapKeyRef": map[string]interface{}{
|
||||
"name": configmapref,
|
||||
"key": "somekey",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "SECRET_FOO",
|
||||
"valueFrom": map[string]interface{}{
|
||||
"secretKeyRef": map[string]interface{}{
|
||||
"name": secretref,
|
||||
"key": "somekey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if metadatanamespace != "" {
|
||||
metadata := deployment["metadata"].(map[string]interface{})
|
||||
metadata["namespace"] = metadatanamespace
|
||||
}
|
||||
return deployment
|
||||
}
|
||||
|
||||
const (
|
||||
defaultNs = "default"
|
||||
ns1 = "ns1"
|
||||
ns2 = "ns2"
|
||||
ns3 = "ns3"
|
||||
ns4 = "ns4"
|
||||
|
||||
orgname = "uniquename"
|
||||
prefixedname = "prefix-uniquename"
|
||||
suffixedname = "uniquename-suffix"
|
||||
modifiedname = "modifiedname"
|
||||
)
|
||||
|
||||
// TestNameReferenceNamespace creates serviceAccount and clusterRoleBinding
|
||||
// object with the same original names (uniquename) in different namespaces
|
||||
// and with different current Id.
|
||||
func TestNameReferenceNamespace(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
// Add ConfigMap with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
}}).
|
||||
AddWithNsAndName(ns1, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": prefixedname,
|
||||
"namespace": ns1,
|
||||
}}).
|
||||
AddWithNsAndName(ns2, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": suffixedname,
|
||||
"namespace": ns2,
|
||||
}}).
|
||||
// Add Secret with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||
AddWithNsAndName(defaultNs, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
"namespace": defaultNs,
|
||||
}}).
|
||||
AddWithNsAndName(ns1, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": prefixedname,
|
||||
"namespace": ns1,
|
||||
}}).
|
||||
AddWithNsAndName(ns2, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": suffixedname,
|
||||
"namespace": ns2,
|
||||
}}).
|
||||
// Add Deployment with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||
AddWithNsAndName(defaultNs, orgname, deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
|
||||
AddWithNsAndName(ns1, orgname, deploymentMap(ns1, prefixedname, orgname, orgname)).
|
||||
AddWithNsAndName(ns2, orgname, deploymentMap(ns2, suffixedname, orgname, orgname)).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
ReplaceResource(deploymentMap(defaultNs, modifiedname, modifiedname, modifiedname)).
|
||||
ReplaceResource(deploymentMap(ns1, prefixedname, prefixedname, prefixedname)).
|
||||
ReplaceResource(deploymentMap(ns2, suffixedname, suffixedname, suffixedname)).ResMap()
|
||||
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
err := nrt.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNameReferenceNamespace creates serviceAccount and clusterRoleBinding
|
||||
// object with the same original names (uniquename) in different namespaces
|
||||
// and with different current Id.
|
||||
func TestNameReferenceClusterWide(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
// Add ServiceAccount with the same org name in noNs, "ns1" and "ns2" namespaces
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
}}).
|
||||
AddWithNsAndName(ns1, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": prefixedname,
|
||||
"namespace": ns1,
|
||||
}}).
|
||||
AddWithNsAndName(ns2, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": suffixedname,
|
||||
"namespace": ns2,
|
||||
}}).
|
||||
// Add a PersistentVolume to have a clusterwide resource
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolume",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
}}).
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
},
|
||||
"rules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"resources": []interface{}{
|
||||
"persistentvolumes",
|
||||
},
|
||||
"resourceNames": []interface{}{
|
||||
orgname,
|
||||
},
|
||||
},
|
||||
}}).
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": orgname,
|
||||
},
|
||||
"subjects": []interface{}{
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": defaultNs,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": ns1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": ns2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": "random",
|
||||
},
|
||||
}}).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
ReplaceResource(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
},
|
||||
// Behavior of the transformer is still imperfect
|
||||
// It should use the (resources,apigroup,resourceNames) as
|
||||
// combination to select the candidates.
|
||||
"rules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"resources": []interface{}{
|
||||
"persistentvolumes",
|
||||
},
|
||||
"resourceNames": []interface{}{
|
||||
modifiedname,
|
||||
},
|
||||
},
|
||||
}}).
|
||||
ReplaceResource(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": modifiedname,
|
||||
},
|
||||
// The following tests required a change in
|
||||
// getNameFunc implementation in order to leverage
|
||||
// the namespace field.
|
||||
"subjects": []interface{}{
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": modifiedname,
|
||||
"namespace": defaultNs,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": prefixedname,
|
||||
"namespace": ns1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": suffixedname,
|
||||
"namespace": ns2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": "random",
|
||||
},
|
||||
},
|
||||
}).ResMap()
|
||||
|
||||
clusterRoleId := resid.NewResId(
|
||||
gvk.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
||||
clusterRoleBindingId := resid.NewResId(
|
||||
gvk.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
err := nrt.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNameReferenceNamespaceTransformation creates serviceAccount and clusterRoleBinding
|
||||
// object with the same original names (uniquename) in different namespaces
|
||||
// and with different current Id.
|
||||
func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
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
|
||||
AddWithNsAndName(ns1, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": prefixedname,
|
||||
"namespace": ns1,
|
||||
}}).
|
||||
// Simulate NamespaceTransformer effect (ns3 transformed in ns2)
|
||||
AddWithNsAndName(ns3, orgname, map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": suffixedname,
|
||||
"namespace": ns2,
|
||||
}}).
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
}}).
|
||||
AddWithName(orgname, map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": orgname,
|
||||
},
|
||||
"subjects": []interface{}{
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": ns1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": ns3,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": "random",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": ns4,
|
||||
},
|
||||
}}).ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
ReplaceResource(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": modifiedname,
|
||||
},
|
||||
"roleRef": map[string]interface{}{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "ClusterRole",
|
||||
"name": modifiedname,
|
||||
},
|
||||
// The following tests required a change in
|
||||
// getNameFunc implementation in order to leverage
|
||||
// the namespace field.
|
||||
"subjects": []interface{}{
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": prefixedname,
|
||||
"namespace": ns1,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": suffixedname,
|
||||
"namespace": ns2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": "random",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"kind": "ServiceAccount",
|
||||
"name": orgname,
|
||||
"namespace": ns4,
|
||||
},
|
||||
},
|
||||
}).ResMap()
|
||||
|
||||
clusterRoleId := resid.NewResId(
|
||||
gvk.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}, modifiedname)
|
||||
clusterRoleBindingId := resid.NewResId(
|
||||
gvk.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}, modifiedname)
|
||||
clusterRole, _ := expected.GetByCurrentId(clusterRoleId)
|
||||
clusterRole.AppendRefBy(clusterRoleBindingId)
|
||||
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
err := nrt.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNameReferenceNamespace creates configmap, secret, deployment
|
||||
// It validates the change done is IsSameFuzzyNamespace which
|
||||
// uses the IsNsEquals method instead of the simple == operator.
|
||||
func TestNameReferenceCandidateSelection(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddWithName("cm1", map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "p1-cm1-hash",
|
||||
}}).
|
||||
AddWithNsAndName("default", "secret1", map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "p1-secret1-hash",
|
||||
"namespace": "default",
|
||||
}}).
|
||||
AddWithName("deploy1", deploymentMap("", "p1-deploy1", "cm1", "secret1")).
|
||||
ResMap()
|
||||
|
||||
expected := resmaptest_test.NewSeededRmBuilder(t, rf, m.ShallowCopy()).
|
||||
ReplaceResource(deploymentMap("", "p1-deploy1", "p1-cm1-hash", "p1-secret1-hash")).
|
||||
ResMap()
|
||||
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
err := nrt.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err = expected.ErrorIfNotEqualLists(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/image"
|
||||
)
|
||||
@@ -195,21 +193,6 @@ func (k *Kustomization) EnforceFields() []string {
|
||||
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{
|
||||
"patches:": "patchesStrategicMerge:",
|
||||
"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.
|
||||
type GeneratorArgs struct {
|
||||
// Namespace for the configmap, optional
|
||||
@@ -348,6 +331,9 @@ type PatchJson6902 struct {
|
||||
|
||||
// relative file path for a json patch file inside a kustomization
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
|
||||
// inline patch string
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
}
|
||||
|
||||
// PatchTarget represents the kubernetes object that the patch is applied to
|
||||
@@ -386,7 +372,7 @@ type Patch struct {
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -18,6 +18,7 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -54,6 +55,7 @@ type Target struct {
|
||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||
gvk.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// FieldSelector contains the fieldPath to an object field.
|
||||
@@ -64,10 +66,22 @@ type FieldSelector struct {
|
||||
}
|
||||
|
||||
// defaulting sets reference to field used by default.
|
||||
func (v *Var) defaulting() {
|
||||
func (v *Var) Defaulting() {
|
||||
if v.FieldRef.FieldPath == "" {
|
||||
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.
|
||||
@@ -129,11 +143,52 @@ func (vs *VarSet) Merge(v Var) error {
|
||||
return fmt.Errorf(
|
||||
"var '%s' already encountered", v.Name)
|
||||
}
|
||||
v.defaulting()
|
||||
v.Defaulting()
|
||||
vs.set[v.Name] = v
|
||||
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.
|
||||
func (vs *VarSet) Contains(other Var) bool {
|
||||
return vs.Get(other.Name) != nil
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestDefaulting(t *testing.T) {
|
||||
Name: "my-secret",
|
||||
},
|
||||
}
|
||||
v.defaulting()
|
||||
v.Defaulting()
|
||||
if v.FieldRef.FieldPath != defaultFieldPath {
|
||||
t.Fatalf("expected %s, got %v",
|
||||
defaultFieldPath, v.FieldRef.FieldPath)
|
||||
|
||||
@@ -149,7 +149,7 @@ func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) e
|
||||
|
||||
func isImageMatched(s, t string) bool {
|
||||
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$")
|
||||
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.-]*)?$")
|
||||
return pattern.MatchString(s)
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ func split(imageName string) (name string, tag string) {
|
||||
}
|
||||
|
||||
i := ic
|
||||
if ic < 0 {
|
||||
if ia > 0 {
|
||||
i = ia
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
@@ -36,23 +35,23 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
id := r.OrgId()
|
||||
fs, ok := p.isSelected(id)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(r.Map()) == 0 {
|
||||
// Don't mutate empty objects?
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
p.updateClusterRoleBinding(m)
|
||||
p.updateServiceReference(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -62,104 +61,64 @@ const metaNamespace = "metadata/namespace"
|
||||
// all objects have it, even "ClusterKind" objects
|
||||
// that don't exist in a namespace (the Namespace
|
||||
// object itself doesn't live in a namespace).
|
||||
func doIt(id resid.ResId, fs *config.FieldSpec) bool {
|
||||
return fs.Path != metaNamespace ||
|
||||
(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) {
|
||||
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []config.FieldSpec {
|
||||
res := []config.FieldSpec{}
|
||||
for _, fs := range p.FieldSpecs {
|
||||
if id.IsSelected(&fs.Gvk) {
|
||||
return &fs, true
|
||||
if id.IsSelected(&fs.Gvk) && (fs.Path != metaNamespace || (fs.Path == metaNamespace && id.IsNamespaceableKind())) {
|
||||
res = append(res, fs)
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *NamespaceTransformerPlugin) updateClusterRoleBinding(m resmap.ResMap) {
|
||||
srvAccount := gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
|
||||
saMap := map[string]bool{}
|
||||
for _, id := range m.AllIds() {
|
||||
if id.Gvk.Equals(srvAccount) {
|
||||
saMap[id.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, res := range m.Resources() {
|
||||
if res.OrgId().Kind != "ClusterRoleBinding" &&
|
||||
res.OrgId().Kind != "RoleBinding" {
|
||||
continue
|
||||
}
|
||||
objMap := res.Map()
|
||||
subjects, ok := objMap["subjects"].([]interface{})
|
||||
if subjects == nil || !ok {
|
||||
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
|
||||
func (o *NamespaceTransformerPlugin) changeNamespace(
|
||||
referrer *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch in.(type) {
|
||||
case string:
|
||||
// will happen when the metadata/namespace
|
||||
// value is replaced
|
||||
return o.Namespace, nil
|
||||
case []interface{}:
|
||||
l, _ := in.([]interface{})
|
||||
for idx, item := range l {
|
||||
switch item.(type) {
|
||||
case map[string]interface{}:
|
||||
// Will happen when mutating the subjects
|
||||
// field of ClusterRoleBinding and RoleBinding
|
||||
inMap, _ := item.(map[string]interface{})
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
continue
|
||||
}
|
||||
return svc, nil
|
||||
})
|
||||
webhooks[i] = webhook
|
||||
name, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type PatchStrategicMergeTransformerPlugin struct {
|
||||
rf *resmap.Factory
|
||||
loadedPatches []*resource.Resource
|
||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
||||
Patches string `json:patches,omitempty" yaml:"patches,omitempty"`
|
||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
||||
}
|
||||
|
||||
//noinspection GoUnusedGlobalVariable
|
||||
@@ -35,11 +35,18 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
return fmt.Errorf("empty file path and empty patch content")
|
||||
}
|
||||
if len(p.Paths) != 0 {
|
||||
res, err := p.rf.RF().SliceFromPatches(ldr, p.Paths)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, onePath := range p.Paths {
|
||||
res, err := p.rf.RF().SliceFromBytes([]byte(onePath))
|
||||
if err == nil {
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
continue
|
||||
}
|
||||
res, err = p.rf.RF().SliceFromPatches(ldr, []types.PatchStrategicMerge{onePath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
p.loadedPatches = res
|
||||
}
|
||||
if p.Patches != "" {
|
||||
res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches))
|
||||
@@ -70,6 +77,14 @@ func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
148
plugin/builtin/PatchTransformer.go
Normal file
148
plugin/builtin/PatchTransformer.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Code generated by pluginator on PatchTransformer; DO NOT EDIT.
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchTransformerPlugin struct {
|
||||
ldr ifc.Loader
|
||||
rf *resmap.Factory
|
||||
loadedPatch *resource.Resource
|
||||
decodedPatch jsonpatch.Patch
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
Target *types.Selector `json:"target,omitempty", yaml:"target,omitempty"`
|
||||
}
|
||||
|
||||
//noinspection GoUnusedGlobalVariable
|
||||
func NewPatchTransformerPlugin() *PatchTransformerPlugin {
|
||||
return &PatchTransformerPlugin{}
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Config(
|
||||
ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) {
|
||||
p.ldr = ldr
|
||||
p.rf = rf
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Patch == "" && p.Path == "" {
|
||||
err = fmt.Errorf(
|
||||
"must specify one of patch and path in\n%s", string(c))
|
||||
return
|
||||
}
|
||||
if p.Patch != "" && p.Path != "" {
|
||||
err = fmt.Errorf(
|
||||
"patch and path can't be set at the same time\n%s", string(c))
|
||||
return
|
||||
}
|
||||
var in []byte
|
||||
if p.Path != "" {
|
||||
in, err = ldr.Load(p.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.Patch != "" {
|
||||
in = []byte(p.Patch)
|
||||
}
|
||||
|
||||
patchSM, errSM := p.rf.RF().FromBytes(in)
|
||||
patchJson, errJson := jsonPatchFromBytes(in)
|
||||
if errSM != nil && errJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"unable to get either a Strategic Merge Patch or JSON patch 6902 from %s", p.Patch)
|
||||
return
|
||||
}
|
||||
if errSM == nil && errJson != nil {
|
||||
p.loadedPatch = patchSM
|
||||
}
|
||||
if errJson == nil && errSM != nil {
|
||||
p.decodedPatch = patchJson
|
||||
}
|
||||
if patchSM != nil && patchJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"a patch can't be both a Strategic Merge Patch and JSON patch 6902 %s", p.Patch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if p.loadedPatch != nil && p.Target == nil {
|
||||
target, err := m.GetById(p.loadedPatch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = target.Patch(p.loadedPatch.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.Target == nil {
|
||||
return fmt.Errorf("must specify a target for patch %s", p.Patch)
|
||||
}
|
||||
|
||||
resources, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, resource := range resources {
|
||||
if p.decodedPatch != nil {
|
||||
rawObj, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modifiedObj, err := p.decodedPatch.Apply(rawObj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "failed to apply json patch '%s'", p.Patch)
|
||||
}
|
||||
err = resource.UnmarshalJSON(modifiedObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.loadedPatch != nil {
|
||||
patchCopy := p.loadedPatch.DeepCopy()
|
||||
patchCopy.SetName(resource.GetName())
|
||||
patchCopy.SetNamespace(resource.GetNamespace())
|
||||
patchCopy.SetGvk(resource.GetGvk())
|
||||
err = resource.Patch(patchCopy.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// jsonPatchFromBytes loads a Json 6902 patch from
|
||||
// a bytes input
|
||||
func jsonPatchFromBytes(
|
||||
in []byte) (jsonpatch.Patch, error) {
|
||||
ops := string(in)
|
||||
if ops == "" {
|
||||
return nil, fmt.Errorf("empty json patch operations")
|
||||
}
|
||||
|
||||
if ops[0] != '[' {
|
||||
jsonOps, err := yaml.YAMLToJSON(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = string(jsonOps)
|
||||
}
|
||||
return jsonpatch.DecodePatch([]byte(ops))
|
||||
}
|
||||
@@ -49,22 +49,40 @@ func (p *PrefixSuffixTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
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() {
|
||||
if p.shouldSkip(r.OrgId()) {
|
||||
// Don't change the actual definition
|
||||
// of a CRD.
|
||||
continue
|
||||
}
|
||||
id := r.OrgId()
|
||||
// current default configuration contains
|
||||
// only one entry: "metadata/name" with no GVK
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !id.IsSelected(&path.Gvk) {
|
||||
// With the currrent default configuration,
|
||||
// because no Gvk is specified, so a wild
|
||||
// card
|
||||
continue
|
||||
}
|
||||
|
||||
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.AddNameSuffix(p.Suffix)
|
||||
}
|
||||
|
||||
// the addPrefixSuffix method will not
|
||||
// change the name if both the prefix and suffix
|
||||
// are empty.
|
||||
err := transformers.MutateField(
|
||||
r.Map(),
|
||||
path.PathSlice(),
|
||||
|
||||
@@ -34,8 +34,15 @@ func (p *ReplicaCountTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
found := false
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
for _, res := range m.GetMatchingResourcesByOriginalId(p.createMatcher(i)) {
|
||||
matcher := p.createMatcher(i)
|
||||
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
|
||||
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
|
||||
|
||||
for _, res := range append(matchOriginal, matchCurrent...) {
|
||||
found = true
|
||||
err := transformers.MutateField(
|
||||
res.Map(), replicaSpec.PathSlice(),
|
||||
replicaSpec.CreateIfNotPresent, p.addReplicas)
|
||||
@@ -45,6 +52,15 @@ func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
gvks := make([]string, len(p.FieldSpecs))
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
gvks[i] = replicaSpec.Gvk.String()
|
||||
}
|
||||
return fmt.Errorf("Resource with name %s does not match a config with the following GVK %v",
|
||||
p.Replica.Name, gvks)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestAnnotationsTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestConfigMapGenerator(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestHashTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -150,7 +150,7 @@ func (p *plugin) findContainers(obj map[string]interface{}) error {
|
||||
|
||||
func isImageMatched(s, t string) bool {
|
||||
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$")
|
||||
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.-]*)?$")
|
||||
return pattern.MatchString(s)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func split(imageName string) (name string, tag string) {
|
||||
}
|
||||
|
||||
i := ic
|
||||
if ic < 0 {
|
||||
if ia > 0 {
|
||||
i = ia
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestImageTagTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
func TestImageTagTransformerNewTag(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -36,18 +36,22 @@ metadata:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: nginx2
|
||||
image: my-nginx:1.8.0
|
||||
- name: init-alpine
|
||||
image: alpine:1.8.0
|
||||
containers:
|
||||
- name: ngnix
|
||||
image: nginx:1.7.9
|
||||
- name: repliaced-with-digest
|
||||
image: foobar:1
|
||||
- name: postgresdb
|
||||
image: postgres:1.8.0
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
@@ -61,14 +65,307 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:v2
|
||||
name: ngnix
|
||||
name: nginx-tagged
|
||||
- image: nginx:v2
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: repliaced-with-digest
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: my-nginx:1.8.0
|
||||
name: nginx2
|
||||
- image: nginx:v2
|
||||
name: nginx-notag
|
||||
- image: nginx:v2
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
}
|
||||
func TestImageTagTransformerNewImage(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "ImageTagTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: ImageTagTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
imageTag:
|
||||
name: nginx
|
||||
newName: busybox
|
||||
`, `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: busybox:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox
|
||||
name: nginx-notag
|
||||
- image: busybox@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
}
|
||||
|
||||
func TestImageTagTransformerNewImageAndTag(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "ImageTagTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: ImageTagTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
imageTag:
|
||||
name: nginx
|
||||
newName: busybox
|
||||
newTag: v2
|
||||
`, `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox:v2
|
||||
name: nginx-tagged
|
||||
- image: busybox:v2
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox:v2
|
||||
name: nginx-notag
|
||||
- image: busybox:v2
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
}
|
||||
|
||||
func TestImageTagTransformerNewDigest(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "ImageTagTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: ImageTagTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
imageTag:
|
||||
name: nginx
|
||||
Digest: sha256:222222222222222222
|
||||
`, `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-tagged
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
}
|
||||
|
||||
func TestImageTagTransformerNewImageAndDigest(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "ImageTagTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: ImageTagTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
imageTag:
|
||||
name: nginx
|
||||
newName: busybox
|
||||
Digest: sha256:222222222222222222
|
||||
`, `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-tagged
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-notag
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`)
|
||||
|
||||
@@ -6,8 +6,8 @@ package main_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
kusttest_test "sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,7 +59,7 @@ metadata:
|
||||
)
|
||||
|
||||
func TestInventoryTransformerCollect(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -80,7 +80,7 @@ policy: GarbageCollect
|
||||
}
|
||||
|
||||
func TestInventoryTransformerIgnore(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -101,7 +101,7 @@ policy: GarbageIgnore
|
||||
}
|
||||
|
||||
func TestInventoryTransformerDefaultPolicy(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestLabelTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestLegacyOrderTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
@@ -37,23 +36,23 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
id := r.OrgId()
|
||||
fs, ok := p.isSelected(id)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(r.Map()) == 0 {
|
||||
// Don't mutate empty objects?
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
p.updateClusterRoleBinding(m)
|
||||
p.updateServiceReference(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -63,104 +62,64 @@ const metaNamespace = "metadata/namespace"
|
||||
// all objects have it, even "ClusterKind" objects
|
||||
// that don't exist in a namespace (the Namespace
|
||||
// object itself doesn't live in a namespace).
|
||||
func doIt(id resid.ResId, fs *config.FieldSpec) bool {
|
||||
return fs.Path != metaNamespace ||
|
||||
(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) {
|
||||
func (p *plugin) applicableFieldSpecs(id resid.ResId) []config.FieldSpec {
|
||||
res := []config.FieldSpec{}
|
||||
for _, fs := range p.FieldSpecs {
|
||||
if id.IsSelected(&fs.Gvk) {
|
||||
return &fs, true
|
||||
if id.IsSelected(&fs.Gvk) && (fs.Path != metaNamespace || (fs.Path == metaNamespace && id.IsNamespaceableKind())) {
|
||||
res = append(res, fs)
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *plugin) updateClusterRoleBinding(m resmap.ResMap) {
|
||||
srvAccount := gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
|
||||
saMap := map[string]bool{}
|
||||
for _, id := range m.AllIds() {
|
||||
if id.Gvk.Equals(srvAccount) {
|
||||
saMap[id.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, res := range m.Resources() {
|
||||
if res.OrgId().Kind != "ClusterRoleBinding" &&
|
||||
res.OrgId().Kind != "RoleBinding" {
|
||||
continue
|
||||
}
|
||||
objMap := res.Map()
|
||||
subjects, ok := objMap["subjects"].([]interface{})
|
||||
if subjects == nil || !ok {
|
||||
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
|
||||
func (o *plugin) changeNamespace(
|
||||
referrer *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch in.(type) {
|
||||
case string:
|
||||
// will happen when the metadata/namespace
|
||||
// value is replaced
|
||||
return o.Namespace, nil
|
||||
case []interface{}:
|
||||
l, _ := in.([]interface{})
|
||||
for idx, item := range l {
|
||||
switch item.(type) {
|
||||
case map[string]interface{}:
|
||||
// Will happen when mutating the subjects
|
||||
// field of ClusterRoleBinding and RoleBinding
|
||||
inMap, _ := item.(map[string]interface{})
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
continue
|
||||
}
|
||||
return svc, nil
|
||||
})
|
||||
webhooks[i] = webhook
|
||||
name, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestNamespaceTransformer1(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -28,6 +28,12 @@ metadata:
|
||||
fieldSpecs:
|
||||
- path: metadata/namespace
|
||||
create: true
|
||||
- path: subjects
|
||||
kind: RoleBinding
|
||||
group: rbac.authorization.k8s.io
|
||||
- path: subjects
|
||||
kind: ClusterRoleBinding
|
||||
group: rbac.authorization.k8s.io
|
||||
`, `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@@ -54,7 +60,7 @@ apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: default
|
||||
namespace: system
|
||||
namespace: test
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -99,6 +105,15 @@ metadata:
|
||||
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, `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@@ -145,7 +160,7 @@ subjects:
|
||||
namespace: test
|
||||
- kind: ServiceAccount
|
||||
name: service-account
|
||||
namespace: test
|
||||
namespace: system
|
||||
- kind: ServiceAccount
|
||||
name: another
|
||||
namespace: random
|
||||
@@ -158,7 +173,7 @@ webhooks:
|
||||
- clientConfig:
|
||||
service:
|
||||
name: svc1
|
||||
namespace: test
|
||||
namespace: system
|
||||
name: example1
|
||||
- clientConfig:
|
||||
service:
|
||||
@@ -174,7 +189,7 @@ metadata:
|
||||
}
|
||||
|
||||
func TestNamespaceTransformerClusterLevelKinds(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -213,6 +228,12 @@ metadata:
|
||||
fieldSpecs:
|
||||
- path: metadata/namespace
|
||||
create: true
|
||||
- path: subjects
|
||||
kind: RoleBinding
|
||||
group: rbac.authorization.k8s.io
|
||||
- path: subjects
|
||||
kind: ClusterRoleBinding
|
||||
group: rbac.authorization.k8s.io
|
||||
`, noChangeExpected)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, noChangeExpected)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
const target = `
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
`
|
||||
|
||||
func TestPatchJson6902TransformerMissingFile(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -58,7 +58,7 @@ path: jsonpatch.json
|
||||
}
|
||||
|
||||
func TestBadPatchJson6902Transformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -87,7 +87,7 @@ jsonOp: 'thisIsNotAPatch'
|
||||
}
|
||||
|
||||
func TestBothEmptyJson6902Transformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -115,7 +115,7 @@ target:
|
||||
}
|
||||
|
||||
func TestBothSpecifiedJson6902Transformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -151,7 +151,7 @@ jsonOp: '[{"op": "add", "path": "/spec/template/spec/dnsPolicy", "value": "Clust
|
||||
}
|
||||
|
||||
func TestPatchJson6902TransformerFromJsonFile(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -201,7 +201,7 @@ spec:
|
||||
}
|
||||
|
||||
func TestPatchJson6902TransformerFromYamlFile(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -250,8 +250,8 @@ spec:
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPatchJson6902TransformerWithInline(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
func TestPatchJson6902TransformerWithInlineJSON(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -290,3 +290,47 @@ spec:
|
||||
dnsPolicy: ClusterFirst
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPatchJson6902TransformerWithInlineYAML(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchJson6902Transformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchJson6902Transformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: myDeploy
|
||||
jsonOp: |-
|
||||
- op: add
|
||||
path: /spec/template/spec/dnsPolicy
|
||||
value: ClusterFirst
|
||||
`, target)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
dnsPolicy: ClusterFirst
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type plugin struct {
|
||||
rf *resmap.Factory
|
||||
loadedPatches []*resource.Resource
|
||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
||||
Patches string `json:patches,omitempty" yaml:"patches,omitempty"`
|
||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
||||
}
|
||||
|
||||
//noinspection GoUnusedGlobalVariable
|
||||
@@ -36,11 +36,18 @@ func (p *plugin) Config(
|
||||
return fmt.Errorf("empty file path and empty patch content")
|
||||
}
|
||||
if len(p.Paths) != 0 {
|
||||
res, err := p.rf.RF().SliceFromPatches(ldr, p.Paths)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, onePath := range p.Paths {
|
||||
res, err := p.rf.RF().SliceFromBytes([]byte(onePath))
|
||||
if err == nil {
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
continue
|
||||
}
|
||||
res, err = p.rf.RF().SliceFromPatches(ldr, []types.PatchStrategicMerge{onePath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
p.loadedPatches = res
|
||||
}
|
||||
if p.Patches != "" {
|
||||
res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches))
|
||||
@@ -71,6 +78,14 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -58,7 +59,7 @@ spec:
|
||||
)
|
||||
|
||||
func TestPatchStrategicMergeTransformerMissingFile(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -77,13 +78,15 @@ paths:
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(),
|
||||
"cannot read file \"/app/patch.yaml\"") {
|
||||
"cannot read file \"/app/patch.yaml\"") &&
|
||||
!strings.Contains(err.Error(),
|
||||
"cannot unmarshal string") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadPatchStrategicMergeTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -108,7 +111,7 @@ patches: 'thisIsNotAPatch'
|
||||
}
|
||||
|
||||
func TestBothEmptyPatchStrategicMergeTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -131,7 +134,7 @@ metadata:
|
||||
}
|
||||
|
||||
func TestPatchStrategicMergeTransformerFromFiles(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -180,8 +183,8 @@ spec:
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPatchStrategicMergeTransformerWithInline(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
func TestPatchStrategicMergeTransformerWithInlineJSON(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -215,8 +218,60 @@ spec:
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPatchStrategicMergeTransformerWithInlineYAML(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchStrategicMergeTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchStrategicMergeTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
patches: |-
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
replica: 3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
`, target)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: nginx
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPatchStrategicMergeTransformerMultiplePatches(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -293,7 +348,7 @@ spec:
|
||||
}
|
||||
|
||||
func TestStrategicMergeTransformerMultiplePatchesWithConflicts(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -354,7 +409,7 @@ paths:
|
||||
}
|
||||
|
||||
func TestStrategicMergeTransformerWrongNamespace(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -397,7 +452,7 @@ paths:
|
||||
}
|
||||
|
||||
func TestStrategicMergeTransformerNoSchema(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -437,7 +492,7 @@ spec:
|
||||
}
|
||||
|
||||
func TestStrategicMergeTransformerNoSchemaMultiPatches(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -493,7 +548,7 @@ spec:
|
||||
}
|
||||
|
||||
func TestStrategicMergeTransformerNoSchemaMultiPatchesWithConflict(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -533,3 +588,696 @@ paths:
|
||||
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, ``)
|
||||
}
|
||||
|
||||
149
plugin/builtin/patchtransformer/PatchTransformer.go
Normal file
149
plugin/builtin/patchtransformer/PatchTransformer.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:generate go run sigs.k8s.io/kustomize/v3/cmd/pluginator
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type plugin struct {
|
||||
ldr ifc.Loader
|
||||
rf *resmap.Factory
|
||||
loadedPatch *resource.Resource
|
||||
decodedPatch jsonpatch.Patch
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
Target *types.Selector `json:"target,omitempty", yaml:"target,omitempty"`
|
||||
}
|
||||
|
||||
//noinspection GoUnusedGlobalVariable
|
||||
var KustomizePlugin plugin
|
||||
|
||||
func (p *plugin) Config(
|
||||
ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) {
|
||||
p.ldr = ldr
|
||||
p.rf = rf
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Patch == "" && p.Path == "" {
|
||||
err = fmt.Errorf(
|
||||
"must specify one of patch and path in\n%s", string(c))
|
||||
return
|
||||
}
|
||||
if p.Patch != "" && p.Path != "" {
|
||||
err = fmt.Errorf(
|
||||
"patch and path can't be set at the same time\n%s", string(c))
|
||||
return
|
||||
}
|
||||
var in []byte
|
||||
if p.Path != "" {
|
||||
in, err = ldr.Load(p.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.Patch != "" {
|
||||
in = []byte(p.Patch)
|
||||
}
|
||||
|
||||
patchSM, errSM := p.rf.RF().FromBytes(in)
|
||||
patchJson, errJson := jsonPatchFromBytes(in)
|
||||
if errSM != nil && errJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"unable to get either a Strategic Merge Patch or JSON patch 6902 from %s", p.Patch)
|
||||
return
|
||||
}
|
||||
if errSM == nil && errJson != nil {
|
||||
p.loadedPatch = patchSM
|
||||
}
|
||||
if errJson == nil && errSM != nil {
|
||||
p.decodedPatch = patchJson
|
||||
}
|
||||
if patchSM != nil && patchJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"a patch can't be both a Strategic Merge Patch and JSON patch 6902 %s", p.Patch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
if p.loadedPatch != nil && p.Target == nil {
|
||||
target, err := m.GetById(p.loadedPatch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = target.Patch(p.loadedPatch.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.Target == nil {
|
||||
return fmt.Errorf("must specify a target for patch %s", p.Patch)
|
||||
}
|
||||
|
||||
resources, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, resource := range resources {
|
||||
if p.decodedPatch != nil {
|
||||
rawObj, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modifiedObj, err := p.decodedPatch.Apply(rawObj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "failed to apply json patch '%s'", p.Patch)
|
||||
}
|
||||
err = resource.UnmarshalJSON(modifiedObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.loadedPatch != nil {
|
||||
patchCopy := p.loadedPatch.DeepCopy()
|
||||
patchCopy.SetName(resource.GetName())
|
||||
patchCopy.SetNamespace(resource.GetNamespace())
|
||||
patchCopy.SetGvk(resource.GetGvk())
|
||||
err = resource.Patch(patchCopy.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// jsonPatchFromBytes loads a Json 6902 patch from
|
||||
// a bytes input
|
||||
func jsonPatchFromBytes(
|
||||
in []byte) (jsonpatch.Patch, error) {
|
||||
ops := string(in)
|
||||
if ops == "" {
|
||||
return nil, fmt.Errorf("empty json patch operations")
|
||||
}
|
||||
|
||||
if ops[0] != '[' {
|
||||
jsonOps, err := yaml.YAMLToJSON(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = string(jsonOps)
|
||||
}
|
||||
return jsonpatch.DecodePatch([]byte(ops))
|
||||
}
|
||||
341
plugin/builtin/patchtransformer/PatchTransformer_test.go
Normal file
341
plugin/builtin/patchtransformer/PatchTransformer_test.go
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
kusttest_test "sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
const (
|
||||
target = `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
labels:
|
||||
old-label: old-value
|
||||
kind: Deployment
|
||||
spec:
|
||||
replica: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: yourDeploy
|
||||
labels:
|
||||
new-label: new-value
|
||||
kind: Deployment
|
||||
spec:
|
||||
replica: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
new-label: new-value
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.7.9
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
label:
|
||||
old-label: old-value
|
||||
kind: MyKind
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
`
|
||||
)
|
||||
|
||||
func TestPatchTransformerMissingFile(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
_, err := th.RunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
path: patch.yaml
|
||||
`, target)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(),
|
||||
"cannot read file \"/app/patch.yaml\"") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchTransformerBadPatch(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
_, err := th.RunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
patch: "thisIsNotAPatch"
|
||||
`, target)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(),
|
||||
"unable to get either a Strategic Merge Patch or JSON patch 6902 from") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchTransformerMissingSelector(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
_, err := th.RunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
patch: '[{"op": "add", "path": "/spec/template/spec/dnsPolicy", "value": "ClusterFirst"}]'
|
||||
`, target)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(),
|
||||
"must specify a target for patch") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchTransformerBothEmptyPathAndPatch(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
_, err := th.RunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
`, target)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "must specify one of patch and path in") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchTransformerBothNonEmptyPathAndPatch(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
_, err := th.RunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
Path: patch.yaml
|
||||
Patch: "something"
|
||||
`, target)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "patch and path can't be set at the same time") {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchTransformerFromFiles(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
th.WriteF("/app/patch.yaml", `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 3
|
||||
`)
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
path: patch.yaml
|
||||
target:
|
||||
name: .*Deploy
|
||||
`, target)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
new-label: new-value
|
||||
name: yourDeploy
|
||||
spec:
|
||||
replica: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
new-label: new-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: MyKind
|
||||
metadata:
|
||||
label:
|
||||
old-label: old-value
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPatchTransformerWithInline(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "PatchTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
patch: '[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "nginx:latest"}]'
|
||||
target:
|
||||
name: .*Deploy
|
||||
kind: Deployment
|
||||
`, target)
|
||||
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: nginx
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
new-label: new-value
|
||||
name: yourDeploy
|
||||
spec:
|
||||
replica: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
new-label: new-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: nginx
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: MyKind
|
||||
metadata:
|
||||
label:
|
||||
old-label: old-value
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`)
|
||||
}
|
||||
@@ -50,22 +50,40 @@ func (p *plugin) Config(
|
||||
}
|
||||
|
||||
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() {
|
||||
if p.shouldSkip(r.OrgId()) {
|
||||
// Don't change the actual definition
|
||||
// of a CRD.
|
||||
continue
|
||||
}
|
||||
id := r.OrgId()
|
||||
// current default configuration contains
|
||||
// only one entry: "metadata/name" with no GVK
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !id.IsSelected(&path.Gvk) {
|
||||
// With the currrent default configuration,
|
||||
// because no Gvk is specified, so a wild
|
||||
// card
|
||||
continue
|
||||
}
|
||||
|
||||
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.AddNameSuffix(p.Suffix)
|
||||
}
|
||||
|
||||
// the addPrefixSuffix method will not
|
||||
// change the name if both the prefix and suffix
|
||||
// are empty.
|
||||
err := transformers.MutateField(
|
||||
r.Map(),
|
||||
path.PathSlice(),
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestPrefixSuffixTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -35,8 +35,15 @@ func (p *plugin) Config(
|
||||
}
|
||||
|
||||
func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
found := false
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
for _, res := range m.GetMatchingResourcesByOriginalId(p.createMatcher(i)) {
|
||||
matcher := p.createMatcher(i)
|
||||
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
|
||||
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
|
||||
|
||||
for _, res := range append(matchOriginal, matchCurrent...) {
|
||||
found = true
|
||||
err := transformers.MutateField(
|
||||
res.Map(), replicaSpec.PathSlice(),
|
||||
replicaSpec.CreateIfNotPresent, p.addReplicas)
|
||||
@@ -46,6 +53,15 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
gvks := make([]string, len(p.FieldSpecs))
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
gvks[i] = replicaSpec.Gvk.String()
|
||||
}
|
||||
return fmt.Errorf("Resource with name %s does not match a config with the following GVK %v",
|
||||
p.Replica.Name, gvks)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestReplicaCountTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
@@ -151,3 +151,85 @@ spec:
|
||||
app: app
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchesCurrentID(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin("builtin", "", "PrefixSuffixTransformer")
|
||||
tc.BuildGoPlugin("builtin", "", "ReplicaCountTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
rm := th.LoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: PrefixSuffixTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
suffix: -test
|
||||
fieldSpecs:
|
||||
- path: metadata/name
|
||||
`, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment`)
|
||||
|
||||
rm, _ = th.RunTransformerFromResMap(`
|
||||
apiVersion: builtin
|
||||
kind: ReplicaCountTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
|
||||
replica:
|
||||
name: deployment-test
|
||||
count: 23
|
||||
fieldSpecs:
|
||||
- path: spec/replicas
|
||||
create: true
|
||||
kind: Deployment`, rm)
|
||||
th.AssertActualEqualsExpected(rm, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-test
|
||||
spec:
|
||||
replicas: 23
|
||||
`)
|
||||
}
|
||||
|
||||
func TestNoMatch(t *testing.T) {
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin("builtin", "", "ReplicaCountTransformer")
|
||||
|
||||
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
|
||||
|
||||
err := th.ErrorFromLoadAndRunTransformer(`
|
||||
apiVersion: builtin
|
||||
kind: ReplicaCountTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
replica:
|
||||
name: service
|
||||
count: 3
|
||||
fieldSpecs:
|
||||
- path: spec/replicas
|
||||
create: true
|
||||
kind: Deployment
|
||||
`, `
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service
|
||||
spec:
|
||||
`)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("No match should return an error")
|
||||
}
|
||||
if err.Error() !=
|
||||
"Resource with name service does not match a config with the following GVK [~G_~V_Deployment]" {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestSecretGenerator(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestBashedConfigMapPlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin(
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
// This test requires having the helm binary on the PATH.
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
// TODO: Download and inflate the chart, and check that
|
||||
// in for the test.
|
||||
func TestChartInflator(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestDatePrefixerPlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func shouldContain(t *testing.T, s []byte, x string) {
|
||||
@@ -18,7 +18,7 @@ func shouldContain(t *testing.T, s []byte, x string) {
|
||||
}
|
||||
|
||||
func TestPrintWorkDirPlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestSecretsFromDatabasePlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestSedTransformer(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin("someteam.example.com", "v1", "SedTransformer")
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestSomeServiceGeneratorPlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestStringPrefixerPlugin(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
|
||||
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
||||
plugins_test "sigs.k8s.io/kustomize/v3/pkg/plugins/test"
|
||||
)
|
||||
|
||||
func TestValidatorHappy(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin("someteam.example.com", "v1", "Validator")
|
||||
@@ -49,7 +49,7 @@ metadata:
|
||||
}
|
||||
|
||||
func TestValidatorUnHappy(t *testing.T) {
|
||||
tc := plugins.NewEnvForTest(t).Set()
|
||||
tc := plugins_test.NewEnvForTest(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildExecPlugin("someteam.example.com", "v1", "Validator")
|
||||
|
||||
Reference in New Issue
Block a user