Compare commits

...

187 Commits

Author SHA1 Message Date
Jeff Regan
633c43a672 Merge pull request #292 from babiel/pdb-matchlabels
Disable creation of PDB matchLabels
2018-08-24 09:27:41 -07:00
Maximilian Gaß
0833693372 Disable creation of PDB matchLabels
Allow for using matchExpressions on its own
2018-08-24 13:51:05 +02:00
Jeff Regan
77c07ba96e Merge pull request #290 from philoserf/patch-1
trivial: Change dep constraint to an override
2018-08-23 17:07:44 -07:00
Mark Ayers
6847bb7924 trivial: Change dep constraint to an override
fixes #289
2018-08-23 15:58:27 -07:00
k8s-ci-robot
f0deaf707d Merge pull request #287 from emosbaugh/fix-gopath-isrepourl
Fix loader loader when run from gopath
2018-08-23 15:08:25 -07:00
Ethan Mosbaugh
e113944027 fix loader loader when run from gopath 2018-08-23 14:54:58 -07:00
k8s-ci-robot
1cf9131ae2 Merge pull request #285 from monopole/declutter
Move some docs to docs dir (declutter the top).
2018-08-23 13:33:06 -07:00
Jeffrey Regan
da142a8e97 Move some docs to docs dir (declutter the top). 2018-08-23 11:33:12 -07:00
Jeff Regan
6c81e3b95f Merge pull request #284 from Liujingfang1/vendor
Add missing files when running dep ensure
2018-08-23 11:27:47 -07:00
Jingfang Liu
a0089a2521 Add missing files when running dep ensure 2018-08-23 11:07:42 -07:00
k8s-ci-robot
11768f6232 Merge pull request #280 from monopole/fixntis
Fix some Go nits.
2018-08-23 10:37:58 -07:00
Jeff Regan
675c17737f Merge pull request #279 from Liujingfang1/quote
fix the double quotation problem in ConfigMapGenerator
2018-08-23 10:35:34 -07:00
Jeffrey Regan
735a93d000 Fix some Go nits. 2018-08-23 10:30:32 -07:00
Jeff Regan
67d2c2ed4a Merge pull request #281 from monopole/addVarCheck
Add varcheck to presubmit.
2018-08-23 10:25:32 -07:00
Jeffrey Regan
f931e15653 Add varcheck to presubmit. 2018-08-23 10:17:37 -07:00
Jingfang Liu
34169174a8 fix the double quotation problem in ConfigMapGenerator 2018-08-23 09:36:50 -07:00
Jeff Regan
ebf33964c7 Merge pull request #276 from kubernetes-sigs/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2018-08-22 20:05:47 -07:00
Jeff Regan
38a5e12d66 Create CODE_OF_CONDUCT.md 2018-08-22 20:05:19 -07:00
k8s-ci-robot
04ab218fa0 Merge pull request #275 from monopole/deletediff
Delete diff command and code it uses.
2018-08-22 18:38:39 -07:00
Jeffrey Regan
950c353f90 Delete diff command and code it uses. 2018-08-22 17:18:39 -07:00
Jeff Regan
aff09b1108 Merge pull request #271 from Liujingfang1/cleanup
cleanup after handling remote bases
2018-08-22 17:14:11 -07:00
Jingfang Liu
6da691f874 cleanup after handling remote bases 2018-08-22 13:22:45 -07:00
Jeff Regan
22c99aa535 Merge pull request #274 from monopole/docsreadme
Tweak docs readme
2018-08-22 12:21:32 -07:00
Jeffrey Regan
5fa209acfa Tweak docs readme 2018-08-22 12:20:13 -07:00
Jeff Regan
d72879e109 Merge pull request #147 from guineveresaenger/labels-and-annotations
Edit add label/annotation
2018-08-22 10:33:00 -07:00
Jeff Regan
337f3631ff Merge pull request #272 from Liujingfang1/docs
Add docs README.md
2018-08-22 10:22:00 -07:00
guineveresaenger
b3993dc874 Adds starter validation framework for semantic validation of inputs. 2018-08-22 18:20:51 +02:00
guineveresaenger
11c04dd6c4 Removes semantic validation from addmetadata.go and tests.
Due to moving some input parsing to the Validate method, it was renamed to reflect this additional purpose.
Tests were removed where appropriate.
2018-08-22 18:15:34 +02:00
Jingfang Liu
b29e449d4a Add docs README.md 2018-08-22 09:05:54 -07:00
Jingfang Liu
430f2f84fb Merge pull request #270 from Liujingfang1/docs
fix bug in the example download links
2018-08-21 12:08:32 -07:00
Jingfang Liu
52c6b5755b fix bug in the example download links 2018-08-21 12:04:53 -07:00
k8s-ci-robot
958bc63293 Merge pull request #269 from Liujingfang1/pvc
Add namereference of PersistentVolume in PersistentVolumeClain
2018-08-21 09:48:33 -07:00
Jingfang Liu
94ed0fe515 Add namereference of PersistentVolume in PersistentVolumeClain 2018-08-21 09:37:25 -07:00
Jeff Regan
bb8233ceff Merge pull request #265 from Liujingfang1/metadata
add typemeta to kustomization
2018-08-20 13:59:43 -07:00
Jingfang Liu
6221bed190 add typemeta to kustomization 2018-08-20 13:47:33 -07:00
k8s-ci-robot
1ffeb181e7 Merge pull request #264 from Liujingfang1/docs
Add example for kustomize build {url} and remote bases
2018-08-20 12:19:00 -07:00
Jingfang Liu
759ba1cbf4 Add example for kustomize build {url} 2018-08-20 09:52:32 -07:00
k8s-ci-robot
12f2c41273 Merge pull request #266 from ahmetb/patch-1
add homebrew package to INSTALL.md
2018-08-17 10:48:01 -07:00
Ahmet Alp Balkan
2174741376 add homebrew package to INSTALL.md 2018-08-17 07:50:28 -07:00
guineveresaenger
31dd8fc5b1 Restructured tests 2018-08-16 13:32:42 -07:00
guineveresaenger
77f4811779 Tests test Validate function 2018-08-16 02:36:11 -07:00
Jeff Regan
7050a45134 Merge pull request #262 from Liujingfang1/repoUrl3
Add all dependency of go-getter
2018-08-15 15:47:35 -07:00
k8s-ci-robot
3b64f1e102 Merge pull request #260 from Liujingfang1/repoUrl2
Add kustomize build {repoUrl}
2018-08-15 15:45:00 -07:00
Jingfang Liu
6c4c79e2cc Merge pull request #263 from Liujingfang1/gc
Add garbage collection KEP link
2018-08-15 15:22:21 -07:00
Jingfang Liu
3975ebc21a Add garbage collection KEP link 2018-08-15 15:19:18 -07:00
Jingfang Liu
ec95e5f97e Add all dependency of go-getter 2018-08-15 11:37:03 -07:00
Jingfang Liu
72b1a4bc5c combine fileLoader.New and NewLoader into one function: NewLoader 2018-08-15 11:04:31 -07:00
k8s-ci-robot
16447efca3 Merge pull request #255 from sethpollack/secret
add docs for envCommand
2018-08-15 09:35:02 -07:00
guineveresaenger
524d593c5c Separate functions for RunnAddLabel and RunAddAnnotation 2018-08-15 03:51:56 -07:00
guineveresaenger
3b644474c4 Parse data into string map for easy access in RunAddMetadata 2018-08-15 03:13:03 -07:00
Seth Pollack
42e6ced2b0 add docs 2018-08-14 20:55:26 -04:00
Jingfang Liu
f018370628 Add kustomize build {repoUrl} 2018-08-14 16:10:51 -07:00
Jeff Regan
c9a8bc1121 Merge pull request #256 from ivan4th/command-timeout-1
Add timeoutSeconds to secretArgs
2018-08-14 14:54:50 -07:00
k8s-ci-robot
8c7cbb12dd Merge pull request #257 from Liujingfang1/repoUrl2
Add dependency: github.com/hashicorp/go-getter
2018-08-14 14:49:29 -07:00
Jingfang Liu
b02f7775c5 manually add dependency on go-getter 2018-08-14 14:37:31 -07:00
Ivan Shvedunov
f9a0e671b7 Add timeoutSeconds to secretArgs 2018-08-14 22:55:59 +03:00
Jeff Regan
70fb22cad6 Merge pull request #250 from ivan4th/fix-resource-load-crash
Don't crash on resource load errors
2018-08-14 12:53:24 -07:00
Ivan Shvedunov
2ae00db6a9 Don't crash on resource load errors 2018-08-14 22:14:12 +03:00
k8s-ci-robot
f9577ab540 Merge pull request #247 from ryane/setnamespace
add support for `kustomize edit set namespace`
2018-08-13 11:17:06 -07:00
guineveresaenger
6a2786a5c4 Remove Complete function and references 2018-08-13 07:49:51 -07:00
guineveresaenger
924aa6fb29 Use iota declaration for constants and implements string method for KindOfAdd metadata 2018-08-13 07:46:06 -07:00
ryane
e2cd44f9d8 add support for kustomize edit set namespace
fixes #246
2018-08-10 22:42:50 -04:00
Jeff Regan
017c4ae0aa Merge pull request #245 from Liujingfang1/output
Add -o flag to kustomize build
2018-08-09 16:49:25 -07:00
Jingfang Liu
7b2baad390 Add -o flag to kustomize build 2018-08-09 13:15:39 -07:00
Jeff Regan
bc2d69f4f9 Merge pull request #241 from sethpollack/secret
add env sources to secrets
2018-08-09 13:05:21 -07:00
Jeff Regan
e913a71fad Merge pull request #244 from Liujingfang1/deprecation
Add deprecation message for namePrefix behavior change
2018-08-09 13:04:36 -07:00
Seth Pollack
7406dda074 fixes 2018-08-09 14:45:56 -04:00
Jingfang Liu
0b4df3d414 Add deprecation message for namePrefix behavior change 2018-08-09 11:25:37 -07:00
Jeff Regan
7d38916d63 Merge pull request #243 from monopole/updateDeps
Automated update of Gopkg.lock via dep ensure
2018-08-09 11:08:47 -07:00
Jeffrey Regan
79d1abe573 dep ensure run 2018-08-09 10:54:29 -07:00
Jeff Regan
9563052094 Merge pull request #233 from Liujingfang1/glob2
preserve order and comments in edit
2018-08-09 09:49:30 -07:00
Seth Pollack
f881c19bb6 add env sources to secrets 2018-08-09 09:17:53 -04:00
Jingfang Liu
8d7b5f82c4 preserve order and comments in edit 2018-08-08 15:03:16 -07:00
Jingfang Liu
7554406c61 Merge pull request #240 from kubernetes-sigs/revert-239-namespace2
Revert "Skip adding nameprefix to namespace"
2018-08-08 13:41:38 -07:00
Jingfang Liu
cf17050170 Revert "Skip adding nameprefix to namespace" 2018-08-08 13:39:01 -07:00
k8s-ci-robot
3857a67701 Merge pull request #239 from Liujingfang1/namespace2
Skip adding nameprefix to namespace
2018-08-08 11:56:15 -07:00
Jingfang Liu
10665c6fc9 Skip adding nameprefix to namespace 2018-08-08 10:02:42 -07:00
k8s-ci-robot
e0a09f4755 Merge pull request #237 from Liujingfang1/ingress
add namepreference for secret in ingress annotation
2018-08-08 09:49:06 -07:00
Jingfang Liu
31c6a55747 add namepreference for secret in ingress annotation 2018-08-07 13:26:39 -07:00
Jeff Regan
8332a70d19 Merge pull request #231 from bendory/master
Container Builder has been renamed Cloud Build
2018-08-03 10:29:23 -07:00
David Bendory
7fe2338acd Container Builder has been renamed Cloud Build 2018-08-03 13:22:49 -04:00
k8s-ci-robot
43d4dbc07a Merge pull request #228 from Liujingfang1/glob2
Change the order of validate and  expandFileSource in add configmap
2018-08-02 16:26:27 -07:00
Jingfang Liu
f0cf4579d2 Change the order of validate and expandFileSource in add configmap subcommand 2018-08-02 11:39:27 -07:00
k8s-ci-robot
68ba37f139 Merge pull request #226 from Liujingfang1/glob2
Add glob support in subcommands `add patch` and `add configmap`
2018-08-02 11:19:27 -07:00
Jingfang Liu
bf73633cda Add glob support in subcommands add patch and add configmap 2018-08-02 11:01:20 -07:00
Jeff Regan
55f8828ba1 Merge pull request #222 from Liujingfang1/glob
Add glob support in edit add resource
2018-08-01 15:51:45 -07:00
Jeff Regan
0e1307dccf Merge pull request #224 from Liujingfang1/imagetag
Use regexp in set imagetag
2018-08-01 15:50:50 -07:00
Jingfang Liu
4471b75912 Use regexp in set imagetag 2018-08-01 11:58:21 -07:00
k8s-ci-robot
75c6204337 Merge pull request #225 from Liujingfang1/pathconfig
Add ingress annotations to the namereference path config
2018-08-01 11:52:40 -07:00
Jingfang Liu
1b7171ac9e Add glob support in edit add resource 2018-08-01 11:43:28 -07:00
Jingfang Liu
5193d6b4a8 Add ingress annotations to the namereference path config 2018-08-01 10:47:01 -07:00
Jeff Regan
6a834b6262 Merge pull request #223 from monopole/noFlags
More description of eschewed features
2018-07-31 11:49:56 -07:00
Jeffrey Regan
083d3cbb65 More description of eschewed features 2018-07-31 11:48:36 -07:00
k8s-ci-robot
e68411b71e Merge pull request #220 from monopole/noglobbing
Eschew globbing doc
2018-07-31 10:04:47 -07:00
Jeff Regan
664774576c Merge pull request #219 from Liujingfang1/glob
remove glob support from kustomization.yaml
2018-07-30 17:03:53 -07:00
Jeffrey Regan
37e97084f9 Eschew globbing doc 2018-07-30 16:57:16 -07:00
Jingfang Liu
de4d8b7dfa remove glob support from kustomization.yaml 2018-07-30 16:28:40 -07:00
k8s-ci-robot
7f97108686 Merge pull request #216 from Liujingfang1/namespace
Add multibases example with different namespace
2018-07-30 15:54:46 -07:00
Jingfang Liu
71f069cf95 Add multibases example with different namespace 2018-07-30 15:21:53 -07:00
k8s-ci-robot
3dbe732cb5 Merge pull request #215 from monopole/eschewed
Enumerate eschewed features in a document.
2018-07-30 15:01:40 -07:00
Jeff Regan
e5aea4423b Merge pull request #214 from Liujingfang1/namespace
add namespace in ResId
2018-07-30 15:00:10 -07:00
Jeff Regan
100f05260e Merge pull request #209 from Liujingfang1/yaml
ignore the empty YAML object
2018-07-30 14:57:15 -07:00
Jeffrey Regan
02f9329747 Enumerate eschewed features in docs 2018-07-30 14:56:20 -07:00
Jingfang Liu
b6abd7600c add namespace in ResId 2018-07-30 14:04:35 -07:00
Jingfang Liu
2e7093e67f ignore the empty YAML object 2018-07-30 12:58:11 -07:00
k8s-ci-robot
3b3a272d27 Merge pull request #213 from Liujingfang1/imagetags
use regexp to determine if the image matched in imagetag transformer
2018-07-30 11:58:56 -07:00
Jingfang Liu
36115a7fa3 use regexp to determin if the image matched in imagetag transformer 2018-07-30 11:09:32 -07:00
k8s-ci-robot
4d9d54e2c7 Merge pull request #204 from Liujingfang1/diamond
Add support for using common base
2018-07-27 14:00:57 -07:00
Jingfang Liu
88aec95628 remove commented code
update multibases/README.md
2018-07-27 13:45:49 -07:00
Jingfang Liu
e30401489d Add example for multibases 2018-07-27 10:43:16 -07:00
Jingfang Liu
58bc4b14a2 Add support for using common base 2018-07-27 10:16:44 -07:00
Jeff Regan
2824c28e08 Merge pull request #203 from mortent/BetterSecretGenErrorMessage
More information in error message when secret gen fails
2018-07-26 15:45:40 -07:00
Morten Torkildsen
d7cbb95d9c More information in error message when secret gen fails 2018-07-26 12:50:07 -07:00
k8s-ci-robot
e771ec1169 Merge pull request #201 from monopole/meh
Combine loaderImpl and fileLoader.
2018-07-26 10:09:56 -07:00
Jeffrey Regan
9e5374e725 Combine loaderImpl and fileLoader. 2018-07-25 17:23:04 -07:00
k8s-ci-robot
4569a09d54 Merge pull request #200 from monopole/deleteDuplicativeCode
Delete duplicative code.
2018-07-25 09:33:55 -07:00
jregan
25d3ad7522 Delete duplicative code. 2018-07-24 20:42:43 -07:00
Jeff Regan
77e18724db Merge pull request #199 from monopole/moarDeletion
Drop the notion of the SchemeLoader
2018-07-24 18:18:25 -07:00
k8s-ci-robot
12d1771bb3 Merge pull request #197 from Liujingfang1/master
Change configmapGenerator to configmap.yaml in helloWorld example
2018-07-24 13:50:56 -07:00
Jeffrey Regan
a78aa22399 Drop useless or duplicative code. 2018-07-24 13:25:26 -07:00
Jingfang Liu
05a91893bf break helloWorld example into two examples:
- one for declaring a ConfigMap as resources
- one for declaring a ConfigMap from ConfigMapGenerator and rolling
update
2018-07-24 11:35:15 -07:00
Jeff Regan
8d420ec3f7 Merge pull request #196 from Liujingfang1/cr
Add docs and demo for imageTags
2018-07-23 16:44:12 -07:00
Jingfang Liu
838a766d12 Add docs and demo for imageTags 2018-07-23 16:35:04 -07:00
k8s-ci-robot
50d79e4d3e Merge pull request #198 from monopole/anotherWayToDelete
Delete some code.
2018-07-23 15:52:24 -07:00
Jeff Regan
4d2d450f6e Merge pull request #191 from babiel/fix-diff-tests-on-macos
Fix wrong path in diff tests on macOS
2018-07-23 15:37:29 -07:00
Jeff Regan
fdc46fb0b1 Delete some code. 2018-07-23 15:23:30 -07:00
k8s-ci-robot
92ac9b5a0e Merge pull request #194 from droot/bugfix/version-fix-issue-148
fixed version info injection in build script
2018-07-23 14:42:18 -07:00
Jeff Regan
857a9df70f Merge pull request #195 from monopole/tightenUp
Pull factories up call stack (make them less often).
2018-07-23 14:02:47 -07:00
Jeffrey Regan
969f4f28fa Pull factories out of the bowels. 2018-07-23 13:48:46 -07:00
Sunil Arora
58aa45c50a fixed version info injection in build script
fixes #148
2018-07-23 11:54:37 -07:00
k8s-ci-robot
5715f4bab4 Merge pull request #192 from Liujingfang1/cr
Add set imagetag command
2018-07-23 10:56:18 -07:00
Jingfang Liu
c8502c78f5 drop complete function from setImageTag subcommand 2018-07-23 10:47:52 -07:00
Jingfang Liu
909de5c94a Add set imagetag command 2018-07-23 10:24:32 -07:00
Maximilian Gaß
2eaeb83ec3 Fix wrong path in diff tests on macOS 2018-07-23 17:00:41 +02:00
k8s-ci-robot
03b9c2a3a3 Merge pull request #188 from Liujingfang1/cr
Enable imageTagTransformer in application
2018-07-20 11:54:56 -07:00
Jingfang Liu
59b98727ec enable imageTagTransformer in application 2018-07-20 11:30:34 -07:00
Jingfang Liu
5851f96524 Add initContainers in imageTagTransformer 2018-07-20 11:30:06 -07:00
k8s-ci-robot
08be3f061e Merge pull request #187 from monopole/secFactory
Introduce secret factory.
2018-07-20 11:17:53 -07:00
k8s-ci-robot
5906aaba19 Merge pull request #184 from Liujingfang1/cr
Add imageTagTransformer
2018-07-20 10:54:07 -07:00
Jingfang Liu
4b6f180d0c address comments 2018-07-20 10:45:17 -07:00
Jeffrey Regan
7f22f187f8 Introduce secret factory. 2018-07-20 10:40:47 -07:00
Jingfang Liu
fa3a64e352 Add imageTagTransformer 2018-07-20 10:23:12 -07:00
Jeff Regan
82f2cf9124 Merge pull request #186 from monopole/secretFactory
Inject a file system object into "Application".
2018-07-20 09:24:19 -07:00
Jeffrey Regan
276693cf0e Make a secret factory. 2018-07-20 09:09:52 -07:00
Jeff Regan
0197c019cc Merge pull request #185 from monopole/evenMoreFix86
Start remerging two forked sets of configmap factory code
2018-07-19 18:44:20 -07:00
Jeffrey Regan
9576a81787 Put the two sets of configmap make codes sidebyside 2018-07-19 18:33:55 -07:00
k8s-ci-robot
ff4a1c0b4f Merge pull request #183 from monopole/moreFix86
Remove a util package; more cleanup for #86
2018-07-19 16:19:10 -07:00
Jeff Regan
7dd28b1fd9 Merge pull request #176 from babiel/do-not-create-networkpolicy-matchlabels
Disable NetworkPolicy podSelector.matchLabels CreateIfNotPresent
2018-07-19 15:51:13 -07:00
Jeffrey Regan
b754557418 Remove a util package; more cleanup for #86 2018-07-19 14:39:18 -07:00
k8s-ci-robot
f305c0d791 Merge pull request #182 from Liujingfang1/cr
Add ContainerRef in kustomization type
2018-07-19 14:34:41 -07:00
Jingfang Liu
3fdaa2e903 Add ImageTags in kustomization type 2018-07-19 14:29:29 -07:00
k8s-ci-robot
964c74fb46 Merge pull request #181 from monopole/fix86
configMap factory refactor for #86
2018-07-19 14:14:53 -07:00
Jeffrey Regan
f14988ff80 configMap factory refactor for #86 2018-07-19 14:06:51 -07:00
k8s-ci-robot
f1adbfdbff Merge pull request #180 from knqyf263/fix_docs
Fix configGeneration.md
2018-07-19 08:37:53 -07:00
knqyf263
072bf992b0 Fix configGeneration.md 2018-07-19 11:09:00 +09:00
Jeff Regan
2d0d09e178 Merge pull request #179 from monopole/nitfixes
Fix cluster of silly Go nits.
2018-07-18 17:49:28 -07:00
Jeffrey Regan
564b0d6827 Fix cluster of silly Go nits. 2018-07-18 17:45:17 -07:00
guineveresaenger
187415430f Removed individual files in favor of combined metadata file 2018-07-18 17:09:41 -07:00
guineveresaenger
afac2fb46a Uses single file for both addLabel and addAnnotation commands, as the code is nearly identical. Tests included. 2018-07-18 17:09:41 -07:00
guineveresaenger
20fd433f75 Add tests 2018-07-18 17:09:41 -07:00
guineveresaenger
1e3824057b Implements labels and annotations as subcommands of edit 2018-07-18 17:09:41 -07:00
k8s-ci-robot
5edae84a9e Merge pull request #177 from monopole/improveFsAbstraction
Replace os.Stat with IsDir and Exists, simplifying FS abstraction.
2018-07-18 13:43:06 -07:00
Jeffrey Regan
9432671887 Replace os.Stat with IsDir, simplifying FS abstraction. 2018-07-18 12:57:53 -07:00
Jeff Regan
8fda0f87ab Merge pull request #159 from Liujingfang1/master
remove adding hash for configmap/secret read from resource yaml files
2018-07-18 11:10:09 -07:00
Jingfang Liu
08bc8637c8 set the default behavior for SecretGenerator and ConfigMapGenerator as create 2018-07-18 10:59:38 -07:00
Jingfang Liu
9645f397ef remove adding hash for configmap/secret read from resource yaml files 2018-07-18 10:57:50 -07:00
Maximilian Gaß
ed9f716361 Add unit test for NetworkPolicy 2018-07-18 14:11:18 +02:00
Maximilian Gaß
9986b65326 Disable creation of NetworkPolicy podSelector.matchLabels 2018-07-18 14:01:22 +02:00
Jingfang Liu
94dab9ddc4 Merge pull request #175 from Liujingfang1/crd
skip adding namespace to CustomResourceDefinitions
2018-07-17 13:31:15 -07:00
Jingfang Liu
81f246ed60 skip adding namespace to CustomResourceDefinitions 2018-07-17 13:18:43 -07:00
Jingfang Liu
30ed50eb27 Merge pull request #166 from Liujingfang1/namespace
Add namespace transformation for subjects.namespace in ClusterRoleBinding
2018-07-17 13:15:40 -07:00
Jingfang Liu
4325401fe7 Add namespace transformation for subjects.namespace in ClusterRoleBinding 2018-07-17 13:10:51 -07:00
Jingfang Liu
65af5c13f1 Merge pull request #146 from babiel/fix-name-hash-in-refvars
Run namehash transformer before resolving names for refvars
2018-07-13 09:15:07 -07:00
Maximilian Gaß
9674fd12b2 Run namehash transformer before resolving names for refvars 2018-07-13 09:45:48 +02:00
Jingfang Liu
2377902a0b Merge pull request #145 from babiel/pod-affinity-label-transformer
Add common labels to pod affinity/anti-affinity label selector
2018-07-12 15:41:58 -07:00
Jingfang Liu
1dbde0b085 Merge pull request #140 from babiel/add-networkpolicy-to-labels
Add NetworkPolicy to label transformer
2018-07-12 15:41:51 -07:00
Jingfang Liu
5920563bbd Merge pull request #158 from george-angel/master
Interpret projected configMap volume names
2018-07-11 14:30:04 -07:00
Jingfang Liu
23201c27f0 Merge pull request #160 from Liujingfang1/helloworld
update helloWorld example: change configmap to configmapGenerator
2018-07-11 14:25:47 -07:00
George Angel
d4c7131f8f fix linter dupl errors 2018-07-11 22:18:49 +01:00
Jingfang Liu
d2b189874b update helloWorld example: change configmap to configmapGenerator 2018-07-11 14:00:07 -07:00
Jingfang Liu
98a38eb290 Merge pull request #151 from Rio/vars-docs
docs(vars): Add some documentation explaining the usage and limits of vars
2018-07-11 09:45:40 -07:00
George Angel
aa729229e2 Interpret projected configMap volume names
Append hashes to configMap names under the projected configMap volume,
for Kind: Deployment and StatefulSet
2018-07-11 10:30:31 +01:00
Rio Kierkels
afbc1b0401 docs(vars): add some docs explaining the usage and limitations of vars 2018-07-11 10:43:11 +02:00
Jingfang Liu
3305be9589 Merge pull request #150 from ryane/fix-pvc-claimname
add PersistentVolumeClaim to name reference transformer
2018-07-09 13:51:52 -07:00
ryane
36772aac89 add PersistentVolumeClaim to name reference transformer
fixes #149
2018-07-03 10:16:58 -04:00
Maximilian Gaß
7755d6cac2 Add common labels to pod affinity/anti-affinity label selector 2018-07-02 16:02:11 +02:00
Maximilian Gaß
0c260ef804 Add NetworkPolicy to label transformer 2018-06-26 17:18:50 +02:00
3292 changed files with 2007074 additions and 2423 deletions

