Compare commits

...

179 Commits

Author SHA1 Message Date
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
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
Jingfang Liu
6f82073d4b Merge pull request #133 from Liujingfang1/loader
Add glob support
2018-06-27 15:20:01 -07:00
Jingfang Liu
2a3f09a2f0 Add integration test for glob support 2018-06-27 14:48:50 -07:00
Jingfang Liu
6392e6629f Add glob support 2018-06-27 10:54:12 -07:00
Jingfang Liu
c25ed7f7bc Merge pull request #137 from babiel/add-cronjob-to-refvars
Add CronJob to refvars transformer
2018-06-26 10:02:38 -07:00
Jingfang Liu
918247d2cc Merge pull request #138 from guineveresaenger/secret-docs
Adds example for secret download using curl
2018-06-26 10:02:01 -07:00
Maximilian Gaß
0c260ef804 Add NetworkPolicy to label transformer 2018-06-26 17:18:50 +02:00
Maximilian Gaß
2a06a174e8 Add CronJob to refvars transformer 2018-06-26 12:20:52 +02:00
guineveresaenger
54e8a014bc Adds example for secret download using curl
It is possible to download secrets from web locations, as the command subfield can execute any terminal command specified.
This is a useful feature that should be highlighted.
2018-06-25 16:39:29 -07:00
Jingfang Liu
5b67b580f2 Merge pull request #129 from fanzhangio/issue114
Enhancement for format error message
2018-06-25 10:33:36 -07:00
fanzhangio
6a67183ed7 Enhancement for format error message
- add yaml format error handler
- silent usage when build command fails
2018-06-25 07:35:43 -07:00
Jingfang Liu
a38befdaa1 Merge pull request #132 from Liujingfang1/order
correct ordering of the k8s objects
2018-06-22 10:33:31 -07:00
Jingfang Liu
0312cdf677 Merge pull request #130 from sethpollack/image-pull-secret
add imagePullSecrets namerefs
2018-06-22 10:22:33 -07:00
Jingfang Liu
991ffbbdfc populate the map from a slice 2018-06-22 10:19:41 -07:00
Seth Pollack
bbd29d9dc1 add test 2018-06-21 23:17:03 -04:00
Jingfang Liu
28953e03a0 Merge pull request #120 from guineveresaenger/edit-add-base
New command: kustomize edit add base
2018-06-21 16:54:15 -07:00
guineveresaenger
37489ec2e9 Adds ability to add multiple base directories to kustomization 2018-06-21 16:39:46 -07:00
Jingfang Liu
636ab874eb update failed test 2018-06-21 16:36:27 -07:00
Jingfang Liu
90d16c2377 correct ordering of the k8s objects:
NameSpace, CRD, ServiceAccount, Role, ClusterRole, RoleBinding,
ClusterRoleBinding
2018-06-21 16:21:31 -07:00
Seth Pollack
5d24dda28a add imagePullSecrets namerefs 2018-06-20 23:23:31 -04:00
Jingfang Liu
dec5109e31 Merge pull request #127 from Liujingfang1/master
remove extra package comment on crds
2018-06-20 13:23:41 -07:00
Jingfang Liu
cc8690381c remove extra package comment on crds 2018-06-20 13:22:17 -07:00
Jingfang Liu
f5f95e3692 Merge pull request #125 from Liujingfang1/order
put namespace objects first in the output
2018-06-19 16:10:48 -07:00
Jingfang Liu
809d5b1fe2 put namespace objects first in the output 2018-06-19 15:44:33 -07:00
Jingfang Liu
38b4365ab3 Merge pull request #121 from traherom/master
Jobs do not create a selector if not already present
2018-06-19 14:22:15 -07:00
Jingfang Liu
d865300fdb Merge pull request #105 from Liujingfang1/crdsupport
Add support for CRDs
2018-06-19 12:04:35 -07:00
Jingfang Liu
e2677cdc8a Merge pull request #123 from Liujingfang1/master
Add variable reference to pod command,args,env
2018-06-19 12:04:06 -07:00
Jingfang Liu
ea00134776 Update pathconfigs library
implement CRD support and add unit tests

Add integration test for crd support

