Compare commits

...

58 Commits

Author SHA1 Message Date
Kubernetes Prow Robot
30b378a924 Merge pull request #1325 from keleustes/residequals-namereference
NameReference Transformer needs to account for namespace and cluster wide objects.
2019-07-17 13:20:13 -07:00
Kubernetes Prow Robot
3a843f1eca Merge pull request #1362 from Liujingfang1/doc
update the latest version in readme
2019-07-17 13:04:14 -07:00
Jerome Brette
9b40f8ab47 Implement code review comments to NameReferenceTransformer changes.
- Add comments where code with potentially misleading.
- Rename functions according to comments
2019-07-17 14:10:01 -05:00
jingfangliu
dc6dcd8150 update the latest version in readme 2019-07-17 12:05:12 -07:00
Kubernetes Prow Robot
3cb6c7f1f4 Merge pull request #1349 from yujunz/faq
Add FAQ about how to customize configuration
2019-07-17 11:26:11 -07:00
Kubernetes Prow Robot
7632839bc8 Merge pull request #1350 from yujunz/docs/plugins
Convert go plugin example to GPG based
2019-07-17 10:40:37 -07:00
Yujun Zhang
c3ea109b59 Update goPluginGuidedExample.md 2019-07-17 08:19:50 +08:00
Jerome Brette
579995dc8a Address simultaneous transformation of name and namespace
Namereference handler needs to address simulatenous change of
name and namespace in ClusterRoleBinding for instance.
2019-07-16 18:17:33 -05:00
Jerome Brette
b43bd5440d Update Issue 1264 Reproduction Test 2019-07-16 18:17:33 -05:00
Jerome Brette
c4d899f7f3 Improve NameReference Test cases
- Add more NameReference Namespace tests
- Address issue when mixing empty/no namespace and default namespace.
- Address ClusterRoleBinding subjects field pointing at multiple namespaces.
2019-07-16 18:17:33 -05:00
Jerome Brette
7998ee7036 Addresses slice case with notNamespaceable objects 2019-07-16 18:17:33 -05:00
Kubernetes Prow Robot
878960d7b1 Merge pull request #1355 from Liujingfang1/patch
enable extended patch transformer and add tests
2019-07-16 15:58:34 -07:00
jingfangliu
ed0cfc685b add test for extended patch with overlapping patches 2019-07-16 15:16:00 -07:00
Kubernetes Prow Robot
b0a7345123 Merge pull request #1359 from keleustes/imagetag
Address replacement of digest by ImageTransformer
2019-07-16 13:10:49 -07:00
Jerome Brette
580963ea76 Address replacement of digest by ImageTransformer
- See [Issue 1357](https://github.com/kubernetes-sigs/kustomize/issues/1357)
- Add more plugin tests.
2019-07-16 14:03:56 -05:00
Kubernetes Prow Robot
0707deae95 Merge pull request #1356 from keleustes/droppatch
Test tracking issue patchesStrategicMerge elements can be dropped
2019-07-16 10:50:18 -07:00
Yujun Zhang
fb44880b8c Add back GCP KMS example 2019-07-16 20:10:16 +08:00
Jerome Brette
e5ebca6604 Test tracking issue "patchesStrategicMerge elements can be dropped"
- Issue 1354
- $patch: delete is ignored or not depending of the include order
  in the kustomization.yaml
2019-07-15 19:02:52 -05:00
jingfangliu
f5fc9acb84 fix local test failures 2019-07-15 18:59:16 -05:00
jingfangliu
28d1bad3cb fix the ci failure 2019-07-15 18:58:52 -05:00
jingfangliu
6f74419628 fix local test failures 2019-07-15 16:34:13 -07:00
jingfangliu
8121467c1e fix the ci failure 2019-07-15 16:01:23 -07:00
jingfangliu
a85f297f31 enable extended patch transformer and add tests 2019-07-15 15:45:08 -07:00
Kubernetes Prow Robot
76a7816aeb Merge pull request #1348 from yujunz/nameref
Add storage class name ref
2019-07-15 13:17:25 -07:00
Kubernetes Prow Robot
7872405379 Merge pull request #1336 from richardmarshall/fix_test_flags
Remove go testing flags from kustomize help
2019-07-15 13:13:24 -07:00
Kubernetes Prow Robot
6c17a3409f Merge pull request #1346 from Liujingfang1/patchallkinds
add extended patch transformer
2019-07-15 11:45:24 -07:00
Yujun Zhang
f1dbab9dee Convert go plugin example to GPG based 2019-07-14 11:33:37 +08:00
Yujun Zhang
bfafbbf47f Add FAQ about how to customize configuration 2019-07-14 10:39:45 +08:00
Yujun Zhang
08d7c35da7 Add storage class name ref 2019-07-14 10:05:19 +08:00
Kubernetes Prow Robot
f12704f6c1 Merge pull request #1331 from Rjerk/fix-vp-doc
docs/versioningPolicy.md: fix expired urls
2019-07-12 14:41:05 -07:00
Tony Hsu
0edab60b30 Fix typo: kubectl v1.15 -> kubectl v1.14 (#1333)
* Fix typo: kubectl v1.15 -> kubectl v1.14

Match version number to the version in the link.

* Add both kubectl v1.14 and v1.15

* Add both kubectl v1.14 and v1.15
2019-07-12 14:38:10 -07:00
jingfangliu
3c05e2d664 add extended patch transformer 2019-07-12 14:34:08 -07:00
Kubernetes Prow Robot
aa2313c282 Merge pull request #1344 from Liujingfang1/fix
include nameprefix and namesuffix to find matched reference for cluster level kinds
2019-07-12 11:55:06 -07:00
jingfangliu
eeed1954fb include nameprefix and namesuffix to find matched reference for cluster level kinds 2019-07-12 10:27:02 -07:00
Kubernetes Prow Robot
cd00ce7ab1 Merge pull request #1341 from Liujingfang1/refactor
move strategic merge patch transformer to a builtin transformer
2019-07-12 09:25:05 -07:00
jingfangliu
145d07363f add labels in test patch files 2019-07-12 08:56:34 -07:00
jingfangliu
33fff655db move strategic merge patch transformer to a builtin transformer 2019-07-11 13:39:30 -07:00
Jingfang Liu
31ab347da2 refactor the strategic merge patch transformer toward moving it to a plugin (#1340) 2019-07-11 10:22:56 -07:00
Kubernetes Prow Robot
7a48b2ba8e Merge pull request #1338 from yujunz/transformer/config
Fix missing nameReference in default config
2019-07-11 09:32:55 -07:00
Yujun Zhang
876f2a8236 Fix missing nameReference in default config
Related to #1322
2019-07-11 19:46:29 +08:00
Richard Marshall
095333ffb1 Update references to NewEnvForTest 2019-07-10 20:43:50 -07:00
Richard Marshall
0d8d9e2f2b Move plugin EnvForTest manager into new package
Move the EnvForTest manager into an independent package that is not
imported by any non-test code. Previously this code was directly
embedded in the plugins package resulting in testing flags being exposed
in the main kustomize binary.
2019-07-10 17:16:05 -07:00
Kubernetes Prow Robot
9bff2e8883 Merge pull request #1330 from qiujian16/generate-ns-transformer
Generate updated ns transformer
2019-07-10 08:22:28 -07:00
Liu Lan
120ba6b870 docs/versioningPolicy.md: fix expired urls
Signed-off-by: Liu Lan <liulan@umcloud.com>
2019-07-10 11:41:54 +08:00
Jian Qiu
483188ba89 Generate updated ns transformer 2019-07-10 11:07:31 +08:00
Kubernetes Prow Robot
672bda0c9c Merge pull request #1328 from Liujingfang1/cmgenerator
fix the regression on merging configmap with different namespace
2019-07-09 14:22:24 -07:00
jingfangliu
49b32473ca fix the regression on merging configmap with different namespace 2019-07-09 13:39:19 -07:00
Kubernetes Prow Robot
08400d77a6 Merge pull request #1321 from qiujian16/webhook-ns-transform
Enable ns transformer for webhook
2019-07-09 10:08:03 -07:00
Jian Qiu
c912baeb3a Enable ns transformer for webhook
Add namespace transformer for ValidatingWebhookConfiguration
and MutatingWebhookConfiguration
2019-07-09 13:32:33 +08:00
Kubernetes Prow Robot
433733eb0e Merge pull request #1309 from richardmarshall/go_plugin_guide
Fix typo in the go plugin guide
2019-07-08 09:18:35 -07:00
Richard Marshall
f996ac82c7 Fix typo in the go plugin guide 2019-07-03 20:48:07 -07:00
Jeff Regan
efcb7cc5a5 Update README.md 2019-07-03 12:43:17 -07:00
Jeff Regan
bf7b57537b Merge pull request #1306 from monopole/updateV3Notes
Update v3 notes
2019-07-03 12:40:53 -07:00
Jeffrey Regan
6b597f8711 Update v3 notes 2019-07-03 12:40:30 -07:00
Jeff Regan
088739900f Merge pull request #1305 from monopole/tweakDocs
Update goPluginGuidedExample.md
2019-07-03 12:25:21 -07:00
Jeff Regan
3bf13f83d3 Update goPluginGuidedExample.md 2019-07-03 12:24:23 -07:00
Jeff Regan
c64a72f1f9 Update goPluginGuidedExample.md 2019-07-03 11:34:16 -07:00
Jeff Regan
8b60b456ac Update README.md 2019-07-03 11:22:27 -07:00
73 changed files with 4409 additions and 1015 deletions

View File

@@ -1,5 +1,7 @@
# kustomize # kustomize
_[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 `kustomize` lets you customize raw, template-free YAML
files for multiple purposes, leaving the original YAML files for multiple purposes, leaving the original YAML
untouched and usable as is. untouched and usable as is.
@@ -22,7 +24,7 @@ these [instructions](docs/INSTALL.md).
Browse the [docs](docs) or jump right into the Browse the [docs](docs) or jump right into the
tested [examples](examples). tested [examples](examples).
kustomize [v2.0.3] is available in [kubectl v1.15][kubectl]. kustomize [v2.0.3] is available in [kubectl v1.14 and v1.15][kubectl].
## Usage ## Usage

View File

@@ -28,3 +28,31 @@ To disable this, use v3, and the `load_restrictor` flag:
``` ```
kustomize build --load_restrictor none $target 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.

View File

@@ -1,10 +1,14 @@
# Go Plugin Guided Example for Linux # Go Plugin Guided Example for Linux
This is a (no reading allowed!) 60 second copy/paste guided
example. Full plugin docs [here](README.md).
[SopsEncodedSecrets repository]: https://github.com/monopole/sopsencodedsecrets [SopsEncodedSecrets repository]: https://github.com/monopole/sopsencodedsecrets
[Go plugin]: https://golang.org/pkg/plugin [Go plugin]: https://golang.org/pkg/plugin
[Go plugin caveats]: goPluginCaveats.md
This is a (no reading allowed!) 60 second copy/paste guided
example.
Full plugin docs [here](README.md).
Be sure to read the [Go plugin caveats].
This demo uses a Go plugin, `SopsEncodedSecrets`, This demo uses a Go plugin, `SopsEncodedSecrets`,
that lives in the [sopsencodedsecrets repository]. that lives in the [sopsencodedsecrets repository].
@@ -17,23 +21,32 @@ current setup.
#### requirements #### requirements
* linux, git, curl, Go 1.12 * linux, git, curl, Go 1.12
* Google cloud (gcloud) install
* a Google account (will use Google kms - For encryption
volunteers needed to convert to a GPG example).
* gpg
Or
* Google cloud (gcloud) install
* a Google account with KMS permission
## Make a place to work ## Make a place to work
``` ```shell
# Keeping these separate to avoid cluttering the DEMO dir.
DEMO=$(mktemp -d) DEMO=$(mktemp -d)
tmpGoPath=$(mktemp -d)
``` ```
## Install kustomize ## Install kustomize
Need v3.0.0 for what follows: Need v3.0.0 for what follows, and you must _compile_
it (not download the binary from the release page):
``` ```shell
GOBIN=$DEMO/bin go get sigs.k8s.io/kustomize/v3/cmd/kustomize@v3.0.0-pre GOPATH=$tmpGoPath go install sigs.k8s.io/kustomize/v3/cmd/kustomize
``` ```
## Make a home for plugins ## Make a home for plugins
@@ -54,7 +67,7 @@ The kustomize program reads the config file
kustomization file), then locates the Go plugin's kustomization file), then locates the Go plugin's
object code at the following location: object code at the following location:
> ``` > ```shell
> $XGD_CONFIG_HOME/kustomize/plugin/$apiVersion/$lKind/$kind.so > $XGD_CONFIG_HOME/kustomize/plugin/$apiVersion/$lKind/$kind.so
> ``` > ```
@@ -74,7 +87,7 @@ left to plugins to find their own config.
This demo will house the plugin it uses at the This demo will house the plugin it uses at the
ephemeral directory ephemeral directory
``` ```shell
PLUGIN_ROOT=$DEMO/kustomize/plugin PLUGIN_ROOT=$DEMO/kustomize/plugin
``` ```
@@ -100,7 +113,7 @@ and it lives in the [SopsEncodedSecrets repository].
Somewhat arbitrarily, we'll chose to install Somewhat arbitrarily, we'll chose to install
this plugin with this plugin with
``` ```shell
apiVersion=mygenerators apiVersion=mygenerators
kind=SopsEncodedSecrets kind=SopsEncodedSecrets
``` ```
@@ -111,7 +124,7 @@ By convention, the ultimate home of the plugin
code and supplemental data, tests, documentation, code and supplemental data, tests, documentation,
etc. is the lowercase form of its kind. etc. is the lowercase form of its kind.
``` ```shell
lKind=$(echo $kind | awk '{print tolower($0)}') lKind=$(echo $kind | awk '{print tolower($0)}')
``` ```
@@ -121,7 +134,7 @@ In this case, the repo name matches the lowercase
kind already, so we just clone the repo and get kind already, so we just clone the repo and get
the proper directory name automatically: the proper directory name automatically:
``` ```shell
mkdir -p $PLUGIN_ROOT/${apiVersion} mkdir -p $PLUGIN_ROOT/${apiVersion}
cd $PLUGIN_ROOT/${apiVersion} cd $PLUGIN_ROOT/${apiVersion}
git clone git@github.com:monopole/sopsencodedsecrets.git git clone git@github.com:monopole/sopsencodedsecrets.git
@@ -129,7 +142,7 @@ git clone git@github.com:monopole/sopsencodedsecrets.git
Remember this directory: Remember this directory:
``` ```shell
MY_PLUGIN_DIR=$PLUGIN_ROOT/${apiVersion}/${lKind} MY_PLUGIN_DIR=$PLUGIN_ROOT/${apiVersion}/${lKind}
``` ```
@@ -138,16 +151,16 @@ MY_PLUGIN_DIR=$PLUGIN_ROOT/${apiVersion}/${lKind}
Plugins may come with their own tests. Plugins may come with their own tests.
This one does, and it hopefully passes: This one does, and it hopefully passes:
``` ```shell
cd $MY_PLUGIN_DIR cd $MY_PLUGIN_DIR
go test SopsEncodedSecrets_test.go go test SopsEncodedSecrets_test.go
``` ```
Build the object code for use by kustomize: Build the object code for use by kustomize:
``` ```shell
cd $MY_PLUGIN_DIR cd $MY_PLUGIN_DIR
go build -buildmode plugin -o ${kind}.so ${kind}.go GOPATH=$tmpGoPath go build -buildmode plugin -o ${kind}.so ${kind}.go
``` ```
This step may succeed, but kustomize might This step may succeed, but kustomize might
@@ -180,11 +193,11 @@ reusable instead of bizarrely woven throughout the
code as a individual special cases. code as a individual special cases.
## Create a kustomization ## Create a kustomization
Make a kustomization directory to Make a kustomization directory to
hold all your config: hold all your config:
``` ```shell
MYAPP=$DEMO/myapp MYAPP=$DEMO/myapp
mkdir -p $MYAPP mkdir -p $MYAPP
``` ```
@@ -194,12 +207,13 @@ Make a config file for the SopsEncodedSecrets plugin.
Its `apiVersion` and `kind` allow the plugin to be Its `apiVersion` and `kind` allow the plugin to be
found: found:
``` ```shell
cat <<EOF >$MYAPP/secGenerator.yaml cat <<EOF >$MYAPP/secGenerator.yaml
apiVersion: ${apiVersion} apiVersion: ${apiVersion}
kind: ${kind} kind: ${kind}
metadata: metadata:
name: forbiddenValues name: mySecretGenerator
name: forbiddenValues
namespace: production namespace: production
file: myEncryptedData.yaml file: myEncryptedData.yaml
keys: keys:
@@ -214,7 +228,7 @@ This plugin expects to find more data in
Make a kustomization file referencing the plugin Make a kustomization file referencing the plugin
config: config:
``` ```shell
cat <<EOF >$MYAPP/kustomization.yaml cat <<EOF >$MYAPP/kustomization.yaml
commonLabels: commonLabels:
app: hello app: hello
@@ -223,31 +237,46 @@ generators:
EOF 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: 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 gcloud kms keys list --location global --keyring sops
``` ```
If it succeeds, presumably you've already If it succeeds, presumably you've already created keys and placed them in a keyring called sops. If not, do this:
created keys and placed them in a keyring called `sops`.
If not, do this:
``` ```shell
gcloud kms keyrings create sops --location global gcloud kms keyrings create sops --location global
gcloud kms keys create sops-key --location global \ gcloud kms keys create sops-key --location global \
--keyring sops --purpose encryption --keyring sops --purpose encryption
``` ```
Extract your keyLocation for use below: Extract your keyLocation for use below:
```
```shell
keyLocation=$(\ keyLocation=$(\
gcloud kms keys list --location global --keyring sops |\ gcloud kms keys list --location global --keyring sops |\
grep GOOGLE | cut -d " " -f1) grep GOOGLE | cut -d " " -f1)
@@ -256,42 +285,73 @@ echo $keyLocation
### Install `sops` ### Install `sops`
``` ```shell
GOBIN=$DEMO/bin go install go.mozilla.org/sops/cmd/sops 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: Create raw data to encrypt:
```
```shell
cat <<EOF >$MYAPP/myClearData.yaml cat <<EOF >$MYAPP/myClearData.yaml
VEGETABLE: carrot VEGETABLE: carrot
ROCKET: saturn-v ROCKET: saturn-v
FRUIT: apple FRUIT: apple
CAR: dymaxion CAR: dymaxion
EOF EOF
``` ```
Encrypt the data into file the plugin wants to read: 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
``` ```
$DEMO/bin/sops --encrypt \
Or GCP KMS
```shell
$tmpGoPath/bin/sops --encrypt \
--gcp-kms $keyLocation \ --gcp-kms $keyLocation \
$MYAPP/myClearData.yaml >$MYAPP/myEncryptedData.yaml $MYAPP/myClearData.yaml >$MYAPP/myEncryptedData.yaml
``` ```
Review the files Review the files
```
```shell
tree $DEMO tree $DEMO
``` ```
This should look something like:
> ```shell
> /tmp/tmp.0kIE9VclPt
> ├── kustomize
> │   └── plugin
> │   └── mygenerators
> │   └── sopsencodedsecrets
> │   ├── go.mod
> │   ├── go.sum
> │   ├── LICENSE
> │   ├── README.md
> │   ├── SopsEncodedSecrets.go
> │   ├── SopsEncodedSecrets.so
> │   └── SopsEncodedSecrets_test.go
> └── myapp
> ├── kustomization.yaml
> ├── myClearData.yaml
> ├── myEncryptedData.yaml
> └── secGenerator.yaml
> ```
## Build your app, using the plugin: ## Build your app, using the plugin:
``` ```shell
XDG_CONFIG_HOME=$DEMO $DEMO/bin/kustomize build --enable_alpha_plugins $MYAPP XDG_CONFIG_HOME=$DEMO $tmpGoPath/bin/kustomize build --enable_alpha_plugins $MYAPP
``` ```
This should emit a kubernetes secret, with This should emit a kubernetes secret, with
@@ -299,10 +359,9 @@ encrypted data for the names `ROCKET` and `CAR`.
Above, if you had set Above, if you had set
> ``` > ```shell
> PLUGIN_ROOT=$HOME/.config/kustomize/plugin > PLUGIN_ROOT=$HOME/.config/kustomize/plugin
> ``` > ```
there would be no need to use `XDG_CONFIG_HOME` in the there would be no need to use `XDG_CONFIG_HOME` in the
_kustomize_ command above. _kustomize_ command above.

View File

@@ -1,18 +1,15 @@
# kustomize 3.0.0 # kustomize 3.0.0
This release is basically [v2.1.0](v2.1.0.md), This release is basically [v2.1.0](v2.1.0.md),
with some post-v2.1.0 bugs fixed and a `v3` in Go with many post-v2.1.0 bugs fixed (in about 150
package paths. commits) and a `v3` in Go package paths.
[plugin]: https://github.com/kubernetes-sigs/kustomize/tree/master/docs/plugins [plugin]: https://github.com/kubernetes-sigs/kustomize/tree/master/docs/plugins
The major version increment to `v3` puts a new The major version increment to `v3` puts a new
floor on a stable API for [plugin] developers floor on a stable API for [plugin] developers
(both _Go_ plugin developers and _exec_ plugin (both _Go_ plugin developers and _exec_ plugin
developers who happen to use Go), to carry them developers who happen to use Go).
through the coming series of minor releases and
patches.
### Why so soon after v2.1.0? ### Why so soon after v2.1.0?

View File

@@ -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 [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 [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 [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 [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 [conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
[release process]: ../releasing/README.md [release process]: ../releasing/README.md
[kustomization]: glossary.md#kustomization [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 [`apiVersion`]: https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning
[semantic versioning]: https://semver.org [semantic versioning]: https://semver.org

1
go.sum
View File

@@ -149,6 +149,7 @@ k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c=
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 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 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco=
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= 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/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 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -20,6 +20,10 @@ package kunstruct
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
jsonpatch "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/kustomize/v3/pkg/types" "sigs.k8s.io/kustomize/v3/pkg/types"
@@ -65,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. // Copy provides a copy behind an interface.
func (fs *UnstructAdapter) Copy() ifc.Kunstructured { func (fs *UnstructAdapter) Copy() ifc.Kunstructured {
return &UnstructAdapter{*fs.DeepCopy()} return &UnstructAdapter{*fs.DeepCopy()}
@@ -285,3 +294,59 @@ func (fs *UnstructAdapter) MatchesAnnotationSelector(selector string) (bool, err
} }
return s.Matches(labels.Set(fs.GetAnnotations())), nil return s.Matches(labels.Set(fs.GetAnnotations())), nil
} }
func (fs *UnstructAdapter) Patch(patch ifc.Kunstructured) error {
versionedObj, err := scheme.Scheme.New(
toSchemaGvk(patch.GetGvk()))
merged := map[string]interface{}{}
saveName := fs.GetName()
switch {
case runtime.IsNotRegisteredError(err):
baseBytes, err := json.Marshal(fs.Map())
if err != nil {
return err
}
patchBytes, err := json.Marshal(patch.Map())
if err != nil {
return err
}
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
if err != nil {
return err
}
err = json.Unmarshal(mergedBytes, &merged)
if err != nil {
return err
}
case err != nil:
return err
default:
// Use Strategic-Merge-Patch to handle types w/ schema
// TODO: Change this to use the new Merge package.
// Store the name of the target object, because this name may have been munged.
// Apply this name to the patched object.
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
if err != nil {
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
fs.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
fs.SetMap(merged)
fs.SetName(saveName)
return nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@@ -6,8 +6,8 @@ package transformer
import ( import (
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer/patch" "sigs.k8s.io/kustomize/v3/k8sdeps/transformer/patch"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource" "sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers"
) )
// FactoryImpl makes patch transformer and name hash transformer // FactoryImpl makes patch transformer and name hash transformer
@@ -18,9 +18,8 @@ func NewFactoryImpl() *FactoryImpl {
return &FactoryImpl{} return &FactoryImpl{}
} }
// MakePatchTransformer makes a new patch transformer func (p *FactoryImpl) MergePatches(patches []*resource.Resource,
func (p *FactoryImpl) MakePatchTransformer( rf *resource.Factory) (
slice []*resource.Resource, resmap.ResMap, error) {
rf *resource.Factory) (transformers.Transformer, error) { return patch.MergePatches(patches, rf)
return patch.NewTransformer(slice, rf)
} }

View File

@@ -5,6 +5,11 @@ package patch
import ( import (
"encoding/json" "encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/v3/pkg/gvk"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"github.com/evanphx/json-patch" "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@@ -122,3 +127,64 @@ func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource)
smp.lookupPatchMeta, patch1.Map(), patch2.Map()) smp.lookupPatchMeta, patch1.Map(), patch2.Map())
return smp.rf.FromMap(mergeJSONMap), err return smp.rf.FromMap(mergeJSONMap), err
} }
// mergePatches merge and index patches by OrgId.
// It errors out if there is conflict between patches.
func MergePatches(patches []*resource.Resource,
rf *resource.Factory) (resmap.ResMap, error) {
rc := resmap.New()
for ix, patch := range patches {
id := patch.OrgId()
existing := rc.GetMatchingResourcesByOriginalId(id.GvknEquals)
if len(existing) == 0 {
rc.Append(patch)
continue
}
if len(existing) > 1 {
return nil, fmt.Errorf("self conflict in patches")
}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk))
if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err
}
var cd conflictDetector
if err != nil {
cd = newJMPConflictDetector(rf)
} else {
cd, err = newSMPConflictDetector(versionedObj, rf)
if err != nil {
return nil, err
}
}
conflict, err := cd.hasConflict(existing[0], patch)
if err != nil {
return nil, err
}
if conflict {
conflictingPatch, err := cd.findConflict(ix, patches)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(
"conflict between %#v and %#v",
conflictingPatch.Map(), patch.Map())
}
merged, err := cd.mergePatches(existing[0], patch)
if err != nil {
return nil, err
}
rc.Replace(merged)
}
return rc, nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@@ -1,156 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package patch
import (
"encoding/json"
"fmt"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/v3/pkg/gvk"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers"
)
// transformer applies strategic merge patches.
type transformer struct {
patches []*resource.Resource
rf *resource.Factory
}
var _ transformers.Transformer = &transformer{}
// NewTransformer constructs a strategic merge patch transformer.
func NewTransformer(
slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
if len(slice) == 0 {
return transformers.NewNoOpTransformer(), nil
}
return &transformer{patches: slice, rf: rf}, nil
}
// Transform apply the patches on top of the base resources.
// nolint:ineffassign
func (tf *transformer) Transform(m resmap.ResMap) error {
patches, err := tf.mergePatches()
if err != nil {
return err
}
for _, patch := range patches.Resources() {
target, err := m.GetById(patch.OrgId())
if err != nil {
return err
}
merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(
toSchemaGvk(patch.OrgId().Gvk))
saveName := target.GetName()
switch {
case runtime.IsNotRegisteredError(err):
// Use JSON merge patch to handle types w/o schema
baseBytes, err := json.Marshal(target.Map())
if err != nil {
return err
}
patchBytes, err := json.Marshal(patch.Map())
if err != nil {
return err
}
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
if err != nil {
return err
}
err = json.Unmarshal(mergedBytes, &merged)
if err != nil {
return err
}
case err != nil:
return err
default:
// Use Strategic-Merge-Patch to handle types w/ schema
// TODO: Change this to use the new Merge package.
// Store the name of the target object, because this name may have been munged.
// Apply this name to the patched object.
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
if err != nil {
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
target.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
target.SetMap(merged)
target.SetName(saveName)
}
return nil
}
// mergePatches merge and index patches by OrgId.
// It errors out if there is conflict between patches.
func (tf *transformer) mergePatches() (resmap.ResMap, error) {
rc := resmap.New()
for ix, patch := range tf.patches {
id := patch.OrgId()
existing := rc.GetMatchingResourcesByOriginalId(id.GvknEquals)
if len(existing) == 0 {
rc.Append(patch)
continue
}
if len(existing) > 1 {
return nil, fmt.Errorf("self conflict in patches")
}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk))
if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err
}
var cd conflictDetector
if err != nil {
cd = newJMPConflictDetector(tf.rf)
} else {
cd, err = newSMPConflictDetector(versionedObj, tf.rf)
if err != nil {
return nil, err
}
}
conflict, err := cd.hasConflict(existing[0], patch)
if err != nil {
return nil, err
}
if conflict {
conflictingPatch, err := cd.findConflict(ix, tf.patches)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(
"conflict between %#v and %#v",
conflictingPatch.Map(), patch.Map())
}
merged, err := cd.mergePatches(existing[0], patch)
if err != nil {
return nil, err
}
rc.Replace(merged)
}
return rc, nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@@ -1,579 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package patch
import (
"reflect"
"strings"
"testing"
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/v3/pkg/resmaptest"
"sigs.k8s.io/kustomize/v3/pkg/resource"
)
var rf = resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
func TestOverlayRun(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"old-label": "old-value",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"another-label": "foo",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
}),
}
expected := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"old-label": "old-value",
"another-label": "foo",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
}).ResMap()
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(base, expected) {
err = expected.ErrorIfNotEqualLists(base)
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestMultiplePatches(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
}),
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"env": []interface{}{
map[string]interface{}{
"name": "ANOTHERENV",
"value": "HELLO",
},
},
},
map[string]interface{}{
"name": "busybox",
"image": "busybox",
},
},
},
},
},
}),
}
expected := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "ANOTHERENV",
"value": "HELLO",
},
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
map[string]interface{}{
"name": "busybox",
"image": "busybox",
},
},
},
},
},
}).ResMap()
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(base, expected) {
err = expected.ErrorIfNotEqualLists(base)
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestMultiplePatchesWithConflict(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
}),
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:1.7.9",
},
},
},
},
},
}),
}
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
}
}
func TestPatchesWithWrongNamespace(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
"namespace": "namespace1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
"namespace": "namespace2",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:1.7.9",
},
},
},
},
},
}),
}
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "failed to find unique target for patch") {
t.Fatalf("expected error to contain %q but get %v", "failed to find target for patch", err)
}
}
func TestNoSchemaOverlayRun(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"B": "Y",
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"B": nil,
"C": "Z",
},
},
}),
}
expected := resmaptest_test.NewRmBuilder(t, rf).
Add(
map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"C": "Z",
},
},
}).ResMap()
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = expected.ErrorIfNotEqualLists(base); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestNoSchemaMultiplePatches(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"B": "Y",
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"B": nil,
"C": "Z",
},
},
}),
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"C": "Z",
"D": "W",
},
"baz": map[string]interface{}{
"hello": "world",
},
},
}),
}
expected := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"C": "Z",
"D": "W",
},
"baz": map[string]interface{}{
"hello": "world",
},
},
}).ResMap()
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = expected.ErrorIfNotEqualLists(base); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) {
base := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"B": "Y",
},
},
}).ResMap()
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"B": nil,
"C": "Z",
},
},
}),
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"C": "NOT_Z",
},
},
}),
}
lt, err := NewTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
}
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/kustomize/v3/pkg/fs" "sigs.k8s.io/kustomize/v3/pkg/fs"
"sigs.k8s.io/kustomize/v3/pkg/ifc" "sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/ifc/transformer"
"sigs.k8s.io/kustomize/v3/pkg/loader" "sigs.k8s.io/kustomize/v3/pkg/loader"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/plugins" "sigs.k8s.io/kustomize/v3/pkg/plugins"
@@ -61,7 +60,7 @@ https://github.com/hashicorp/go-getter#url-format
func NewCmdBuild( func NewCmdBuild(
out io.Writer, fSys fs.FileSystem, out io.Writer, fSys fs.FileSystem,
v ifc.Validator, rf *resmap.Factory, v ifc.Validator, rf *resmap.Factory,
ptf transformer.Factory) *cobra.Command { ptf resmap.PatchFactory) *cobra.Command {
var o Options var o Options
pluginConfig := plugins.DefaultPluginConfig() pluginConfig := plugins.DefaultPluginConfig()
@@ -115,7 +114,7 @@ func (o *Options) Validate(args []string) (err error) {
// RunBuild runs build command. // RunBuild runs build command.
func (o *Options) RunBuild( func (o *Options) RunBuild(
out io.Writer, v ifc.Validator, fSys fs.FileSystem, out io.Writer, v ifc.Validator, fSys fs.FileSystem,
rf *resmap.Factory, ptf transformer.Factory, rf *resmap.Factory, ptf resmap.PatchFactory,
pl *plugins.Loader) error { pl *plugins.Loader) error {
ldr, err := loader.NewLoader( ldr, err := loader.NewLoader(
o.loadRestrictor, v, o.kustomizationPath, fSys) o.loadRestrictor, v, o.kustomizationPath, fSys)
@@ -136,7 +135,7 @@ func (o *Options) RunBuild(
func (o *Options) RunBuildPrune( func (o *Options) RunBuildPrune(
out io.Writer, v ifc.Validator, fSys fs.FileSystem, out io.Writer, v ifc.Validator, fSys fs.FileSystem,
rf *resmap.Factory, ptf transformer.Factory, rf *resmap.Factory, ptf resmap.PatchFactory,
pl *plugins.Loader) error { pl *plugins.Loader) error {
ldr, err := loader.NewLoader( ldr, err := loader.NewLoader(
o.loadRestrictor, v, o.kustomizationPath, fSys) o.loadRestrictor, v, o.kustomizationPath, fSys)
@@ -180,7 +179,7 @@ func (o *Options) emitResources(
func NewCmdBuildPrune( func NewCmdBuildPrune(
out io.Writer, v ifc.Validator, fSys fs.FileSystem, out io.Writer, v ifc.Validator, fSys fs.FileSystem,
rf *resmap.Factory, ptf transformer.Factory, rf *resmap.Factory, ptf resmap.PatchFactory,
pl *plugins.Loader) *cobra.Command { pl *plugins.Loader) *cobra.Command {
var o Options var o Options
@@ -202,7 +201,7 @@ func NewCmdBuildPrune(
func writeIndividualFiles( func writeIndividualFiles(
fSys fs.FileSystem, folderPath string, m resmap.ResMap) error { fSys fs.FileSystem, folderPath string, m resmap.ResMap) error {
byNamespace := m.GroupedByNamespace() byNamespace := m.GroupedByCurrentNamespace()
for namespace, resList := range byNamespace { for namespace, resList := range byNamespace {
for _, res := range resList { for _, res := range resList {
fName := fileName(res) fName := fileName(res)

View File

@@ -36,12 +36,13 @@ See https://sigs.k8s.io/kustomize
} }
uf := kunstruct.NewKunstructuredFactoryImpl() uf := kunstruct.NewKunstructuredFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf)) pf := transformer.NewFactoryImpl()
rf := resmap.NewFactory(resource.NewFactory(uf), pf)
v := validator.NewKustValidator() v := validator.NewKustValidator()
c.AddCommand( c.AddCommand(
build.NewCmdBuild( build.NewCmdBuild(
stdOut, fSys, v, stdOut, fSys, v,
rf, transformer.NewFactoryImpl()), rf, pf),
edit.NewCmdEdit(fSys, v, uf), edit.NewCmdEdit(fSys, v, uf),
misc.NewCmdConfig(fSys), misc.NewCmdConfig(fSys),
misc.NewCmdVersion(stdOut), misc.NewCmdVersion(stdOut),

View File

@@ -54,15 +54,18 @@ type Kunstructured interface {
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
UnmarshalJSON([]byte) error UnmarshalJSON([]byte) error
GetGvk() gvk.Gvk GetGvk() gvk.Gvk
SetGvk(gvk.Gvk)
GetKind() string GetKind() string
GetName() string GetName() string
SetName(string) SetName(string)
SetNamespace(string)
GetLabels() map[string]string GetLabels() map[string]string
SetLabels(map[string]string) SetLabels(map[string]string)
GetAnnotations() map[string]string GetAnnotations() map[string]string
SetAnnotations(map[string]string) SetAnnotations(map[string]string)
MatchesLabelSelector(selector string) (bool, error) MatchesLabelSelector(selector string) (bool, error)
MatchesAnnotationSelector(selector string) (bool, error) MatchesAnnotationSelector(selector string) (bool, error)
Patch(Kunstructured) error
} }
// KunstructuredFactory makes instances of Kunstructured. // KunstructuredFactory makes instances of Kunstructured.

View File

@@ -1,17 +0,0 @@
/// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package patch holds miscellaneous interfaces used by kustomize.
package transformer
import (
"sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/transformers"
)
// Factory makes transformers that require k8sdeps.
type Factory interface {
MakePatchTransformer(
slice []*resource.Resource,
rf *resource.Factory) (transformers.Transformer, error)
}

View File

@@ -49,7 +49,7 @@ func NewKustTestHarnessFull(
t *testing.T, path string, t *testing.T, path string,
lr loader.LoadRestrictorFunc, pc *types.PluginConfig) *KustTestHarness { lr loader.LoadRestrictorFunc, pc *types.PluginConfig) *KustTestHarness {
rf := resmap.NewFactory(resource.NewFactory( rf := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), transformer.NewFactoryImpl())
return &KustTestHarness{ return &KustTestHarness{
t: t, t: t,
rf: rf, rf: rf,

View File

@@ -30,7 +30,7 @@ func TestExecPluginConfig(t *testing.T) {
path := "/app" path := "/app"
rf := resmap.NewFactory( rf := resmap.NewFactory(
resource.NewFactory( resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
ldr := loadertest.NewFakeLoader(path) ldr := loadertest.NewFakeLoader(path)
pluginConfig := rf.RF().FromMap( pluginConfig := rf.RF().FromMap(
map[string]interface{}{ map[string]interface{}{

View File

@@ -9,6 +9,7 @@ import (
"sigs.k8s.io/kustomize/v3/internal/loadertest" "sigs.k8s.io/kustomize/v3/internal/loadertest"
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" "sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
. "sigs.k8s.io/kustomize/v3/pkg/plugins" . "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/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource" "sigs.k8s.io/kustomize/v3/pkg/resource"
) )
@@ -41,7 +42,7 @@ port: "12345"
) )
func TestLoader(t *testing.T) { func TestLoader(t *testing.T) {
tc := NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -50,7 +51,7 @@ func TestLoader(t *testing.T) {
"someteam.example.com", "v1", "SomeServiceGenerator") "someteam.example.com", "v1", "SomeServiceGenerator")
rmF := resmap.NewFactory(resource.NewFactory( rmF := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
l := NewLoader(ActivePluginConfig(), rmF) l := NewLoader(ActivePluginConfig(), rmF)
if l == nil { if l == nil {

View File

@@ -1,7 +1,7 @@
// Copyright 2019 The Kubernetes Authors. // Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package plugins package test
import ( import (
"io/ioutil" "io/ioutil"
@@ -12,13 +12,14 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/plugins"
) )
// EnvForTest manages the plugin test environment. // EnvForTest manages the plugin test environment.
// It sets/resets XDG_CONFIG_HOME, makes/removes a temp objRoot. // It sets/resets XDG_CONFIG_HOME, makes/removes a temp objRoot.
type EnvForTest struct { type EnvForTest struct {
t *testing.T t *testing.T
compiler *Compiler compiler *plugins.Compiler
workDir string workDir string
oldXdg string oldXdg string
wasSet bool 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 // The plugin loader wants to find object code under
// $XDG_CONFIG_HOME/kustomize/plugins // $XDG_CONFIG_HOME/kustomize/plugins
// and the compiler writes object code to // and the compiler writes object code to
@@ -73,11 +74,11 @@ func (x *EnvForTest) makeCompiler() *Compiler {
if err != nil { if err != nil {
x.t.Error(err) x.t.Error(err)
} }
srcRoot, err := DefaultSrcRoot() srcRoot, err := plugins.DefaultSrcRoot()
if err != nil { if err != nil {
x.t.Error(err) x.t.Error(err)
} }
return NewCompiler(srcRoot, objRoot) return plugins.NewCompiler(srcRoot, objRoot)
} }
func (x *EnvForTest) createWorkDir() { func (x *EnvForTest) createWorkDir() {

View File

@@ -14,11 +14,12 @@ import (
// Factory makes instances of ResMap. // Factory makes instances of ResMap.
type Factory struct { type Factory struct {
resF *resource.Factory resF *resource.Factory
tf PatchFactory
} }
// NewFactory returns a new resmap.Factory. // NewFactory returns a new resmap.Factory.
func NewFactory(rf *resource.Factory) *Factory { func NewFactory(rf *resource.Factory, tf PatchFactory) *Factory {
return &Factory{resF: rf} return &Factory{resF: rf, tf: tf}
} }
// RF returns a resource.Factory. // RF returns a resource.Factory.
@@ -118,6 +119,11 @@ func (rmF *Factory) FromSecretArgs(
return rmF.FromResource(res), nil return rmF.FromResource(res), nil
} }
func (rmF *Factory) MergePatches(patches []*resource.Resource) (
ResMap, error) {
return rmF.tf.MergePatches(patches, rmF.resF)
}
func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) { func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) {
result := New() result := New()
for _, res := range resources { for _, res := range resources {

View File

@@ -0,0 +1,15 @@
/// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package patch holds miscellaneous interfaces used by kustomize.
package resmap
import (
"sigs.k8s.io/kustomize/v3/pkg/resource"
)
// PatchFactory makes transformers that require k8sdeps.
type PatchFactory interface {
MergePatches(patches []*resource.Resource,
rf *resource.Factory) (ResMap, error)
}

View File

@@ -112,13 +112,18 @@ type ResMap interface {
// match. // match.
GetById(resid.ResId) (*resource.Resource, error) 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. // to a slice of *Resource in that namespace.
// Resources for whom IsNamespaceableKind is false are // Resources for whom IsNamespaceableKind is false are
// are not included at all (see NonNamespaceable). // are not included at all (see NonNamespaceable).
// Resources with an empty namespace are placed // Resources with an empty namespace are placed
// in the resid.DefaultNamespace entry. // 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 // NonNamespaceable returns a slice of resources that
// cannot be placed in a namespace, e.g. // 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) return nil, fmt.Errorf("no matches for %sId %s", s, id)
} }
// GroupedByNamespace implements ResMap.GroupByNamespace // GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
func (m *resWrangler) GroupedByNamespace() map[string][]*resource.Resource { func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
items := m.groupedByNamespace() items := m.groupedByCurrentNamespace()
delete(items, resid.TotallyNotANamespace) delete(items, resid.TotallyNotANamespace)
return items return items
} }
// NonNamespaceable implements ResMap.NonNamespaceable // NonNamespaceable implements ResMap.NonNamespaceable
func (m *resWrangler) NonNamespaceable() []*resource.Resource { 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) byNamespace := make(map[string][]*resource.Resource)
for _, res := range m.rList { for _, res := range m.rList {
namespace := res.CurId().EffectiveNamespace() namespace := res.CurId().EffectiveNamespace()
@@ -414,6 +419,25 @@ func (m *resWrangler) groupedByNamespace() map[string][]*resource.Resource {
return byNamespace 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. // AsYaml implements ResMap.
func (m *resWrangler) AsYaml() ([]byte, error) { func (m *resWrangler) AsYaml() ([]byte, error) {
firstObj := true firstObj := true
@@ -530,7 +554,20 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
inputRes *resource.Resource) ResMap { inputRes *resource.Resource) ResMap {
inputId := inputRes.OrgId() inputId := inputRes.OrgId()
if !inputId.IsNamespaceableKind() { if !inputId.IsNamespaceableKind() {
return m if inputRes.GetOutermostNamePrefix() == "" {
return m
}
result := New()
for _, r := range m.Resources() {
if r.GetOutermostNamePrefix() == inputRes.GetOutermostNamePrefix() &&
r.GetOutermostNameSuffix() == inputRes.GetOutermostNameSuffix() {
err := result.Append(r)
if err != nil {
panic(err)
}
}
}
return result
} }
result := New() result := New()
for _, r := range m.Resources() { for _, r := range m.Resources() {

View File

@@ -19,7 +19,7 @@ import (
var rf = resource.NewFactory( var rf = resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl()) kunstruct.NewKunstructuredFactoryImpl())
var rmF = NewFactory(rf) var rmF = NewFactory(rf, nil)
func doAppend(t *testing.T, w ResMap, r *resource.Resource) { func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
err := w.Append(r) err := w.Append(r)

View File

@@ -62,6 +62,14 @@ func (rm *rmBuilder) AddWithNs(ns string, m map[string]interface{}) *rmBuilder {
return rm 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 { func (rm *rmBuilder) ReplaceResource(m map[string]interface{}) *rmBuilder {
r := rm.rf.FromMap(m) r := rm.rf.FromMap(m)
_, err := rm.m.Replace(r) _, err := rm.m.Replace(r)

View File

@@ -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) 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. // FromMapAndOption returns a new instance of Resource with given options.
func (rf *Factory) FromMapAndOption( func (rf *Factory) FromMapAndOption(
m map[string]interface{}, args *types.GeneratorArgs, option *types.GeneratorOptions) *Resource { m map[string]interface{}, args *types.GeneratorArgs, option *types.GeneratorOptions) *Resource {

View File

@@ -42,6 +42,7 @@ func (r *Resource) Replace(other *Resource) {
r.SetAnnotations( r.SetAnnotations(
mergeStringMaps(other.GetAnnotations(), r.GetAnnotations())) mergeStringMaps(other.GetAnnotations(), r.GetAnnotations()))
r.SetName(other.GetName()) r.SetName(other.GetName())
r.SetNamespace(other.GetNamespace())
r.copyOtherFields(other) r.copyOtherFields(other)
} }
@@ -126,7 +127,7 @@ func (r *Resource) GetOutermostNameSuffix() string {
} }
func (r *Resource) InSameFuzzyNamespace(o *Resource) bool { func (r *Resource) InSameFuzzyNamespace(o *Resource) bool {
return r.GetNamespace() == o.GetNamespace() && return r.CurId().IsNsEquals(o.CurId()) &&
r.GetOutermostNamePrefix() == o.GetOutermostNamePrefix() && r.GetOutermostNamePrefix() == o.GetOutermostNamePrefix() &&
r.GetOutermostNameSuffix() == o.GetOutermostNameSuffix() r.GetOutermostNameSuffix() == o.GetOutermostNameSuffix()
} }

View File

@@ -8,10 +8,10 @@
package target_test package target_test
import ( import (
"sigs.k8s.io/kustomize/v3/pkg/plugins"
"testing" "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, // 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 // TODO: Download and inflate the chart, and check that
// in for the test. // in for the test.
func TestChartInflatorPlugin(t *testing.T) { func TestChartInflatorPlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin( tc.BuildExecPlugin(

View File

@@ -9,8 +9,8 @@ import (
"strings" "strings"
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" kusttest_test "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 patchAddProbe = ` const patchAddProbe = `
@@ -340,7 +340,7 @@ patchesStrategicMerge:
} }
func TestIssue1251_Plugins_ProdVsDev(t *testing.T) { func TestIssue1251_Plugins_ProdVsDev(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -380,7 +380,7 @@ transformers:
} }
func TestIssue1251_Plugins_Local(t *testing.T) { func TestIssue1251_Plugins_Local(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -430,7 +430,7 @@ jsonOp: '%s'
// Remote in the sense that they are bundled in a different kustomization. // Remote in the sense that they are bundled in a different kustomization.
func TestIssue1251_Plugins_Bundled(t *testing.T) { func TestIssue1251_Plugins_Bundled(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/kustomize/v3/pkg/accumulator" "sigs.k8s.io/kustomize/v3/pkg/accumulator"
"sigs.k8s.io/kustomize/v3/pkg/ifc" "sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/ifc/transformer"
"sigs.k8s.io/kustomize/v3/pkg/pgmconfig" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig"
"sigs.k8s.io/kustomize/v3/pkg/plugins" "sigs.k8s.io/kustomize/v3/pkg/plugins"
"sigs.k8s.io/kustomize/v3/pkg/resmap" "sigs.k8s.io/kustomize/v3/pkg/resmap"
@@ -30,7 +29,7 @@ type KustTarget struct {
kustomization *types.Kustomization kustomization *types.Kustomization
ldr ifc.Loader ldr ifc.Loader
rFactory *resmap.Factory rFactory *resmap.Factory
tFactory transformer.Factory tFactory resmap.PatchFactory
pLdr *plugins.Loader pLdr *plugins.Loader
} }
@@ -38,7 +37,7 @@ type KustTarget struct {
func NewKustTarget( func NewKustTarget(
ldr ifc.Loader, ldr ifc.Loader,
rFactory *resmap.Factory, rFactory *resmap.Factory,
tFactory transformer.Factory, tFactory resmap.PatchFactory,
pLdr *plugins.Loader) (*KustTarget, error) { pLdr *plugins.Loader) (*KustTarget, error) {
content, err := loadKustFile(ldr) content, err := loadKustFile(ldr)
if err != nil { if err != nil {
@@ -293,19 +292,7 @@ func (kt *KustTarget) configureExternalGenerators() ([]transformers.Generator, e
} }
func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error { func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error {
patches, err := kt.rFactory.RF().SliceFromPatches(
kt.ldr, kt.kustomization.PatchesStrategicMerge)
if err != nil {
return errors.Wrapf(
err, "reading strategic merge patches %v",
kt.kustomization.PatchesStrategicMerge)
}
var r []transformers.Transformer var r []transformers.Transformer
t, err := kt.tFactory.MakePatchTransformer(patches, kt.rFactory.RF())
if err != nil {
return err
}
r = append(r, t)
tConfig := ra.GetTransformerConfig() tConfig := ra.GetTransformerConfig()
lts, err := kt.configureBuiltinTransformers(tConfig) lts, err := kt.configureBuiltinTransformers(tConfig)
if err != nil { if err != nil {
@@ -317,7 +304,7 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error {
return err return err
} }
r = append(r, lts...) r = append(r, lts...)
t = transformers.NewMultiTransformer(r) t := transformers.NewMultiTransformer(r)
return ra.Transform(t) return ra.Transform(t)
} }

View File

@@ -63,6 +63,8 @@ func (kt *KustTarget) configureBuiltinTransformers(
// with tests: // with tests:
// - patch SMP // - patch SMP
configurators := []transformerConfigurator{ configurators := []transformerConfigurator{
kt.configureBuiltinPatchStrategicMergeTransformer,
kt.configureBuiltinPatchTransformer,
kt.configureBuiltinNamespaceTransformer, kt.configureBuiltinNamespaceTransformer,
kt.configureBuiltinNameTransformer, kt.configureBuiltinNameTransformer,
kt.configureBuiltinLabelTransformer, kt.configureBuiltinLabelTransformer,
@@ -165,6 +167,52 @@ func (kt *KustTarget) configureBuiltinPatchJson6902Transformer(
return return
} }
func (kt *KustTarget) configureBuiltinPatchStrategicMergeTransformer(
tConfig *config.TransformerConfig) (
result []transformers.Transformer, err error) {
if len(kt.kustomization.PatchesStrategicMerge) == 0 {
return
}
var c struct {
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
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 {
return nil, err
}
result = append(result, p)
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( func (kt *KustTarget) configureBuiltinLabelTransformer(
tConfig *config.TransformerConfig) ( tConfig *config.TransformerConfig) (
result []transformers.Transformer, err error) { result []transformers.Transformer, err error) {

View File

@@ -61,6 +61,8 @@ spec:
volumeMounts: volumeMounts:
- name: nginx-persistent-storage - name: nginx-persistent-storage
mountPath: /tmp/ps mountPath: /tmp/ps
- name: sidecar
image: sidecar:latest
volumes: volumes:
- name: nginx-persistent-storage - name: nginx-persistent-storage
emptyDir: {} emptyDir: {}
@@ -138,8 +140,6 @@ spec:
env: env:
- name: ANOTHERENV - name: ANOTHERENV
value: FOO value: FOO
- name: sidecar
image: sidecar
volumes: volumes:
- name: nginx-persistent-storage - name: nginx-persistent-storage
`) `)
@@ -187,7 +187,7 @@ spec:
volumeMounts: volumeMounts:
- mountPath: /tmp/ps - mountPath: /tmp/ps
name: nginx-persistent-storage name: nginx-persistent-storage
- image: sidecar - image: sidecar:latest
name: sidecar name: sidecar
volumes: volumes:
- gcePersistentDisk: - gcePersistentDisk:
@@ -293,3 +293,258 @@ spec:
t.Fatalf("Unexpected err: %v", err) 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
`)
}