View File

@@ -8,6 +8,7 @@ go:
before_install:
- source ./bin/consider-early-travis-exit.sh
- sudo apt-get install tree
- go get -u github.com/opennota/check/cmd/varcheck
- go get -u github.com/golang/lint/golint
- go get -u golang.org/x/tools/cmd/goimports
- go get -u github.com/onsi/ginkgo/ginkgo

230
Gopkg.lock generated
View File

@@ -2,175 +2,351 @@
[[projects]]
digest = "1:8e47871087b94913898333f37af26732faaab30cdb41571136cf7aec9921dae7"
name = "github.com/PuerkitoBio/purell"
packages = ["."]
pruneopts = ""
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:331a419049c2be691e5ba1d24342fc77c7e767a80c666a18fd8a9f7b82419c1c"
name = "github.com/PuerkitoBio/urlesc"
packages = ["."]
pruneopts = ""
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[projects]]
digest = "1:9299ad32dcec0f92ad06773f73426bd46a21efa96f6a8138c287bb185933e77e"
name = "github.com/aws/aws-sdk-go"
packages = [
"aws",
"aws/awserr",
"aws/awsutil",
"aws/client",
"aws/client/metadata",
"aws/corehandlers",
"aws/credentials",
"aws/credentials/ec2rolecreds",
"aws/credentials/endpointcreds",
"aws/credentials/stscreds",
"aws/csm",
"aws/defaults",
"aws/ec2metadata",
"aws/endpoints",
"aws/request",
"aws/session",
"aws/signer/v4",
"internal/sdkio",
"internal/sdkrand",
"internal/sdkuri",
"internal/shareddefaults",
"private/protocol",
"private/protocol/eventstream",
"private/protocol/eventstream/eventstreamapi",
"private/protocol/query",
"private/protocol/query/queryutil",
"private/protocol/rest",
"private/protocol/restxml",
"private/protocol/xml/xmlutil",
"service/s3",
"service/sts",
]
pruneopts = ""
revision = "daed0c76021ea9c4e659e3ec80bcd2d657297100"
version = "v1.15.12"
[[projects]]
branch = "master"
digest = "1:98e84060475ed245c3b355042afd43a74aa7d32efe50658f4f995977916f9fc3"
name = "github.com/bgentry/go-netrc"
packages = ["netrc"]
pruneopts = ""
revision = "9fd32a8b3d3d3f9d43c341bfe098430e07609480"
[[projects]]
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = ""
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:971e9ba63a417c5f1f83ab358677bc59e96ff04285f26c6646ff089fb60b15e8"
name = "github.com/emicklei/go-restful"
packages = [
".",
"log"
"log",
]
pruneopts = ""
revision = "3658237ded108b4134956c1b3050349d93e7b895"
version = "v2.7.1"
[[projects]]
digest = "1:dcefbadf4534c5ecac8573698fba6e6e601157bfa8f96aafe29df31ae582ef2a"
name = "github.com/evanphx/json-patch"
packages = ["."]
pruneopts = ""
revision = "afac545df32f2287a079e2dfb7ba2745a643747e"
version = "v3.0.0"
[[projects]]
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
name = "github.com/ghodss/yaml"
packages = ["."]
pruneopts = ""
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
digest = "1:858b7fe7b0f4bc7ef9953926828f2816ea52d01a88d72d1c45bc8c108f23c356"
name = "github.com/go-ini/ini"
packages = ["."]
pruneopts = ""
revision = "358ee7663966325963d4e8b2e1fbd570c5195153"
version = "v1.38.1"
[[projects]]
branch = "master"
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
name = "github.com/go-openapi/jsonpointer"
packages = ["."]
pruneopts = ""
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
[[projects]]
branch = "master"
digest = "1:3830527ef0f4f9b268d9286661c0f52f9115f8aefd9f45ee7352516f93489ac9"
name = "github.com/go-openapi/jsonreference"
packages = ["."]
pruneopts = ""
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
[[projects]]
branch = "master"
digest = "1:238a056875c4b053b4b29984765ee335bf8c539fdf17e527fd9b7aa72521c8dd"
name = "github.com/go-openapi/spec"
packages = ["."]
pruneopts = ""
revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5"
[[projects]]
branch = "master"
digest = "1:7b067ca8b94982960860d18c42e29f15bbd0e8d9ae8145a83a218296e75393cf"
name = "github.com/go-openapi/swag"
packages = ["."]
pruneopts = ""
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
[[projects]]
digest = "1:0a3f6a0c68ab8f3d455f8892295503b179e571b7fefe47cc6c556405d1f83411"
name = "github.com/gogo/protobuf"
packages = [
"proto",
"sortkeys"
"sortkeys",
]
pruneopts = ""
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a"
name = "github.com/golang/glog"
packages = ["."]
pruneopts = ""
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[[projects]]
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
"ptypes/timestamp",
]
pruneopts = ""
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692"
name = "github.com/google/gofuzz"
packages = ["."]
pruneopts = ""
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
[[projects]]
digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16"
name = "github.com/googleapis/gnostic"
packages = [
"OpenAPIv2",
"compiler",
"extensions"
"extensions",
]
pruneopts = ""
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
version = "v0.1.0"
[[projects]]
branch = "master"
digest = "1:f5d25fd7bdda08e39e01193ef94a1ebf7547b1b931bcdec785d08050598f306c"
name = "github.com/hashicorp/go-cleanhttp"
packages = ["."]
pruneopts = ""
revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
[[projects]]
branch = "master"
digest = "1:fd15b3f6aac9d0fe68c6e38922282e0d2e88cd77b927ac3dd842e363645522c0"
name = "github.com/hashicorp/go-getter"
packages = [
".",
"helper/url",
]
pruneopts = ""
revision = "4bda8fa99001c61db3cad96b421d4c12a81f256d"
[[projects]]
branch = "master"
digest = "1:2cf6c60c74eacadd31652674364af55c8d54a86b8ea193548f1c37f8c9af8f9c"
name = "github.com/hashicorp/go-safetemp"
packages = ["."]
pruneopts = ""
revision = "b1a1dbde6fdc11e3ae79efd9039009e22d4ae240"
[[projects]]
branch = "master"
digest = "1:139bdc2c89779b8ff8b1150be28f889b0ed964e6da96f32cbc9035bd4642881c"
name = "github.com/hashicorp/go-version"
packages = ["."]
pruneopts = ""
revision = "270f2f71b1ee587f3b609f00f422b76a6b28f348"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = ""
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e"
name = "github.com/jmespath/go-jmespath"
packages = ["."]
pruneopts = ""
revision = "0b12d6b5"
[[projects]]
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
name = "github.com/json-iterator/go"
packages = ["."]
pruneopts = ""
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
version = "1.1.3"
[[projects]]
branch = "master"
digest = "1:d9e483f4b9e306facf126bd90b02d512bd22ea4471e1568867e32221a8abbb16"
name = "github.com/mailru/easyjson"
packages = [
"buffer",
"jlexer",
"jwriter"
"jwriter",
]
pruneopts = ""
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
[[projects]]
branch = "master"
digest = "1:83854f6b1d2ce047b69657e3a87ba7602f4c5505e8bdfd02ab857db8e983bde1"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
pruneopts = ""
revision = "58046073cbffe2f25d425fe1331102f55cf719de"
[[projects]]
branch = "master"
digest = "1:51c98e2c9a8d0a724a69f46421876af14e12132cb02f1d0e144785d752247162"
name = "github.com/mitchellh/go-testing-interface"
packages = ["."]
pruneopts = ""
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
[[projects]]
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
name = "github.com/modern-go/concurrent"
packages = ["."]
pruneopts = ""
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
digest = "1:420f9231f816eeca3ff5aab070caac3ed7f27e4d37ded96ce9de3d7a7a2e31ad"
name = "github.com/modern-go/reflect2"
packages = ["."]
pruneopts = ""
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
version = "1.0.0"
[[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = ""
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:74c32990510c9f188556aa17600313e867d1d06f5a9db244056a95d144ec34ce"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = ""
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
version = "v0.0.2"
[[projects]]
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = ""
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:ee723e6a1962a196eeba1b24f82af61a4f60f8821d7aa96d48e787f8337bcffc"
name = "github.com/ulikunitz/xz"
packages = [
".",
"internal/hash",
"internal/xlog",
"lzma",
]
pruneopts = ""
revision = "0c6b41e72360850ca4f98dc341fd999726ea007f"
version = "v0.5.4"
[[projects]]
branch = "master"
digest = "1:9e548233d0dc00e74be262e54a9d1bbe7e4c19e5951083520261740e37daeb02"
name = "golang.org/x/net"
packages = [
"http/httpguts",
"http2",
"http2/hpack",
"idna"
"idna",
]
pruneopts = ""
revision = "2491c5de3490fced2f6cff376127c667efeed857"
[[projects]]
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
name = "golang.org/x/text"
packages = [
"collate",
@@ -187,25 +363,31 @@
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
"width"
"width",
]
pruneopts = ""
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
digest = "1:75fb3fcfc73a8c723efde7777b40e8e8ff9babf30d8c56160d01beffea8a95a6"
name = "gopkg.in/inf.v0"
packages = ["."]
pruneopts = ""
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = ""
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
branch = "master"
digest = "1:663df6da5560210fc39194a0a2c4fceba09ead717c330f1174bb15597cf18ce8"
name = "k8s.io/api"
packages = [
"admissionregistration/v1alpha1",
@@ -235,12 +417,14 @@
"settings/v1alpha1",
"storage/v1",
"storage/v1alpha1",
"storage/v1beta1"
"storage/v1beta1",
]
pruneopts = ""
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
[[projects]]
branch = "master"
digest = "1:bcb2285bb525712de7903a5d254c2789df65c8b58d2cfac5a26d950ad94c2079"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/resource",
@@ -274,28 +458,52 @@
"pkg/util/yaml",
"pkg/watch",
"third_party/forked/golang/json",
"third_party/forked/golang/reflect"
"third_party/forked/golang/reflect",
]
pruneopts = ""
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
[[projects]]
digest = "1:071cc2f032b701b9dba26568e040940f26931a49e3a3985f3375f17f7f6d9c5f"
name = "k8s.io/client-go"
packages = ["kubernetes/scheme"]
pruneopts = ""
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
version = "v7.0.0"
[[projects]]
branch = "master"
digest = "1:386c5d69077ce740614e8309ddf107dde91a5db25d3d779143f452fb4fbdfd1e"
name = "k8s.io/kube-openapi"
packages = [
"pkg/common",
"pkg/util/proto"
"pkg/util/proto",
]
pruneopts = ""
revision = "b3f03f55328800731ce03a164b80973014ecd455"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "586d4cb9094e9b5c0731f16e931b953e9c0f709b7125a7537ae3625ada179eee"
input-imports = [
"github.com/evanphx/json-patch",
"github.com/ghodss/yaml",
"github.com/golang/glog",
"github.com/hashicorp/go-getter",
"github.com/pkg/errors",
"github.com/spf13/cobra",
"k8s.io/api/core/v1",
"k8s.io/apimachinery/pkg/apis/meta/v1",
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"k8s.io/apimachinery/pkg/runtime",
"k8s.io/apimachinery/pkg/runtime/schema",
"k8s.io/apimachinery/pkg/util/mergepatch",
"k8s.io/apimachinery/pkg/util/sets",
"k8s.io/apimachinery/pkg/util/strategicpatch",
"k8s.io/apimachinery/pkg/util/validation",
"k8s.io/apimachinery/pkg/util/yaml",
"k8s.io/client-go/kubernetes/scheme",
"k8s.io/kube-openapi/pkg/common",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -49,10 +49,14 @@
name = "k8s.io/client-go"
version = "7.0.0"
[[constraint]]
[[override]]
branch = "master"
name = "k8s.io/utils"
[[constraint]]
[[override]]
branch = "master"
name = "github.com/go-openapi/spec"
[[constraint]]
branch = "master"
name = "github.com/hashicorp/go-getter"

View File

@@ -127,7 +127,7 @@ This tool is sponsored by [sig-cli] ([KEP]).
[examples]: examples/README.md
[imageBase]: docs/base.jpg
[imageOverlay]: docs/overlay.jpg
[install]: INSTALL.md
[install]: docs/INSTALL.md
[kubernetes style]: docs/glossary.md#kubernetes-style-object
[kustomization]: docs/glossary.md#kustomization
[overlay]: docs/glossary.md#overlay

View File

@@ -41,12 +41,14 @@ function testGoLint {
diff -u <(echo -n) <(go_dirs | xargs -0 golint --min_confidence 0.85 )
}
# Not using the 'goimports' check because it reports hyphens in imported
# package names as errors, and we vendor in packages that have
# hyphens in their names.
function testGoMetalinter {
diff -u <(echo -n) <(go_dirs | xargs -0 gometalinter.v2 --disable-all --deadline 5m \
--enable=misspell \
--enable=structcheck \
--enable=deadcode \
--enable=goimports \
--enable=varcheck \
--enable=goconst \
--enable=unparam \
@@ -58,7 +60,6 @@ function testGoMetalinter {
--dupl-threshold=400 --enable=dupl)
}
function testGoVet {
go vet -all ./...
}

View File

@@ -1,16 +1,16 @@
[releases page]: https://github.com/kubernetes-sigs/kustomize/releases
[`container-builder-local`]: https://github.com/GoogleCloudPlatform/container-builder-local
[Google Container Builder]: https://cloud.google.com/container-builder
[`cloud-build-local`]: https://github.com/GoogleCloudPlatform/cloud-build-local
[Google Cloud Build]: https://cloud.google.com/cloud-build
Scripts and configuration files for publishing a
`kustomize` release on the [releases page].
### Build a release locally
Install [`container-builder-local`], then run
Install [`cloud-build-local`], then run
```
container-builder-local \
cloud-build-local \
--config=build/cloudbuild_local.yaml \
--dryrun=false --write-workspace=/tmp/w .
```
@@ -41,5 +41,5 @@ Push the tag upstream:
git push upstream $version
```
The new tag will trigger a job in [Google Container
Builder] to put a new release on the [releases page].
The new tag will trigger a job in [Google Cloud
Build] to put a new release on the [releases page].

View File

@@ -56,4 +56,4 @@ case $key in
esac
done
/goreleaser release --config=build/goreleaser.yml --rm-dist --skip-validate ${SNAPSHOT}
/goreleaser release --config=build/goreleaser.yaml --rm-dist --skip-validate ${SNAPSHOT}

View File

@@ -4,7 +4,7 @@ project_name: kustomize
builds:
- main: ./kustomize.go
binary: kustomize
ldflags: -s -X github.com/kubernetes-sigs/kustomize/version.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/version.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/version.buildDate={{.Date}}
ldflags: -s -X github.com/kubernetes-sigs/kustomize/pkg/commands.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/pkg/commands.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/pkg/commands.buildDate={{.Date}}
goos:
- darwin
- linux

View File

@@ -1,33 +0,0 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
project_name: kustomize
builds:
- main: ./kustomize.go
binary: kustomize
ldflags: -s -X github.com/kubernetes-sigs/kustomize/version.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/version.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/version.buildDate={{.Date}}
goos:
- darwin
- linux
- windows
goarch:
- amd64
env:
- CGO_ENABLED=0
checksum:
name_template: 'checksums.txt'
archive:
format: binary
snapshot:
name_template: "master"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- Merge pull request
- Merge branch
release:
github:
owner: kubernetes-sigs
name: kustomize

View File

@@ -1,6 +0,0 @@
[Kubernetes Community Code of Conduct]: https://git.k8s.io/community/code-of-conduct.md
# Code of Conduct
This project has adopted the
[Kubernetes Community Code of Conduct].

4
docs/CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,4 @@
# Kustomize Community Code of Conduct
Kustomize contributers expected to adhere to
the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@@ -3,7 +3,13 @@
## Installation
Download a binary from the [release page].
On macOS, you can install kustomize with Homebrew package
manager:
brew install kustomize
For all operating systems, download a binary from the
[release page].
Or try this to grab the latest official release
using the command line:

23
docs/README.md Normal file
View File

@@ -0,0 +1,23 @@
# Kustomize docs
* [installation instructions](INSTALL.md)
* [kustomization.yaml](kustomization.yaml) - Example of a
[kustomization](glossary.md#kustomization)
with explanations of each field.
* [workflow](workflows.md) - Some steps one might take in using
bespoke and off-the-shelf configurations.
* [glossary](glossary.md) - An attempt to disambiguiate terminology.
* [eschewed features](eschewedFeatures.md) - Why certain features are (currently)
not supported in Kustomize.
* [contributing guidelines](CONTRIBUTING.md) - Please read before sending a PR.
* [code of conduct](CODE_OF_CONDUCT.md)

80
docs/eschewedFeatures.md Normal file
View File

@@ -0,0 +1,80 @@
# Eschewed Features
## Removal directives
`kustomize` supports configurations that can be reasoned about as
_compositions_ or _mixins_ - concepts that are widely accepted as
a best practice in various programming languages.
To this end, `kustomize` offers various _addition_ directives. One
can add labels, annotations, patches, resources and bases.
Corresponding _removal_ directives are not offered.
Removal semantics would introduce many possibilities for
inconsistency, and the need to add code to detect, report and
reject it. It would also allow, and possibly encourage,
unnecessarily complex configuration layouts.
When faced with a situation where removal is desirable, it's
always possible to remove things from a base like labels and
annotations, and/or split multi-resource manifests into individual
resource files - then add things back as desired via the
[kustomization].
If the underlying base is outside of one's control, an [OTS
workflow] is the recommended best practice. Fork the base, remove
what you don't want and commit it to your private fork, then use
kustomize on your fork. As often as desired, use _git rebase_ to
capture improvements from the upstream base.
## Build-time side effects from CLI args or env variables
`kustomize` supports the best practice of storing one's
entire configuration in a version control system.
Changing `kustomize build` configuration output as a result
of additional arguments or flags to `build`, or by
consulting shell environment variable values in `build`
code, would violate that goal.
`kustomize` insteads offers [kustomization] file `edit`
commands. Like any shell command, they can accept
environment variable arguments.
For example, to set the tag used on an image to match an
environment variable, run
```
kustomize edit set imagetag nginx:$MY_NGINX_VERSION
```
as part of some encapsulating work flow executed before
`kustomize build`.
## Globs in kustomization files
`kustomize` supports the best practice of storing one's
entire configuration in a version control system.
Globbing the local file system for files not explicitly
declared in the [kustomization] file at `kustomize build` time
would violate that goal.
Allowing globbing in a kustomization file would also introduce
the same problems as allowing globbing in [java import]
declarations or BUILD/Makefile dependency rules.
`kustomize` will instead provide kustomization file editting
commands that accept globbed arguments, expand them at _edit
time_ relative to the local file system, and store the resulting
explicit names into the kustomization file.
In this way the resources, patches and bases used at _build time_
remain explicitly declared in version control.
[base]: glossary.md#base
[kustomization]: glossary.md#kustomization
[OTS workflow]: workflows.md#off-the-shelf-configuration
[java import]: https://www.codebyamir.com/blog/pitfalls-java-import-wildcards

View File

@@ -285,7 +285,7 @@ The _target_ is the argument to `kustomize build`, e.g.:
> kustomize build $target
> ```
`$target` must be a path to a directory that
`$target` must be a path or a url to a directory that
immediately contains a [kustomization].
The target contains, or refers to, all the information

View File

@@ -83,15 +83,29 @@ secretGenerator:
tls.key: "cat secret/tls.key"
type: "kubernetes.io/tls"
- name: downloaded_secret
# timeoutSeconds specifies the number of seconds to
# wait for the commands below. It defaults to 5 seconds.
timeoutSeconds: 30
commands:
username: "curl -s https://path/to/secrets/username.yaml"
password: "curl -s https://path/to/secrets/password.yaml"
type: Opaque
- name: env_file_secret
# envCommand is similar to command but outputs lines of key=val pairs
# i.e. a Docker .env file or a .ini file.
# you can only specify one envCommand per secret.
envCommand: printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"
type: Opaque
# Each entry in this list should resolve to a directory
# containing a kustomization file, else the
# customization fails.
#
# The entry could be a relative path pointing to a local directory
# or a url pointing to a directory in a remote repo.
# The url should follow hashicorp/go-getter URL format
# https://github.com/hashicorp/go-getter#url-format
#
# The presence of this field means this file (the file
# you a reading) is an _overlay_ that further
# customizes information coming from these _bases_.
@@ -102,6 +116,9 @@ secretGenerator:
# etc. that differ from the common base).
bases:
- ../../base
- github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
- github.com/Liujingfang1/mysql
- github.com/Liujingfang1/kustomize//examples/helloWorld?ref=test-branch
# Each entry in this list should resolve to
# a partial or complete resource definition file.
@@ -136,3 +153,67 @@ patches:
crds:
- crds/typeA.yaml
- crds/typeB.yaml
# Vars are used to insert values from resources that cannot be referenced
# otherwise. For example if you need to pass a Service's name to the arguments
# or environment variables of a program but without hard coding the actual name
# of the Service you'd insert `$(MY_SERVICE_NAME)` into the value field of the
# env var or into the command or args of the container as shown here:
# ```
# containers:
# - image: myimage
# command: ["start", "--host", "$(MY_SERVICE_NAME)"]
# env:
# - name: SECRET_TOKEN
# value: $(SOME_SECRET_NAME)
# ```
#
# Then you'll add an entry to `vars:` like shown below with the same name
# and a reference to the resource from which to pull the field's value.
# The actual field's path is optional and by default it will use
# `metadata.name`. Currently only string type fields are supported, no integers
# or booleans, etc. Also array access is currently not possible. For example getting
# the image field of container number 2 inside of a pod can currently not be done.
#
# Not every location of a variable is supported. To see a complete list of locations
# see the file [refvars.go](https://github.com/kubernetes-sigs/kustomize/blob/master/pkg/transformers/refvars.go#L20).
#
# An example of a situation where you'd not use vars is when you'd like to set a
# pod's `serviceAccountName`. In that case you would just reference the ServiceAccount
# by name and Kustomize will resolve it to the eventual name while building the manifests.
vars:
- name: SOME_SECRET_NAME
objref:
kind: Secret
name: my-secret
apiVersion: v1
- name: MY_SERVICE_NAME
objref:
kind: Service
name: my-service
apiVersion: v1
fieldref:
fieldpath: metadata.name
- name: ANOTHER_DEPLOYMENTS_POD_RESTART_POLICY
objref:
kind: Deployment
name: my-deployment
apiVersion: apps/v1
fieldref:
fieldpath: spec.template.spec.restartPolicy
# ImageTags modify the tags for images without creating patches.
# E.g. Given this fragment of a Deployment:
# ```
# containers:
# - name: myapp
# image: mycontainerregistry/myimage:v0
# - name: nginxapp
# image: nginx:1.7.9
#```
# one can change the tag of myimage to v1 and the tag of nginx to 1.8.0 with the following:
imageTags:
- name: mycontainerregistry/myimage
newTag: v1
- name: nginx
newTag: 1.8.0

View File

@@ -21,14 +21,17 @@ use and maintain a configuration.
## Bespoke configuration
In this workflow, all configuration files are owned by
the user. No content is incorporated from version
In this workflow, all configuration (resource YAML) files
are owned by the user. No content is incorporated from version
control repositories owned by others.
![bespoke config workflow image][workflowBespoke]
#### 1) create a directory in version control
Speculate some overall cluster application called _ldap_;
we want to keep its configuration in its own repo.
> ```
> git init ~/ldap
> ```

View File

@@ -23,11 +23,20 @@ go get github.com/kubernetes-sigs/kustomize
* [springboot](springboot/README.md) - Create a Spring Boot
application production configuration from scratch.
* [configGeneration](configGeneration.md) -
* [combineConfigs](combineConfigs.md) -
Mixing configuration data from different owners
(e.g. devops/SRE and developers).
* [configGenerations](configGeneration.md) -
Rolling update when ConfigMapGenerator changes
* [breakfast](breakfast.md) - Customize breakfast for
Alice and Bob.
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
* [image tags](imageTags.md) - Updating image tags without applying a patch.
* [multibases](multibases/README.md) - Composing three variants (dev, staging, production) with a common base.
* [remote target](remoteBuild.md) - Building a kustomization from a github URL

298
examples/combineConfigs.md Normal file
View File

@@ -0,0 +1,298 @@
[overlay]: ../docs/glossary.md#overlay
[target]: ../docs/glossary.md#target
# Demo: combining config data from devops and developers
Scenario: you have a Java-based server storefront in
production that various internal development teams
(signups, checkout, search, etc.) contribute to.
The server runs in different environments:
_development_, _testing_, _staging_ and _production_,
accepting configuration parameters from java property
files.
Using one big properties file for each environment is
difficult to manage. The files change frequently, and
have to be changed by devops exclusively because
1. the files must at least partially agree on certain
values that devops cares about and that developers
ignore and
1. because the production
properties contain sensitive data like production
database credentials.
## Property sharding
With some study, we notice that the properties are
separable into categories.
### Common properties
E.g. internationalization data, static data like
physical constants, location of external services, etc.
_Things that are the same regardless of environment._
Only one set of values is needed.
Place them in a file called
* `common.properties`
(relative location defined below).
### Plumbing properties
E.g. serving location of static content (HTML, CSS,
javascript), location of product and customer database
tables, ports expected by load balancers, log sinks,
etc.
_The different values for these properties are
precisely what sets the environments apart._
Devops or SRE will want full control over the values
used in production. Testing will have fixed
databases supporting testing. Developers will want
to do whatever they want to try scenarios under
development.
Places these values in
* `development/plumbing.properties`
* `staging/plumbing.properties`
* `production/plumbing.properties`
### Secret properties
E.g. location of actual user tables, database
credentials, decryption keys, etc.
_Things that are a subset of devops controls, that
nobody else has (or should want) access to._
Places these values in
* `development/secret.properties`
* `staging/secret.properties`
* `production/secret.properties`
[kubernetes secret]: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/
and control access to them with (for example) unix file
owner and mode bits, or better yet, put them in
a server dedicated to storing password protected
secrets, and use a field called `secretGenerator`
in your _kustomization_ to create a kubernetes
secret holding them (not covering that here).
<!--
secretGenerator:
- name: app-tls
commands:
tls.crt: "cat tls.cert"
tls.key: "cat tls.key"
type: "kubernetes.io/tls"
EOF
-->
## A mixin approach to management
The way to create _n_ cluster environments that share
some common information is to create _n_ overlays of a
common base.
For the rest of this example, we'll do _n==2_, just
_development_ and _production_, since adding more
environments follows the same pattern.
A cluster environment is created by
running `kustomize build` on a [target] that happens to
be an [overlay].
[helloworld]: helloWorld/README.md
The following example will do that, but will focus on
configMap construction, and not worry about how to
connect the configMaps to deployments (that is covered
in the [helloworld] example).
All files - including the shared property files
discussed above - will be created in a directory tree
that is consistent with the base vs overlay file layout
defined in the [helloworld] demo.
It will all live in this work directory:
<!-- @makeWorkplace @test -->
```
DEMO_HOME=$(mktemp -d)
```
### Create the base
<!-- kubectl create configmap BOB --dry-run -o yaml --from-file db. -->
Make a place to put the base configuration:
<!-- @baseDir @test -->
```
mkdir -p $DEMO_HOME/base
```
Make the data for the base. This direction by
definition should hold resources common to all
environments. Here we're only defining a java
properties file, and a `kustomization` file that
references it.
<!-- @baseKustomization @test -->
```
cat <<EOF >$DEMO_HOME/base/common.properties
color=blue
height=10m
EOF
cat <<EOF >$DEMO_HOME/base/kustomization.yaml
configMapGenerator:
- name: my-configmap
files:
- common.properties
EOF
```
### Create and use the overlay for _development_
Make an abbreviation for the parent of the overlay
directories:
<!-- @overlays @test -->
```
OVERLAYS=$DEMO_HOME/overlays
```
Create the files that define the _development_ overlay:
<!-- @developmentFiles @test -->
```
mkdir -p $OVERLAYS/development
cat <<EOF >$OVERLAYS/development/plumbing.properties
port=30000
EOF
cat <<EOF >$OVERLAYS/development/secret.properties
dbpassword=mothersMaidenName
EOF
cat <<EOF >$OVERLAYS/development/kustomization.yaml
bases:
- ../../base
namePrefix: dev-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
EOF
```
One can now generate the configMaps for development:
<!-- @runDev @test -->
```
kustomize build $OVERLAYS/development
```
#### Check the ConfigMap name
The name of the generated `ConfigMap` is visible in this
output.
The name should be something like `dev-my-configmap-b5m75ck895`:
* `"dev-"` comes from the `namePrefix` field,
* `"my-configmap"` comes from the `configMapGenerator/name` field,
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
computes from the contents of the configMap.
The hash suffix is critical. If the configMap content
changes, so does the configMap name, along with all
references to that name that appear in the YAML output
from `kustomize`.
The name change means deployments will do a rolling
restart to get new data if this YAML is applied to the
cluster using a command like
> ```
> kustomize build $OVERLAYS/development | kubectl apply -f -
> ```
A deployment has no means to automatically know when or
if a configMap in use by the deployment changes.
If one changes a configMap without changing its name
and all references to that name, one must imperatively
restart the cluster to pick up the change.
The best practice is to treat configMaps as immutable.
Instead of editing configMaps, modify your declarative
specification of the cluster's desired state to
point deployments to _new_ configMaps with _new_ names.
`kustomize` makes this easy with its
`configMapGenerator` directive and associated naming
controls. A GC process in the k8s master eventually
deletes unused configMaps.
### Create and use the overlay for _production_
Next, create the files for the _production_ overlay:
<!-- @productionFiles @test -->
```
mkdir -p $OVERLAYS/production
cat <<EOF >$OVERLAYS/production/plumbing.properties
port=8080
EOF
cat <<EOF >$OVERLAYS/production/secret.properties
dbpassword=thisShouldProbablyBeInASecretInstead
EOF
cat <<EOF >$OVERLAYS/production/kustomization.yaml
bases:
- ../../base
namePrefix: prod-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
EOF
```
One can now generate the configMaps for production:
<!-- @runProd @test -->
```
kustomize build $OVERLAYS/production
```
A CICD process could apply this directly to
the cluser using:
> ```
> kustomize build $OVERLAYS/production | kubectl apply -f -
> ```

View File

@@ -1,298 +1,208 @@
[overlay]: ../docs/glossary.md#overlay
[target]: ../docs/glossary.md#target
[patch]: ../../docs/glossary.md#patch
[resource]: ../../docs/glossary.md#resource
[variant]: ../../docs/glossary.md#variant
# Demo: combining config data from devops and developers
## ConfigMap generation and rolling updates
Scenario: you have a Java-based server storefront in
production that various internal development teams
(signups, checkout, search, etc.) contribute to.
Kustomize provides two ways of adding ConfigMap in one `kustomization`, either by declaring ConfigMap as a [resource] or declaring ConfigMap from a ConfigMapGenerator. The formats inside `kustomization.yaml` are
The server runs in different environments:
_development_, _testing_, _staging_ and _production_,
accepting configuration parameters from java property
files.
> ```
> # declare ConfigMap as a resource
> resources:
> - configmap.yaml
>
> # declare ConfigMap from a ConfigMapGenerator
> configMapGenerator:
> - name: a-configmap
> files:
> - configs/configfile
> - configs/another_configfile
> ```
Using one big properties file for each environment is
difficult to manage. The files change frequently, and
have to be changed by devops exclusively because
The ConfigMaps declared as [resource] are treated the same way as other resources. Kustomize doesn't append any hash to the ConfigMap name. The ConfigMap declared from a ConfigMapGenerator is treated differently. A hash is appended to the name and any change in the ConfigMap will trigger a rolling update.
1. the files must at least partially agree on certain
values that devops cares about and that developers
ignore and
1. because the production
properties contain sensitive data like production
database credentials.
In this demo, the same [hello_world](helloWorld/README.md) is used while the ConfigMap declared as [resources] is replaced by a ConfigMap declared from a ConfigmapGenerator. The change in this ConfigMap will result in a hash change and a rolling update.
## Property sharding
### Establish base and staging
With some study, we notice that the properties are
separable into categories.
### Common properties
E.g. internationalization data, static data like
physical constants, location of external services, etc.
_Things that are the same regardless of environment._
Only one set of values is needed.
Place them in a file called
* `common.properties`
(relative location defined below).
### Plumbing properties
E.g. serving location of static content (HTML, CSS,
javascript), location of product and customer database
tables, ports expected by load balancers, log sinks,
etc.
_The different values for these properties are
precisely what sets the environments apart._
Devops or SRE will want full control over the values
used in production. Testing will have fixed
databases supporting testing. Developers will want
to do whatever they want to try scenarios under
development.
Places these values in
* `development/plumbing.properties`
* `staging/plumbing.properties`
* `production/plumbing.properties`
### Secret properties
E.g. location of actual user tables, database
credentials, decryption keys, etc.
_Things that are a subset of devops controls, that
nobody else has (or should want) access to._
Places these values in
* `development/secret.properties`
* `staging/secret.properties`
* `production/secret.properties`
[kubernetes secret]: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/
and control access to them with (for example) unix file
owner and mode bits, or better yet, put them in
a server dedicated to storing password protected
secrets, and use a field called `secretGenerator`
in your _kustomization_ to create a kubernetes
secret holding them (not covering that here).
<!--
secretGenerator:
- name: app-tls
commands:
tls.crt: "cat tls.cert"
tls.key: "cat tls.key"
type: "kubernetes.io/tls"
EOF
-->
## A mixin approach to management
The way to create _n_ cluster environments that share
some common information is to create _n_ overlays of a
common base.
For the rest of this example, we'll do _n==2_, just
_development_ and _production_, since adding more
environments follows the same pattern.
A cluster environment is created by
running `kustomize build` on a [target] that happens to
be an [overlay].
[helloworld]: helloworld.md
The following example will do that, but will focus on
configMap construction, and not worry about how to
connect the configMaps to deployments (that is covered
in the [helloworld] example).
All files - including the shared property files
discussed above - will be created in a directory tree
that is consistent with the base vs overlay file layout
defined in the [helloworld] demo.
It will all live in this work directory:
<!-- @makeWorkplace @test -->
Establish the base with a configMapGenerator
<!-- @establishBase @test -->
```
DEMO_HOME=$(mktemp -d)
```
### Create the base
BASE=$DEMO_HOME/base
mkdir -p $BASE
<!-- kubectl create configmap BOB --dry-run -o yaml --from-file db. -->
curl -s -o "$BASE/#1.yaml" "https://raw.githubusercontent.com\
/kubernetes-sigs/kustomize\
/master/examples/helloWorld\
/{deployment,service}.yaml"
Make a place to put the base configuration:
<!-- @baseDir @test -->
```
mkdir -p $DEMO_HOME/base
```
Make the data for the base. This direction by
definition should hold resources common to all
environments. Here we're only defining a java
properties file, and a `kustomization` file that
references it.
<!-- @baseKustomization @test -->
```
cat <<EOF >$DEMO_HOME/base/common.properties
color=blue
height=10m
EOF
cat <<EOF >$DEMO_HOME/base/kustomization.yaml
configMapGenerator:
- name: my-configmap
files:
- common.properties
cat <<'EOF' >$BASE/kustomization.yaml
commonLabels:
app: hello
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: the-map
literals:
- altGreeting=Good Morning!
- enableRisky="false"
EOF
```
### Create and use the overlay for _development_
Make an abbreviation for the parent of the overlay
directories:
<!-- @overlays @test -->
Establish the staging with a patch applied to the ConfigMap
<!-- @establishStaging @test -->
```
OVERLAYS=$DEMO_HOME/overlays
```
mkdir -p $OVERLAYS/staging
Create the files that define the _development_ overlay:
<!-- @developmentFiles @test -->
```
mkdir -p $OVERLAYS/development
cat <<EOF >$OVERLAYS/development/plumbing.properties
port=30000
EOF
cat <<EOF >$OVERLAYS/development/secret.properties
dbpassword=mothersMaidenName
EOF
cat <<EOF >$OVERLAYS/development/kustomization.yaml
cat <<'EOF' >$OVERLAYS/staging/kustomization.yaml
namePrefix: staging-
commonLabels:
variant: staging
org: acmeCorporation
commonAnnotations:
note: Hello, I am staging!
bases:
- ../../base
namePrefix: dev-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
patches:
- map.yaml
EOF
cat <<EOF >$OVERLAYS/staging/map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: the-map
data:
altGreeting: "Have a pineapple!"
enableRisky: "true"
EOF
```
One can now generate the configMaps for development:
### Review
<!-- @runDev @test -->
The _hello-world_ deployment running in this cluster is
configured with data from a configMap.
The deployment refers to this map by name:
<!-- @showDeployment @test -->
```
kustomize build $OVERLAYS/development
grep -C 2 configMapKeyRef $BASE/deployment.yaml
```
#### Check the ConfigMap name
Changing the data held by a live configMap in a cluster
is considered bad practice. Deployments have no means
to know that the configMaps they refer to have
changed, so such updates have no effect.
The name of the generated `ConfigMap` is visible in this
output.
The recommended way to change a deployment's
configuration is to
The name should be something like `dev-my-configmap-b5m75ck895`:
1. create a new configMap with a new name,
1. patch the _deployment_, modifying the name value of
the appropriate `configMapKeyRef` field.
* `"dev-"` comes from the `namePrefix` field,
* `"my-configmap"` comes from the `configMapGenerator/name` field,
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
computes from the contents of the configMap.
This latter change initiates rolling update to the pods
in the deployment. The older configMap, when no longer
referenced by any other resource, is eventually [garbage
collected](https://github.com/kubernetes-sigs/kustomize/issues/242).
The hash suffix is critical. If the configMap content
changes, so does the configMap name, along with all
references to that name that appear in the YAML output
from `kustomize`.
### How this works with kustomize
The name change means deployments will do a rolling
restart to get new data if this YAML is applied to the
cluster using a command like
The _staging_ [variant] here has a configMap [patch]:
> ```
> kustomize build $OVERLAYS/development | kubectl apply -f -
> ```
A deployment has no means to automatically know when or
if a configMap in use by the deployment changes.
If one changes a configMap without changing its name
and all references to that name, one must imperatively
restart the cluster to pick up the change.
The best practice is to treat configMaps as immutable.
Instead of editing configMaps, modify your declarative
specification of the cluster's desired state to
point deployments to _new_ configMaps with _new_ names.
`kustomize` makes this easy with its
`configMapGenerator` directive and associated naming
controls. A GC process in the k8s master eventually
deletes unused configMaps.
### Create and use the overlay for _production_
Next, create the files for the _production_ overlay:
<!-- @productionFiles @test -->
<!-- @showMapPatch @test -->
```
mkdir -p $OVERLAYS/production
cat <<EOF >$OVERLAYS/production/plumbing.properties
port=8080
EOF
cat <<EOF >$OVERLAYS/production/secret.properties
dbpassword=thisShouldProbablyBeInASecretInstead
EOF
cat <<EOF >$OVERLAYS/production/kustomization.yaml
bases:
- ../../base
namePrefix: prod-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
EOF
cat $OVERLAYS/staging/map.yaml
```
One can now generate the configMaps for production:
This patch is by definition a named but not necessarily
complete resource spec intended to modify a complete
resource spec.
<!-- @runProd @test -->
The ConfigMap it modifies is declared from a configMapGenerator.
<!-- @showMapBase @test -->
```
kustomize build $OVERLAYS/production
grep -C 4 configMapGenerator $BASE/kustomization.yaml
```
A CICD process could apply this directly to
the cluser using:
For a patch to work, the names in the `metadata/name`
fields must match.
> ```
> kustomize build $OVERLAYS/production | kubectl apply -f -
> ```
However, the name values specified in the file are
_not_ what gets used in the cluster. By design,
kustomize modifies names of ConfigMaps declared from ConfigMapGenerator. To see the names
ultimately used in the cluster, just run kustomize:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
The configMap name is prefixed by _staging-_, per the
`namePrefix` field in
`$OVERLAYS/staging/kustomization.yaml`.
The suffix to the configMap name is generated from a
hash of the maps content - in this case the name suffix
is _hhhhkfmgmk_:
<!-- @grepStagingHash @test -->
```
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
```
Now modify the map patch, to change the greeting
the server will use:
<!-- @changeMap @test -->
```
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
```
See the new greeting:
```
kustomize build $OVERLAYS/staging |\
grep -B 2 -A 3 kiwi
```
Run kustomize again to see the new configMap names:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
Confirm that the change in configMap content resulted
in three new names ending in _khk45ktkd9_ - one in the
configMap name itself, and two in the deployment that
uses the map:
<!-- @countHashes @test -->
```
test 3 == \
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
echo $?
```
Applying these resources to the cluster will result in
a rolling update of the deployments pods, retargetting
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
maps. The system will later garbage collect the
unused maps.
## Rollback
To rollback, one would undo whatever edits were made to
the configuation in source control, then rerun kustomize
on the reverted configuration and apply it to the
cluster.

View File

@@ -315,130 +315,3 @@ To deploy, pipe the above commands to kubectl apply:
> kustomize build $OVERLAYS/production |\
> kubectl apply -f -
> ```
## Rolling updates
### Review
The _hello-world_ deployment running in this cluster is
configured with data from a configMap.
The deployment refers to this map by name:
<!-- @showDeployment @test -->
```
grep -C 2 configMapKeyRef $DEMO_HOME/base/deployment.yaml
```
Changing the data held by a live configMap in a cluster
is considered bad practice. Deployments have no means
to know that the configMaps they refer to have
changed, so such updates have no effect.
The recommended way to change a deployment's
configuration is to
1. create a new configMap with a new name,
1. patch the _deployment_, modifying the name value of
the appropriate `configMapKeyRef` field.
This latter change initiates rolling update to the pods
in the deployment. The older configMap, when no longer
referenced by any other resource, is eventually garbage
collected.
### How this works with kustomize
The _staging_ [variant] here has a configMap [patch]:
<!-- @showMapPatch @test -->
```
cat $OVERLAYS/staging/map.yaml
```
This patch is by definition a named but not necessarily
complete resource spec intended to modify a complete
resource spec.
The resource it modifies is here:
<!-- @showMapBase @test -->
```
cat $DEMO_HOME/base/configMap.yaml
```
For a patch to work, the names in the `metadata/name`
fields must match.
However, the name values specified in the file are
_not_ what gets used in the cluster. By design,
kustomize modifies these names. To see the names
ultimately used in the cluster, just run kustomize:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
The configMap name is prefixed by _staging-_, per the
`namePrefix` field in
`$OVERLAYS/staging/kustomization.yaml`.
The suffix to the configMap name is generated from a
hash of the maps content - in this case the name suffix
is _hhhhkfmgmk_:
<!-- @grepStagingHash @test -->
```
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
```
Now modify the map patch, to change the greeting
the server will use:
<!-- @changeMap @test -->
```
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
```
See the new greeting:
```
kustomize build $OVERLAYS/staging |\
grep -B 2 -A 3 kiwi
```
Run kustomize again to see the new configMap names:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
Confirm that the change in configMap content resulted
in three new names ending in _khk45ktkd9_ - one in the
configMap name itself, and two in the deployment that
uses the map:
<!-- @countHashes @test -->
```
test 3 == \
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
echo $?
```
Applying these resources to the cluster will result in
a rolling update of the deployments pods, retargetting
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
maps. The system will later garbage collect the
unused maps.
## Rollback
To rollback, one would undo whatever edits were made to
the configuation in source control, then rerun kustomize
on the reverted configuration and apply it to the
cluster.

View File

@@ -5,5 +5,5 @@ commonLabels:
resources:
- deployment.yaml
- configMap.yaml
- service.yaml
- configMap.yaml

75
examples/imageTags.md Normal file
View File

@@ -0,0 +1,75 @@
# Demo: change image tags
Define a place to work:
<!-- @makeWorkplace @test -->
```
DEMO_HOME=$(mktemp -d)
```
Make a `kustomization` containing a pod resource
<!-- @createKustomization @test -->
```
cat <<EOF >$DEMO_HOME/kustomization.yaml
resources:
- pod.yaml
EOF
```
Declare the pod resource
<!-- @createDeployment @test -->
```
cat <<EOF >$DEMO_HOME/pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.29.0
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-mydb
image: busybox:1.29.0
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
EOF
```
The `myapp-pod` resource declares an initContainer and a container, both use the image `busybox:1.29.0`.
The tag `1.29.0` can be changed by adding `imageTags` in `kustomization.yaml`.
Add `imageTags`:
<!-- @addImageTags @test -->
```
cd $DEMO_HOME
kustomize edit set imagetag busybox:1.29.1
```
The `kustomization.yaml` will be added following `imageTags`.
> ```
> imageTags:
> - name: busybox
> newTag: 1.29.1
> ```
Now build this `kustomization`
<!-- @kustomizeBuild @test -->
```
kustomize build $DEMO_HOME
```
Confirm that this replaces _both_ busybox tags:
<!-- @confirmTags @test -->
```
test 2 == \
$(kustomize build $DEMO_HOME | grep busybox:1.29.1 | wc -l); \
echo $?
```

View File

@@ -0,0 +1,127 @@
# Demo: multibases with a common base
`kustomize` encourages defining multiple variants - e.g. dev, staging and prod, as overlays on a common base.
It's possible to create an additional overlay to compose these variants together - just declare the overlays as the bases of a new kustomization.
This is also a means to apply a common label or annotation across the variants, if for some reason the base isn't under your control. It also allows one to define a left-most namePrefix across the variants - something that cannot be done by modifying the common base.
The following demonstrates this using a base that's just one pod.
Define a place to work:
<!-- @makeWorkplace @test -->
```
DEMO_HOME=$(mktemp -d)
```
Define a common base:
<!-- @makeBase @test -->
```
BASE=$DEMO_HOME/base
mkdir $BASE
cat <<EOF >$BASE/kustomization.yaml
resources:
- pod.yaml
EOF
cat <<EOF >$BASE/pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: nginx
image: nginx:1.7.9
EOF
```
Define a dev variant overlaying base:
<!-- @makeDev @test -->
```
DEV=$DEMO_HOME/dev
mkdir $DEV
cat <<EOF >$DEV/kustomization.yaml
bases:
- ./../base
namePrefix: dev-
EOF
```
Define a staging variant overlaying base:
<!-- @makeStaging @test -->
```
STAG=$DEMO_HOME/staging
mkdir $STAG
cat <<EOF >$STAG/kustomization.yaml
bases:
- ./../base
namePrefix: stag-
EOF
```
Define a production variant overlaying base:
<!-- @makeProd @test -->
```
PROD=$DEMO_HOME/production
mkdir $PROD
cat <<EOF >$PROD/kustomization.yaml
bases:
- ./../base
namePrefix: prod-
EOF
```
Then define a _Kustomization_ composing three variants together:
<!-- @makeTopLayer @test -->
```
cat <<EOF >$DEMO_HOME/kustomization.yaml
bases:
- ./dev
- ./staging
- ./production
namePrefix: cluster-a-
EOF
```
Now the workspace has following directories
> ```
> .
> ├── base
> │   ├── kustomization.yaml
> │   └── pod.yaml
> ├── dev
> │   └── kustomization.yaml
> ├── kustomization.yaml
> ├── production
> │   └── kustomization.yaml
> └── staging
> └── kustomization.yaml
> ```
Confirm that the `kustomize build` output contains three pod objects from dev, staing and production variants.
<!-- @confirmVariants @test -->
```
test 1 == \
$(kustomize build $DEMO_HOME | grep cluster-a-dev-myapp-pod | wc -l); \
echo $?
test 1 == \
$(kustomize build $DEMO_HOME | grep cluster-a-stag-myapp-pod | wc -l); \
echo $?
test 1 == \
$(kustomize build $DEMO_HOME | grep cluster-a-prod-myapp-pod | wc -l); \
echo $?
```
Similarly to adding different `namePrefix` in different variants, one can also add different `namespace` and compose those variants in
one _kustomization_. For more details, take a look at [multi-namespaces](multi-namespace.md).

View File

@@ -0,0 +1,2 @@
resources:
- pod.yaml

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: nginx
image: nginx:1.7.9

View File

@@ -0,0 +1,4 @@
bases:
- ./../base
namePrefix: dev-

View File

@@ -0,0 +1,6 @@
bases:
- ./dev
- ./staging
- ./production
namePrefix: cluster-a-

View File

@@ -0,0 +1,115 @@
# Demo: multi namespaces with a common base
`kustomize` supports defining multiple variants with different namespace, as overlays on a common base.
It's possible to create an additional overlay to compose these variants together - just declare the overlays as the bases of a new kustomization. The following demonstrates this using a base that's just one pod.
Define a place to work:
<!-- @makeWorkplace @test -->
```
DEMO_HOME=$(mktemp -d)
```
Define a common base:
<!-- @makeBase @test -->
```
BASE=$DEMO_HOME/base
mkdir $BASE
cat <<EOF >$BASE/kustomization.yaml
resources:
- pod.yaml
EOF
cat <<EOF >$BASE/pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: nginx
image: nginx:1.7.9
EOF
```
Define a variant in namespace-a overlaying base:
<!-- @makeNamespaceA @test -->
```
NSA=$DEMO_HOME/namespace-a
mkdir $NSA
cat <<EOF >$NSA/kustomization.yaml
bases:
- ./../base
resources:
- namespace.yaml
namespace: namespace-a
EOF
cat <<EOF >$NSA/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: namespace-a
EOF
```
Define a variant in namespace-b overlaying base:
<!-- @makeNamespaceB @test -->
```
NSB=$DEMO_HOME/namespace-b
mkdir $NSB
cat <<EOF >$NSB/kustomization.yaml
bases:
- ./../base
resources:
- namespace.yaml
namespace: namespace-b
EOF
cat <<EOF >$NSB/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: namespace-b
EOF
```
Then define a _Kustomization_ composing two variants together:
<!-- @makeTopLayer @test -->
```
cat <<EOF >$DEMO_HOME/kustomization.yaml
bases:
- ./namespace-a
- ./namespace-b
EOF
```
Now the workspace has following directories
> ```
> .
> ├── base
> │   ├── kustomization.yaml
> │   └── pod.yaml
> ├── kustomization.yaml
> ├── namespace-a
> │   ├── kustomization.yaml
> │   └── namespace.yaml
> └── namespace-b
> ├── kustomization.yaml
> └── namespace.yaml
> ```
Confirm that the `kustomize build` output contains two pod objects from namespace-a and namespace-b.
<!-- @confirmVariants @test -->
```
test 2 == \
$(kustomize build $DEMO_HOME| grep -B 4 "namespace: namespace-[ab]" | grep "name: myapp-pod" | wc -l); \
echo $?
```

View File

@@ -0,0 +1,4 @@
bases:
- ./../base
namePrefix: prod-

View File

@@ -0,0 +1,4 @@
bases:
- ./../base
namePrefix: staging-

58
examples/remoteBuild.md Normal file
View File

@@ -0,0 +1,58 @@
# remote targets
`kustomize build` can be run against a url. The effect is the same as cloing the repo, checking out the specified ref,
then running `kustomize build` against the desired directory in the local copy.
Take `github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6` as an example.
According to [multibases](multibases/README.md) demo, this kustomization contains three Pod objects with names as
`cluster-a-dev-myapp-pod`, `cluster-a-stag-myapp-pod`, `cluster-a-prod-myapp-pod`.
Running `kustomize build` against the url gives the same output.
<!-- @remoteBuild @test -->
```
target=github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
test 3 == \
$(kustomize build $target | grep cluster-a-.*-myapp-pod | wc -l); \
echo $?
```
A base can also be specified as a URL:
<!-- @createOverlay @test -->
```
DEMO_HOME=$(mktemp -d)
cat <<EOF >$DEMO_HOME/kustomization.yaml
bases:
- github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
namePrefix: remote-
EOF
```
Running `kustomize build $DEMO_HOME` and confirm the output contains three Pods and all have `remote-` prefix.
<!-- @remoteBases @test -->
```
test 3 == \
$(kustomize build $DEMO_HOME | grep remote-.*-myapp-pod | wc -l); \
echo $?
```
## URL format
The url should follow
[hashicorp/go-getter URL format](https://github.com/hashicorp/go-getter#url-format).
Here are some example urls pointing to Github repos following this convention.
- a repo with a root level kustomization.yaml
`github.com/Liujingfang1/mysql`
- a repo with a root level kustomization.yaml on branch test
`github.com/Liujingfang1/mysql?ref=test`
- a subdirectory in a repo on version v1.0.6
`github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6`
- a subdirectory in a repo on branch repoUrl2
`github.com/Liujingfang1/kustomize//examples/helloWorld?ref=repoUrl2`
- a subdirectory in a repo on commit `7050a45134e9848fca214ad7e7007e96e5042c03`
`github.com/Liujingfang1/kustomize//examples/helloWorld?ref=7050a45134e9848fca214ad7e7007e96e5042c03`

View File

@@ -53,6 +53,8 @@ bases:
- wordpress
- mysql
namePrefix: demo-
patches:
- patch.yaml
EOF
```
@@ -65,7 +67,7 @@ In the new kustomization, apply a patch for wordpress deployment. The patch does
```
CONTENT="https://raw.githubusercontent.com\
/kubernetes-sigs/kustomize\
/master/examples/patch.yaml"
/master/examples/wordpress"
curl -s -o "$DEMO_HOME/#1.yaml" \
"$CONTENT/{patch}.yaml"

View File

@@ -26,8 +26,10 @@ import (
"github.com/ghodss/yaml"
"github.com/golang/glog"
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/crds"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
@@ -43,12 +45,13 @@ import (
// https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#target
type Application struct {
kustomization *types.Kustomization
loader loader.Loader
ldr loader.Loader
fSys fs.FileSystem
}
// NewApplication returns a new instance of Application primed with a Loader.
func NewApplication(loader loader.Loader) (*Application, error) {
content, err := loader.Load(constants.KustomizationFileName)
func NewApplication(ldr loader.Loader, fSys fs.FileSystem) (*Application, error) {
content, err := ldr.Load(constants.KustomizationFileName)
if err != nil {
return nil, err
}
@@ -58,7 +61,8 @@ func NewApplication(loader loader.Loader) (*Application, error) {
if err != nil {
return nil, err
}
return &Application{kustomization: &m, loader: loader}, nil
return &Application{kustomization: &m, ldr: ldr, fSys: fSys}, nil
}
func unmarshal(y []byte, o interface{}) error {
@@ -82,24 +86,14 @@ func (a *Application) MakeCustomizedResMap() (resmap.ResMap, error) {
return a.resolveRefsToGeneratedResources(m)
}
// MakeUncustomizedResMap purports to create a ResMap without customization.
// The Resources in the returned ResMap include all resources mentioned
// in the kustomization file and transitively reachable via its Bases,
// and all generated secrets and configMaps.
// Meant for use in generating a diff against customized resources.
// TODO: See https://github.com/kubernetes-sigs/kustomize/issues/85
func (a *Application) MakeUncustomizedResMap() (resmap.ResMap, error) {
m, err := a.loadResMapFromBasesAndResources()
// resolveRefsToGeneratedResources fixes all name references.
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
err := transformers.NewNameHashTransformer().Transform(m)
if err != nil {
return nil, err
}
return a.resolveRefsToGeneratedResources(m)
}
// resolveRefsToGeneratedResources fixes all name references.
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
r := []transformers.Transformer{transformers.NewNameHashTransformer()}
var r []transformers.Transformer
t, err := transformers.NewDefaultingNameReferenceTransformer()
if err != nil {
return nil, err
@@ -130,16 +124,19 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
if err != nil {
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
}
err = crds.RegisterCRDs(a.loader, a.kustomization.CRDs)
err = crds.RegisterCRDs(a.ldr, a.kustomization.Crds)
if err != nil {
errs.Append(errors.Wrap(err, "RegisterCRDs"))
}
cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
cms, err := resmap.NewResMapFromConfigMapArgs(
configmapandsecret.NewConfigMapFactory(a.fSys, a.ldr),
a.kustomization.ConfigMapGenerator)
if err != nil {
errs.Append(errors.Wrap(err, "NewResMapFromConfigMapArgs"))
}
secrets, err := resmap.NewResMapFromSecretArgs(a.loader.Root(), a.kustomization.SecretGenerator)
secrets, err := resmap.NewResMapFromSecretArgs(
configmapandsecret.NewSecretFactory(a.fSys, a.ldr.Root()),
a.kustomization.SecretGenerator)
if err != nil {
errs.Append(errors.Wrap(err, "NewResMapFromSecretArgs"))
}
@@ -153,7 +150,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
return nil, err
}
patches, err := resmap.NewResourceSliceFromPatches(a.loader, a.kustomization.Patches)
patches, err := resmap.NewResourceSliceFromPatches(a.ldr, a.kustomization.Patches)
if err != nil {
errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))
}
@@ -161,11 +158,20 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
if len(errs.Get()) > 0 {
return nil, errs
}
var r []transformers.Transformer
t, err := a.newTransformer(patches)
if err != nil {
return nil, err
}
err = t.Transform(result)
r = append(r, t)
t, err = transformers.NewImageTagTransformer(a.kustomization.ImageTags)
if err != nil {
return nil, err
}
r = append(r, t)
err = transformers.NewMultiTransformer(r).Transform(result)
if err != nil {
return nil, err
}
@@ -175,7 +181,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
// Gets Bases and Resources as advertised.
func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
bases, errs := a.loadCustomizedBases()
resources, err := resmap.NewResMapFromFiles(a.loader, a.kustomization.Resources)
resources, err := resmap.NewResMapFromFiles(a.ldr, a.kustomization.Resources)
if err != nil {
errs.Append(errors.Wrap(err, "rawResources failed to read Resources"))
}
@@ -188,15 +194,15 @@ func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
// Loop through the Bases of this kustomization recursively loading resources.
// Combine into one ResMap, demanding unique Ids for each resource.
func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.KustomizationErrors) {
list := []resmap.ResMap{}
var list []resmap.ResMap
errs := &interror.KustomizationErrors{}
for _, path := range a.kustomization.Bases {
loader, err := a.loader.New(path)
ldr, err := a.ldr.New(path)
if err != nil {
errs.Append(errors.Wrap(err, "couldn't make loader for "+path))
errs.Append(errors.Wrap(err, "couldn't make ldr for "+path))
continue
}
app, err := NewApplication(loader)
app, err := NewApplication(ldr, a.fSys)
if err != nil {
errs.Append(errors.Wrap(err, "couldn't make app for "+path))
continue
@@ -206,6 +212,7 @@ func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.Kustomizat
errs.Append(errors.Wrap(err, "SemiResources"))
continue
}
ldr.Cleanup()
list = append(list, resMap)
}
result, err := resmap.MergeWithoutOverride(list...)
@@ -219,12 +226,12 @@ func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
var result []*Application
errs := &interror.KustomizationErrors{}
for _, path := range a.kustomization.Bases {
loader, err := a.loader.New(path)
ldr, err := a.ldr.New(path)
if err != nil {
errs.Append(err)
continue
}
a, err := NewApplication(loader)
a, err := NewApplication(ldr, a.fSys)
if err != nil {
errs.Append(err)
continue
@@ -239,7 +246,7 @@ func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
// newTransformer makes a Transformer that does everything except resolve generated names.
func (a *Application) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
r := []transformers.Transformer{}
var r []transformers.Transformer
t, err := transformers.NewPatchTransformer(patches)
if err != nil {
return nil, err
@@ -272,7 +279,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
}
for _, v := range vars {
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
if r, found := m[id]; found {
if r, found := m.DemandOneMatchForId(id); found {
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
if err != nil {
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
@@ -287,7 +294,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
// getAllVars returns all the "environment" style Var instances defined in the app.
func (a *Application) getAllVars() ([]types.Var, error) {
result := []types.Var{}
var result []types.Var
errs := &interror.KustomizationErrors{}
bases, err := a.loadBasesAsFlatList()
@@ -302,6 +309,7 @@ func (a *Application) getAllVars() ([]types.Var, error) {
errs.Append(err)
continue
}
b.ldr.Cleanup()
result = append(result, vars...)
}
for _, v := range a.kustomization.Vars {

View File

@@ -18,11 +18,12 @@ package app
import (
"encoding/base64"
"os"
"reflect"
"strings"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
@@ -53,6 +54,14 @@ secretGenerator:
DB_USERNAME: "printf admin"
DB_PASSWORD: "printf somepw"
type: Opaque
`
kustomizationContent2 = `
secretGenerator:
- name: secret
timeoutSeconds: 1
commands:
USER: "sleep 2"
type: Opaque
`
deploymentContent = `apiVersion: apps/v1
metadata:
@@ -67,31 +76,30 @@ metadata:
)
func makeLoader1(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
err = ldr.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
err = ldr.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
return loader
return ldr
}
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
var ns = schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}
var svc = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
func TestResources1(t *testing.T) {
expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(deploy, "dply1", "foo-", "ns1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
@@ -123,7 +131,7 @@ func TestResources1(t *testing.T) {
},
},
}),
resource.NewResId(cmap, "literalConfigMap"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(cmap, "literalConfigMap", "foo-", "ns1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -142,8 +150,8 @@ func TestResources1(t *testing.T) {
"DB_USERNAME": "admin",
"DB_PASSWORD": "somepw",
},
}),
resource.NewResId(secret, "secret"): resource.NewResourceFromMap(
}).SetBehavior(resource.BehaviorCreate),
resource.NewResIdWithPrefixNamespace(secret, "secret", "foo-", "ns1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
@@ -163,8 +171,8 @@ func TestResources1(t *testing.T) {
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
}),
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
}).SetBehavior(resource.BehaviorCreate),
resource.NewResIdWithPrefixNamespace(ns, "ns1", "foo-", ""): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
@@ -180,7 +188,9 @@ func TestResources1(t *testing.T) {
}),
}
l := makeLoader1(t)
app, err := NewApplication(l)
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir("/")
app, err := NewApplication(l, fakeFs)
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}
@@ -195,146 +205,44 @@ func TestResources1(t *testing.T) {
}
}
func TestRawResources1(t *testing.T) {
expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "dply1",
},
}),
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns1",
},
}),
func TestResourceNotFound(t *testing.T) {
l := loadertest.NewFakeLoader("/testpath")
err := l.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
if err != nil {
t.Fatalf("Failed to setup fake ldr.")
}
l := makeLoader1(t)
app, err := NewApplication(l)
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir("/")
app, err := NewApplication(l, fakeFs)
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}
actual, err := app.MakeUncustomizedResMap()
if err != nil {
t.Fatalf("Unexpected RawResources error %v", err)
_, err = app.MakeCustomizedResMap()
if err == nil {
t.Fatalf("Didn't get the expected error for an unknown resource")
}
if err := expected.ErrorIfNotEqual(actual); err != nil {
t.Fatalf("unexpected inequality: %v", err)
if !strings.Contains(err.Error(), `cannot read file "/testpath/deployment.yaml"`) {
t.Fatalf("Unpexpected error message %q", err)
}
}
const (
kustomizationContentBase = `
namePrefix: foo-
commonLabels:
app: banana
resources:
- deployment.yaml
`
kustomizationContentOverlay = `
commonLabels:
env: staging
resources:
- service.yaml
bases:
- base
`
serviceContent = `apiVersion: v1
kind: Service
metadata:
name: svc
spec:
type: LoadBalancer
`
)
func makeLoader2(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
func TestSecretTimeout(t *testing.T) {
l := loadertest.NewFakeLoader("/testpath")
err := l.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent2))
if err != nil {
t.Fatal(err)
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/service.yaml", []byte(serviceContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
}
err = loader.AddDirectory("/testpath/base", os.ModeDir)
if err != nil {
t.Fatalf("Failed to setup fake loader.")
}
err = loader.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
}
err = loader.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
}
return loader
}
// TODO: This test covers incorrect behavior; it should not pass.
// It asks for raw resources. The Service resource is returned in raw form,
// but the resources in the base are modified to have the banana label,
// the 'foo' name prefix, etc. This method exists only to support the
// diff command comparing customized to non-customized resources;
// perhaps it's not worth supporting the command.
func TestRawResources2(t *testing.T) {
expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "foo-dply1",
"labels": map[string]interface{}{
"app": "banana",
},
},
"spec": map[string]interface{}{
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "banana",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "banana",
},
},
},
},
}),
resource.NewResId(svc, "svc"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Service",
"metadata": map[string]interface{}{
"name": "svc",
},
"spec": map[string]interface{}{
"type": "LoadBalancer",
},
}),
}
l := makeLoader2(t)
app, err := NewApplication(l)
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir("/")
app, err := NewApplication(l, fakeFs)
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}
actual, err := app.MakeUncustomizedResMap()
if err != nil {
t.Fatalf("Unexpected RawResources error %v", err)
_, err = app.MakeCustomizedResMap()
if err == nil {
t.Fatalf("Didn't get the expected error for an unknown resource")
}
if err := expected.ErrorIfNotEqual(actual); err != nil {
t.Fatalf("unexpected inequality: %v", err)
if !strings.Contains(err.Error(), "killed") {
t.Fatalf("Unpexpected error message %q", err)
}
}

View File

@@ -84,9 +84,8 @@ func (o *addBaseOptions) RunAddBase(fsys fs.FileSystem) error {
// split directory paths
paths := strings.Split(o.baseDirectoryPaths, ",")
for _, path := range paths {
_, err := fsys.Stat(path)
if err != nil {
return err
if !fsys.Exists(path) {
return errors.New(path + " does not exist")
}
if stringInSlice(path, m.Bases) {
return fmt.Errorf("base %s already in kustomization file", path)

View File

@@ -33,7 +33,7 @@ func TestAddBaseHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base, 0777)
fakeFS.Mkdir(base)
}
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
@@ -60,7 +60,7 @@ func TestAddBaseAlreadyThere(t *testing.T) {
// Create fake directories
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base, 0777)
fakeFS.Mkdir(base)
}
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
@@ -77,9 +77,9 @@ func TestAddBaseAlreadyThere(t *testing.T) {
}
var expectedErrors []string
for _, base := range bases {
error := "base " + base + " already in kustomization file"
expectedErrors = append(expectedErrors, error)
if !stringInSlice(error, expectedErrors) {
msg := "base " + base + " already in kustomization file"
expectedErrors = append(expectedErrors, msg)
if !stringInSlice(msg, expectedErrors) {
t.Errorf("unexpected error %v", err)
}
}

172
pkg/commands/addmetadata.go Normal file
View File

@@ -0,0 +1,172 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"fmt"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/validate"
"github.com/spf13/cobra"
)
// KindOfAdd is the kind of metadata being added: label or annotation
type KindOfAdd int
const (
annotation KindOfAdd = iota
label
)
func (k KindOfAdd) String() string {
kinds := [...]string{
"annotation",
"label",
}
if k < 0 || k > 1 {
return "Unknown metadatakind"
}
return kinds[k]
}
type addMetadataOptions struct {
metadata map[string]string
}
// newCmdAddAnnotation adds one or more commonAnnotations to the kustomization file.
func newCmdAddAnnotation(fsys fs.FileSystem) *cobra.Command {
var o addMetadataOptions
cmd := &cobra.Command{
Use: "annotation",
Short: "Adds one or more commonAnnotations to the kustomization.yaml in current directory",
Example: `
add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2}`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.ValidateAndParse(args, annotation)
if err != nil {
return err
}
return o.RunAddAnnotation(fsys, annotation)
},
}
return cmd
}
// newCmdAddLabel adds one or more commonLabels to the kustomization file.
func newCmdAddLabel(fsys fs.FileSystem) *cobra.Command {
var o addMetadataOptions
cmd := &cobra.Command{
Use: "label",
Short: "Adds one or more commonLabels to the kustomization.yaml in current directory",
Example: `
add label {labelKey1:labelValue1},{labelKey2:labelValue2}`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.ValidateAndParse(args, label)
if err != nil {
return err
}
return o.RunAddLabel(fsys, label)
},
}
return cmd
}
// ValidateAndParse validates addLabel and addAnnotation commands and parses them into o.metadata
func (o *addMetadataOptions) ValidateAndParse(args []string, k KindOfAdd) error {
o.metadata = make(map[string]string)
if len(args) < 1 {
return fmt.Errorf("must specify %s", k)
}
if len(args) > 1 {
return fmt.Errorf("%ss must be comma-separated, with no spaces. See help text for example", k)
}
inputs := strings.Split(args[0], ",")
for _, input := range inputs {
switch k {
case label:
valid, err := validate.IsValidLabel(input)
if !valid {
return err
}
case annotation:
valid, err := validate.IsValidAnnotation(input)
if !valid {
return err
}
default:
return fmt.Errorf("unknown metadata kind %s", k)
}
//parse annotation keys and values into metadata
kv := strings.Split(input, ":")
o.metadata[kv[0]] = kv[1]
}
return nil
}
// RunAddAnnotation runs addAnnotation command, doing the real work.
func (o *addMetadataOptions) RunAddAnnotation(fsys fs.FileSystem, k KindOfAdd) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
if m.CommonAnnotations == nil {
m.CommonAnnotations = make(map[string]string)
}
for key, value := range o.metadata {
if k == annotation {
if _, ok := m.CommonAnnotations[key]; ok {
return fmt.Errorf("%s %s already in kustomization file", k, key)
}
m.CommonAnnotations[key] = value
}
}
return mf.write(m)
}
// RunAddLabel runs addLabel command, doing the real work.
func (o *addMetadataOptions) RunAddLabel(fsys fs.FileSystem, k KindOfAdd) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
if m.CommonLabels == nil {
m.CommonLabels = make(map[string]string)
}
for key, value := range o.metadata {
if _, ok := m.CommonLabels[key]; ok {
return fmt.Errorf("%s %s already in kustomization file", k, key)
}
m.CommonLabels[key] = value
}
return mf.write(m)
}