address comments
2018-06-19 11:45:53 -07:00
Jingfang Liu
ad3cd47c25 Add variable reference to pod command,args,env 2018-06-19 11:25:58 -07:00
guineveresaenger
a1dcf3386b Adds kustomize edit add base command
This pull request adds support for editing the kustomization.yaml in the current directory with a base.
2018-06-19 10:09:13 -07:00
Jingfang Liu
e7ecceb0c2 Update vendor 2018-06-19 09:36:30 -07:00
Ryan Morehart
50c40eb80c CronJob transformation fixed
- no longer add labels in an invalid location (#116)
- only have selector added it already present
2018-06-19 06:14:05 -06:00
Ryan Morehart
398ceb0a92 Jobs do not create a selector if not already present 2018-06-18 17:09:23 -06:00
Jingfang Liu
b7be630924 Merge pull request #113 from uthark/unparam
Enable unparam linter and fix found issues
2018-06-18 11:55:57 -07:00
Oleg Atamanenko
f557841e54 Enable unparam linter and fix issues 2018-06-18 14:49:57 -04:00
Jingfang Liu
9fc24634a2 Merge pull request #115 from woop/patch-1
Update example to show correct environment
2018-06-15 12:35:12 -07:00
Jingfang Liu
0617a283a0 Merge pull request #111 from Liujingfang1/master
Add variable reference to container env
2018-06-15 11:20:25 -07:00
Willem Pienaar
f616e30a38 Update example to show correct environment 2018-06-15 23:27:16 +08:00
Jingfang Liu
50b197f329 Merge pull request #110 from uthark/gometalinter
Add gometalinter to pre-commit hook
2018-06-14 18:52:59 -07:00
Oleg Atamanenko
6fd0330b80 Add gometalinter to pre-commit hook
Enable varcheck and fix found issues

Add ineffassign to list of checks and fix found issues

Added nakedret and fixed found issues

Add interfacer check and fix found issue

Add lll and fix found issues

Add deadcode linter, remove unused code
2018-06-14 20:55:20 -04:00
Jingfang Liu
8127b09d12 Merge pull request #73 from guineveresaenger/example-docfix
Clarifies hello-world example documentation
2018-06-14 15:24:25 -07:00
Jingfang Liu
09ab2bb5c0 Add variable reference to container env 2018-06-14 13:38:58 -07:00
Sunil Arora
54ac4e73e7 Merge pull request #108 from Liujingfang1/args
Add variable reference support for args
2018-06-13 14:51:46 -07:00
Jeff Regan
d4ad7f80e0 Merge pull request #104 from monopole/renameExamplesToTestData
Rename examples dir to examplelayout.
2018-06-12 15:07:14 -07:00
Jeffrey Regan
623e21d1c0 Rename examples dir to examplelayout. 2018-06-12 15:03:45 -07:00
Jeff Regan
0c88c43c67 Merge pull request #102 from monopole/upgradeCrypto
Replace deprecated crypto/md5 with crypto/sha256
2018-06-12 13:51:10 -07:00
Jeffrey Regan
c6d8bcb01b Replace deprecated crypto/md5 with crypto/sha256 2018-06-12 13:47:22 -07:00
Jeff Regan
5285e6101f Merge pull request #101 from monopole/moveVersion
Move version.go to commands dir.
2018-06-12 13:10:00 -07:00
Jeffrey Regan
2fb69db685 Move version.go to commands dir. 2018-06-12 13:08:23 -07:00
guineveresaenger
730597b77e grepping for kiwi 2018-06-12 10:33:37 -07:00
guineveresaenger
d488d9804d grepping for configMap name 2018-06-12 10:29:25 -07:00
Jeff Regan
f98bc42cbb Merge pull request #99 from monopole/tempCaptureExec
Absorb exec package from k8s.io/utils.
2018-06-12 09:46:14 -07:00
Jeffrey Regan
d7b9f64c5a Absorb exec package from utils. 2018-06-12 09:22:07 -07:00
Jeff Regan
785291af62 Merge pull request #95 from Liujingfang1/crdsupport
Add skeleton for CRD support
2018-06-12 09:21:10 -07:00
Jingfang Liu
4f05482e00 Add support for CRDs 2018-06-11 21:52:13 -07:00
Jeff Regan
3c3f85e623 Merge pull request #96 from monopole/secContacts
Fixes #33; add SECURITY_CONTACTS
2018-06-11 19:32:10 -07:00
jregan
40bb81142b Fixes #33; add SECURITY_CONTACTS 2018-06-11 19:30:13 -07:00
Jingfang Liu
46e8fd7065 Add variable reference support for args 2018-06-11 13:57:34 -07:00
guineveresaenger
4e7610a44d Addresses review comments 2018-06-11 12:35:33 -07:00
guineveresaenger
5a3c6553fc Clarifies hello-world example documentation
The example documentation had a few minor discrepancies between commands suggested and expected outcomes. This pull request addresses those, making it easier for folks to use the hello-world demo.
2018-06-08 13:27:20 -07:00
454 changed files with 64391 additions and 3272 deletions

View File

@@ -13,6 +13,7 @@ before_install:
- go get -u github.com/onsi/ginkgo/ginkgo
- go get -u github.com/monopole/mdrip
- go get -u github.com/fzipp/gocyclo
- go get -u gopkg.in/alecthomas/gometalinter.v2 && gometalinter.v2 --install
# Install must be set to prevent default `go get` to run.
# The dependencies have already been vendored by `dep` so

163
Gopkg.lock generated
View File

@@ -2,120 +2,224 @@
[[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: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",
]
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]]
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]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = ""
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[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",
]
pruneopts = ""
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
[[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]]
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",
@@ -131,25 +235,32 @@
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
"unicode/rangetable",
"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",
@@ -179,12 +290,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",
@@ -218,31 +331,51 @@
"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/util/proto"]
packages = [
"pkg/common",
"pkg/util/proto",
]
pruneopts = ""
revision = "b3f03f55328800731ce03a164b80973014ecd455"
[[projects]]
branch = "master"
name = "k8s.io/utils"
packages = ["exec"]
revision = "258e2a2fa64568210fbd6267cf1d8fd87c3cb86e"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e966d7880a29cf5669060d6564407f0f4c164e93eb844c22efec383383af2d3e"
input-imports = [
"github.com/evanphx/json-patch",
"github.com/ghodss/yaml",
"github.com/golang/glog",
"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

@@ -52,3 +52,7 @@
[[constraint]]
branch = "master"
name = "k8s.io/utils"
[[constraint]]
branch = "master"
name = "github.com/go-openapi/spec"

15
SECURITY_CONTACTS Normal file
View File

@@ -0,0 +1,15 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
monopole
Liujingfang1
pwittrock

View File

@@ -1,4 +1,5 @@
#!/bin/bash
set -e
# Make sure, we run in the root of the repo and
# therefore run the tests on all packages
@@ -36,14 +37,30 @@ function testGoCyclo {
diff <(echo -n) <(go_dirs | xargs -0 gocyclo -over 15)
}
function testGoImports {
diff -u <(echo -n) <(go_dirs | xargs -0 goimports -l)
}
function testGoLint {
diff -u <(echo -n) <(go_dirs | xargs -0 golint --min_confidence 0.85 )
}
function testGoMetalinter {
diff -u <(echo -n) <(go_dirs | xargs -0 gometalinter.v2 --disable-all --deadline 5m \
--enable=misspell \
--enable=structcheck \
--enable=deadcode \
# Disabling 'goimports' because it reports hyphens in imported package \
# names as errors, and we have to vendor them in regardless. \
# --enable=goimports \
--enable=varcheck \
--enable=goconst \
--enable=unparam \
--enable=ineffassign \
--enable=nakedret \
--enable=interfacer \
--enable=misspell \
--line-length=170 --enable=lll \
--dupl-threshold=400 --enable=dupl)
}
function testGoVet {
go vet -all ./...
}
@@ -57,7 +74,7 @@ function testExamples {
}
runTest testGoFmt
runTest testGoImports
runTest testGoMetalinter
runTest testGoLint
runTest testGoVet
runTest testGoCyclo

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

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

@@ -82,6 +82,11 @@ secretGenerator:
tls.crt: "cat secret/tls.cert"
tls.key: "cat secret/tls.key"
type: "kubernetes.io/tls"
- name: downloaded_secret
commands:
username: "curl -s https://path/to/secrets/username.yaml"
password: "curl -s https://path/to/secrets/password.yaml"
type: Opaque
# Each entry in this list should resolve to a directory
# containing a kustomization file, else the
@@ -115,3 +120,83 @@ patches:
- service_port_8888.yaml
- deployment_increase_replicas.yaml
- deployment_increase_memory.yaml
# Each entry in this list should be a relative path to
# a file for custom resource definition(CRD).
#
# The presence of this field is to allow kustomize be
# aware of CRDs and apply proper
# transformation for any objects in those types.
#
# Typical use case: A CRD object refers to a ConfigMap object.
# In kustomization, the ConfigMap object name may change by adding namePrefix or hashing
# The name reference for this ConfigMap object in CRD object need to be
# updated with namePrefix or hashing in the same way.
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

@@ -23,11 +23,18 @@ 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.

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.
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

@@ -112,6 +112,12 @@ sed -i 's/app: hello/app: my-hello/' \
$BASE/kustomization.yaml
```
On a Mac, use:
```
sed -i '' $pattern $file
```
to get in-place editing.
See the effect:
<!-- @checkLabel @test -->
```
@@ -309,122 +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
```
Run kustomize again to see the new 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)
```
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-

View File

@@ -4,7 +4,7 @@ metadata:
name: demo-configmap
data:
application.properties: |
app.name=Staging Kinflate Demo
app.name=Production Kinflate Demo
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://<production_db_ip>:3306/db_example
spring.datasource.username=root

View File

@@ -89,7 +89,7 @@ The patch has following content
> - name: wordpress
> env:
> - name: WORDPRESS_DB_HOST
> value: mysql
> value: $(MYSQL_SERVICE)
> - name: WORDPRESS_DB_PASSWORD
> valueFrom:
> secretKeyRef:

View File

@@ -15,7 +15,7 @@ spec:
- name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: mysql
value: $(MYSQL_SERVICE)
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:

View File

@@ -26,7 +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"
@@ -42,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
}
@@ -57,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 {
@@ -97,8 +102,12 @@ func (a *Application) MakeUncustomizedResMap() (resmap.ResMap, error) {
// resolveRefsToGeneratedResources fixes all name references.
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
r := []transformers.Transformer{transformers.NewNameHashTransformer()}
err := transformers.NewNameHashTransformer().Transform(m)
if err != nil {
return nil, err
}
var r []transformers.Transformer
t, err := transformers.NewDefaultingNameReferenceTransformer()
if err != nil {
return nil, err
@@ -127,14 +136,21 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
errs := &interror.KustomizationErrors{}
result, err := a.loadResMapFromBasesAndResources()
if err != nil {
errs.Append(errors.Wrap(err, "rawResources"))
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
}
cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
err = crds.RegisterCRDs(a.ldr, a.kustomization.Crds)
if err != nil {
errs.Append(errors.Wrap(err, "RegisterCRDs"))
}
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"))
}
@@ -148,7 +164,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"))
}
@@ -156,11 +172,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
}
@@ -170,7 +195,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"))
}
@@ -183,15 +208,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
@@ -214,12 +239,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
@@ -234,7 +259,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
@@ -267,7 +292,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)
@@ -282,7 +307,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()

