Compare commits

..

119 Commits

Author SHA1 Message Date
Jeff Regan
633c43a672 Merge pull request #292 from babiel/pdb-matchlabels
Disable creation of PDB matchLabels
2018-08-24 09:27:41 -07:00
Maximilian Gaß
0833693372 Disable creation of PDB matchLabels
Allow for using matchExpressions on its own
2018-08-24 13:51:05 +02:00
Jeff Regan
77c07ba96e Merge pull request #290 from philoserf/patch-1
trivial: Change dep constraint to an override
2018-08-23 17:07:44 -07:00
Mark Ayers
6847bb7924 trivial: Change dep constraint to an override
fixes #289
2018-08-23 15:58:27 -07:00
k8s-ci-robot
f0deaf707d Merge pull request #287 from emosbaugh/fix-gopath-isrepourl
Fix loader loader when run from gopath
2018-08-23 15:08:25 -07:00
Ethan Mosbaugh
e113944027 fix loader loader when run from gopath 2018-08-23 14:54:58 -07:00
k8s-ci-robot
1cf9131ae2 Merge pull request #285 from monopole/declutter
Move some docs to docs dir (declutter the top).
2018-08-23 13:33:06 -07:00
Jeffrey Regan
da142a8e97 Move some docs to docs dir (declutter the top). 2018-08-23 11:33:12 -07:00
Jeff Regan
6c81e3b95f Merge pull request #284 from Liujingfang1/vendor
Add missing files when running dep ensure
2018-08-23 11:27:47 -07:00
Jingfang Liu
a0089a2521 Add missing files when running dep ensure 2018-08-23 11:07:42 -07:00
k8s-ci-robot
11768f6232 Merge pull request #280 from monopole/fixntis
Fix some Go nits.
2018-08-23 10:37:58 -07:00
Jeff Regan
675c17737f Merge pull request #279 from Liujingfang1/quote
fix the double quotation problem in ConfigMapGenerator
2018-08-23 10:35:34 -07:00
Jeffrey Regan
735a93d000 Fix some Go nits. 2018-08-23 10:30:32 -07:00
Jeff Regan
67d2c2ed4a Merge pull request #281 from monopole/addVarCheck
Add varcheck to presubmit.
2018-08-23 10:25:32 -07:00
Jeffrey Regan
f931e15653 Add varcheck to presubmit. 2018-08-23 10:17:37 -07:00
Jingfang Liu
34169174a8 fix the double quotation problem in ConfigMapGenerator 2018-08-23 09:36:50 -07:00
Jeff Regan
ebf33964c7 Merge pull request #276 from kubernetes-sigs/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2018-08-22 20:05:47 -07:00
Jeff Regan
38a5e12d66 Create CODE_OF_CONDUCT.md 2018-08-22 20:05:19 -07:00
k8s-ci-robot
04ab218fa0 Merge pull request #275 from monopole/deletediff
Delete diff command and code it uses.
2018-08-22 18:38:39 -07:00
Jeffrey Regan
950c353f90 Delete diff command and code it uses. 2018-08-22 17:18:39 -07:00
Jeff Regan
aff09b1108 Merge pull request #271 from Liujingfang1/cleanup
cleanup after handling remote bases
2018-08-22 17:14:11 -07:00
Jingfang Liu
6da691f874 cleanup after handling remote bases 2018-08-22 13:22:45 -07:00
Jeff Regan
22c99aa535 Merge pull request #274 from monopole/docsreadme
Tweak docs readme
2018-08-22 12:21:32 -07:00
Jeffrey Regan
5fa209acfa Tweak docs readme 2018-08-22 12:20:13 -07:00
Jeff Regan
d72879e109 Merge pull request #147 from guineveresaenger/labels-and-annotations
Edit add label/annotation
2018-08-22 10:33:00 -07:00
Jeff Regan
337f3631ff Merge pull request #272 from Liujingfang1/docs
Add docs README.md
2018-08-22 10:22:00 -07:00
guineveresaenger
b3993dc874 Adds starter validation framework for semantic validation of inputs. 2018-08-22 18:20:51 +02:00
guineveresaenger
11c04dd6c4 Removes semantic validation from addmetadata.go and tests.
Due to moving some input parsing to the Validate method, it was renamed to reflect this additional purpose.
Tests were removed where appropriate.
2018-08-22 18:15:34 +02:00
Jingfang Liu
b29e449d4a Add docs README.md 2018-08-22 09:05:54 -07:00
Jingfang Liu
430f2f84fb Merge pull request #270 from Liujingfang1/docs
fix bug in the example download links
2018-08-21 12:08:32 -07:00
Jingfang Liu
52c6b5755b fix bug in the example download links 2018-08-21 12:04:53 -07:00
k8s-ci-robot
958bc63293 Merge pull request #269 from Liujingfang1/pvc
Add namereference of PersistentVolume in PersistentVolumeClain
2018-08-21 09:48:33 -07:00
Jingfang Liu
94ed0fe515 Add namereference of PersistentVolume in PersistentVolumeClain 2018-08-21 09:37:25 -07:00
Jeff Regan
bb8233ceff Merge pull request #265 from Liujingfang1/metadata
add typemeta to kustomization
2018-08-20 13:59:43 -07:00
Jingfang Liu
6221bed190 add typemeta to kustomization 2018-08-20 13:47:33 -07:00
k8s-ci-robot
1ffeb181e7 Merge pull request #264 from Liujingfang1/docs
Add example for kustomize build {url} and remote bases
2018-08-20 12:19:00 -07:00
Jingfang Liu
759ba1cbf4 Add example for kustomize build {url} 2018-08-20 09:52:32 -07:00
k8s-ci-robot
12f2c41273 Merge pull request #266 from ahmetb/patch-1
add homebrew package to INSTALL.md
2018-08-17 10:48:01 -07:00
Ahmet Alp Balkan
2174741376 add homebrew package to INSTALL.md 2018-08-17 07:50:28 -07:00
guineveresaenger
31dd8fc5b1 Restructured tests 2018-08-16 13:32:42 -07:00
guineveresaenger
77f4811779 Tests test Validate function 2018-08-16 02:36:11 -07:00
Jeff Regan
7050a45134 Merge pull request #262 from Liujingfang1/repoUrl3
Add all dependency of go-getter
2018-08-15 15:47:35 -07:00
k8s-ci-robot
3b64f1e102 Merge pull request #260 from Liujingfang1/repoUrl2
Add kustomize build {repoUrl}
2018-08-15 15:45:00 -07:00
Jingfang Liu
6c4c79e2cc Merge pull request #263 from Liujingfang1/gc
Add garbage collection KEP link
2018-08-15 15:22:21 -07:00
Jingfang Liu
3975ebc21a Add garbage collection KEP link 2018-08-15 15:19:18 -07:00
Jingfang Liu
ec95e5f97e Add all dependency of go-getter 2018-08-15 11:37:03 -07:00
Jingfang Liu
72b1a4bc5c combine fileLoader.New and NewLoader into one function: NewLoader 2018-08-15 11:04:31 -07:00
k8s-ci-robot
16447efca3 Merge pull request #255 from sethpollack/secret
add docs for envCommand
2018-08-15 09:35:02 -07:00
guineveresaenger
524d593c5c Separate functions for RunnAddLabel and RunAddAnnotation 2018-08-15 03:51:56 -07:00
guineveresaenger
3b644474c4 Parse data into string map for easy access in RunAddMetadata 2018-08-15 03:13:03 -07:00
Seth Pollack
42e6ced2b0 add docs 2018-08-14 20:55:26 -04:00
Jingfang Liu
f018370628 Add kustomize build {repoUrl} 2018-08-14 16:10:51 -07:00
Jeff Regan
c9a8bc1121 Merge pull request #256 from ivan4th/command-timeout-1
Add timeoutSeconds to secretArgs
2018-08-14 14:54:50 -07:00
k8s-ci-robot
8c7cbb12dd Merge pull request #257 from Liujingfang1/repoUrl2
Add dependency: github.com/hashicorp/go-getter
2018-08-14 14:49:29 -07:00
Jingfang Liu
b02f7775c5 manually add dependency on go-getter 2018-08-14 14:37:31 -07:00
Ivan Shvedunov
f9a0e671b7 Add timeoutSeconds to secretArgs 2018-08-14 22:55:59 +03:00
Jeff Regan
70fb22cad6 Merge pull request #250 from ivan4th/fix-resource-load-crash
Don't crash on resource load errors
2018-08-14 12:53:24 -07:00
Ivan Shvedunov
2ae00db6a9 Don't crash on resource load errors 2018-08-14 22:14:12 +03:00
k8s-ci-robot
f9577ab540 Merge pull request #247 from ryane/setnamespace
add support for `kustomize edit set namespace`
2018-08-13 11:17:06 -07:00
guineveresaenger
6a2786a5c4 Remove Complete function and references 2018-08-13 07:49:51 -07:00
guineveresaenger
924aa6fb29 Use iota declaration for constants and implements string method for KindOfAdd metadata 2018-08-13 07:46:06 -07:00
ryane
e2cd44f9d8 add support for kustomize edit set namespace
fixes #246
2018-08-10 22:42:50 -04:00
Jeff Regan
017c4ae0aa Merge pull request #245 from Liujingfang1/output
Add -o flag to kustomize build
2018-08-09 16:49:25 -07:00
Jingfang Liu
7b2baad390 Add -o flag to kustomize build 2018-08-09 13:15:39 -07:00
Jeff Regan
bc2d69f4f9 Merge pull request #241 from sethpollack/secret
add env sources to secrets
2018-08-09 13:05:21 -07:00
Jeff Regan
e913a71fad Merge pull request #244 from Liujingfang1/deprecation
Add deprecation message for namePrefix behavior change
2018-08-09 13:04:36 -07:00
Seth Pollack
7406dda074 fixes 2018-08-09 14:45:56 -04:00
Jingfang Liu
0b4df3d414 Add deprecation message for namePrefix behavior change 2018-08-09 11:25:37 -07:00
Jeff Regan
7d38916d63 Merge pull request #243 from monopole/updateDeps
Automated update of Gopkg.lock via dep ensure
2018-08-09 11:08:47 -07:00
Jeffrey Regan
79d1abe573 dep ensure run 2018-08-09 10:54:29 -07:00
Jeff Regan
9563052094 Merge pull request #233 from Liujingfang1/glob2
preserve order and comments in edit
2018-08-09 09:49:30 -07:00
Seth Pollack
f881c19bb6 add env sources to secrets 2018-08-09 09:17:53 -04:00
Jingfang Liu
8d7b5f82c4 preserve order and comments in edit 2018-08-08 15:03:16 -07:00
Jingfang Liu
7554406c61 Merge pull request #240 from kubernetes-sigs/revert-239-namespace2
Revert "Skip adding nameprefix to namespace"
2018-08-08 13:41:38 -07:00
Jingfang Liu
cf17050170 Revert "Skip adding nameprefix to namespace" 2018-08-08 13:39:01 -07:00
k8s-ci-robot
3857a67701 Merge pull request #239 from Liujingfang1/namespace2
Skip adding nameprefix to namespace
2018-08-08 11:56:15 -07:00
Jingfang Liu
10665c6fc9 Skip adding nameprefix to namespace 2018-08-08 10:02:42 -07:00
k8s-ci-robot
e0a09f4755 Merge pull request #237 from Liujingfang1/ingress
add namepreference for secret in ingress annotation
2018-08-08 09:49:06 -07:00
Jingfang Liu
31c6a55747 add namepreference for secret in ingress annotation 2018-08-07 13:26:39 -07:00
Jeff Regan
8332a70d19 Merge pull request #231 from bendory/master
Container Builder has been renamed Cloud Build
2018-08-03 10:29:23 -07:00
David Bendory
7fe2338acd Container Builder has been renamed Cloud Build 2018-08-03 13:22:49 -04:00
k8s-ci-robot
43d4dbc07a Merge pull request #228 from Liujingfang1/glob2
Change the order of validate and  expandFileSource in add configmap
2018-08-02 16:26:27 -07:00
Jingfang Liu
f0cf4579d2 Change the order of validate and expandFileSource in add configmap subcommand 2018-08-02 11:39:27 -07:00
k8s-ci-robot
68ba37f139 Merge pull request #226 from Liujingfang1/glob2
Add glob support in subcommands `add patch` and `add configmap`
2018-08-02 11:19:27 -07:00
Jingfang Liu
bf73633cda Add glob support in subcommands add patch and add configmap 2018-08-02 11:01:20 -07:00
Jeff Regan
55f8828ba1 Merge pull request #222 from Liujingfang1/glob
Add glob support in edit add resource
2018-08-01 15:51:45 -07:00
Jeff Regan
0e1307dccf Merge pull request #224 from Liujingfang1/imagetag
Use regexp in set imagetag
2018-08-01 15:50:50 -07:00
Jingfang Liu
4471b75912 Use regexp in set imagetag 2018-08-01 11:58:21 -07:00
k8s-ci-robot
75c6204337 Merge pull request #225 from Liujingfang1/pathconfig
Add ingress annotations to the namereference path config
2018-08-01 11:52:40 -07:00
Jingfang Liu
1b7171ac9e Add glob support in edit add resource 2018-08-01 11:43:28 -07:00
Jingfang Liu
5193d6b4a8 Add ingress annotations to the namereference path config 2018-08-01 10:47:01 -07:00
Jeff Regan
6a834b6262 Merge pull request #223 from monopole/noFlags
More description of eschewed features
2018-07-31 11:49:56 -07:00
Jeffrey Regan
083d3cbb65 More description of eschewed features 2018-07-31 11:48:36 -07:00
k8s-ci-robot
e68411b71e Merge pull request #220 from monopole/noglobbing
Eschew globbing doc
2018-07-31 10:04:47 -07:00
Jeff Regan
664774576c Merge pull request #219 from Liujingfang1/glob
remove glob support from kustomization.yaml
2018-07-30 17:03:53 -07:00
Jeffrey Regan
37e97084f9 Eschew globbing doc 2018-07-30 16:57:16 -07:00
Jingfang Liu
de4d8b7dfa remove glob support from kustomization.yaml 2018-07-30 16:28:40 -07:00
k8s-ci-robot
7f97108686 Merge pull request #216 from Liujingfang1/namespace
Add multibases example with different namespace
2018-07-30 15:54:46 -07:00
Jingfang Liu
71f069cf95 Add multibases example with different namespace 2018-07-30 15:21:53 -07:00
k8s-ci-robot
3dbe732cb5 Merge pull request #215 from monopole/eschewed
Enumerate eschewed features in a document.
2018-07-30 15:01:40 -07:00
Jeff Regan
e5aea4423b Merge pull request #214 from Liujingfang1/namespace
add namespace in ResId
2018-07-30 15:00:10 -07:00
Jeff Regan
100f05260e Merge pull request #209 from Liujingfang1/yaml
ignore the empty YAML object
2018-07-30 14:57:15 -07:00
Jeffrey Regan
02f9329747 Enumerate eschewed features in docs 2018-07-30 14:56:20 -07:00
Jingfang Liu
b6abd7600c add namespace in ResId 2018-07-30 14:04:35 -07:00
Jingfang Liu
2e7093e67f ignore the empty YAML object 2018-07-30 12:58:11 -07:00
k8s-ci-robot
3b3a272d27 Merge pull request #213 from Liujingfang1/imagetags
use regexp to determine if the image matched in imagetag transformer
2018-07-30 11:58:56 -07:00
Jingfang Liu
36115a7fa3 use regexp to determin if the image matched in imagetag transformer 2018-07-30 11:09:32 -07:00
k8s-ci-robot
4d9d54e2c7 Merge pull request #204 from Liujingfang1/diamond
Add support for using common base
2018-07-27 14:00:57 -07:00
Jingfang Liu
88aec95628 remove commented code
update multibases/README.md
2018-07-27 13:45:49 -07:00
Jingfang Liu
e30401489d Add example for multibases 2018-07-27 10:43:16 -07:00
Jingfang Liu
58bc4b14a2 Add support for using common base 2018-07-27 10:16:44 -07:00
Jeff Regan
2824c28e08 Merge pull request #203 from mortent/BetterSecretGenErrorMessage
More information in error message when secret gen fails
2018-07-26 15:45:40 -07:00
Morten Torkildsen
d7cbb95d9c More information in error message when secret gen fails 2018-07-26 12:50:07 -07:00
k8s-ci-robot
e771ec1169 Merge pull request #201 from monopole/meh
Combine loaderImpl and fileLoader.
2018-07-26 10:09:56 -07:00
Jeffrey Regan
9e5374e725 Combine loaderImpl and fileLoader. 2018-07-25 17:23:04 -07:00
guineveresaenger
187415430f Removed individual files in favor of combined metadata file 2018-07-18 17:09:41 -07:00
guineveresaenger
afac2fb46a Uses single file for both addLabel and addAnnotation commands, as the code is nearly identical. Tests included. 2018-07-18 17:09:41 -07:00
guineveresaenger
20fd433f75 Add tests 2018-07-18 17:09:41 -07:00
guineveresaenger
1e3824057b Implements labels and annotations as subcommands of edit 2018-07-18 17:09:41 -07:00
3249 changed files with 2004851 additions and 870 deletions