View File

@@ -0,0 +1,190 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func TestParseValidateInput(t *testing.T) {
var testcases = []struct {
input string
valid bool
name string
expectedData map[string]string
kind KindOfAdd
}{
{
input: "otters:cute",
valid: true,
name: "Adds single input",
expectedData: map[string]string{
"otters": "cute",
},
kind: label,
},
{
input: "owls:great,unicorns:magical",
valid: true,
name: "Adds two items",
expectedData: map[string]string{
"owls": "great",
"unicorns": "magical",
},
kind: label,
},
{
input: "123:45",
valid: true,
name: "Numeric input is allowed",
expectedData: map[string]string{
"123": "45",
},
kind: annotation,
},
{
input: " ",
valid: false,
name: "Empty space input",
expectedData: nil,
kind: annotation,
},
}
var o addMetadataOptions
for _, tc := range testcases {
args := []string{tc.input}
err := o.ValidateAndParse(args, tc.kind)
if err != nil && tc.valid {
t.Errorf("for test case %s, unexpected cmd error: %v", tc.name, err)
}
if err == nil && !tc.valid {
t.Errorf("unexpected error: expected invalid format error for test case %v", tc.name)
}
//o.metadata should be the same as expectedData
if tc.valid {
if !reflect.DeepEqual(o.metadata, tc.expectedData) {
t.Errorf("unexpected error: for test case %s, unexpected data was added", tc.name)
}
} else {
if len(o.metadata) != 0 {
t.Errorf("unexpected error: for test case %s, expected no data to be added", tc.name)
}
}
}
}
func TestRunAddAnnotation(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
var o addMetadataOptions
o.metadata = map[string]string{"owls": "cute", "otters": "adorable"}
err := o.RunAddAnnotation(fakeFS, annotation)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.RunAddAnnotation(fakeFS, annotation)
if err == nil {
t.Errorf("expected already in kustomization file error")
}
// adding new annotations should work
o.metadata = map[string]string{"new": "annotation"}
err = o.RunAddAnnotation(fakeFS, annotation)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
}
func TestAddAnnotationNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddAnnotation(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected an error but error is %v", err)
}
if err != nil && err.Error() != "must specify annotation" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddAnnotationMultipleArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddAnnotation(fakeFS)
args := []string{"this:annotation", "has:spaces"}
err := cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected an error but error is %v", err)
}
if err != nil && err.Error() != "annotations must be comma-separated, with no spaces. See help text for example" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRunAddLabel(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
var o addMetadataOptions
o.metadata = map[string]string{"owls": "cute", "otters": "adorable"}
err := o.RunAddLabel(fakeFS, label)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.RunAddLabel(fakeFS, label)
if err == nil {
t.Errorf("expected already in kustomization file error")
}
// adding new labels should work
o.metadata = map[string]string{"new": "label"}
err = o.RunAddLabel(fakeFS, label)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
}
func TestAddLabelNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddLabel(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected an error but error is: %v", err)
}
if err != nil && err.Error() != "must specify label" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestAddLabelMultipleArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddLabel(fakeFS)
args := []string{"this:input", "has:spaces"}
err := cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected an error but error is: %v", err)
}
if err != nil && err.Error() != "labels must be comma-separated, with no spaces. See help text for example" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -18,7 +18,7 @@ package commands
import (
"errors"
"fmt"
"log"
"github.com/spf13/cobra"
@@ -27,7 +27,7 @@ import (
)
type addPatchOptions struct {
patchFilePath string
patchFilePaths []string
}
// newCmdAddPatch adds the name of a file containing a patch to the kustomization file.
@@ -56,10 +56,10 @@ func newCmdAddPatch(fsys fs.FileSystem) *cobra.Command {
// Validate validates addPatch command.
func (o *addPatchOptions) Validate(args []string) error {
if len(args) != 1 {
if len(args) == 0 {
return errors.New("must specify a patch file")
}
o.patchFilePath = args[0]
o.patchFilePaths = args
return nil
}
@@ -70,10 +70,13 @@ func (o *addPatchOptions) Complete(cmd *cobra.Command, args []string) error {
// RunAddPatch runs addPatch command (do real work).
func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
_, err := fsys.Stat(o.patchFilePath)
patches, err := globPatterns(fsys, o.patchFilePaths)
if err != nil {
return err
}
if len(patches) == 0 {
return nil
}
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
@@ -85,11 +88,13 @@ func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
return err
}
if stringInSlice(o.patchFilePath, m.Patches) {
return fmt.Errorf("patch %s already in kustomization file", o.patchFilePath)
for _, patch := range patches {
if stringInSlice(patch, m.Patches) {
log.Printf("patch %s already in kustomization file", patch)
continue
}
m.Patches = append(m.Patches, patch)
}
m.Patches = append(m.Patches, o.patchFilePath)
return mf.write(m)
}

View File

@@ -36,10 +36,11 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
func TestAddPatchHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteFile(patchFileName+"another", []byte(patchFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddPatch(fakeFS)
args := []string{patchFileName}
args := []string{patchFileName + "*"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
@@ -51,6 +52,9 @@ func TestAddPatchHappyPath(t *testing.T) {
if !strings.Contains(string(content), patchFileName) {
t.Errorf("expected patch name in kustomization")
}
if !strings.Contains(string(content), patchFileName+"another") {
t.Errorf("expected patch name in kustomization")
}
}
func TestAddPatchAlreadyThere(t *testing.T) {
@@ -65,13 +69,10 @@ func TestAddPatchAlreadyThere(t *testing.T) {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing patch should return an error
// adding an existing patch shouldn't return an error
err = cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected already there problem")
}
if err.Error() != "patch "+patchFileName+" already in kustomization file" {
t.Errorf("unexpected error %v", err)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
}

View File

@@ -18,7 +18,7 @@ package commands
import (
"errors"
"fmt"
"log"
"github.com/spf13/cobra"
@@ -27,7 +27,7 @@ import (
)
type addResourceOptions struct {
resourceFilePath string
resourceFilePaths []string
}
// newCmdAddResource adds the name of a file containing a resource to the kustomization file.
@@ -56,10 +56,10 @@ func newCmdAddResource(fsys fs.FileSystem) *cobra.Command {
// Validate validates addResource command.
func (o *addResourceOptions) Validate(args []string) error {
if len(args) != 1 {
if len(args) == 0 {
return errors.New("must specify a resource file")
}
o.resourceFilePath = args[0]
o.resourceFilePaths = args
return nil
}
@@ -70,10 +70,13 @@ func (o *addResourceOptions) Complete(cmd *cobra.Command, args []string) error {
// RunAddResource runs addResource command (do real work).
func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
_, err := fsys.Stat(o.resourceFilePath)
resources, err := globPatterns(fsys, o.resourceFilePaths)
if err != nil {
return err
}
if len(resources) == 0 {
return nil
}
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
@@ -85,11 +88,13 @@ func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
return err
}
if stringInSlice(o.resourceFilePath, m.Resources) {
return fmt.Errorf("resource %s already in kustomization file", o.resourceFilePath)
for _, resource := range resources {
if stringInSlice(resource, m.Resources) {
log.Printf("resource %s already in kustomization file", resource)
continue
}
m.Resources = append(m.Resources, resource)
}
m.Resources = append(m.Resources, o.resourceFilePath)
return mf.write(m)
}

View File

@@ -17,9 +17,8 @@ limitations under the License.
package commands
import (
"testing"
"strings"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
@@ -52,10 +51,11 @@ secretGenerator: []
func TestAddResourceHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(resourceFileName+"another", []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddResource(fakeFS)
args := []string{resourceFileName}
args := []string{resourceFileName + "*"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
@@ -67,6 +67,9 @@ func TestAddResourceHappyPath(t *testing.T) {
if !strings.Contains(string(content), resourceFileName) {
t.Errorf("expected resource name in kustomization")
}
if !strings.Contains(string(content), resourceFileName+"another") {
t.Errorf("expected resource name in kustomization")
}
}
func TestAddResourceAlreadyThere(t *testing.T) {
@@ -81,13 +84,10 @@ func TestAddResourceAlreadyThere(t *testing.T) {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing resource should return an error
// adding an existing resource doesn't return an error
err = cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected already there problem")
}
if err.Error() != "resource "+resourceFileName+" already in kustomization file" {
t.Errorf("unexpected error %v", err)
if err != nil {
t.Errorf("unexpected cmd error :%v", err)
}
}

View File

@@ -18,7 +18,6 @@ package commands
import (
"io"
"path/filepath"
"github.com/spf13/cobra"
@@ -32,17 +31,32 @@ import (
type buildOptions struct {
kustomizationPath string
outputPath string
}
var examples = `
Use the file somedir/kustomization.yaml to generate a set of api resources:
build somedir
Use a url pointing to a remote directory/kustomization.yaml to generate a set of api resources:
build url
The url should follow hashicorp/go-getter URL format described in
https://github.com/hashicorp/go-getter#url-format
url examples:
github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
github.com/Liujingfang1/mysql
github.com/Liujingfang1/kustomize//examples/helloWorld?ref=repoUrl2
`
// newCmdBuild creates a new build command.
func newCmdBuild(out io.Writer, fs fs.FileSystem) *cobra.Command {
var o buildOptions
cmd := &cobra.Command{
Use: "build [path]",
Short: "Print current configuration per contents of " + constants.KustomizationFileName,
Example: "Use the file somedir/" + constants.KustomizationFileName +
" to generate a set of api resources:\nbuild somedir/",
Use: "build [path]",
Short: "Print current configuration per contents of " + constants.KustomizationFileName,
Example: examples,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
@@ -52,6 +66,10 @@ func newCmdBuild(out io.Writer, fs fs.FileSystem) *cobra.Command {
return o.RunBuild(out, fs)
},
}
cmd.Flags().StringVarP(
&o.outputPath,
"output", "o", "",
"If specified, write the build output to this path.")
return cmd
}
@@ -69,20 +87,14 @@ func (o *buildOptions) Validate(args []string) error {
}
// RunBuild runs build command.
func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
absPath, err := filepath.Abs(o.kustomizationPath)
func (o *buildOptions) RunBuild(out io.Writer, fSys fs.FileSystem) error {
rootLoader, err := loader.NewLoader(o.kustomizationPath, "", fSys)
if err != nil {
return err
}
defer rootLoader.Cleanup()
rootLoader, err := l.New(absPath)
if err != nil {
return err
}
application, err := app.NewApplication(rootLoader)
application, err := app.NewApplication(rootLoader, fSys)
if err != nil {
return err
}
@@ -98,6 +110,10 @@ func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
if err != nil {
return err
}
if o.outputPath != "" {
return fSys.WriteFile(o.outputPath, res)
}
_, err = out.Write(res)
return err
}

View File

@@ -79,7 +79,7 @@ func TestBuildValidate(t *testing.T) {
func TestBuild(t *testing.T) {
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
fs := fs.MakeRealFS()
fSys := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
@@ -104,12 +104,12 @@ func TestBuild(t *testing.T) {
}
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fs) })
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fSys) })
}
}
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fs fs.FileSystem) {
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fSys fs.FileSystem) {
name := testcaseName
testcase := buildTestCase{}
testcaseDir := filepath.Join("testdata", "testcase-"+name)
@@ -125,7 +125,7 @@ func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected
kustomizationPath: testcase.Filename,
}
buf := bytes.NewBuffer([]byte{})
err = ops.RunBuild(buf, fs)
err = ops.RunBuild(buf, fSys)
switch {
case err != nil && len(testcase.ExpectedError) == 0:
t.Errorf("unexpected error: %v", err)

View File

@@ -18,10 +18,12 @@ package commands
import (
"fmt"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
// dataConfig encapsulates the options for add configmap/Secret commands.
type dataConfig struct {
// cMapFlagsAndArgs encapsulates the options for add configmap commands.
type cMapFlagsAndArgs struct {
// Name of configMap/Secret (required)
Name string
// FileSources to derive the configMap/Secret from (optional)
@@ -34,7 +36,7 @@ type dataConfig struct {
}
// Validate validates required fields are set to support structured generation.
func (a *dataConfig) Validate(args []string) error {
func (a *cMapFlagsAndArgs) Validate(args []string) error {
if len(args) != 1 {
return fmt.Errorf("name must be specified once")
}
@@ -48,3 +50,12 @@ func (a *dataConfig) Validate(args []string) error {
// TODO: Should we check if the path exists? if it's valid, if it's within the same (sub-)directory?
return nil
}
func (a *cMapFlagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error {
result, err := globPatterns(fSys, a.FileSources)
if err != nil {
return err
}
a.FileSources = result
return nil
}

View File

@@ -17,11 +17,14 @@ limitations under the License.
package commands
import (
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func TestDataConfigValidation_NoName(t *testing.T) {
config := dataConfig{}
config := cMapFlagsAndArgs{}
if config.Validate([]string{}) == nil {
t.Fatal("Validation should fail if no name is specified")
@@ -29,7 +32,7 @@ func TestDataConfigValidation_NoName(t *testing.T) {
}
func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
config := dataConfig{}
config := cMapFlagsAndArgs{}
if config.Validate([]string{"name", "othername"}) == nil {
t.Fatal("Validation should fail if more than one name is specified")
@@ -39,12 +42,12 @@ func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
func TestDataConfigValidation_Flags(t *testing.T) {
tests := []struct {
name string
config dataConfig
config cMapFlagsAndArgs
shouldFail bool
}{
{
name: "env-file-source and literal are both set",
config: dataConfig{
config: cMapFlagsAndArgs{
LiteralSources: []string{"one", "two"},
EnvFileSource: "three",
},
@@ -52,7 +55,7 @@ func TestDataConfigValidation_Flags(t *testing.T) {
},
{
name: "env-file-source and from-file are both set",
config: dataConfig{
config: cMapFlagsAndArgs{
FileSources: []string{"one", "two"},
EnvFileSource: "three",
},
@@ -60,12 +63,12 @@ func TestDataConfigValidation_Flags(t *testing.T) {
},
{
name: "we don't have any option set",
config: dataConfig{},
config: cMapFlagsAndArgs{},
shouldFail: true,
},
{
name: "we have from-file and literal ",
config: dataConfig{
config: cMapFlagsAndArgs{
LiteralSources: []string{"one", "two"},
FileSources: []string{"three", "four"},
},
@@ -81,3 +84,21 @@ func TestDataConfigValidation_Flags(t *testing.T) {
}
}
}
func TestExpandFileSource(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/config1")
fakeFS.Create("dir/config2")
fakeFS.Create("dir/reademe")
config := cMapFlagsAndArgs{
FileSources: []string{"dir/config*"},
}
config.ExpandFileSource(fakeFS)
expected := []string{
"dir/config1",
"dir/config2",
}
if !reflect.DeepEqual(config.FileSources, expected) {
t.Fatalf("FileSources is not correctly expanded: %v", config.FileSources)
}
}

View File

@@ -28,7 +28,7 @@ import (
// NewDefaultCommand returns the default (aka root) command for kustomize command.
func NewDefaultCommand() *cobra.Command {
fsys := fs.MakeRealFS()
stdOut, stdErr := os.Stdout, os.Stderr
stdOut := os.Stdout
c := &cobra.Command{
Use: "kustomize",
@@ -43,7 +43,6 @@ See https://github.com/kubernetes-sigs/kustomize
c.AddCommand(
// TODO: Make consistent API for newCmd* functions.
newCmdBuild(stdOut, fsys),
newCmdDiff(stdOut, stdErr, fsys),
newCmdEdit(fsys),
newCmdVersion(stdOut),
)
@@ -96,6 +95,12 @@ func newCmdAdd(fsys fs.FileSystem) *cobra.Command {
# Adds one or more base directories to the kustomization
kustomize edit add base <filepath>
kustomize edit add base <filepath1>,<filepath2>,<filepath3>
# Adds one or more commonLabels to the kustomization
kustomize edit add label {labelKey1:labelValue1},{labelKey2:labelValue2}
# Adds one or more commonAnnotations to the kustomization
kustomize edit add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2}
`,
Args: cobra.MinimumNArgs(1),
}
@@ -104,6 +109,8 @@ func newCmdAdd(fsys fs.FileSystem) *cobra.Command {
newCmdAddPatch(fsys),
newCmdAddConfigMap(fsys),
newCmdAddBase(fsys),
newCmdAddLabel(fsys),
newCmdAddAnnotation(fsys),
)
return c
}
@@ -123,6 +130,8 @@ func newCmdSet(fsys fs.FileSystem) *cobra.Command {
c.AddCommand(
newCmdSetNamePrefix(fsys),
newCmdSetNamespace(fsys),
newCmdSetImageTag(fsys),
)
return c
}

View File

@@ -24,11 +24,12 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
var config dataConfig
func newCmdAddConfigMap(fSys fs.FileSystem) *cobra.Command {
var flagsAndArgs cMapFlagsAndArgs
cmd := &cobra.Command{
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1]",
Short: "Adds a configmap to the kustomization file.",
@@ -44,47 +45,55 @@ func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
kustomize edit add configmap my-configmap --from-env-file=env/path.env
`,
RunE: func(_ *cobra.Command, args []string) error {
err := config.Validate(args)
err := flagsAndArgs.ExpandFileSource(fSys)
if err != nil {
return err
}
// Load in the kustomization file.
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
err = flagsAndArgs.Validate(args)
if err != nil {
return err
}
m, err := mf.read()
// Load the kustomization file.
mf, err := newKustomizationFile(constants.KustomizationFileName, fSys)
if err != nil {
return err
}
// Add the config map to the kustomization file.
err = addConfigMap(m, config)
kustomization, err := mf.read()
if err != nil {
return err
}
// Add the flagsAndArgs map to the kustomization file.
err = addConfigMap(
kustomization, flagsAndArgs,
configmapandsecret.NewConfigMapFactory(
fSys, loader.NewFileLoader(fSys)))
if err != nil {
return err
}
// Write out the kustomization file with added configmap.
return mf.write(m)
return mf.write(kustomization)
},
}
cmd.Flags().StringSliceVar(
&config.FileSources,
&flagsAndArgs.FileSources,
"from-file",
[]string{},
"Key file can be specified using its file path, in which case file basename will be used as configmap "+
"key, or optionally with a key and file path, in which case the given key will be used. Specifying a "+
"directory will iterate each named file in the directory whose basename is a valid configmap key.")
cmd.Flags().StringArrayVar(
&config.LiteralSources,
&flagsAndArgs.LiteralSources,
"from-literal",
[]string{},
"Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
cmd.Flags().StringVar(
&config.EnvFileSource,
&flagsAndArgs.EnvFileSource,
"from-env-file",
"",
"Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
@@ -92,27 +101,27 @@ func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
return cmd
}
// addConfigMap updates a configmap within a kustomization file, using the data in config.
// Note: error may leave kustomization file in an undefined state. Suggest passing a copy
// of kustomization file.
func addConfigMap(m *types.Kustomization, config dataConfig) error {
cm := getOrCreateConfigMap(m, config.Name)
err := mergeData(&cm.DataSources, config)
// addConfigMap adds a configmap to a kustomization file.
// Note: error may leave kustomization file in an undefined state.
// Suggest passing a copy of kustomization file.
func addConfigMap(
k *types.Kustomization,
flagsAndArgs cMapFlagsAndArgs,
factory *configmapandsecret.ConfigMapFactory) error {
cmArgs := makeConfigMapArgs(k, flagsAndArgs.Name)
err := mergeFlagsIntoCmArgs(&cmArgs.DataSources, flagsAndArgs)
if err != nil {
return err
}
// Validate by trying to create corev1.configmap.
_, _, err = configmapandsecret.MakeConfigmapAndGenerateName(*cm)
_, _, err = factory.MakeUnstructAndGenerateName(cmArgs)
if err != nil {
return err
}
return nil
}
func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapArgs {
func makeConfigMapArgs(m *types.Kustomization, name string) *types.ConfigMapArgs {
for i, v := range m.ConfigMapGenerator {
if name == v.Name {
return &m.ConfigMapGenerator[i]
@@ -124,13 +133,12 @@ func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapA
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
}
func mergeData(src *types.DataSources, config dataConfig) error {
src.LiteralSources = append(src.LiteralSources, config.LiteralSources...)
src.FileSources = append(src.FileSources, config.FileSources...)
if src.EnvSource != "" && src.EnvSource != config.EnvFileSource {
return fmt.Errorf("updating existing env source '%s' not allowed.", src.EnvSource)
func mergeFlagsIntoCmArgs(src *types.DataSources, flags cMapFlagsAndArgs) error {
src.LiteralSources = append(src.LiteralSources, flags.LiteralSources...)
src.FileSources = append(src.FileSources, flags.FileSources...)
if src.EnvSource != "" && src.EnvSource != flags.EnvFileSource {
return fmt.Errorf("updating existing env source '%s' not allowed", src.EnvSource)
}
src.EnvSource = config.EnvFileSource
src.EnvSource = flags.EnvFileSource
return nil
}

View File

@@ -29,7 +29,7 @@ func TestNewAddConfigMapIsNotNil(t *testing.T) {
}
}
func TestGetOrCreateConfigMap(t *testing.T) {
func TestMakeConfigMapArgs(t *testing.T) {
cmName := "test-config-name"
kustomization := &types.Kustomization{
@@ -39,24 +39,24 @@ func TestGetOrCreateConfigMap(t *testing.T) {
if len(kustomization.ConfigMapGenerator) != 0 {
t.Fatal("Initial kustomization should not have any configmaps")
}
cm := getOrCreateConfigMap(kustomization, cmName)
args := makeConfigMapArgs(kustomization, cmName)
if cm == nil {
t.Fatalf("ConfigMap should always be non-nil")
if args == nil {
t.Fatalf("args should always be non-nil")
}
if len(kustomization.ConfigMapGenerator) != 1 {
t.Fatalf("Kustomization should have newly created configmap")
}
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != cm {
t.Fatalf("Pointer address for newly inserted configmap should be same")
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != args {
t.Fatalf("Pointer address for newly inserted configmap generator should be same")
}
existingCM := getOrCreateConfigMap(kustomization, cmName)
args2 := makeConfigMapArgs(kustomization, cmName)
if existingCM != cm {
t.Fatalf("should have returned an existing cm with name: %v", cmName)
if args2 != args {
t.Fatalf("should have returned an existing args with name: %v", cmName)
}
if len(kustomization.ConfigMapGenerator) != 1 {
@@ -64,10 +64,10 @@ func TestGetOrCreateConfigMap(t *testing.T) {
}
}
func TestMergeData_LiteralSources(t *testing.T) {
func TestMergeFlagsIntoCmArgs_LiteralSources(t *testing.T) {
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{LiteralSources: []string{"k1=v1"}})
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k1=v1"}})
if err != nil {
t.Fatalf("Merge initial literal source should not return error")
}
@@ -76,7 +76,7 @@ func TestMergeData_LiteralSources(t *testing.T) {
t.Fatalf("Initial literal source should have been added")
}
err = mergeData(ds, dataConfig{LiteralSources: []string{"k2=v2"}})
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k2=v2"}})
if err != nil {
t.Fatalf("Merge second literal source should not return error")
}
@@ -86,10 +86,10 @@ func TestMergeData_LiteralSources(t *testing.T) {
}
}
func TestMergeData_FileSources(t *testing.T) {
func TestMergeFlagsIntoCmArgs_FileSources(t *testing.T) {
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{FileSources: []string{"file1"}})
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file1"}})
if err != nil {
t.Fatalf("Merge initial file source should not return error")
}
@@ -98,7 +98,7 @@ func TestMergeData_FileSources(t *testing.T) {
t.Fatalf("Initial file source should have been added")
}
err = mergeData(ds, dataConfig{FileSources: []string{"file2"}})
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file2"}})
if err != nil {
t.Fatalf("Merge second file source should not return error")
}
@@ -108,12 +108,12 @@ func TestMergeData_FileSources(t *testing.T) {
}
}
func TestMergeData_EnvSource(t *testing.T) {
func TestMergeFlagsIntoCmArgs_EnvSource(t *testing.T) {
envFileName := "env1"
envFileName2 := "env2"
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{EnvFileSource: envFileName})
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName})
if err != nil {
t.Fatalf("Merge initial env source should not return error")
}
@@ -122,7 +122,7 @@ func TestMergeData_EnvSource(t *testing.T) {
t.Fatalf("Initial env source filename should have been added")
}
err = mergeData(ds, dataConfig{EnvFileSource: envFileName2})
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName2})
if err == nil {
t.Fatalf("Updating env source should return an error")
}

View File

@@ -1,97 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"io"
"path/filepath"
"github.com/spf13/cobra"
"github.com/kubernetes-sigs/kustomize/pkg/app"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/diff"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
)
type diffOptions struct {
kustomizationPath string
}
// newCmdDiff makes the diff command.
func newCmdDiff(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
var o diffOptions
cmd := &cobra.Command{
Use: "diff [path]",
Short: "diff between customized resources and uncustomized resources",
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunDiff(out, errOut, fs)
},
}
return cmd
}
// Validate validates diff command.
func (o *diffOptions) Validate(args []string) error {
if len(args) > 1 {
return errors.New("specify one path to " + constants.KustomizationFileName)
}
if len(args) == 0 {
o.kustomizationPath = "./"
return nil
}
o.kustomizationPath = args[0]
return nil
}
// RunDiff gets the differences between Application.MakeCustomizedResMap() and Application.MakeUncustomizedResMap().
func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
return err
}
rootLoader, err := l.New(absPath)
if err != nil {
return err
}
application, err := app.NewApplication(rootLoader)
if err != nil {
return err
}
transformedResources, err := application.MakeCustomizedResMap()
if err != nil {
return err
}
rawResources, err := application.MakeUncustomizedResMap()
if err != nil {
return err
}
return diff.RunDiff(rawResources, transformedResources, out, errOut)
}

View File

@@ -1,130 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"testing"
"github.com/ghodss/yaml"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"k8s.io/apimachinery/pkg/util/sets"
)
type DiffTestCase struct {
Description string `yaml:"description"`
Args []string `yaml:"args"`
Filename string `yaml:"filename"`
// path to the file that contains the expected output
ExpectedDiff string `yaml:"expectedDiff"`
ExpectedError string `yaml:"expectedError"`
}
func TestDiff(t *testing.T) {
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
noopDir, _ := regexp.Compile(`/tmp/noop-[0-9]*/`)
transformedDir, _ := regexp.Compile(`/tmp/transformed-[0-9]*/`)
timestamp, _ := regexp.Compile(`[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9].[0-9]* [+-]{1}[0-9]{4}`)
fs := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == "testdata" {
return nil
}
name := filepath.Base(path)
if info.IsDir() {
if strings.HasPrefix(name, "testcase-") {
testcases.Insert(strings.TrimPrefix(name, "testcase-"))
}
return filepath.SkipDir
}
return nil
})
// sanity check that we found the right folder
if !testcases.Has("simple") {
t.Fatalf("Error locating testcases")
}
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) {
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fs,
noopDir, transformedDir, timestamp)
})
}
}
func runDiffTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fs fs.FileSystem,
noopDir, transformedDir, timestamp *regexp.Regexp) {
name := testcaseName
testcase := DiffTestCase{}
testcaseDir := filepath.Join("testdata", "testcase-"+name)
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
t.Fatalf("%s: %v", name, err)
}
diffOps := &diffOptions{
kustomizationPath: testcase.Filename,
}
buf := bytes.NewBuffer([]byte{})
err = diffOps.RunDiff(buf, os.Stderr, fs)
switch {
case err != nil && len(testcase.ExpectedError) == 0:
t.Errorf("unexpected error: %v", err)
case err != nil && len(testcase.ExpectedError) != 0:
if !strings.Contains(err.Error(), testcase.ExpectedError) {
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
}
return
case err == nil && len(testcase.ExpectedError) != 0:
t.Errorf("unexpected no error")
}
actualString := string(buf.Bytes())
actualString = noopDir.ReplaceAllString(actualString, "/tmp/noop/")
actualString = transformedDir.ReplaceAllString(actualString, "/tmp/transformed/")
actualString = timestamp.ReplaceAllString(actualString, "YYYY-MM-DD HH:MM:SS")
actualBytes := []byte(actualString)
if !updateKustomizeExpected {
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedDiff)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(actualBytes, expectedBytes) {
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
}
} else {
ioutil.WriteFile(testcase.ExpectedDiff, actualBytes, 0644)
}
}

View File

@@ -17,9 +17,13 @@ limitations under the License.
package commands
import (
"bytes"
"errors"
"fmt"
"io"
"path"
"reflect"
"regexp"
"strings"
"github.com/ghodss/yaml"
@@ -30,9 +34,46 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
var (
// These field names are the exact kustomization fields
kustomizationFields = []string{
"APIVersion",
"Kind",
"Resources",
"Bases",
"NamePrefix",
"Namespace",
"Crds",
"CommonLabels",
"CommonAnnotations",
"Patches",
"ConfigMapGenerator",
"SecretGenerator",
"Vars",
"ImageTags",
}
)
// commentedField records the comment associated with a kustomization field
// field has to be a recognized kustomization field
// comment can be empty
type commentedField struct {
field string
comment []byte
}
func (cf *commentedField) appendComment(comment []byte) {
cf.comment = append(cf.comment, comment...)
}
func squash(x [][]byte) []byte {
return bytes.Join(x, []byte(``))
}
type kustomizationFile struct {
path string
fsys fs.FileSystem
path string
fsys fs.FileSystem
originalFields []*commentedField
}
func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile, error) { // nolint
@@ -45,37 +86,39 @@ func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile,
}
func (mf *kustomizationFile) validate() error {
f, err := mf.fsys.Stat(mf.path)
if err != nil {
if !mf.fsys.Exists(mf.path) {
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
if f.IsDir() {
if mf.fsys.IsDir(mf.path) {
mf.path = path.Join(mf.path, constants.KustomizationFileName)
_, err = mf.fsys.Stat(mf.path)
if err != nil {
if !mf.fsys.Exists(mf.path) {
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
} else {
if !strings.HasSuffix(mf.path, constants.KustomizationFileName) {
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n", mf.path, constants.KustomizationFileSuffix)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n",
mf.path, constants.KustomizationFileSuffix)
return interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
}
}
return nil
}
func (mf *kustomizationFile) read() (*types.Kustomization, error) {
bytes, err := mf.fsys.ReadFile(mf.path)
data, err := mf.fsys.ReadFile(mf.path)
if err != nil {
return nil, err
}
var kustomization types.Kustomization
err = yaml.Unmarshal(bytes, &kustomization)
err = yaml.Unmarshal(data, &kustomization)
if err != nil {
return nil, err
}
err = mf.parseCommentedFields(data)
if err != nil {
return nil, err
}
@@ -84,14 +127,13 @@ func (mf *kustomizationFile) read() (*types.Kustomization, error) {
func (mf *kustomizationFile) write(kustomization *types.Kustomization) error {
if kustomization == nil {
return errors.New("util: kustomization file arg is nil.")
return errors.New("util: kustomization file arg is nil")
}
bytes, err := yaml.Marshal(kustomization)
data, err := mf.marshal(kustomization)
if err != nil {
return err
}
return mf.fsys.WriteFile(mf.path, bytes)
return mf.fsys.WriteFile(mf.path, data)
}
func stringInSlice(str string, list []string) bool {
@@ -102,3 +144,106 @@ func stringInSlice(str string, list []string) bool {
}
return false
}
func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
buffer := bytes.NewBuffer(content)
var comments [][]byte
line, err := buffer.ReadBytes('\n')
for err == nil {
if isCommentOrBlankLine(line) {
comments = append(comments, line)
} else {
matched, field := findMatchedField(line)
if matched {
mf.originalFields = append(mf.originalFields, &commentedField{field: field, comment: squash(comments)})
comments = [][]byte{}
} else if len(comments) > 0 {
mf.originalFields[len(mf.originalFields)-1].appendComment(squash(comments))
comments = [][]byte{}
}
}
line, err = buffer.ReadBytes('\n')
}
if err != io.EOF {
return err
}
return nil
}
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
var output []byte
for _, comment := range mf.originalFields {
output = append(output, comment.comment...)
content, err := marshalField(comment.field, kustomization)
if err != nil {
return content, err
}
output = append(output, content...)
}
for _, field := range kustomizationFields {
if mf.hasField(field) {
continue
}
content, err := marshalField(field, kustomization)
if err != nil {
return content, nil
}
output = append(output, content...)
}
return output, nil
}
func (mf *kustomizationFile) hasField(name string) bool {
for _, n := range mf.originalFields {
if n.field == name {
return true
}
}
return false
}
/*
isCommentOrBlankLine determines if a line is a comment or blank line
Return true for following lines
# This line is a comment
# This line is also a comment with several leading white spaces
(The line above is a blank line)
*/
func isCommentOrBlankLine(line []byte) bool {
s := bytes.TrimRight(bytes.TrimLeft(line, " "), "\n")
return len(s) == 0 || bytes.HasPrefix(s, []byte(`#`))
}
func findMatchedField(line []byte) (bool, string) {
for _, field := range kustomizationFields {
// (?i) is for case insensitive regexp matching
r := regexp.MustCompile("^(" + "(?i)" + field + "):")
if r.Match(line) {
return true, field
}
}
return false, ""
}
// marshalField marshal a given field of a kustomization object into yaml format.
// If the field wasn't in the original kustomization.yaml file or wasn't added,
// an empty []byte is returned.
func marshalField(field string, kustomization *types.Kustomization) ([]byte, error) {
r := reflect.ValueOf(*kustomization)
v := r.FieldByName(strings.Title(field))
if !v.IsValid() || v.Len() == 0 {
return []byte{}, nil
}
k := &types.Kustomization{}
kr := reflect.ValueOf(k)
kv := kr.Elem().FieldByName(strings.Title(field))
kv.Set(v)
return yaml.Marshal(k)
}

View File

@@ -62,7 +62,7 @@ func TestEmptyFile(t *testing.T) {
func TestNewNotExist(t *testing.T) {
badSuffix := "foo.bar"
fakeFS := fs.MakeFakeFS()
fakeFS.Mkdir(".", 0644)
fakeFS.Mkdir(".")
fakeFS.Create(badSuffix)
_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
if err == nil {
@@ -88,3 +88,140 @@ func TestNewNotExist(t *testing.T) {
t.Fatalf("expect an error contains %q, but got %v", contained, err)
}
}
func TestPreserveComments(t *testing.T) {
kustomizationContentWithComments := []byte(
`# shem qing some comments
# This is some comment we should preserve
# don't delete it
resources:
- pod.yaml
- service.yaml
# something you may want to keep
vars:
- fieldref:
fieldPath: metadata.name
name: MY_SERVICE_NAME
objref:
apiVersion: v1
kind: Service
name: my-service
bases:
- ../namespaces
# some descriptions for the patches
patches:
- service.yaml
- pod.yaml
`)
fsys := fs.MakeFakeFS()
fsys.Create(constants.KustomizationFileName)
fsys.WriteFile(constants.KustomizationFileName, kustomizationContentWithComments)
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fsys.ReadFile(mf.path)
if !reflect.DeepEqual(kustomizationContentWithComments, bytes) {
t.Fatal("written kustomization with comments is not the same as original one")
}
}
func TestPreserveCommentsWithAdjust(t *testing.T) {
kustomizationContentWithComments := []byte(`
# shem qing some comments
# This is some comment we should preserve
# don't delete it
resources:
- pod.yaml
# See which field this comment goes into
- service.yaml
APIVersion: v1beta1
kind: kustomization.yaml
# something you may want to keep
vars:
- fieldref:
fieldPath: metadata.name
name: MY_SERVICE_NAME
objref:
apiVersion: v1
kind: Service
name: my-service
BASES:
- ../namespaces
# some descriptions for the patches
patches:
- service.yaml
- pod.yaml
`)
expected := []byte(`
# shem qing some comments
# This is some comment we should preserve
# don't delete it
# See which field this comment goes into
resources:
- pod.yaml
- service.yaml
apiVersion: v1beta1
kind: kustomization.yaml
# something you may want to keep
vars:
- fieldref:
fieldPath: metadata.name
name: MY_SERVICE_NAME
objref:
apiVersion: v1
kind: Service
name: my-service
bases:
- ../namespaces
# some descriptions for the patches
patches:
- service.yaml
- pod.yaml
`)
fsys := fs.MakeFakeFS()
fsys.Create(constants.KustomizationFileName)
fsys.WriteFile(constants.KustomizationFileName, kustomizationContentWithComments)
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fsys.ReadFile(mf.path)
if !reflect.DeepEqual(expected, bytes) {
t.Fatal("written kustomization with comments is not the same as original one\n", string(bytes))
}
}

113
pkg/commands/setimagetag.go Normal file
View File

@@ -0,0 +1,113 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"regexp"
"sort"
"github.com/spf13/cobra"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
type setImageTagOptions struct {
imageTagMap map[string]string
}
var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$")
// newCmdSetImageTag sets the new tags for images in the kustomization.
func newCmdSetImageTag(fsys fs.FileSystem) *cobra.Command {
var o setImageTagOptions
cmd := &cobra.Command{
Use: "imagetag",
Short: "Sets images and their new tags in the kustomization file",
Example: `
The command
set imagetag nginx:1.8.0 my-app:latest
will add
imageTags:
- name: nginx
newTag: 1.8.0
- name: my-app
newTag: latest
to the kustomization file if it doesn't exist,
and overwrite the previous newTag if the image name exists.
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunSetImageTags(fsys)
},
}
return cmd
}
// Validate validates setImageTag command.
func (o *setImageTagOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("no image and newTag specified")
}
o.imageTagMap = make(map[string]string)
for _, arg := range args {
imagetag := pattern.FindStringSubmatch(arg)
if len(imagetag) != 3 {
return errors.New("invalid format of imagetag, must specify it as <image>:<newtag>")
}
o.imageTagMap[imagetag[1]] = imagetag[2]
}
return nil
}
// RunSetImageTags runs setImageTags command (does real work).
func (o *setImageTagOptions) RunSetImageTags(fsys fs.FileSystem) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
imageTagMap := map[string]string{}
for _, it := range m.ImageTags {
imageTagMap[it.Name] = it.NewTag
}
for key, value := range o.imageTagMap {
imageTagMap[key] = value
}
var imageTags []types.ImageTag
for key, value := range imageTagMap {
imageTags = append(imageTags, types.ImageTag{Name: key, NewTag: value})
}
sort.Slice(imageTags, func(i, j int) bool {
return imageTags[i].Name < imageTags[j].Name
})
m.ImageTags = imageTags
return mf.write(m)
}

View File

@@ -0,0 +1,99 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"strings"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func TestSetImageTagsHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag2", "localhost:5000/operator:1.0.0"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(`
imageTags:
- name: image1
newTag: tag1
- name: image2
newTag: tag2
- name: localhost:5000/operator
newTag: 1.0.0
`)
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected imageTags in kustomization file")
}
}
func TestSetImageTagsOverride(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag1"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
args = []string{"image2:tag2", "image3:tag3"}
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(`
imageTags:
- name: image1
newTag: tag1
- name: image2
newTag: tag2
- name: image3
newTag: tag3
`)
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected imageTags in kustomization file %s", string(content))
}
}
func TestSetImageTagsNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetImageTag(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "no image and newTag specified" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -0,0 +1,83 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"fmt"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/validation"
)
type setNamespaceOptions struct {
namespace string
}
// newCmdSetNamespace sets the value of the namespace field in the kustomization.
func newCmdSetNamespace(fsys fs.FileSystem) *cobra.Command {
var o setNamespaceOptions
cmd := &cobra.Command{
Use: "namespace",
Short: "Sets the value of the namespace field in the kustomization file",
Example: `
The command
set namespace staging
will add the field "namespace: staging" to the kustomization file if it doesn't exist,
and overwrite the value with "staging" if the field does exist.
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunSetNamespace(fsys)
},
}
return cmd
}
// Validate validates setNamespace command.
func (o *setNamespaceOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify exactly one namespace value")
}
ns := args[0]
if errs := validation.IsDNS1123Label(ns); len(errs) != 0 {
return fmt.Errorf("%q is not a valid namespace name: %s", ns, strings.Join(errs, ";"))
}
o.namespace = ns
return nil
}
// RunSetNamespace runs setNamespace command (does real work).
func (o *setNamespaceOptions) RunSetNamespace(fsys fs.FileSystem) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
m.Namespace = o.namespace
return mf.write(m)
}

View File

@@ -0,0 +1,103 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"fmt"
"strings"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
const (
goodNamespaceValue = "staging"
)
func TestSetNamespaceHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetNamespace(fakeFS)
args := []string{goodNamespaceValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(fmt.Sprintf("namespace: %s", goodNamespaceValue))
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected namespace in kustomization file")
}
}
func TestSetNamespaceOverride(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetNamespace(fakeFS)
args := []string{goodNamespaceValue}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
args = []string{"newnamespace"}
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte("namespace: newnamespace")
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected namespace in kustomization file %s", string(content))
}
}
func TestSetNamespaceNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNamespace(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify exactly one namespace value" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestSetNamespaceInvalid(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetNamespace(fakeFS)
args := []string{"/badnamespace/"}
err := cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected error: %v", err)
}
if !strings.Contains(err.Error(), "is not a valid namespace name") {
t.Errorf("unexpected error: %v", err.Error())
}
}

View File

@@ -32,6 +32,33 @@ diff -u -N /tmp/noop/apps_v1beta2_Deployment_nginx.yaml /tmp/transformed/apps_v1
spec:
containers:
- image: nginx
diff -u -N /tmp/noop/networking.k8s.io_v1_NetworkPolicy_nginx.yaml /tmp/transformed/networking.k8s.io_v1_NetworkPolicy_nginx.yaml
--- /tmp/noop/networking.k8s.io_v1_NetworkPolicy_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/networking.k8s.io_v1_NetworkPolicy_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -1,13 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
- name: nginx
+ annotations:
+ note: This is a test annotation
+ labels:
+ app: mynginx
+ org: example.com
+ team: foo
+ name: team-foo-nginx
spec:
ingress:
- from:
- podSelector:
matchLabels:
- app: nginx
+ app: mynginx
+ org: example.com
+ team: foo
podSelector:
matchExpressions:
- key: app
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS

View File

@@ -44,3 +44,28 @@ spec:
containers:
- image: nginx
name: nginx
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
org: example.com
team: foo
name: team-foo-nginx
spec:
ingress:
- from:
- podSelector:
matchLabels:
app: mynginx
org: example.com
team: foo
podSelector:
matchExpressions:
- key: app
operator: In
values:
- test

View File

@@ -6,4 +6,6 @@ commonLabels:
commonAnnotations:
note: This is a test annotation
resources:
- resources/*.yaml
- resources/deployment.yaml
- resources/networkpolicy.yaml
- resources/service.yaml

View File

@@ -0,0 +1,13 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: nginx
spec:
podSelector:
matchExpressions:
- {key: app, operator: In, values: [test]}
ingress:
- from:
- podSelector:
matchLabels:
app: nginx

View File

@@ -12,8 +12,8 @@ diff -u -N /tmp/noop/jingfang.example.com_v1beta1_MyKind_mykind.yaml /tmp/transf
- name: bee
+ name: test-bee
secretRef:
- name: crdsecret-m5ht5thcb4
+ name: test-crdsecret-m48btmkck5
- name: crdsecret
+ name: test-crdsecret
diff -u -N /tmp/noop/v1beta1_Bee_bee.yaml /tmp/transformed/v1beta1_Bee_bee.yaml
--- /tmp/noop/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
@@ -32,5 +32,5 @@ diff -u -N /tmp/noop/v1_Secret_crdsecret.yaml /tmp/transformed/v1_Secret_crdsecr
PATH: YmJiYmJiYmIK
kind: Secret
metadata:
- name: crdsecret-m5ht5thcb4
+ name: test-crdsecret-m48btmkck5
- name: crdsecret
+ name: test-crdsecret

View File

@@ -3,7 +3,7 @@ data:
PATH: YmJiYmJiYmIK
kind: Secret
metadata:
name: test-crdsecret-m48btmkck5
name: test-crdsecret
---
apiVersion: v1beta1
kind: Bee
@@ -20,4 +20,4 @@ spec:
beeRef:
name: test-bee
secretRef:
name: test-crdsecret-m48btmkck5
name: test-crdsecret

View File

@@ -2,7 +2,8 @@ namePrefix: staging-
commonLabels:
env: staging
patches:
- patches/deployment-patch*.yaml
- patches/deployment-patch1.yaml
- patches/deployment-patch2.yaml
bases:
- ../package/
configMapGenerator:

View File

@@ -48,7 +48,7 @@ diff -u -N /tmp/noop/extensions_v1beta1_Deployment_mungebot.yaml /tmp/transforme
- name: foo
value: bar
- image: nginx
+ image: nginx:1.7.9
+ image: nginx:1.8.0
name: nginx
ports:
- containerPort: 80

View File

@@ -111,7 +111,7 @@ spec:
name: test-infra-app-tls-6hkmhf2224
- name: foo
value: bar
image: nginx:1.7.9
image: nginx:1.8.0
name: nginx
ports:
- containerPort: 80

View File

@@ -53,12 +53,14 @@ diff -u -N /tmp/noop/batch_v1beta1_CronJob_cronjob-example.yaml /tmp/transformed
spec:
concurrencyPolicy: Forbid
jobTemplate:
@@ -11,10 +11,10 @@
@@ -11,11 +11,11 @@
containers:
- command:
- echo
- - base-cockroachdb
- - base-test-config-map-259876d7fg
+ - dev-base-cockroachdb
+ - dev-base-test-config-map-b2g2dmd64b
env:
- name: CDB_PUBLIC_SVC
- value: base-cockroachdb-public
@@ -140,6 +142,15 @@ diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml /tm
rules:
- apiGroups:
- ""
diff -u -N /tmp/noop/v1_ConfigMap_test-config-map.yaml /tmp/transformed/v1_ConfigMap_test-config-map.yaml
--- /tmp/noop/v1_ConfigMap_test-config-map.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ConfigMap_test-config-map.yaml YYYY-MM-DD HH:MM:SS
@@ -5,4 +5,4 @@
kind: ConfigMap
metadata:
creationTimestamp: null
- name: base-test-config-map-259876d7fg
+ name: dev-base-test-config-map-b2g2dmd64b
diff -u -N /tmp/noop/v1_ServiceAccount_cockroachdb.yaml /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml
--- /tmp/noop/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS

View File

@@ -67,6 +67,15 @@ subjects:
namespace: default
---
apiVersion: v1
data:
baz: qux
foo: bar
kind: ConfigMap
metadata:
creationTimestamp: null
name: dev-base-test-config-map-b2g2dmd64b
---
apiVersion: v1
kind: Service
metadata:
annotations:
@@ -206,6 +215,7 @@ spec:
- command:
- echo
- dev-base-cockroachdb
- dev-base-test-config-map-b2g2dmd64b
env:
- name: CDB_PUBLIC_SVC
value: dev-base-cockroachdb-public

View File

@@ -15,6 +15,7 @@ spec:
command:
- echo
- "$(CDB_STATEFULSET_NAME)"
- "$(TEST_CONFIG_MAP)"
env:
- name: CDB_PUBLIC_SVC
value: "$(CDB_PUBLIC_SVC)"

View File

@@ -2,6 +2,11 @@ namePrefix: base-
resources:
- cockroachdb-statefulset-secure.yaml
- cronjob.yaml
configMapGenerator:
- name: test-config-map
literals:
- foo=bar
- baz=qux
vars:
- name: CDB_PUBLIC_SVC
objref:
@@ -25,3 +30,10 @@ vars:
fieldref:
fieldpath: metadata.name
- name: TEST_CONFIG_MAP
objref:
kind: ConfigMap
name: test-config-map
apiVersion: v1
fieldref:
fieldpath: metadata.name

39
pkg/commands/util.go Normal file
View File

@@ -0,0 +1,39 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"log"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func globPatterns(fsys fs.FileSystem, patterns []string) ([]string, error) {
var result []string
for _, pattern := range patterns {
files, err := fsys.Glob(pattern)
if err != nil {
return nil, err
}
if len(files) == 0 {
log.Printf("%s has no match", pattern)
continue
}
result = append(result, files...)
}
return result, nil
}

View File

@@ -1,136 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package configmapandsecret generates configmaps and secrets per generator rules.
package configmapandsecret
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
"github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
// MakeConfigmapAndGenerateName makes a configmap and returns the configmap and the name appended with a hash.
func MakeConfigmapAndGenerateName(cm types.ConfigMapArgs) (*unstructured.Unstructured, string, error) {
corev1CM, err := makeConfigMap(cm)
if err != nil {
return nil, "", err
}
h, err := hash.ConfigMapHash(corev1CM)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", corev1CM.GetName(), h)
unstructuredCM, err := objectToUnstructured(corev1CM)
return unstructuredCM, nameWithHash, err
}
// MakeSecretAndGenerateName returns a secret with the name appended with a hash.
func MakeSecretAndGenerateName(secret types.SecretArgs, path string) (*unstructured.Unstructured, string, error) {
corev1Secret, err := makeSecret(secret, path)
if err != nil {
return nil, "", err
}
h, err := hash.SecretHash(corev1Secret)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", secret.Name, h)
unstructuredCM, err := objectToUnstructured(corev1Secret)
return unstructuredCM, nameWithHash, err
}
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
err = out.UnmarshalJSON(marshaled)
return &out, err
}
func makeConfigMap(cm types.ConfigMapArgs) (*corev1.ConfigMap, error) {
corev1cm := &corev1.ConfigMap{}
corev1cm.APIVersion = "v1"
corev1cm.Kind = "ConfigMap"
corev1cm.Name = cm.Name
corev1cm.Data = map[string]string{}
if cm.EnvSource != "" {
if err := cutil.HandleConfigMapFromEnvFileSource(corev1cm, cm.EnvSource); err != nil {
return nil, err
}
}
if cm.FileSources != nil {
if err := cutil.HandleConfigMapFromFileSources(corev1cm, cm.FileSources); err != nil {
return nil, err
}
}
if cm.LiteralSources != nil {
if err := cutil.HandleConfigMapFromLiteralSources(corev1cm, cm.LiteralSources); err != nil {
return nil, err
}
}
return corev1cm, nil
}
func makeSecret(secret types.SecretArgs, path string) (*corev1.Secret, error) {
corev1secret := &corev1.Secret{}
corev1secret.APIVersion = "v1"
corev1secret.Kind = "Secret"
corev1secret.Name = secret.Name
corev1secret.Type = corev1.SecretType(secret.Type)
if corev1secret.Type == "" {
corev1secret.Type = corev1.SecretTypeOpaque
}
corev1secret.Data = map[string][]byte{}
for k, v := range secret.Commands {
out, err := createSecretKey(path, v)
if err != nil {
return nil, err
}
corev1secret.Data[k] = out
}
return corev1secret, nil
}
func createSecretKey(wd string, command string) ([]byte, error) {
fi, err := os.Stat(wd)
if err != nil || !fi.IsDir() {
wd = filepath.Dir(wd)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = wd
return cmd.Output()
}

View File

@@ -0,0 +1,216 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package configmapandsecret generates configmaps and secrets per generator rules.
package configmapandsecret
import (
"encoding/json"
"fmt"
"path"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
)
// ConfigMapFactory makes ConfigMaps.
type ConfigMapFactory struct {
fSys fs.FileSystem
ldr loader.Loader
}
// NewConfigMapFactory returns a new ConfigMapFactory.
func NewConfigMapFactory(
fSys fs.FileSystem, l loader.Loader) *ConfigMapFactory {
return &ConfigMapFactory{fSys: fSys, ldr: l}
}
// MakeUnstructAndGenerateName returns an configmap and the name appended with a hash.
func (f *ConfigMapFactory) MakeUnstructAndGenerateName(
args *types.ConfigMapArgs) (*unstructured.Unstructured, string, error) {
cm, err := f.MakeConfigMap(args)
if err != nil {
return nil, "", err
}
h, err := hash.ConfigMapHash(cm)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", cm.GetName(), h)
unstructuredCM, err := objectToUnstructured(cm)
return unstructuredCM, nameWithHash, err
}
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
err = out.UnmarshalJSON(marshaled)
return &out, err
}
func (f *ConfigMapFactory) makeFreshConfigMap(
args *types.ConfigMapArgs) *corev1.ConfigMap {
cm := &corev1.ConfigMap{}
cm.APIVersion = "v1"
cm.Kind = "ConfigMap"
cm.Name = args.Name
cm.Data = map[string]string{}
return cm
}
// MakeConfigMap returns a new ConfigMap, or nil and an error.
func (f *ConfigMapFactory) MakeConfigMap(
args *types.ConfigMapArgs) (*corev1.ConfigMap, error) {
var all []kvPair
var err error
cm := f.makeFreshConfigMap(args)
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvSource))
}
all = append(all, pairs...)
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"literal sources %v", args.LiteralSources))
}
all = append(all, pairs...)
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"file sources: %v", args.FileSources))
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToConfigMap(cm, kv.key, kv.value)
if err != nil {
return nil, err
}
}
return cm, nil
}
func keyValuesFromLiteralSources(sources []string) ([]kvPair, error) {
var kvs []kvPair
for _, s := range sources {
k, v, err := parseLiteralSource(s)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: k, value: v})
}
return kvs, nil
}
func keyValuesFromFileSources(ldr loader.Loader, sources []string) ([]kvPair, error) {
var kvs []kvPair
for _, s := range sources {
k, fPath, err := parseFileSource(s)
if err != nil {
return nil, err
}
content, err := ldr.Load(fPath)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: k, value: string(content)})
}
return kvs, nil
}
func keyValuesFromEnvFile(l loader.Loader, path string) ([]kvPair, error) {
if path == "" {
return nil, nil
}
content, err := l.Load(path)
if err != nil {
return nil, err
}
return keyValuesFromLines(content)
}
// addKvToConfigMap adds the given key and data to the given config map.
// Error if key invalid, or already exists.
func addKvToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v", keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}
// parseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func parseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("key names or file paths cannot contain '='")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// parseLiteralSource parses the source key=val pair into its component pieces.
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
// it returns an error in the case of empty keys, values, or a missing equals sign.
func parseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], strings.Trim(items[1], "\"'"), nil
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package configmapandsecret
import (
"encoding/base64"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -89,41 +90,8 @@ func makeLiteralConfigMap(name string) *corev1.ConfigMap {
Data: map[string]string{
"a": "x",
"b": "y",
},
}
}
func makeTestSecret(name string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"DB_USERNAME": []byte("admin"),
"DB_PASSWORD": []byte("somepw"),
},
Type: corev1.SecretTypeOpaque,
}
}
func makeUnstructuredSecret(name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": name,
"creationTimestamp": nil,
},
"type": string(corev1.SecretTypeOpaque),
"data": map[string]interface{}{
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
"c": "Hello World",
"d": "true",
},
}
}
@@ -161,15 +129,18 @@ func TestConstructConfigMap(t *testing.T) {
input: types.ConfigMapArgs{
Name: "literalConfigMap",
DataSources: types.DataSources{
LiteralSources: []string{"a=x", "b=y"},
LiteralSources: []string{"a=x", "b=y", "c=\"Hello World\"", "d='true'"},
},
},
expected: makeLiteralConfigMap("literalConfigMap"),
},
}
// TODO: all tests should use a FakeFs
fSys := fs.MakeRealFS()
f := NewConfigMapFactory(fSys, loader.NewFileLoader(fSys))
for _, tc := range testCases {
cm, err := makeConfigMap(tc.input)
cm, err := f.MakeConfigMap(&tc.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -179,39 +150,6 @@ func TestConstructConfigMap(t *testing.T) {
}
}
func TestConstructSecret(t *testing.T) {
secret := types.SecretArgs{
Name: "secret",
Commands: map[string]string{
"DB_USERNAME": "printf admin",
"DB_PASSWORD": "printf somepw",
},
Type: "Opaque",
}
cm, err := makeSecret(secret, ".")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := makeTestSecret("secret")
if !reflect.DeepEqual(*cm, *expected) {
t.Fatalf("%#v\ndoesn't match expected:\n%#v", *cm, *expected)
}
}
func TestFailConstructSecret(t *testing.T) {
secret := types.SecretArgs{
Name: "secret",
Commands: map[string]string{
"FAILURE": "false", // This will fail.
},
Type: "Opaque",
}
_, err := makeSecret(secret, ".")
if err == nil {
t.Fatalf("Expected failure.")
}
}
func TestObjectConvertToUnstructured(t *testing.T) {
type testCase struct {
description string

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
package configmapandsecret
import (
"bufio"
@@ -28,14 +28,14 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// kvPair represents a key value pair.
type kvPair struct {
key string
value string
}
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// keyValuesFromLines parses given content in to a list of key-value pairs.
func keyValuesFromLines(content []byte) ([]kvPair, error) {
var kvs []kvPair

View File

@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
package configmapandsecret
import (
"reflect"

View File

@@ -0,0 +1,143 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package configmapandsecret
import (
"context"
"fmt"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
const (
defaultCommandTimeout = 5 * time.Second
)
// SecretFactory makes Secrets.
type SecretFactory struct {
fSys fs.FileSystem
wd string
}
// NewSecretFactory returns a new SecretFactory.
func NewSecretFactory(fSys fs.FileSystem, wd string) *SecretFactory {
return &SecretFactory{fSys: fSys, wd: wd}
}
func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret {
s := &corev1.Secret{}
s.APIVersion = "v1"
s.Kind = "Secret"
s.Name = args.Name
s.Type = corev1.SecretType(args.Type)
if s.Type == "" {
s.Type = corev1.SecretTypeOpaque
}
s.Data = map[string][]byte{}
return s
}
// MakeSecret returns a new secret.
func (f *SecretFactory) MakeSecret(args *types.SecretArgs) (*corev1.Secret, error) {
var all []kvPair
var err error
s := f.makeFreshSecret(args)
timeout := defaultCommandTimeout
if args.TimeoutSeconds != nil {
timeout = time.Duration(*args.TimeoutSeconds) * time.Second
}
pairs, err := f.keyValuesFromEnvFileCommand(args.EnvCommand, timeout)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvCommand))
}
all = append(all, pairs...)
pairs, err = f.keyValuesFromCommands(args.Commands, timeout)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"commands %v", args.Commands))
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToSecret(s, kv.key, kv.value)
if err != nil {
return nil, err
}
}
return s, nil
}
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
// Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
}
secret.Data[keyName] = []byte(data)
return nil
}
func (f *SecretFactory) keyValuesFromEnvFileCommand(cmd string, timeout time.Duration) ([]kvPair, error) {
content, err := f.createSecretKey(cmd, timeout)
if err != nil {
return nil, err
}
return keyValuesFromLines(content)
}
func (f *SecretFactory) keyValuesFromCommands(sources map[string]string, timeout time.Duration) ([]kvPair, error) {
var kvs []kvPair
for k, cmd := range sources {
content, err := f.createSecretKey(cmd, timeout)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: k, value: string(content)})
}
return kvs, nil
}
// Run a command, return its output as the secret.
func (f *SecretFactory) createSecretKey(command string, timeout time.Duration) ([]byte, error) {
if !f.fSys.IsDir(f.wd) {
f.wd = filepath.Dir(f.wd)
if !f.fSys.IsDir(f.wd) {
return nil, errors.New("not a directory: " + f.wd)
}
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = f.wd
return cmd.Output()
}

View File

@@ -1,134 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
// HandleConfigMapFromLiteralSources adds the specified literal source
// information into the provided configMap.
func HandleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := ParseLiteralSource(literalSource)
if err != nil {
return err
}
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
if err != nil {
return err
}
}
return nil
}
// HandleConfigMapFromFileSources adds the specified file source information
// into the provided configMap
func HandleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
if err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
return err
}
}
}
return nil
}
// HandleConfigMapFromEnvFileSource adds the specified env file source information
// into the provided configMap
func HandleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("env config file cannot be a directory")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value)
})
}
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
// the value with the content of the given file path, or returns an error.
func addKeyFromFileToConfigMap(configMap *v1.ConfigMap, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
}
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
// returning an error if the key is not valid or if the key already exists.
func addKeyFromLiteralToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}

View File

@@ -1,103 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
"k8s.io/apimachinery/pkg/util/validation"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// processEnvFileLine returns a blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary.
func processEnvFileLine(line []byte, filePath string,
currentLine int) (key, value string, err error) {
if !utf8.Valid(line) {
return ``, ``, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v",
filePath, currentLine+1, line)
}
// We trim UTF8 BOM from the first line of the file but no others
if currentLine == 0 {
line = bytes.TrimPrefix(line, utf8bom)
}
// trim the line from all leading whitespace first
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// If the line is empty or a comment, we return a blank key/value pair.
if len(line) == 0 || line[0] == '#' {
return ``, ``, nil
}
data := strings.SplitN(string(line), "=", 2)
key = data[0]
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
return ``, ``, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
if len(data) == 2 {
value = data[1]
} else {
// No value (no `=` in the line) is a signal to obtain the value
// from the environment.
value = os.Getenv(key)
}
return key, value, err
}
// addFromEnvFile processes an env file allows a generic addTo to handle the
// collection of key value pairs or returns an error.
func addFromEnvFile(filePath string, addTo func(key, value string) error) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
currentLine := 0
for scanner.Scan() {
// Process the current line, retrieving a key/value pair if
// possible.
scannedBytes := scanner.Bytes()
key, value, err := processEnvFileLine(scannedBytes, filePath, currentLine)
if err != nil {
return err
}
currentLine++
if len(key) == 0 {
// no key means line was empty or a comment
continue
}
if err = addTo(key, value); err != nil {
return err
}
}
return nil
}

View File

@@ -1,126 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
// HandleFromLiteralSources adds the specified literal source information into the provided secret
func HandleFromLiteralSources(secret *v1.Secret, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := ParseLiteralSource(literalSource)
if err != nil {
return err
}
if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
return err
}
}
return nil
}
// HandleFromFileSources adds the specified file source information into the provided secret
func HandleFromFileSources(secret *v1.Secret, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
if err = addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
return err
}
}
}
return nil
}
// HandleFromEnvFileSource adds the specified env file source information
// into the provided secret
func HandleFromEnvFileSource(secret *v1.Secret, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("env secret file cannot be a directory")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value))
})
}
func addKeyFromFileToSecret(secret *v1.Secret, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToSecret(secret, keyName, data)
}
func addKeyFromLiteralToSecret(secret *v1.Secret, keyName string, data []byte) error {
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, secret.Data)
}
secret.Data[keyName] = data
return nil
}

View File

@@ -1,93 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package util offers Configmap and Secret generation utilities.
package util
import (
"crypto/sha256"
"errors"
"fmt"
"path"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// ParseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format.
func ParseRFC3339(s string) (metav1.Time, error) {
if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
return metav1.Time{Time: t}, nil
}
t, err := time.Parse(time.RFC3339, s)
if err != nil {
return metav1.Time{}, err
}
return metav1.Time{Time: t}, nil
}
// HashObject encodes object using given codec and returns MD5 sum of the result.
func HashObject(obj runtime.Object, codec runtime.Encoder) (string, error) {
data, err := runtime.Encode(codec, obj)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", sha256.Sum256(data)), nil
}
// ParseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func ParseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("Key names or file paths cannot contain '='.")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// ParseLiteralSource parses the source key=val pair into its component pieces.
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
// it returns an error in the case of empty keys, values, or a missing equals sign.
func ParseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], items[1], nil
}

View File

@@ -66,7 +66,7 @@ func (p *pathConfigs) addPrefixPathConfig(config transformers.PathConfig) {
// RegisterCRDs parse CRD schemas from paths and update various pathConfigs
func RegisterCRDs(loader loader.Loader, paths []string) error {
pathConfigs := []pathConfigs{}
var pathConfigs []pathConfigs
for _, path := range paths {
pathConfig, err := registerCRD(loader, path)
if err != nil {
@@ -80,8 +80,7 @@ func RegisterCRDs(loader loader.Loader, paths []string) error {
// register CRD from one path
func registerCRD(loader loader.Loader, path string) ([]pathConfigs, error) {
result := []pathConfigs{}
var result []pathConfigs
content, err := loader.Load(path)
if err != nil {
return result, err

View File

@@ -142,12 +142,12 @@ If it is not set we generate a secret dynamically",
)
func makeLoader(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/crd.json", []byte(crdContent))
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/crd.json", []byte(crdContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
return loader
return ldr
}
func TestRegisterCRD(t *testing.T) {
@@ -184,9 +184,9 @@ func TestRegisterCRD(t *testing.T) {
},
}
loader := makeLoader(t)
ldr := makeLoader(t)
pathconfig, _ := registerCRD(loader, "/testpath/crd.json")
pathconfig, _ := registerCRD(ldr, "/testpath/crd.json")
sort.Slice(pathconfig[0].namereferencePathConfigs, func(i, j int) bool {
return pathconfig[0].namereferencePathConfigs[i].GVK() < pathconfig[0].namereferencePathConfigs[j].GVK()

View File

@@ -1,38 +0,0 @@
package diff
import (
"io/ioutil"
"os"
"path/filepath"
)
// directory represents a new temp directory and lets one create files in it.
type directory struct {
n string
}
// newDirectory makes a directory instance holding a new temp directory on disk.
// The directory name is the given prefix following by a random string.
func newDirectory(prefix string) (*directory, error) {
name, err := ioutil.TempDir("", prefix+"-")
if err != nil {
return nil, err
}
return &directory{n: name}, nil
}
// newFile creates a new file in the directory.
func (d *directory) newFile(name string) (*os.File, error) {
return os.OpenFile(filepath.Join(d.n, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
}
// delete removes the directory recursively.
func (d *directory) delete() error {
return os.RemoveAll(d.n)
}
// name is the name of the directory.
func (d *directory) name() string {
return d.n
}

View File

@@ -1,58 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package diff
import (
"io"
"os"
"github.com/kubernetes-sigs/kustomize/pkg/exec"
)
// program wraps the system diff program.
// If specified, the value of KUBERNETES_EXTERNAL_DIFF environment variable
// will be used instead of simply `diff(1)`.
type program struct {
stdout io.Writer
stderr io.Writer
}
func newProgram(out, errOut io.Writer) *program {
return &program{
stdout: out,
stderr: errOut,
}
}
func (d *program) makeCommand(args ...string) exec.Cmd {
diff := "diff"
if envDiff := os.Getenv("KUBERNETES_EXTERNAL_DIFF"); envDiff != "" {
diff = envDiff
} else {
args = append([]string{"-u", "-N"}, args...)
}
cmd := exec.New().Command(diff, args...)
cmd.SetStdout(d.stdout)
cmd.SetStderr(d.stderr)
return cmd
}
// run runs the detected diff program. `from` and `to` are the directory to diff.
func (d *program) run(from, to string) error {
d.makeCommand(from, to).Run() // Ignore diff return code
return nil
}

View File

@@ -1,83 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package diff runs system `diff` to compare resource collections.
package diff
import (
"github.com/ghodss/yaml"
"io"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
)
// RunDiff runs system diff program to compare two Maps.
func RunDiff(raw, transformed resmap.ResMap, out, errOut io.Writer) (err error) {
transformedDir, err := writeYamlToNewDir(transformed, "transformed")
if err != nil {
return err
}
defer func() {
err = transformedDir.delete()
}()
noopDir, err := writeYamlToNewDir(raw, "noop")
if err != nil {
return err
}
defer func() {
err = noopDir.delete()
}()
return newProgram(out, errOut).run(noopDir.name(), transformedDir.name())
}
// writeYamlToNewDir writes each obj in ResMap to a file in a new directory.
// The directory's name will begin with the given prefix.
// Each file is named with GroupVersionKindName.
func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
dir, err := newDirectory(prefix)
if err != nil {
return nil, err
}
for id, obj := range in {
f, err := dir.newFile(id.String())
if err != nil {
return nil, err
}
err = print(obj, f)
f.Close()
if err != nil {
return nil, err
}
}
return dir, nil
}
// Print the object as YAML.
func print(obj interface{}, w io.Writer) error {
if obj == nil {
return nil
}
data, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}

View File

@@ -21,3 +21,6 @@ secretGenerator:
tls.crt: "cat secret/tls.cert"
tls.key: "cat secret/tls.key"
type: "kubernetes.io/tls"
imageTags:
- name: nginx
newTag: 1.8.0

View File

@@ -24,9 +24,7 @@ import (
)
func ExampleNew() {
exec := exec.New()
cmd := exec.Command("echo", "Bonjour!")
cmd := exec.New().Command("echo", "Bonjour!")
buff := bytes.Buffer{}
cmd.SetStdout(&buff)
if err := cmd.Run(); err != nil {

View File

@@ -114,6 +114,6 @@ func tryReadVariableName(input string) (string, bool, int) {
// Not the beginning of an expression, ie, an operator
// that doesn't begin an expression. Return the operator
// and the first rune in the string.
return (string(operator) + string(input[0])), false, 1
return string(operator) + string(input[0]), false, 1
}
}

View File

@@ -18,7 +18,8 @@ package fs
import (
"fmt"
"os"
"path/filepath"
"sort"
)
var _ FileSystem = &FakeFS{}
@@ -42,7 +43,7 @@ func (fs *FakeFS) Create(name string) (File, error) {
}
// Mkdir assures a fake directory appears in the in-memory file system.
func (fs *FakeFS) Mkdir(name string, perm os.FileMode) error {
func (fs *FakeFS) Mkdir(name string) error {
fs.m[name] = makeDir(name)
return nil
}
@@ -55,12 +56,31 @@ func (fs *FakeFS) Open(name string) (File, error) {
return fs.m[name], nil
}
// Stat always returns nil FileInfo, and returns an error if file does not exist.
func (fs *FakeFS) Stat(name string) (os.FileInfo, error) {
if f, found := fs.m[name]; found {
return &Fakefileinfo{f}, nil
// Exists returns true if file is known.
func (fs *FakeFS) Exists(name string) bool {
_, found := fs.m[name]
return found
}
// Glob returns the list of matching files
func (fs *FakeFS) Glob(pattern string) ([]string, error) {
var result []string
for p := range fs.m {
if fs.pathMatch(p, pattern) {
result = append(result, p)
}
}
return nil, fmt.Errorf("file %q does not exist", name)
sort.Strings(result)
return result, nil
}
// IsDir returns true if the file exists and is a directory.
func (fs *FakeFS) IsDir(name string) bool {
f, found := fs.m[name]
if !found {
return false
}
return f.dir
}
// ReadFile always returns an empty bytes and error depending on content of m.
@@ -71,18 +91,6 @@ func (fs *FakeFS) ReadFile(name string) ([]byte, error) {
return nil, fmt.Errorf("cannot read file %q", name)
}
// ReadFiles looks through all files in the fake filesystem
// and find the matching files and then read content from all of them
func (fs *FakeFS) ReadFiles(name string) (map[string][]byte, error) {
result := map[string][]byte{}
for p, f := range fs.m {
if fs.pathMatch(p, name) {
result[p] = f.content
}
}
return result, nil
}
// WriteFile always succeeds and does nothing.
func (fs *FakeFS) WriteFile(name string, c []byte) error {
ff := &FakeFile{}
@@ -92,8 +100,6 @@ func (fs *FakeFS) WriteFile(name string, c []byte) error {
}
func (fs *FakeFS) pathMatch(path, pattern string) bool {
if path == pattern {
return true
}
return false
match, _ := filepath.Match(pattern, path)
return match
}

View File

@@ -18,37 +18,29 @@ package fs
import (
"bytes"
"reflect"
"testing"
)
func TestStatNotExist(t *testing.T) {
func TestExists(t *testing.T) {
x := MakeFakeFS()
info, err := x.Stat("foo")
if info != nil {
t.Fatalf("expected nil info")
}
if err == nil {
t.Fatalf("expected error")
if x.Exists("foo") {
t.Fatalf("expected no foo")
}
}
func TestStat(t *testing.T) {
func TestIsDir(t *testing.T) {
x := MakeFakeFS()
expectedName := "my-dir"
err := x.Mkdir(expectedName, 0666)
err := x.Mkdir(expectedName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
info, err := x.Stat(expectedName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
if !x.Exists(expectedName) {
t.Fatalf(expectedName + " should exist")
}
name := info.Name()
if name != expectedName {
t.Fatalf("expected %v but got %v", expectedName, name)
}
if !info.IsDir() {
t.Fatalf("expected IsDir() return true")
if !x.IsDir(expectedName) {
t.Fatalf(expectedName + " should be a dir")
}
}
@@ -61,12 +53,8 @@ func TestCreate(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error")
}
info, err := x.Stat("foo")
if info == nil {
t.Fatalf("expected non-nil info")
}
if err != nil {
t.Fatalf("expected no error")
if !x.Exists("foo") {
t.Fatalf("expected foo to exist")
}
}
@@ -103,3 +91,20 @@ func TestWriteFile(t *testing.T) {
t.Fatalf("incorrect content: %v", content)
}
}
func TestGlob(t *testing.T) {
x := MakeFakeFS()
x.Create("dir/foo")
x.Create("dir/bar")
files, err := x.Glob("dir/*")
if err != nil {
t.Fatalf("expected no error")
}
expected := []string{
"dir/bar",
"dir/foo",
}
if !reflect.DeepEqual(files, expected) {
t.Fatalf("incorrect files found by glob: %v", files)
}
}

View File

@@ -25,11 +25,12 @@ import (
// FileSystem groups basic os filesystem methods.
type FileSystem interface {
Create(name string) (File, error)
Mkdir(name string, perm os.FileMode) error
Mkdir(name string) error
Open(name string) (File, error)
Stat(name string) (os.FileInfo, error)
IsDir(name string) bool
Exists(name string) bool
Glob(pattern string) ([]string, error)
ReadFile(name string) ([]byte, error)
ReadFiles(name string) (map[string][]byte, error)
WriteFile(name string, data []byte) error
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package fs
import (
"errors"
"os"
)
@@ -28,14 +27,6 @@ type realFile struct {
file *os.File
}
// MakeRealFile makes an instance of realFile.
func MakeRealFile(f *os.File) (File, error) {
if f == nil {
return nil, errors.New("file argument may not be nil")
}
return &realFile{file: f}, nil
}
// Close closes a file.
func (f *realFile) Close() error { return f.file.Close() }

View File

@@ -36,37 +36,36 @@ func MakeRealFS() FileSystem {
func (realFS) Create(name string) (File, error) { return os.Create(name) }
// Mkdir delegates to os.Mkdir.
func (realFS) Mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
func (realFS) Mkdir(name string) error {
return os.Mkdir(name, 0777|os.ModeDir)
}
// Open delegates to os.Open.
func (realFS) Open(name string) (File, error) { return os.Open(name) }
// Stat delegates to os.Stat.
func (realFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
// Exists returns true if os.Stat succeeds.
func (realFS) Exists(name string) bool {
_, err := os.Stat(name)
return err == nil
}
// Glob returns the list of matching files
func (realFS) Glob(pattern string) ([]string, error) {
return filepath.Glob(pattern)
}
// IsDir delegates to os.Stat and FileInfo.IsDir
func (realFS) IsDir(name string) bool {
info, err := os.Stat(name)
if err != nil {
return false
}
return info.IsDir()
}
// ReadFile delegates to ioutil.ReadFile.
func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
// ReadFiles use glob to find the matching files and then read content from all of them
func (realFS) ReadFiles(name string) (map[string][]byte, error) {
files, err := filepath.Glob(name)
if err != nil || len(files) == 0 {
return nil, err
}
output := map[string][]byte{}
for _, file := range files {
bytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
if bytes != nil {
output[file] = bytes
}
}
return output, nil
}
// WriteFile delegates to ioutil.WriteFile with read/write permissions.
func (realFS) WriteFile(name string, c []byte) error {
return ioutil.WriteFile(name, c, 0666)

View File

@@ -26,33 +26,44 @@ import (
func TestReadFilesRealFS(t *testing.T) {
x := MakeRealFS()
testDir := "kustomize_testing_dir"
err := x.Mkdir(testDir, 0777)
err := x.Mkdir(testDir)
defer os.RemoveAll(testDir)
if err != nil {
t.Fatalf("unexpected error %s", err)
}
if !x.Exists(testDir) {
t.Fatalf("expected existence")
}
if !x.IsDir(testDir) {
t.Fatalf("expected directory")
}
err = x.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
if err != nil {
t.Fatalf("unexpected error %s", err)
}
if !x.Exists(path.Join(testDir, "foo")) {
t.Fatalf("expected foo")
}
if x.IsDir(path.Join(testDir, "foo")) {
t.Fatalf("expected foo not to be a directory")
}
err = x.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
if err != nil {
t.Fatalf("unexpected error %s", err)
}
expected := map[string][]byte{
path.Join(testDir, "foo"): []byte(`foo`),
path.Join(testDir, "bar"): []byte(`bar`),
}
content, err := x.ReadFiles("kustomize_testing_dir/*")
if !reflect.DeepEqual(content, expected) {
t.Fatalf("actual: %+v doesn't match expected: %+v", content, expected)
files, err := x.Glob(path.Join("testDir", "*"))
expected := []string{
path.Join(testDir, "bar"),
path.Join(testDir, "foo"),
}
if err != nil {
t.Fatalf("unexpected error %s", err)
t.Fatalf("expected no error")
}
if reflect.DeepEqual(files, expected) {
t.Fatalf("incorrect files found by glob: %v", files)
}
}

View File

@@ -20,7 +20,7 @@ package error
import (
"fmt"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/util/yaml"
)
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
@@ -33,8 +33,8 @@ func (e YamlFormatError) Error() string {
return fmt.Sprintf("YAML file [%s] encounters a format error.\n%s\n", e.Path, e.ErrorMsg)
}
// ErrorHandler handles YamlFormatError
func ErrorHandler(e error, path string) error {
// Handler handles YamlFormatError
func Handler(e error, path string) error {
if err, ok := e.(yaml.YAMLSyntaxError); ok {
return YamlFormatError{
Path: path,

View File

@@ -22,7 +22,7 @@ import (
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/util/yaml"
)
var (
@@ -49,9 +49,9 @@ func TestYamlFormatError_Error(t *testing.T) {
func TestErrorHandler(t *testing.T) {
f := foo{}
err := yaml.NewYAMLToJSONDecoder(bytes.NewReader([]byte(doc))).Decode(&f)
testErr := ErrorHandler(err, filepath)
expectedErr := fmt.Errorf("Format error message")
fmtErr := ErrorHandler(expectedErr, filepath)
testErr := Handler(err, filepath)
expectedErr := fmt.Errorf("format error message")
fmtErr := Handler(expectedErr, filepath)
if fmtErr.Error() != expectedErr.Error() {
t.Errorf("Expected returning fmt.Error, but found %T", fmtErr)
}

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