View File

@@ -18,11 +18,11 @@ package app
import (
"encoding/base64"
"os"
"reflect"
"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"
@@ -67,20 +67,20 @@ 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"}
@@ -91,7 +91,7 @@ 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 +123,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 +142,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 +163,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 +180,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)
}
@@ -215,7 +217,7 @@ func TestRawResources1(t *testing.T) {
}),
}
l := makeLoader1(t)
app, err := NewApplication(l)
app, err := NewApplication(l, fs.MakeFakeFS())
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}
@@ -255,28 +257,28 @@ spec:
)
func makeLoader2(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
if err != nil {
t.Fatal(err)
}
err = loader.AddFile("/testpath/service.yaml", []byte(serviceContent))
err = ldr.AddFile("/testpath/service.yaml", []byte(serviceContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddDirectory("/testpath/base", os.ModeDir)
err = ldr.AddDirectory("/testpath/base")
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
err = ldr.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
err = ldr.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
return loader
return ldr
}
// TODO: This test covers incorrect behavior; it should not pass.
@@ -287,7 +289,7 @@ func makeLoader2(t *testing.T) loader.Loader {
// perhaps it's not worth supporting the command.
func TestRawResources2(t *testing.T) {
expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
@@ -325,7 +327,7 @@ func TestRawResources2(t *testing.T) {
}),
}
l := makeLoader2(t)
app, err := NewApplication(l)
app, err := NewApplication(l, fs.MakeFakeFS())
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}

98
pkg/commands/addbase.go Normal file
View File

@@ -0,0 +1,98 @@
/*
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/spf13/cobra"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
type addBaseOptions struct {
baseDirectoryPaths string
}
// newCmdAddBase adds the file path of the kustomize base to the kustomization file.
func newCmdAddBase(fsys fs.FileSystem) *cobra.Command {
var o addBaseOptions
cmd := &cobra.Command{
Use: "base",
Short: "Adds one or more bases to the kustomization.yaml in current directory",
Example: `
add base {filepath1},{filepath2}`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunAddBase(fsys)
},
}
return cmd
}
// Validate validates addBase command.
func (o *addBaseOptions) Validate(args []string) error {
if len(args) != 1 {
return errors.New("must specify a base directory")
}
o.baseDirectoryPaths = args[0]
return nil
}
// Complete completes addBase command.
func (o *addBaseOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunAddBase runs addBase command (do real work).
func (o *addBaseOptions) RunAddBase(fsys fs.FileSystem) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
// split directory paths
paths := strings.Split(o.baseDirectoryPaths, ",")
for _, path := range paths {
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)
}
m.Bases = append(m.Bases, path)
}
return mf.write(m)
}

View File

@@ -0,0 +1,100 @@
/*
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 (
"testing"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
const (
baseDirectoryPaths = "my/path/to/wonderful/base,other/path/to/even/more/wonderful/base"
)
func TestAddBaseHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base)
}
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddBase(fakeFS)
args := []string{baseDirectoryPaths}
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)
}
for _, base := range bases {
if !strings.Contains(string(content), base) {
t.Errorf("expected base name in kustomization")
}
}
}
func TestAddBaseAlreadyThere(t *testing.T) {
fakeFS := fs.MakeFakeFS()
// Create fake directories
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base)
}
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddBase(fakeFS)
args := []string{baseDirectoryPaths}
err := cmd.RunE(cmd, args)
if err != nil {
t.Fatalf("unexpected cmd error: %v", err)
}
// adding an existing base should return an error
err = cmd.RunE(cmd, args)
if err == nil {
t.Errorf("expected already there problem")
}
var expectedErrors []string
for _, base := range bases {
msg := "base " + base + " already in kustomization file"
expectedErrors = append(expectedErrors, msg)
if !stringInSlice(msg, expectedErrors) {
t.Errorf("unexpected error %v", err)
}
}
}
func TestAddBaseNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddBase(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "must specify a base directory" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -18,8 +18,7 @@ package commands
import (
"errors"
"fmt"
"io"
"log"
"github.com/spf13/cobra"
@@ -28,11 +27,11 @@ import (
)
type addPatchOptions struct {
patchFilePath string
patchFilePaths []string
}
// newCmdAddPatch adds the name of a file containing a patch to the kustomization file.
func newCmdAddPatch(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
func newCmdAddPatch(fsys fs.FileSystem) *cobra.Command {
var o addPatchOptions
cmd := &cobra.Command{
@@ -49,7 +48,7 @@ func newCmdAddPatch(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
if err != nil {
return err
}
return o.RunAddPatch(out, errOut, fsys)
return o.RunAddPatch(fsys)
},
}
return cmd
@@ -57,10 +56,10 @@ func newCmdAddPatch(out, errOut io.Writer, 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,11 +69,14 @@ func (o *addPatchOptions) Complete(cmd *cobra.Command, args []string) error {
}
// RunAddPatch runs addPatch command (do real work).
func (o *addPatchOptions) RunAddPatch(out, errOut io.Writer, fsys fs.FileSystem) error {
_, err := fsys.Stat(o.patchFilePath)
func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
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 {
@@ -86,11 +88,13 @@ func (o *addPatchOptions) RunAddPatch(out, errOut io.Writer, fsys fs.FileSystem)
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

@@ -17,8 +17,6 @@ limitations under the License.
package commands
import (
"bytes"
"os"
"testing"
"strings"
@@ -36,13 +34,13 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
)
func TestAddPatchHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteFile(patchFileName+"another", []byte(patchFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddPatch(buf, os.Stderr, fakeFS)
args := []string{patchFileName}
cmd := newCmdAddPatch(fakeFS)
args := []string{patchFileName + "*"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
@@ -54,36 +52,34 @@ 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) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(patchFileName, []byte(patchFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddPatch(buf, os.Stderr, fakeFS)
cmd := newCmdAddPatch(fakeFS)
args := []string{patchFileName}
err := cmd.RunE(cmd, args)
if err != nil {
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)
}
}
func TestAddPatchNoArgs(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddPatch(buf, os.Stderr, fakeFS)
cmd := newCmdAddPatch(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)

View File

@@ -18,8 +18,7 @@ package commands
import (
"errors"
"fmt"
"io"
"log"
"github.com/spf13/cobra"
@@ -28,11 +27,11 @@ import (
)
type addResourceOptions struct {
resourceFilePath string
resourceFilePaths []string
}
// newCmdAddResource adds the name of a file containing a resource to the kustomization file.
func newCmdAddResource(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
func newCmdAddResource(fsys fs.FileSystem) *cobra.Command {
var o addResourceOptions
cmd := &cobra.Command{
@@ -49,7 +48,7 @@ func newCmdAddResource(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command
if err != nil {
return err
}
return o.RunAddResource(out, errOut, fsys)
return o.RunAddResource(fsys)
},
}
return cmd
@@ -57,10 +56,10 @@ func newCmdAddResource(out, errOut io.Writer, 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,11 +69,14 @@ func (o *addResourceOptions) Complete(cmd *cobra.Command, args []string) error {
}
// RunAddResource runs addResource command (do real work).
func (o *addResourceOptions) RunAddResource(out, errOut io.Writer, fsys fs.FileSystem) error {
_, err := fsys.Stat(o.resourceFilePath)
func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
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 {
@@ -86,11 +88,13 @@ func (o *addResourceOptions) RunAddResource(out, errOut io.Writer, fsys fs.FileS
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,11 +17,8 @@ limitations under the License.
package commands
import (
"bytes"
"os"
"testing"
"strings"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
@@ -52,13 +49,13 @@ secretGenerator: []
)
func TestAddResourceHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(resourceFileName+"another", []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddResource(buf, os.Stderr, fakeFS)
args := []string{resourceFileName}
cmd := newCmdAddResource(fakeFS)
args := []string{resourceFileName + "*"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
@@ -70,36 +67,34 @@ 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) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(resourceFileName, []byte(resourceFileContent))
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdAddResource(buf, os.Stderr, fakeFS)
cmd := newCmdAddResource(fakeFS)
args := []string{resourceFileName}
err := cmd.RunE(cmd, args)
if err != nil {
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)
}
}
func TestAddResourceNoArgs(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdAddResource(buf, os.Stderr, fakeFS)
cmd := newCmdAddResource(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)

View File

@@ -32,10 +32,11 @@ import (
type buildOptions struct {
kustomizationPath string
outputPath string
}
// newCmdBuild creates a new build command.
func newCmdBuild(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
func newCmdBuild(out io.Writer, fs fs.FileSystem) *cobra.Command {
var o buildOptions
cmd := &cobra.Command{
@@ -43,14 +44,19 @@ func newCmdBuild(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
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/",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunBuild(out, errOut, fs)
return o.RunBuild(out, fs)
},
}
cmd.Flags().StringVarP(
&o.outputPath,
"output", "o", "",
"If specified, write the build output to this path.")
return cmd
}
@@ -68,8 +74,8 @@ func (o *buildOptions) Validate(args []string) error {
}
// RunBuild runs build command.
func (o *buildOptions) RunBuild(out, errOut io.Writer, fs fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
func (o *buildOptions) RunBuild(out io.Writer, fSys fs.FileSystem) error {
l := loader.NewFileLoader(fSys)
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
@@ -81,7 +87,7 @@ func (o *buildOptions) RunBuild(out, errOut io.Writer, fs fs.FileSystem) error {
return err
}
application, err := app.NewApplication(rootLoader)
application, err := app.NewApplication(rootLoader, fSys)
if err != nil {
return err
}
@@ -97,6 +103,10 @@ func (o *buildOptions) RunBuild(out, errOut 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, os.Stderr, 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

@@ -19,11 +19,9 @@ package commands
import (
"flag"
"io"
"os"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/version"
"github.com/spf13/cobra"
)
@@ -43,10 +41,11 @@ See https://github.com/kubernetes-sigs/kustomize
}
c.AddCommand(
newCmdBuild(stdOut, stdErr, fsys),
// TODO: Make consistent API for newCmd* functions.
newCmdBuild(stdOut, fsys),
newCmdDiff(stdOut, stdErr, fsys),
newCmdEdit(stdOut, stdErr, fsys),
version.NewCmdVersion(stdOut),
newCmdEdit(fsys),
newCmdVersion(stdOut),
)
c.PersistentFlags().AddGoFlagSet(flag.CommandLine)
@@ -57,7 +56,7 @@ See https://github.com/kubernetes-sigs/kustomize
}
// newCmdEdit returns an instance of 'edit' subcommand.
func newCmdEdit(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
func newCmdEdit(fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "edit",
Short: "Edits a kustomization file",
@@ -72,17 +71,17 @@ func newCmdEdit(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdAdd(stdOut, stdErr, fsys),
newCmdSet(stdOut, stdErr, fsys),
newCmdAdd(fsys),
newCmdSet(fsys),
)
return c
}
// newAddCommand returns an instance of 'add' subcommand.
func newCmdAdd(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
func newCmdAdd(fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "add",
Short: "Adds configmap/resource/patch to the kustomization file.",
Short: "Adds configmap/resource/patch/base to the kustomization file.",
Long: "",
Example: `
# Adds a configmap to the kustomization file
@@ -93,19 +92,24 @@ func newCmdAdd(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
# Adds a patch to the kustomization
kustomize edit add patch <filepath>
# Adds one or more base directories to the kustomization
kustomize edit add base <filepath>
kustomize edit add base <filepath1>,<filepath2>,<filepath3>
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdAddResource(stdOut, stdErr, fsys),
newCmdAddPatch(stdOut, stdErr, fsys),
newCmdAddConfigMap(stdErr, fsys),
newCmdAddResource(fsys),
newCmdAddPatch(fsys),
newCmdAddConfigMap(fsys),
newCmdAddBase(fsys),
)
return c
}
// newSetCommand returns an instance of 'set' subcommand.
func newCmdSet(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
func newCmdSet(fsys fs.FileSystem) *cobra.Command {
c := &cobra.Command{
Use: "set",
Short: "Sets the value of different fields in kustomization file.",
@@ -118,7 +122,8 @@ func newCmdSet(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
}
c.AddCommand(
newCmdSetNamePrefix(stdOut, stdErr, fsys),
newCmdSetNamePrefix(fsys),
newCmdSetImageTag(fsys),
)
return c
}

View File

@@ -18,18 +18,18 @@ package commands
import (
"fmt"
"io"
"github.com/spf13/cobra"
"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(errOut io.Writer, 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.",
@@ -45,45 +45,55 @@ func newCmdAddConfigMap(errOut io.Writer, 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.")
"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).")
@@ -91,27 +101,27 @@ func newCmdAddConfigMap(errOut io.Writer, 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]
@@ -123,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

@@ -24,12 +24,12 @@ import (
)
func TestNewAddConfigMapIsNotNil(t *testing.T) {
if newCmdAddConfigMap(nil, fs.MakeFakeFS()) == nil {
if newCmdAddConfigMap(fs.MakeFakeFS()) == nil {
t.Fatal("newCmdAddConfigMap shouldn't be nil")
}
}
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

@@ -42,7 +42,7 @@ func newCmdDiff(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
Use: "diff [path]",
Short: "diff between customized resources and uncustomized resources",
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(cmd, args)
err := o.Validate(args)
if err != nil {
return err
}
@@ -53,7 +53,7 @@ func newCmdDiff(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
}
// Validate validates diff command.
func (o *diffOptions) Validate(cmd *cobra.Command, args []string) error {
func (o *diffOptions) Validate(args []string) error {
if len(args) > 1 {
return errors.New("specify one path to " + constants.KustomizationFileName)
}
@@ -66,9 +66,9 @@ func (o *diffOptions) Validate(cmd *cobra.Command, args []string) error {
}
// RunDiff gets the differences between Application.MakeCustomizedResMap() and Application.MakeUncustomizedResMap().
func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
func (o *diffOptions) RunDiff(out, errOut io.Writer, fSys fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
l := loader.NewFileLoader(fSys)
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
@@ -80,7 +80,7 @@ func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
return err
}
application, err := app.NewApplication(rootLoader)
application, err := app.NewApplication(rootLoader, fSys)
if err != nil {
return err
}

View File

@@ -44,11 +44,12 @@ 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]*/`)
tempDir := regexp.QuoteMeta(filepath.Clean(os.TempDir()))
noopDir, _ := regexp.Compile(tempDir + `/noop-[0-9]*/`)
transformedDir, _ := regexp.Compile(tempDir + `/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()
fSys := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
@@ -74,7 +75,7 @@ func TestDiff(t *testing.T) {
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) {
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fs,
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fSys,
noopDir, transformedDir, timestamp)
})
}