View File

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

230
Gopkg.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

4
docs/CODE_OF_CONDUCT.md Normal file
View File

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

View File

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

23
docs/README.md Normal file
View File

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

80
docs/eschewedFeatures.md Normal file
View File

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

View File

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

View File

@@ -83,15 +83,29 @@ secretGenerator:
tls.key: "cat secret/tls.key"
type: "kubernetes.io/tls"
- name: downloaded_secret
# timeoutSeconds specifies the number of seconds to
# wait for the commands below. It defaults to 5 seconds.
timeoutSeconds: 30
commands:
username: "curl -s https://path/to/secrets/username.yaml"
password: "curl -s https://path/to/secrets/password.yaml"
type: Opaque
- name: env_file_secret
# envCommand is similar to command but outputs lines of key=val pairs
# i.e. a Docker .env file or a .ini file.
# you can only specify one envCommand per secret.
envCommand: printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"
type: Opaque
# Each entry in this list should resolve to a directory
# containing a kustomization file, else the
# customization fails.
#
# The entry could be a relative path pointing to a local directory
# or a url pointing to a directory in a remote repo.
# The url should follow hashicorp/go-getter URL format
# https://github.com/hashicorp/go-getter#url-format
#
# The presence of this field means this file (the file
# you a reading) is an _overlay_ that further
# customizes information coming from these _bases_.
@@ -102,6 +116,9 @@ secretGenerator:
# etc. that differ from the common base).
bases:
- ../../base
- github.com/kubernetes-sigs/kustomize//examples/multibases?ref=v1.0.6
- github.com/Liujingfang1/mysql
- github.com/Liujingfang1/kustomize//examples/helloWorld?ref=test-branch
# Each entry in this list should resolve to
# a partial or complete resource definition file.

View File

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

View File

@@ -36,3 +36,7 @@ go get github.com/kubernetes-sigs/kustomize
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
* [image tags](imageTags.md) - Updating image tags without applying a patch.
* [multibases](multibases/README.md) - Composing three variants (dev, staging, production) with a common base.
* [remote target](remoteBuild.md) - Building a kustomization from a github URL

View File