View File

@@ -87,3 +87,43 @@ metadata:
type: Opaque type: Opaque
`) `)
} }
func TestNamespacedGeneratorWithOverlays(t *testing.T) {
th := kusttest_test.NewKustTestHarness(t, "/app/overlay")
th.WriteK("/app/base", `
namespace: base
configMapGenerator:
- name: testCase
literals:
- base=true
`)
th.WriteK("/app/overlay", `
resources:
- ../base
namespace: overlay
configMapGenerator:
- name: testCase
behavior: merge
literals:
- overlay=true
`)
m, err := th.MakeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.AssertActualEqualsExpected(m, `
apiVersion: v1
data:
base: "true"
overlay: "true"
kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: testCase-4g75kbk6gm
namespace: overlay
`)
}

View File

@@ -5,7 +5,6 @@ package target_test
import ( import (
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "sigs.k8s.io/kustomize/v3/pkg/kusttest"
"strings"
"testing" "testing"
) )
@@ -51,19 +50,46 @@ resources:
- role.yaml - role.yaml
`) `)
_, err := th.MakeKustTarget().MakeCustomizedResMap() m, err := th.MakeKustTarget().MakeCustomizedResMap()
// TODO: Fix #1044 // This validates Fix #1444. This should not be an error anymore -
// This should not be an error -
// the secrets have the same name but are in different namespaces. // the secrets have the same name but are in different namespaces.
// The ClusterRole (by def) is not in a namespace, // The ClusterRole (by def) is not in a namespace,
// an in this case applies to *any* Secret resource // an in this case applies to *any* Secret resource
// named "dummy" // named "dummy"
if err == nil { if err != nil {
t.Fatalf("unexpected lack of error") 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
`)
} }

View File

@@ -12,9 +12,10 @@ import (
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" "sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer" "sigs.k8s.io/kustomize/v3/k8sdeps/transformer"
"sigs.k8s.io/kustomize/v3/pkg/fs" "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/loader"
"sigs.k8s.io/kustomize/v3/pkg/plugins" "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/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resource" "sigs.k8s.io/kustomize/v3/pkg/resource"
"sigs.k8s.io/kustomize/v3/pkg/target" "sigs.k8s.io/kustomize/v3/pkg/target"
@@ -22,7 +23,7 @@ import (
) )
func TestPluginDir(t *testing.T) { func TestPluginDir(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin( tc.BuildExecPlugin(
@@ -62,7 +63,7 @@ metadata:
t.Fatalf("Err: %v", err) t.Fatalf("Err: %v", err)
} }
rf := resmap.NewFactory(resource.NewFactory( rf := resmap.NewFactory(resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())) kunstruct.NewKunstructuredFactoryImpl()), nil)
pl := plugins.NewLoader(plugins.ActivePluginConfig(), rf) pl := plugins.NewLoader(plugins.ActivePluginConfig(), rf)
tg, err := target.NewKustTarget(ldr, rf, transformer.NewFactoryImpl(), pl) tg, err := target.NewKustTarget(ldr, rf, transformer.NewFactoryImpl(), pl)

View File

@@ -15,6 +15,7 @@ func writeBase(th *kusttest_test.KustTestHarness) {
resources: resources:
- serviceaccount.yaml - serviceaccount.yaml
- rolebinding.yaml - rolebinding.yaml
- clusterrolebinding.yaml
namePrefix: pfx- namePrefix: pfx-
nameSuffix: -sfx nameSuffix: -sfx
`) `)
@@ -34,6 +35,19 @@ roleRef:
kind: Role kind: Role
name: role name: role
subjects: subjects:
- kind: ServiceAccount
name: serviceaccount
`)
th.WriteF("/app/base/clusterrolebinding.yaml", `
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role
subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: serviceaccount name: serviceaccount
`) `)
@@ -86,6 +100,18 @@ roleRef:
kind: Role kind: Role
name: role name: role
subjects: subjects:
- kind: ServiceAccount
name: pfx-serviceaccount-sfx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: pfx-rolebinding-sfx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role
subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: pfx-serviceaccount-sfx name: pfx-serviceaccount-sfx
`) `)
@@ -114,6 +140,18 @@ roleRef:
kind: Role kind: Role
name: role name: role
subjects: 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: Role
name: role
subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: a-pfx-serviceaccount-sfx-suffixA name: a-pfx-serviceaccount-sfx-suffixA
`) `)
@@ -142,6 +180,18 @@ roleRef:
kind: Role kind: Role
name: role name: role
subjects: 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: Role
name: role
subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: b-pfx-serviceaccount-sfx-suffixB name: b-pfx-serviceaccount-sfx-suffixB
`) `)
@@ -174,6 +224,18 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: a-pfx-serviceaccount-sfx-suffixA 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: Role
name: role
subjects:
- kind: ServiceAccount
name: a-pfx-serviceaccount-sfx-suffixA
---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
@@ -188,6 +250,18 @@ roleRef:
kind: Role kind: Role
name: role name: role
subjects: 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: Role
name: role
subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: b-pfx-serviceaccount-sfx-suffixB name: b-pfx-serviceaccount-sfx-suffixB
`) `)

View File

@@ -7,8 +7,8 @@ import (
"strings" "strings"
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" kusttest_test "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 writeDeployment(th *kusttest_test.KustTestHarness, path string) { func writeDeployment(th *kusttest_test.KustTestHarness, path string) {
@@ -50,7 +50,7 @@ metadata:
} }
func TestOrderedTransformers(t *testing.T) { func TestOrderedTransformers(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -96,7 +96,7 @@ spec:
} }
func TestPluginsNotEnabled(t *testing.T) { func TestPluginsNotEnabled(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -119,7 +119,7 @@ transformers:
} }
func TestSedTransformer(t *testing.T) { func TestSedTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin( tc.BuildExecPlugin(
@@ -187,7 +187,7 @@ metadata:
} }
func TestTransformedTransformers(t *testing.T) { func TestTransformedTransformers(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -52,6 +52,9 @@ nameReference:
- path: spec/initContainers/envFrom/configMapRef/name - path: spec/initContainers/envFrom/configMapRef/name
version: v1 version: v1
kind: Pod kind: Pod
- path: spec/volumes/projected/sources/configMap/name
version: v1
kind: Pod
- path: spec/template/spec/volumes/configMap/name - path: spec/template/spec/volumes/configMap/name
kind: Deployment kind: Deployment
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name - path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
@@ -74,6 +77,8 @@ nameReference:
kind: ReplicaSet kind: ReplicaSet
- path: spec/template/spec/initContainers/envFrom/configMapRef/name - path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: ReplicaSet kind: ReplicaSet
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: ReplicaSet
- path: spec/template/spec/volumes/configMap/name - path: spec/template/spec/volumes/configMap/name
kind: DaemonSet kind: DaemonSet
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name - path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
@@ -84,6 +89,8 @@ nameReference:
kind: DaemonSet kind: DaemonSet
- path: spec/template/spec/initContainers/envFrom/configMapRef/name - path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: DaemonSet kind: DaemonSet
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: DaemonSet
- path: spec/template/spec/volumes/configMap/name - path: spec/template/spec/volumes/configMap/name
kind: StatefulSet kind: StatefulSet
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name - path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
@@ -106,6 +113,8 @@ nameReference:
kind: Job kind: Job
- path: spec/template/spec/initContainers/envFrom/configMapRef/name - path: spec/template/spec/initContainers/envFrom/configMapRef/name
kind: Job kind: Job
- path: spec/template/spec/volumes/projected/sources/configMap/name
kind: Job
- path: spec/jobTemplate/spec/template/spec/volumes/configMap/name - path: spec/jobTemplate/spec/template/spec/volumes/configMap/name
kind: CronJob kind: CronJob
- path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/configMap/name - path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/configMap/name
@@ -140,6 +149,9 @@ nameReference:
- path: spec/imagePullSecrets/name - path: spec/imagePullSecrets/name
version: v1 version: v1
kind: Pod kind: Pod
- path: spec/volumes/projected/sources/secret/name
version: v1
kind: Pod
- path: spec/template/spec/volumes/secret/secretName - path: spec/template/spec/volumes/secret/secretName
kind: Deployment kind: Deployment
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name - path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
@@ -166,6 +178,8 @@ nameReference:
kind: ReplicaSet kind: ReplicaSet
- path: spec/template/spec/imagePullSecrets/name - path: spec/template/spec/imagePullSecrets/name
kind: ReplicaSet kind: ReplicaSet
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: ReplicaSet
- path: spec/template/spec/volumes/secret/secretName - path: spec/template/spec/volumes/secret/secretName
kind: DaemonSet kind: DaemonSet
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name - path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
@@ -178,6 +192,8 @@ nameReference:
kind: DaemonSet kind: DaemonSet
- path: spec/template/spec/imagePullSecrets/name - path: spec/template/spec/imagePullSecrets/name
kind: DaemonSet kind: DaemonSet
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: DaemonSet
- path: spec/template/spec/volumes/secret/secretName - path: spec/template/spec/volumes/secret/secretName
kind: StatefulSet kind: StatefulSet
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name - path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
@@ -204,6 +220,8 @@ nameReference:
kind: Job kind: Job
- path: spec/template/spec/imagePullSecrets/name - path: spec/template/spec/imagePullSecrets/name
kind: Job kind: Job
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: Job
- path: spec/jobTemplate/spec/template/spec/volumes/secret/secretName - path: spec/jobTemplate/spec/template/spec/volumes/secret/secretName
kind: CronJob kind: CronJob
- path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/secret/name - path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/secret/name
@@ -281,10 +299,10 @@ nameReference:
- kind: ServiceAccount - kind: ServiceAccount
version: v1 version: v1
fieldSpecs: fieldSpecs:
- path: subjects/name - path: subjects
kind: RoleBinding kind: RoleBinding
group: rbac.authorization.k8s.io group: rbac.authorization.k8s.io
- path: subjects/name - path: subjects
kind: ClusterRoleBinding kind: ClusterRoleBinding
group: rbac.authorization.k8s.io group: rbac.authorization.k8s.io
- path: spec/serviceAccountName - path: spec/serviceAccountName
@@ -325,6 +343,8 @@ nameReference:
fieldSpecs: fieldSpecs:
- path: spec/volumeName - path: spec/volumeName
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
- path: rules/resourceNames
kind: ClusterRole
- kind: StorageClass - kind: StorageClass
version: v1 version: v1
@@ -334,5 +354,7 @@ nameReference:
kind: PersistentVolume kind: PersistentVolume
- path: spec/storageClassName - path: spec/storageClassName
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
- path: spec/volumeClaimTemplates/spec/storageClassName
kind: StatefulSet
` `
) )

View File

@@ -106,6 +106,102 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
return nil 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
}
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( func (o *nameReferenceTransformer) getNewNameFunc(
referrer *resource.Resource, referrer *resource.Resource,
target gvk.Gvk, target gvk.Gvk,
@@ -114,52 +210,40 @@ func (o *nameReferenceTransformer) getNewNameFunc(
switch in.(type) { switch in.(type) {
case string: case string:
oldName, _ := in.(string) oldName, _ := in.(string)
for _, res := range referralCandidates.Resources() { return o.getSimpleNameField(oldName, referrer, target,
id := res.OrgId() referralCandidates, referralCandidates.Resources())
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
case []interface{}: case []interface{}:
l, _ := in.([]interface{}) l, _ := in.([]interface{})
var names []string for idx, item := range l {
for _, item := range l { switch item.(type) {
name, ok := item.(string) case string:
if !ok { // 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( return nil, fmt.Errorf(
"%#v is expected to be %T", item, name) "%#v is expected to be either a []string or a []map[string]interface{}", in)
}
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
} }
} }
return in, nil return in, nil

View File

@@ -8,6 +8,8 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" "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/resmap"
"sigs.k8s.io/kustomize/v3/pkg/resmaptest" "sigs.k8s.io/kustomize/v3/pkg/resmaptest"
"sigs.k8s.io/kustomize/v3/pkg/resource" "sigs.k8s.io/kustomize/v3/pkg/resource"
@@ -466,6 +468,7 @@ func TestNameReferenceHappyRun(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if err = expected.ErrorIfNotEqualLists(m); err != nil { if err = expected.ErrorIfNotEqualLists(m); err != nil {
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
@@ -497,7 +500,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
}, },
}, },
}).ResMap(), }).ResMap(),
expectedErr: "is expected to be string"}, expectedErr: "is expected to be"},
{ {
resMap: resmaptest_test.NewRmBuilder(t, rf).Add( resMap: resmaptest_test.NewRmBuilder(t, rf).Add(
map[string]interface{}{ map[string]interface{}{
@@ -517,7 +520,7 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
}, },
}, },
}).ResMap(), }).ResMap(),
expectedErr: "is expected to be either a string or a []interface{}"}, expectedErr: "is expected to be"},
} }
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference) nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
@@ -590,3 +593,431 @@ func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
t.Fatalf("actual doesn't match expected: %v", err) 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"
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,
},
}}).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,
},
},
}).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).
// 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,
},
}}).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,
},
},
}).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)
}
}

View File

@@ -200,7 +200,6 @@ func (k *Kustomization) EnforceFields() []string {
// new field names. // new field names.
func FixKustomizationPreUnmarshalling(data []byte) []byte { func FixKustomizationPreUnmarshalling(data []byte) []byte {
deprecateFieldsMap := map[string]string{ deprecateFieldsMap := map[string]string{
"patches:": "patchesStrategicMerge:",
"imageTags:": "images:", "imageTags:": "images:",
} }
for oldname, newname := range deprecateFieldsMap { for oldname, newname := range deprecateFieldsMap {

View File

@@ -149,7 +149,7 @@ func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) e
func isImageMatched(s, t string) bool { func isImageMatched(s, t string) bool {
// Tag values are limited to [a-zA-Z0-9_.-]. // 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) return pattern.MatchString(s)
} }
@@ -175,7 +175,7 @@ func split(imageName string) (name string, tag string) {
} }
i := ic i := ic
if ic < 0 { if ia > 0 {
i = ia i = ia
} }

View File

@@ -52,6 +52,7 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
} }
} }
p.updateClusterRoleBinding(m) p.updateClusterRoleBinding(m)
p.updateServiceReference(m)
return nil return nil
} }
@@ -125,3 +126,40 @@ func (p *NamespaceTransformerPlugin) updateClusterRoleBinding(m resmap.ResMap) {
objMap["subjects"] = subjects objMap["subjects"] = subjects
} }
} }
func (p *NamespaceTransformerPlugin) updateServiceReference(m resmap.ResMap) {
svc := gvk.Gvk{Version: "v1", Kind: "Service"}
svcMap := map[string]bool{}
for _, id := range m.AllIds() {
if id.Gvk.Equals(svc) {
svcMap[id.Name] = true
}
}
for _, res := range m.Resources() {
if res.OrgId().Kind != "ValidatingWebhookConfiguration" &&
res.OrgId().Kind != "MutatingWebhookConfiguration" {
continue
}
objMap := res.Map()
webhooks, ok := objMap["webhooks"].([]interface{})
if webhooks == nil || !ok {
continue
}
for i := range webhooks {
webhook := webhooks[i].(map[string]interface{})
transformers.MutateField(
webhook, []string{"clientConfig", "service"},
false, func(obj interface{}) (interface{}, error) {
svc := obj.(map[string]interface{})
svcName, foundN := svc["name"]
if foundN && svcMap[svcName.(string)] {
svc["namespace"] = p.Namespace
}
return svc, nil
})
webhooks[i] = webhook
}
objMap["webhooks"] = webhooks
}
}

View File

@@ -0,0 +1,75 @@
// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT.
package builtin
import (
"fmt"
"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 PatchStrategicMergeTransformerPlugin struct {
ldr ifc.Loader
rf *resmap.Factory
loadedPatches []*resource.Resource
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
Patches string `json:patches,omitempty" yaml:"patches,omitempty"`
}
//noinspection GoUnusedGlobalVariable
func NewPatchStrategicMergeTransformerPlugin() *PatchStrategicMergeTransformerPlugin {
return &PatchStrategicMergeTransformerPlugin{}
}
func (p *PatchStrategicMergeTransformerPlugin) 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 len(p.Paths) == 0 && p.Patches == "" {
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
}
p.loadedPatches = res
}
if p.Patches != "" {
res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches))
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, res...)
}
if len(p.loadedPatches) == 0 {
return fmt.Errorf(
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
}
return err
}
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
patches, err := p.rf.MergePatches(p.loadedPatches)
if err != nil {
return err
}
for _, patch := range patches.Resources() {
target, err := m.GetById(patch.OrgId())
if err != nil {
return err
}
err = target.Patch(patch.Kunstructured)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,147 @@
// 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
}
}
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))
}

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestAnnotationsTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestConfigMapGenerator(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestHashTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -150,7 +150,7 @@ func (p *plugin) findContainers(obj map[string]interface{}) error {
func isImageMatched(s, t string) bool { func isImageMatched(s, t string) bool {
// Tag values are limited to [a-zA-Z0-9_.-]. // 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) return pattern.MatchString(s)
} }
@@ -176,7 +176,7 @@ func split(imageName string) (name string, tag string) {
} }
i := ic i := ic
if ic < 0 { if ia > 0 {
i = ia i = ia
} }

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestImageTagTransformerNewTag(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -36,18 +36,22 @@ metadata:
spec: spec:
template: template:
spec: spec:
initContainers:
- name: nginx2
image: my-nginx:1.8.0
- name: init-alpine
image: alpine:1.8.0
containers: containers:
- name: ngnix - image: nginx:1.7.9
image: nginx:1.7.9 name: nginx-tagged
- name: repliaced-with-digest - image: nginx:latest
image: foobar:1 name: nginx-latest
- name: postgresdb - image: foobar:1
image: postgres:1.8.0 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, ` th.AssertActualEqualsExpected(rm, `
@@ -61,14 +65,307 @@ spec:
spec: spec:
containers: containers:
- image: nginx:v2 - image: nginx:v2
name: ngnix name: nginx-tagged
- image: nginx:v2
name: nginx-latest
- image: foobar:1 - image: foobar:1
name: repliaced-with-digest name: replaced-with-digest
- image: postgres:1.8.0 - image: postgres:1.8.0
name: postgresdb name: postgresdb
initContainers: initContainers:
- image: my-nginx:1.8.0 - image: nginx:v2
name: nginx2 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 - image: alpine:1.8.0
name: init-alpine name: init-alpine
`) `)

View File

@@ -6,8 +6,8 @@ package main_test
import ( import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" kusttest_test "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 ( const (
@@ -59,7 +59,7 @@ metadata:
) )
func TestInventoryTransformerCollect(t *testing.T) { func TestInventoryTransformerCollect(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -80,7 +80,7 @@ policy: GarbageCollect
} }
func TestInventoryTransformerIgnore(t *testing.T) { func TestInventoryTransformerIgnore(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -101,7 +101,7 @@ policy: GarbageIgnore
} }
func TestInventoryTransformerDefaultPolicy(t *testing.T) { func TestInventoryTransformerDefaultPolicy(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestLabelTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestLegacyOrderTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -53,6 +53,7 @@ func (p *plugin) Transform(m resmap.ResMap) error {
} }
} }
p.updateClusterRoleBinding(m) p.updateClusterRoleBinding(m)
p.updateServiceReference(m)
return nil return nil
} }
@@ -126,3 +127,40 @@ func (p *plugin) updateClusterRoleBinding(m resmap.ResMap) {
objMap["subjects"] = subjects objMap["subjects"] = subjects
} }
} }
func (p *plugin) updateServiceReference(m resmap.ResMap) {
svc := gvk.Gvk{Version: "v1", Kind: "Service"}
svcMap := map[string]bool{}
for _, id := range m.AllIds() {
if id.Gvk.Equals(svc) {
svcMap[id.Name] = true
}
}
for _, res := range m.Resources() {
if res.OrgId().Kind != "ValidatingWebhookConfiguration" &&
res.OrgId().Kind != "MutatingWebhookConfiguration" {
continue
}
objMap := res.Map()
webhooks, ok := objMap["webhooks"].([]interface{})
if webhooks == nil || !ok {
continue
}
for i := range webhooks {
webhook := webhooks[i].(map[string]interface{})
transformers.MutateField(
webhook, []string{"clientConfig", "service"},
false, func(obj interface{}) (interface{}, error) {
svc := obj.(map[string]interface{})
svcName, foundN := svc["name"]
if foundN && svcMap[svcName.(string)] {
svc["namespace"] = p.Namespace
}
return svc, nil
})
webhooks[i] = webhook
}
objMap["webhooks"] = webhooks
}
}

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestNamespaceTransformer1(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -41,6 +41,11 @@ metadata:
namespace: foo namespace: foo
--- ---
apiVersion: v1 apiVersion: v1
kind: Service
metadata:
name: svc1
---
apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: ns1 name: ns1
@@ -72,6 +77,22 @@ subjects:
name: another name: another
namespace: random namespace: random
--- ---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: example
webhooks:
- name: example1
clientConfig:
service:
name: svc1
namespace: system
- name: example2
clientConfig:
service:
name: svc2
namespace: system
---
apiVersion: apiextensions.k8s.io/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
@@ -92,6 +113,12 @@ metadata:
namespace: test namespace: test
--- ---
apiVersion: v1 apiVersion: v1
kind: Service
metadata:
name: svc1
namespace: test
---
apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: ns1 name: ns1
@@ -123,6 +150,22 @@ subjects:
name: another name: another
namespace: random namespace: random
--- ---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: example
webhooks:
- clientConfig:
service:
name: svc1
namespace: test
name: example1
- clientConfig:
service:
name: svc2
namespace: system
name: example2
---
apiVersion: apiextensions.k8s.io/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
@@ -131,7 +174,7 @@ metadata:
} }
func TestNamespaceTransformerClusterLevelKinds(t *testing.T) { func TestNamespaceTransformerClusterLevelKinds(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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 = ` const target = `
@@ -29,7 +29,7 @@ spec:
` `
func TestPatchJson6902TransformerMissingFile(t *testing.T) { func TestPatchJson6902TransformerMissingFile(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -58,7 +58,7 @@ path: jsonpatch.json
} }
func TestBadPatchJson6902Transformer(t *testing.T) { func TestBadPatchJson6902Transformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -87,7 +87,7 @@ jsonOp: 'thisIsNotAPatch'
} }
func TestBothEmptyJson6902Transformer(t *testing.T) { func TestBothEmptyJson6902Transformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -115,7 +115,7 @@ target:
} }
func TestBothSpecifiedJson6902Transformer(t *testing.T) { func TestBothSpecifiedJson6902Transformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -151,7 +151,7 @@ jsonOp: '[{"op": "add", "path": "/spec/template/spec/dnsPolicy", "value": "Clust
} }
func TestPatchJson6902TransformerFromJsonFile(t *testing.T) { func TestPatchJson6902TransformerFromJsonFile(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -201,7 +201,7 @@ spec:
} }
func TestPatchJson6902TransformerFromYamlFile(t *testing.T) { func TestPatchJson6902TransformerFromYamlFile(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(
@@ -251,7 +251,7 @@ spec:
} }
func TestPatchJson6902TransformerWithInline(t *testing.T) { func TestPatchJson6902TransformerWithInline(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -0,0 +1,76 @@
// 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"
"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
loadedPatches []*resource.Resource
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
Patches string `json:patches,omitempty" yaml:"patches,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 len(p.Paths) == 0 && p.Patches == "" {
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
}
p.loadedPatches = res
}
if p.Patches != "" {
res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches))
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, res...)
}
if len(p.loadedPatches) == 0 {
return fmt.Errorf(
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
}
return err
}
func (p *plugin) Transform(m resmap.ResMap) error {
patches, err := p.rf.MergePatches(p.loadedPatches)
if err != nil {
return err
}
for _, patch := range patches.Resources() {
target, err := m.GetById(patch.OrgId())
if err != nil {
return err
}
err = target.Patch(patch.Kunstructured)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,535 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package main_test
import (
"strings"
"testing"
"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
kind: Deployment
spec:
replica: 2
template:
metadata:
labels:
old-label: old-value
spec:
containers:
- name: nginx
image: nginx
`
targetWithNamespace = `
apiVersion: apps/v1
metadata:
name: myDeploy
namespace: namespace1
kind: Deployment
spec:
replica: 2
template:
metadata:
labels:
old-label: old-value
spec:
containers:
- name: nginx
image: nginx
`
targetNoschema = `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
A: X
B: Y
`
)
func TestPatchStrategicMergeTransformerMissingFile(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "PatchStrategicMergeTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- 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 TestBadPatchStrategicMergeTransformer(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "PatchStrategicMergeTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
patches: 'thisIsNotAPatch'
`, target)
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(err.Error(),
"cannot unmarshal string into Go value of type map[string]interface {}") {
t.Fatalf("unexpected err: %v", err)
}
}
func TestBothEmptyPatchStrategicMergeTransformer(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "PatchStrategicMergeTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
_, err := th.RunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
`, target)
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(err.Error(), "empty file path and empty patch content") {
t.Fatalf("unexpected err: %v", err)
}
}
func TestPatchStrategicMergeTransformerFromFiles(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
spec:
template:
metadata:
labels:
new-label: new-value
replica: 3
`)
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch.yaml
`, target)
th.AssertActualEqualsExpected(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeploy
spec:
replica: 3
template:
metadata:
labels:
new-label: new-value
old-label: old-value
spec:
containers:
- image: nginx
name: nginx
`)
}
func TestPatchStrategicMergeTransformerWithInline(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}}'
`, 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
name: nginx
`)
}
func TestPatchStrategicMergeTransformerMultiplePatches(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/patch1.yaml", `
apiVersion: apps/v1
metadata:
name: myDeploy
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx:latest
env:
- name: SOMEENV
value: BAR
`)
th.WriteF("/app/patch2.yaml", `
apiVersion: apps/v1
metadata:
name: myDeploy
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
env:
- name: ANOTHERENV
value: HELLO
- name: busybox
image: busybox
`)
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch1.yaml
- patch2.yaml
`, target)
th.AssertActualEqualsExpected(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeploy
spec:
replica: 2
template:
metadata:
labels:
old-label: old-value
spec:
containers:
- env:
- name: ANOTHERENV
value: HELLO
- name: SOMEENV
value: BAR
image: nginx:latest
name: nginx
- image: busybox
name: busybox
`)
}
func TestStrategicMergeTransformerMultiplePatchesWithConflicts(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/patch1.yaml", `
apiVersion: apps/v1
metadata:
name: myDeploy
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx:latest
env:
- name: SOMEENV
value: BAR
`)
th.WriteF("/app/patch2.yaml", `
apiVersion: apps/v1
metadata:
name: myDeploy
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9
env:
- name: ANOTHERENV
value: HELLO
- name: busybox
image: busybox
`)
err := th.ErrorFromLoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch1.yaml
- patch2.yaml
`, target)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
}
}
func TestStrategicMergeTransformerWrongNamespace(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
namespace: namespace2
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx:latest
env:
- name: SOMEENV
value: BAR
`)
err := th.ErrorFromLoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch.yaml
`, targetWithNamespace)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "failed to find unique target for patch") {
t.Fatalf("expected error to contain %q but get %v", "failed to find target for patch", err)
}
}
func TestStrategicMergeTransformerNoSchema(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: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
B:
C: Z
`)
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch.yaml
`, targetNoschema)
th.AssertActualEqualsExpected(rm, `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
A: X
C: Z
`)
}
func TestStrategicMergeTransformerNoSchemaMultiPatches(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/patch1.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
B:
C: Z
`)
th.WriteF("/app/patch2.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: Z
D: W
baz:
hello: world
`)
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch1.yaml
- patch2.yaml
`, targetNoschema)
th.AssertActualEqualsExpected(rm, `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
A: X
C: Z
D: W
baz:
hello: world
`)
}
func TestStrategicMergeTransformerNoSchemaMultiPatchesWithConflict(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/patch1.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: Z
`)
th.WriteF("/app/patch2.yaml", `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: NOT_Z
`)
err := th.ErrorFromLoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
paths:
- patch1.yaml
- patch2.yaml
`, targetNoschema)
if !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
}
}

View File

@@ -0,0 +1,148 @@
// 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
}
}
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))
}

View 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
`)
}

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestPrefixSuffixTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestReplicaCountTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestSecretGenerator(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestBashedConfigMapPlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin( tc.BuildExecPlugin(

View File

@@ -11,7 +11,7 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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. // This test requires having the helm binary on the PATH.
@@ -19,7 +19,7 @@ import (
// TODO: Download and inflate the chart, and check that // TODO: Download and inflate the chart, and check that
// in for the test. // in for the test.
func TestChartInflator(t *testing.T) { func TestChartInflator(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin( tc.BuildExecPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestDatePrefixerPlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { 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) { func TestPrintWorkDirPlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin( tc.BuildExecPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestSecretsFromDatabasePlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestSedTransformer(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin("someteam.example.com", "v1", "SedTransformer") tc.BuildExecPlugin("someteam.example.com", "v1", "SedTransformer")

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestSomeServiceGeneratorPlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -7,11 +7,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestStringPrefixerPlugin(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildGoPlugin( tc.BuildGoPlugin(

View File

@@ -10,11 +10,11 @@ import (
"testing" "testing"
"sigs.k8s.io/kustomize/v3/pkg/kusttest" "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) { func TestValidatorHappy(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin("someteam.example.com", "v1", "Validator") tc.BuildExecPlugin("someteam.example.com", "v1", "Validator")
@@ -49,7 +49,7 @@ metadata:
} }
func TestValidatorUnHappy(t *testing.T) { func TestValidatorUnHappy(t *testing.T) {
tc := plugins.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
tc.BuildExecPlugin("someteam.example.com", "v1", "Validator") tc.BuildExecPlugin("someteam.example.com", "v1", "Validator")

View File

@@ -75,14 +75,14 @@ git tag -a $version -m "Release $version"
``` ```
### trigger the cloud build ### trigger the cloud build
Push the tag:
Pushing the tag will trigger a job in [Google Cloud
Build] to put a new release on the [releases page].
``` ```
git push upstream $version git push upstream $version
``` ```
This triggers a job in [Google Cloud Build] to
put a new release on the [release page].
### Update release notes ### Update release notes
Visit the [release page] and edit the release notes as desired. Visit the [release page] and edit the release notes as desired.