View File

@@ -1,96 +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 commands
import (
"fmt"
"io"
"errors"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/spf13/cobra"
)
const kustomizationTemplate = `
namePrefix: some-prefix
# Labels to add to all objects and selectors.
# These labels would also be used to form the selector for apply --prune
# Named differently than “labels” to avoid confusion with metadata for this object
commonLabels:
app: helloworld
commonAnnotations:
note: This is an example annotation
resources: []
#- service.yaml
#- ../some-dir/
# There could also be configmaps in Base, which would make these overlays
configMapGenerator: []
# There could be secrets in Base, if just using a fork/rebase workflow
secretGenerator: []
`
type initOptions struct {
}
// NewCmdInit makes the init command.
func newCmdInit(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command {
var o initOptions
cmd := &cobra.Command{
Use: "init",
Short: "Creates a file called \"" + constants.KustomizationFileName + "\" in the current directory",
Long: "Creates a file called \"" +
constants.KustomizationFileName + "\" in the current directory with example values.",
Example: `init`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(cmd, args)
if err != nil {
return err
}
err = o.Complete(cmd, args)
if err != nil {
return err
}
return o.RunInit(out, errOut, fs)
},
}
return cmd
}
// Validate validates init command.
func (o *initOptions) Validate(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.New("The init command takes no arguments.")
}
return nil
}
// Complete completes init command.
func (o *initOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}
// RunInit writes a kustomization file.
func (o *initOptions) RunInit(out, errOut io.Writer, fs fs.FileSystem) error {
if _, err := fs.Stat(constants.KustomizationFileName); err == nil {
return fmt.Errorf("%q already exists", constants.KustomizationFileName)
}
return fs.WriteFile(constants.KustomizationFileName, []byte(kustomizationTemplate))
}

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,12 +34,30 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
type kustomizationFile struct {
path string
fsys fs.FileSystem
var (
kustomizationFields = []string{"resources", "bases", "namePrefix", "namespace", "crds", "commonLabels", "commonAnnotations", "patches", "configMapGenerator", "secretGenerator", "vars", "imageTags"}
recognizedFields = regexp.MustCompile("^(" + strings.Join(kustomizationFields, "|") + "):")
)
// 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 newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile, error) {
func (cf *commentedField) appendComment(comment []byte) {
cf.comment = append(cf.comment, comment...)
}
type kustomizationFile struct {
path string
fsys fs.FileSystem
originalFields []*commentedField
}
func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile, error) { // nolint
mf := &kustomizationFile{path: mPath, fsys: fsys}
err := mf.validate()
if err != nil {
@@ -45,25 +67,23 @@ 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
@@ -79,18 +99,21 @@ func (mf *kustomizationFile) read() (*types.Kustomization, error) {
if err != nil {
return nil, err
}
err = mf.parseCommentedFields(bytes)
if err != nil {
return nil, err
}
return &kustomization, err
}
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)
bytes, err := mf.marshal(kustomization)
if err != nil {
return err
}
return mf.fsys.WriteFile(mf.path, bytes)
}
@@ -102,3 +125,95 @@ func stringInSlice(str string, list []string) bool {
}
return false
}
func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
buffer := bytes.NewBuffer(content)
var comments [][]byte
var currentfield string
line, err := buffer.ReadBytes('\n')
for err == nil {
if isCommentOrBlankLine(line) {
comments = append(comments, line)
} else if recognizedFields.Match(line) {
fields := recognizedFields.FindSubmatch(line)
currentfield = string(fields[1])
mf.originalFields = append(mf.originalFields, &commentedField{field: currentfield, comment: bytes.Join(comments, []byte(``))})
comments = [][]byte{}
} else if len(comments) > 0 {
mf.originalFields[len(mf.originalFields)-1].appendComment(bytes.Join(comments, []byte(``)))
comments = [][]byte{}
}
line, err = buffer.ReadBytes('\n')
}
if err != io.EOF {
return err
}
return nil
}
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
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(`#`))
}
// 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,136 @@ 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
# 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
# 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))
}
}