@@ -109,8 +109,8 @@ configuration is to
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.
referenced by any other resource, is eventually [garbage
collected](https://github.com/kubernetes-sigs/kustomize/issues/242).
### How this works with kustomize

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

58
examples/remoteBuild.md Normal file
View File

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

View File

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

View File

@@ -86,20 +86,6 @@ func (a *Application) MakeCustomizedResMap() (resmap.ResMap, error) {
return a.resolveRefsToGeneratedResources(m)
}
// MakeUncustomizedResMap purports to create a ResMap without customization.
// The Resources in the returned ResMap include all resources mentioned
// in the kustomization file and transitively reachable via its Bases,
// and all generated secrets and configMaps.
// Meant for use in generating a diff against customized resources.
// TODO: See https://github.com/kubernetes-sigs/kustomize/issues/85
func (a *Application) MakeUncustomizedResMap() (resmap.ResMap, error) {
m, err := a.loadResMapFromBasesAndResources()
if err != nil {
return nil, err
}
return a.resolveRefsToGeneratedResources(m)
}
// resolveRefsToGeneratedResources fixes all name references.
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
err := transformers.NewNameHashTransformer().Transform(m)
@@ -138,7 +124,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
if err != nil {
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
}
err = crds.RegisterCRDs(a.ldr, a.kustomization.CRDs)
err = crds.RegisterCRDs(a.ldr, a.kustomization.Crds)
if err != nil {
errs.Append(errors.Wrap(err, "RegisterCRDs"))
}
@@ -226,6 +212,7 @@ func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.Kustomizat
errs.Append(errors.Wrap(err, "SemiResources"))
continue
}
ldr.Cleanup()
list = append(list, resMap)
}
result, err := resmap.MergeWithoutOverride(list...)
@@ -292,7 +279,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
}
for _, v := range vars {
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
if r, found := m[id]; found {
if r, found := m.DemandOneMatchForId(id); found {
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
if err != nil {
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
@@ -322,6 +309,7 @@ func (a *Application) getAllVars() ([]types.Var, error) {
errs.Append(err)
continue
}
b.ldr.Cleanup()
result = append(result, vars...)
}
for _, v := range a.kustomization.Vars {

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -18,6 +18,8 @@ package commands
import (
"fmt"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
// cMapFlagsAndArgs encapsulates the options for add configmap commands.
@@ -48,3 +50,12 @@ func (a *cMapFlagsAndArgs) 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,7 +17,10 @@ limitations under the License.
package commands
import (
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func TestDataConfigValidation_NoName(t *testing.T) {
@@ -81,3 +84,21 @@ func TestDataConfigValidation_Flags(t *testing.T) {
}
}
}
func TestExpandFileSource(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/config1")
fakeFS.Create("dir/config2")
fakeFS.Create("dir/reademe")
config := cMapFlagsAndArgs{
FileSources: []string{"dir/config*"},
}
config.ExpandFileSource(fakeFS)
expected := []string{
"dir/config1",
"dir/config2",
}
if !reflect.DeepEqual(config.FileSources, expected) {
t.Fatalf("FileSources is not correctly expanded: %v", config.FileSources)
}
}

View File

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

View File

@@ -45,7 +45,12 @@ func newCmdAddConfigMap(fSys fs.FileSystem) *cobra.Command {
kustomize edit add configmap my-configmap --from-env-file=env/path.env
`,
RunE: func(_ *cobra.Command, args []string) error {
err := flagsAndArgs.Validate(args)
err := flagsAndArgs.ExpandFileSource(fSys)
if err != nil {
return err
}
err = flagsAndArgs.Validate(args)
if err != nil {
return err
}
@@ -65,7 +70,7 @@ func newCmdAddConfigMap(fSys fs.FileSystem) *cobra.Command {
err = addConfigMap(
kustomization, flagsAndArgs,
configmapandsecret.NewConfigMapFactory(
fSys, loader.NewLoader(loader.NewFileLoader(fSys))))
fSys, loader.NewFileLoader(fSys)))
if err != nil {
return err
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,20 +18,22 @@ package commands
import (
"errors"
"strings"
"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"
"sort"
)
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
@@ -67,15 +69,15 @@ and overwrite the previous newTag if the image name exists.
// Validate validates setImageTag command.
func (o *setImageTagOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("No image and newTag specified.")
return errors.New("no image and newTag specified")
}
o.imageTagMap = make(map[string]string)
for _, arg := range args {
imagetag := strings.Split(arg, ":")
if len(imagetag) != 2 {
return errors.New("Invalid format of imagetag, must specify it as <image>:<newtag>")
imagetag := pattern.FindStringSubmatch(arg)
if len(imagetag) != 3 {
return errors.New("invalid format of imagetag, must specify it as <image>:<newtag>")
}
o.imageTagMap[imagetag[0]] = imagetag[1]
o.imageTagMap[imagetag[1]] = imagetag[2]
}
return nil
}

View File

@@ -29,7 +29,7 @@ func TestSetImageTagsHappyPath(t *testing.T) {
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag2"}
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)
@@ -44,6 +44,8 @@ imageTags:
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")
@@ -91,7 +93,7 @@ func TestSetImageTagsNoArgs(t *testing.T) {
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "No image and newTag specified." {
if err.Error() != "no image and newTag specified" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

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

View File

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

View File

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

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

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

View File

@@ -212,6 +212,5 @@ func parseLiteralSource(source string) (keyName, value string, err error) {
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], items[1], nil
return items[0], strings.Trim(items[1], "\"'"), nil
}

View File

@@ -90,6 +90,8 @@ func makeLiteralConfigMap(name string) *corev1.ConfigMap {
Data: map[string]string{
"a": "x",
"b": "y",
"c": "Hello World",
"d": "true",
},
}
}
@@ -127,7 +129,7 @@ func TestConstructConfigMap(t *testing.T) {
input: types.ConfigMapArgs{
Name: "literalConfigMap",
DataSources: types.DataSources{
LiteralSources: []string{"a=x", "b=y"},
LiteralSources: []string{"a=x", "b=y", "c=\"Hello World\"", "d='true'"},
},
},
expected: makeLiteralConfigMap("literalConfigMap"),
@@ -136,8 +138,7 @@ func TestConstructConfigMap(t *testing.T) {
// TODO: all tests should use a FakeFs
fSys := fs.MakeRealFS()
f := NewConfigMapFactory(fSys,
loader.NewLoader(loader.NewFileLoader(fSys)))
f := NewConfigMapFactory(fSys, loader.NewFileLoader(fSys))
for _, tc := range testCases {
cm, err := f.MakeConfigMap(&tc.input)
if err != nil {

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,8 @@ package fs
import (
"fmt"
"path/filepath"
"sort"
)
var _ FileSystem = &FakeFS{}
@@ -60,6 +62,18 @@ func (fs *FakeFS) Exists(name string) bool {
return found
}
// Glob returns the list of matching files
func (fs *FakeFS) Glob(pattern string) ([]string, error) {
var result []string
for p := range fs.m {
if fs.pathMatch(p, pattern) {
result = append(result, p)
}
}
sort.Strings(result)
return result, nil
}
// IsDir returns true if the file exists and is a directory.
func (fs *FakeFS) IsDir(name string) bool {
f, found := fs.m[name]
@@ -77,18 +91,6 @@ func (fs *FakeFS) ReadFile(name string) ([]byte, error) {
return nil, fmt.Errorf("cannot read file %q", name)
}
// ReadFiles looks through all files in the fake filesystem
// and find the matching files and then read content from all of them
func (fs *FakeFS) ReadFiles(name string) (map[string][]byte, error) {
result := map[string][]byte{}
for p, f := range fs.m {
if fs.pathMatch(p, name) {
result[p] = f.content
}
}
return result, nil
}
// WriteFile always succeeds and does nothing.
func (fs *FakeFS) WriteFile(name string, c []byte) error {
ff := &FakeFile{}
@@ -98,8 +100,6 @@ func (fs *FakeFS) WriteFile(name string, c []byte) error {
}
func (fs *FakeFS) pathMatch(path, pattern string) bool {
if path == pattern {
return true
}
return false
match, _ := filepath.Match(pattern, path)
return match
}

View File

@@ -18,6 +18,7 @@ package fs
import (
"bytes"
"reflect"
"testing"
)
@@ -90,3 +91,20 @@ func TestWriteFile(t *testing.T) {
t.Fatalf("incorrect content: %v", content)
}
}
func TestGlob(t *testing.T) {
x := MakeFakeFS()
x.Create("dir/foo")
x.Create("dir/bar")
files, err := x.Glob("dir/*")
if err != nil {
t.Fatalf("expected no error")
}
expected := []string{
"dir/bar",
"dir/foo",
}
if !reflect.DeepEqual(files, expected) {
t.Fatalf("incorrect files found by glob: %v", files)
}
}

View File

@@ -29,8 +29,8 @@ type FileSystem interface {
Open(name string) (File, error)
IsDir(name string) bool
Exists(name string) bool
Glob(pattern string) ([]string, error)
ReadFile(name string) ([]byte, error)
ReadFiles(name string) (map[string][]byte, error)
WriteFile(name string, data []byte) error
}

View File

@@ -49,6 +49,11 @@ func (realFS) Exists(name string) bool {
return err == nil
}
// Glob returns the list of matching files
func (realFS) Glob(pattern string) ([]string, error) {
return filepath.Glob(pattern)
}
// IsDir delegates to os.Stat and FileInfo.IsDir
func (realFS) IsDir(name string) bool {
info, err := os.Stat(name)
@@ -61,26 +66,6 @@ func (realFS) IsDir(name string) bool {
// ReadFile delegates to ioutil.ReadFile.
func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
// ReadFiles use glob to find the matching files and then read content from all of them
func (realFS) ReadFiles(name string) (map[string][]byte, error) {
files, err := filepath.Glob(name)
if err != nil || len(files) == 0 {
return nil, err
}
output := map[string][]byte{}
for _, file := range files {
bytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
if bytes != nil {
output[file] = bytes
}
}
return output, nil
}
// WriteFile delegates to ioutil.WriteFile with read/write permissions.
func (realFS) WriteFile(name string, c []byte) error {
return ioutil.WriteFile(name, c, 0666)

View File

@@ -55,17 +55,15 @@ func TestReadFilesRealFS(t *testing.T) {
t.Fatalf("unexpected error %s", err)
}
expected := map[string][]byte{
path.Join(testDir, "foo"): []byte(`foo`),
path.Join(testDir, "bar"): []byte(`bar`),
}
content, err := x.ReadFiles("kustomize_testing_dir/*")
if !reflect.DeepEqual(content, expected) {
t.Fatalf("actual: %+v doesn't match expected: %+v", content, expected)
files, err := x.Glob(path.Join("testDir", "*"))
expected := []string{
path.Join(testDir, "bar"),
path.Join(testDir, "foo"),
}
if err != nil {
t.Fatalf("unexpected error %s", err)
t.Fatalf("expected no error")
}
if reflect.DeepEqual(files, expected) {
t.Fatalf("incorrect files found by glob: %v", files)
}
}

View File

@@ -22,6 +22,9 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/loader"
)
// CleanupCalled indicates if Cleanup is called.
var CleanupCalled = false
// FakeLoader encapsulates the delegate Loader and the fake file system.
type FakeLoader struct {
fs fs.FileSystem
@@ -34,7 +37,7 @@ type FakeLoader struct {
func NewFakeLoader(initialDir string) FakeLoader {
// Create fake filesystem and inject it into initial Loader.
fakefs := fs.MakeFakeFS()
rootLoader := loader.NewLoader(loader.NewFileLoader(fakefs))
rootLoader := loader.NewFileLoader(fakefs)
ldr, _ := rootLoader.New(initialDir)
return FakeLoader{fs: fakefs, delegate: ldr}
}
@@ -56,7 +59,11 @@ func (f FakeLoader) Root() string {
// New creates a new loader from a new root.
func (f FakeLoader) New(newRoot string) (loader.Loader, error) {
return f.delegate.New(newRoot)
l, err := f.delegate.New(newRoot)
if err != nil {
return nil, err
}
return FakeLoader{fs: f.fs, delegate: l}, nil
}
// Load performs load from a given location.
@@ -64,7 +71,8 @@ func (f FakeLoader) Load(location string) ([]byte, error) {
return f.delegate.Load(location)
}
// GlobLoad performs load from a given location.
func (f FakeLoader) GlobLoad(location string) (map[string][]byte, error) {
return f.delegate.GlobLoad(location)
// Cleanup does nothing
func (f FakeLoader) Cleanup() error {
CleanupCalled = true
return nil
}

View File

@@ -26,32 +26,52 @@ import (
const currentDir = "."
// FileLoader loads files from a file system.
type FileLoader struct {
fs fs.FileSystem
// fileLoader loads files from a file system.
type fileLoader struct {
root string
fSys fs.FileSystem
}
// NewFileLoader returns a new FileLoader.
func NewFileLoader(fs fs.FileSystem) *FileLoader {
return &FileLoader{fs: fs}
// NewFileLoader returns a new fileLoader.
func NewFileLoader(fSys fs.FileSystem) *fileLoader {
return newFileLoaderAtRoot("", fSys)
}
// newFileLoaderAtRoot returns a new fileLoader with given root.
func newFileLoaderAtRoot(root string, fs fs.FileSystem) *fileLoader {
return &fileLoader{root: root, fSys: fs}
}
// Root returns the root location for this Loader.
func (l *fileLoader) Root() string {
return l.root
}
// Returns a new Loader rooted at newRoot. "newRoot" MUST be
// a directory (not a file). The directory can have a trailing
// slash or not.
// Example: "/home/seans/project" or "/home/seans/project/"
// NOT "/home/seans/project/file.yaml".
func (l *fileLoader) New(newRoot string) (Loader, error) {
return NewLoader(newRoot, l.root, l.fSys)
}
// IsAbsPath return true if the location calculated with the root
// and location params a full file path.
func (l *FileLoader) IsAbsPath(root string, location string) bool {
fullFilePath, err := l.FullLocation(root, location)
func (l *fileLoader) IsAbsPath(root string, location string) bool {
fullFilePath, err := l.fullLocation(root, location)
if err != nil {
return false
}
return filepath.IsAbs(fullFilePath)
}
// FullLocation returns some notion of a full path.
// fullLocation returns some notion of a full path.
// If location is a full file path, then ignore root. If location is relative, then
// join the root path with the location path. Either root or location can be empty,
// but not both. Special case for ".": Expands to current working directory.
// Example: "/home/seans/project", "subdir/bar" -> "/home/seans/project/subdir/bar".
func (l *FileLoader) FullLocation(root string, location string) (string, error) {
func (l *fileLoader) fullLocation(root string, location string) (string, error) {
// First, validate the parameters
if len(root) == 0 && len(location) == 0 {
return "", fmt.Errorf("unable to calculate full location: root and location empty")
@@ -74,12 +94,16 @@ func (l *FileLoader) FullLocation(root string, location string) (string, error)
// Load returns the bytes from reading a file at fullFilePath.
// Implements the Loader interface.
func (l *FileLoader) Load(p string) ([]byte, error) {
return l.fs.ReadFile(p)
func (l *fileLoader) Load(location string) ([]byte, error) {
fullLocation, err := l.fullLocation(l.root, location)
if err != nil {
fmt.Printf("Trouble in fulllocation: %v\n", err)
return nil, err
}
return l.fSys.ReadFile(fullLocation)
}
// GlobLoad returns the map from path to bytes from reading a glob path.
// Implements the Loader interface.
func (l *FileLoader) GlobLoad(p string) (map[string][]byte, error) {
return l.fs.ReadFiles(p)
// Cleanup does nothing
func (l *fileLoader) Cleanup() error {
return nil
}

View File

@@ -24,10 +24,6 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func initializeRootLoader(fakefs fs.FileSystem) Loader {
return NewLoader(NewFileLoader(fakefs))
}
func TestLoader_Root(t *testing.T) {
// Initialize the fake file system and the root loader.
@@ -35,7 +31,7 @@ func TestLoader_Root(t *testing.T) {
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("Unused"))
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Unused"))
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("Unused"))
rootLoader := initializeRootLoader(fakefs)
rootLoader := NewFileLoader(fakefs)
_, err := rootLoader.New("")
if err == nil {
@@ -51,7 +47,7 @@ func TestLoader_Root(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected in New(): %v\n", err)
}
if "/home/seans/project/" != loader.Root() {
if "/home/seans/project" != loader.Root() {
t.Fatalf("Incorrect Loader Root: %s\n", loader.Root())
}
@@ -89,7 +85,7 @@ func TestLoader_Load(t *testing.T) {
fakefs.WriteFile("/home/seans/project/file.yaml", []byte("This is a yaml file"))
fakefs.WriteFile("/home/seans/project/subdir/file.yaml", []byte("Subdirectory file content"))
fakefs.WriteFile("/home/seans/project2/file.yaml", []byte("This is another yaml file"))
rootLoader := initializeRootLoader(fakefs)
rootLoader := NewFileLoader(fakefs)
loader, err := rootLoader.New("/home/seans/project")
if err != nil {

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 loader
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/go-getter"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
// githubLoader loads files from a checkout github repo
type githubLoader struct {
repo string
checkoutDir string
loader *fileLoader
}
// Root returns the root location for this Loader.
func (l *githubLoader) Root() string {
return l.checkoutDir
}
// New delegates to fileLoader.New
func (l *githubLoader) New(newRoot string) (Loader, error) {
return l.loader.New(newRoot)
}
// Load delegates to fileLoader.Load
func (l *githubLoader) Load(location string) ([]byte, error) {
return l.loader.Load(location)
}
// Cleanup removes the checked out repo
func (l *githubLoader) Cleanup() error {
return os.RemoveAll(l.checkoutDir)
}
// newGithubLoader returns a new fileLoader with given github Url.
func newGithubLoader(repoUrl string, fs fs.FileSystem) (*githubLoader, error) {
dir, err := ioutil.TempDir("", "kustomize-")
if err != nil {
return nil, err
}
target := filepath.Join(dir, "repo")
err = checkout(repoUrl, target)
if err != nil {
return nil, err
}
l := newFileLoaderAtRoot(target, fs)
return &githubLoader{
repo: repoUrl,
checkoutDir: dir,
loader: l,
}, nil
}
// isRepoUrl checks if a string is a repo Url
func isRepoUrl(s string) bool {
if strings.HasPrefix(s, "https://") {
return true
}
host := strings.SplitN(s, "/", 2)[0]
return strings.Contains(host, ".com") || strings.Contains(host, ".org")
}
// Checkout clones a github repo with specified commit/tag/branch
func checkout(url, dir string) error {
pwd, err := os.Getwd()
if err != nil {
return err
}
client := &getter.Client{
Src: url,
Dst: dir,
Pwd: pwd,
Mode: getter.ClientModeDir,
}
return client.Get()
}

View File

@@ -0,0 +1,56 @@
/*
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 loader
import (
"testing"
)
func TestIsRepoURL(t *testing.T) {
testcases := []struct {
input string
expected bool
}{
{
input: "https://github.com/org/repo",
expected: true,
},
{
input: "github.com/org/repo",
expected: true,
},
{
input: "/github.com/org/repo",
expected: false,
},
{
input: "/abs/path/to/file",
expected: false,
},
{
input: "../relative",
expected: false,
},
}
for _, tc := range testcases {
actual := isRepoUrl(tc.input)
if actual != tc.expected {
t.Errorf("unexpected error: unexpected result %t for input %s", actual, tc.input)
}
}
}

View File

@@ -17,7 +17,12 @@ limitations under the License.
// Package loader has a data loading interface and various implementations.
package loader
import "fmt"
import (
"fmt"
"path/filepath"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
// Loader interface exposes methods to read bytes.
type Loader interface {
@@ -27,63 +32,49 @@ type Loader interface {
New(newRoot string) (Loader, error)
// Load returns the bytes read from the location or an error.
Load(location string) ([]byte, error)
// GlobLoad returns the bytes read from a glob path or an error.
GlobLoad(location string) (map[string][]byte, error)
// Cleanup cleans the loader
Cleanup() error
}
// Private implementation of Loader interface.
type loaderImpl struct {
root string
fLoader *FileLoader
}
const emptyRoot = ""
// NewLoader initializes the first loader with the supported fLoader.
func NewLoader(fl *FileLoader) Loader {
return &loaderImpl{root: emptyRoot, fLoader: fl}
}
// Root returns the root location for this Loader.
func (l *loaderImpl) Root() string {
return l.root
}
// Returns a new Loader rooted at newRoot. "newRoot" MUST be
// a directory (not a file). The directory can have a trailing
// slash or not.
// Example: "/home/seans/project" or "/home/seans/project/"
// NOT "/home/seans/project/file.yaml".
func (l *loaderImpl) New(newRoot string) (Loader, error) {
if !l.fLoader.IsAbsPath(l.root, newRoot) {
return nil, fmt.Errorf("Not abs path: l.root='%s', loc='%s'\n", l.root, newRoot)
// NewLoader returns a Loader given a target
// The target can be a local disk directory or a github Url
func NewLoader(target, r string, fSys fs.FileSystem) (Loader, error) {
if !isValidLoaderPath(target, r) {
return nil, fmt.Errorf("Not valid path: root='%s', loc='%s'\n", r, target)
}
root, err := l.fLoader.FullLocation(l.root, newRoot)
if !isLocalTarget(target, fSys) && isRepoUrl(target) {
return newGithubLoader(target, fSys)
}
l := newFileLoaderAtRoot(r, fSys)
if isRootLoaderPath(r) {
absPath, err := filepath.Abs(target)
if err != nil {
return nil, err
}
target = absPath
}
if !l.IsAbsPath(l.root, target) {
return nil, fmt.Errorf("Not abs path: l.root='%s', loc='%s'\n", l.root, target)
}
root, err := l.fullLocation(l.root, target)
if err != nil {
return nil, err
}
return &loaderImpl{root: root, fLoader: l.fLoader}, nil
return newFileLoaderAtRoot(root, l.fSys), nil
}
// Load returns all the bytes read from location or an error.
// "location" can be an absolute path, or if relative, full location is
// calculated from the Root().
func (l *loaderImpl) Load(location string) ([]byte, error) {
fullLocation, err := l.fLoader.FullLocation(l.root, location)
if err != nil {
fmt.Printf("Trouble in fulllocation: %v\n", err)
return nil, err
}
return l.fLoader.Load(fullLocation)
func isValidLoaderPath(target, root string) bool {
return target != "" || root != ""
}
// GlobLoad returns a map from path to bytes read from the location or an error.
// "location" can be an absolute path, or if relative, full location is
// calculated from the Root().
func (l *loaderImpl) GlobLoad(location string) (map[string][]byte, error) {
fullLocation, err := l.fLoader.FullLocation(l.root, location)
if err != nil {
return nil, err
}
return l.fLoader.GlobLoad(fullLocation)
func isRootLoaderPath(root string) bool {
return root == ""
}
// isLocalTarget checks if a file exists in the filesystem
func isLocalTarget(s string, fs fs.FileSystem) bool {
return fs.Exists(s)
}

View File

@@ -104,7 +104,7 @@ BAR=baz
{
Name: "literalConfigMap",
DataSources: types.DataSources{
LiteralSources: []string{"a=x", "b=y"},
LiteralSources: []string{"a=x", "b=y", "c=\"Good Morning\"", "d=\"false\""},
},
},
},
@@ -120,6 +120,8 @@ BAR=baz
"data": map[string]interface{}{
"a": "x",
"b": "y",
"c": "Good Morning",
"d": "false",
},
}).SetBehavior(resource.BehaviorCreate),
},

View File

@@ -23,6 +23,7 @@ import (
"io"
"reflect"
"sort"
"strings"
"github.com/ghodss/yaml"
"github.com/golang/glog"
@@ -37,6 +38,26 @@ import (
// ResMap is a map from ResId to Resource.
type ResMap map[resource.ResId]*resource.Resource
// FindByGVKN find the matched ResIds by Group/Version/Kind and Name
func (m ResMap) FindByGVKN(inputId resource.ResId) []resource.ResId {
var result []resource.ResId
for id := range m {
if id.GvknEquals(inputId) {
result = append(result, id)
}
}
return result
}
// DemandOneMatchForId find the matched resource by Group/Version/Kind and Name
func (m ResMap) DemandOneMatchForId(inputId resource.ResId) (*resource.Resource, bool) {
result := m.FindByGVKN(inputId)
if len(result) == 1 {
return m[result[0]], true
}
return nil, false
}
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
func (m ResMap) EncodeAsYaml() ([]byte, error) {
var ids []resource.ResId
@@ -113,18 +134,15 @@ func NewResourceSliceFromPatches(
loader loader.Loader, paths []string) ([]*resource.Resource, error) {
var result []*resource.Resource
for _, path := range paths {
contents, err := loader.GlobLoad(path)
content, err := loader.Load(path)
if err != nil {
return nil, err
}
for p, content := range contents {
res, err := newResourceSliceFromBytes(content)
if err != nil {
return nil, internal.Handler(err, p)
}
result = append(result, res...)
res, err := newResourceSliceFromBytes(content)
if err != nil {
return nil, internal.Handler(err, path)
}
result = append(result, res...)
}
return result, nil
}
@@ -133,17 +151,15 @@ func NewResourceSliceFromPatches(
func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) {
var result []ResMap
for _, path := range paths {
contents, err := loader.GlobLoad(path)
content, err := loader.Load(path)
if err != nil {
return nil, errors.Wrap(err, "Load from path "+path+" failed")
}
for p, content := range contents {
res, err := newResMapFromBytes(content)
if err != nil {
return nil, internal.Handler(err, p)
}
result = append(result, res)
res, err := newResMapFromBytes(content)
if err != nil {
return nil, internal.Handler(err, path)
}
result = append(result, res)
}
return MergeWithoutOverride(result...)
}
@@ -182,13 +198,12 @@ func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
var result []*resource.Resource
var err error
for {
for err == nil || isEmptyYamlError(err) {
var out unstructured.Unstructured
err = decoder.Decode(&out)
if err != nil {
break
if err == nil {
result = append(result, resource.NewResourceFromUnstruct(out))
}
result = append(result, resource.NewResourceFromUnstruct(out))
}
if err != io.EOF {
return nil, err
@@ -196,10 +211,14 @@ func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
return result, nil
}
// MergeWithoutOverride combines multiple ResMap instances, failing on key collision.
// MergeWithoutOverride combines multiple ResMap instances, failing on key collision
// and skipping nil maps. In case if all of the maps are nil, an empty ResMap is returned.
func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
result := ResMap{}
for _, m := range maps {
if m == nil {
continue
}
for id, res := range m {
if _, found := result[id]; found {
return nil, fmt.Errorf("id '%q' already used", id)
@@ -211,16 +230,25 @@ func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
}
// MergeWithOverride combines multiple ResMap instances, allowing and sometimes
// demanding certain collisions.
// demanding certain collisions and skipping nil maps.
// In case if all of the maps are nil, an empty ResMap is returned.
// When looping over the instances to combine them, if a resource id for resource X
// is found to be already in the combined map, then the behavior field for X
// must be BehaviorMerge or BehaviorReplace. If X is not in the map, then it's
// behavior cannot be merge or replace.
func MergeWithOverride(maps ...ResMap) (ResMap, error) {
result := ResMap{}
for _, m := range maps {
result := maps[0]
if result == nil {
result = ResMap{}
}
for _, m := range maps[1:] {
if m == nil {
continue
}
for id, r := range m {
if _, found := result[id]; found {
matchedId := result.FindByGVKN(id)
if len(matchedId) == 1 {
id = matchedId[0]
switch r.Behavior() {
case resource.BehaviorReplace:
glog.V(4).Infof("Replace %v with %v", result[id].Object, r.Object)
@@ -236,15 +264,21 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) {
default:
return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
}
} else {
} else if len(matchedId) == 0 {
switch r.Behavior() {
case resource.BehaviorMerge, resource.BehaviorReplace:
return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
default:
result[id] = r
}
} else {
return nil, fmt.Errorf("merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id)
}
}
}
return result, nil
}
func isEmptyYamlError(err error) bool {
return strings.Contains(err.Error(), "is missing in 'null'")
}

View File

@@ -78,6 +78,10 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: dply2
---
# some comment
---
---
`
l := loadertest.NewFakeLoader("/home/seans/project")
@@ -198,6 +202,22 @@ func TestMergeWithoutOverride(t *testing.T) {
if !reflect.DeepEqual(merged, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged, expected)
}
input3 := []ResMap{merged, nil}
merged1, err := MergeWithoutOverride(input3...)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(merged1, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged1, expected)
}
input4 := []ResMap{nil, merged}
merged2, err := MergeWithoutOverride(input4...)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(merged2, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged2, expected)
}
}
func TestMergeWithOverride(t *testing.T) {
@@ -258,4 +278,20 @@ func TestMergeWithOverride(t *testing.T) {
if !reflect.DeepEqual(merged, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged, expected)
}
input3 := []ResMap{merged, nil}
merged1, err := MergeWithOverride(input3...)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(merged1, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged1, expected)
}
input4 := []ResMap{nil, merged}
merged2, err := MergeWithOverride(input4...)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(merged2, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged2, expected)
}
}

View File

@@ -30,7 +30,7 @@ func NewResMapFromSecretArgs(
secretList []types.SecretArgs) (ResMap, error) {
var allResources []*resource.Resource
for _, args := range secretList {
s, err := f.MakeSecret(args)
s, err := f.MakeSecret(&args)
if err != nil {
return nil, errors.Wrap(err, "makeSecret")
}

View File

@@ -35,9 +35,18 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
secrets := []types.SecretArgs{
{
Name: "apple",
Commands: map[string]string{
"DB_USERNAME": "printf admin",
"DB_PASSWORD": "printf somepw",
CommandSources: types.CommandSources{
Commands: map[string]string{
"DB_USERNAME": "printf admin",
"DB_PASSWORD": "printf somepw",
},
},
Type: "Opaque",
},
{
Name: "peanuts",
CommandSources: types.CommandSources{
EnvCommand: "printf \"DB_USERNAME=admin\nDB_PASSWORD=somepw\"",
},
Type: "Opaque",
},
@@ -66,8 +75,46 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
}).SetBehavior(resource.BehaviorCreate),
resource.NewResId(secret, "peanuts"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": "peanuts",
"creationTimestamp": nil,
},
"type": string(corev1.SecretTypeOpaque),
"data": map[string]interface{}{
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
}).SetBehavior(resource.BehaviorCreate),
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected)
}
}
func TestSecretTimeout(t *testing.T) {
timeout := int64(1)
secrets := []types.SecretArgs{
{
Name: "slow",
TimeoutSeconds: &timeout,
CommandSources: types.CommandSources{
Commands: map[string]string{
"USER": "sleep 2",
},
},
Type: "Opaque",
},
}
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir(".")
_, err := NewResMapFromSecretArgs(
configmapandsecret.NewSecretFactory(fakeFs, "."), secrets)
if err == nil {
t.Fatal("didn't get the expected timeout error", err)
}
}

