mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-30 09:51:23 +00:00
Compare commits
445 Commits
release-cm
...
api/v0.6.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b736b81167 | ||
|
|
0a04b1bb78 | ||
|
|
f7ebaae39e | ||
|
|
08099f0cea | ||
|
|
6fd04dd253 | ||
|
|
9ac97ef91f | ||
|
|
cfbf426174 | ||
|
|
9aafc61c5b | ||
|
|
cd2ebd3046 | ||
|
|
b20e5d7f84 | ||
|
|
13c9a2873e | ||
|
|
fc06283905 | ||
|
|
119d7cadf5 | ||
|
|
cdc6d1fc28 | ||
|
|
49dced2e01 | ||
|
|
76a8f034cb | ||
|
|
52060ac480 | ||
|
|
719532e4df | ||
|
|
70dcc79bf4 | ||
|
|
55b4448862 | ||
|
|
bcaac6f8c1 | ||
|
|
ba4b44db6b | ||
|
|
fd280d0c0b | ||
|
|
1dbf490146 | ||
|
|
62e4df72d3 | ||
|
|
bb77a7c86d | ||
|
|
41abeb85be | ||
|
|
3c86d37148 | ||
|
|
287b38cc87 | ||
|
|
d8d727b1ca | ||
|
|
a81a3d40ce | ||
|
|
52e682489c | ||
|
|
8714ca5a58 | ||
|
|
0490ca163f | ||
|
|
b4947fe8a0 | ||
|
|
9c7b4fddf9 | ||
|
|
5d5b1c2c38 | ||
|
|
cf1aafb121 | ||
|
|
2d4e406a86 | ||
|
|
2a8edd2859 | ||
|
|
a3bc13847c | ||
|
|
944b19ff7c | ||
|
|
f75274bae7 | ||
|
|
62a8a8c57d | ||
|
|
9514f9cd3a | ||
|
|
c1c2725360 | ||
|
|
e9ff26bb1b | ||
|
|
14dc3dfb81 | ||
|
|
44619d5ca2 | ||
|
|
a458ed84f9 | ||
|
|
108f44377d | ||
|
|
dc8439fbfa | ||
|
|
f5353fafa1 | ||
|
|
3d1376bbbc | ||
|
|
b1ea25e86a | ||
|
|
495f6df973 | ||
|
|
a4f1f0841e | ||
|
|
9d0fba81f0 | ||
|
|
92826c6a1e | ||
|
|
7e04be9ec6 | ||
|
|
d954c39ef7 | ||
|
|
176ac5b4fa | ||
|
|
dd696b5cb4 | ||
|
|
501404e403 | ||
|
|
ddf94175ee | ||
|
|
232da9e12b | ||
|
|
8b9ce8eacb | ||
|
|
ee9a4f2526 | ||
|
|
006ce72b2d | ||
|
|
a80bd15bda | ||
|
|
6c63bb2727 | ||
|
|
a7ba93b1d8 | ||
|
|
4a78cd6579 | ||
|
|
b2b8c12203 | ||
|
|
8cc281fad6 | ||
|
|
7346813b8d | ||
|
|
52f3aca22d | ||
|
|
a6a061215f | ||
|
|
7464d8ac8f | ||
|
|
64fda38e2d | ||
|
|
4cefb62d41 | ||
|
|
ccca424234 | ||
|
|
ca45907af0 | ||
|
|
dcf43c7f2b | ||
|
|
1386ec3850 | ||
|
|
de8e16df15 | ||
|
|
222b2d4485 | ||
|
|
e107020bd2 | ||
|
|
430665e984 | ||
|
|
017d5673ba | ||
|
|
f346b9803e | ||
|
|
58092bf66d | ||
|
|
747323efce | ||
|
|
7428e08f93 | ||
|
|
3c8e6d7151 | ||
|
|
43bd2f4cdb | ||
|
|
1dfc9a88a8 | ||
|
|
01beba8697 | ||
|
|
b1e01b238b | ||
|
|
1f595da9ad | ||
|
|
1cf876927d | ||
|
|
a422c935d8 | ||
|
|
1971816663 | ||
|
|
3d1eab872b | ||
|
|
10c1b0c5fa | ||
|
|
4d95cd3630 | ||
|
|
0c169e96e5 | ||
|
|
5baea8400f | ||
|
|
4052cd4fd8 | ||
|
|
21ac400d49 | ||
|
|
351a4a48e4 | ||
|
|
b3cf475024 | ||
|
|
ded25075b1 | ||
|
|
05b8671d17 | ||
|
|
1bfcc81f08 | ||
|
|
3d058830b9 | ||
|
|
bdb53fca9e | ||
|
|
b7265440f8 | ||
|
|
72e1a27177 | ||
|
|
0a1fde1e41 | ||
|
|
ae4618d327 | ||
|
|
aee6ccb05c | ||
|
|
3cd2a0a2f7 | ||
|
|
c96fa7c347 | ||
|
|
ad01d8d34e | ||
|
|
b214fa7d5a | ||
|
|
68f67c183e | ||
|
|
fe5c3a291f | ||
|
|
f38cc4446b | ||
|
|
e695b0534d | ||
|
|
51ecca8f2f | ||
|
|
460c54064c | ||
|
|
50c0200429 | ||
|
|
b1b5a95466 | ||
|
|
3f71c671df | ||
|
|
2ce9c02ada | ||
|
|
3ffc13dd6e | ||
|
|
2fb8603b2a | ||
|
|
1d4b3fa36c | ||
|
|
03ea8f3615 | ||
|
|
74d0d7960e | ||
|
|
ffed4c95b3 | ||
|
|
c59b393fa4 | ||
|
|
fafe24c9df | ||
|
|
d62d8dcade | ||
|
|
ae7f984c71 | ||
|
|
7c8c827a88 | ||
|
|
ff927fd11a | ||
|
|
a4f6fee6c8 | ||
|
|
dd8edb1b01 | ||
|
|
bb42d8aa1b | ||
|
|
bb60c29672 | ||
|
|
c93274c224 | ||
|
|
f5feffbd23 | ||
|
|
e17bab7e55 | ||
|
|
bd534441ce | ||
|
|
85f79edc97 | ||
|
|
4c48a4ff83 | ||
|
|
eb6c715bc3 | ||
|
|
1320e0c3dc | ||
|
|
df0022c985 | ||
|
|
15fc341a13 | ||
|
|
dd90c41f85 | ||
|
|
3a5951563d | ||
|
|
acdfd9a920 | ||
|
|
52016b22dd | ||
|
|
11049fa0bb | ||
|
|
db6c825c05 | ||
|
|
cb7974cf45 | ||
|
|
00111846d3 | ||
|
|
eafa37810b | ||
|
|
0c0cb9aaba | ||
|
|
b51e09d5fe | ||
|
|
c8cd5e55fc | ||
|
|
103d1461a1 | ||
|
|
f02af7a48b | ||
|
|
eba9edd7a6 | ||
|
|
cf38166bd6 | ||
|
|
03498b46b8 | ||
|
|
9fa9c6c30c | ||
|
|
884e35b4c8 | ||
|
|
28787396b2 | ||
|
|
e3cf8987e1 | ||
|
|
551841b789 | ||
|
|
66740dfad6 | ||
|
|
537ff024dd | ||
|
|
cfab28a5ff | ||
|
|
471d5ccf84 | ||
|
|
6a9e75ee0d | ||
|
|
7500764cbf | ||
|
|
544fc60bfe | ||
|
|
0850eae0b9 | ||
|
|
3818cebe33 | ||
|
|
31b395a33f | ||
|
|
ce0dba9217 | ||
|
|
9abd0119e1 | ||
|
|
b6c6cfa7ac | ||
|
|
0c5fc5e694 | ||
|
|
64cbfbe56d | ||
|
|
6a94eb873f | ||
|
|
0bfec6b39b | ||
|
|
9002c338cb | ||
|
|
f86cb6479e | ||
|
|
8285af8cf1 | ||
|
|
5fa1282dcb | ||
|
|
5a0abc8b12 | ||
|
|
3f2508fa94 | ||
|
|
9d992aae68 | ||
|
|
8c906b804f | ||
|
|
4ff4940fa7 | ||
|
|
09aec5694a | ||
|
|
1f917c0499 | ||
|
|
225ffc7cd8 | ||
|
|
eb638cc312 | ||
|
|
7dfb96425e | ||
|
|
6d4c6127c8 | ||
|
|
6aa72b66ef | ||
|
|
f03fdc09cb | ||
|
|
30b6eeb460 | ||
|
|
bf67fcb6d6 | ||
|
|
4ae420cce1 | ||
|
|
87d2187436 | ||
|
|
f1dabbd4fc | ||
|
|
747e05f2a4 | ||
|
|
3514317b3d | ||
|
|
9299821571 | ||
|
|
d91f313137 | ||
|
|
161af9d99c | ||
|
|
b115c95ea1 | ||
|
|
4c75dac10a | ||
|
|
2f8a376ae4 | ||
|
|
20cd4bfef9 | ||
|
|
b314ca185f | ||
|
|
f6c06b58ef | ||
|
|
c45e05b7bd | ||
|
|
76bae738a0 | ||
|
|
98c88805c3 | ||
|
|
0770661b2a | ||
|
|
67d5871e87 | ||
|
|
f98c683915 | ||
|
|
ffe9c9d947 | ||
|
|
4d42ffc7f8 | ||
|
|
d7dc7d911e | ||
|
|
04404ff61b | ||
|
|
f864e15c68 | ||
|
|
29a444fffc | ||
|
|
327035a43a | ||
|
|
ad7fed061e | ||
|
|
cea2986574 | ||
|
|
00f0fd7109 | ||
|
|
1c6481d011 | ||
|
|
f0bc926640 | ||
|
|
11d9ff5690 | ||
|
|
6a0a909e73 | ||
|
|
bd8f0c88e5 | ||
|
|
e5809e49cb | ||
|
|
880009b648 | ||
|
|
d083c7f1d0 | ||
|
|
684ce141de | ||
|
|
5c8c7a043a | ||
|
|
0fe7f65ef2 | ||
|
|
950c1de46d | ||
|
|
fc690f14a8 | ||
|
|
a6e03e4d11 | ||
|
|
60428be5fb | ||
|
|
4d402d4875 | ||
|
|
fbddd264be | ||
|
|
d3c46d3f7c | ||
|
|
dda3984a8f | ||
|
|
f889ca8885 | ||
|
|
341bacb9a2 | ||
|
|
badc1177d9 | ||
|
|
1680cc72c0 | ||
|
|
2f89de86f8 | ||
|
|
ab4e9c718b | ||
|
|
288c03ddca | ||
|
|
5c60285f25 | ||
|
|
6df0a45368 | ||
|
|
8206987580 | ||
|
|
6189ca9798 | ||
|
|
b8c1601a93 | ||
|
|
34d610a38d | ||
|
|
8e4c8464e7 | ||
|
|
43ab7a8e71 | ||
|
|
d2f23a4b8b | ||
|
|
0dc36a4f7c | ||
|
|
678ae12115 | ||
|
|
c4d937322f | ||
|
|
a2adb835b6 | ||
|
|
01b5c4e9da | ||
|
|
5a4e2c2898 | ||
|
|
51719d8089 | ||
|
|
eb4c5dc035 | ||
|
|
e976386931 | ||
|
|
bae9986422 | ||
|
|
e7970d82a8 | ||
|
|
9bdd489c96 | ||
|
|
0f49fef5ed | ||
|
|
8d74b8c3b5 | ||
|
|
39a8798a87 | ||
|
|
980f407552 | ||
|
|
9ca8f4602d | ||
|
|
ba0f583ee5 | ||
|
|
f432f4d75e | ||
|
|
17793abacd | ||
|
|
64cd4ec1d5 | ||
|
|
fb822984e3 | ||
|
|
6d2a737c29 | ||
|
|
6e7713281e | ||
|
|
6a7bb9e33e | ||
|
|
0e9428c8b0 | ||
|
|
c838962432 | ||
|
|
548d10ef08 | ||
|
|
2db8487f02 | ||
|
|
b42f71a20f | ||
|
|
e9824aa749 | ||
|
|
92cc9fc5e1 | ||
|
|
e53b4c9884 | ||
|
|
d4503dfd1e | ||
|
|
e2973f6ecc | ||
|
|
2bf9fc816d | ||
|
|
ff55856c63 | ||
|
|
ceef219eec | ||
|
|
b21699a277 | ||
|
|
2ab85d2f63 | ||
|
|
320545884c | ||
|
|
16bbc2d67e | ||
|
|
6d860e8ace | ||
|
|
80c8a6df61 | ||
|
|
90bc96d9d8 | ||
|
|
2be59aefec | ||
|
|
91b779269f | ||
|
|
e6ea4ad260 | ||
|
|
f5cab0f6e1 | ||
|
|
e39afc9f68 | ||
|
|
3801a29d9b | ||
|
|
39cf4af638 | ||
|
|
9d65dd0786 | ||
|
|
3dced70850 | ||
|
|
4356043582 | ||
|
|
c202be0338 | ||
|
|
9359155418 | ||
|
|
ba22bbe19e | ||
|
|
3e5989ae18 | ||
|
|
fbebd990a4 | ||
|
|
257707d839 | ||
|
|
c1cd872df6 | ||
|
|
646e0b4f61 | ||
|
|
0f67692265 | ||
|
|
6a7afd8694 | ||
|
|
46194b3385 | ||
|
|
904a9dea08 | ||
|
|
30b58e90a3 | ||
|
|
5bdd8657a5 | ||
|
|
893c99da1c | ||
|
|
43980f8586 | ||
|
|
0c8e033c96 | ||
|
|
fa15242719 | ||
|
|
a2e080bf6c | ||
|
|
e91cdb5eba | ||
|
|
ef54f9be5a | ||
|
|
f051acb83c | ||
|
|
bbb046081b | ||
|
|
77b28a986f | ||
|
|
97bc34eb37 | ||
|
|
719380f523 | ||
|
|
640ae9521b | ||
|
|
1dffc7577b | ||
|
|
a0b7288329 | ||
|
|
cc5617c048 | ||
|
|
a77d7e5164 | ||
|
|
40dc90b3b1 | ||
|
|
16229095b3 | ||
|
|
1d91401772 | ||
|
|
007a5327d7 | ||
|
|
24beeb429d | ||
|
|
9b4d4c9d46 | ||
|
|
d5f868c5c7 | ||
|
|
ff3f39d84b | ||
|
|
451c5c32c9 | ||
|
|
faef5714bf | ||
|
|
7833c6edcf | ||
|
|
a1cd23c91d | ||
|
|
e39a5adc00 | ||
|
|
b6900ead22 | ||
|
|
d03cf061e8 | ||
|
|
8293f3002d | ||
|
|
edced4b3f6 | ||
|
|
501684a9c6 | ||
|
|
4e42e1a058 | ||
|
|
b450b624e8 | ||
|
|
0be4a61f64 | ||
|
|
596c39b7bc | ||
|
|
037ac3b134 | ||
|
|
0ff4e53046 | ||
|
|
660f7f9435 | ||
|
|
e6ee03e3e3 | ||
|
|
ca04c874f2 | ||
|
|
cbfef858a0 | ||
|
|
62fbfdfa21 | ||
|
|
5d72fbc6c9 | ||
|
|
bc37ec9d88 | ||
|
|
8619c9aa13 | ||
|
|
5d8722a786 | ||
|
|
0d5552fca6 | ||
|
|
8a8e35f3bb | ||
|
|
25dbe1eaa8 | ||
|
|
2289e7d2e9 | ||
|
|
eb0f484e3d | ||
|
|
d438271263 | ||
|
|
c4c8decb74 | ||
|
|
0590b225c7 | ||
|
|
45131a6d62 | ||
|
|
4dfe3c6296 | ||
|
|
881f358228 | ||
|
|
86c93b9fb7 | ||
|
|
25e30de2d6 | ||
|
|
a8160356bd | ||
|
|
32de6de313 | ||
|
|
501ec38777 | ||
|
|
1b2a966c62 | ||
|
|
bcdbb1a282 | ||
|
|
01f28e6779 | ||
|
|
3f8e3686e2 | ||
|
|
6f782ac8c3 | ||
|
|
e5bc644653 | ||
|
|
1bc9225302 | ||
|
|
99d7ad6dc9 | ||
|
|
345dbc83e3 | ||
|
|
8ddf2297e8 | ||
|
|
b407675fc0 | ||
|
|
1366e0344a | ||
|
|
b71b36a213 | ||
|
|
e5a78710aa | ||
|
|
4da40461d3 | ||
|
|
c96a4f3d73 | ||
|
|
45893b2588 | ||
|
|
2f3c89e73f | ||
|
|
eee581462c | ||
|
|
bd7d0f864b | ||
|
|
b3f147d012 | ||
|
|
43b0f2d925 | ||
|
|
efd5f414a8 | ||
|
|
9b8232533f | ||
|
|
bc5859d44b |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.github
|
||||||
|
docs
|
||||||
|
examples
|
||||||
|
functions
|
||||||
|
hack
|
||||||
|
site
|
||||||
|
travis
|
||||||
|
*.md
|
||||||
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ""
|
||||||
|
labels:
|
||||||
|
- kind/bug
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please read this page: https://kubernetes-sigs.github.io/kustomize/contributing/bugs/ before
|
||||||
|
filing a bug
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Files that can reproduce the issue**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
We cannot figure out or fix the issue if we don't know how to reproduce. Please
|
||||||
|
provide a minimum set of files that can reproduce the issue. You can paste the
|
||||||
|
file contents here or provide a link to a tarball or git repo.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
kustomization.yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
resources.yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
...
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Expected output**
|
||||||
|
|
||||||
|
<!-- What's the expected output? -->
|
||||||
|
|
||||||
|
**Actual output**
|
||||||
|
|
||||||
|
<!-- What's the actual output? -->
|
||||||
|
|
||||||
|
**Kustomize version**
|
||||||
|
|
||||||
|
<!-- Please use the latest version when it's possible. -->
|
||||||
|
|
||||||
|
**Platform**
|
||||||
|
|
||||||
|
<!-- Linux/macOS/Windows? -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
|
||||||
|
<!-- Add any other context about the problem here. -->
|
||||||
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ""
|
||||||
|
labels:
|
||||||
|
- kind/feature
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
9
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Ask a question about the kustomize
|
||||||
|
title: "[Question]"
|
||||||
|
labels: ""
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please describe your question here -->
|
||||||
63
Makefile
63
Makefile
@@ -15,7 +15,8 @@ verify-kustomize: \
|
|||||||
lint-kustomize \
|
lint-kustomize \
|
||||||
test-unit-kustomize-all \
|
test-unit-kustomize-all \
|
||||||
test-examples-kustomize-against-HEAD \
|
test-examples-kustomize-against-HEAD \
|
||||||
test-examples-kustomize-against-3.8.0
|
test-examples-kustomize-against-3.8.0 \
|
||||||
|
test-examples-kustomize-against-3.8.2
|
||||||
|
|
||||||
# The following target referenced by a file in
|
# The following target referenced by a file in
|
||||||
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
||||||
@@ -26,7 +27,8 @@ prow-presubmit-check: \
|
|||||||
test-unit-cmd-all \
|
test-unit-cmd-all \
|
||||||
test-go-mod \
|
test-go-mod \
|
||||||
test-examples-kustomize-against-HEAD \
|
test-examples-kustomize-against-HEAD \
|
||||||
test-examples-kustomize-against-3.8.0
|
test-examples-kustomize-against-3.8.0 \
|
||||||
|
test-examples-kustomize-against-3.8.2
|
||||||
|
|
||||||
.PHONY: verify-kustomize-e2e
|
.PHONY: verify-kustomize-e2e
|
||||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
verify-kustomize-e2e: test-examples-e2e-kustomize
|
||||||
@@ -45,34 +47,37 @@ $(MYGOBIN)/golangci-lint-kustomize:
|
|||||||
GO111MODULE=on go build -tags=tools -o $(MYGOBIN)/golangci-lint-kustomize github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
GO111MODULE=on go build -tags=tools -o $(MYGOBIN)/golangci-lint-kustomize github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
||||||
)
|
)
|
||||||
|
|
||||||
# Version pinned by api/go.mod
|
# Install from version specified in api/go.mod.
|
||||||
$(MYGOBIN)/mdrip:
|
$(MYGOBIN)/mdrip:
|
||||||
cd api; \
|
cd api; \
|
||||||
go install github.com/monopole/mdrip
|
go install github.com/monopole/mdrip
|
||||||
|
|
||||||
# Version pinned by api/go.mod
|
# Install from version specified in api/go.mod.
|
||||||
$(MYGOBIN)/stringer:
|
$(MYGOBIN)/stringer:
|
||||||
cd api; \
|
cd api; \
|
||||||
go install golang.org/x/tools/cmd/stringer
|
go install golang.org/x/tools/cmd/stringer
|
||||||
|
|
||||||
# Version pinned by api/go.mod
|
# Install from version specified in api/go.mod.
|
||||||
$(MYGOBIN)/goimports:
|
$(MYGOBIN)/goimports:
|
||||||
cd api; \
|
cd api; \
|
||||||
go install golang.org/x/tools/cmd/goimports
|
go install golang.org/x/tools/cmd/goimports
|
||||||
|
|
||||||
# Install resource from whatever is checked out.
|
# Build from local source.
|
||||||
$(MYGOBIN)/resource:
|
$(MYGOBIN)/gorepomod:
|
||||||
cd cmd/resource; \
|
cd cmd/gorepomod; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
# To pin pluginator, use this recipe instead:
|
# Build from local source.
|
||||||
# cd api;
|
|
||||||
# go install sigs.k8s.io/kustomize/pluginator/v2
|
|
||||||
$(MYGOBIN)/pluginator:
|
$(MYGOBIN)/pluginator:
|
||||||
cd pluginator; \
|
cd cmd/pluginator; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
# Install kustomize from whatever is checked out.
|
# Build from local source.
|
||||||
|
$(MYGOBIN)/prchecker:
|
||||||
|
cd cmd/prchecker; \
|
||||||
|
go install .
|
||||||
|
|
||||||
|
# Build from local source.
|
||||||
$(MYGOBIN)/kustomize:
|
$(MYGOBIN)/kustomize:
|
||||||
cd kustomize; \
|
cd kustomize; \
|
||||||
go install .
|
go install .
|
||||||
@@ -81,8 +86,11 @@ $(MYGOBIN)/kustomize:
|
|||||||
install-tools: \
|
install-tools: \
|
||||||
$(MYGOBIN)/goimports \
|
$(MYGOBIN)/goimports \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize \
|
$(MYGOBIN)/golangci-lint-kustomize \
|
||||||
|
$(MYGOBIN)/gh \
|
||||||
|
$(MYGOBIN)/gorepomod \
|
||||||
$(MYGOBIN)/mdrip \
|
$(MYGOBIN)/mdrip \
|
||||||
$(MYGOBIN)/pluginator \
|
$(MYGOBIN)/pluginator \
|
||||||
|
$(MYGOBIN)/prchecker \
|
||||||
$(MYGOBIN)/stringer
|
$(MYGOBIN)/stringer
|
||||||
|
|
||||||
### Begin kustomize plugin rules.
|
### Begin kustomize plugin rules.
|
||||||
@@ -185,8 +193,8 @@ lint-kustomize: install-tools $(builtinplugins)
|
|||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||||
cd kustomize; \
|
cd kustomize; \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||||
cd pluginator; \
|
cd cmd/pluginator; \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
$(MYGOBIN)/golangci-lint-kustomize -c ../../.golangci-kustomize.yml run ./...
|
||||||
|
|
||||||
# Used to add non-default compilation flags when experimenting with
|
# Used to add non-default compilation flags when experimenting with
|
||||||
# plugin-to-api compatibility checks.
|
# plugin-to-api compatibility checks.
|
||||||
@@ -245,6 +253,19 @@ test-examples-kustomize-against-3.8.0: $(MYGOBIN)/mdrip
|
|||||||
cd kustomize; go install .; \
|
cd kustomize; go install .; \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.PHONY:
|
||||||
|
test-examples-kustomize-against-3.8.2: $(MYGOBIN)/mdrip
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
tag=v3.8.2; \
|
||||||
|
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||||
|
echo "Installing kustomize $$tag."; \
|
||||||
|
GO111MODULE=on go get sigs.k8s.io/kustomize/kustomize/v3@$${tag}; \
|
||||||
|
./hack/testExamplesAgainstKustomize.sh $$tag; \
|
||||||
|
echo "Reinstalling kustomize from HEAD."; \
|
||||||
|
cd kustomize; go install .; \
|
||||||
|
)
|
||||||
|
|
||||||
# linux only.
|
# linux only.
|
||||||
# This is for testing an example plugin that
|
# This is for testing an example plugin that
|
||||||
# uses kubeval for validation.
|
# uses kubeval for validation.
|
||||||
@@ -304,6 +325,18 @@ $(MYGOBIN)/kind:
|
|||||||
rm -rf $$d; \
|
rm -rf $$d; \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# linux only.
|
||||||
|
$(MYGOBIN)/gh:
|
||||||
|
( \
|
||||||
|
set -e; \
|
||||||
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
|
tgzFile=gh_1.0.0_linux_amd64.tar.gz; \
|
||||||
|
wget https://github.com/cli/cli/releases/download/v1.0.0/$$tgzFile; \
|
||||||
|
tar -xvzf $$tgzFile; \
|
||||||
|
mv gh_1.0.0_linux_amd64/bin/gh $(MYGOBIN)/gh; \
|
||||||
|
rm -rf $$d \
|
||||||
|
)
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: kustomize-external-go-plugin-clean
|
clean: kustomize-external-go-plugin-clean
|
||||||
go clean --cache
|
go clean --cache
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ aliases:
|
|||||||
- pwittrock
|
- pwittrock
|
||||||
- mortent
|
- mortent
|
||||||
- phanimarupaka
|
- phanimarupaka
|
||||||
|
- Shell32-Natsu
|
||||||
|
|||||||
@@ -31,14 +31,15 @@ func (p *ImageTagTransformerPlugin) Config(
|
|||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
// If you're here because someone expected any field containing
|
// traverse all fields at first
|
||||||
// the string "containers" or "initContainers" to get an image
|
err := filtersutil.ApplyToJSON(imagetag.LegacyFilter{
|
||||||
// update (not just spec/template/spec/containers[], etc.) then
|
ImageTag: p.ImageTag,
|
||||||
// a code change is needed. See api/filters/imagetag/legacy
|
}, r)
|
||||||
// for the start of an implementation that won't use an
|
if err != nil {
|
||||||
// allowlist like FsSlice, and instead walks the object looking
|
return err
|
||||||
// for fields named containers or initContainers.
|
}
|
||||||
err := filtersutil.ApplyToJSON(imagetag.Filter{
|
// then use user specified field specs
|
||||||
|
err = filtersutil.ApplyToJSON(imagetag.Filter{
|
||||||
ImageTag: p.ImageTag,
|
ImageTag: p.ImageTag,
|
||||||
FsSlice: p.FieldSpecs,
|
FsSlice: p.FieldSpecs,
|
||||||
}, r)
|
}, r)
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
@@ -33,7 +31,7 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
if len(r.Map()) == 0 {
|
if r.IsEmpty() {
|
||||||
// Don't mutate empty objects?
|
// Don't mutate empty objects?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -53,74 +51,6 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special casing metadata.namespace since
|
|
||||||
// all objects have it, even "ClusterKind" objects
|
|
||||||
// that don't exist in a namespace (the Namespace
|
|
||||||
// object itself doesn't live in a namespace).
|
|
||||||
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []types.FieldSpec {
|
|
||||||
var res []types.FieldSpec
|
|
||||||
for _, fs := range p.FieldSpecs {
|
|
||||||
if id.IsSelected(&fs.Gvk) &&
|
|
||||||
(fs.Path != types.MetadataNamespacePath ||
|
|
||||||
(fs.Path == types.MetadataNamespacePath && id.IsNamespaceableKind())) {
|
|
||||||
res = append(res, fs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) changeNamespace(
|
|
||||||
_ *resource.Resource) func(in interface{}) (interface{}, error) {
|
|
||||||
return func(in interface{}) (interface{}, error) {
|
|
||||||
switch in.(type) {
|
|
||||||
case string:
|
|
||||||
// will happen when the metadata/namespace
|
|
||||||
// value is replaced
|
|
||||||
return p.Namespace, nil
|
|
||||||
case []interface{}:
|
|
||||||
l, _ := in.([]interface{})
|
|
||||||
for idx, item := range l {
|
|
||||||
switch item.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
// Will happen when mutating the subjects
|
|
||||||
// field of ClusterRoleBinding and RoleBinding
|
|
||||||
inMap, _ := item.(map[string]interface{})
|
|
||||||
if _, ok := inMap["name"]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, ok := inMap["name"].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The only case we need to force the namespace
|
|
||||||
// if for the "service account". "default" is
|
|
||||||
// kind of hardcoded here for right now.
|
|
||||||
if name != "default" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
inMap["namespace"] = p.Namespace
|
|
||||||
l[idx] = inMap
|
|
||||||
default:
|
|
||||||
// nothing to do for right now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return in, nil
|
|
||||||
case map[string]interface{}:
|
|
||||||
// Will happen if the createField=true
|
|
||||||
// when the namespace is added to the
|
|
||||||
// object
|
|
||||||
inMap := in.(map[string]interface{})
|
|
||||||
if len(inMap) == 0 {
|
|
||||||
return p.Namespace, nil
|
|
||||||
} else {
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
||||||
return &NamespaceTransformerPlugin{}
|
return &NamespaceTransformerPlugin{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
patches, err := p.h.ResmapFactory().MergePatches(p.loadedPatches)
|
patches, err := p.h.ResmapFactory().Merge(p.loadedPatches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -94,14 +94,14 @@ func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error
|
|||||||
// Some unknown error, let it through.
|
// Some unknown error, let it through.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(target.Map()) != 0 {
|
if !target.IsEmpty() {
|
||||||
return errors.Wrapf(
|
return errors.Wrapf(
|
||||||
err, "with unexpectedly non-empty object map of size %d",
|
err, "with unexpectedly non-empty object map of size %d",
|
||||||
len(target.Map()))
|
len(target.Map()))
|
||||||
}
|
}
|
||||||
// Fall through to handle deleted object.
|
// Fall through to handle deleted object.
|
||||||
}
|
}
|
||||||
if len(target.Map()) == 0 {
|
if target.IsEmpty() {
|
||||||
// This means all fields have been removed from the object.
|
// This means all fields have been removed from the object.
|
||||||
// This can happen if a patch required deletion of the
|
// This can happen if a patch required deletion of the
|
||||||
// entire resource (not just a part of it). This means
|
// entire resource (not just a part of it). This means
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
@@ -101,7 +102,31 @@ func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap, patch
|
|||||||
patchCopy.SetGvk(res.GetGvk())
|
patchCopy.SetGvk(res.GetGvk())
|
||||||
err := p.applySMPatch(res, patchCopy)
|
err := p.applySMPatch(res, patchCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// Check for an error string from UnmarshalJSON that's indicative
|
||||||
|
// of an object that's missing basic KRM fields, and thus may have been
|
||||||
|
// entirely deleted (an acceptable outcome). This error handling should
|
||||||
|
// be deleted along with use of ResMap and apimachinery functions like
|
||||||
|
// UnmarshalJSON.
|
||||||
|
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
|
||||||
|
// Some unknown error, let it through.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !res.IsEmpty() {
|
||||||
|
return errors.Wrapf(
|
||||||
|
err, "with unexpectedly non-empty object map of size %d",
|
||||||
|
len(res.Map()))
|
||||||
|
}
|
||||||
|
// Fall through to handle deleted object.
|
||||||
|
}
|
||||||
|
if res.IsEmpty() {
|
||||||
|
// This means all fields have been removed from the object.
|
||||||
|
// This can happen if a patch required deletion of the
|
||||||
|
// entire resource (not just a part of it). This means
|
||||||
|
// the overall resmap must shrink by one.
|
||||||
|
err = m.Remove(res.CurId())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -114,9 +139,15 @@ func (p *PatchTransformerPlugin) applySMPatch(resource, patch *resource.Resource
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
n, ns := resource.GetName(), resource.GetNamespace()
|
||||||
|
err = filtersutil.ApplyToJSON(patchstrategicmerge.Filter{
|
||||||
Patch: node,
|
Patch: node,
|
||||||
}, resource)
|
}, resource)
|
||||||
|
if !resource.IsEmpty() {
|
||||||
|
resource.SetName(n)
|
||||||
|
resource.SetNamespace(ns)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// transformJson6902 applies the provided json6902 patch
|
// transformJson6902 applies the provided json6902 patch
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ func (fltr Filter) filter(obj *yaml.RNode) error {
|
|||||||
// found the field -- set its value
|
// found the field -- set its value
|
||||||
return fltr.SetValue(obj)
|
return fltr.SetValue(obj)
|
||||||
}
|
}
|
||||||
|
if obj.IsTaggedNull() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
switch obj.YNode().Kind {
|
switch obj.YNode().Kind {
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
return fltr.seq(obj)
|
return fltr.seq(obj)
|
||||||
@@ -67,7 +70,7 @@ func (fltr Filter) field(obj *yaml.RNode) error {
|
|||||||
// lookup the field matching the next path element
|
// lookup the field matching the next path element
|
||||||
var lookupField yaml.Filter
|
var lookupField yaml.Filter
|
||||||
var kind yaml.Kind
|
var kind yaml.Kind
|
||||||
tag := "" // TODO: change to yaml.NodeTagEmpty
|
tag := yaml.NodeTagEmpty
|
||||||
switch {
|
switch {
|
||||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
||||||
// dont' create the field if we don't find it
|
// dont' create the field if we don't find it
|
||||||
@@ -95,9 +98,10 @@ func (fltr Filter) field(obj *yaml.RNode) error {
|
|||||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the value exists, but is null, then change it to the creation type
|
// if the value exists, but is null and kind is set,
|
||||||
|
// then change it to the creation type
|
||||||
// TODO: update yaml.LookupCreate to support this
|
// TODO: update yaml.LookupCreate to support this
|
||||||
if field.YNode().Tag == yaml.NodeTagNull {
|
if field.YNode().Tag == yaml.NodeTagNull && yaml.IsCreate(kind) {
|
||||||
field.YNode().Kind = kind
|
field.YNode().Kind = kind
|
||||||
field.YNode().Tag = tag
|
field.YNode().Tag = tag
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,7 +433,6 @@ spec:
|
|||||||
SetValue: filtersutil.SetScalar("bar"),
|
SetValue: filtersutil.SetScalar("bar"),
|
||||||
CreateKind: yaml.ScalarNode,
|
CreateKind: yaml.ScalarNode,
|
||||||
},
|
},
|
||||||
error: "obj '' at path 'spec/containers/image': expected sequence or mapping node",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "filedname with slash '/'",
|
name: "filedname with slash '/'",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package nameref
|
package nameref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||||
@@ -9,6 +11,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
kyaml_filtersutil "sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
@@ -19,6 +22,7 @@ type Filter struct {
|
|||||||
Referrer *resource.Resource
|
Referrer *resource.Resource
|
||||||
Target resid.Gvk
|
Target resid.Gvk
|
||||||
ReferralCandidates resmap.ResMap
|
ReferralCandidates resmap.ResMap
|
||||||
|
isRoleRef bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
@@ -37,6 +41,9 @@ func (f Filter) set(node *yaml.RNode) error {
|
|||||||
if yaml.IsMissingOrNull(node) {
|
if yaml.IsMissingOrNull(node) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.HasSuffix(f.FieldSpec.Path, "roleRef/name") {
|
||||||
|
f.isRoleRef = true
|
||||||
|
}
|
||||||
switch node.YNode().Kind {
|
switch node.YNode().Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
return f.setScalar(node)
|
return f.setScalar(node)
|
||||||
@@ -65,6 +72,7 @@ func (f Filter) setMapping(node *yaml.RNode) error {
|
|||||||
f.Referrer,
|
f.Referrer,
|
||||||
f.Target,
|
f.Target,
|
||||||
f.ReferralCandidates,
|
f.ReferralCandidates,
|
||||||
|
f.isRoleRef,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +83,7 @@ func (f Filter) setScalar(node *yaml.RNode) error {
|
|||||||
f.Target,
|
f.Target,
|
||||||
f.ReferralCandidates,
|
f.ReferralCandidates,
|
||||||
f.ReferralCandidates.Resources(),
|
f.ReferralCandidates.Resources(),
|
||||||
|
f.isRoleRef,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -86,6 +95,60 @@ func (f Filter) setScalar(node *yaml.RNode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
|
||||||
|
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
||||||
|
func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
||||||
|
n, err := kyaml_filtersutil.GetRNode(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if roleRef.IsNil() {
|
||||||
|
return nil, fmt.Errorf("roleRef cannot be found in %s", n.MustString())
|
||||||
|
}
|
||||||
|
apiGroup, err := roleRef.Pipe(yaml.Lookup("apiGroup"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if apiGroup.IsNil() {
|
||||||
|
return nil, fmt.Errorf("apiGroup cannot be found in roleRef %s", roleRef.MustString())
|
||||||
|
}
|
||||||
|
kind, err := roleRef.Pipe(yaml.Lookup("kind"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if kind.IsNil() {
|
||||||
|
return nil, fmt.Errorf("kind cannot be found in roleRef %s", roleRef.MustString())
|
||||||
|
}
|
||||||
|
return &resid.Gvk{
|
||||||
|
Group: apiGroup.YNode().Value,
|
||||||
|
Kind: kind.YNode().Value,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterReferralCandidates(
|
||||||
|
referrer *resource.Resource,
|
||||||
|
matches []*resource.Resource,
|
||||||
|
target resid.Gvk,
|
||||||
|
) []*resource.Resource {
|
||||||
|
var ret []*resource.Resource
|
||||||
|
for _, m := range matches {
|
||||||
|
// If target kind is not ServiceAccount, we shouldn't consider condidates which
|
||||||
|
// doesn't have same namespace.
|
||||||
|
if target.Kind != "ServiceAccount" && m.GetNamespace() != referrer.GetNamespace() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !referrer.PrefixesSuffixesEquals(m) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, m)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// selectReferral picks the referral among a subset of candidates.
|
// selectReferral picks the referral among a subset of candidates.
|
||||||
// It returns the current name and namespace of the selected candidate.
|
// It returns the current name and namespace of the selected candidate.
|
||||||
// Note that the content of the referricalCandidateSubset slice is most of the time
|
// Note that the content of the referricalCandidateSubset slice is most of the time
|
||||||
@@ -97,18 +160,36 @@ func selectReferral(
|
|||||||
referrer *resource.Resource,
|
referrer *resource.Resource,
|
||||||
target resid.Gvk,
|
target resid.Gvk,
|
||||||
referralCandidates resmap.ResMap,
|
referralCandidates resmap.ResMap,
|
||||||
referralCandidateSubset []*resource.Resource) (string, string, error) {
|
referralCandidateSubset []*resource.Resource,
|
||||||
|
isRoleRef bool) (string, string, error) {
|
||||||
|
var roleRefGvk *resid.Gvk
|
||||||
|
if isRoleRef {
|
||||||
|
var err error
|
||||||
|
roleRefGvk, err = getRoleRefGvk(referrer)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, res := range referralCandidateSubset {
|
for _, res := range referralCandidateSubset {
|
||||||
id := res.OrgId()
|
id := res.OrgId()
|
||||||
if id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
// If the we are processing a roleRef, the apiGroup and Kind in the
|
||||||
|
// roleRef are needed to be considered.
|
||||||
|
if (!isRoleRef || id.IsSelected(roleRefGvk)) &&
|
||||||
|
id.IsSelected(&target) && res.GetOriginalName() == oldName {
|
||||||
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
matches := referralCandidates.GetMatchingResourcesByOriginalId(id.Equals)
|
||||||
// If there's more than one match, there's no way
|
// If there's more than one match,
|
||||||
// to know which one to pick, so emit error.
|
// filter the matches by prefix and suffix
|
||||||
if len(matches) > 1 {
|
if len(matches) > 1 {
|
||||||
return "", "", fmt.Errorf(
|
filteredMatches := filterReferralCandidates(referrer, matches, target)
|
||||||
"multiple matches for %s:\n %v",
|
if len(filteredMatches) > 1 {
|
||||||
id, getIds(matches))
|
return "", "", fmt.Errorf(
|
||||||
|
"multiple matches for %s:\n %v",
|
||||||
|
id, getIds(filteredMatches))
|
||||||
|
}
|
||||||
|
// Check is the match the resource we are working on
|
||||||
|
if len(filteredMatches) == 0 || res != filteredMatches[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// In the resource, note that it is referenced
|
// In the resource, note that it is referenced
|
||||||
// by the referrer.
|
// by the referrer.
|
||||||
@@ -128,10 +209,11 @@ func getSimpleNameField(
|
|||||||
referrer *resource.Resource,
|
referrer *resource.Resource,
|
||||||
target resid.Gvk,
|
target resid.Gvk,
|
||||||
referralCandidates resmap.ResMap,
|
referralCandidates resmap.ResMap,
|
||||||
referralCandidateSubset []*resource.Resource) (string, error) {
|
referralCandidateSubset []*resource.Resource,
|
||||||
|
isRoleRef bool) (string, error) {
|
||||||
|
|
||||||
newName, _, err := selectReferral(oldName, referrer, target,
|
newName, _, err := selectReferral(oldName, referrer, target,
|
||||||
referralCandidates, referralCandidateSubset)
|
referralCandidates, referralCandidateSubset, isRoleRef)
|
||||||
|
|
||||||
return newName, err
|
return newName, err
|
||||||
}
|
}
|
||||||
@@ -150,7 +232,8 @@ func setNameAndNs(
|
|||||||
in *yaml.RNode,
|
in *yaml.RNode,
|
||||||
referrer *resource.Resource,
|
referrer *resource.Resource,
|
||||||
target resid.Gvk,
|
target resid.Gvk,
|
||||||
referralCandidates resmap.ResMap) error {
|
referralCandidates resmap.ResMap,
|
||||||
|
isRoleRef bool) error {
|
||||||
|
|
||||||
if in.YNode().Kind != yaml.MappingNode {
|
if in.YNode().Kind != yaml.MappingNode {
|
||||||
return fmt.Errorf("expect a mapping node")
|
return fmt.Errorf("expect a mapping node")
|
||||||
@@ -186,7 +269,7 @@ func setNameAndNs(
|
|||||||
|
|
||||||
oldName := nameNode.YNode().Value
|
oldName := nameNode.YNode().Value
|
||||||
newname, newnamespace, err := selectReferral(oldName, referrer, target,
|
newname, newnamespace, err := selectReferral(oldName, referrer, target,
|
||||||
referralCandidates, subset)
|
referralCandidates, subset, isRoleRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,44 @@ metadata:
|
|||||||
map:
|
map:
|
||||||
name: newName
|
name: newName
|
||||||
namespace: oldNs
|
namespace: oldNs
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "map"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"null value": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
map:
|
||||||
|
name: null
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: NotSecret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", ""},
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
map:
|
||||||
|
name: null
|
||||||
`,
|
`,
|
||||||
filter: Filter{
|
filter: Filter{
|
||||||
FieldSpec: types.FieldSpec{Path: "map"},
|
FieldSpec: types.FieldSpec{Path: "map"},
|
||||||
@@ -217,27 +255,6 @@ func TestNamerefFilterUnhappy(t *testing.T) {
|
|||||||
filter Filter
|
filter Filter
|
||||||
originalNames []string
|
originalNames []string
|
||||||
}{
|
}{
|
||||||
"invalid node type": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dep
|
|
||||||
ref:
|
|
||||||
name: null
|
|
||||||
`,
|
|
||||||
candidates: "",
|
|
||||||
originalNames: []string{},
|
|
||||||
expected: "obj '' at path 'ref/name': node is expected to be either a string or a slice of string or a map of string",
|
|
||||||
filter: Filter{
|
|
||||||
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
|
||||||
Target: resid.Gvk{
|
|
||||||
Group: "apps",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "Secret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"multiple match": {
|
"multiple match": {
|
||||||
input: `
|
input: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -331,3 +348,434 @@ metadata:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCandidatesWithDifferentPrefixSuffix(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input string
|
||||||
|
candidates string
|
||||||
|
expected string
|
||||||
|
filter Filter
|
||||||
|
originalNames []string
|
||||||
|
prefix []string
|
||||||
|
suffix []string
|
||||||
|
inputPrefix string
|
||||||
|
inputSuffix string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
"prefix match": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix1", "prefix2"},
|
||||||
|
suffix: []string{"", "suffix2"},
|
||||||
|
inputPrefix: "prefix1",
|
||||||
|
inputSuffix: "",
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: newName
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
"suffix match": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"", "prefix2"},
|
||||||
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
|
inputPrefix: "",
|
||||||
|
inputSuffix: "suffix1",
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: newName
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
"prefix suffix both match": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix1", "prefix2"},
|
||||||
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
|
inputPrefix: "prefix1",
|
||||||
|
inputSuffix: "suffix1",
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: newName
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
"multiple match: both": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix", "prefix"},
|
||||||
|
suffix: []string{"suffix", "suffix"},
|
||||||
|
inputPrefix: "prefix",
|
||||||
|
inputSuffix: "suffix",
|
||||||
|
expected: "",
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
"multiple match: only prefix": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix", "prefix"},
|
||||||
|
suffix: []string{"", ""},
|
||||||
|
inputPrefix: "prefix",
|
||||||
|
inputSuffix: "",
|
||||||
|
expected: "",
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
"multiple match: only suffix": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"", ""},
|
||||||
|
suffix: []string{"suffix", "suffix"},
|
||||||
|
inputPrefix: "",
|
||||||
|
inputSuffix: "suffix",
|
||||||
|
expected: "",
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
"no match: neither match": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix1", "prefix2"},
|
||||||
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
|
inputPrefix: "prefix",
|
||||||
|
inputSuffix: "suffix",
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
"no match: prefix doesn't match": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix1", "prefix2"},
|
||||||
|
suffix: []string{"suffix", "suffix"},
|
||||||
|
inputPrefix: "prefix",
|
||||||
|
inputSuffix: "suffix",
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
"no match: suffix doesn't match": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
candidates: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: newName2
|
||||||
|
`,
|
||||||
|
originalNames: []string{"oldName", "oldName"},
|
||||||
|
prefix: []string{"prefix", "prefix"},
|
||||||
|
suffix: []string{"suffix1", "suffix2"},
|
||||||
|
inputPrefix: "prefix",
|
||||||
|
inputSuffix: "suffix",
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
ref:
|
||||||
|
name: oldName
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
FieldSpec: types.FieldSpec{Path: "ref/name"},
|
||||||
|
Target: resid.Gvk{
|
||||||
|
Group: "apps",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range testCases {
|
||||||
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
factory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
||||||
|
referrer, err := factory.FromBytes([]byte(tc.input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tc.inputPrefix != "" {
|
||||||
|
referrer.AddNamePrefix(tc.inputPrefix)
|
||||||
|
}
|
||||||
|
if tc.inputSuffix != "" {
|
||||||
|
referrer.AddNameSuffix(tc.inputSuffix)
|
||||||
|
}
|
||||||
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
|
resMapFactory := resmap.NewFactory(factory, nil)
|
||||||
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
|
tc.originalNames, []byte(tc.candidates))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i := range candidatesRes {
|
||||||
|
if tc.prefix[i] != "" {
|
||||||
|
candidatesRes[i].AddNamePrefix(tc.prefix[i])
|
||||||
|
}
|
||||||
|
if tc.suffix[i] != "" {
|
||||||
|
candidatesRes[i].AddNameSuffix(tc.suffix[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||||
|
tc.filter.ReferralCandidates = candidates
|
||||||
|
|
||||||
|
if !tc.err {
|
||||||
|
if !assert.Equal(t,
|
||||||
|
strings.TrimSpace(tc.expected),
|
||||||
|
strings.TrimSpace(
|
||||||
|
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("an error is expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (ns Filter) hacks(obj *yaml.RNode) error {
|
|||||||
// metaNamespaceHack is a hack for implementing the namespace transform
|
// metaNamespaceHack is a hack for implementing the namespace transform
|
||||||
// for the metadata.namespace field on namespace scoped resources.
|
// for the metadata.namespace field on namespace scoped resources.
|
||||||
// namespace scoped resources are determined by NOT being present
|
// namespace scoped resources are determined by NOT being present
|
||||||
// in a blacklist of cluster-scoped resource types (by apiVersion and kind).
|
// in a hard-coded list of cluster-scoped resource types (by apiVersion and kind).
|
||||||
//
|
//
|
||||||
// This hack should be updated to allow individual resources to specify
|
// This hack should be updated to allow individual resources to specify
|
||||||
// if they are cluster scoped through either an annotation on the resources,
|
// if they are cluster scoped through either an annotation on the resources,
|
||||||
|
|||||||
@@ -194,47 +194,47 @@ metadata:
|
|||||||
{
|
{
|
||||||
name: "update-clusterrolebinding",
|
name: "update-clusterrolebinding",
|
||||||
input: `
|
input: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
namespace: foo
|
namespace: foo
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
namespace: foo
|
namespace: foo
|
||||||
`,
|
`,
|
||||||
expected: `
|
expected: `
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
namespace: bar
|
namespace: bar
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: default
|
- name: default
|
||||||
namespace: bar
|
namespace: bar
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
---
|
---
|
||||||
apiVersion: example.com/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
subjects:
|
subjects:
|
||||||
- name: something
|
- name: something
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ var _ kio.Filter = Filter{}
|
|||||||
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
var result []*yaml.RNode
|
var result []*yaml.RNode
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
r, err := merge2.Merge(pf.Patch, nodes[i])
|
r, err := merge2.Merge(
|
||||||
|
pf.Patch, nodes[i],
|
||||||
|
yaml.MergeOptions{
|
||||||
|
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ spec:
|
|||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
|
- name: foo0
|
||||||
- name: foo1
|
- name: foo1
|
||||||
- name: foo2
|
- name: foo2
|
||||||
- name: foo3
|
- name: foo3
|
||||||
- name: foo0
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"volumes patch": {
|
"volumes patch": {
|
||||||
@@ -107,10 +107,10 @@ spec:
|
|||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: foo0
|
||||||
- name: foo1
|
- name: foo1
|
||||||
- name: foo2
|
- name: foo2
|
||||||
- name: foo3
|
- name: foo3
|
||||||
- name: foo0
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"nested patch": {
|
"nested patch": {
|
||||||
@@ -142,6 +142,234 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
args:
|
args:
|
||||||
- def
|
- def
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"remove mapping - directive": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
`,
|
||||||
|
patch: yaml.MustParse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
$patch: delete
|
||||||
|
`),
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers: []
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"replace mapping - directive": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
`,
|
||||||
|
patch: yaml.MustParse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
$patch: replace
|
||||||
|
containers:
|
||||||
|
- name: new
|
||||||
|
`),
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: new
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"merge mapping - directive": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
`,
|
||||||
|
patch: yaml.MustParse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test1
|
||||||
|
$patch: merge
|
||||||
|
`),
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"remove list - directive": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
`,
|
||||||
|
patch: yaml.MustParse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- whatever
|
||||||
|
- $patch: delete
|
||||||
|
`),
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec: {}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"replace list - directive": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
`,
|
||||||
|
patch: yaml.MustParse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: replace
|
||||||
|
image: replace
|
||||||
|
- $patch: replace
|
||||||
|
`),
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: replace
|
||||||
|
image: replace
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"merge list - directive": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
|
`,
|
||||||
|
patch: yaml.MustParse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test2
|
||||||
|
image: test2
|
||||||
|
- $patch: merge
|
||||||
|
`),
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: myDeploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test2
|
||||||
|
image: test2
|
||||||
|
- name: test
|
||||||
|
image: test
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,26 @@ data:
|
|||||||
FieldSpec: types.FieldSpec{Path: "data/slice2"},
|
FieldSpec: types.FieldSpec{Path: "data/slice2"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"null value": {
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
data:
|
||||||
|
FOO: null`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dep
|
||||||
|
data:
|
||||||
|
FOO: null`,
|
||||||
|
filter: Filter{
|
||||||
|
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{}),
|
||||||
|
FieldSpec: types.FieldSpec{Path: "data/FOO"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
@@ -260,20 +280,6 @@ data:
|
|||||||
FieldSpec: types.FieldSpec{Path: "data"},
|
FieldSpec: types.FieldSpec{Path: "data"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"null input": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dep
|
|
||||||
data:
|
|
||||||
FOO: null`,
|
|
||||||
expectedError: "obj '' at path 'data/FOO': invalid type encountered 0",
|
|
||||||
filter: Filter{
|
|
||||||
MappingFunc: expansion2.MappingFuncFor(replacementCounts, map[string]interface{}{}),
|
|
||||||
FieldSpec: types.FieldSpec{Path: "data/FOO"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
|
|||||||
@@ -10,14 +10,13 @@ require (
|
|||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
github.com/yujunz/go-getter v1.4.1-lite
|
github.com/yujunz/go-getter v1.4.1-lite
|
||||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
|
||||||
k8s.io/api v0.17.0
|
k8s.io/api v0.17.0
|
||||||
k8s.io/apimachinery v0.17.0
|
k8s.io/apimachinery v0.17.0
|
||||||
k8s.io/client-go v0.17.0
|
k8s.io/client-go v0.17.0
|
||||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
||||||
sigs.k8s.io/kustomize/kyaml v0.5.0
|
sigs.k8s.io/kustomize/kyaml v0.9.3
|
||||||
sigs.k8s.io/yaml v1.2.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace sigs.k8s.io/kustomize/kyaml v0.5.0 => ../kyaml
|
|
||||||
|
|||||||
@@ -307,6 +307,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||||
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
@@ -526,6 +528,8 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs=
|
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs=
|
||||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
@@ -584,6 +588,8 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphD
|
|||||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
|
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
|
||||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||||
|
sigs.k8s.io/kustomize/kyaml v0.9.3 h1:kZ5HnNmmnbndSXFivrAVM6u3Ik1dwu4kbpaV8KNwy8I=
|
||||||
|
sigs.k8s.io/kustomize/kyaml v0.9.3/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw=
|
||||||
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/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||||
|
|||||||
@@ -38,35 +38,68 @@ type Loader interface {
|
|||||||
Cleanup() error
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kunstructured allows manipulation of k8s objects
|
// Kunstructured represents a Kubernetes Resource Model object.
|
||||||
// that do not have Golang structs.
|
|
||||||
type Kunstructured interface {
|
type Kunstructured interface {
|
||||||
|
// Several uses.
|
||||||
Copy() Kunstructured
|
Copy() Kunstructured
|
||||||
|
|
||||||
|
// Used by Resource.Replace, which in turn is used in many places, e.g.
|
||||||
|
// - resource.Resource.Merge
|
||||||
|
// - resWrangler.appendReplaceOrMerge (AbsorbAll)
|
||||||
|
// - api.internal.k8sdeps.transformer.patch.conflictdetector
|
||||||
GetAnnotations() map[string]string
|
GetAnnotations() map[string]string
|
||||||
GetBool(path string) (bool, error)
|
|
||||||
|
// Used by ResAccumulator and ReplacementTransformer.
|
||||||
GetFieldValue(string) (interface{}, error)
|
GetFieldValue(string) (interface{}, error)
|
||||||
GetFloat64(path string) (float64, error)
|
|
||||||
|
// Used by Resource.OrgId
|
||||||
GetGvk() resid.Gvk
|
GetGvk() resid.Gvk
|
||||||
GetInt64(path string) (int64, error)
|
|
||||||
|
// Used by resource.Factory.SliceFromBytes
|
||||||
GetKind() string
|
GetKind() string
|
||||||
|
|
||||||
|
// Used by Resource.Replace
|
||||||
GetLabels() map[string]string
|
GetLabels() map[string]string
|
||||||
GetMap(path string) (map[string]interface{}, error)
|
|
||||||
|
// Used by Resource.CurId and resource factory.
|
||||||
GetName() string
|
GetName() string
|
||||||
|
|
||||||
|
// Used by special case code in
|
||||||
|
// ResMap.SubsetThatCouldBeReferencedByResource
|
||||||
GetSlice(path string) ([]interface{}, error)
|
GetSlice(path string) ([]interface{}, error)
|
||||||
|
|
||||||
|
// GetString returns the value of a string field.
|
||||||
|
// Used by Resource.GetNamespace
|
||||||
GetString(string) (string, error)
|
GetString(string) (string, error)
|
||||||
GetStringMap(path string) (map[string]string, error)
|
|
||||||
GetStringSlice(string) ([]string, error)
|
// Several uses.
|
||||||
Map() map[string]interface{}
|
Map() map[string]interface{}
|
||||||
|
|
||||||
|
// Used by Resource.AsYAML and Resource.String
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
|
|
||||||
|
// Used by resWrangler.Select
|
||||||
MatchesAnnotationSelector(selector string) (bool, error)
|
MatchesAnnotationSelector(selector string) (bool, error)
|
||||||
|
|
||||||
|
// Used by resWrangler.Select
|
||||||
MatchesLabelSelector(selector string) (bool, error)
|
MatchesLabelSelector(selector string) (bool, error)
|
||||||
Patch(Kunstructured) error
|
|
||||||
|
// Used by Resource.Replace.
|
||||||
SetAnnotations(map[string]string)
|
SetAnnotations(map[string]string)
|
||||||
|
|
||||||
|
// Used by PatchStrategicMergeTransformer.
|
||||||
SetGvk(resid.Gvk)
|
SetGvk(resid.Gvk)
|
||||||
|
|
||||||
|
// Used by Resource.Replace and used to remove "validated by" labels.
|
||||||
SetLabels(map[string]string)
|
SetLabels(map[string]string)
|
||||||
SetMap(map[string]interface{})
|
|
||||||
|
// Used by Resource.Replace.
|
||||||
SetName(string)
|
SetName(string)
|
||||||
|
|
||||||
|
// Used by Resource.Replace.
|
||||||
SetNamespace(string)
|
SetNamespace(string)
|
||||||
|
|
||||||
|
// Needed, for now, by kyaml/filtersutil.ApplyToJSON.
|
||||||
UnmarshalJSON([]byte) error
|
UnmarshalJSON([]byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -789,9 +789,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": orgname,
|
"name": orgname,
|
||||||
},
|
},
|
||||||
"subjects": []interface{}{
|
"subjects": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
@@ -845,9 +845,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
// The following tests required a change in
|
// The following tests required a change in
|
||||||
// getNameFunc implementation in order to leverage
|
// getNameFunc implementation in order to leverage
|
||||||
@@ -937,9 +937,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": orgname,
|
"name": orgname,
|
||||||
},
|
},
|
||||||
"subjects": []interface{}{
|
"subjects": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
@@ -973,9 +973,9 @@ func TestNameReferenceNamespaceTransformation(t *testing.T) {
|
|||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
"roleRef": map[string]interface{}{
|
"roleRef": map[string]interface{}{
|
||||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
"apiGroup": "rbac.authorization.k8s.io",
|
||||||
"kind": "ClusterRole",
|
"kind": "ClusterRole",
|
||||||
"name": modifiedname,
|
"name": modifiedname,
|
||||||
},
|
},
|
||||||
// The following tests required a change in
|
// The following tests required a change in
|
||||||
// getNameFunc implementation in order to leverage
|
// getNameFunc implementation in order to leverage
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
' at path 'data/slice': invalid value type expect a string`,
|
' at path 'data/slice': invalid value type expect a string`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "var replacement panic in nil",
|
description: "var replacement in nil",
|
||||||
given: given{
|
given: given{
|
||||||
varMap: map[string]interface{}{},
|
varMap: map[string]interface{}{},
|
||||||
fs: []types.FieldSpec{
|
fs: []types.FieldSpec{
|
||||||
@@ -150,7 +150,19 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"nil": nil, // noticeably *not* a []string
|
"nil": nil, // noticeably *not* a []string
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
},
|
},
|
||||||
errMessage: `obj '' at path 'data/nil': invalid type encountered 0`,
|
expected: expected{
|
||||||
|
res: resmaptest_test.NewRmBuilder(
|
||||||
|
t, resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())).
|
||||||
|
Add(map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"nil": nil, // noticeably *not* a []string
|
||||||
|
}}).ResMap(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ overview of each component with the following sections going into more details.
|
|||||||
|
|
||||||
The overall structure is outlined in the following figure:
|
The overall structure is outlined in the following figure:
|
||||||

|
https://github.com/kubernetes-sigs/kustomize/blob/master/api/internal/crawl/pictures/token_config.png)
|
||||||
|
|
||||||
#### Crawler
|
#### Crawler
|
||||||
The leftmost component consists of a crawler with an http cache of GitHub
|
The leftmost component consists of a crawler with an http cache of GitHub
|
||||||
|
|||||||
@@ -271,6 +271,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
||||||
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
@@ -338,6 +340,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
|||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
@@ -469,6 +472,7 @@ golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDq
|
|||||||
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
@@ -523,8 +527,8 @@ k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl
|
|||||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.5.0 h1:xufpSxgpugQxtd0aN1ZsWnr3Kj0fpAi7GN4dnEs4oPg=
|
sigs.k8s.io/kustomize/kyaml v0.9.3 h1:kZ5HnNmmnbndSXFivrAVM6u3Ik1dwu4kbpaV8KNwy8I=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.5.0/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
|
sigs.k8s.io/kustomize/kyaml v0.9.3/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw=
|
||||||
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=
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if repoSpec.Ref == "" {
|
|
||||||
repoSpec.Ref = "master"
|
|
||||||
}
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
gitProgram,
|
gitProgram,
|
||||||
"clone",
|
"clone",
|
||||||
@@ -45,28 +42,30 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
|||||||
repoSpec.CloneSpec(), repoSpec.Dir.String())
|
repoSpec.CloneSpec(), repoSpec.Dir.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command(
|
if repoSpec.Ref != "" {
|
||||||
gitProgram,
|
cmd = exec.Command(
|
||||||
"fetch",
|
gitProgram,
|
||||||
"--depth=1",
|
"fetch",
|
||||||
"origin",
|
"--depth=1",
|
||||||
repoSpec.Ref)
|
"origin",
|
||||||
cmd.Dir = repoSpec.Dir.String()
|
repoSpec.Ref)
|
||||||
out, err = cmd.CombinedOutput()
|
cmd.Dir = repoSpec.Dir.String()
|
||||||
if err != nil {
|
out, err = cmd.CombinedOutput()
|
||||||
log.Printf("Error fetching ref: %s", out)
|
if err != nil {
|
||||||
return errors.Wrapf(err, "trouble fetching %s", repoSpec.Ref)
|
log.Printf("Error fetching ref: %s", out)
|
||||||
}
|
return errors.Wrapf(err, "trouble fetching %s", repoSpec.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
cmd = exec.Command(
|
cmd = exec.Command(
|
||||||
gitProgram,
|
gitProgram,
|
||||||
"checkout",
|
"checkout",
|
||||||
"FETCH_HEAD")
|
"FETCH_HEAD")
|
||||||
cmd.Dir = repoSpec.Dir.String()
|
cmd.Dir = repoSpec.Dir.String()
|
||||||
out, err = cmd.CombinedOutput()
|
out, err = cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error checking out ref: %s", out)
|
log.Printf("Error checking out ref: %s", out)
|
||||||
return errors.Wrapf(err, "trouble checking out %s", repoSpec.Ref)
|
return errors.Wrapf(err, "trouble checking out %s", repoSpec.Ref)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command(
|
cmd = exec.Command(
|
||||||
|
|||||||
@@ -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 patch
|
package merge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -20,18 +20,20 @@ import (
|
|||||||
|
|
||||||
type conflictDetector interface {
|
type conflictDetector interface {
|
||||||
hasConflict(patch1, patch2 *resource.Resource) (bool, error)
|
hasConflict(patch1, patch2 *resource.Resource) (bool, error)
|
||||||
findConflict(conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error)
|
findConflict(
|
||||||
|
conflictingPatchIdx int,
|
||||||
|
patches []*resource.Resource) (*resource.Resource, error)
|
||||||
mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error)
|
mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonMergePatch struct {
|
type jsonMergePatch struct {
|
||||||
rf *resource.Factory
|
resourceFactory *resource.Factory
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ conflictDetector = &jsonMergePatch{}
|
var _ conflictDetector = &jsonMergePatch{}
|
||||||
|
|
||||||
func newJMPConflictDetector(rf *resource.Factory) conflictDetector {
|
func newJMPConflictDetector(rf *resource.Factory) conflictDetector {
|
||||||
return &jsonMergePatch{rf: rf}
|
return &jsonMergePatch{resourceFactory: rf}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jmp *jsonMergePatch) hasConflict(
|
func (jmp *jsonMergePatch) hasConflict(
|
||||||
@@ -40,7 +42,8 @@ func (jmp *jsonMergePatch) hasConflict(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (jmp *jsonMergePatch) findConflict(
|
func (jmp *jsonMergePatch) findConflict(
|
||||||
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
|
conflictingPatchIdx int,
|
||||||
|
patches []*resource.Resource) (*resource.Resource, error) {
|
||||||
for i, patch := range patches {
|
for i, patch := range patches {
|
||||||
if i == conflictingPatchIdx {
|
if i == conflictingPatchIdx {
|
||||||
continue
|
continue
|
||||||
@@ -77,7 +80,7 @@ func (jmp *jsonMergePatch) mergePatches(
|
|||||||
}
|
}
|
||||||
mergedMap := make(map[string]interface{})
|
mergedMap := make(map[string]interface{})
|
||||||
err = json.Unmarshal(mergedBytes, &mergedMap)
|
err = json.Unmarshal(mergedBytes, &mergedMap)
|
||||||
return jmp.rf.FromMap(mergedMap), err
|
return jmp.resourceFactory.FromMap(mergedMap), err
|
||||||
}
|
}
|
||||||
|
|
||||||
type strategicMergePatch struct {
|
type strategicMergePatch struct {
|
||||||
@@ -94,13 +97,15 @@ func newSMPConflictDetector(
|
|||||||
return &strategicMergePatch{lookupPatchMeta: lookupPatchMeta, rf: rf}, err
|
return &strategicMergePatch{lookupPatchMeta: lookupPatchMeta, rf: rf}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smp *strategicMergePatch) hasConflict(p1, p2 *resource.Resource) (bool, error) {
|
func (smp *strategicMergePatch) hasConflict(
|
||||||
|
p1, p2 *resource.Resource) (bool, error) {
|
||||||
return strategicpatch.MergingMapsHaveConflicts(
|
return strategicpatch.MergingMapsHaveConflicts(
|
||||||
p1.Map(), p2.Map(), smp.lookupPatchMeta)
|
p1.Map(), p2.Map(), smp.lookupPatchMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smp *strategicMergePatch) findConflict(
|
func (smp *strategicMergePatch) findConflict(
|
||||||
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
|
conflictingPatchIdx int,
|
||||||
|
patches []*resource.Resource) (*resource.Resource, error) {
|
||||||
for i, patch := range patches {
|
for i, patch := range patches {
|
||||||
if i == conflictingPatchIdx {
|
if i == conflictingPatchIdx {
|
||||||
continue
|
continue
|
||||||
@@ -122,10 +127,12 @@ func (smp *strategicMergePatch) findConflict(
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error) {
|
func (smp *strategicMergePatch) mergePatches(
|
||||||
|
patch1, patch2 *resource.Resource) (*resource.Resource, error) {
|
||||||
if hasDeleteDirectiveMarker(patch2.Map()) {
|
if hasDeleteDirectiveMarker(patch2.Map()) {
|
||||||
if hasDeleteDirectiveMarker(patch1.Map()) {
|
if hasDeleteDirectiveMarker(patch1.Map()) {
|
||||||
return nil, fmt.Errorf("cannot merge patches both containing '$patch: delete' directives")
|
return nil, fmt.Errorf(
|
||||||
|
"cannot merge patches both containing '$patch: delete' directives")
|
||||||
}
|
}
|
||||||
patch1, patch2 = patch2, patch1
|
patch1, patch2 = patch2, patch1
|
||||||
}
|
}
|
||||||
@@ -134,10 +141,21 @@ func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource)
|
|||||||
return smp.rf.FromMap(mergeJSONMap), err
|
return smp.rf.FromMap(mergeJSONMap), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergePatches merge and index patches by OrgId.
|
type merginatorImpl struct {
|
||||||
// It errors out if there is conflict between patches.
|
rf *resource.Factory
|
||||||
func MergePatches(patches []*resource.Resource,
|
}
|
||||||
rf *resource.Factory) (resmap.ResMap, error) {
|
|
||||||
|
// NewMerginator returns a new implementation of resmap.Merginator.
|
||||||
|
func NewMerginator(rf *resource.Factory) resmap.Merginator {
|
||||||
|
return &merginatorImpl{rf: rf}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ resmap.Merginator = (*merginatorImpl)(nil)
|
||||||
|
|
||||||
|
// Merge merges the incoming resources into a new resmap.ResMap.
|
||||||
|
// Returns an error on conflict.
|
||||||
|
func (m *merginatorImpl) Merge(
|
||||||
|
patches []*resource.Resource) (resmap.ResMap, error) {
|
||||||
rc := resmap.New()
|
rc := resmap.New()
|
||||||
for ix, patch := range patches {
|
for ix, patch := range patches {
|
||||||
id := patch.OrgId()
|
id := patch.OrgId()
|
||||||
@@ -156,9 +174,9 @@ func MergePatches(patches []*resource.Resource,
|
|||||||
}
|
}
|
||||||
var cd conflictDetector
|
var cd conflictDetector
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cd = newJMPConflictDetector(rf)
|
cd = newJMPConflictDetector(m.rf)
|
||||||
} else {
|
} else {
|
||||||
cd, err = newSMPConflictDetector(versionedObj, rf)
|
cd, err = newSMPConflictDetector(versionedObj, m.rf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package transformer provides transformer factory
|
|
||||||
package transformer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/k8sdeps/transformer/patch"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FactoryImpl makes patch transformer and name hash transformer
|
|
||||||
type FactoryImpl struct{}
|
|
||||||
|
|
||||||
// NewFactoryImpl makes a new factoryImpl instance
|
|
||||||
func NewFactoryImpl() *FactoryImpl {
|
|
||||||
return &FactoryImpl{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *FactoryImpl) MergePatches(patches []*resource.Resource,
|
|
||||||
rf *resource.Factory) (
|
|
||||||
resmap.ResMap, error) {
|
|
||||||
return patch.MergePatches(patches, rf)
|
|
||||||
}
|
|
||||||
25
api/internal/merge/merginator.go
Normal file
25
api/internal/merge/merginator.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package merge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Merginator implements resmap.Merginator using kyaml libs.
|
||||||
|
type Merginator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ resmap.Merginator = (*Merginator)(nil)
|
||||||
|
|
||||||
|
func NewMerginator(_ *resource.Factory) *Merginator {
|
||||||
|
return &Merginator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge implements resmap.Merginator
|
||||||
|
func (m Merginator) Merge(
|
||||||
|
resources []*resource.Resource) (resmap.ResMap, error) {
|
||||||
|
panic("TODO(#Merginator): implement Merge")
|
||||||
|
}
|
||||||
4
api/internal/merge/merginator_test.go
Normal file
4
api/internal/merge/merginator_test.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package merge_test
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
@@ -50,7 +51,14 @@ func (p *ExecPlugin) ErrIfNotExecutable() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if f.Mode()&0111 == 0000 {
|
// In Windows, it is not possible to determine whether a
|
||||||
|
// file is executable through file mode.
|
||||||
|
// TODO: provide for setting the executable FileMode bit on Windows
|
||||||
|
// The (fs *fileStat) Mode() (m FileMode) {} function in
|
||||||
|
// https://golang.org/src/os/types_windows.go
|
||||||
|
// lacks the ability to set the FileMode executable bit in response
|
||||||
|
// to file data on Windows.
|
||||||
|
if f.Mode()&0111 == 0000 && runtime.GOOS != "windows" {
|
||||||
return fmt.Errorf("unexecutable plugin at: %s", p.path)
|
return fmt.Errorf("unexecutable plugin at: %s", p.path)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
package execplugin_test
|
package execplugin_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
||||||
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
fLdr "sigs.k8s.io/kustomize/api/loader"
|
fLdr "sigs.k8s.io/kustomize/api/loader"
|
||||||
@@ -89,3 +92,32 @@ metadata:
|
|||||||
t.Fatalf("unexpected arg array: %#v", p.Args())
|
t.Fatalf("unexpected arg array: %#v", p.Args())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecPlugin_ErrIfNotExecutable(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skipf("always returns nil on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcRoot, err := utils.DeterminePluginSrcRoot(filesys.MakeFsOnDisk())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test unexecutable plugin
|
||||||
|
unexecutablePlugin := filepath.Join(
|
||||||
|
srcRoot, "builtin", "", "secretgenerator", "SecretGenerator.so")
|
||||||
|
p := NewExecPlugin(unexecutablePlugin)
|
||||||
|
err = p.ErrIfNotExecutable()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test executable plugin
|
||||||
|
executablePlugin := filepath.Join(
|
||||||
|
srcRoot, "someteam.example.com", "v1", "bashedconfigmap", "BashedConfigMap")
|
||||||
|
p = NewExecPlugin(executablePlugin)
|
||||||
|
err = p.ErrIfNotExecutable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,10 +74,10 @@ func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin {
|
|||||||
runFns: runfn.RunFns{
|
runFns: runfn.RunFns{
|
||||||
Functions: []*yaml.RNode{},
|
Functions: []*yaml.RNode{},
|
||||||
Network: o.Network,
|
Network: o.Network,
|
||||||
NetworkName: o.NetworkName,
|
|
||||||
EnableStarlark: o.EnableStar,
|
EnableStarlark: o.EnableStar,
|
||||||
EnableExec: o.EnableExec,
|
EnableExec: o.EnableExec,
|
||||||
StorageMounts: toStorageMounts(o.Mounts),
|
StorageMounts: toStorageMounts(o.Mounts),
|
||||||
|
Env: o.Env,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,11 +173,11 @@ func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) {
|
|||||||
// TODO(donnyxia): This is actually not used by generator and only used to bypass a kio limitation.
|
// TODO(donnyxia): This is actually not used by generator and only used to bypass a kio limitation.
|
||||||
// Need better solution.
|
// Need better solution.
|
||||||
if input == nil {
|
if input == nil {
|
||||||
yaml, err := functionConfig.String()
|
yml, err := functionConfig.String()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
input = []byte(yaml)
|
input = []byte(yml)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure and Execute Fn. We don't need to convert resources to ResourceList here
|
// Configure and Execute Fn. We don't need to convert resources to ResourceList here
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ func TestLoader(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, behavior := range []types.BuiltinPluginLoadingOptions{
|
for _, behavior := range []types.BuiltinPluginLoadingOptions{
|
||||||
types.BploUseStaticallyLinked,
|
/* types.BploUseStaticallyLinked,
|
||||||
types.BploLoadFromFileSys} {
|
types.BploLoadFromFileSys */} {
|
||||||
c, err := konfig.EnabledPluginConfig(behavior)
|
c, err := konfig.EnabledPluginConfig(behavior)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
"sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/transform"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
@@ -28,7 +27,6 @@ type KustTarget struct {
|
|||||||
ldr ifc.Loader
|
ldr ifc.Loader
|
||||||
validator ifc.Validator
|
validator ifc.Validator
|
||||||
rFactory *resmap.Factory
|
rFactory *resmap.Factory
|
||||||
tFactory resmap.PatchFactory
|
|
||||||
pLdr *loader.Loader
|
pLdr *loader.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,13 +35,11 @@ func NewKustTarget(
|
|||||||
ldr ifc.Loader,
|
ldr ifc.Loader,
|
||||||
validator ifc.Validator,
|
validator ifc.Validator,
|
||||||
rFactory *resmap.Factory,
|
rFactory *resmap.Factory,
|
||||||
tFactory resmap.PatchFactory,
|
|
||||||
pLdr *loader.Loader) *KustTarget {
|
pLdr *loader.Loader) *KustTarget {
|
||||||
return &KustTarget{
|
return &KustTarget{
|
||||||
ldr: ldr,
|
ldr: ldr,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
rFactory: rFactory,
|
rFactory: rFactory,
|
||||||
tFactory: tFactory,
|
|
||||||
pLdr: pLdr,
|
pLdr: pLdr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,8 +254,7 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r = append(r, lts...)
|
r = append(r, lts...)
|
||||||
t := transform.NewMultiTransformer(r)
|
return ra.Transform(newMultiTransformer(r))
|
||||||
return ra.Transform(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]resmap.Transformer, error) {
|
func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]resmap.Transformer, error) {
|
||||||
@@ -294,7 +289,6 @@ func (kt *KustTarget) runValidators(ra *accumulator.ResAccumulator) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (kt *KustTarget) removeValidatedByLabel(rm resmap.ResMap) {
|
func (kt *KustTarget) removeValidatedByLabel(rm resmap.ResMap) {
|
||||||
|
|
||||||
resources := rm.Resources()
|
resources := rm.Resources()
|
||||||
for _, r := range resources {
|
for _, r := range resources {
|
||||||
labels := r.GetLabels()
|
labels := r.GetLabels()
|
||||||
@@ -353,8 +347,7 @@ func (kt *KustTarget) accumulateComponents(
|
|||||||
func (kt *KustTarget) accumulateDirectory(
|
func (kt *KustTarget) accumulateDirectory(
|
||||||
ra *accumulator.ResAccumulator, ldr ifc.Loader, isComponent bool) (*accumulator.ResAccumulator, error) {
|
ra *accumulator.ResAccumulator, ldr ifc.Loader, isComponent bool) (*accumulator.ResAccumulator, error) {
|
||||||
defer ldr.Cleanup()
|
defer ldr.Cleanup()
|
||||||
subKt := NewKustTarget(
|
subKt := NewKustTarget(ldr, kt.validator, kt.rFactory, kt.pLdr)
|
||||||
ldr, kt.validator, kt.rFactory, kt.tFactory, kt.pLdr)
|
|
||||||
err := subKt.Load()
|
err := subKt.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(
|
return nil, errors.Wrapf(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/internal/k8sdeps/transformer"
|
"sigs.k8s.io/kustomize/api/internal/k8sdeps/merge"
|
||||||
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/internal/target"
|
"sigs.k8s.io/kustomize/api/internal/target"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||||
@@ -35,17 +35,17 @@ func makeKustTargetWithRf(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
fSys filesys.FileSystem,
|
fSys filesys.FileSystem,
|
||||||
root string,
|
root string,
|
||||||
resFact *resource.Factory) *target.KustTarget {
|
resourceFactory *resource.Factory) *target.KustTarget {
|
||||||
rf := resmap.NewFactory(resFact, transformer.NewFactoryImpl())
|
|
||||||
pc := konfig.DisabledPluginConfig()
|
|
||||||
ldr, err := fLdr.NewLoader(fLdr.RestrictionRootOnly, root, fSys)
|
ldr, err := fLdr.NewLoader(fLdr.RestrictionRootOnly, root, fSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
rf := resmap.NewFactory(
|
||||||
|
resourceFactory, merge.NewMerginator(resourceFactory))
|
||||||
|
pc := konfig.DisabledPluginConfig()
|
||||||
return target.NewKustTarget(
|
return target.NewKustTarget(
|
||||||
ldr,
|
ldr,
|
||||||
valtest_test.MakeFakeValidator(),
|
valtest_test.MakeFakeValidator(),
|
||||||
rf,
|
rf,
|
||||||
transformer.NewFactoryImpl(),
|
|
||||||
pLdr.NewLoader(pc, rf))
|
pLdr.NewLoader(pc, rf))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 transform
|
package target
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -17,8 +17,8 @@ type multiTransformer struct {
|
|||||||
|
|
||||||
var _ resmap.Transformer = &multiTransformer{}
|
var _ resmap.Transformer = &multiTransformer{}
|
||||||
|
|
||||||
// NewMultiTransformer constructs a multiTransformer.
|
// newMultiTransformer constructs a multiTransformer.
|
||||||
func NewMultiTransformer(t []resmap.Transformer) resmap.Transformer {
|
func newMultiTransformer(t []resmap.Transformer) resmap.Transformer {
|
||||||
r := &multiTransformer{
|
r := &multiTransformer{
|
||||||
transformers: make([]resmap.Transformer, len(t)),
|
transformers: make([]resmap.Transformer, len(t)),
|
||||||
checkConflictEnabled: false}
|
checkConflictEnabled: false}
|
||||||
@@ -26,13 +26,15 @@ func NewMultiTransformer(t []resmap.Transformer) resmap.Transformer {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform prepends the name prefix.
|
// Transform applies the member transformers in order to the resources,
|
||||||
|
// optionally detecting and erroring on commutation conflict.
|
||||||
func (o *multiTransformer) Transform(m resmap.ResMap) error {
|
func (o *multiTransformer) Transform(m resmap.ResMap) error {
|
||||||
if o.checkConflictEnabled {
|
if o.checkConflictEnabled {
|
||||||
return o.transformWithCheckConflict(m)
|
return o.transformWithCheckConflict(m)
|
||||||
}
|
}
|
||||||
return o.transform(m)
|
return o.transform(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *multiTransformer) transform(m resmap.ResMap) error {
|
func (o *multiTransformer) transform(m resmap.ResMap) error {
|
||||||
for _, t := range o.transformers {
|
for _, t := range o.transformers {
|
||||||
err := t.Transform(m)
|
err := t.Transform(m)
|
||||||
@@ -21,5 +21,5 @@ import (
|
|||||||
// major version increments in pluginator with each
|
// major version increments in pluginator with each
|
||||||
// api release to allow this trick to work and not
|
// api release to allow this trick to work and not
|
||||||
// introduce cycles.
|
// introduce cycles.
|
||||||
// _ "sigs.k8s.io/kustomize/pluginator/v2"
|
// _ "sigs.k8s.io/kustomize/cmd/pluginator/v2"
|
||||||
)
|
)
|
||||||
|
|||||||
64
api/internal/validate/fieldvalidator.go
Normal file
64
api/internal/validate/fieldvalidator.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FieldValidator implements ifc.Validator to check
|
||||||
|
// the values of various KRM string fields,
|
||||||
|
// e.g. labels, annotations, names, namespaces.
|
||||||
|
type FieldValidator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ifc.Validator = (*FieldValidator)(nil)
|
||||||
|
|
||||||
|
func NewFieldValidator() *FieldValidator {
|
||||||
|
return &FieldValidator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement MakeAnnotationValidator
|
||||||
|
func (f FieldValidator) MakeAnnotationValidator() func(map[string]string) error {
|
||||||
|
return func(x map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement MakeAnnotationNameValidator
|
||||||
|
func (f FieldValidator) MakeAnnotationNameValidator() func([]string) error {
|
||||||
|
return func(x []string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement MakeLabelValidator
|
||||||
|
func (f FieldValidator) MakeLabelValidator() func(map[string]string) error {
|
||||||
|
return func(x map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement MakeLabelNameValidator
|
||||||
|
func (f FieldValidator) MakeLabelNameValidator() func([]string) error {
|
||||||
|
return func(x []string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement ValidateNamespace
|
||||||
|
func (f FieldValidator) ValidateNamespace(s string) []string {
|
||||||
|
var errs []string
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement ErrIfInvalidKey
|
||||||
|
func (f FieldValidator) ErrIfInvalidKey(s string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#FieldValidator): implement IsEnvVarName
|
||||||
|
func (f FieldValidator) IsEnvVarName(k string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
4
api/internal/validate/fieldvalidator_test.go
Normal file
4
api/internal/validate/fieldvalidator_test.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package validate_test
|
||||||
41
api/internal/wrappy/factory.go
Normal file
41
api/internal/wrappy/factory.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package wrappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WNodeFactory makes instances of WNode.
|
||||||
|
// These instances in turn adapt
|
||||||
|
// sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
||||||
|
// to implement ifc.Unstructured.
|
||||||
|
// This factory is meant to implement ifc.KunstructuredFactory.
|
||||||
|
type WNodeFactory struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ifc.KunstructuredFactory = (*WNodeFactory)(nil)
|
||||||
|
|
||||||
|
func (k *WNodeFactory) SliceFromBytes(bs []byte) ([]ifc.Kunstructured, error) {
|
||||||
|
panic("TODO(#WNodeFactory): implement SliceFromBytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *WNodeFactory) FromMap(m map[string]interface{}) ifc.Kunstructured {
|
||||||
|
panic("TODO(#WNodeFactory): implement FromMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *WNodeFactory) Hasher() ifc.KunstructuredHasher {
|
||||||
|
panic("TODO(#WNodeFactory): implement Hasher")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *WNodeFactory) MakeConfigMap(
|
||||||
|
kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (ifc.Kunstructured, error) {
|
||||||
|
panic("TODO(#WNodeFactory): implement MakeConfigMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *WNodeFactory) MakeSecret(
|
||||||
|
kvLdr ifc.KvLoader, args *types.SecretArgs) (ifc.Kunstructured, error) {
|
||||||
|
panic("TODO(#WNodeFactory): implement MakeSecret")
|
||||||
|
}
|
||||||
4
api/internal/wrappy/factory_test.go
Normal file
4
api/internal/wrappy/factory_test.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package wrappy_test
|
||||||
143
api/internal/wrappy/wnode.go
Normal file
143
api/internal/wrappy/wnode.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package wrappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WNode implements ifc.Kunstructured using yaml.RNode.
|
||||||
|
//
|
||||||
|
// It exists only to help manage a switch from
|
||||||
|
// kunstruct.UnstructAdapter to yaml.RNode as the core
|
||||||
|
// representation of KRM objects in kustomize.
|
||||||
|
//
|
||||||
|
// It's got a silly name because we don't want it around for long,
|
||||||
|
// and want its use to be obvious.
|
||||||
|
type WNode struct {
|
||||||
|
node *yaml.RNode
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ifc.Kunstructured = (*WNode)(nil)
|
||||||
|
|
||||||
|
func NewWNode() *WNode {
|
||||||
|
return FromRNode(yaml.NewRNode(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromRNode(node *yaml.RNode) *WNode {
|
||||||
|
return &WNode{node: node}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wn *WNode) demandMetaData(label string) yaml.ResourceMeta {
|
||||||
|
meta, err := wn.node.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
// Log and die since interface doesn't allow error.
|
||||||
|
log.Fatalf("for %s', expected valid resource: %v", label, err)
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) Copy() ifc.Kunstructured {
|
||||||
|
return &WNode{node: wn.node.Copy()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnotations implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetAnnotations() map[string]string {
|
||||||
|
return wn.demandMetaData("GetAnnotations").Annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFieldValue implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetFieldValue(path string) (interface{}, error) {
|
||||||
|
// The argument is a json path, e.g. "metadata.name"
|
||||||
|
// fields := strings.Split(path, ".")
|
||||||
|
// return wn.node.Pipe(yaml.Lookup(fields...))
|
||||||
|
panic("TODO(#WNode): GetFieldValue; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGvk implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetGvk() resid.Gvk {
|
||||||
|
meta := wn.demandMetaData("GetGvk")
|
||||||
|
g, v := resid.ParseGroupVersion(meta.APIVersion)
|
||||||
|
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKind implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetKind() string {
|
||||||
|
return wn.demandMetaData("GetKind").Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabels implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetLabels() map[string]string {
|
||||||
|
return wn.demandMetaData("GetLabels").Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetName() string {
|
||||||
|
return wn.demandMetaData("GetName").Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSlice implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetSlice(string) ([]interface{}, error) {
|
||||||
|
panic("TODO(#WNode) GetSlice; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSlice implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) GetString(string) (string, error) {
|
||||||
|
panic("TODO(#WNode) GetString; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) Map() map[string]interface{} {
|
||||||
|
panic("TODO(#WNode) Map; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) MarshalJSON() ([]byte, error) {
|
||||||
|
return wn.node.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchesAnnotationSelector implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) MatchesAnnotationSelector(string) (bool, error) {
|
||||||
|
panic("TODO(#WNode) MatchesAnnotationSelector; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchesLabelSelector implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) MatchesLabelSelector(string) (bool, error) {
|
||||||
|
panic("TODO(#WNode) MatchesLabelSelector; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnotations implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) SetAnnotations(map[string]string) {
|
||||||
|
panic("TODO(#WNode) SetAnnotations; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGvk implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) SetGvk(resid.Gvk) {
|
||||||
|
panic("TODO(#WNode) SetGvk; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLabels implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) SetLabels(map[string]string) {
|
||||||
|
panic("TODO(#WNode) SetLabels; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetName implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) SetName(string) {
|
||||||
|
panic("TODO(#WNode) SetName; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNamespace implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) SetNamespace(string) {
|
||||||
|
panic("TODO(#WNode) SetNamespace; implement or drop from API")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements ifc.Kunstructured.
|
||||||
|
func (wn *WNode) UnmarshalJSON(data []byte) error {
|
||||||
|
return wn.node.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
339
api/internal/wrappy/wnode_test.go
Normal file
339
api/internal/wrappy/wnode_test.go
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package wrappy_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
. "sigs.k8s.io/kustomize/api/internal/wrappy"
|
||||||
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
|
||||||
|
`"metadata":{"name":"homer","namespace":"simpsons"}}`
|
||||||
|
|
||||||
|
deploymentBiggerJson = `
|
||||||
|
{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": {
|
||||||
|
"name": "homer",
|
||||||
|
"namespace": "simpsons",
|
||||||
|
"labels": {
|
||||||
|
"fruit": "apple",
|
||||||
|
"veggie": "carrot"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"area": "51",
|
||||||
|
"greeting": "Take me to your leader."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
bigMapYaml = `Kind: Service
|
||||||
|
complextree:
|
||||||
|
- field1:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.01
|
||||||
|
intsubfield: 1010
|
||||||
|
stringsubfield: idx1010
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.011
|
||||||
|
intsubfield: 1011
|
||||||
|
stringsubfield: idx1011
|
||||||
|
field2:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.02
|
||||||
|
intsubfield: 1020
|
||||||
|
stringsubfield: idx1020
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.021
|
||||||
|
intsubfield: 1021
|
||||||
|
stringsubfield: idx1021
|
||||||
|
- field1:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.11
|
||||||
|
intsubfield: 1110
|
||||||
|
stringsubfield: idx1110
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.111
|
||||||
|
intsubfield: 1111
|
||||||
|
stringsubfield: idx1111
|
||||||
|
field2:
|
||||||
|
- boolfield: true
|
||||||
|
floatsubfield: 1.112
|
||||||
|
intsubfield: 1120
|
||||||
|
stringsubfield: idx1120
|
||||||
|
- boolfield: false
|
||||||
|
floatsubfield: 1.1121
|
||||||
|
intsubfield: 1121
|
||||||
|
stringsubfield: idx1121
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: application-name
|
||||||
|
name: service-name
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
port: 80
|
||||||
|
that:
|
||||||
|
- idx0
|
||||||
|
- idx1
|
||||||
|
- idx2
|
||||||
|
- idx3
|
||||||
|
these:
|
||||||
|
- field1:
|
||||||
|
- idx010
|
||||||
|
- idx011
|
||||||
|
field2:
|
||||||
|
- idx020
|
||||||
|
- idx021
|
||||||
|
- field1:
|
||||||
|
- idx110
|
||||||
|
- idx111
|
||||||
|
field2:
|
||||||
|
- idx120
|
||||||
|
- idx121
|
||||||
|
- field1:
|
||||||
|
- idx210
|
||||||
|
- idx211
|
||||||
|
field2:
|
||||||
|
- idx220
|
||||||
|
- idx221
|
||||||
|
this:
|
||||||
|
is:
|
||||||
|
aBool: true
|
||||||
|
aFloat: 1.001
|
||||||
|
aNilValue: null
|
||||||
|
aNumber: 1000
|
||||||
|
anEmptyMap: {}
|
||||||
|
anEmptySlice: []
|
||||||
|
those:
|
||||||
|
- field1: idx0foo
|
||||||
|
field2: idx0bar
|
||||||
|
- field1: idx1foo
|
||||||
|
field2: idx1bar
|
||||||
|
- field1: idx2foo
|
||||||
|
field2: idx2bar
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeBigMap() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"Kind": "Service",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"app": "application-name",
|
||||||
|
},
|
||||||
|
"name": "service-name",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"ports": map[string]interface{}{
|
||||||
|
"port": int64(80),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"this": map[string]interface{}{
|
||||||
|
"is": map[string]interface{}{
|
||||||
|
"aNumber": int64(1000),
|
||||||
|
"aFloat": float64(1.001),
|
||||||
|
"aNilValue": nil,
|
||||||
|
"aBool": true,
|
||||||
|
"anEmptyMap": map[string]interface{}{},
|
||||||
|
"anEmptySlice": []interface{}{},
|
||||||
|
/*
|
||||||
|
TODO: test for unrecognizable (e.g. a function)
|
||||||
|
"unrecognizable": testing.InternalExample{
|
||||||
|
Name: "fooBar",
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"that": []interface{}{
|
||||||
|
"idx0",
|
||||||
|
"idx1",
|
||||||
|
"idx2",
|
||||||
|
"idx3",
|
||||||
|
},
|
||||||
|
"those": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx0foo",
|
||||||
|
"field2": "idx0bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx1foo",
|
||||||
|
"field2": "idx1bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx2foo",
|
||||||
|
"field2": "idx2bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"these": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx010", "idx011"},
|
||||||
|
"field2": []interface{}{"idx020", "idx021"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx110", "idx111"},
|
||||||
|
"field2": []interface{}{"idx120", "idx121"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx210", "idx211"},
|
||||||
|
"field2": []interface{}{"idx220", "idx221"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"complextree": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1010",
|
||||||
|
"intsubfield": int64(1010),
|
||||||
|
"floatsubfield": float64(1.010),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1011",
|
||||||
|
"intsubfield": int64(1011),
|
||||||
|
"floatsubfield": float64(1.011),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"field2": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1020",
|
||||||
|
"intsubfield": int64(1020),
|
||||||
|
"floatsubfield": float64(1.020),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1021",
|
||||||
|
"intsubfield": int64(1021),
|
||||||
|
"floatsubfield": float64(1.021),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1110",
|
||||||
|
"intsubfield": int64(1110),
|
||||||
|
"floatsubfield": float64(1.110),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1111",
|
||||||
|
"intsubfield": int64(1111),
|
||||||
|
"floatsubfield": float64(1.111),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"field2": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1120",
|
||||||
|
"intsubfield": int64(1120),
|
||||||
|
"floatsubfield": float64(1.1120),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1121",
|
||||||
|
"intsubfield": int64(1121),
|
||||||
|
"floatsubfield": float64(1.1121),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicYamlOperationFromMap(t *testing.T) {
|
||||||
|
bytes, err := yaml.Marshal(makeBigMap())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
if string(bytes) != bigMapYaml {
|
||||||
|
t.Fatalf("unexpected string equality")
|
||||||
|
}
|
||||||
|
rNode, err := kyaml.Parse(string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
||||||
|
}
|
||||||
|
rNodeString := rNode.MustString()
|
||||||
|
// The result from MustString has more indentation
|
||||||
|
// than bigMapYaml.
|
||||||
|
rNodeStrings := strings.Split(rNodeString, "\n")
|
||||||
|
bigMapStrings := strings.Split(bigMapYaml, "\n")
|
||||||
|
if len(rNodeStrings) != len(bigMapStrings) {
|
||||||
|
t.Fatalf("line count mismatch")
|
||||||
|
}
|
||||||
|
for i := range rNodeStrings {
|
||||||
|
s1 := strings.TrimSpace(rNodeStrings[i])
|
||||||
|
s2 := strings.TrimSpace(bigMapStrings[i])
|
||||||
|
if s1 != s2 {
|
||||||
|
t.Fatalf("expected '%s'=='%s'", s1, s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTripJSON(t *testing.T) {
|
||||||
|
wn := NewWNode()
|
||||||
|
err := wn.UnmarshalJSON([]byte(deploymentLittleJson))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
|
||||||
|
}
|
||||||
|
data, err := wn.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected MarshalJSON err: %v", err)
|
||||||
|
}
|
||||||
|
actual := string(data)
|
||||||
|
if actual != deploymentLittleJson {
|
||||||
|
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGettingFields(t *testing.T) {
|
||||||
|
wn := NewWNode()
|
||||||
|
err := wn.UnmarshalJSON([]byte(deploymentBiggerJson))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
||||||
|
}
|
||||||
|
gvk := wn.GetGvk()
|
||||||
|
expected := "apps"
|
||||||
|
actual := gvk.Group
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "v1"
|
||||||
|
actual = gvk.Version
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "Deployment"
|
||||||
|
actual = gvk.Kind
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
actual = wn.GetKind()
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "homer"
|
||||||
|
actual = wn.GetName()
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
actualMap := wn.GetLabels()
|
||||||
|
v, ok := actualMap["fruit"]
|
||||||
|
if !ok || v != "apple" {
|
||||||
|
t.Fatalf("unexpected labels '%v'", actualMap)
|
||||||
|
}
|
||||||
|
actualMap = wn.GetAnnotations()
|
||||||
|
v, ok = actualMap["greeting"]
|
||||||
|
if !ok || v != "Take me to your leader." {
|
||||||
|
t.Fatalf("unexpected annotations '%v'", actualMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,11 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KunstructuredFactoryImpl hides construction using apimachinery types.
|
// KunstructuredFactoryImpl makes instances of UnstructAdapter.
|
||||||
|
// These instances in turn adapt structs in
|
||||||
|
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
|
||||||
|
// to implement ifc.Kunstructured.
|
||||||
|
// This factory is meant to implement ifc.KunstructuredFactory.
|
||||||
type KunstructuredFactoryImpl struct {
|
type KunstructuredFactoryImpl struct {
|
||||||
hasher *kustHash
|
hasher *kustHash
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -466,278 +466,6 @@ func TestGetString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetInt64(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pathToField string
|
|
||||||
expectedValue int64
|
|
||||||
errorExpected bool
|
|
||||||
errorMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "numberAsValue",
|
|
||||||
pathToField: "this.is.aNumber",
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: int64(1000),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldIndexSubfield",
|
|
||||||
pathToField: "complextree[1].field2[1].intsubfield",
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: int64(1121),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "twoFieldsOneMissing",
|
|
||||||
pathToField: "metadata.banana",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'metadata.banana'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldOutOfBoundIndex",
|
|
||||||
pathToField: "these[1].field2[99]",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'these[1].field2[99]'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validDownwardAPISpecs",
|
|
||||||
pathToField: `spec.ports['port']`,
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: int64(80),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
s, err := kunstructured.GetInt64(test.pathToField)
|
|
||||||
if test.errorExpected {
|
|
||||||
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
unExpectedError(t, test.name, test.pathToField, err)
|
|
||||||
}
|
|
||||||
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFloat64(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pathToField string
|
|
||||||
expectedValue float64
|
|
||||||
errorExpected bool
|
|
||||||
errorMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "floatAsValue",
|
|
||||||
pathToField: "this.is.aFloat",
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: float64(1.001),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldIndexSubfield",
|
|
||||||
pathToField: "complextree[1].field2[1].floatsubfield",
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: float64(1.1121),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validDownwardAPIThis",
|
|
||||||
pathToField: `this.is[aFloat]`,
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: float64(1.001),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "twoFieldsOneMissing",
|
|
||||||
pathToField: "metadata.banana",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'metadata.banana'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldOutOfBoundIndex",
|
|
||||||
pathToField: "these[1].field2[99]",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "index 99 is out of bounds",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
s, err := kunstructured.GetFloat64(test.pathToField)
|
|
||||||
if test.errorExpected {
|
|
||||||
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
unExpectedError(t, test.name, test.pathToField, err)
|
|
||||||
}
|
|
||||||
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBool(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pathToField string
|
|
||||||
expectedValue bool
|
|
||||||
errorExpected bool
|
|
||||||
errorMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "boolAsValue",
|
|
||||||
pathToField: "this.is.aBool",
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldIndexSubfield",
|
|
||||||
pathToField: "complextree[1].field2[1].boolfield",
|
|
||||||
errorExpected: false,
|
|
||||||
expectedValue: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "twoFieldsOneMissing",
|
|
||||||
pathToField: "metadata.banana",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'metadata.banana'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldOutOfBoundIndex",
|
|
||||||
pathToField: "these[1].field2[99]",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'these[1].field2[99]'",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
s, err := kunstructured.GetBool(test.pathToField)
|
|
||||||
if test.errorExpected {
|
|
||||||
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
unExpectedError(t, test.name, test.pathToField, err)
|
|
||||||
}
|
|
||||||
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetStringMap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pathToField string
|
|
||||||
errorExpected bool
|
|
||||||
errorMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "validStringMap",
|
|
||||||
pathToField: "those[2]",
|
|
||||||
errorExpected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "twoFieldsOneMissing",
|
|
||||||
pathToField: "metadata.banana",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'metadata.banana'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldOutOfBoundIndex",
|
|
||||||
pathToField: "these[1].field2[99]",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'these[1].field2[99]'",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
_, err := kunstructured.GetStringMap(test.pathToField)
|
|
||||||
if test.errorExpected {
|
|
||||||
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
unExpectedError(t, test.name, test.pathToField, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetMap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pathToField string
|
|
||||||
errorExpected bool
|
|
||||||
errorMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "validMap",
|
|
||||||
pathToField: "those[2]",
|
|
||||||
errorExpected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldIndexSubfield",
|
|
||||||
pathToField: "complextree[1].field2[1]",
|
|
||||||
errorExpected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "twoFieldsOneMissing",
|
|
||||||
pathToField: "metadata.banana",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'metadata.banana'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldOutOfBoundIndex",
|
|
||||||
pathToField: "these[1].field2[99]",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'these[1].field2[99]'",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
_, err := kunstructured.GetMap(test.pathToField)
|
|
||||||
if test.errorExpected {
|
|
||||||
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
unExpectedError(t, test.name, test.pathToField, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetStringSlice(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pathToField string
|
|
||||||
errorExpected bool
|
|
||||||
errorMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "validStringSlice",
|
|
||||||
pathToField: "that",
|
|
||||||
errorExpected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "twoFieldsOneMissing",
|
|
||||||
pathToField: "metadata.banana",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'metadata.banana'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validStructSubFieldOutOfBoundIndex",
|
|
||||||
pathToField: "these[1].field2[99]",
|
|
||||||
errorExpected: true,
|
|
||||||
errorMsg: "no field named 'these[1].field2[99]'",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
_, err := kunstructured.GetStringSlice(test.pathToField)
|
|
||||||
if test.errorExpected {
|
|
||||||
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
unExpectedError(t, test.name, test.pathToField, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSlice(t *testing.T) {
|
func TestGetSlice(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -14,11 +14,14 @@ import (
|
|||||||
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KustValidator validates Labels and annotations by apimachinery
|
// KustValidator validates Labels and annotations by apimachinery
|
||||||
type KustValidator struct{}
|
type KustValidator struct{}
|
||||||
|
|
||||||
|
var _ ifc.Validator = (*KustValidator)(nil)
|
||||||
|
|
||||||
// NewKustValidator returns a KustValidator object
|
// NewKustValidator returns a KustValidator object
|
||||||
func NewKustValidator() *KustValidator {
|
func NewKustValidator() *KustValidator {
|
||||||
return &KustValidator{}
|
return &KustValidator{}
|
||||||
@@ -238,6 +238,8 @@ nameReference:
|
|||||||
kind: Ingress
|
kind: Ingress
|
||||||
- path: metadata/annotations/nginx.ingress.kubernetes.io\/auth-tls-secret
|
- path: metadata/annotations/nginx.ingress.kubernetes.io\/auth-tls-secret
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
- path: spec/tls/secretName
|
||||||
|
kind: Ingress
|
||||||
- path: imagePullSecrets/name
|
- path: imagePullSecrets/name
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
- path: parameters/secretName
|
- path: parameters/secretName
|
||||||
@@ -267,6 +269,10 @@ nameReference:
|
|||||||
kind: Ingress
|
kind: Ingress
|
||||||
- path: spec/backend/serviceName
|
- path: spec/backend/serviceName
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
- path: spec/rules/http/paths/backend/service/name
|
||||||
|
kind: Ingress
|
||||||
|
- path: spec/defaultBackend/service/name
|
||||||
|
kind: Ingress
|
||||||
- path: spec/service/name
|
- path: spec/service/name
|
||||||
kind: APIService
|
kind: APIService
|
||||||
group: apiregistration.k8s.io
|
group: apiregistration.k8s.io
|
||||||
@@ -373,5 +379,12 @@ nameReference:
|
|||||||
kind: Job
|
kind: Job
|
||||||
- path: spec/template/spec/priorityClassName
|
- path: spec/template/spec/priorityClassName
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
|
|
||||||
|
- kind: IngressClass
|
||||||
|
version: v1
|
||||||
|
group: networking.k8s.io/v1
|
||||||
|
fieldSpecs:
|
||||||
|
- path: spec/ingressClassName
|
||||||
|
kind: Ingress
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,9 +8,16 @@ const (
|
|||||||
namespace:
|
namespace:
|
||||||
- path: metadata/namespace
|
- path: metadata/namespace
|
||||||
create: true
|
create: true
|
||||||
|
- path: metadata/name
|
||||||
|
kind: Namespace
|
||||||
|
create: true
|
||||||
- path: subjects
|
- path: subjects
|
||||||
kind: RoleBinding
|
kind: RoleBinding
|
||||||
- path: subjects
|
- path: subjects
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
|
- path: spec/service/namespace
|
||||||
|
group: apiregistration.k8s.io
|
||||||
|
kind: APIService
|
||||||
|
create: true
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ varReference:
|
|||||||
- path: spec/template/spec/volumes/nfs/server
|
- path: spec/template/spec/volumes/nfs/server
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
|
||||||
|
- path: spec/template/metadata/annotations
|
||||||
|
kind: Deployment
|
||||||
|
|
||||||
- path: spec/rules/host
|
- path: spec/rules/host
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
|
||||||
|
|||||||
@@ -225,13 +225,13 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- env:
|
- env:
|
||||||
- name: foo
|
|
||||||
value: bar
|
|
||||||
- name: FOO
|
- name: FOO
|
||||||
valueFrom:
|
valueFrom:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
key: somekey
|
key: somekey
|
||||||
name: test-infra-app-env-8h5mh7f7ch
|
name: test-infra-app-env-8h5mh7f7ch
|
||||||
|
- name: foo
|
||||||
|
value: bar
|
||||||
image: nginx:1.8.0
|
image: nginx:1.8.0
|
||||||
name: nginx
|
name: nginx
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -464,3 +464,50 @@ metadata:
|
|||||||
name: another-namespace
|
name: another-namespace
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFnContainerEnvVars(t *testing.T) {
|
||||||
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
|
th := kusttest_test.MakeEnhancedHarness(t)
|
||||||
|
defer th.Reset()
|
||||||
|
|
||||||
|
th.WriteK("/app", `
|
||||||
|
generators:
|
||||||
|
- gener.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
// TODO: cheange image to gcr.io/kpt-functions/templater:stable
|
||||||
|
// when https://github.com/GoogleContainerTools/kpt-functions-catalog/pull/103
|
||||||
|
// is merged
|
||||||
|
th.WriteF("/app/gener.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: demo
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container:
|
||||||
|
image: quay.io/aodinokov/kpt-templater:0.0.1
|
||||||
|
envs:
|
||||||
|
- TESTTEMPLATE=value
|
||||||
|
data:
|
||||||
|
template: |
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: env
|
||||||
|
data:
|
||||||
|
value: '{{ env "TESTTEMPLATE" }}'
|
||||||
|
`)
|
||||||
|
m := th.Run("/app", th.MakeOptionsPluginsEnabled())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
value: value
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/path: configmap_env.yaml
|
||||||
|
name: env
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -385,12 +385,12 @@ spec:
|
|||||||
- gcePersistentDisk:
|
- gcePersistentDisk:
|
||||||
pdName: nginx-persistent-storage
|
pdName: nginx-persistent-storage
|
||||||
name: nginx-persistent-storage
|
name: nginx-persistent-storage
|
||||||
- configMap:
|
|
||||||
name: staging-team-foo-configmap-in-base-hc6g9dk6g9
|
|
||||||
name: configmap-in-base
|
|
||||||
- configMap:
|
- configMap:
|
||||||
name: staging-configmap-in-overlay-dc6fm46dhm
|
name: staging-configmap-in-overlay-dc6fm46dhm
|
||||||
name: configmap-in-overlay
|
name: configmap-in-overlay
|
||||||
|
- configMap:
|
||||||
|
name: staging-team-foo-configmap-in-base-hc6g9dk6g9
|
||||||
|
name: configmap-in-base
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
183
api/krusty/internal/provider/depprovider.go
Normal file
183
api/krusty/internal/provider/depprovider.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/k8sdeps/merge"
|
||||||
|
kmerge "sigs.k8s.io/kustomize/api/internal/merge"
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/validate"
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
||||||
|
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||||
|
"sigs.k8s.io/kustomize/api/k8sdeps/validator"
|
||||||
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DepProvider is a dependency provider.
|
||||||
|
//
|
||||||
|
// The instances it returns are either
|
||||||
|
// - old implementations backed by k8sdeps code,
|
||||||
|
// - new implementations backed by kyaml code.
|
||||||
|
//
|
||||||
|
// History:
|
||||||
|
//
|
||||||
|
// kubectl depends on k8s.io code, and at the time of writing, so
|
||||||
|
// does kustomize. Code that imports k8s.io/api* cannot be imported
|
||||||
|
// back into k8s.io/*, yet kustomize appears inside k8s.io/kubectl.
|
||||||
|
//
|
||||||
|
// To allow kustomize to appear inside kubectl, yet still be developed
|
||||||
|
// outside kubectl, the kustomize code was divided into the following
|
||||||
|
// packages
|
||||||
|
//
|
||||||
|
// api/
|
||||||
|
// k8sdeps/ (and internal/ks8deps/)
|
||||||
|
// ifc/
|
||||||
|
// krusty/
|
||||||
|
// everythingElse/
|
||||||
|
//
|
||||||
|
// with the following rules:
|
||||||
|
//
|
||||||
|
// - Only k8sdeps/ may import k8s.io/api*.
|
||||||
|
//
|
||||||
|
// - Only krusty/ (and its internals) may import k8sdeps/.
|
||||||
|
// I.e., ifc/ and everythingElse/ must not
|
||||||
|
// import k8sdeps/ or k8s.io/api*.
|
||||||
|
//
|
||||||
|
// - Code in krusty/ may use code in k8sdeps/ to create
|
||||||
|
// objects then inject said objects into
|
||||||
|
// everythingElse/ behind dependency neutral interfaces.
|
||||||
|
//
|
||||||
|
// The idea was to periodically copy, not import, the large k8sdeps/
|
||||||
|
// tree (plus a snippet from krusty/kustomizer.go) into the kubectl
|
||||||
|
// codebase via a large PR, and have kubectl depend on the rest via
|
||||||
|
// normal importing.
|
||||||
|
//
|
||||||
|
// Over 2019, however, kubectl underwent large changes including
|
||||||
|
// a switch to Go modules, and a concerted attempt to extract kubectl
|
||||||
|
// from the k8s repo. This made large kustomize integration PRs too
|
||||||
|
// intrusive to review.
|
||||||
|
//
|
||||||
|
// In 2020, kubectl is based on Go modules, and almost entirely
|
||||||
|
// extracted from the k8s.io repositories, and further the kyaml
|
||||||
|
// library has a appeared as a viable replacement to k8s.io/api*
|
||||||
|
// KRM manipulation code.
|
||||||
|
//
|
||||||
|
// The new plan is to eliminate k8sdeps/ entirely, along with its
|
||||||
|
// k8s.io/api* dependence, allowing kustomize code to be imported
|
||||||
|
// into kubectl via normal Go module imports. Then the kustomize API
|
||||||
|
// code can then move into the github.com/kubernetes-sigs/cli-utils
|
||||||
|
// repo. The kustomize CLI in github.com/kubernetes-sigs/kustomize
|
||||||
|
// and the kubectl CLI can then both depend on the kustomize API.
|
||||||
|
//
|
||||||
|
// So, all code that depends on k8sdeps must go behind interfaces,
|
||||||
|
// and kustomize must be factored to choose the implementation.
|
||||||
|
//
|
||||||
|
// That problem has been reduced to three interfaces, each having
|
||||||
|
// two implementations. (1) is k8sdeps-based, (2) is kyaml-based.
|
||||||
|
//
|
||||||
|
// - ifc.Kunstructured
|
||||||
|
//
|
||||||
|
// 1) api/k8sdeps/kunstruct.UnstructAdapter
|
||||||
|
//
|
||||||
|
// This adapts structs in
|
||||||
|
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
|
||||||
|
// to ifc.Kunstructured.
|
||||||
|
//
|
||||||
|
// 2) api/wrappy.WNode
|
||||||
|
//
|
||||||
|
// This adapts sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
||||||
|
// to ifc.Unstructured.
|
||||||
|
//
|
||||||
|
// At time of writing, implementation started.
|
||||||
|
// Further reducing the size of ifc.Kunstructed
|
||||||
|
// would really reduce the work
|
||||||
|
// (e.g. drop Vars, drop ReplacementTranformer).
|
||||||
|
//
|
||||||
|
// - resmap.Merginator
|
||||||
|
//
|
||||||
|
// 1) api/internal/k8sdeps/merge.Merginator
|
||||||
|
//
|
||||||
|
// Uses k8s.io/apimachinery/pkg/util/strategicpatch,
|
||||||
|
// apimachinery/pkg/util/mergepatch, etc. to merge
|
||||||
|
// resource.Resource instances.
|
||||||
|
//
|
||||||
|
// 2) api/internal/merge.Merginator
|
||||||
|
//
|
||||||
|
// At time of writing, this is unimplemented.
|
||||||
|
//
|
||||||
|
// - ifc.Validator
|
||||||
|
//
|
||||||
|
// 1) api/k8sdeps/validator.KustValidator
|
||||||
|
//
|
||||||
|
// Uses k8s.io/apimachinery/pkg/api/validation and
|
||||||
|
// friends to validate strings.
|
||||||
|
//
|
||||||
|
// 2) api/internal/validate.FieldValidator
|
||||||
|
//
|
||||||
|
// At time of writing, this is a do-nothing
|
||||||
|
// validator as it's not critical to kustomize function.
|
||||||
|
//
|
||||||
|
// Proposed plan:
|
||||||
|
// [ ] Ship kustomize with the ability to switch from 1 to 2 via
|
||||||
|
// an --enable_kyaml flag.
|
||||||
|
// [ ] Make --enable_kyaml true by default.
|
||||||
|
// [ ] When 2 is not noticeably more buggy than 1, delete 1.
|
||||||
|
// I.e. delete k8sdeps/, transitively deleting all k8s.io/api* deps.
|
||||||
|
// This DepProvider should be left in place to retain these
|
||||||
|
// comments, but it will have only one choice.
|
||||||
|
// [ ] The way is now clear to reintegrate into kubectl.
|
||||||
|
// This should be done ASAP; the last step is cleanup.
|
||||||
|
// [ ] With only one impl of Kunstructure remaining, that interface
|
||||||
|
// and WNode can be deleted, along with this DepProvider.
|
||||||
|
// The other two interfaces could be dropped too.
|
||||||
|
//
|
||||||
|
// When the above is done, kustomize will use yaml.RNode and/or
|
||||||
|
// KRM Config Functions directly and exclusively.
|
||||||
|
// If you're reading this, plan not done.
|
||||||
|
//
|
||||||
|
type DepProvider struct {
|
||||||
|
resourceFactory *resource.Factory
|
||||||
|
merginator resmap.Merginator
|
||||||
|
fieldValidator ifc.Validator
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeK8sdepBasedInstances() *DepProvider {
|
||||||
|
kf := kunstruct.NewKunstructuredFactoryImpl()
|
||||||
|
rf := resource.NewFactory(kf)
|
||||||
|
return &DepProvider{
|
||||||
|
resourceFactory: rf,
|
||||||
|
merginator: merge.NewMerginator(rf),
|
||||||
|
fieldValidator: validator.NewKustValidator(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeKyamlBasedInstances() *DepProvider {
|
||||||
|
kf := &wrappy.WNodeFactory{}
|
||||||
|
rf := resource.NewFactory(kf)
|
||||||
|
return &DepProvider{
|
||||||
|
resourceFactory: rf,
|
||||||
|
merginator: kmerge.NewMerginator(rf),
|
||||||
|
fieldValidator: validate.NewFieldValidator(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDepProvider(useKyaml bool) *DepProvider {
|
||||||
|
if useKyaml {
|
||||||
|
return makeKyamlBasedInstances()
|
||||||
|
}
|
||||||
|
return makeK8sdepBasedInstances()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
|
||||||
|
return dp.resourceFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DepProvider) GetMerginator() resmap.Merginator {
|
||||||
|
return dp.merginator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DepProvider) GetFieldValidator() ifc.Validator {
|
||||||
|
return dp.fieldValidator
|
||||||
|
}
|
||||||
106
api/krusty/issue2896_test.go
Normal file
106
api/krusty/issue2896_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeIssueBase(th kusttest_test.Harness) {
|
||||||
|
th.WriteK("base", `
|
||||||
|
nameSuffix: -test-api
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- deploy.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("base/deploy.yaml", `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: example
|
||||||
|
image: example:1.0
|
||||||
|
volumeMounts:
|
||||||
|
- name: conf
|
||||||
|
mountPath: /etc/config
|
||||||
|
volumes:
|
||||||
|
- name: conf
|
||||||
|
configMap:
|
||||||
|
name: conf
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue2896Base(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
writeIssueBase(th)
|
||||||
|
m := th.Run("base", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: example-test-api
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: example:1.0
|
||||||
|
name: example
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc/config
|
||||||
|
name: conf
|
||||||
|
volumes:
|
||||||
|
- configMap:
|
||||||
|
name: conf
|
||||||
|
name: conf
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue2896Overlay(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
writeIssueBase(th)
|
||||||
|
th.WriteK("overlay", `
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- patch: |-
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: example
|
||||||
|
image: example:2.0
|
||||||
|
`)
|
||||||
|
|
||||||
|
m := th.Run("overlay", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: example-test-api
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: example:2.0
|
||||||
|
name: example
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc/config
|
||||||
|
name: conf
|
||||||
|
volumes:
|
||||||
|
- configMap:
|
||||||
|
name: conf
|
||||||
|
name: conf
|
||||||
|
`)
|
||||||
|
}
|
||||||
57
api/krusty/kustomizationmetadata_test.go
Normal file
57
api/krusty/kustomizationmetadata_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKustomizationMetadata(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteF("/app/resources.yaml", `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: testing123
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector: null
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: event
|
||||||
|
image: testing123
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
imagePullSecrets: []`)
|
||||||
|
|
||||||
|
th.WriteK("/app", `
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/local-config: "true"
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
name: test_kustomization
|
||||||
|
resources:
|
||||||
|
- resources.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: testing123
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector: null
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: testing123
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
name: event
|
||||||
|
imagePullSecrets: []
|
||||||
|
`)
|
||||||
|
}
|
||||||
@@ -8,16 +8,13 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/builtins"
|
"sigs.k8s.io/kustomize/api/builtins"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/internal/k8sdeps/transformer"
|
|
||||||
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/internal/target"
|
"sigs.k8s.io/kustomize/api/internal/target"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/validator"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
|
"sigs.k8s.io/kustomize/api/krusty/internal/provider"
|
||||||
fLdr "sigs.k8s.io/kustomize/api/loader"
|
fLdr "sigs.k8s.io/kustomize/api/loader"
|
||||||
"sigs.k8s.io/kustomize/api/provenance"
|
"sigs.k8s.io/kustomize/api/provenance"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,13 +25,18 @@ import (
|
|||||||
// number of overlays and bases), then make a Kustomizer
|
// number of overlays and bases), then make a Kustomizer
|
||||||
// injected with the given fileystem, then call Run.
|
// injected with the given fileystem, then call Run.
|
||||||
type Kustomizer struct {
|
type Kustomizer struct {
|
||||||
fSys filesys.FileSystem
|
fSys filesys.FileSystem
|
||||||
options *Options
|
options *Options
|
||||||
|
depProvider *provider.DepProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeKustomizer returns an instance of Kustomizer.
|
// MakeKustomizer returns an instance of Kustomizer.
|
||||||
func MakeKustomizer(fSys filesys.FileSystem, o *Options) *Kustomizer {
|
func MakeKustomizer(fSys filesys.FileSystem, o *Options) *Kustomizer {
|
||||||
return &Kustomizer{fSys: fSys, options: o}
|
return &Kustomizer{
|
||||||
|
fSys: fSys,
|
||||||
|
options: o,
|
||||||
|
depProvider: provider.NewDepProvider(o.UseKyaml),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run performs a kustomization.
|
// Run performs a kustomization.
|
||||||
@@ -49,11 +51,9 @@ func MakeKustomizer(fSys filesys.FileSystem, o *Options) *Kustomizer {
|
|||||||
// on any number of internal paths (e.g. the filesystem may contain
|
// on any number of internal paths (e.g. the filesystem may contain
|
||||||
// multiple overlays, and Run can be called on each of them).
|
// multiple overlays, and Run can be called on each of them).
|
||||||
func (b *Kustomizer) Run(path string) (resmap.ResMap, error) {
|
func (b *Kustomizer) Run(path string) (resmap.ResMap, error) {
|
||||||
pf := transformer.NewFactoryImpl()
|
resmapFactory := resmap.NewFactory(
|
||||||
rf := resmap.NewFactory(
|
b.depProvider.GetResourceFactory(),
|
||||||
resource.NewFactory(
|
b.depProvider.GetMerginator())
|
||||||
kunstruct.NewKunstructuredFactoryImpl()),
|
|
||||||
pf)
|
|
||||||
lr := fLdr.RestrictionNone
|
lr := fLdr.RestrictionNone
|
||||||
if b.options.LoadRestrictions == types.LoadRestrictionsRootOnly {
|
if b.options.LoadRestrictions == types.LoadRestrictionsRootOnly {
|
||||||
lr = fLdr.RestrictionRootOnly
|
lr = fLdr.RestrictionRootOnly
|
||||||
@@ -65,10 +65,9 @@ func (b *Kustomizer) Run(path string) (resmap.ResMap, error) {
|
|||||||
defer ldr.Cleanup()
|
defer ldr.Cleanup()
|
||||||
kt := target.NewKustTarget(
|
kt := target.NewKustTarget(
|
||||||
ldr,
|
ldr,
|
||||||
validator.NewKustValidator(),
|
b.depProvider.GetFieldValidator(),
|
||||||
rf,
|
resmapFactory,
|
||||||
pf,
|
pLdr.NewLoader(b.options.PluginConfig, resmapFactory),
|
||||||
pLdr.NewLoader(b.options.PluginConfig, rf),
|
|
||||||
)
|
)
|
||||||
err = kt.Load()
|
err = kt.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,7 +83,9 @@ func (b *Kustomizer) Run(path string) (resmap.ResMap, error) {
|
|||||||
}
|
}
|
||||||
if b.options.AddManagedbyLabel {
|
if b.options.AddManagedbyLabel {
|
||||||
t := builtins.LabelTransformerPlugin{
|
t := builtins.LabelTransformerPlugin{
|
||||||
Labels: map[string]string{konfig.ManagedbyLabelKey: fmt.Sprintf("kustomize-%s", provenance.GetProvenance().Version)},
|
Labels: map[string]string{
|
||||||
|
konfig.ManagedbyLabelKey: fmt.Sprintf(
|
||||||
|
"kustomize-%s", provenance.GetProvenance().Version)},
|
||||||
FieldSpecs: []types.FieldSpec{{
|
FieldSpecs: []types.FieldSpec{{
|
||||||
Path: "metadata/labels",
|
Path: "metadata/labels",
|
||||||
CreateIfNotPresent: true,
|
CreateIfNotPresent: true,
|
||||||
|
|||||||
@@ -146,12 +146,12 @@ spec:
|
|||||||
- gcePersistentDisk:
|
- gcePersistentDisk:
|
||||||
pdName: nginx-persistent-storage
|
pdName: nginx-persistent-storage
|
||||||
name: nginx-persistent-storage
|
name: nginx-persistent-storage
|
||||||
- configMap:
|
|
||||||
name: a-b-configmap-in-base-798k5k7g9f
|
|
||||||
name: configmap-in-base
|
|
||||||
- configMap:
|
- configMap:
|
||||||
name: a-configmap-in-overlay-dc6fm46dhm
|
name: a-configmap-in-overlay-dc6fm46dhm
|
||||||
name: configmap-in-overlay
|
name: configmap-in-overlay
|
||||||
|
- configMap:
|
||||||
|
name: a-b-configmap-in-base-798k5k7g9f
|
||||||
|
name: configmap-in-base
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
@@ -351,12 +351,12 @@ spec:
|
|||||||
- gcePersistentDisk:
|
- gcePersistentDisk:
|
||||||
pdName: nginx-persistent-storage
|
pdName: nginx-persistent-storage
|
||||||
name: nginx-persistent-storage
|
name: nginx-persistent-storage
|
||||||
- configMap:
|
|
||||||
name: staging-team-foo-configmap-in-base-798k5k7g9f
|
|
||||||
name: configmap-in-base
|
|
||||||
- configMap:
|
- configMap:
|
||||||
name: staging-configmap-in-overlay-dc6fm46dhm
|
name: staging-configmap-in-overlay-dc6fm46dhm
|
||||||
name: configmap-in-overlay
|
name: configmap-in-overlay
|
||||||
|
- configMap:
|
||||||
|
name: staging-team-foo-configmap-in-base-798k5k7g9f
|
||||||
|
name: configmap-in-base
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
113
api/krusty/nameprefixsuffixpatch_test.go
Normal file
113
api/krusty/nameprefixsuffixpatch_test.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Coverage for issue #2609
|
||||||
|
func TestNamePrefixSuffixPatch(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
|
th.WriteF("handlers/kustomization.yaml", `
|
||||||
|
nameSuffix: -suffix
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteF("handlers/deployment.yaml", `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: short
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: handler
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteF("mysql/kustomization.yaml", `
|
||||||
|
configMapGenerator:
|
||||||
|
- name: mysql
|
||||||
|
literals:
|
||||||
|
- MYSQL_USER=default
|
||||||
|
- MYSQL_DATABASE=default
|
||||||
|
- PORT=3306
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteK(".", `
|
||||||
|
resources:
|
||||||
|
- mysql
|
||||||
|
- handlers
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: mysql
|
||||||
|
behavior: merge
|
||||||
|
literals:
|
||||||
|
- MYSQL_DATABASE=db
|
||||||
|
- MYSQL_USER=my-user
|
||||||
|
- MYSQL_PASSWORD='correct horse battery staple'
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Deployment
|
||||||
|
name: s.*
|
||||||
|
patch: |-
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ignored
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: handler
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: mysql
|
||||||
|
env:
|
||||||
|
- valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: mysql
|
||||||
|
key: MYSQL_DATABASE
|
||||||
|
`)
|
||||||
|
|
||||||
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
// Per #2609, the desired behavior is for configMapRef.name and configMapKeyRef.name to be "mysql-9792mdchtg" not "mysql"
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
MYSQL_DATABASE: db
|
||||||
|
MYSQL_PASSWORD: correct horse battery staple
|
||||||
|
MYSQL_USER: my-user
|
||||||
|
PORT: "3306"
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
annotations: {}
|
||||||
|
labels: {}
|
||||||
|
name: mysql-9792mdchtg
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: short-suffix
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
key: MYSQL_DATABASE
|
||||||
|
name: mysql-9792mdchtg
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: mysql-9792mdchtg
|
||||||
|
name: handler
|
||||||
|
`)
|
||||||
|
}
|
||||||
@@ -159,7 +159,7 @@ subjects:
|
|||||||
name: default
|
name: default
|
||||||
namespace: irrelevant
|
namespace: irrelevant
|
||||||
---
|
---
|
||||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
kind: ValidatingWebhookConfiguration
|
kind: ValidatingWebhookConfiguration
|
||||||
metadata:
|
metadata:
|
||||||
name: example
|
name: example
|
||||||
@@ -180,15 +180,17 @@ webhooks:
|
|||||||
name: svc3
|
name: svc3
|
||||||
namespace: random
|
namespace: random
|
||||||
---
|
---
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: crds.my.org
|
name: crds.my.org
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
name: cr1
|
name: cr1
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
metadata:
|
metadata:
|
||||||
name: crb1
|
name: crb1
|
||||||
@@ -197,6 +199,7 @@ subjects:
|
|||||||
name: default
|
name: default
|
||||||
namespace: irrelevant
|
namespace: irrelevant
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
kind: PersistentVolume
|
kind: PersistentVolume
|
||||||
metadata:
|
metadata:
|
||||||
name: pv1
|
name: pv1
|
||||||
@@ -257,7 +260,7 @@ subjects:
|
|||||||
name: default
|
name: default
|
||||||
namespace: newnamespace
|
namespace: newnamespace
|
||||||
---
|
---
|
||||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
kind: ValidatingWebhookConfiguration
|
kind: ValidatingWebhookConfiguration
|
||||||
metadata:
|
metadata:
|
||||||
name: p1-example-s1
|
name: p1-example-s1
|
||||||
@@ -278,15 +281,17 @@ webhooks:
|
|||||||
namespace: random
|
namespace: random
|
||||||
name: example3
|
name: example3
|
||||||
---
|
---
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: crds.my.org
|
name: crds.my.org
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
name: p1-cr1-s1
|
name: p1-cr1-s1
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
metadata:
|
metadata:
|
||||||
name: p1-crb1-s1
|
name: p1-crb1-s1
|
||||||
@@ -295,6 +300,7 @@ subjects:
|
|||||||
name: default
|
name: default
|
||||||
namespace: newnamespace
|
namespace: newnamespace
|
||||||
---
|
---
|
||||||
|
apiVersion: v1
|
||||||
kind: PersistentVolume
|
kind: PersistentVolume
|
||||||
metadata:
|
metadata:
|
||||||
name: p1-pv1-s1
|
name: p1-pv1-s1
|
||||||
|
|||||||
287
api/krusty/nameupdateinroleref_test.go
Normal file
287
api/krusty/nameupdateinroleref_test.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/issues/2640
|
||||||
|
func TestNameUpdateInRoleRef(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteF("/app/rbac.yaml", `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- '*'
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: my-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: foo
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
version: v1
|
||||||
|
kind: Role
|
||||||
|
name: my-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteK("/app", `
|
||||||
|
namespace: foo
|
||||||
|
resources:
|
||||||
|
- rbac.yaml
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /metadata/name
|
||||||
|
value: prefix_my-role
|
||||||
|
target:
|
||||||
|
group: rbac.authorization.k8s.io
|
||||||
|
version: v1
|
||||||
|
kind: ClusterRole
|
||||||
|
name: my-role
|
||||||
|
`)
|
||||||
|
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: prefix_my-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- '*'
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: prefix_my-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: foo
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
namespace: foo
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: my-role
|
||||||
|
namespace: foo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: my-role
|
||||||
|
version: v1
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: foo
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/issues/3073
|
||||||
|
func TestNameUpdateInRoleRef2(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteF("/app/workloads.yaml", `
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes/metrics
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: myapp
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: myapp
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: myapp
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: myapp
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteF("/app/suffixTransformer.yaml", `
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: PrefixSuffixTransformer
|
||||||
|
metadata:
|
||||||
|
name: notImportantHere
|
||||||
|
suffix: -suffix
|
||||||
|
fieldSpecs:
|
||||||
|
- path: metadata/name
|
||||||
|
kind: ClusterRole
|
||||||
|
name: myapp
|
||||||
|
- path: metadata/name
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
name: myapp
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteK("/app", `
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- workloads.yaml
|
||||||
|
transformers:
|
||||||
|
- suffixTransformer.yaml
|
||||||
|
namespace: test
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
namespace: test
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: myapp-suffix
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes/metrics
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: myapp-suffix
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: myapp-suffix
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: myapp
|
||||||
|
namespace: test
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
namespace: test
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: myapp
|
||||||
|
namespace: test
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: myapp
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: myapp
|
||||||
|
namespace: test
|
||||||
|
`)
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNullValues(t *testing.T) {
|
func TestNullValues1(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
th.WriteF("/app/deployment.yaml", `
|
th.WriteF("/app/deployment.yaml", `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -62,3 +62,36 @@ spec:
|
|||||||
name: example
|
name: example
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNullValues2(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteF("deploy.yaml", `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
volumes: null
|
||||||
|
`)
|
||||||
|
th.WriteK(".", `
|
||||||
|
resources:
|
||||||
|
- deploy.yaml
|
||||||
|
`)
|
||||||
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
volumes: null
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,14 +32,20 @@ type Options struct {
|
|||||||
|
|
||||||
// Options related to kustomize plugins.
|
// Options related to kustomize plugins.
|
||||||
PluginConfig *types.PluginConfig
|
PluginConfig *types.PluginConfig
|
||||||
|
|
||||||
|
// When true, use kyaml/ packages to manipulate KRM yaml.
|
||||||
|
// When false, use k8sdeps/ instead (uses k8s.io/api* packages).
|
||||||
|
UseKyaml bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeDefaultOptions returns a default instance of Options.
|
// MakeDefaultOptions returns a default instance of Options.
|
||||||
func MakeDefaultOptions() *Options {
|
func MakeDefaultOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
DoLegacyResourceSort: true,
|
DoLegacyResourceSort: false,
|
||||||
|
AddManagedbyLabel: false,
|
||||||
LoadRestrictions: types.LoadRestrictionsRootOnly,
|
LoadRestrictions: types.LoadRestrictionsRootOnly,
|
||||||
DoPrune: false,
|
DoPrune: false,
|
||||||
PluginConfig: konfig.DisabledPluginConfig(),
|
PluginConfig: konfig.DisabledPluginConfig(),
|
||||||
|
UseKyaml: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,3 +213,132 @@ roleRef:
|
|||||||
name: my-role-ns2
|
name: my-role-ns2
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The ServiceAccount in subjects in role binding can be across namespace
|
||||||
|
// but the roleRef is not. This test is used to cover such case.
|
||||||
|
func TestRoleBindingWhenSubjectsAcrossNamespace(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeEnhancedHarness(t)
|
||||||
|
defer th.Reset()
|
||||||
|
th.WriteK("/app", `
|
||||||
|
resources:
|
||||||
|
- ./ns1
|
||||||
|
- ./ns2
|
||||||
|
`)
|
||||||
|
th.WriteK("/app/ns1", `
|
||||||
|
namespace: namespace-1
|
||||||
|
resources:
|
||||||
|
- role-ns1.yaml
|
||||||
|
- rolebinding-ns1.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/ns1/role-ns1.yaml", `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: testRole
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get"]
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/ns1/rolebinding-ns1.yaml", `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: testRoleBinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: testRole
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: testAccount
|
||||||
|
namespace: namespace-2
|
||||||
|
`)
|
||||||
|
th.WriteK("/app/ns2", `
|
||||||
|
namespace: namespace-2
|
||||||
|
resources:
|
||||||
|
- role-ns2.yaml
|
||||||
|
- rolebinding-ns2.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/ns2/role-ns2.yaml", `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: testRole
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get"]
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/ns2/rolebinding-ns2.yaml", `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: testRoleBinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: testRole
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: testAccount
|
||||||
|
namespace: namespace-1
|
||||||
|
`)
|
||||||
|
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: testRole
|
||||||
|
namespace: namespace-1
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: testRoleBinding
|
||||||
|
namespace: namespace-1
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: testRole
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: testAccount
|
||||||
|
namespace: namespace-2
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: testRole
|
||||||
|
namespace: namespace-2
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: testRoleBinding
|
||||||
|
namespace: namespace-2
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: testRole
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: testAccount
|
||||||
|
namespace: namespace-1
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|||||||
79
api/krusty/stringquoteblank_test.go
Normal file
79
api/krusty/stringquoteblank_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This test is for output string style.
|
||||||
|
// Currently all quotes will be removed if the string is valid as plain (unquoted) style.
|
||||||
|
// If a string cannot be unquoted, it will be put into a pair of single quotes.
|
||||||
|
// See https://yaml.org/spec/1.2/spec.html#id2788859 for more details about what kind of string
|
||||||
|
// is invalid as plain style.
|
||||||
|
func TestLongLineBreaks(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteF("/app/deployment.yaml", `
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mariadb
|
||||||
|
image: test
|
||||||
|
env:
|
||||||
|
- name: SHORT_STRING
|
||||||
|
value: short_string
|
||||||
|
- name: SHORT_STRING_WITH_SINGLE_QUOTE
|
||||||
|
value: 'short_string'
|
||||||
|
- name: SHORT_STRING_WITH_DOUBLE_QUOTE
|
||||||
|
value: "short_string"
|
||||||
|
- name: SHORT_STRING_BLANK
|
||||||
|
value: short string
|
||||||
|
- name: LONG_STRING_BLANK
|
||||||
|
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
|
||||||
|
- name: LONG_STRING_BLANK_WITH_SINGLE_QUOTE
|
||||||
|
value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.'
|
||||||
|
- name: LONG_STRING_BLANK_WITH_DOUBLE_QUOTE
|
||||||
|
value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius."
|
||||||
|
- name: INVALID_PLAIN_STYLE_STRING
|
||||||
|
value: ': test'
|
||||||
|
`)
|
||||||
|
th.WriteK("/app", `
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
`)
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: SHORT_STRING
|
||||||
|
value: short_string
|
||||||
|
- name: SHORT_STRING_WITH_SINGLE_QUOTE
|
||||||
|
value: short_string
|
||||||
|
- name: SHORT_STRING_WITH_DOUBLE_QUOTE
|
||||||
|
value: short_string
|
||||||
|
- name: SHORT_STRING_BLANK
|
||||||
|
value: short string
|
||||||
|
- name: LONG_STRING_BLANK
|
||||||
|
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
|
||||||
|
- name: LONG_STRING_BLANK_WITH_SINGLE_QUOTE
|
||||||
|
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
|
||||||
|
- name: LONG_STRING_BLANK_WITH_DOUBLE_QUOTE
|
||||||
|
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
|
||||||
|
- name: INVALID_PLAIN_STYLE_STRING
|
||||||
|
value: ': test'
|
||||||
|
image: test
|
||||||
|
name: mariadb
|
||||||
|
`)
|
||||||
|
}
|
||||||
@@ -215,25 +215,25 @@ spec2:
|
|||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: nginx:v1
|
- image: nginx:v2
|
||||||
name: nginx3
|
name: nginx3
|
||||||
- image: my-nginx:latest
|
- image: my-nginx:previous
|
||||||
name: nginx4
|
name: nginx4
|
||||||
spec3:
|
spec3:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
initContainers:
|
initContainers:
|
||||||
- image: postgres:alpine-9
|
- image: my-postgres:v3
|
||||||
name: postgresdb
|
name: postgresdb
|
||||||
- image: docker:17-git
|
- image: my-docker@sha256:25a0d4b4
|
||||||
name: init-docker
|
name: init-docker
|
||||||
- image: myprivaterepohostname:1234/my/image:latest
|
- image: myprivaterepohostname:1234/my/image:v1.0.1
|
||||||
name: myImage
|
name: myImage
|
||||||
- image: myprivaterepohostname:1234/my/image
|
- image: myprivaterepohostname:1234/my/image:v1.0.1
|
||||||
name: myImage2
|
name: myImage2
|
||||||
- image: my-app-image:v1
|
- image: my-app-image:v1
|
||||||
name: my-app
|
name: my-app
|
||||||
- image: gcr.io:8080/my-project/my-cool-app:latest
|
- image: my-cool-app:latest
|
||||||
name: my-cool-app
|
name: my-cool-app
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1969,3 +1969,70 @@ spec:
|
|||||||
server: kustomized-nfs-server-service.default.srv.cluster.local
|
server: kustomized-nfs-server-service.default.srv.cluster.local
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeploymentAnnotations(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteK("/app", `
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: testConfigMap
|
||||||
|
envs:
|
||||||
|
- test.properties
|
||||||
|
|
||||||
|
vars:
|
||||||
|
- name: FOO
|
||||||
|
objref:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: testConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
fieldref:
|
||||||
|
fieldpath: data.foo
|
||||||
|
|
||||||
|
commonAnnotations:
|
||||||
|
foo: $(FOO)
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
`)
|
||||||
|
|
||||||
|
th.WriteF("/app/deployment.yaml", `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/test.properties", `foo=bar`)
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
name: test
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: test
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
foo: bar
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
name: testConfigMap-798k5k7g9f
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -199,7 +199,9 @@ func (fl *fileLoader) New(path string) (ifc.Loader, error) {
|
|||||||
}
|
}
|
||||||
root, errDir := demandDirectoryRoot(fl.fSys, fl.root.Join(path))
|
root, errDir := demandDirectoryRoot(fl.fSys, fl.root.Join(path))
|
||||||
if errDir != nil {
|
if errDir != nil {
|
||||||
return nil, fmt.Errorf("Error loading %s with git: %v, dir: %v, get: %v", path, errGit, errDir, errGet)
|
return nil, fmt.Errorf(
|
||||||
|
"error loading %s with git: %v, dir: %v, get: %v",
|
||||||
|
path, errGit, errDir, errGet)
|
||||||
}
|
}
|
||||||
if errDir := fl.errIfGitContainmentViolation(root); errDir != nil {
|
if errDir := fl.errIfGitContainmentViolation(root); errDir != nil {
|
||||||
return nil, errDir
|
return nil, errDir
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ type remoteTargetSpec struct {
|
|||||||
|
|
||||||
// Dir is where the resource is saved
|
// Dir is where the resource is saved
|
||||||
Dir filesys.ConfirmedDir
|
Dir filesys.ConfirmedDir
|
||||||
|
|
||||||
|
// TempDir is the directory created to hold all resources, including Dir
|
||||||
|
TempDir filesys.ConfirmedDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getter is a function that can gets resource
|
// Getter is a function that can gets resource
|
||||||
@@ -31,7 +34,7 @@ func newLoaderAtGetter(raw string, fSys filesys.FileSystem, referrer *fileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
cleaner := func() error {
|
cleaner := func() error {
|
||||||
return fSys.RemoveAll(rs.Dir.String())
|
return fSys.RemoveAll(rs.TempDir.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := getter(rs); err != nil {
|
if err := getter(rs); err != nil {
|
||||||
@@ -55,12 +58,12 @@ func newLoaderAtGetter(raw string, fSys filesys.FileSystem, referrer *fileLoader
|
|||||||
func getRemoteTarget(rs *remoteTargetSpec) error {
|
func getRemoteTarget(rs *remoteTargetSpec) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
rs.Dir, err = filesys.NewTmpConfirmedDir()
|
rs.TempDir, err = filesys.NewTmpConfirmedDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rs.Dir = filesys.ConfirmedDir(rs.Dir.Join("repo"))
|
rs.Dir = filesys.ConfirmedDir(rs.TempDir.Join("repo"))
|
||||||
|
|
||||||
// Get the pwd
|
// Get the pwd
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
|
|||||||
@@ -39,5 +39,7 @@ func NewLoader(
|
|||||||
return newLoaderAtConfirmedDir(lr, root, fSys, nil, git.ClonerUsingGitExec, getRemoteTarget), nil
|
return newLoaderAtConfirmedDir(lr, root, fSys, nil, git.ClonerUsingGitExec, getRemoteTarget), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("Error creating new loader with git: %v, dir: %v, get: %v", errGit, errDir, errGet)
|
return nil, fmt.Errorf(
|
||||||
|
"error creating new loader with git: %v, dir: %v, get: %v",
|
||||||
|
errGit, errDir, errGet)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ package resid
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Gvk identifies a Kubernetes API type.
|
// Gvk identifies a Kubernetes API type.
|
||||||
@@ -22,8 +25,15 @@ func FromKind(k string) Gvk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GvkFromString makes a Gvk with a string,
|
// ParseGroupVersion parses a KRM metadata apiVersion field.
|
||||||
// which is constructed by String() function
|
func ParseGroupVersion(apiVersion string) (group, version string) {
|
||||||
|
if i := strings.Index(apiVersion, "/"); i > -1 {
|
||||||
|
return apiVersion[:i], apiVersion[i+1:]
|
||||||
|
}
|
||||||
|
return "", apiVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// GvkFromString makes a Gvk from the output of Gvk.String().
|
||||||
func GvkFromString(s string) Gvk {
|
func GvkFromString(s string) Gvk {
|
||||||
values := strings.Split(s, fieldSep)
|
values := strings.Split(s, fieldSep)
|
||||||
g := values[0]
|
g := values[0]
|
||||||
@@ -70,6 +80,22 @@ func (x Gvk) String() string {
|
|||||||
return strings.Join([]string{g, v, k}, fieldSep)
|
return strings.Join([]string{g, v, k}, fieldSep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringWoEmptyField returns a string representation of the GVK. Non-exist
|
||||||
|
// fields will be omitted.
|
||||||
|
func (x Gvk) StringWoEmptyField() string {
|
||||||
|
var s []string
|
||||||
|
if x.Group != "" {
|
||||||
|
s = append(s, x.Group)
|
||||||
|
}
|
||||||
|
if x.Version != "" {
|
||||||
|
s = append(s, x.Version)
|
||||||
|
}
|
||||||
|
if x.Kind != "" {
|
||||||
|
s = append(s, x.Kind)
|
||||||
|
}
|
||||||
|
return strings.Join(s, fieldSep)
|
||||||
|
}
|
||||||
|
|
||||||
// Equals returns true if the Gvk's have equal fields.
|
// Equals returns true if the Gvk's have equal fields.
|
||||||
func (x Gvk) Equals(o Gvk) bool {
|
func (x Gvk) Equals(o Gvk) bool {
|
||||||
return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind
|
return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind
|
||||||
@@ -92,6 +118,7 @@ var orderFirst = []string{
|
|||||||
"ClusterRoleBinding",
|
"ClusterRoleBinding",
|
||||||
"ConfigMap",
|
"ConfigMap",
|
||||||
"Secret",
|
"Secret",
|
||||||
|
"Endpoints",
|
||||||
"Service",
|
"Service",
|
||||||
"LimitRange",
|
"LimitRange",
|
||||||
"PriorityClass",
|
"PriorityClass",
|
||||||
@@ -164,39 +191,23 @@ func (x Gvk) IsSelected(selector *Gvk) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var notNamespaceableKinds = []string{
|
// toKyamlTypeMeta returns a yaml.TypeMeta from x's information.
|
||||||
"APIService",
|
func (x Gvk) toKyamlTypeMeta() yaml.TypeMeta {
|
||||||
"CSIDriver",
|
var apiVersion strings.Builder
|
||||||
"CSINode",
|
if x.Group != "" {
|
||||||
"CertificateSigningRequest",
|
apiVersion.WriteString(x.Group)
|
||||||
"Cluster",
|
apiVersion.WriteString("/")
|
||||||
"ClusterRole",
|
}
|
||||||
"ClusterRoleBinding",
|
apiVersion.WriteString(x.Version)
|
||||||
"ComponentStatus",
|
return yaml.TypeMeta{
|
||||||
"CustomResourceDefinition",
|
APIVersion: apiVersion.String(),
|
||||||
"MutatingWebhookConfiguration",
|
Kind: x.Kind,
|
||||||
"Namespace",
|
}
|
||||||
"Node",
|
|
||||||
"PersistentVolume",
|
|
||||||
"PodSecurityPolicy",
|
|
||||||
"PriorityClass",
|
|
||||||
"RuntimeClass",
|
|
||||||
"SelfSubjectAccessReview",
|
|
||||||
"SelfSubjectRulesReview",
|
|
||||||
"StorageClass",
|
|
||||||
"SubjectAccessReview",
|
|
||||||
"TokenReview",
|
|
||||||
"ValidatingWebhookConfiguration",
|
|
||||||
"VolumeAttachment",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNamespaceableKind returns true if x is a namespaceable Gvk
|
// IsNamespaceableKind returns true if x is a namespaceable Gvk
|
||||||
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
||||||
func (x Gvk) IsNamespaceableKind() bool {
|
func (x Gvk) IsNamespaceableKind() bool {
|
||||||
for _, k := range notNamespaceableKinds {
|
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())
|
||||||
if k == x.Kind {
|
return !found || isNamespaceScoped
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package resid
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var equalsTests = []struct {
|
var equalsTests = []struct {
|
||||||
@@ -78,6 +80,8 @@ var lessThanTests = []struct {
|
|||||||
Gvk{Group: "a", Version: "b", Kind: "ValidatingWebhookConfiguration"}},
|
Gvk{Group: "a", Version: "b", Kind: "ValidatingWebhookConfiguration"}},
|
||||||
{Gvk{Group: "a", Version: "b", Kind: "Service"},
|
{Gvk{Group: "a", Version: "b", Kind: "Service"},
|
||||||
Gvk{Group: "a", Version: "b", Kind: "APIService"}},
|
Gvk{Group: "a", Version: "b", Kind: "APIService"}},
|
||||||
|
{Gvk{Group: "a", Version: "b", Kind: "Endpoints"},
|
||||||
|
Gvk{Group: "a", Version: "b", Kind: "Service"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsLessThan1(t *testing.T) {
|
func TestIsLessThan1(t *testing.T) {
|
||||||
@@ -94,15 +98,16 @@ func TestIsLessThan1(t *testing.T) {
|
|||||||
var stringTests = []struct {
|
var stringTests = []struct {
|
||||||
x Gvk
|
x Gvk
|
||||||
s string
|
s string
|
||||||
|
r string
|
||||||
}{
|
}{
|
||||||
{Gvk{}, "~G_~V_~K"},
|
{Gvk{}, "~G_~V_~K", ""},
|
||||||
{Gvk{Kind: "k"}, "~G_~V_k"},
|
{Gvk{Kind: "k"}, "~G_~V_k", "k"},
|
||||||
{Gvk{Version: "v"}, "~G_v_~K"},
|
{Gvk{Version: "v"}, "~G_v_~K", "v"},
|
||||||
{Gvk{Version: "v", Kind: "k"}, "~G_v_k"},
|
{Gvk{Version: "v", Kind: "k"}, "~G_v_k", "v_k"},
|
||||||
{Gvk{Group: "g"}, "g_~V_~K"},
|
{Gvk{Group: "g"}, "g_~V_~K", "g"},
|
||||||
{Gvk{Group: "g", Kind: "k"}, "g_~V_k"},
|
{Gvk{Group: "g", Kind: "k"}, "g_~V_k", "g_k"},
|
||||||
{Gvk{Group: "g", Version: "v"}, "g_v_~K"},
|
{Gvk{Group: "g", Version: "v"}, "g_v_~K", "g_v"},
|
||||||
{Gvk{Group: "g", Version: "v", Kind: "k"}, "g_v_k"},
|
{Gvk{Group: "g", Version: "v", Kind: "k"}, "g_v_k", "g_v_k"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestString(t *testing.T) {
|
func TestString(t *testing.T) {
|
||||||
@@ -113,6 +118,38 @@ func TestString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringWoEmptyField(t *testing.T) {
|
||||||
|
for _, hey := range stringTests {
|
||||||
|
if hey.x.StringWoEmptyField() != hey.r {
|
||||||
|
t.Fatalf("bad string %s for %v '%s'", hey.x.StringWoEmptyField(), hey.x, hey.r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGroupVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
g string
|
||||||
|
v string
|
||||||
|
}{
|
||||||
|
{input: "", g: "", v: ""},
|
||||||
|
{input: "v1", g: "", v: "v1"},
|
||||||
|
{input: "apps/v1", g: "apps", v: "v1"},
|
||||||
|
{input: "/v1", g: "", v: "v1"},
|
||||||
|
{input: "apps/", g: "apps", v: ""},
|
||||||
|
{input: "/apps/", g: "", v: "apps/"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
g, v := ParseGroupVersion(tc.input)
|
||||||
|
if g != tc.g {
|
||||||
|
t.Errorf("%s: expected group '%s', got '%s'", tc.input, tc.g, g)
|
||||||
|
}
|
||||||
|
if v != tc.v {
|
||||||
|
t.Errorf("%s: expected version '%s', got '%s'", tc.input, tc.v, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSelectByGVK(t *testing.T) {
|
func TestSelectByGVK(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
description string
|
description string
|
||||||
@@ -220,3 +257,40 @@ func TestSelectByGVK(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsNamespaceableKind(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
gvk Gvk
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"namespaceable resource",
|
||||||
|
Gvk{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clusterscoped resource",
|
||||||
|
Gvk{Group: "", Version: "v1", Kind: "Namespace"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unknown resource (should default to namespaceable)",
|
||||||
|
Gvk{Group: "example1.com", Version: "v1", Kind: "Bar"},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unknown resource (should default to namespaceable)",
|
||||||
|
Gvk{Group: "apps", Version: "v1", Kind: "ClusterRoleBinding"},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range testCases {
|
||||||
|
test := testCases[i]
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
isNamespaceable := test.gvk.IsNamespaceableKind()
|
||||||
|
assert.Equal(t, test.expected, isNamespaceable)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ func TestResIdEquals(t *testing.T) {
|
|||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
gVknResult: false,
|
gVknResult: false,
|
||||||
nsEquals: false,
|
nsEquals: true,
|
||||||
equals: false,
|
equals: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -376,7 +376,7 @@ func TestEffectiveNamespace(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Gvk: Gvk{Group: "g", Version: "v", Kind: "Node"},
|
Gvk: Gvk{Group: "", Version: "v1", Kind: "Node"},
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
expected: TotallyNotANamespace,
|
expected: TotallyNotANamespace,
|
||||||
@@ -384,7 +384,7 @@ func TestEffectiveNamespace(t *testing.T) {
|
|||||||
{
|
{
|
||||||
id: ResId{
|
id: ResId{
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Gvk: Gvk{Group: "g", Version: "v", Kind: "Node"},
|
Gvk: Gvk{Group: "", Version: "v1", Kind: "Node"},
|
||||||
Name: "nm",
|
Name: "nm",
|
||||||
},
|
},
|
||||||
expected: TotallyNotANamespace,
|
expected: TotallyNotANamespace,
|
||||||
|
|||||||
@@ -11,15 +11,24 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Merginator merges resources.
|
||||||
|
type Merginator interface {
|
||||||
|
// Merge creates a new ResMap by merging incoming resources.
|
||||||
|
// Error if conflict found.
|
||||||
|
Merge([]*resource.Resource) (ResMap, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Factory makes instances of ResMap.
|
// Factory makes instances of ResMap.
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
|
// Makes resources.
|
||||||
resF *resource.Factory
|
resF *resource.Factory
|
||||||
tf PatchFactory
|
// Makes ResMaps via merging.
|
||||||
|
pm Merginator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactory returns a new resmap.Factory.
|
// NewFactory returns a new resmap.Factory.
|
||||||
func NewFactory(rf *resource.Factory, tf PatchFactory) *Factory {
|
func NewFactory(rf *resource.Factory, pm Merginator) *Factory {
|
||||||
return &Factory{resF: rf, tf: tf}
|
return &Factory{resF: rf, pm: pm}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RF returns a resource.Factory.
|
// RF returns a resource.Factory.
|
||||||
@@ -87,6 +96,7 @@ func (rmF *Factory) NewResMapFromConfigMapArgs(
|
|||||||
return newResMapFromResourceSlice(resources)
|
return newResMapFromResourceSlice(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromConfigMapArgs creates a new ResMap containing one ConfigMap.
|
||||||
func (rmF *Factory) FromConfigMapArgs(
|
func (rmF *Factory) FromConfigMapArgs(
|
||||||
kvLdr ifc.KvLoader, args types.ConfigMapArgs) (ResMap, error) {
|
kvLdr ifc.KvLoader, args types.ConfigMapArgs) (ResMap, error) {
|
||||||
res, err := rmF.resF.MakeConfigMap(kvLdr, &args)
|
res, err := rmF.resF.MakeConfigMap(kvLdr, &args)
|
||||||
@@ -111,6 +121,7 @@ func (rmF *Factory) NewResMapFromSecretArgs(
|
|||||||
return newResMapFromResourceSlice(resources)
|
return newResMapFromResourceSlice(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromSecretArgs creates a new ResMap containing one secret.
|
||||||
func (rmF *Factory) FromSecretArgs(
|
func (rmF *Factory) FromSecretArgs(
|
||||||
kvLdr ifc.KvLoader, args types.SecretArgs) (ResMap, error) {
|
kvLdr ifc.KvLoader, args types.SecretArgs) (ResMap, error) {
|
||||||
res, err := rmF.resF.MakeSecret(kvLdr, &args)
|
res, err := rmF.resF.MakeSecret(kvLdr, &args)
|
||||||
@@ -120,12 +131,14 @@ func (rmF *Factory) FromSecretArgs(
|
|||||||
return rmF.FromResource(res), nil
|
return rmF.FromResource(res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rmF *Factory) MergePatches(patches []*resource.Resource) (
|
// Merge creates a new ResMap by merging incoming resources.
|
||||||
ResMap, error) {
|
// Error if conflict found.
|
||||||
return rmF.tf.MergePatches(patches, rmF.resF)
|
func (rmF *Factory) Merge(patches []*resource.Resource) (ResMap, error) {
|
||||||
|
return rmF.pm.Merge(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
err := result.Append(res)
|
err := result.Append(res)
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/// 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/api/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PatchFactory makes transformers that require k8sdeps.
|
|
||||||
type PatchFactory interface {
|
|
||||||
MergePatches(patches []*resource.Resource,
|
|
||||||
rf *resource.Factory) (ResMap, error)
|
|
||||||
}
|
|
||||||
@@ -6,16 +6,10 @@
|
|||||||
package resmap
|
package resmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Transformer modifies an instance of ResMap.
|
// A Transformer modifies an instance of ResMap.
|
||||||
@@ -242,569 +236,3 @@ type ResMap interface {
|
|||||||
// are selected by a Selector
|
// are selected by a Selector
|
||||||
Select(types.Selector) ([]*resource.Resource, error)
|
Select(types.Selector) ([]*resource.Resource, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resWrangler holds the content manipulated by kustomize.
|
|
||||||
type resWrangler struct {
|
|
||||||
// Resource list maintained in load (append) order.
|
|
||||||
// This is important for transformers, which must
|
|
||||||
// be performed in a specific order, and for users
|
|
||||||
// who for whatever reasons wish the order they
|
|
||||||
// specify in kustomizations to be maintained and
|
|
||||||
// available as an option for final YAML rendering.
|
|
||||||
rList []*resource.Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOne() *resWrangler {
|
|
||||||
result := &resWrangler{}
|
|
||||||
result.Clear()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear implements ResMap.
|
|
||||||
func (m *resWrangler) Clear() {
|
|
||||||
m.rList = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size implements ResMap.
|
|
||||||
func (m *resWrangler) Size() int {
|
|
||||||
return len(m.rList)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resWrangler) indexOfResource(other *resource.Resource) int {
|
|
||||||
for i, r := range m.rList {
|
|
||||||
if r == other {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resources implements ResMap.
|
|
||||||
func (m *resWrangler) Resources() []*resource.Resource {
|
|
||||||
tmp := make([]*resource.Resource, len(m.rList))
|
|
||||||
copy(tmp, m.rList)
|
|
||||||
return tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append implements ResMap.
|
|
||||||
func (m *resWrangler) Append(res *resource.Resource) error {
|
|
||||||
id := res.CurId()
|
|
||||||
if r := m.GetMatchingResourcesByCurrentId(id.Equals); len(r) > 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"may not add resource with an already registered id: %s", id)
|
|
||||||
}
|
|
||||||
m.rList = append(m.rList, res)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove implements ResMap.
|
|
||||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
|
||||||
tmp := newOne()
|
|
||||||
for _, r := range m.rList {
|
|
||||||
if r.CurId() != adios {
|
|
||||||
tmp.Append(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if tmp.Size() != m.Size()-1 {
|
|
||||||
return fmt.Errorf("id %s not found in removal", adios)
|
|
||||||
}
|
|
||||||
m.rList = tmp.rList
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace implements ResMap.
|
|
||||||
func (m *resWrangler) Replace(res *resource.Resource) (int, error) {
|
|
||||||
id := res.CurId()
|
|
||||||
i, err := m.GetIndexOfCurrentId(id)
|
|
||||||
if err != nil {
|
|
||||||
return -1, errors.Wrap(err, "in Replace")
|
|
||||||
}
|
|
||||||
if i < 0 {
|
|
||||||
return -1, fmt.Errorf("cannot find resource with id %s to replace", id)
|
|
||||||
}
|
|
||||||
m.rList[i] = res
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllIds implements ResMap.
|
|
||||||
func (m *resWrangler) AllIds() (ids []resid.ResId) {
|
|
||||||
ids = make([]resid.ResId, m.Size())
|
|
||||||
for i, r := range m.rList {
|
|
||||||
ids[i] = r.CurId()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug implements ResMap.
|
|
||||||
func (m *resWrangler) Debug(title string) {
|
|
||||||
fmt.Println("--------------------------- " + title)
|
|
||||||
firstObj := true
|
|
||||||
for i, r := range m.rList {
|
|
||||||
if firstObj {
|
|
||||||
firstObj = false
|
|
||||||
} else {
|
|
||||||
fmt.Println("---")
|
|
||||||
}
|
|
||||||
fmt.Printf("# %d %s\n", i, r.OrgId())
|
|
||||||
blob, err := yaml.Marshal(r.Map())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(blob))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type IdMatcher func(resid.ResId) bool
|
|
||||||
|
|
||||||
// GetByIndex implements ResMap.
|
|
||||||
func (m *resWrangler) GetByIndex(i int) *resource.Resource {
|
|
||||||
if i < 0 || i >= m.Size() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return m.rList[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndexOfCurrentId implements ResMap.
|
|
||||||
func (m *resWrangler) GetIndexOfCurrentId(id resid.ResId) (int, error) {
|
|
||||||
count := 0
|
|
||||||
result := -1
|
|
||||||
for i, r := range m.rList {
|
|
||||||
if id.Equals(r.CurId()) {
|
|
||||||
count++
|
|
||||||
result = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 1 {
|
|
||||||
return -1, fmt.Errorf("id matched %d resources", count)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type IdFromResource func(r *resource.Resource) resid.ResId
|
|
||||||
|
|
||||||
func GetOriginalId(r *resource.Resource) resid.ResId { return r.OrgId() }
|
|
||||||
func GetCurrentId(r *resource.Resource) resid.ResId { return r.CurId() }
|
|
||||||
|
|
||||||
// GetMatchingResourcesByCurrentId implements ResMap.
|
|
||||||
func (m *resWrangler) GetMatchingResourcesByCurrentId(
|
|
||||||
matches IdMatcher) []*resource.Resource {
|
|
||||||
return m.filteredById(matches, GetCurrentId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMatchingResourcesByOriginalId implements ResMap.
|
|
||||||
func (m *resWrangler) GetMatchingResourcesByOriginalId(
|
|
||||||
matches IdMatcher) []*resource.Resource {
|
|
||||||
return m.filteredById(matches, GetOriginalId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resWrangler) filteredById(
|
|
||||||
matches IdMatcher, idGetter IdFromResource) []*resource.Resource {
|
|
||||||
var result []*resource.Resource
|
|
||||||
for _, r := range m.rList {
|
|
||||||
if matches(idGetter(r)) {
|
|
||||||
result = append(result, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByCurrentId implements ResMap.
|
|
||||||
func (m *resWrangler) GetByCurrentId(
|
|
||||||
id resid.ResId) (*resource.Resource, error) {
|
|
||||||
return demandOneMatch(m.GetMatchingResourcesByCurrentId, id, "Current")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByOriginalId implements ResMap.
|
|
||||||
func (m *resWrangler) GetByOriginalId(
|
|
||||||
id resid.ResId) (*resource.Resource, error) {
|
|
||||||
return demandOneMatch(m.GetMatchingResourcesByOriginalId, id, "Original")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetById implements ResMap.
|
|
||||||
func (m *resWrangler) GetById(
|
|
||||||
id resid.ResId) (*resource.Resource, error) {
|
|
||||||
match, err1 := m.GetByOriginalId(id)
|
|
||||||
if err1 == nil {
|
|
||||||
return match, nil
|
|
||||||
}
|
|
||||||
match, err2 := m.GetByCurrentId(id)
|
|
||||||
if err2 == nil {
|
|
||||||
return match, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"%s; %s; failed to find unique target for patch %s",
|
|
||||||
err1.Error(), err2.Error(), id.GvknString())
|
|
||||||
}
|
|
||||||
|
|
||||||
type resFinder func(IdMatcher) []*resource.Resource
|
|
||||||
|
|
||||||
func demandOneMatch(
|
|
||||||
f resFinder, id resid.ResId, s string) (*resource.Resource, error) {
|
|
||||||
r := f(id.Equals)
|
|
||||||
if len(r) == 1 {
|
|
||||||
return r[0], nil
|
|
||||||
}
|
|
||||||
if len(r) > 1 {
|
|
||||||
return nil, fmt.Errorf("multiple matches for %sId %s", s, id)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("no matches for %sId %s", s, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
|
|
||||||
func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
|
|
||||||
items := m.groupedByCurrentNamespace()
|
|
||||||
delete(items, resid.TotallyNotANamespace)
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonNamespaceable implements ResMap.NonNamespaceable
|
|
||||||
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
|
||||||
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resWrangler) groupedByCurrentNamespace() map[string][]*resource.Resource {
|
|
||||||
byNamespace := make(map[string][]*resource.Resource)
|
|
||||||
for _, res := range m.rList {
|
|
||||||
namespace := res.CurId().EffectiveNamespace()
|
|
||||||
if _, found := byNamespace[namespace]; !found {
|
|
||||||
byNamespace[namespace] = []*resource.Resource{}
|
|
||||||
}
|
|
||||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
|
||||||
}
|
|
||||||
return byNamespace
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupedByNamespace implements ResMap.GroupByOrginalNamespace
|
|
||||||
func (m *resWrangler) GroupedByOriginalNamespace() map[string][]*resource.Resource {
|
|
||||||
items := m.groupedByOriginalNamespace()
|
|
||||||
delete(items, resid.TotallyNotANamespace)
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resWrangler) groupedByOriginalNamespace() map[string][]*resource.Resource {
|
|
||||||
byNamespace := make(map[string][]*resource.Resource)
|
|
||||||
for _, res := range m.rList {
|
|
||||||
namespace := res.OrgId().EffectiveNamespace()
|
|
||||||
if _, found := byNamespace[namespace]; !found {
|
|
||||||
byNamespace[namespace] = []*resource.Resource{}
|
|
||||||
}
|
|
||||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
|
||||||
}
|
|
||||||
return byNamespace
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsYaml implements ResMap.
|
|
||||||
func (m *resWrangler) AsYaml() ([]byte, error) {
|
|
||||||
firstObj := true
|
|
||||||
var b []byte
|
|
||||||
buf := bytes.NewBuffer(b)
|
|
||||||
for _, res := range m.Resources() {
|
|
||||||
out, err := yaml.Marshal(res.Map())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if firstObj {
|
|
||||||
firstObj = false
|
|
||||||
} else {
|
|
||||||
if _, err = buf.WriteString("---\n"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = buf.Write(out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorIfNotEqualSets implements ResMap.
|
|
||||||
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
|
||||||
m2, ok := other.(*resWrangler)
|
|
||||||
if !ok {
|
|
||||||
panic("bad cast")
|
|
||||||
}
|
|
||||||
if m.Size() != m2.Size() {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"lists have different number of entries: %#v doesn't equal %#v",
|
|
||||||
m.rList, m2.rList)
|
|
||||||
}
|
|
||||||
seen := make(map[int]bool)
|
|
||||||
for _, r1 := range m.rList {
|
|
||||||
id := r1.CurId()
|
|
||||||
others := m2.GetMatchingResourcesByCurrentId(id.Equals)
|
|
||||||
if len(others) == 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"id in self missing from other; id: %s", id)
|
|
||||||
}
|
|
||||||
if len(others) > 1 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"id in self matches %d in other; id: %s", len(others), id)
|
|
||||||
}
|
|
||||||
r2 := others[0]
|
|
||||||
if !r1.KunstructEqual(r2) {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
|
||||||
r1, r2, r1, r2)
|
|
||||||
}
|
|
||||||
seen[m2.indexOfResource(r2)] = true
|
|
||||||
}
|
|
||||||
if len(seen) != m.Size() {
|
|
||||||
return fmt.Errorf("counting problem %d != %d", len(seen), m.Size())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorIfNotEqualList implements ResMap.
|
|
||||||
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
|
||||||
m2, ok := other.(*resWrangler)
|
|
||||||
if !ok {
|
|
||||||
panic("bad cast")
|
|
||||||
}
|
|
||||||
if m.Size() != m2.Size() {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"lists have different number of entries: %#v doesn't equal %#v",
|
|
||||||
m.rList, m2.rList)
|
|
||||||
}
|
|
||||||
for i, r1 := range m.rList {
|
|
||||||
r2 := m2.rList[i]
|
|
||||||
if !r1.Equals(r2) {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Item i=%d differs:\n n1 = %s\n n2 = %s\n o1 = %s\n o2 = %s\n",
|
|
||||||
i, r1.OrgId(), r2.OrgId(), r1, r2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type resCopier func(r *resource.Resource) *resource.Resource
|
|
||||||
|
|
||||||
// ShallowCopy implements ResMap.
|
|
||||||
func (m *resWrangler) ShallowCopy() ResMap {
|
|
||||||
return m.makeCopy(
|
|
||||||
func(r *resource.Resource) *resource.Resource {
|
|
||||||
return r
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy implements ResMap.
|
|
||||||
func (m *resWrangler) DeepCopy() ResMap {
|
|
||||||
return m.makeCopy(
|
|
||||||
func(r *resource.Resource) *resource.Resource {
|
|
||||||
return r.DeepCopy()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeCopy copies the ResMap.
|
|
||||||
func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
|
||||||
result := &resWrangler{}
|
|
||||||
result.rList = make([]*resource.Resource, m.Size())
|
|
||||||
for i, r := range m.rList {
|
|
||||||
result.rList[i] = copier(r)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
|
||||||
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
|
||||||
inputRes *resource.Resource) ResMap {
|
|
||||||
result := newOne()
|
|
||||||
inputId := inputRes.CurId()
|
|
||||||
isInputIdNamespaceable := inputId.IsNamespaceableKind()
|
|
||||||
rctxm := inputRes.PrefixesSuffixesEquals
|
|
||||||
subjectNamespaces := getNamespacesForRoleBinding(inputRes)
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
// Need to match more accuratly both at the time of selection and transformation.
|
|
||||||
// OutmostPrefixSuffixEquals is not accurate enough since it is only using
|
|
||||||
// the outer most suffix and the last prefix. Use PrefixedSuffixesEquals instead.
|
|
||||||
resId := r.CurId()
|
|
||||||
if (!isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId) ||
|
|
||||||
isRoleBindingNamespace(&subjectNamespaces, r.GetNamespace())) && r.InSameKustomizeCtx(rctxm) {
|
|
||||||
result.append(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// isRoleBindingNamespace returns true is the namespace `ns` is in role binding
|
|
||||||
// namespaces `m`
|
|
||||||
func isRoleBindingNamespace(m *map[string]bool, ns string) bool {
|
|
||||||
return (*m)[ns]
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamespacesForRoleBinding returns referenced ServiceAccount namespaces if the inputRes is
|
|
||||||
// a RoleBinding
|
|
||||||
func getNamespacesForRoleBinding(inputRes *resource.Resource) map[string]bool {
|
|
||||||
res := make(map[string]bool)
|
|
||||||
if inputRes.GetKind() != "RoleBinding" {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
subjects, err := inputRes.GetSlice("subjects")
|
|
||||||
if err != nil || subjects == nil {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range subjects {
|
|
||||||
subject := s.(map[string]interface{})
|
|
||||||
if subject["namespace"] == nil || subject["kind"] == nil ||
|
|
||||||
subject["kind"].(string) != "ServiceAccount" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res[subject["namespace"].(string)] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resWrangler) append(res *resource.Resource) {
|
|
||||||
m.rList = append(m.rList, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendAll implements ResMap.
|
|
||||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
|
||||||
if other == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, res := range other.Resources() {
|
|
||||||
if err := m.Append(res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AbsorbAll implements ResMap.
|
|
||||||
func (m *resWrangler) AbsorbAll(other ResMap) error {
|
|
||||||
if other == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, r := range other.Resources() {
|
|
||||||
err := m.appendReplaceOrMerge(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *resWrangler) appendReplaceOrMerge(
|
|
||||||
res *resource.Resource) error {
|
|
||||||
id := res.CurId()
|
|
||||||
matches := m.GetMatchingResourcesByOriginalId(id.Equals)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
matches = m.GetMatchingResourcesByCurrentId(id.Equals)
|
|
||||||
}
|
|
||||||
switch len(matches) {
|
|
||||||
case 0:
|
|
||||||
switch res.Behavior() {
|
|
||||||
case types.BehaviorMerge, types.BehaviorReplace:
|
|
||||||
return fmt.Errorf(
|
|
||||||
"id %#v does not exist; cannot merge or replace", id)
|
|
||||||
default:
|
|
||||||
// presumably types.BehaviorCreate
|
|
||||||
err := m.Append(res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
old := matches[0]
|
|
||||||
if old == nil {
|
|
||||||
return fmt.Errorf("id lookup failure")
|
|
||||||
}
|
|
||||||
index := m.indexOfResource(old)
|
|
||||||
if index < 0 {
|
|
||||||
return fmt.Errorf("indexing problem")
|
|
||||||
}
|
|
||||||
switch res.Behavior() {
|
|
||||||
case types.BehaviorReplace:
|
|
||||||
res.Replace(old)
|
|
||||||
case types.BehaviorMerge:
|
|
||||||
res.Merge(old)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf(
|
|
||||||
"id %#v exists; behavior must be merge or replace", id)
|
|
||||||
}
|
|
||||||
i, err := m.Replace(res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if i != index {
|
|
||||||
return fmt.Errorf("unexpected index in replacement")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf(
|
|
||||||
"found multiple objects %v that could accept merge of %v",
|
|
||||||
matches, id)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func anchorRegex(pattern string) string {
|
|
||||||
if pattern == "" {
|
|
||||||
return pattern
|
|
||||||
}
|
|
||||||
return "^" + pattern + "$"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select returns a list of resources that
|
|
||||||
// are selected by a Selector
|
|
||||||
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
|
||||||
ns := regexp.MustCompile(anchorRegex(s.Namespace))
|
|
||||||
nm := regexp.MustCompile(anchorRegex(s.Name))
|
|
||||||
var result []*resource.Resource
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
curId := r.CurId()
|
|
||||||
orgId := r.OrgId()
|
|
||||||
|
|
||||||
// matches the namespace when namespace is not empty in the selector
|
|
||||||
// It first tries to match with the original namespace
|
|
||||||
// then matches with the current namespace
|
|
||||||
if r.GetNamespace() != "" {
|
|
||||||
matched := ns.MatchString(orgId.EffectiveNamespace())
|
|
||||||
if !matched {
|
|
||||||
matched = ns.MatchString(curId.EffectiveNamespace())
|
|
||||||
if !matched {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// matches the name when name is not empty in the selector
|
|
||||||
// It first tries to match with the original name
|
|
||||||
// then matches with the current name
|
|
||||||
if r.GetName() != "" {
|
|
||||||
matched := nm.MatchString(orgId.Name)
|
|
||||||
if !matched {
|
|
||||||
matched = nm.MatchString(curId.Name)
|
|
||||||
if !matched {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// matches the GVK
|
|
||||||
if !r.GetGvk().IsSelected(&s.Gvk) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// matches the label selector
|
|
||||||
matched, err := r.MatchesLabelSelector(s.LabelSelector)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !matched {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// matches the annotation selector
|
|
||||||
matched, err = r.MatchesAnnotationSelector(s.AnnotationSelector)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !matched {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result = append(result, r)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,899 +3,4 @@
|
|||||||
|
|
||||||
package resmap_test
|
package resmap_test
|
||||||
|
|
||||||
import (
|
// See reswrangler_test.go
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
. "sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var rf = resource.NewFactory(
|
|
||||||
kunstruct.NewKunstructuredFactoryImpl())
|
|
||||||
var rmF = NewFactory(rf, nil)
|
|
||||||
|
|
||||||
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
|
|
||||||
err := w.Append(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("append error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func doRemove(t *testing.T, w ResMap, id resid.ResId) {
|
|
||||||
err := w.Remove(id)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("remove error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a resource with a predictable name.
|
|
||||||
func makeCm(i int) *resource.Resource {
|
|
||||||
return rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": fmt.Sprintf("cm%03d", i),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maintain the class invariant that no two
|
|
||||||
// resources can have the same CurId().
|
|
||||||
func TestAppendRejectsDuplicateResId(t *testing.T) {
|
|
||||||
w := New()
|
|
||||||
if err := w.Append(makeCm(1)); err != nil {
|
|
||||||
t.Fatalf("append error: %v", err)
|
|
||||||
}
|
|
||||||
err := w.Append(makeCm(1))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected append error")
|
|
||||||
}
|
|
||||||
if !strings.Contains(
|
|
||||||
err.Error(),
|
|
||||||
"may not add resource with an already registered id") {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppendRemove(t *testing.T) {
|
|
||||||
w1 := New()
|
|
||||||
doAppend(t, w1, makeCm(1))
|
|
||||||
doAppend(t, w1, makeCm(2))
|
|
||||||
doAppend(t, w1, makeCm(3))
|
|
||||||
doAppend(t, w1, makeCm(4))
|
|
||||||
doAppend(t, w1, makeCm(5))
|
|
||||||
doAppend(t, w1, makeCm(6))
|
|
||||||
doAppend(t, w1, makeCm(7))
|
|
||||||
doRemove(t, w1, makeCm(1).OrgId())
|
|
||||||
doRemove(t, w1, makeCm(3).OrgId())
|
|
||||||
doRemove(t, w1, makeCm(5).OrgId())
|
|
||||||
doRemove(t, w1, makeCm(7).OrgId())
|
|
||||||
|
|
||||||
w2 := New()
|
|
||||||
doAppend(t, w2, makeCm(2))
|
|
||||||
doAppend(t, w2, makeCm(4))
|
|
||||||
doAppend(t, w2, makeCm(6))
|
|
||||||
if !reflect.DeepEqual(w1, w1) {
|
|
||||||
w1.Debug("w1")
|
|
||||||
w2.Debug("w2")
|
|
||||||
t.Fatalf("mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := w2.Append(makeCm(6))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemove(t *testing.T) {
|
|
||||||
w := New()
|
|
||||||
r := makeCm(1)
|
|
||||||
err := w.Remove(r.OrgId())
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error")
|
|
||||||
}
|
|
||||||
err = w.Append(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
err = w.Remove(r.OrgId())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
err = w.Remove(r.OrgId())
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplace(t *testing.T) {
|
|
||||||
cm5 := makeCm(5)
|
|
||||||
cm700 := makeCm(700)
|
|
||||||
otherCm5 := makeCm(5)
|
|
||||||
|
|
||||||
w := New()
|
|
||||||
doAppend(t, w, makeCm(1))
|
|
||||||
doAppend(t, w, makeCm(2))
|
|
||||||
doAppend(t, w, makeCm(3))
|
|
||||||
doAppend(t, w, makeCm(4))
|
|
||||||
doAppend(t, w, cm5)
|
|
||||||
doAppend(t, w, makeCm(6))
|
|
||||||
doAppend(t, w, makeCm(7))
|
|
||||||
|
|
||||||
oldSize := w.Size()
|
|
||||||
_, err := w.Replace(otherCm5)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if w.Size() != oldSize {
|
|
||||||
t.Fatalf("unexpected size %d", w.Size())
|
|
||||||
}
|
|
||||||
if r, err := w.GetByCurrentId(cm5.OrgId()); err != nil || r != otherCm5 {
|
|
||||||
t.Fatalf("unexpected result r=%s, err=%v", r.CurId(), err)
|
|
||||||
}
|
|
||||||
if err := w.Append(cm5); err == nil {
|
|
||||||
t.Fatalf("expected id already there error")
|
|
||||||
}
|
|
||||||
if err := w.Remove(cm5.OrgId()); err != nil {
|
|
||||||
t.Fatalf("unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
if err := w.Append(cm700); err != nil {
|
|
||||||
t.Fatalf("unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
if err := w.Append(cm5); err != nil {
|
|
||||||
t.Fatalf("unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeAsYaml(t *testing.T) {
|
|
||||||
encoded := []byte(`apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: cm1
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: cm2
|
|
||||||
`)
|
|
||||||
input := resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
},
|
|
||||||
}).Add(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm2",
|
|
||||||
},
|
|
||||||
}).ResMap()
|
|
||||||
out, err := input.AsYaml()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(out, encoded) {
|
|
||||||
t.Fatalf("%s doesn't match expected %s", out, encoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
|
||||||
r1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "alice",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r2 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "bob",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r3 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "bob",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r4 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "charlie",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r5 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "charlie",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
|
|
||||||
|
|
||||||
result := m.GetMatchingResourcesByCurrentId(
|
|
||||||
resid.NewResId(cmap, "alice").GvknEquals)
|
|
||||||
if len(result) != 1 {
|
|
||||||
t.Fatalf("Expected single map entry but got %v", result)
|
|
||||||
}
|
|
||||||
result = m.GetMatchingResourcesByCurrentId(
|
|
||||||
resid.NewResId(cmap, "bob").GvknEquals)
|
|
||||||
if len(result) != 2 {
|
|
||||||
t.Fatalf("Expected two, got %v", result)
|
|
||||||
}
|
|
||||||
result = m.GetMatchingResourcesByCurrentId(
|
|
||||||
resid.NewResIdWithNamespace(cmap, "bob", "system").GvknEquals)
|
|
||||||
if len(result) != 2 {
|
|
||||||
t.Fatalf("Expected two but got %v", result)
|
|
||||||
}
|
|
||||||
result = m.GetMatchingResourcesByCurrentId(
|
|
||||||
resid.NewResIdWithNamespace(cmap, "bob", "happy").Equals)
|
|
||||||
if len(result) != 1 {
|
|
||||||
t.Fatalf("Expected single map entry but got %v", result)
|
|
||||||
}
|
|
||||||
result = m.GetMatchingResourcesByCurrentId(
|
|
||||||
resid.NewResId(cmap, "charlie").GvknEquals)
|
|
||||||
if len(result) != 1 {
|
|
||||||
t.Fatalf("Expected single map entry but got %v", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:goconst
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
matcher IdMatcher
|
|
||||||
count int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"match everything",
|
|
||||||
func(resid.ResId) bool { return true },
|
|
||||||
5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match nothing",
|
|
||||||
func(resid.ResId) bool { return false },
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name is alice",
|
|
||||||
func(x resid.ResId) bool { return x.Name == "alice" },
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name is charlie",
|
|
||||||
func(x resid.ResId) bool { return x.Name == "charlie" },
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name is bob",
|
|
||||||
func(x resid.ResId) bool { return x.Name == "bob" },
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"happy namespace",
|
|
||||||
func(x resid.ResId) bool {
|
|
||||||
return x.Namespace == "happy"
|
|
||||||
},
|
|
||||||
3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"happy deployment",
|
|
||||||
func(x resid.ResId) bool {
|
|
||||||
return x.Namespace == "happy" &&
|
|
||||||
x.Gvk.Kind == "Deployment"
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"happy ConfigMap",
|
|
||||||
func(x resid.ResId) bool {
|
|
||||||
return x.Namespace == "happy" &&
|
|
||||||
x.Gvk.Kind == "ConfigMap"
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tst := range tests {
|
|
||||||
result := m.GetMatchingResourcesByCurrentId(tst.matcher)
|
|
||||||
if len(result) != tst.count {
|
|
||||||
t.Fatalf("test '%s'; actual: %d, expected: %d",
|
|
||||||
tst.name, len(result), tst.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
|
||||||
r1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "alice",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r2 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "bob",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r3 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "bob",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r4 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "charlie",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r5 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "charlie",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r5.AddNamePrefix("little-")
|
|
||||||
r6 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "domino",
|
|
||||||
"namespace": "happy",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r6.AddNamePrefix("little-")
|
|
||||||
r7 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ClusterRoleBinding",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "meh",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
tests := map[string]struct {
|
|
||||||
filter *resource.Resource
|
|
||||||
expected ResMap
|
|
||||||
}{
|
|
||||||
"default namespace 1": {
|
|
||||||
filter: r2,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
|
||||||
},
|
|
||||||
"default namespace 2": {
|
|
||||||
filter: r1,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
|
||||||
},
|
|
||||||
"happy namespace no prefix": {
|
|
||||||
filter: r3,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
|
||||||
},
|
|
||||||
"happy namespace with prefix": {
|
|
||||||
filter: r5,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
|
||||||
},
|
|
||||||
"cluster level": {
|
|
||||||
filter: r7,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap()
|
|
||||||
for name, test := range tests {
|
|
||||||
test := test
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
|
||||||
err := test.expected.ErrorIfNotEqualLists(got)
|
|
||||||
if err != nil {
|
|
||||||
test.expected.Debug("expected")
|
|
||||||
got.Debug("actual")
|
|
||||||
t.Fatalf("Expected match")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPfxSfx(r *resource.Resource, prefixes []string, suffixes []string) {
|
|
||||||
for _, pfx := range prefixes {
|
|
||||||
r.AddNamePrefix(pfx)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sfx := range suffixes {
|
|
||||||
r.AddNameSuffix(sfx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubsetThatCouldBeReferencedByResourceMultiLevel(t *testing.T) {
|
|
||||||
// Simulates ConfigMap and Deployment defined at level 1
|
|
||||||
// No prefix nor suffix added at that level
|
|
||||||
cm1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(cm1, []string{""}, []string{""})
|
|
||||||
dep1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(dep1, []string{""}, []string{""})
|
|
||||||
|
|
||||||
// Simulates ConfigMap and Deployment defined at level 1
|
|
||||||
// and prefix added in level 2 of kustomization
|
|
||||||
cm2p := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level2p",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(cm2p, []string{"", "level2p-"}, []string{"", ""})
|
|
||||||
|
|
||||||
dep2p := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level2p",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(dep2p, []string{"", "level2p-"}, []string{"", ""})
|
|
||||||
|
|
||||||
// Simulates ConfigMap and Deployment defined at level 1
|
|
||||||
// and suffix added in level 2 of kustomization
|
|
||||||
cm2s := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level2s",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(cm2s, []string{"", ""}, []string{"", "-level2s"})
|
|
||||||
|
|
||||||
dep2s := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level2s",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(dep2s, []string{"", ""}, []string{"", "-level2s"})
|
|
||||||
|
|
||||||
// Simulates ConfigMap and Deployment defined at level 1,
|
|
||||||
// prefix added in levels 2 and 3 of kustomization.
|
|
||||||
cm3e := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level3e",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(cm3e, []string{"", "level2p-", "level3e-"}, []string{"", "", ""})
|
|
||||||
|
|
||||||
dep3e := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level3e",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(dep3e, []string{"", "level2p-", "level3e-"}, []string{"", "", ""})
|
|
||||||
|
|
||||||
// Simulates Deployment defined at level 1, ConfigMap defined at level 2,
|
|
||||||
// prefix added in levels 2 and 3 of kustomization.
|
|
||||||
// This reproduce issue 1440.
|
|
||||||
cm3i := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level3i",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(cm3i, []string{"level2p-", "level3i-"}, []string{"", ""})
|
|
||||||
|
|
||||||
dep3i := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "level3i",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
addPfxSfx(dep3i, []string{"", "level2p-", "level3i-"}, []string{"", "", ""})
|
|
||||||
|
|
||||||
tests := map[string]struct {
|
|
||||||
filter *resource.Resource
|
|
||||||
expected ResMap
|
|
||||||
}{
|
|
||||||
"level1": {
|
|
||||||
filter: dep1,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(cm1).AddR(dep1).ResMap(),
|
|
||||||
},
|
|
||||||
"level2p": {
|
|
||||||
filter: dep2p,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(cm2p).AddR(dep2p).ResMap(),
|
|
||||||
},
|
|
||||||
"level2s": {
|
|
||||||
filter: dep2s,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(cm2s).AddR(dep2s).ResMap(),
|
|
||||||
},
|
|
||||||
"level3p": {
|
|
||||||
filter: dep3e,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(cm3e).AddR(dep3e).ResMap(),
|
|
||||||
},
|
|
||||||
"level3i": {
|
|
||||||
filter: dep3i,
|
|
||||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(cm3i).AddR(dep3i).ResMap(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
||||||
AddR(cm1).AddR(dep1).AddR(cm2s).AddR(dep2s).AddR(cm2p).AddR(dep2p).AddR(cm3e).AddR(dep3e).AddR(cm3i).AddR(dep3i).ResMap()
|
|
||||||
for name, test := range tests {
|
|
||||||
test := test
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
|
||||||
err := test.expected.ErrorIfNotEqualLists(got)
|
|
||||||
if err != nil {
|
|
||||||
test.expected.Debug("expected")
|
|
||||||
got.Debug("actual")
|
|
||||||
t.Fatalf("Expected match")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeepCopy(t *testing.T) {
|
|
||||||
rm1 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
},
|
|
||||||
}).Add(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm2",
|
|
||||||
},
|
|
||||||
}).ResMap()
|
|
||||||
|
|
||||||
rm2 := rm1.DeepCopy()
|
|
||||||
|
|
||||||
if &rm1 == &rm2 {
|
|
||||||
t.Fatal("DeepCopy returned a reference to itself instead of a copy")
|
|
||||||
}
|
|
||||||
err := rm1.ErrorIfNotEqualLists(rm1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorIfNotEqualSets(t *testing.T) {
|
|
||||||
r1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r2 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm2",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r3 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm2",
|
|
||||||
"namespace": "system",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualSets(m1); err != nil {
|
|
||||||
t.Fatalf("object should equal itself %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualSets(m2); err == nil {
|
|
||||||
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m3 := resmaptest_test.NewRmBuilder(t, rf).AddR(r2).ResMap()
|
|
||||||
if err := m2.ErrorIfNotEqualSets(m3); err == nil {
|
|
||||||
t.Fatalf("%v should not equal %v %v", m2, m3, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m3 = resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
}}).ResMap()
|
|
||||||
if err := m2.ErrorIfNotEqualSets(m3); err != nil {
|
|
||||||
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m4 = m1.ShallowCopy()
|
|
||||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
m4 = m1.DeepCopy()
|
|
||||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorIfNotEqualLists(t *testing.T) {
|
|
||||||
r1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r2 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm2",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r3 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm2",
|
|
||||||
"namespace": "system",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m1); err != nil {
|
|
||||||
t.Fatalf("object should equal itself %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m2); err == nil {
|
|
||||||
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m3 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
}}).ResMap()
|
|
||||||
if err := m2.ErrorIfNotEqualLists(m3); err != nil {
|
|
||||||
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m4); err == nil {
|
|
||||||
t.Fatalf("expected inequality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m4 = m1.ShallowCopy()
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
m4 = m1.DeepCopy()
|
|
||||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
|
||||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppendAll(t *testing.T) {
|
|
||||||
r1 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "foo-deploy1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
input1 := rmF.FromResource(r1)
|
|
||||||
r2 := rf.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "StatefulSet",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "bar-stateful",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
input2 := rmF.FromResource(r2)
|
|
||||||
|
|
||||||
expected := New()
|
|
||||||
if err := expected.Append(r1); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := expected.Append(r2); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := input1.AppendAll(input2); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
|
||||||
input1.Debug("1")
|
|
||||||
expected.Debug("ex")
|
|
||||||
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
|
||||||
}
|
|
||||||
if err := input1.AppendAll(nil); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
|
||||||
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMap1() ResMap {
|
|
||||||
return rmF.FromResource(rf.FromMapAndOption(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cmap",
|
|
||||||
},
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"a": "x",
|
|
||||||
"b": "y",
|
|
||||||
},
|
|
||||||
}, &types.GeneratorArgs{
|
|
||||||
Behavior: "create",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMap2(b types.GenerationBehavior) ResMap {
|
|
||||||
return rmF.FromResource(rf.FromMapAndOption(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cmap",
|
|
||||||
},
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"a": "u",
|
|
||||||
"b": "v",
|
|
||||||
"c": "w",
|
|
||||||
},
|
|
||||||
}, &types.GeneratorArgs{
|
|
||||||
Behavior: b.String(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbsorbAll(t *testing.T) {
|
|
||||||
expected := rmF.FromResource(rf.FromMapAndOption(
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"annotations": map[string]interface{}{},
|
|
||||||
"labels": map[string]interface{}{},
|
|
||||||
"name": "cmap",
|
|
||||||
},
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"a": "u",
|
|
||||||
"b": "v",
|
|
||||||
"c": "w",
|
|
||||||
},
|
|
||||||
}, &types.GeneratorArgs{
|
|
||||||
Behavior: "create",
|
|
||||||
}))
|
|
||||||
w := makeMap1()
|
|
||||||
if err := w.AbsorbAll(makeMap2(types.BehaviorMerge)); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := expected.ErrorIfNotEqualLists(w); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
w = makeMap1()
|
|
||||||
if err := w.AbsorbAll(nil); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := w.ErrorIfNotEqualLists(makeMap1()); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
w = makeMap1()
|
|
||||||
w2 := makeMap2(types.BehaviorReplace)
|
|
||||||
if err := w.AbsorbAll(w2); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := w2.ErrorIfNotEqualLists(w); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
w = makeMap1()
|
|
||||||
w2 = makeMap2(types.BehaviorUnspecified)
|
|
||||||
err := w.AbsorbAll(w2)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error with unspecified behavior")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
581
api/resmap/reswrangler.go
Normal file
581
api/resmap/reswrangler.go
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package resmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resWrangler implements ResMap.
|
||||||
|
type resWrangler struct {
|
||||||
|
// Resource list maintained in load (append) order.
|
||||||
|
// This is important for transformers, which must
|
||||||
|
// be performed in a specific order, and for users
|
||||||
|
// who for whatever reasons wish the order they
|
||||||
|
// specify in kustomizations to be maintained and
|
||||||
|
// available as an option for final YAML rendering.
|
||||||
|
rList []*resource.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOne() *resWrangler {
|
||||||
|
result := &resWrangler{}
|
||||||
|
result.Clear()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear implements ResMap.
|
||||||
|
func (m *resWrangler) Clear() {
|
||||||
|
m.rList = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size implements ResMap.
|
||||||
|
func (m *resWrangler) Size() int {
|
||||||
|
return len(m.rList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resWrangler) indexOfResource(other *resource.Resource) int {
|
||||||
|
for i, r := range m.rList {
|
||||||
|
if r == other {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resources implements ResMap.
|
||||||
|
func (m *resWrangler) Resources() []*resource.Resource {
|
||||||
|
tmp := make([]*resource.Resource, len(m.rList))
|
||||||
|
copy(tmp, m.rList)
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append implements ResMap.
|
||||||
|
func (m *resWrangler) Append(res *resource.Resource) error {
|
||||||
|
id := res.CurId()
|
||||||
|
if r := m.GetMatchingResourcesByCurrentId(id.Equals); len(r) > 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"may not add resource with an already registered id: %s", id)
|
||||||
|
}
|
||||||
|
m.rList = append(m.rList, res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove implements ResMap.
|
||||||
|
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||||
|
tmp := newOne()
|
||||||
|
for _, r := range m.rList {
|
||||||
|
if r.CurId() != adios {
|
||||||
|
tmp.Append(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tmp.Size() != m.Size()-1 {
|
||||||
|
return fmt.Errorf("id %s not found in removal", adios)
|
||||||
|
}
|
||||||
|
m.rList = tmp.rList
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace implements ResMap.
|
||||||
|
func (m *resWrangler) Replace(res *resource.Resource) (int, error) {
|
||||||
|
id := res.CurId()
|
||||||
|
i, err := m.GetIndexOfCurrentId(id)
|
||||||
|
if err != nil {
|
||||||
|
return -1, errors.Wrap(err, "in Replace")
|
||||||
|
}
|
||||||
|
if i < 0 {
|
||||||
|
return -1, fmt.Errorf("cannot find resource with id %s to replace", id)
|
||||||
|
}
|
||||||
|
m.rList[i] = res
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllIds implements ResMap.
|
||||||
|
func (m *resWrangler) AllIds() (ids []resid.ResId) {
|
||||||
|
ids = make([]resid.ResId, m.Size())
|
||||||
|
for i, r := range m.rList {
|
||||||
|
ids[i] = r.CurId()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug implements ResMap.
|
||||||
|
func (m *resWrangler) Debug(title string) {
|
||||||
|
fmt.Println("--------------------------- " + title)
|
||||||
|
firstObj := true
|
||||||
|
for i, r := range m.rList {
|
||||||
|
if firstObj {
|
||||||
|
firstObj = false
|
||||||
|
} else {
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
fmt.Printf("# %d %s\n", i, r.OrgId())
|
||||||
|
blob, err := yaml.Marshal(r.Map())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(blob))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IdMatcher func(resid.ResId) bool
|
||||||
|
|
||||||
|
// GetByIndex implements ResMap.
|
||||||
|
func (m *resWrangler) GetByIndex(i int) *resource.Resource {
|
||||||
|
if i < 0 || i >= m.Size() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m.rList[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIndexOfCurrentId implements ResMap.
|
||||||
|
func (m *resWrangler) GetIndexOfCurrentId(id resid.ResId) (int, error) {
|
||||||
|
count := 0
|
||||||
|
result := -1
|
||||||
|
for i, r := range m.rList {
|
||||||
|
if id.Equals(r.CurId()) {
|
||||||
|
count++
|
||||||
|
result = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 1 {
|
||||||
|
return -1, fmt.Errorf("id matched %d resources", count)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IdFromResource func(r *resource.Resource) resid.ResId
|
||||||
|
|
||||||
|
func GetOriginalId(r *resource.Resource) resid.ResId { return r.OrgId() }
|
||||||
|
func GetCurrentId(r *resource.Resource) resid.ResId { return r.CurId() }
|
||||||
|
|
||||||
|
// GetMatchingResourcesByCurrentId implements ResMap.
|
||||||
|
func (m *resWrangler) GetMatchingResourcesByCurrentId(
|
||||||
|
matches IdMatcher) []*resource.Resource {
|
||||||
|
return m.filteredById(matches, GetCurrentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMatchingResourcesByOriginalId implements ResMap.
|
||||||
|
func (m *resWrangler) GetMatchingResourcesByOriginalId(
|
||||||
|
matches IdMatcher) []*resource.Resource {
|
||||||
|
return m.filteredById(matches, GetOriginalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resWrangler) filteredById(
|
||||||
|
matches IdMatcher, idGetter IdFromResource) []*resource.Resource {
|
||||||
|
var result []*resource.Resource
|
||||||
|
for _, r := range m.rList {
|
||||||
|
if matches(idGetter(r)) {
|
||||||
|
result = append(result, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByCurrentId implements ResMap.
|
||||||
|
func (m *resWrangler) GetByCurrentId(
|
||||||
|
id resid.ResId) (*resource.Resource, error) {
|
||||||
|
return demandOneMatch(m.GetMatchingResourcesByCurrentId, id, "Current")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByOriginalId implements ResMap.
|
||||||
|
func (m *resWrangler) GetByOriginalId(
|
||||||
|
id resid.ResId) (*resource.Resource, error) {
|
||||||
|
return demandOneMatch(m.GetMatchingResourcesByOriginalId, id, "Original")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetById implements ResMap.
|
||||||
|
func (m *resWrangler) GetById(
|
||||||
|
id resid.ResId) (*resource.Resource, error) {
|
||||||
|
match, err1 := m.GetByOriginalId(id)
|
||||||
|
if err1 == nil {
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
match, err2 := m.GetByCurrentId(id)
|
||||||
|
if err2 == nil {
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"%s; %s; failed to find unique target for patch %s",
|
||||||
|
err1.Error(), err2.Error(), id.GvknString())
|
||||||
|
}
|
||||||
|
|
||||||
|
type resFinder func(IdMatcher) []*resource.Resource
|
||||||
|
|
||||||
|
func demandOneMatch(
|
||||||
|
f resFinder, id resid.ResId, s string) (*resource.Resource, error) {
|
||||||
|
r := f(id.Equals)
|
||||||
|
if len(r) == 1 {
|
||||||
|
return r[0], nil
|
||||||
|
}
|
||||||
|
if len(r) > 1 {
|
||||||
|
return nil, fmt.Errorf("multiple matches for %sId %s", s, id)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no matches for %sId %s", s, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
|
||||||
|
func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||||
|
items := m.groupedByCurrentNamespace()
|
||||||
|
delete(items, resid.TotallyNotANamespace)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonNamespaceable implements ResMap.NonNamespaceable
|
||||||
|
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
||||||
|
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resWrangler) groupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||||
|
byNamespace := make(map[string][]*resource.Resource)
|
||||||
|
for _, res := range m.rList {
|
||||||
|
namespace := res.CurId().EffectiveNamespace()
|
||||||
|
if _, found := byNamespace[namespace]; !found {
|
||||||
|
byNamespace[namespace] = []*resource.Resource{}
|
||||||
|
}
|
||||||
|
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||||
|
}
|
||||||
|
return byNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupedByNamespace implements ResMap.GroupByOrginalNamespace
|
||||||
|
func (m *resWrangler) GroupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||||
|
items := m.groupedByOriginalNamespace()
|
||||||
|
delete(items, resid.TotallyNotANamespace)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resWrangler) groupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||||
|
byNamespace := make(map[string][]*resource.Resource)
|
||||||
|
for _, res := range m.rList {
|
||||||
|
namespace := res.OrgId().EffectiveNamespace()
|
||||||
|
if _, found := byNamespace[namespace]; !found {
|
||||||
|
byNamespace[namespace] = []*resource.Resource{}
|
||||||
|
}
|
||||||
|
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||||
|
}
|
||||||
|
return byNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsYaml implements ResMap.
|
||||||
|
func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||||
|
firstObj := true
|
||||||
|
var b []byte
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
for _, res := range m.Resources() {
|
||||||
|
out, err := yaml.Marshal(res.Map())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if firstObj {
|
||||||
|
firstObj = false
|
||||||
|
} else {
|
||||||
|
if _, err = buf.WriteString("---\n"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err = buf.Write(out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorIfNotEqualSets implements ResMap.
|
||||||
|
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||||
|
m2, ok := other.(*resWrangler)
|
||||||
|
if !ok {
|
||||||
|
panic("bad cast")
|
||||||
|
}
|
||||||
|
if m.Size() != m2.Size() {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"lists have different number of entries: %#v doesn't equal %#v",
|
||||||
|
m.rList, m2.rList)
|
||||||
|
}
|
||||||
|
seen := make(map[int]bool)
|
||||||
|
for _, r1 := range m.rList {
|
||||||
|
id := r1.CurId()
|
||||||
|
others := m2.GetMatchingResourcesByCurrentId(id.Equals)
|
||||||
|
if len(others) == 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"id in self missing from other; id: %s", id)
|
||||||
|
}
|
||||||
|
if len(others) > 1 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"id in self matches %d in other; id: %s", len(others), id)
|
||||||
|
}
|
||||||
|
r2 := others[0]
|
||||||
|
if !r1.KunstructEqual(r2) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
||||||
|
r1, r2, r1, r2)
|
||||||
|
}
|
||||||
|
seen[m2.indexOfResource(r2)] = true
|
||||||
|
}
|
||||||
|
if len(seen) != m.Size() {
|
||||||
|
return fmt.Errorf("counting problem %d != %d", len(seen), m.Size())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorIfNotEqualList implements ResMap.
|
||||||
|
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
||||||
|
m2, ok := other.(*resWrangler)
|
||||||
|
if !ok {
|
||||||
|
panic("bad cast")
|
||||||
|
}
|
||||||
|
if m.Size() != m2.Size() {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"lists have different number of entries: %#v doesn't equal %#v",
|
||||||
|
m.rList, m2.rList)
|
||||||
|
}
|
||||||
|
for i, r1 := range m.rList {
|
||||||
|
r2 := m2.rList[i]
|
||||||
|
if !r1.Equals(r2) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Item i=%d differs:\n n1 = %s\n n2 = %s\n o1 = %s\n o2 = %s\n",
|
||||||
|
i, r1.OrgId(), r2.OrgId(), r1, r2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type resCopier func(r *resource.Resource) *resource.Resource
|
||||||
|
|
||||||
|
// ShallowCopy implements ResMap.
|
||||||
|
func (m *resWrangler) ShallowCopy() ResMap {
|
||||||
|
return m.makeCopy(
|
||||||
|
func(r *resource.Resource) *resource.Resource {
|
||||||
|
return r
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy implements ResMap.
|
||||||
|
func (m *resWrangler) DeepCopy() ResMap {
|
||||||
|
return m.makeCopy(
|
||||||
|
func(r *resource.Resource) *resource.Resource {
|
||||||
|
return r.DeepCopy()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCopy copies the ResMap.
|
||||||
|
func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
||||||
|
result := &resWrangler{}
|
||||||
|
result.rList = make([]*resource.Resource, m.Size())
|
||||||
|
for i, r := range m.rList {
|
||||||
|
result.rList[i] = copier(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
||||||
|
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||||
|
inputRes *resource.Resource) ResMap {
|
||||||
|
result := newOne()
|
||||||
|
inputId := inputRes.CurId()
|
||||||
|
isInputIdNamespaceable := inputId.IsNamespaceableKind()
|
||||||
|
subjectNamespaces := getNamespacesForRoleBinding(inputRes)
|
||||||
|
for _, r := range m.Resources() {
|
||||||
|
// Need to match more accuratly both at the time of selection and transformation.
|
||||||
|
// OutmostPrefixSuffixEquals is not accurate enough since it is only using
|
||||||
|
// the outer most suffix and the last prefix. Use PrefixedSuffixesEquals instead.
|
||||||
|
resId := r.CurId()
|
||||||
|
if !isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId) ||
|
||||||
|
isRoleBindingNamespace(&subjectNamespaces, r.GetNamespace()) {
|
||||||
|
result.append(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRoleBindingNamespace returns true is the namespace `ns` is in role binding
|
||||||
|
// namespaces `m`
|
||||||
|
func isRoleBindingNamespace(m *map[string]bool, ns string) bool {
|
||||||
|
return (*m)[ns]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNamespacesForRoleBinding returns referenced ServiceAccount namespaces if the inputRes is
|
||||||
|
// a RoleBinding
|
||||||
|
func getNamespacesForRoleBinding(inputRes *resource.Resource) map[string]bool {
|
||||||
|
res := make(map[string]bool)
|
||||||
|
if inputRes.GetKind() != "RoleBinding" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
subjects, err := inputRes.GetSlice("subjects")
|
||||||
|
if err != nil || subjects == nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range subjects {
|
||||||
|
subject := s.(map[string]interface{})
|
||||||
|
if subject["namespace"] == nil || subject["kind"] == nil ||
|
||||||
|
subject["kind"].(string) != "ServiceAccount" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res[subject["namespace"].(string)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resWrangler) append(res *resource.Resource) {
|
||||||
|
m.rList = append(m.rList, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendAll implements ResMap.
|
||||||
|
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, res := range other.Resources() {
|
||||||
|
if err := m.Append(res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsorbAll implements ResMap.
|
||||||
|
func (m *resWrangler) AbsorbAll(other ResMap) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, r := range other.Resources() {
|
||||||
|
err := m.appendReplaceOrMerge(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *resWrangler) appendReplaceOrMerge(
|
||||||
|
res *resource.Resource) error {
|
||||||
|
id := res.CurId()
|
||||||
|
matches := m.GetMatchingResourcesByOriginalId(id.Equals)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
matches = m.GetMatchingResourcesByCurrentId(id.Equals)
|
||||||
|
}
|
||||||
|
switch len(matches) {
|
||||||
|
case 0:
|
||||||
|
switch res.Behavior() {
|
||||||
|
case types.BehaviorMerge, types.BehaviorReplace:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"id %#v does not exist; cannot merge or replace", id)
|
||||||
|
default:
|
||||||
|
// presumably types.BehaviorCreate
|
||||||
|
err := m.Append(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
old := matches[0]
|
||||||
|
if old == nil {
|
||||||
|
return fmt.Errorf("id lookup failure")
|
||||||
|
}
|
||||||
|
index := m.indexOfResource(old)
|
||||||
|
if index < 0 {
|
||||||
|
return fmt.Errorf("indexing problem")
|
||||||
|
}
|
||||||
|
switch res.Behavior() {
|
||||||
|
case types.BehaviorReplace:
|
||||||
|
res.Replace(old)
|
||||||
|
case types.BehaviorMerge:
|
||||||
|
res.Merge(old)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"id %#v exists; behavior must be merge or replace", id)
|
||||||
|
}
|
||||||
|
i, err := m.Replace(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if i != index {
|
||||||
|
return fmt.Errorf("unexpected index in replacement")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"found multiple objects %v that could accept merge of %v",
|
||||||
|
matches, id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func anchorRegex(pattern string) string {
|
||||||
|
if pattern == "" {
|
||||||
|
return pattern
|
||||||
|
}
|
||||||
|
return "^(?:" + pattern + ")$"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select returns a list of resources that
|
||||||
|
// are selected by a Selector
|
||||||
|
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
||||||
|
ns := regexp.MustCompile(anchorRegex(s.Namespace))
|
||||||
|
nm := regexp.MustCompile(anchorRegex(s.Name))
|
||||||
|
var result []*resource.Resource
|
||||||
|
for _, r := range m.Resources() {
|
||||||
|
curId := r.CurId()
|
||||||
|
orgId := r.OrgId()
|
||||||
|
|
||||||
|
// matches the namespace when namespace is not empty in the selector
|
||||||
|
// It first tries to match with the original namespace
|
||||||
|
// then matches with the current namespace
|
||||||
|
if s.Namespace != "" {
|
||||||
|
matched := ns.MatchString(orgId.EffectiveNamespace())
|
||||||
|
if !matched {
|
||||||
|
matched = ns.MatchString(curId.EffectiveNamespace())
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches the name when name is not empty in the selector
|
||||||
|
// It first tries to match with the original name
|
||||||
|
// then matches with the current name
|
||||||
|
if s.Name != "" {
|
||||||
|
matched := nm.MatchString(orgId.Name)
|
||||||
|
if !matched {
|
||||||
|
matched = nm.MatchString(curId.Name)
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches the GVK
|
||||||
|
if !r.GetGvk().IsSelected(&s.Gvk) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches the label selector
|
||||||
|
matched, err := r.MatchesLabelSelector(s.LabelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches the annotation selector
|
||||||
|
matched, err = r.MatchesAnnotationSelector(s.AnnotationSelector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, r)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
734
api/resmap/reswrangler_test.go
Normal file
734
api/resmap/reswrangler_test.go
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package resmap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||||
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
|
. "sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rf = resource.NewFactory(
|
||||||
|
kunstruct.NewKunstructuredFactoryImpl())
|
||||||
|
var rmF = NewFactory(rf, nil)
|
||||||
|
|
||||||
|
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
|
||||||
|
err := w.Append(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("append error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func doRemove(t *testing.T, w ResMap, id resid.ResId) {
|
||||||
|
err := w.Remove(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("remove error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a resource with a predictable name.
|
||||||
|
func makeCm(i int) *resource.Resource {
|
||||||
|
return rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": fmt.Sprintf("cm%03d", i),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintain the class invariant that no two
|
||||||
|
// resources can have the same CurId().
|
||||||
|
func TestAppendRejectsDuplicateResId(t *testing.T) {
|
||||||
|
w := New()
|
||||||
|
if err := w.Append(makeCm(1)); err != nil {
|
||||||
|
t.Fatalf("append error: %v", err)
|
||||||
|
}
|
||||||
|
err := w.Append(makeCm(1))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected append error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(
|
||||||
|
err.Error(),
|
||||||
|
"may not add resource with an already registered id") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendRemove(t *testing.T) {
|
||||||
|
w1 := New()
|
||||||
|
doAppend(t, w1, makeCm(1))
|
||||||
|
doAppend(t, w1, makeCm(2))
|
||||||
|
doAppend(t, w1, makeCm(3))
|
||||||
|
doAppend(t, w1, makeCm(4))
|
||||||
|
doAppend(t, w1, makeCm(5))
|
||||||
|
doAppend(t, w1, makeCm(6))
|
||||||
|
doAppend(t, w1, makeCm(7))
|
||||||
|
doRemove(t, w1, makeCm(1).OrgId())
|
||||||
|
doRemove(t, w1, makeCm(3).OrgId())
|
||||||
|
doRemove(t, w1, makeCm(5).OrgId())
|
||||||
|
doRemove(t, w1, makeCm(7).OrgId())
|
||||||
|
|
||||||
|
w2 := New()
|
||||||
|
doAppend(t, w2, makeCm(2))
|
||||||
|
doAppend(t, w2, makeCm(4))
|
||||||
|
doAppend(t, w2, makeCm(6))
|
||||||
|
if !reflect.DeepEqual(w1, w1) {
|
||||||
|
w1.Debug("w1")
|
||||||
|
w2.Debug("w2")
|
||||||
|
t.Fatalf("mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w2.Append(makeCm(6))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemove(t *testing.T) {
|
||||||
|
w := New()
|
||||||
|
r := makeCm(1)
|
||||||
|
err := w.Remove(r.OrgId())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
err = w.Append(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = w.Remove(r.OrgId())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = w.Remove(r.OrgId())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplace(t *testing.T) {
|
||||||
|
cm5 := makeCm(5)
|
||||||
|
cm700 := makeCm(700)
|
||||||
|
otherCm5 := makeCm(5)
|
||||||
|
|
||||||
|
w := New()
|
||||||
|
doAppend(t, w, makeCm(1))
|
||||||
|
doAppend(t, w, makeCm(2))
|
||||||
|
doAppend(t, w, makeCm(3))
|
||||||
|
doAppend(t, w, makeCm(4))
|
||||||
|
doAppend(t, w, cm5)
|
||||||
|
doAppend(t, w, makeCm(6))
|
||||||
|
doAppend(t, w, makeCm(7))
|
||||||
|
|
||||||
|
oldSize := w.Size()
|
||||||
|
_, err := w.Replace(otherCm5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if w.Size() != oldSize {
|
||||||
|
t.Fatalf("unexpected size %d", w.Size())
|
||||||
|
}
|
||||||
|
if r, err := w.GetByCurrentId(cm5.OrgId()); err != nil || r != otherCm5 {
|
||||||
|
t.Fatalf("unexpected result r=%s, err=%v", r.CurId(), err)
|
||||||
|
}
|
||||||
|
if err := w.Append(cm5); err == nil {
|
||||||
|
t.Fatalf("expected id already there error")
|
||||||
|
}
|
||||||
|
if err := w.Remove(cm5.OrgId()); err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
if err := w.Append(cm700); err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
if err := w.Append(cm5); err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeAsYaml(t *testing.T) {
|
||||||
|
encoded := []byte(`apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm2
|
||||||
|
`)
|
||||||
|
input := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
}).Add(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
},
|
||||||
|
}).ResMap()
|
||||||
|
out, err := input.AsYaml()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(out, encoded) {
|
||||||
|
t.Fatalf("%s doesn't match expected %s", out, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
||||||
|
r1 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "alice",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r2 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "bob",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r3 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "bob",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r4 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "charlie",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r5 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "charlie",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
|
||||||
|
|
||||||
|
result := m.GetMatchingResourcesByCurrentId(
|
||||||
|
resid.NewResId(cmap, "alice").GvknEquals)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("Expected single map entry but got %v", result)
|
||||||
|
}
|
||||||
|
result = m.GetMatchingResourcesByCurrentId(
|
||||||
|
resid.NewResId(cmap, "bob").GvknEquals)
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Fatalf("Expected two, got %v", result)
|
||||||
|
}
|
||||||
|
result = m.GetMatchingResourcesByCurrentId(
|
||||||
|
resid.NewResIdWithNamespace(cmap, "bob", "system").GvknEquals)
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Fatalf("Expected two but got %v", result)
|
||||||
|
}
|
||||||
|
result = m.GetMatchingResourcesByCurrentId(
|
||||||
|
resid.NewResIdWithNamespace(cmap, "bob", "happy").Equals)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("Expected single map entry but got %v", result)
|
||||||
|
}
|
||||||
|
result = m.GetMatchingResourcesByCurrentId(
|
||||||
|
resid.NewResId(cmap, "charlie").GvknEquals)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("Expected single map entry but got %v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:goconst
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
matcher IdMatcher
|
||||||
|
count int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"match everything",
|
||||||
|
func(resid.ResId) bool { return true },
|
||||||
|
5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match nothing",
|
||||||
|
func(resid.ResId) bool { return false },
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name is alice",
|
||||||
|
func(x resid.ResId) bool { return x.Name == "alice" },
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name is charlie",
|
||||||
|
func(x resid.ResId) bool { return x.Name == "charlie" },
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name is bob",
|
||||||
|
func(x resid.ResId) bool { return x.Name == "bob" },
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"happy namespace",
|
||||||
|
func(x resid.ResId) bool {
|
||||||
|
return x.Namespace == "happy"
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"happy deployment",
|
||||||
|
func(x resid.ResId) bool {
|
||||||
|
return x.Namespace == "happy" &&
|
||||||
|
x.Gvk.Kind == "Deployment"
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"happy ConfigMap",
|
||||||
|
func(x resid.ResId) bool {
|
||||||
|
return x.Namespace == "happy" &&
|
||||||
|
x.Gvk.Kind == "ConfigMap"
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tst := range tests {
|
||||||
|
result := m.GetMatchingResourcesByCurrentId(tst.matcher)
|
||||||
|
if len(result) != tst.count {
|
||||||
|
t.Fatalf("test '%s'; actual: %d, expected: %d",
|
||||||
|
tst.name, len(result), tst.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
||||||
|
r1 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "alice",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r2 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "bob",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r3 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "bob",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r4 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "charlie",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r5 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "charlie",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r5.AddNamePrefix("little-")
|
||||||
|
r6 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "domino",
|
||||||
|
"namespace": "happy",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r6.AddNamePrefix("little-")
|
||||||
|
r7 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||||
|
"kind": "ClusterRoleBinding",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "meh",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
filter *resource.Resource
|
||||||
|
expected ResMap
|
||||||
|
}{
|
||||||
|
"default namespace 1": {
|
||||||
|
filter: r2,
|
||||||
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
||||||
|
},
|
||||||
|
"default namespace 2": {
|
||||||
|
filter: r1,
|
||||||
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
||||||
|
},
|
||||||
|
"happy namespace no prefix": {
|
||||||
|
filter: r3,
|
||||||
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||||
|
},
|
||||||
|
"happy namespace with prefix": {
|
||||||
|
filter: r5,
|
||||||
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||||
|
},
|
||||||
|
"cluster level": {
|
||||||
|
filter: r7,
|
||||||
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||||
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap()
|
||||||
|
for name, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
||||||
|
err := test.expected.ErrorIfNotEqualLists(got)
|
||||||
|
if err != nil {
|
||||||
|
test.expected.Debug("expected")
|
||||||
|
got.Debug("actual")
|
||||||
|
t.Fatalf("Expected match")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeepCopy(t *testing.T) {
|
||||||
|
rm1 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
}).Add(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
},
|
||||||
|
}).ResMap()
|
||||||
|
|
||||||
|
rm2 := rm1.DeepCopy()
|
||||||
|
|
||||||
|
if &rm1 == &rm2 {
|
||||||
|
t.Fatal("DeepCopy returned a reference to itself instead of a copy")
|
||||||
|
}
|
||||||
|
err := rm1.ErrorIfNotEqualLists(rm1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorIfNotEqualSets(t *testing.T) {
|
||||||
|
r1 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r2 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r3 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
"namespace": "system",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualSets(m1); err != nil {
|
||||||
|
t.Fatalf("object should equal itself %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualSets(m2); err == nil {
|
||||||
|
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m3 := resmaptest_test.NewRmBuilder(t, rf).AddR(r2).ResMap()
|
||||||
|
if err := m2.ErrorIfNotEqualSets(m3); err == nil {
|
||||||
|
t.Fatalf("%v should not equal %v %v", m2, m3, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m3 = resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
}}).ResMap()
|
||||||
|
if err := m2.ErrorIfNotEqualSets(m3); err != nil {
|
||||||
|
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 = m1.ShallowCopy()
|
||||||
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
m4 = m1.DeepCopy()
|
||||||
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorIfNotEqualLists(t *testing.T) {
|
||||||
|
r1 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r2 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r3 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
"namespace": "system",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualLists(m1); err != nil {
|
||||||
|
t.Fatalf("object should equal itself %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualLists(m2); err == nil {
|
||||||
|
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m3 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
}}).ResMap()
|
||||||
|
if err := m2.ErrorIfNotEqualLists(m3); err != nil {
|
||||||
|
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
||||||
|
if err := m1.ErrorIfNotEqualLists(m4); err == nil {
|
||||||
|
t.Fatalf("expected inequality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 = m1.ShallowCopy()
|
||||||
|
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
m4 = m1.DeepCopy()
|
||||||
|
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||||
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendAll(t *testing.T) {
|
||||||
|
r1 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "foo-deploy1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
input1 := rmF.FromResource(r1)
|
||||||
|
r2 := rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "StatefulSet",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "bar-stateful",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
input2 := rmF.FromResource(r2)
|
||||||
|
|
||||||
|
expected := New()
|
||||||
|
if err := expected.Append(r1); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := expected.Append(r2); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := input1.AppendAll(input2); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
||||||
|
input1.Debug("1")
|
||||||
|
expected.Debug("ex")
|
||||||
|
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
||||||
|
}
|
||||||
|
if err := input1.AppendAll(nil); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
||||||
|
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMap1() ResMap {
|
||||||
|
return rmF.FromResource(rf.FromMapAndOption(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cmap",
|
||||||
|
},
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"a": "x",
|
||||||
|
"b": "y",
|
||||||
|
},
|
||||||
|
}, &types.GeneratorArgs{
|
||||||
|
Behavior: "create",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMap2(b types.GenerationBehavior) ResMap {
|
||||||
|
return rmF.FromResource(rf.FromMapAndOption(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cmap",
|
||||||
|
},
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"a": "u",
|
||||||
|
"b": "v",
|
||||||
|
"c": "w",
|
||||||
|
},
|
||||||
|
}, &types.GeneratorArgs{
|
||||||
|
Behavior: b.String(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsorbAll(t *testing.T) {
|
||||||
|
expected := rmF.FromResource(rf.FromMapAndOption(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"annotations": map[string]interface{}{},
|
||||||
|
"labels": map[string]interface{}{},
|
||||||
|
"name": "cmap",
|
||||||
|
},
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"a": "u",
|
||||||
|
"b": "v",
|
||||||
|
"c": "w",
|
||||||
|
},
|
||||||
|
}, &types.GeneratorArgs{
|
||||||
|
Behavior: "create",
|
||||||
|
}))
|
||||||
|
w := makeMap1()
|
||||||
|
if err := w.AbsorbAll(makeMap2(types.BehaviorMerge)); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := expected.ErrorIfNotEqualLists(w); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
w = makeMap1()
|
||||||
|
if err := w.AbsorbAll(nil); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := w.ErrorIfNotEqualLists(makeMap1()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
w = makeMap1()
|
||||||
|
w2 := makeMap2(types.BehaviorReplace)
|
||||||
|
if err := w.AbsorbAll(w2); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := w2.ErrorIfNotEqualLists(w); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
w = makeMap1()
|
||||||
|
w2 = makeMap2(types.BehaviorUnspecified)
|
||||||
|
err := w.AbsorbAll(w2)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error with unspecified behavior")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -164,6 +164,18 @@ func TestFindPatchTargets(t *testing.T) {
|
|||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: types.Selector{
|
||||||
|
Namespace: "NotMatched",
|
||||||
|
},
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: types.Selector{
|
||||||
|
Namespace: "ns1",
|
||||||
|
},
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
actual, err := rm.Select(testcase.target)
|
actual, err := rm.Select(testcase.target)
|
||||||
|
|||||||
@@ -14,55 +14,9 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resource is a representation of a Kubernetes Resource Model object paired
|
// Resource is a representation of a Kubernetes Resource Model (KRM) object
|
||||||
// with metadata used by kustomize.
|
// paired with metadata used by kustomize.
|
||||||
//
|
// For more history, see sigs.k8s.io/kustomize/api/ifc.Unstructured
|
||||||
// At time of writing Resource is changing from being based on an object
|
|
||||||
// sitting behind
|
|
||||||
//
|
|
||||||
// sigs.k8s.io/kustomize/api/ifc.Unstructured
|
|
||||||
//
|
|
||||||
// to being based on an instance of
|
|
||||||
//
|
|
||||||
// sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
|
||||||
//
|
|
||||||
// Ultimately, use of the Resource struct in kustomize should be entirely
|
|
||||||
// replaced by instances of RNode for direct use in kyaml, with all metadata
|
|
||||||
// stored directly in the RNodes as short-lived annotations, to be deleted
|
|
||||||
// before final output as part of a final filtration step. Using annotations
|
|
||||||
// will allow pipelined KRM Config Functions to share metadata that would
|
|
||||||
// otherwise be hidden in kustomize-only structures.
|
|
||||||
//
|
|
||||||
// ifc.Unstructured is an interface hiding an instance of
|
|
||||||
// sigs.k8s.io/kustomize/api/k8sdeps/kunstruct.UnstructAdapter
|
|
||||||
// which in turn adapts an instance of
|
|
||||||
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
|
|
||||||
// to the ifc.Unstructured interface. This latter code handles
|
|
||||||
// mutations in the old code.
|
|
||||||
//
|
|
||||||
// The rule in kustomize development has been
|
|
||||||
// api/
|
|
||||||
// k8sdeps/ ifc/ krusty/ theRest/
|
|
||||||
//
|
|
||||||
// 1) Depend on k8s.io/ only via sigs.k8s.io/kustomize/api/k8sdeps.
|
|
||||||
//
|
|
||||||
// 2) Instances created in k8sdeps/ can only be injected into theRest/
|
|
||||||
// behind interfaces defined in ifc/, arranged by a small bit of code
|
|
||||||
// in krusty/. Nothing in theRest/ can import k8sdeps/.
|
|
||||||
//
|
|
||||||
// This was to allow for importing kustomize code into kubectl, which also
|
|
||||||
// depends on k8s.io/apimachinery, albeit via a strange, old arrangement
|
|
||||||
// predating Go modules. The idea was to copy the k8sdeps/ tree into kubectl
|
|
||||||
// via a large PR, and depend on the rest via vendoring (and a small copy of
|
|
||||||
// krusty/ code). Over 2019, however, kubectl underwent large code changes
|
|
||||||
// including a switch to Go modules, and an attempt to extract it from the
|
|
||||||
// k8s repo, and this made large kustomize integration PRs hard to get through
|
|
||||||
// code review.
|
|
||||||
//
|
|
||||||
// The new plan is to eliminate k8sdeps/ entirely, along with its k8s.io/
|
|
||||||
// dependence, switch to kyaml, and thus allow kustomize to be imported into
|
|
||||||
// kubectl via normal Go module imports.
|
|
||||||
//
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
kunStr ifc.Kunstructured
|
kunStr ifc.Kunstructured
|
||||||
originalName string
|
originalName string
|
||||||
@@ -75,7 +29,7 @@ type Resource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
||||||
r.kunStr = incoming.kunStr.Copy()
|
r.kunStr = incoming.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetAnnotations() map[string]string {
|
func (r *Resource) GetAnnotations() map[string]string {
|
||||||
@@ -86,26 +40,14 @@ func (r *Resource) Copy() ifc.Kunstructured {
|
|||||||
return r.kunStr.Copy()
|
return r.kunStr.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetBool(p string) (bool, error) {
|
|
||||||
return r.kunStr.GetBool(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
||||||
return r.kunStr.GetFieldValue(f)
|
return r.kunStr.GetFieldValue(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetFloat64(p string) (float64, error) {
|
|
||||||
return r.kunStr.GetFloat64(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetGvk() resid.Gvk {
|
func (r *Resource) GetGvk() resid.Gvk {
|
||||||
return r.kunStr.GetGvk()
|
return r.kunStr.GetGvk()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetInt64(p string) (int64, error) {
|
|
||||||
return r.kunStr.GetInt64(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetKind() string {
|
func (r *Resource) GetKind() string {
|
||||||
return r.kunStr.GetKind()
|
return r.kunStr.GetKind()
|
||||||
}
|
}
|
||||||
@@ -114,10 +56,6 @@ func (r *Resource) GetLabels() map[string]string {
|
|||||||
return r.kunStr.GetLabels()
|
return r.kunStr.GetLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetMap(p string) (map[string]interface{}, error) {
|
|
||||||
return r.kunStr.GetMap(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetName() string {
|
func (r *Resource) GetName() string {
|
||||||
return r.kunStr.GetName()
|
return r.kunStr.GetName()
|
||||||
}
|
}
|
||||||
@@ -130,12 +68,8 @@ func (r *Resource) GetString(p string) (string, error) {
|
|||||||
return r.kunStr.GetString(p)
|
return r.kunStr.GetString(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetStringMap(p string) (map[string]string, error) {
|
func (r *Resource) IsEmpty() bool {
|
||||||
return r.kunStr.GetStringMap(p)
|
return len(r.kunStr.Map()) == 0
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetStringSlice(p string) ([]string, error) {
|
|
||||||
return r.kunStr.GetStringSlice(p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Map() map[string]interface{} {
|
func (r *Resource) Map() map[string]interface{} {
|
||||||
@@ -154,10 +88,6 @@ func (r *Resource) MatchesAnnotationSelector(selector string) (bool, error) {
|
|||||||
return r.kunStr.MatchesAnnotationSelector(selector)
|
return r.kunStr.MatchesAnnotationSelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Patch(other ifc.Kunstructured) error {
|
|
||||||
return r.kunStr.Patch(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) SetAnnotations(m map[string]string) {
|
func (r *Resource) SetAnnotations(m map[string]string) {
|
||||||
r.kunStr.SetAnnotations(m)
|
r.kunStr.SetAnnotations(m)
|
||||||
}
|
}
|
||||||
@@ -170,10 +100,6 @@ func (r *Resource) SetLabels(m map[string]string) {
|
|||||||
r.kunStr.SetLabels(m)
|
r.kunStr.SetLabels(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetMap(m map[string]interface{}) {
|
|
||||||
r.kunStr.SetMap(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) SetName(n string) {
|
func (r *Resource) SetName(n string) {
|
||||||
r.kunStr.SetName(n)
|
r.kunStr.SetName(n)
|
||||||
}
|
}
|
||||||
@@ -205,7 +131,7 @@ type ResCtxMatcher func(ResCtx) bool
|
|||||||
// DeepCopy returns a new copy of resource
|
// DeepCopy returns a new copy of resource
|
||||||
func (r *Resource) DeepCopy() *Resource {
|
func (r *Resource) DeepCopy() *Resource {
|
||||||
rc := &Resource{
|
rc := &Resource{
|
||||||
kunStr: r.kunStr.Copy(),
|
kunStr: r.Copy(),
|
||||||
}
|
}
|
||||||
rc.copyOtherFields(r)
|
rc.copyOtherFields(r)
|
||||||
return rc
|
return rc
|
||||||
@@ -213,11 +139,10 @@ func (r *Resource) DeepCopy() *Resource {
|
|||||||
|
|
||||||
// Replace performs replace with other resource.
|
// Replace performs replace with other resource.
|
||||||
func (r *Resource) Replace(other *Resource) {
|
func (r *Resource) Replace(other *Resource) {
|
||||||
r.kunStr.SetLabels(mergeStringMaps(other.kunStr.GetLabels(), r.kunStr.GetLabels()))
|
r.SetLabels(mergeStringMaps(other.GetLabels(), r.GetLabels()))
|
||||||
r.kunStr.SetAnnotations(
|
r.SetAnnotations(mergeStringMaps(other.GetAnnotations(), r.GetAnnotations()))
|
||||||
mergeStringMaps(other.kunStr.GetAnnotations(), r.kunStr.GetAnnotations()))
|
r.SetName(other.GetName())
|
||||||
r.kunStr.SetName(other.GetName())
|
r.SetNamespace(other.GetNamespace())
|
||||||
r.kunStr.SetNamespace(other.GetNamespace())
|
|
||||||
r.copyOtherFields(other)
|
r.copyOtherFields(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +183,7 @@ func (r *Resource) KunstructEqual(o *Resource) bool {
|
|||||||
// Merge performs merge with other resource.
|
// Merge performs merge with other resource.
|
||||||
func (r *Resource) Merge(other *Resource) {
|
func (r *Resource) Merge(other *Resource) {
|
||||||
r.Replace(other)
|
r.Replace(other)
|
||||||
mergeConfigmap(r.kunStr.Map(), other.Map(), r.Map())
|
mergeConfigmap(r.Map(), other.Map(), r.Map())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) copyRefBy() []resid.ResId {
|
func (r *Resource) copyRefBy() []resid.ResId {
|
||||||
@@ -356,12 +281,6 @@ func (r *Resource) PrefixesSuffixesEquals(o ResCtx) bool {
|
|||||||
return sameEndingSubarray(r.GetNamePrefixes(), o.GetNamePrefixes()) && sameEndingSubarray(r.GetNameSuffixes(), o.GetNameSuffixes())
|
return sameEndingSubarray(r.GetNamePrefixes(), o.GetNamePrefixes()) && sameEndingSubarray(r.GetNameSuffixes(), o.GetNameSuffixes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is used to compute if a referrer could potentially be impacted
|
|
||||||
// by the change of name of a referral.
|
|
||||||
func (r *Resource) InSameKustomizeCtx(rctxm ResCtxMatcher) bool {
|
|
||||||
return rctxm(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetOriginalName() string {
|
func (r *Resource) GetOriginalName() string {
|
||||||
return r.originalName
|
return r.originalName
|
||||||
}
|
}
|
||||||
@@ -384,7 +303,7 @@ func (r *Resource) setOriginalNs(n string) *Resource {
|
|||||||
|
|
||||||
// String returns resource as JSON.
|
// String returns resource as JSON.
|
||||||
func (r *Resource) String() string {
|
func (r *Resource) String() string {
|
||||||
bs, err := r.kunStr.MarshalJSON()
|
bs, err := r.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "<" + err.Error() + ">"
|
return "<" + err.Error() + ">"
|
||||||
}
|
}
|
||||||
@@ -394,7 +313,7 @@ func (r *Resource) String() string {
|
|||||||
// AsYAML returns the resource in Yaml form.
|
// AsYAML returns the resource in Yaml form.
|
||||||
// Easier to read than JSON.
|
// Easier to read than JSON.
|
||||||
func (r *Resource) AsYAML() ([]byte, error) {
|
func (r *Resource) AsYAML() ([]byte, error) {
|
||||||
json, err := r.kunStr.MarshalJSON()
|
json, err := r.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -419,7 +338,7 @@ func (r *Resource) NeedHashSuffix() bool {
|
|||||||
|
|
||||||
// GetNamespace returns the namespace the resource thinks it's in.
|
// GetNamespace returns the namespace the resource thinks it's in.
|
||||||
func (r *Resource) GetNamespace() string {
|
func (r *Resource) GetNamespace() string {
|
||||||
namespace, _ := r.kunStr.GetString("metadata.namespace")
|
namespace, _ := r.GetString("metadata.namespace")
|
||||||
// if err, namespace is empty, so no need to check.
|
// if err, namespace is empty, so no need to check.
|
||||||
return namespace
|
return namespace
|
||||||
}
|
}
|
||||||
@@ -429,7 +348,7 @@ func (r *Resource) GetNamespace() string {
|
|||||||
// TODO: compute this once and save it in the resource.
|
// TODO: compute this once and save it in the resource.
|
||||||
func (r *Resource) OrgId() resid.ResId {
|
func (r *Resource) OrgId() resid.ResId {
|
||||||
return resid.NewResIdWithNamespace(
|
return resid.NewResIdWithNamespace(
|
||||||
r.kunStr.GetGvk(), r.GetOriginalName(), r.GetOriginalNs())
|
r.GetGvk(), r.GetOriginalName(), r.GetOriginalNs())
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurId returns a ResId for the resource using the
|
// CurId returns a ResId for the resource using the
|
||||||
@@ -437,7 +356,7 @@ func (r *Resource) OrgId() resid.ResId {
|
|||||||
// This should be unique in any ResMap.
|
// This should be unique in any ResMap.
|
||||||
func (r *Resource) CurId() resid.ResId {
|
func (r *Resource) CurId() resid.ResId {
|
||||||
return resid.NewResIdWithNamespace(
|
return resid.NewResIdWithNamespace(
|
||||||
r.kunStr.GetGvk(), r.kunStr.GetName(), r.GetNamespace())
|
r.GetGvk(), r.GetName(), r.GetNamespace())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRefBy returns the ResIds that referred to current resource
|
// GetRefBy returns the ResIds that referred to current resource
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Harness manages a kustomize environment for tests.
|
// Harness manages a test environment.
|
||||||
type Harness struct {
|
type Harness struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
fSys filesys.FileSystem
|
fSys filesys.FileSystem
|
||||||
@@ -72,15 +72,12 @@ func (th Harness) MakeDefaultOptions() krusty.Options {
|
|||||||
|
|
||||||
// This has no impact on Builtin plugins, as they are always enabled.
|
// This has no impact on Builtin plugins, as they are always enabled.
|
||||||
func (th Harness) MakeOptionsPluginsDisabled() krusty.Options {
|
func (th Harness) MakeOptionsPluginsDisabled() krusty.Options {
|
||||||
return krusty.Options{
|
return *krusty.MakeDefaultOptions()
|
||||||
LoadRestrictions: types.LoadRestrictionsRootOnly,
|
|
||||||
PluginConfig: konfig.DisabledPluginConfig(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enables use of non-builtin plugins.
|
// Enables use of non-builtin plugins.
|
||||||
func (th Harness) MakeOptionsPluginsEnabled() krusty.Options {
|
func (th Harness) MakeOptionsPluginsEnabled() krusty.Options {
|
||||||
c, err := konfig.EnabledPluginConfig(types.BploLoadFromFileSys)
|
pc, err := konfig.EnabledPluginConfig(types.BploLoadFromFileSys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "unable to find plugin root") {
|
if strings.Contains(err.Error(), "unable to find plugin root") {
|
||||||
th.t.Log(
|
th.t.Log(
|
||||||
@@ -89,10 +86,9 @@ func (th Harness) MakeOptionsPluginsEnabled() krusty.Options {
|
|||||||
}
|
}
|
||||||
th.t.Fatal(err)
|
th.t.Fatal(err)
|
||||||
}
|
}
|
||||||
return krusty.Options{
|
o := *krusty.MakeDefaultOptions()
|
||||||
LoadRestrictions: types.LoadRestrictionsRootOnly,
|
o.PluginConfig = pc
|
||||||
PluginConfig: c,
|
return o
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run, failing on error.
|
// Run, failing on error.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/k8sdeps/transformer"
|
"sigs.k8s.io/kustomize/api/internal/k8sdeps/merge"
|
||||||
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
@@ -46,16 +46,17 @@ func MakeEnhancedHarness(t *testing.T) *HarnessEnhanced {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
resourceFactory := resource.NewFactory(
|
||||||
rf := resmap.NewFactory(
|
kunstruct.NewKunstructuredFactoryImpl())
|
||||||
resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()),
|
resmapFactory := resmap.NewFactory(
|
||||||
transformer.NewFactoryImpl())
|
resourceFactory,
|
||||||
|
merge.NewMerginator(resourceFactory))
|
||||||
|
|
||||||
result := &HarnessEnhanced{
|
result := &HarnessEnhanced{
|
||||||
Harness: MakeHarness(t),
|
Harness: MakeHarness(t),
|
||||||
pte: pte,
|
pte: pte,
|
||||||
rf: rf,
|
rf: resmapFactory,
|
||||||
pl: pLdr.NewLoader(pc, rf)}
|
pl: pLdr.NewLoader(pc, resmapFactory)}
|
||||||
|
|
||||||
// Point the file loader to the root ('/') of the in-memory file system.
|
// Point the file loader to the root ('/') of the in-memory file system.
|
||||||
result.ResetLoaderRoot(filesys.Separator)
|
result.ResetLoaderRoot(filesys.Separator)
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package transform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mutateFunc func(interface{}) (interface{}, error)
|
|
||||||
|
|
||||||
func MutateField(
|
|
||||||
m map[string]interface{},
|
|
||||||
pathToField []string,
|
|
||||||
createIfNotPresent bool,
|
|
||||||
fns ...mutateFunc) error {
|
|
||||||
if len(pathToField) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
firstPathSegment, isArray := getFirstPathSegment(pathToField)
|
|
||||||
|
|
||||||
_, found := m[firstPathSegment]
|
|
||||||
if !found {
|
|
||||||
if !createIfNotPresent || isArray {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m[firstPathSegment] = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pathToField) == 1 {
|
|
||||||
var err error
|
|
||||||
for _, fn := range fns {
|
|
||||||
m[firstPathSegment], err = fn(m[firstPathSegment])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v := m[firstPathSegment]
|
|
||||||
newPathToField := pathToField[1:]
|
|
||||||
switch typedV := v.(type) {
|
|
||||||
case nil:
|
|
||||||
log.Printf(
|
|
||||||
"nil value at `%s` ignored in mutation attempt",
|
|
||||||
strings.Join(pathToField, "."))
|
|
||||||
return nil
|
|
||||||
case map[string]interface{}:
|
|
||||||
return MutateField(typedV, newPathToField, createIfNotPresent, fns...)
|
|
||||||
case []interface{}:
|
|
||||||
for i := range typedV {
|
|
||||||
item := typedV[i]
|
|
||||||
typedItem, ok := item.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%#v is expected to be %T", item, typedItem)
|
|
||||||
}
|
|
||||||
err := MutateField(typedItem, newPathToField, createIfNotPresent, fns...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%#v is not expected to be a primitive type", typedV)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFirstPathSegment(pathToField []string) (string, bool) {
|
|
||||||
if strings.HasSuffix(pathToField[0], "[]") {
|
|
||||||
return pathToField[0][:len(pathToField[0])-2], true
|
|
||||||
}
|
|
||||||
return pathToField[0], false
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package transform_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
|
||||||
. "sigs.k8s.io/kustomize/api/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
type noopMutator struct {
|
|
||||||
wasCalled bool
|
|
||||||
errorToReturn error
|
|
||||||
}
|
|
||||||
|
|
||||||
var errExpected = fmt.Errorf("oops")
|
|
||||||
|
|
||||||
const originalValue = "tomato"
|
|
||||||
const newValue = "notThe" + originalValue
|
|
||||||
|
|
||||||
func (m *noopMutator) mutate(_ interface{}) (interface{}, error) {
|
|
||||||
m.wasCalled = true
|
|
||||||
return newValue, m.errorToReturn
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestDeployment() ifc.Kunstructured {
|
|
||||||
factory := kunstruct.NewKunstructuredFactoryImpl()
|
|
||||||
return factory.FromMap(
|
|
||||||
map[string]interface{}{
|
|
||||||
"group": "apps",
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": originalValue,
|
|
||||||
},
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"template": map[string]interface{}{
|
|
||||||
"env": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "HELLO",
|
|
||||||
"value": "hi there",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "GOODBYE",
|
|
||||||
"value": "adios!",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"labels": map[string]interface{}{
|
|
||||||
"vegetable": originalValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"containers": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "tangerine",
|
|
||||||
"image": originalValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFieldValue(t *testing.T, obj ifc.Kunstructured, fieldName string) string {
|
|
||||||
v, err := obj.GetString(fieldName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected field error: %v", err)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoPath(t *testing.T) {
|
|
||||||
obj := makeTestDeployment()
|
|
||||||
m := &noopMutator{}
|
|
||||||
err := MutateField(
|
|
||||||
obj.Map(), []string{}, false, m.mutate)
|
|
||||||
if m.wasCalled {
|
|
||||||
t.Fatalf("mutator should not have been called.")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHappyPath(t *testing.T) {
|
|
||||||
obj := makeTestDeployment()
|
|
||||||
v := getFieldValue(t, obj, "metadata.name")
|
|
||||||
if v != originalValue {
|
|
||||||
t.Fatalf("unexpected original value: %v", v)
|
|
||||||
}
|
|
||||||
v = getFieldValue(t, obj, "spec.template.metadata.labels.vegetable")
|
|
||||||
if v != originalValue {
|
|
||||||
t.Fatalf("unexpected original value: %v", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &noopMutator{}
|
|
||||||
err := MutateField(
|
|
||||||
obj.Map(), []string{"metadata", "name"}, false, m.mutate)
|
|
||||||
if !m.wasCalled {
|
|
||||||
t.Fatalf("mutator should have been called.")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected mutate error: %v", err)
|
|
||||||
}
|
|
||||||
v = getFieldValue(t, obj, "metadata.name")
|
|
||||||
if v != newValue {
|
|
||||||
t.Fatalf("unexpected new value: %v", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
m = &noopMutator{}
|
|
||||||
err = MutateField(
|
|
||||||
obj.Map(), []string{"spec", "template", "metadata", "labels", "vegetable"}, false, m.mutate)
|
|
||||||
if !m.wasCalled {
|
|
||||||
t.Fatalf("mutator should have been called.")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected mutate error: %v", err)
|
|
||||||
}
|
|
||||||
v = getFieldValue(t, obj, "spec.template.metadata.labels.vegetable")
|
|
||||||
if v != newValue {
|
|
||||||
t.Fatalf("unexpected new value: %v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithError(t *testing.T) {
|
|
||||||
obj := makeTestDeployment()
|
|
||||||
m := noopMutator{errorToReturn: errExpected}
|
|
||||||
err := MutateField(
|
|
||||||
obj.Map(), []string{"metadata", "name"}, false, m.mutate)
|
|
||||||
if !m.wasCalled {
|
|
||||||
t.Fatalf("mutator was not called!")
|
|
||||||
}
|
|
||||||
if err != errExpected {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithNil(t *testing.T) {
|
|
||||||
obj := makeTestDeployment()
|
|
||||||
foo := obj.Map()["spec"]
|
|
||||||
foo = foo.(map[string]interface{})["template"]
|
|
||||||
foo = foo.(map[string]interface{})["metadata"]
|
|
||||||
foo.(map[string]interface{})["labels"] = nil
|
|
||||||
|
|
||||||
m := &noopMutator{}
|
|
||||||
err := MutateField(
|
|
||||||
obj.Map(), []string{"spec", "template", "metadata", "labels", "vegetable"}, false, m.mutate)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,14 +9,14 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FixKustomizationPreUnmarshalling modies the raw data
|
// FixKustomizationPreUnmarshalling modifies the raw data
|
||||||
// before marshalling - e.g. changes old field names to
|
// before marshalling - e.g. changes old field names to
|
||||||
// new field names.
|
// new field names.
|
||||||
func FixKustomizationPreUnmarshalling(data []byte) ([]byte, error) {
|
func FixKustomizationPreUnmarshalling(data []byte) ([]byte, error) {
|
||||||
deprecateFieldsMap := map[string]string{
|
deprecatedFieldsMap := map[string]string{
|
||||||
"imageTags:": "images:",
|
"imageTags:": "images:",
|
||||||
}
|
}
|
||||||
for oldname, newname := range deprecateFieldsMap {
|
for oldname, newname := range deprecatedFieldsMap {
|
||||||
pattern := regexp.MustCompile(oldname)
|
pattern := regexp.MustCompile(oldname)
|
||||||
data = pattern.ReplaceAll(data, []byte(newname))
|
data = pattern.ReplaceAll(data, []byte(newname))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ const (
|
|||||||
type Kustomization struct {
|
type Kustomization struct {
|
||||||
TypeMeta `json:",inline" yaml:",inline"`
|
TypeMeta `json:",inline" yaml:",inline"`
|
||||||
|
|
||||||
|
// MetaData is a pointer to avoid marshalling empty struct
|
||||||
|
MetaData *ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||||
|
|
||||||
//
|
//
|
||||||
// Operators - what kustomize can do.
|
// Operators - what kustomize can do.
|
||||||
//
|
//
|
||||||
@@ -144,6 +147,7 @@ type Kustomization struct {
|
|||||||
// moving content of deprecated fields to newer
|
// moving content of deprecated fields to newer
|
||||||
// fields.
|
// fields.
|
||||||
func (k *Kustomization) FixKustomizationPostUnmarshalling() {
|
func (k *Kustomization) FixKustomizationPostUnmarshalling() {
|
||||||
|
|
||||||
if k.Kind == "" {
|
if k.Kind == "" {
|
||||||
k.Kind = KustomizationKind
|
k.Kind = KustomizationKind
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,17 @@ func (k *Kustomization) FixKustomizationPostUnmarshalling() {
|
|||||||
k.Bases = nil
|
k.Bases = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FixKustomizationPreMarshalling fixes things
|
||||||
|
// that should occur after the kustomization file
|
||||||
|
// has been processed.
|
||||||
|
func (k *Kustomization) FixKustomizationPreMarshalling() {
|
||||||
|
// PatchesJson6902 should be under the Patches field.
|
||||||
|
for _, patch := range k.PatchesJson6902 {
|
||||||
|
k.Patches = append(k.Patches, patch.ToPatch())
|
||||||
|
}
|
||||||
|
k.PatchesJson6902 = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (k *Kustomization) EnforceFields() []string {
|
func (k *Kustomization) EnforceFields() []string {
|
||||||
var errs []string
|
var errs []string
|
||||||
if k.Kind != "" && k.Kind != KustomizationKind && k.Kind != ComponentKind {
|
if k.Kind != "" && k.Kind != KustomizationKind && k.Kind != ComponentKind {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func fixKustomizationPostUnmarshallingCheck(k, e *Kustomization) bool {
|
func fixKustomizationPostUnmarshallingCheck(k, e *Kustomization) bool {
|
||||||
return (k.Kind == e.Kind && k.APIVersion == e.APIVersion &&
|
return k.Kind == e.Kind &&
|
||||||
len(k.Resources) == len(e.Resources) && k.Resources[0] == e.Resources[0] &&
|
k.APIVersion == e.APIVersion &&
|
||||||
k.Bases == nil)
|
len(k.Resources) == len(e.Resources) &&
|
||||||
|
k.Resources[0] == e.Resources[0] &&
|
||||||
|
k.Bases == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFixKustomizationPostUnmarshalling(t *testing.T) {
|
func TestFixKustomizationPostUnmarshalling(t *testing.T) {
|
||||||
@@ -139,6 +141,13 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
y := []byte(`
|
y := []byte(`
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: kust
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
resources:
|
resources:
|
||||||
- foo
|
- foo
|
||||||
- bar
|
- bar
|
||||||
@@ -149,8 +158,20 @@ namePrefix: cat`)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
meta := ObjectMeta{
|
||||||
|
Name: "kust",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
if k.Kind != KustomizationKind || k.APIVersion != KustomizationVersion ||
|
if k.Kind != KustomizationKind || k.APIVersion != KustomizationVersion ||
|
||||||
len(k.Resources) != 2 || k.NamePrefix != "cat" || k.NameSuffix != "dog" {
|
len(k.Resources) != 2 || k.NamePrefix != "cat" || k.NameSuffix != "dog" ||
|
||||||
|
k.MetaData.Name != meta.Name || k.MetaData.Namespace != meta.Namespace ||
|
||||||
|
k.MetaData.Labels["foo"] != meta.Labels["foo"] || k.MetaData.Annotations["foo"] != meta.Annotations["foo"] {
|
||||||
t.Fatalf("wrong unmarshal result: %v", k)
|
t.Fatalf("wrong unmarshal result: %v", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ package types
|
|||||||
// ObjectMeta partially copies apimachinery/pkg/apis/meta/v1.ObjectMeta
|
// ObjectMeta partially copies apimachinery/pkg/apis/meta/v1.ObjectMeta
|
||||||
// No need for a direct dependence; the fields are stable.
|
// No need for a direct dependence; the fields are stable.
|
||||||
type ObjectMeta struct {
|
type ObjectMeta struct {
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
|
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,3 +17,12 @@ type Patch struct {
|
|||||||
// Target points to the resources that the patch is applied to
|
// Target points to the resources that the patch is applied to
|
||||||
Target *Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
Target *Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equals return true if p equals o.
|
||||||
|
func (p *Patch) Equals(o Patch) bool {
|
||||||
|
targetEqual := (p.Target == o.Target) ||
|
||||||
|
(p.Target != nil && o.Target != nil && *p.Target == *o.Target)
|
||||||
|
return p.Path == o.Path &&
|
||||||
|
p.Patch == o.Patch &&
|
||||||
|
targetEqual
|
||||||
|
}
|
||||||
|
|||||||
125
api/types/patch_test.go
Normal file
125
api/types/patch_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
|
. "sigs.k8s.io/kustomize/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPatchEquals(t *testing.T) {
|
||||||
|
selector := Selector{
|
||||||
|
Gvk: resid.Gvk{
|
||||||
|
Group: "group",
|
||||||
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Namespace: "namespace",
|
||||||
|
LabelSelector: "selector",
|
||||||
|
AnnotationSelector: "selector",
|
||||||
|
}
|
||||||
|
type testcase struct {
|
||||||
|
patch1 Patch
|
||||||
|
patch2 Patch
|
||||||
|
expect bool
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
testcases := []testcase{
|
||||||
|
{
|
||||||
|
name: "empty patches",
|
||||||
|
patch1: Patch{},
|
||||||
|
patch2: Patch{},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full patches",
|
||||||
|
patch1: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
Target: &Selector{
|
||||||
|
Gvk: resid.Gvk{
|
||||||
|
Group: "group",
|
||||||
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Namespace: "namespace",
|
||||||
|
LabelSelector: "selector",
|
||||||
|
AnnotationSelector: "selector",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
patch2: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
Target: &Selector{
|
||||||
|
Gvk: resid.Gvk{
|
||||||
|
Group: "group",
|
||||||
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Namespace: "namespace",
|
||||||
|
LabelSelector: "selector",
|
||||||
|
AnnotationSelector: "selector",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same target",
|
||||||
|
patch1: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
Target: &selector,
|
||||||
|
},
|
||||||
|
patch2: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
Target: &selector,
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "omit target",
|
||||||
|
patch1: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
},
|
||||||
|
patch2: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one nil target",
|
||||||
|
patch1: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
Target: &selector,
|
||||||
|
},
|
||||||
|
patch2: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
Patch: "bar",
|
||||||
|
},
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different path",
|
||||||
|
patch1: Patch{
|
||||||
|
Path: "foo",
|
||||||
|
},
|
||||||
|
patch2: Patch{
|
||||||
|
Path: "bar",
|
||||||
|
},
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
if tc.expect != tc.patch1.Equals(tc.patch2) {
|
||||||
|
t.Fatalf("%s: unexpected result %v", tc.name, !tc.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,3 +19,9 @@ type PatchJson6902 struct {
|
|||||||
// inline patch string
|
// inline patch string
|
||||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToPatch converts a PatchJson6902 to its superset Patch.
|
||||||
|
func (patch *PatchJson6902) ToPatch() Patch {
|
||||||
|
selector := patch.Target.ToSelector()
|
||||||
|
return Patch{Path: patch.Path, Patch: patch.Patch, Target: &selector}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,3 +13,8 @@ type PatchTarget struct {
|
|||||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSelector converts a PatchTarget to a Selector.
|
||||||
|
func (target *PatchTarget) ToSelector() Selector {
|
||||||
|
return Selector{Name: target.Name, Namespace: target.Namespace, Gvk: target.Gvk}
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,4 +53,6 @@ type FnPluginLoadingOptions struct {
|
|||||||
NetworkName string
|
NetworkName string
|
||||||
// list of mounts
|
// list of mounts
|
||||||
Mounts []string
|
Mounts []string
|
||||||
|
// list of env variables to pass to fn
|
||||||
|
Env []string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package complete
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/posener/complete/v2"
|
|
||||||
"github.com/posener/complete/v2/predict"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewCommand returns a new install-completion command
|
|
||||||
func NewCommand() *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "install-completion",
|
|
||||||
Short: commands.CompletionShort,
|
|
||||||
Long: commands.CompletionLong,
|
|
||||||
PreRunE: preRunE,
|
|
||||||
Run: run,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func preRunE(cmd *cobra.Command, args []string) error {
|
|
||||||
// install by default
|
|
||||||
if os.Getenv("COMP_INSTALL") == "" {
|
|
||||||
if err := errors.Wrap(os.Setenv("COMP_INSTALL", "1")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(cmd *cobra.Command, args []string) {
|
|
||||||
// find the root command
|
|
||||||
for cmd.Parent() != nil {
|
|
||||||
cmd = cmd.Parent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// do completion
|
|
||||||
Complete(cmd).Complete("kustomize")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete returns a completion command for a cobra command
|
|
||||||
func Complete(cmd *cobra.Command) *complete.Command {
|
|
||||||
cc := &complete.Command{
|
|
||||||
Flags: map[string]complete.Predictor{},
|
|
||||||
Sub: map[string]*complete.Command{},
|
|
||||||
}
|
|
||||||
if strings.Contains(cmd.Use, "DIR") {
|
|
||||||
// if usage contains directory, then use a file predictor
|
|
||||||
cc.Args = predict.Dirs("*")
|
|
||||||
}
|
|
||||||
|
|
||||||
// add completion for each subcommand
|
|
||||||
for i := range cmd.Commands() {
|
|
||||||
c := cmd.Commands()[i]
|
|
||||||
name := strings.Split(c.Use, " ")[0]
|
|
||||||
cc.Sub[name] = Complete(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add completion for each flag
|
|
||||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
|
||||||
cc.Flags[flag.Name] = predict.Nothing
|
|
||||||
})
|
|
||||||
return cc
|
|
||||||
}
|
|
||||||
33
cmd/config/completion/completion.go
Normal file
33
cmd/config/completion/completion.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package completion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommand() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "completion [bash|zsh|fish|powershell]",
|
||||||
|
Short: "Generate shell completion script",
|
||||||
|
Long: "Generate shell completion.",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
|
Args: cobra.ExactValidArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
switch args[0] {
|
||||||
|
case "bash":
|
||||||
|
cmd.Root().GenBashCompletion(os.Stdout)
|
||||||
|
case "zsh":
|
||||||
|
cmd.Root().GenZshCompletion(os.Stdout)
|
||||||
|
case "fish":
|
||||||
|
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||||
|
case "powershell":
|
||||||
|
cmd.Root().GenPowerShellCompletion(os.Stdout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
|
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
|
||||||
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/api"
|
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/api"
|
||||||
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/tutorials"
|
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/tutorials"
|
||||||
|
"sigs.k8s.io/kustomize/cmd/config/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Export commands publicly for composition
|
// Export commands publicly for composition
|
||||||
@@ -20,6 +21,7 @@ var (
|
|||||||
CreateSetter = commands.CreateSetterCommand
|
CreateSetter = commands.CreateSetterCommand
|
||||||
CreateSubstitution = commands.CreateSubstitutionCommand
|
CreateSubstitution = commands.CreateSubstitutionCommand
|
||||||
DeleteSetter = commands.DeleteSetterCommand
|
DeleteSetter = commands.DeleteSetterCommand
|
||||||
|
DeleteSubstitution = commands.DeleteSubstitutionCommand
|
||||||
Fmt = commands.FmtCommand
|
Fmt = commands.FmtCommand
|
||||||
Grep = commands.GrepCommand
|
Grep = commands.GrepCommand
|
||||||
Init = commands.InitCommand
|
Init = commands.InitCommand
|
||||||
@@ -34,20 +36,19 @@ var (
|
|||||||
Wrap = commands.WrapCommand
|
Wrap = commands.WrapCommand
|
||||||
XArgs = commands.XArgsCommand
|
XArgs = commands.XArgsCommand
|
||||||
|
|
||||||
StackOnError = &commands.StackOnError
|
StackOnError = &runner.StackOnError
|
||||||
ExitOnError = &commands.ExitOnError
|
ExitOnError = &runner.ExitOnError
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddCommands adds the cfg, fn and live commands to kustomize.
|
// AddCommands adds the cfg and fn commands to kustomize.
|
||||||
func AddCommands(root *cobra.Command, name string) *cobra.Command {
|
func AddCommands(root *cobra.Command, name string) *cobra.Command {
|
||||||
commands.ExitOnError = true
|
runner.ExitOnError = true
|
||||||
|
|
||||||
root.PersistentFlags().BoolVar(StackOnError, "stack-trace", false,
|
root.PersistentFlags().BoolVar(StackOnError, "stack-trace", false,
|
||||||
"print a stack-trace on error")
|
"print a stack-trace on error")
|
||||||
|
|
||||||
root.AddCommand(GetCfg(name))
|
root.AddCommand(GetCfg(name))
|
||||||
root.AddCommand(GetFn(name))
|
root.AddCommand(GetFn(name))
|
||||||
root.AddCommand(GetLive(name))
|
|
||||||
|
|
||||||
root.AddCommand(&cobra.Command{
|
root.AddCommand(&cobra.Command{
|
||||||
Use: "docs-merge",
|
Use: "docs-merge",
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package configcobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
||||||
"k8s.io/client-go/discovery"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
"k8s.io/kubectl/pkg/cmd/util"
|
|
||||||
"sigs.k8s.io/cli-utils/cmd/apply"
|
|
||||||
"sigs.k8s.io/cli-utils/cmd/destroy"
|
|
||||||
"sigs.k8s.io/cli-utils/cmd/diff"
|
|
||||||
"sigs.k8s.io/cli-utils/cmd/initcmd"
|
|
||||||
"sigs.k8s.io/cli-utils/cmd/preview"
|
|
||||||
"sigs.k8s.io/cli-utils/pkg/util/factory"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetLive(name string) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "live",
|
|
||||||
Short: "Commands for reading and writing resources to a cluster.",
|
|
||||||
}
|
|
||||||
ioStreams := genericclioptions.IOStreams{
|
|
||||||
In: cmd.InOrStdin(),
|
|
||||||
Out: cmd.OutOrStdout(),
|
|
||||||
ErrOut: cmd.ErrOrStderr(),
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.PersistentFlags()
|
|
||||||
kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
|
|
||||||
kubeConfigFlags.AddFlags(flags)
|
|
||||||
userAgentKubeConfigFlags := &UserAgentKubeConfigFlags{
|
|
||||||
Delegate: kubeConfigFlags,
|
|
||||||
UserAgent: "kustomize",
|
|
||||||
}
|
|
||||||
matchVersionKubeConfigFlags := util.NewMatchVersionFlags(
|
|
||||||
&factory.CachingRESTClientGetter{
|
|
||||||
Delegate: userAgentKubeConfigFlags,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
matchVersionKubeConfigFlags.AddFlags(cmd.PersistentFlags())
|
|
||||||
cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
|
|
||||||
f := util.NewFactory(matchVersionKubeConfigFlags)
|
|
||||||
|
|
||||||
applyCmd := apply.ApplyCommand(f, ioStreams)
|
|
||||||
_ = applyCmd.Flags().MarkHidden("no-prune")
|
|
||||||
|
|
||||||
cmd.AddCommand(
|
|
||||||
applyCmd,
|
|
||||||
initcmd.NewCmdInit(ioStreams),
|
|
||||||
preview.NewCmdPreview(f, ioStreams),
|
|
||||||
diff.NewCmdDiff(f, ioStreams),
|
|
||||||
destroy.NewCmdDestroy(f, ioStreams))
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserAgentKubeConfigFlags struct {
|
|
||||||
Delegate genericclioptions.RESTClientGetter
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserAgentKubeConfigFlags) ToRESTConfig() (*rest.Config, error) {
|
|
||||||
clientConfig, err := u.Delegate.ToRESTConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if u.UserAgent != "" {
|
|
||||||
clientConfig.UserAgent = u.UserAgent
|
|
||||||
}
|
|
||||||
return clientConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserAgentKubeConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
|
||||||
return u.Delegate.ToDiscoveryClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserAgentKubeConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
|
|
||||||
return u.Delegate.ToRESTMapper()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserAgentKubeConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
|
||||||
return u.Delegate.ToRawKubeConfigLoader()
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user