View File

@@ -18,7 +18,6 @@ package commands
import (
"errors"
"io"
"github.com/spf13/cobra"
@@ -31,7 +30,7 @@ type setNamePrefixOptions struct {
}
// newCmdSetNamePrefix sets the value of the namePrefix field in the kustomization.
func newCmdSetNamePrefix(out, errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
func newCmdSetNamePrefix(fsys fs.FileSystem) *cobra.Command {
var o setNamePrefixOptions
cmd := &cobra.Command{
@@ -52,7 +51,7 @@ and overwrite the value with "acme-" if the field does exist.
if err != nil {
return err
}
return o.RunSetNamePrefix(out, errOut, fsys)
return o.RunSetNamePrefix(fsys)
},
}
return cmd
@@ -74,7 +73,7 @@ func (o *setNamePrefixOptions) Complete(cmd *cobra.Command, args []string) error
}
// RunSetNamePrefix runs setNamePrefix command (does real work).
func (o *setNamePrefixOptions) RunSetNamePrefix(out, errOut io.Writer, fsys fs.FileSystem) error {
func (o *setNamePrefixOptions) RunSetNamePrefix(fsys fs.FileSystem) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err

View File

@@ -17,8 +17,6 @@ limitations under the License.
package commands
import (
"bytes"
"os"
"testing"
"strings"
@@ -32,11 +30,10 @@ const (
)
func TestSetNamePrefixHappyPath(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetNamePrefix(buf, os.Stderr, fakeFS)
cmd := newCmdSetNamePrefix(fakeFS)
args := []string{goodPrefixValue}
err := cmd.RunE(cmd, args)
if err != nil {
@@ -52,10 +49,9 @@ func TestSetNamePrefixHappyPath(t *testing.T) {
}
func TestSetNamePrefixNoArgs(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetNamePrefix(buf, os.Stderr, fakeFS)
cmd := newCmdSetNamePrefix(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)

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

@@ -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,5 +6,6 @@ commonLabels:
commonAnnotations:
note: This is a test annotation
resources:
- deployment.yaml
- service.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

@@ -0,0 +1,6 @@
apiVersion: v1beta1
kind: Bee
metadata:
name: bee
spec:
action: fly

View File

@@ -0,0 +1,9 @@
crds:
- mycrd.json
resources:
- secret.yaml
- mykind.yaml
- bee.yaml
namePrefix: test-

View File

@@ -0,0 +1,170 @@
{
"github.com/example/pkg/apis/jingfang/v1beta1.Bee": {
"Schema": {
"description": "Bee",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
},
"spec": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec"
},
"status": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec",
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus",
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.BeeList": {
"Schema": {
"required": [
"items"
],
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"items": {
"type": "array",
"items": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.Bee"
}
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.Bee",
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.BeeObjectReference": {
"Schema": {
"properties": {
"name": {
"type": "string"
}
}
},
"Dependencies": []
},
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec": {
"Schema": {
"description": "BeeSpec defines the desired state of Bee"
},
"Dependencies": []
},
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus": {
"Schema": {
"description": "BeeStatus defines the observed state of Bee"
},
"Dependencies": []
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKind": {
"Schema": {
"description": "MyKind",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
},
"spec": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec"
},
"status": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec",
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus",
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindList": {
"Schema": {
"required": [
"items"
],
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"items": {
"type": "array",
"items": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKind"
}
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.MyKind",
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec": {
"Schema": {
"description": "MyKindSpec defines the desired state of MyKind",
"properties": {
"beeRef": {
"x-kubernetes-object-ref-api-version": "v1beta1",
"x-kubernetes-object-ref-kind": "Bee",
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeObjectReference"
},
"secretRef": {
"description": "If defined, we use this secret for configuring the MYSQL_ROOT_PASSWORD If it is not set we generate a secret dynamically",
"x-kubernetes-object-ref-api-version": "v1",
"x-kubernetes-object-ref-kind": "Secret",
"$ref": "k8s.io/api/core/v1.LocalObjectReference"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.BeeObjectReference",
"k8s.io/api/core/v1.LocalObjectReference"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus": {
"Schema": {
"description": "MyKindStatus defines the observed state of MyKind"
},
"Dependencies": []
}
}

View File

@@ -0,0 +1,9 @@
apiVersion: jingfang.example.com/v1beta1
kind: MyKind
metadata:
name: mykind
spec:
secretRef:
name: crdsecret
beeRef:
name: bee

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: Secret
metadata:
name: crdsecret
data:
PATH: YmJiYmJiYmIK

View File

@@ -0,0 +1,36 @@
diff -u -N /tmp/noop/jingfang.example.com_v1beta1_MyKind_mykind.yaml /tmp/transformed/jingfang.example.com_v1beta1_MyKind_mykind.yaml
--- /tmp/noop/jingfang.example.com_v1beta1_MyKind_mykind.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/jingfang.example.com_v1beta1_MyKind_mykind.yaml YYYY-MM-DD HH:MM:SS
@@ -1,9 +1,9 @@
apiVersion: jingfang.example.com/v1beta1
kind: MyKind
metadata:
- name: mykind
+ name: test-mykind
spec:
beeRef:
- name: bee
+ name: test-bee
secretRef:
- 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
@@ -1,6 +1,6 @@
apiVersion: v1beta1
kind: Bee
metadata:
- name: bee
+ name: test-bee
spec:
action: fly
diff -u -N /tmp/noop/v1_Secret_crdsecret.yaml /tmp/transformed/v1_Secret_crdsecret.yaml
--- /tmp/noop/v1_Secret_crdsecret.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Secret_crdsecret.yaml YYYY-MM-DD HH:MM:SS
@@ -3,4 +3,4 @@
PATH: YmJiYmJiYmIK
kind: Secret
metadata:
- name: crdsecret
+ name: test-crdsecret

View File

@@ -0,0 +1,23 @@
apiVersion: v1
data:
PATH: YmJiYmJiYmIK
kind: Secret
metadata:
name: test-crdsecret
---
apiVersion: v1beta1
kind: Bee
metadata:
name: test-bee
spec:
action: fly
---
apiVersion: jingfang.example.com/v1beta1
kind: MyKind
metadata:
name: test-mykind
spec:
beeRef:
name: test-bee
secretRef:
name: test-crdsecret

View File

@@ -0,0 +1,5 @@
description: name reference in CRDs
args: []
filename: testdata/testcase-crds/crd
expectedStdout: testdata/testcase-crds/expected.yaml
expectedDiff: testdata/testcase-crds/expected.diff

View File

@@ -2,8 +2,8 @@ namePrefix: staging-
commonLabels:
env: staging
patches:
- deployment-patch1.yaml
- deployment-patch2.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

@@ -1,5 +1,5 @@
description: simple
args: []
filename: ../examples/simple/instances/exampleinstance/
filename: ../examplelayout/simple/instances/exampleinstance/
expectedStdout: testdata/testcase-simple/expected.yaml
expectedDiff: testdata/testcase-simple/expected.diff

View File

@@ -41,6 +41,33 @@ diff -u -N /tmp/noop/apps_v1beta1_StatefulSet_cockroachdb.yaml /tmp/transformed/
terminationGracePeriodSeconds: 60
volumes:
- name: datadir
diff -u -N /tmp/noop/batch_v1beta1_CronJob_cronjob-example.yaml /tmp/transformed/batch_v1beta1_CronJob_cronjob-example.yaml
--- /tmp/noop/batch_v1beta1_CronJob_cronjob-example.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/batch_v1beta1_CronJob_cronjob-example.yaml YYYY-MM-DD HH:MM:SS
@@ -1,7 +1,7 @@
apiVersion: batch/v1beta1
kind: CronJob
metadata:
- name: base-cronjob-example
+ name: dev-base-cronjob-example
spec:
concurrencyPolicy: Forbid
jobTemplate:
@@ -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
+ value: dev-base-cockroachdb-public
image: cockroachdb/cockroach:v1.1.5
name: cronjob-example
schedule: '*/1 * * * *'
diff -u -N /tmp/noop/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml /tmp/transformed/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml
--- /tmp/noop/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml YYYY-MM-DD HH:MM:SS
@@ -115,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

@@ -1,4 +1,81 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- get
- watch
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dev-base-cockroachdb
subjects:
- kind: ServiceAccount
name: dev-base-cockroachdb
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dev-base-cockroachdb
subjects:
- kind: ServiceAccount
name: dev-base-cockroachdb
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:
@@ -38,13 +115,6 @@ spec:
selector:
app: cockroachdb
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
@@ -131,6 +201,28 @@ spec:
requests:
storage: 1Gi
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: dev-base-cronjob-example
spec:
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- command:
- echo
- dev-base-cockroachdb
- dev-base-test-config-map-b2g2dmd64b
env:
- name: CDB_PUBLIC_SVC
value: dev-base-cockroachdb-public
image: cockroachdb/cockroach:v1.1.5
name: cronjob-example
schedule: '*/1 * * * *'
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
@@ -142,64 +234,3 @@ spec:
selector:
matchLabels:
app: cockroachdb
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- get
- watch
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dev-base-cockroachdb
subjects:
- kind: ServiceAccount
name: dev-base-cockroachdb
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
labels:
app: cockroachdb
name: dev-base-cockroachdb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dev-base-cockroachdb
subjects:
- kind: ServiceAccount
name: dev-base-cockroachdb
namespace: default

View File

@@ -0,0 +1,21 @@
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-example
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: cronjob-example
image: cockroachdb/cockroach:v1.1.5
command:
- echo
- "$(CDB_STATEFULSET_NAME)"
- "$(TEST_CONFIG_MAP)"
env:
- name: CDB_PUBLIC_SVC
value: "$(CDB_PUBLIC_SVC)"

View File

@@ -1,6 +1,12 @@
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:
@@ -24,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

View File

@@ -1,5 +1,5 @@
/*
Copyright 2017 The Kubernetes Authors.
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.
@@ -14,6 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package temptest provides utilities for testing temp
// files/directories testing.
package temptest
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

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package version
package commands
import (
"fmt"
@@ -32,8 +32,8 @@ var (
buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
// Version represens kustomize version.
type Version struct {
// version returns the version of kustomize.
type version struct {
// KustomizeVersion is a kustomize binary version.
KustomizeVersion string `json:"kustomizeVersion"`
// GitCommit is a git commit
@@ -46,9 +46,9 @@ type Version struct {
GoArch string `json:"goArch"`
}
// GetVersion returns version.
func GetVersion() Version {
return Version{
// getVersion returns version.
func getVersion() version {
return version{
kustomizeVersion,
gitCommit,
buildDate,
@@ -58,18 +58,18 @@ func GetVersion() Version {
}
// Print prints version.
func (v Version) Print(w io.Writer) {
func (v version) Print(w io.Writer) {
fmt.Fprintf(w, "Version: %+v\n", v)
}
// NewCmdVersion makes version command.
func NewCmdVersion(w io.Writer) *cobra.Command {
func newCmdVersion(w io.Writer) *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Prints the kustomize version",
Example: `kustomize version`,
Run: func(cmd *cobra.Command, args []string) {
GetVersion().Print(w)
getVersion().Print(w)
},
}
}

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,217 @@
/*
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], 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"
@@ -93,41 +94,6 @@ func makeLiteralConfigMap(name string) *corev1.ConfigMap {
}
}
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")),
},
},
}
}
func TestConstructConfigMap(t *testing.T) {
type testCase struct {
description string
@@ -141,7 +107,7 @@ func TestConstructConfigMap(t *testing.T) {
input: types.ConfigMapArgs{
Name: "envConfigMap",
DataSources: types.DataSources{
EnvSource: "../examples/simple/instances/exampleinstance/configmap/app.env",
EnvSource: "../examplelayout/simple/instances/exampleinstance/configmap/app.env",
},
},
expected: makeEnvConfigMap("envConfigMap"),
@@ -151,7 +117,7 @@ func TestConstructConfigMap(t *testing.T) {
input: types.ConfigMapArgs{
Name: "fileConfigMap",
DataSources: types.DataSources{
FileSources: []string{"../examples/simple/instances/exampleinstance/configmap/app-init.ini"},
FileSources: []string{"../examplelayout/simple/instances/exampleinstance/configmap/app-init.ini"},
},
},
expected: makeFileConfigMap("fileConfigMap"),
@@ -168,8 +134,11 @@ func TestConstructConfigMap(t *testing.T) {
},
}
// 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 +148,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
@@ -234,10 +170,10 @@ func TestObjectConvertToUnstructured(t *testing.T) {
for _, tc := range testCases {
actual, err := objectToUnstructured(tc.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
t.Fatalf("%s: unexpected error: %v", tc.description, err)
}
if !reflect.DeepEqual(actual, tc.expected) {
t.Fatalf("%#v\ndoesn't match expected\n%#v\n", actual, tc.expected)
t.Fatalf("%s: %#v\ndoesn't match expected\n%#v\n", tc.description, actual, tc.expected)
}
}
}

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,134 @@
/*
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"
)
// 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)
pairs, err := f.keyValuesFromEnvFileCommand(args.EnvCommand)
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)
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) ([]kvPair, error) {
content, err := f.createSecretKey(cmd)
if err != nil {
return nil, err
}
return keyValuesFromLines(content)
}
func (f *SecretFactory) keyValuesFromCommands(sources map[string]string) ([]kvPair, error) {
var kvs []kvPair
for k, cmd := range sources {
content, err := f.createSecretKey(cmd)
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) ([]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(), 5*time.Second)
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
}
// 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/md5"
"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, nowFn func() metav1.Time) (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.Codec) (string, error) {
data, err := runtime.Encode(codec, obj)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", md5.Sum(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
}

42
pkg/crds/constants.go Normal file
View File

@@ -0,0 +1,42 @@
/*
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 crds
// Annotation is to mark a field as annotations.
// "x-kubernetes-annotation": ""
const Annotation = "x-kubernetes-annotation"
// LabelSelector is to mark a field as LabelSelector
// "x-kubernetes-label-selector": ""
const LabelSelector = "x-kubernetes-label-selector"
// Identity is to mark a field as Identity
// "x-kubernetes-identity": ""
const Identity = "x-kubernetes-identity"
// Version marks the type version of an object ref field
// "x-kubernetes-object-ref-api-version": <apiVersion name>
const Version = "x-kubernetes-object-ref-api-version"
// Kind marks the type name of an object ref field
// "x-kubernetes-object-ref-kind": <kind name>
const Kind = "x-kubernetes-object-ref-kind"
// NameKey marks the field key that refers to an object of an object ref field
// "x-kubernetes-object-ref-name-key": "name"
// default is "name"
const NameKey = "x-kubernetes-object-ref-name-key"

208
pkg/crds/crds.go Normal file
View File

@@ -0,0 +1,208 @@
/*
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 crds read in files for CRD schemas and parse annotations from it
package crds
import (
"encoding/json"
"reflect"
"strings"
"github.com/ghodss/yaml"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/transformers"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/common"
)
type pathConfigs struct {
labelPathConfig transformers.PathConfig
annotationPathConfig transformers.PathConfig
prefixPathConfig transformers.PathConfig
namereferencePathConfigs []transformers.ReferencePathConfig
}
func (p *pathConfigs) addLabelPathConfig(config transformers.PathConfig) {
if *(p.labelPathConfig.GroupVersionKind) == *config.GroupVersionKind {
p.labelPathConfig.Path = append(p.labelPathConfig.Path, config.Path...)
} else {
p.labelPathConfig = config
}
}
func (p *pathConfigs) addAnnotationPathConfig(config transformers.PathConfig) {
if *(p.annotationPathConfig.GroupVersionKind) == *config.GroupVersionKind {
p.annotationPathConfig.Path = append(p.labelPathConfig.Path, config.Path...)
} else {
p.annotationPathConfig = config
}
}
func (p *pathConfigs) addNamereferencePathConfig(config transformers.ReferencePathConfig) {
p.namereferencePathConfigs = transformers.MergeNameReferencePathConfigs(p.namereferencePathConfigs, config)
}
func (p *pathConfigs) addPrefixPathConfig(config transformers.PathConfig) {
if *(p.prefixPathConfig.GroupVersionKind) == *config.GroupVersionKind {
p.prefixPathConfig.Path = append(p.prefixPathConfig.Path, config.Path...)
} else {
p.prefixPathConfig = config
}
}
// RegisterCRDs parse CRD schemas from paths and update various pathConfigs
func RegisterCRDs(loader loader.Loader, paths []string) error {
var pathConfigs []pathConfigs
for _, path := range paths {
pathConfig, err := registerCRD(loader, path)
if err != nil {
return err
}
pathConfigs = append(pathConfigs, pathConfig...)
}
addPathConfigs(pathConfigs)
return nil
}
// register CRD from one path
func registerCRD(loader loader.Loader, path string) ([]pathConfigs, error) {
var result []pathConfigs
content, err := loader.Load(path)
if err != nil {
return result, err
}
var types map[string]common.OpenAPIDefinition
if content[0] == '{' {
err = json.Unmarshal(content, &types)
if err != nil {
return nil, err
}
} else {
err = yaml.Unmarshal(content, &types)
if err != nil {
return nil, err
}
}
crds := getCRDs(types)
for crd, gvk := range crds {
crdPathConfigs := pathConfigs{}
err = getCRDPathConfig(types, crd, crd, gvk, []string{}, &crdPathConfigs)
if err != nil {
return result, err
}
if !reflect.DeepEqual(crdPathConfigs, pathConfigs{}) {
result = append(result, crdPathConfigs)
}
}
return result, nil
}
// getCRDs get all CRD types
func getCRDs(types map[string]common.OpenAPIDefinition) map[string]schema.GroupVersionKind {
crds := map[string]schema.GroupVersionKind{}
for typename, t := range types {
properties := t.Schema.SchemaProps.Properties
_, foundKind := properties["kind"]
_, foundAPIVersion := properties["apiVersion"]
_, foundMetadata := properties["metadata"]
if foundKind && foundAPIVersion && foundMetadata {
// TODO: Get Group and Version for CRD from the openAPI definition once
// "x-kubernetes-group-version-kind" is available in CRD
kind := strings.Split(typename, ".")[len(strings.Split(typename, "."))-1]
crds[typename] = schema.GroupVersionKind{Kind: kind}
}
}
return crds
}
// getCRDPathConfig gets pathConfigs for one CRD recursively
func getCRDPathConfig(types map[string]common.OpenAPIDefinition, atype string, crd string, gvk schema.GroupVersionKind,
path []string, configs *pathConfigs) error {
if _, ok := types[crd]; !ok {
return nil
}
for propname, property := range types[atype].Schema.SchemaProps.Properties {
_, annotate := property.Extensions.GetString(Annotation)
if annotate {
configs.addAnnotationPathConfig(
transformers.PathConfig{
CreateIfNotPresent: false,
GroupVersionKind: &gvk,
Path: append(path, propname),
},
)
}
_, label := property.Extensions.GetString(LabelSelector)
if label {
configs.addLabelPathConfig(
transformers.PathConfig{
CreateIfNotPresent: false,
GroupVersionKind: &gvk,
Path: append(path, propname),
},
)
}
_, identity := property.Extensions.GetString(Identity)
if identity {
configs.addPrefixPathConfig(
transformers.PathConfig{
CreateIfNotPresent: false,
GroupVersionKind: &gvk,
Path: append(path, propname),
},
)
}
version, ok := property.Extensions.GetString(Version)
if ok {
kind, ok := property.Extensions.GetString(Kind)
if ok {
nameKey, ok := property.Extensions.GetString(NameKey)
if !ok {
nameKey = "name"
}
configs.addNamereferencePathConfig(transformers.NewReferencePathConfig(
schema.GroupVersionKind{Kind: kind, Version: version},
[]transformers.PathConfig{
{CreateIfNotPresent: false,
GroupVersionKind: &gvk,
Path: append(path, propname, nameKey),
}}))
}
}
if property.Ref.GetURL() != nil {
getCRDPathConfig(types, property.Ref.String(), crd, gvk, append(path, propname), configs)
}
}
return nil
}
// addPathConfigs add extra path configs to the default ones
func addPathConfigs(p []pathConfigs) {
for _, pc := range p {
transformers.AddLabelsPathConfigs(pc.labelPathConfig)
transformers.AddAnnotationsPathConfigs(pc.annotationPathConfig)
transformers.AddNameReferencePathConfigs(pc.namereferencePathConfigs)
transformers.AddPrefixPathConfigs(pc.prefixPathConfig)
}
}

198
pkg/crds/crds_test.go Normal file
View File

@@ -0,0 +1,198 @@
/*
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 crds
import (
"reflect"
"sort"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/transformers"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const (
crdContent = `
{
"github.com/example/pkg/apis/jingfang/v1beta1.Bee": {
"Schema": {
"description": "Bee",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert
recognized schemas to the latest internal value, and may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer
this from the endpoint the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
},
"spec": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec"
},
"status": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec",
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus",
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.BeeSpec": {
"Schema": {
"description": "BeeSpec defines the desired state of Bee"
},
"Dependencies": []
},
"github.com/example/pkg/apis/jingfang/v1beta1.BeeStatus": {
"Schema": {
"description": "BeeStatus defines the observed state of Bee"
},
"Dependencies": []
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKind": {
"Schema": {
"description": "MyKind",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to. Cannot be updated.
In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"$ref": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
},
"spec": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec"
},
"status": {
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec",
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus",
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindSpec": {
"Schema": {
"description": "MyKindSpec defines the desired state of MyKind",
"properties": {
"beeRef": {
"x-kubernetes-object-ref-api-version": "v1beta1",
"x-kubernetes-object-ref-kind": "Bee",
"$ref": "github.com/example/pkg/apis/jingfang/v1beta1.Bee"
},
"secretRef": {
"description": "If defined, we use this secret for configuring the MYSQL_ROOT_PASSWORD
If it is not set we generate a secret dynamically",
"x-kubernetes-object-ref-api-version": "v1",
"x-kubernetes-object-ref-kind": "Secret",
"$ref": "k8s.io/api/core/v1.LocalObjectReference"
}
}
},
"Dependencies": [
"github.com/example/pkg/apis/jingfang/v1beta1.Bee",
"k8s.io/api/core/v1.LocalObjectReference"
]
},
"github.com/example/pkg/apis/jingfang/v1beta1.MyKindStatus": {
"Schema": {
"description": "MyKindStatus defines the observed state of MyKind"
},
"Dependencies": []
}
}
`
)
func makeLoader(t *testing.T) loader.Loader {
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/crd.json", []byte(crdContent))
if err != nil {
t.Fatalf("Failed to setup fake ldr.")
}
return ldr
}
func TestRegisterCRD(t *testing.T) {
refpathconfigs := []transformers.ReferencePathConfig{
transformers.NewReferencePathConfig(
schema.GroupVersionKind{Kind: "Bee", Version: "v1beta1"},
[]transformers.PathConfig{
{
CreateIfNotPresent: false,
GroupVersionKind: &schema.GroupVersionKind{Kind: "MyKind"},
Path: []string{"spec", "beeRef", "name"},
},
},
),
transformers.NewReferencePathConfig(
schema.GroupVersionKind{Kind: "Secret", Version: "v1"},
[]transformers.PathConfig{
{
CreateIfNotPresent: false,
GroupVersionKind: &schema.GroupVersionKind{Kind: "MyKind"},
Path: []string{"spec", "secretRef", "name"},
},
},
),
}
sort.Slice(refpathconfigs, func(i, j int) bool {
return refpathconfigs[i].GVK() < refpathconfigs[j].GVK()
})
expected := []pathConfigs{
{
namereferencePathConfigs: refpathconfigs,
},
}
ldr := makeLoader(t)
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()
})
if !reflect.DeepEqual(pathconfig, expected) {
t.Fatalf("expected\n %v\n but got\n %v\n", expected, pathconfig)
}
}

View File

@@ -20,7 +20,7 @@ import (
"io"
"os"
"k8s.io/utils/exec"
"github.com/kubernetes-sigs/kustomize/pkg/exec"
)
// program wraps the system diff program.
@@ -39,11 +39,10 @@ func newProgram(out, errOut io.Writer) *program {
}
func (d *program) makeCommand(args ...string) exec.Cmd {
diff := ""
diff := "diff"
if envDiff := os.Getenv("KUBERNETES_EXTERNAL_DIFF"); envDiff != "" {
diff = envDiff
} else {
diff = "diff"
args = append([]string{"-u", "-N"}, args...)
}
cmd := exec.New().Command(diff, args...)

View File

@@ -26,18 +26,22 @@ import (
)
// RunDiff runs system diff program to compare two Maps.
func RunDiff(raw, transformed resmap.ResMap, out, errOut io.Writer) error {
func RunDiff(raw, transformed resmap.ResMap, out, errOut io.Writer) (err error) {
transformedDir, err := writeYamlToNewDir(transformed, "transformed")
if err != nil {
return err
}
defer transformedDir.delete()
defer func() {
err = transformedDir.delete()
}()
noopDir, err := writeYamlToNewDir(raw, "noop")
if err != nil {
return err
}
defer noopDir.delete()
defer func() {
err = noopDir.delete()
}()
return newProgram(out, errOut).run(noopDir.name(), transformedDir.name())
}
@@ -52,11 +56,11 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
}
for id, obj := range in {
f, err := dir.newFile(id.String())
f, err := dir.newFile(id.GvknString())
if err != nil {
return nil, err
}
err = print(obj, f)
err = write(obj, f)
f.Close()
if err != nil {
return nil, err
@@ -65,8 +69,8 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
return dir, nil
}
// Print the object as YAML.
func print(obj interface{}, w io.Writer) error {
// Write the object as YAML.
func write(obj interface{}, w io.Writer) error {
if obj == nil {
return nil
}

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

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