View File

@@ -28,18 +28,51 @@ type ResId struct {
gvk schema.GroupVersionKind
// original name of the resource before transformation.
name string
// namePrefix of the resource
// an untransformed resource has no prefix, fully transformed resource has an arbitrary number of prefixes
// concatenated together.
prefix string
// namespace the resource belongs to
// an untransformed resource has no namespace, fully transformed resource has the namespace from
// the top most overlay
namespace string
}
// NewResIdWithPrefixNamespace creates new resource identifier with a prefix and a namespace
func NewResIdWithPrefixNamespace(g schema.GroupVersionKind, n, p, ns string) ResId {
return ResId{gvk: g, name: n, prefix: p, namespace: ns}
}
// NewResIdWithPrefix creates new resource identifier with a prefix
func NewResIdWithPrefix(g schema.GroupVersionKind, n, p string) ResId {
return ResId{gvk: g, name: n, prefix: p}
}
// NewResId creates new resource identifier
func NewResId(g schema.GroupVersionKind, n string) ResId {
return ResId{gvk: g, name: n}
return NewResIdWithPrefix(g, n, "")
}
// String of ResId based on GVK, name and prefix
func (n ResId) String() string {
fields := []string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.namespace, n.prefix, n.name}
return strings.Join(fields, "_") + ".yaml"
}
// GvknString of ResId based on GVK and name
func (n ResId) GvknString() string {
if n.gvk.Group == "" {
return strings.Join([]string{n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
}
return strings.Join([]string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
}
// GvknEquals return if two ResId have the same Group/Version/Kind and name
// The comparison excludes prefix
func (n ResId) GvknEquals(id ResId) bool {
return n.gvk.Group == id.gvk.Group && n.gvk.Version == id.gvk.Version &&
n.gvk.Kind == id.gvk.Kind && n.name == id.name
}
// Gvk returns Group/Version/Kind of the resource.
@@ -51,3 +84,23 @@ func (n ResId) Gvk() schema.GroupVersionKind {
func (n ResId) Name() string {
return n.name
}
// Prefix returns name prefix.
func (n ResId) Prefix() string {
return n.prefix
}
// Namespace returns resource namespace.
func (n ResId) Namespace() string {
return n.namespace
}
// CopyWithNewPrefix make a new copy from current ResId and append a new prefix
func (n ResId) CopyWithNewPrefix(p string) ResId {
return ResId{gvk: n.gvk, name: n.name, prefix: p + n.prefix, namespace: n.namespace}
}
// CopyWithNewNamespace make a new copy from current ResId and set a new namespace
func (n ResId) CopyWithNewNamespace(ns string) ResId {
return ResId{gvk: n.gvk, name: n.name, prefix: n.prefix, namespace: ns}
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package transformers
import (
"regexp"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
@@ -115,6 +116,7 @@ func (pt *imageTagTransformer) findContainers(obj map[string]interface{}) error
}
func isImageMatched(s, t string) bool {
imagetag := strings.Split(s, ":")
return len(imagetag) >= 1 && imagetag[0] == t
// Tag values are limited to [a-zA-Z0-9_.-].
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$")
return pattern.MatchString(s)
}

View File

@@ -66,8 +66,8 @@ func TestImageTagTransformer(t *testing.T) {
"image": "nginx",
},
map[string]interface{}{
"name": "nginx2",
"image": "my-nginx:random",
"name": "myimage",
"image": "myprivaterepohostname:1234/my/image:latest",
},
},
},
@@ -78,7 +78,7 @@ func TestImageTagTransformer(t *testing.T) {
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "ngin3",
"name": "nginx3",
"image": "nginx:v1",
},
map[string]interface{}{
@@ -130,8 +130,8 @@ func TestImageTagTransformer(t *testing.T) {
"image": "nginx:v2",
},
map[string]interface{}{
"name": "nginx2",
"image": "my-nginx:previous",
"name": "myimage",
"image": "myprivaterepohostname:1234/my/image:v1.0.1",
},
},
},
@@ -142,7 +142,7 @@ func TestImageTagTransformer(t *testing.T) {
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "ngin3",
"name": "nginx3",
"image": "nginx:v2",
},
map[string]interface{}{
@@ -159,6 +159,7 @@ func TestImageTagTransformer(t *testing.T) {
it, err := NewImageTagTransformer([]types.ImageTag{
{Name: "nginx", NewTag: "v2"},
{Name: "my-nginx", NewTag: "previous"},
{Name: "myprivaterepohostname:1234/my/image", NewTag: "v1.0.1"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)

View File

@@ -38,6 +38,7 @@ var cronjob = schema.GroupVersionKind{Group: "batch", Version: "v1beta1", Kind:
var pvc = schema.GroupVersionKind{Version: "v1", Kind: "PersistentVolumeClaim"}
var crb = schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}
var sa = schema.GroupVersionKind{Version: "v1", Kind: "ServiceAccount"}
var ingress = schema.GroupVersionKind{Kind: "Ingress"}
func TestLabelsRun(t *testing.T) {
m := resmap.ResMap{

View File

@@ -157,7 +157,7 @@ var defaultLabelsPathConfigs = []PathConfig{
{
GroupVersionKind: &schema.GroupVersionKind{Group: "policy", Kind: "PodDisruptionBudget"},
Path: []string{"spec", "selector", "matchLabels"},
CreateIfNotPresent: true,
CreateIfNotPresent: false,
},
{
GroupVersionKind: &schema.GroupVersionKind{Group: "networking.k8s.io", Kind: "NetworkPolicy"},

View File

@@ -59,6 +59,26 @@ func TestNameReferenceRun(t *testing.T) {
"name": "someprefix-claim1",
},
}),
resource.NewResId(ingress, "ingress1"): resource.NewResourceFromMap(
map[string]interface{}{
"group": "extensions",
"apiVersion": "v1beta1",
"kind": "Ingress",
"metadata": map[string]interface{}{
"name": "ingress1",
"annotations": map[string]interface{}{
"ingress.kubernetes.io/auth-secret": "secret1",
"nginx.ingress.kubernetes.io/auth-secret": "secret1",
},
},
"spec": map[string]interface{}{
"backend": map[string]interface{}{
"serviceName": "testsvc",
"servicePort": "80",
},
},
},
),
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
map[string]interface{}{
"group": "apps",
@@ -282,6 +302,26 @@ func TestNameReferenceRun(t *testing.T) {
},
},
})
expected[resource.NewResId(ingress, "ingress1")] = resource.NewResourceFromMap(
map[string]interface{}{
"group": "extensions",
"apiVersion": "v1beta1",
"kind": "Ingress",
"metadata": map[string]interface{}{
"name": "ingress1",
"annotations": map[string]interface{}{
"ingress.kubernetes.io/auth-secret": "someprefix-secret1-somehash",
"nginx.ingress.kubernetes.io/auth-secret": "someprefix-secret1-somehash",
},
},
"spec": map[string]interface{}{
"backend": map[string]interface{}{
"serviceName": "testsvc",
"servicePort": "80",
},
},
},
)
nrt, err := NewDefaultingNameReferenceTransformer()
if err != nil {

View File

@@ -650,6 +650,20 @@ var defaultNameReferencePathConfigs = []ReferencePathConfig{
Path: []string{"spec", "tls", "secretName"},
CreateIfNotPresent: false,
},
{
GroupVersionKind: &schema.GroupVersionKind{
Kind: "Ingress",
},
Path: []string{"metadata", "annotations", "ingress.kubernetes.io/auth-secret"},
CreateIfNotPresent: false,
},
{
GroupVersionKind: &schema.GroupVersionKind{
Kind: "Ingress",
},
Path: []string{"metadata", "annotations", "nginx.ingress.kubernetes.io/auth-secret"},
CreateIfNotPresent: false,
},
{
GroupVersionKind: &schema.GroupVersionKind{
Kind: "ServiceAccount",
@@ -860,6 +874,21 @@ var defaultNameReferencePathConfigs = []ReferencePathConfig{
},
},
},
{
referencedGVK: schema.GroupVersionKind{
Version: "v1",
Kind: "PersistentVolume",
},
pathConfigs: []PathConfig{
{
GroupVersionKind: &schema.GroupVersionKind{
Kind: "PersistentVolumeClaim",
},
Path: []string{"spec", "volumeName"},
CreateIfNotPresent: false,
},
},
},
}
// AddNameReferencePathConfigs adds extra reference path configs to the default one

View File

@@ -77,17 +77,21 @@ func (o *namespaceTransformer) Transform(m resmap.ResMap) error {
mf := resmap.ResMap{}
for id := range m {
mf[id] = m[id]
found := false
for _, path := range o.skipPathConfigs {
if selectByGVK(id.Gvk(), path.GroupVersionKind) {
delete(mf, id)
found = true
break
}
}
if !found {
mf[id] = m[id]
delete(m, id)
}
}
for id := range mf {
objMap := m[id].UnstructuredContent()
objMap := mf[id].UnstructuredContent()
for _, path := range o.pathConfigs {
if !selectByGVK(id.Gvk(), path.GroupVersionKind) {
continue
@@ -99,6 +103,8 @@ func (o *namespaceTransformer) Transform(m resmap.ResMap) error {
if err != nil {
return err
}
newid := id.CopyWithNewNamespace(o.namespace)
m[newid] = mf[id]
}
}

View File

@@ -104,7 +104,7 @@ func TestNamespaceRun(t *testing.T) {
}),
}
expected := resmap.ResMap{
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(ns, "ns1", "", ""): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
@@ -112,7 +112,7 @@ func TestNamespaceRun(t *testing.T) {
"name": "ns1",
},
}),
resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(cmap, "cm1", "", "test"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -121,7 +121,7 @@ func TestNamespaceRun(t *testing.T) {
"namespace": "test",
},
}),
resource.NewResId(cmap, "cm2"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(cmap, "cm2", "", "test"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -130,7 +130,7 @@ func TestNamespaceRun(t *testing.T) {
"namespace": "test",
},
}),
resource.NewResId(sa, "default"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(sa, "default", "", "test"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ServiceAccount",
@@ -139,7 +139,7 @@ func TestNamespaceRun(t *testing.T) {
"namespace": "test",
},
}),
resource.NewResId(sa, "service-account"): resource.NewResourceFromMap(
resource.NewResIdWithPrefixNamespace(sa, "service-account", "", "test"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ServiceAccount",

View File

@@ -55,10 +55,15 @@ func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
for _, patch := range patches {
// Merge patches with base resource.
id := patch.Id()
base, found := baseResourceMap[id]
if !found {
matchedIds := baseResourceMap.FindByGVKN(id)
if len(matchedIds) == 0 {
return fmt.Errorf("failed to find an object with %#v to apply the patch", id.Gvk())
}
if len(matchedIds) > 1 {
return fmt.Errorf("found multiple objects %#v targeted by patch %#v (ambiguous)", matchedIds, id)
}
id = matchedIds[0]
base := baseResourceMap[id]
merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(id.Gvk())
baseName := base.GetName()

View File

@@ -19,6 +19,7 @@ package transformers
import (
"errors"
"fmt"
"log"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
@@ -48,6 +49,11 @@ var skipNamePrefixPathConfigs = []PathConfig{
},
}
// deprecateNamePrefixPathConfig will be moved into skipNamePrefixPathConfigs in next release
var deprecateNamePrefixPathConfig = PathConfig{
GroupVersionKind: &schema.GroupVersionKind{Kind: "Namespace"},
}
// NewDefaultingNamePrefixTransformer construct a namePrefixTransformer with defaultNamePrefixPathConfigs.
func NewDefaultingNamePrefixTransformer(nameprefix string) (Transformer, error) {
return NewNamePrefixTransformer(defaultNamePrefixPathConfigs, nameprefix)
@@ -69,16 +75,23 @@ func (o *namePrefixTransformer) Transform(m resmap.ResMap) error {
mf := resmap.ResMap{}
for id := range m {
mf[id] = m[id]
found := false
for _, path := range o.skipPathConfigs {
if selectByGVK(id.Gvk(), path.GroupVersionKind) {
delete(mf, id)
found = true
break
}
}
if !found {
mf[id] = m[id]
delete(m, id)
}
}
for id := range mf {
if selectByGVK(id.Gvk(), deprecateNamePrefixPathConfig.GroupVersionKind) {
log.Println("Adding nameprefix to Namespace resource will be deprecated in next release.")
}
objMap := mf[id].UnstructuredContent()
for _, path := range o.pathConfigs {
if !selectByGVK(id.Gvk(), path.GroupVersionKind) {
@@ -88,6 +101,8 @@ func (o *namePrefixTransformer) Transform(m resmap.ResMap) error {
if err != nil {
return err
}
newId := id.CopyWithNewPrefix(o.prefix)
m[newId] = mf[id]
}
}
return nil

View File

@@ -53,7 +53,7 @@ func TestPrefixNameRun(t *testing.T) {
}),
}
expected := resmap.ResMap{
resource.NewResId(cmap, "cm1"): resource.NewResourceFromMap(
resource.NewResIdWithPrefix(cmap, "cm1", "someprefix-"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -61,7 +61,7 @@ func TestPrefixNameRun(t *testing.T) {
"name": "someprefix-cm1",
},
}),
resource.NewResId(cmap, "cm2"): resource.NewResourceFromMap(
resource.NewResIdWithPrefix(cmap, "cm2", "someprefix-"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",

View File

@@ -17,8 +17,14 @@ limitations under the License.
// Package types holds struct definitions that should find a better home.
package types
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Kustomization holds the information needed to generate customized k8s api resources.
type Kustomization struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
// NamePrefix will prefix the names of all resources mentioned in the kustomization
// file including generated configmaps and secrets.
NamePrefix string `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
@@ -35,23 +41,22 @@ type Kustomization struct {
// Annotations to add to all objects.
CommonAnnotations map[string]string `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
// Each entry should be either a path to a file with a name matching the value of
// constants.KustomizationFileName, or a path to a directory containing a file with that name.
// Each entry should be either a path to a directory containing kustomization.yaml
// Or a repo URL pointing to a remote directory containing kustomization.yaml
// The repo URL should follow hashicorp/go-getter URL format
// https://github.com/hashicorp/go-getter#url-format
Bases []string `json:"bases,omitempty" yaml:"bases,omitempty"`
// Resources specifies the relative paths within the package.
// It could be any format that kubectl -f allows, i.e. files, directories,
// URLs and globs.
// Resources specifies the relative paths for resource files within the package.
// URLs and globs are not supported
Resources []string `json:"resources,omitempty" yaml:"resources,omitempty"`
// CRDs specifies relative paths to custom resource definition files.
CRDs []string `json:"crds,omitempty" yaml:"crds,omitempty"`
// Crds specifies relative paths to custom resource definition files.
Crds []string `json:"crds,omitempty" yaml:"crds,omitempty"`
// An Patch entry is very similar to an Resource entry.
// It specifies the relative paths within the package, and could be any
// format that kubectl -f allows.
// It should be able to be merged by Strategic Merge Patch on top of its
// corresponding base resource.
// It specifies the relative paths for patch files within the package.
// URLs and globs are not supported
Patches []string `json:"patches,omitempty" yaml:"patches,omitempty"`
// List of configmaps to generate from configuration sources.
@@ -115,12 +120,23 @@ type SecretArgs struct {
// keys: "tls.key" and "tls.crt"
Type string `json:"type,omitempty" yaml:"type,omitempty"`
// Map of keys to commands to generate the values
Commands map[string]string `json:",commands,omitempty" yaml:",inline,omitempty"`
// CommandSources for secret.
CommandSources `json:",inline,omitempty" yaml:",inline,omitempty"`
// TimeoutSeconds specifies the timeout for commands.
TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" yaml:"timeoutSeconds,omitempty"`
}
// DataSources contains some generic sources for configmap or secret.
// Only one field can be set.
// CommandSources contains some generic sources for secrets.
type CommandSources struct {
// Map of keys to commands to generate the values
Commands map[string]string `json:"commands,omitempty" yaml:"commands,omitempty"`
// EnvCommand to output lines of key=val pairs to create a secret.
// i.e. a Docker .env file or a .ini file.
EnvCommand string `json:"envCommand,omitempty" yaml:"envCommand,omitempty"`
}
// DataSources contains some generic sources for configmaps.
type DataSources struct {
// LiteralSources is a list of literal sources.
// Each literal source should be a key and literal value,

35
pkg/validate/validate.go Normal file
View File

@@ -0,0 +1,35 @@
package validate
import (
"fmt"
"regexp"
)
// TODO: these are rudimentary placeholder validation functions and need
// additional work to truly match expected syntax rules.
// IsValidLabel checks whether a label key/value pair has correct syntax and
// character set
func IsValidLabel(keyval string) (bool, error) {
ok, err := regexp.MatchString(`\A([a-zA-Z0-9_.-]+):([a-zA-Z0-9_.-]+)\z`, keyval)
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("invalid label format: %s", keyval)
}
return true, nil
}
// IsValidAnnotation checks whether an annotation key/value pair has correct
// syntax and character set
func IsValidAnnotation(keyval string) (bool, error) {
ok, err := regexp.MatchString(`\A([a-zA-Z0-9_.-]+):([a-zA-Z0-9_.-]+)\z`, keyval)
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("invalid annotation format: %s", keyval)
}
return true, nil
}

View File

@@ -0,0 +1,103 @@
package validate
import "testing"
func TestIsValidLabel(t *testing.T) {
testcases := []struct {
input, name string
valid bool
}{
{
input: "otters:cute",
valid: true,
name: "Valid input format",
},
{
input: "dogs,cats",
valid: false,
name: "Does not contain colon",
},
{
input: ":noKey",
valid: false,
name: "Missing key",
},
{
input: "noValue:",
valid: false,
name: "Missing value",
},
{
input: "exclamation!:point",
valid: false,
name: "Non-alphanumeric input",
},
{
input: "123:45",
valid: true,
name: "Numeric input is allowed",
},
}
for _, tc := range testcases {
ok, err := IsValidLabel(tc.input)
if tc.valid && err != nil {
t.Errorf("unexpected error: for test case %s, expected no error but got: %s", tc.name, err.Error())
}
if ok && !tc.valid {
t.Errorf("for test case %s, expected invalid label format error", tc.name)
}
if !ok && tc.valid {
t.Errorf("unexpected error: for test case %s, expected test to pass", tc.name)
}
}
}
func TestIsValidAnnotation(t *testing.T) {
testcases := []struct {
input, name string
valid bool
}{
{
input: "owls:adorable",
valid: true,
name: "Valid input format",
},
{
input: "cake,cookies",
valid: false,
name: "Does not contain colon",
},
{
input: ":noKey",
valid: false,
name: "Missing key",
},
{
input: "noValue:",
valid: false,
name: "Missing value",
},
{
input: "exclamation!:point",
valid: false,
name: "Input has a bang!",
},
{
input: "987:65",
valid: true,
name: "Numeric input is valid",
},
}
for _, tc := range testcases {
ok, err := IsValidAnnotation(tc.input)
if tc.valid && err != nil {
t.Errorf("unexpected error: for test case %s, expected no error but got: %s", tc.name, err.Error())
}
if ok && !tc.valid {
t.Errorf("for test case %s, expected invalid annotation format error", tc.name)
}
if !ok && tc.valid {
t.Errorf("unexpected error: for test case %s, expected test to pass", tc.name)
}
}
}

View File

@@ -0,0 +1,14 @@
Please fill out the sections below to help us address your issue.
### Version of AWS SDK for Go?
### Version of Go (`go version`)?
### What issue did you see?
### Steps to reproduce
If you have an runnable example, please include it.

View File

@@ -0,0 +1,3 @@
For changes to files under the `/model/` folder, and manual edits to autogenerated code (e.g. `/service/s3/api.go`) please create an Issue instead of a PR for those type of changes.
If there is an existing bug or feature this PR is answers please reference it here.

11
vendor/github.com/aws/aws-sdk-go/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,11 @@
dist
/doc
/doc-staging
.yardoc
Gemfile.lock
awstesting/integration/smoke/**/importmarker__.go
awstesting/integration/smoke/_test/
/vendor/bin/
/vendor/pkg/
/vendor/src/
/private/model/cli/gen-api/gen-api

14
vendor/github.com/aws/aws-sdk-go/.godoc_config generated vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"PkgHandler": {
"Pattern": "/sdk-for-go/api/",
"StripPrefix": "/sdk-for-go/api",
"Include": ["/src/github.com/aws/aws-sdk-go/aws", "/src/github.com/aws/aws-sdk-go/service"],
"Exclude": ["/src/cmd", "/src/github.com/aws/aws-sdk-go/awstesting", "/src/github.com/aws/aws-sdk-go/awsmigrate", "/src/github.com/aws/aws-sdk-go/private"],
"IgnoredSuffixes": ["iface"]
},
"Github": {
"Tag": "master",
"Repo": "/aws/aws-sdk-go",
"UseGithub": true
}
}

48
vendor/github.com/aws/aws-sdk-go/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,48 @@
language: go
matrix:
allow_failures:
- go: tip
- go: 1.6.x
os: linux
include:
- os: linux
sudo: required
go: 1.5.x
# Use Go 1.5's vendoring experiment for 1.5 tests.
env: GO15VENDOREXPERIMENT=1
- os: linux
sudo: required
go: 1.6.x
- os: linux
sudo: required
go: 1.7.x
- os: linux
sudo: required
go: 1.8.x
- os: linux
sudo: required
go: 1.9.x
- os: linux
sudo: required
go: 1.10.x
- os: linux
sudo: required
go: tip
- os: osx
go: 1.7.x
- os: osx
go: 1.8.x
- os: osx
go: 1.9.x
- os: osx
go: 1.10.x
- os: osx
go: tip
script:
- make ci-test
branches:
only:
- master

3993
vendor/github.com/aws/aws-sdk-go/CHANGELOG.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
### SDK Features
### SDK Enhancements
### SDK Bugs

4
vendor/github.com/aws/aws-sdk-go/CODE_OF_CONDUCT.md generated vendored Normal file
View File

@@ -0,0 +1,4 @@
## Code of Conduct
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
opensource-codeofconduct@amazon.com with any additional questions or comments.

127
vendor/github.com/aws/aws-sdk-go/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,127 @@
Contributing to the AWS SDK for Go
We work hard to provide a high-quality and useful SDK, and we greatly value
feedback and contributions from our community. Whether it's a bug report,
new feature, correction, or additional documentation, we welcome your issues
and pull requests. Please read through this document before submitting any
issues or pull requests to ensure we have all the necessary information to
effectively respond to your bug report or contribution.
## Filing Bug Reports
You can file bug reports against the SDK on the [GitHub issues][issues] page.
If you are filing a report for a bug or regression in the SDK, it's extremely
helpful to provide as much information as possible when opening the original
issue. This helps us reproduce and investigate the possible bug without having
to wait for this extra information to be provided. Please read the following
guidelines prior to filing a bug report.
1. Search through existing [issues][] to ensure that your specific issue has
not yet been reported. If it is a common issue, it is likely there is
already a bug report for your problem.
2. Ensure that you have tested the latest version of the SDK. Although you
may have an issue against an older version of the SDK, we cannot provide
bug fixes for old versions. It's also possible that the bug may have been
fixed in the latest release.
3. Provide as much information about your environment, SDK version, and
relevant dependencies as possible. For example, let us know what version
of Go you are using, which and version of the operating system, and the
the environment your code is running in. e.g Container.
4. Provide a minimal test case that reproduces your issue or any error
information you related to your problem. We can provide feedback much
more quickly if we know what operations you are calling in the SDK. If
you cannot provide a full test case, provide as much code as you can
to help us diagnose the problem. Any relevant information should be provided
as well, like whether this is a persistent issue, or if it only occurs
some of the time.
## Submitting Pull Requests
We are always happy to receive code and documentation contributions to the SDK.
Please be aware of the following notes prior to opening a pull request:
1. The SDK is released under the [Apache license][license]. Any code you submit
will be released under that license. For substantial contributions, we may
ask you to sign a [Contributor License Agreement (CLA)][cla].
2. If you would like to implement support for a significant feature that is not
yet available in the SDK, please talk to us beforehand to avoid any
duplication of effort.
3. Wherever possible, pull requests should contain tests as appropriate.
Bugfixes should contain tests that exercise the corrected behavior (i.e., the
test should fail without the bugfix and pass with it), and new features
should be accompanied by tests exercising the feature.
4. Pull requests that contain failing tests will not be merged until the test
failures are addressed. Pull requests that cause a significant drop in the
SDK's test coverage percentage are unlikely to be merged until tests have
been added.
5. The JSON files under the SDK's `models` folder are sourced from outside the SDK.
Such as `models/apis/ec2/2016-11-15/api.json`. We will not accept pull requests
directly on these models. If you discover an issue with the models please
create a [GitHub issue][issues] describing the issue.
### Testing
To run the tests locally, running the `make unit` command will `go get` the
SDK's testing dependencies, and run vet, link and unit tests for the SDK.
```
make unit
```
Standard go testing functionality is supported as well. To test SDK code that
is tagged with `codegen` you'll need to set the build tag in the go test
command. The `make unit` command will do this automatically.
```
go test -tags codegen ./private/...
```
See the `Makefile` for additional testing tags that can be used in testing.
To test on multiple platform the SDK includes several DockerFiles under the
`awstesting/sandbox` folder, and associated make recipes to execute
unit testing within environments configured for specific Go versions.
```
make sandbox-test-go18
```
To run all sandbox environments use the following make recipe
```
# Optionally update the Go tip that will be used during the batch testing
make update-aws-golang-tip
# Run all SDK tests for supported Go versions in sandboxes
make sandbox-test
```
In addition the sandbox environment include make recipes for interactive modes
so you can run command within the Docker container and context of the SDK.
```
make sandbox-go18
```
### Changelog
You can see all release changes in the `CHANGELOG.md` file at the root of the
repository. The release notes added to this file will contain service client
updates, and major SDK changes.
[issues]: https://github.com/aws/aws-sdk-go/issues
[pr]: https://github.com/aws/aws-sdk-go/pulls
[license]: http://aws.amazon.com/apache2.0/
[cla]: http://en.wikipedia.org/wiki/Contributor_License_Agreement
[releasenotes]: https://github.com/aws/aws-sdk-go/releases

20
vendor/github.com/aws/aws-sdk-go/Gopkg.lock generated vendored Normal file
View File

@@ -0,0 +1,20 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/go-ini/ini"
packages = ["."]
revision = "300e940a926eb277d3901b20bdfcc54928ad3642"
version = "v1.25.4"
[[projects]]
name = "github.com/jmespath/go-jmespath"
packages = ["."]
revision = "0b12d6b5"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "51a86a867df617990082dec6b868e4efe2fdb2ed0e02a3daa93cd30f962b5085"
solver-name = "gps-cdcl"
solver-version = 1

48
vendor/github.com/aws/aws-sdk-go/Gopkg.toml generated vendored Normal file
View File

@@ -0,0 +1,48 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
ignored = [
# Testing/Example/Codegen dependencies
"github.com/stretchr/testify",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"github.com/go-sql-driver/mysql",
"github.com/gucumber/gucumber",
"github.com/pkg/errors",
"golang.org/x/net",
"golang.org/x/net/html",
"golang.org/x/net/http2",
"golang.org/x/text",
"golang.org/x/text/html",
"golang.org/x/tools",
"golang.org/x/tools/go/loader",
]
[[constraint]]
name = "github.com/go-ini/ini"
version = "1.25.4"
[[constraint]]
name = "github.com/jmespath/go-jmespath"
revision = "0b12d6b5"
#version = "0.2.2"

202
vendor/github.com/aws/aws-sdk-go/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

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