mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 10:30:59 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
017c4ae0aa | ||
|
|
7b2baad390 | ||
|
|
bc2d69f4f9 | ||
|
|
e913a71fad | ||
|
|
7406dda074 | ||
|
|
0b4df3d414 | ||
|
|
7d38916d63 | ||
|
|
79d1abe573 | ||
|
|
9563052094 | ||
|
|
f881c19bb6 | ||
|
|
8d7b5f82c4 | ||
|
|
7554406c61 | ||
|
|
cf17050170 | ||
|
|
3857a67701 | ||
|
|
10665c6fc9 | ||
|
|
e0a09f4755 | ||
|
|
31c6a55747 | ||
|
|
8332a70d19 | ||
|
|
7fe2338acd | ||
|
|
43d4dbc07a | ||
|
|
f0cf4579d2 | ||
|
|
68ba37f139 | ||
|
|
bf73633cda | ||
|
|
55f8828ba1 | ||
|
|
0e1307dccf | ||
|
|
4471b75912 | ||
|
|
75c6204337 | ||
|
|
1b7171ac9e | ||
|
|
5193d6b4a8 | ||
|
|
6a834b6262 | ||
|
|
083d3cbb65 | ||
|
|
e68411b71e | ||
|
|
664774576c | ||
|
|
37e97084f9 | ||
|
|
de4d8b7dfa | ||
|
|
7f97108686 | ||
|
|
71f069cf95 | ||
|
|
3dbe732cb5 | ||
|
|
e5aea4423b | ||
|
|
100f05260e | ||
|
|
02f9329747 | ||
|
|
b6abd7600c | ||
|
|
2e7093e67f | ||
|
|
3b3a272d27 | ||
|
|
36115a7fa3 | ||
|
|
4d9d54e2c7 | ||
|
|
88aec95628 | ||
|
|
e30401489d | ||
|
|
58bc4b14a2 | ||
|
|
2824c28e08 | ||
|
|
d7cbb95d9c | ||
|
|
e771ec1169 | ||
|
|
9e5374e725 |
102
Gopkg.lock
generated
102
Gopkg.lock
generated
@@ -2,175 +2,224 @@
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e47871087b94913898333f37af26732faaab30cdb41571136cf7aec9921dae7"
|
||||
name = "github.com/PuerkitoBio/purell"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:331a419049c2be691e5ba1d24342fc77c7e767a80c666a18fd8a9f7b82419c1c"
|
||||
name = "github.com/PuerkitoBio/urlesc"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = ""
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:971e9ba63a417c5f1f83ab358677bc59e96ff04285f26c6646ff089fb60b15e8"
|
||||
name = "github.com/emicklei/go-restful"
|
||||
packages = [
|
||||
".",
|
||||
"log"
|
||||
"log",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3658237ded108b4134956c1b3050349d93e7b895"
|
||||
version = "v2.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dcefbadf4534c5ecac8573698fba6e6e601157bfa8f96aafe29df31ae582ef2a"
|
||||
name = "github.com/evanphx/json-patch"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "afac545df32f2287a079e2dfb7ba2745a643747e"
|
||||
version = "v3.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
|
||||
name = "github.com/go-openapi/jsonpointer"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3830527ef0f4f9b268d9286661c0f52f9115f8aefd9f45ee7352516f93489ac9"
|
||||
name = "github.com/go-openapi/jsonreference"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:238a056875c4b053b4b29984765ee335bf8c539fdf17e527fd9b7aa72521c8dd"
|
||||
name = "github.com/go-openapi/spec"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7b067ca8b94982960860d18c42e29f15bbd0e8d9ae8145a83a218296e75393cf"
|
||||
name = "github.com/go-openapi/swag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a3f6a0c68ab8f3d455f8892295503b179e571b7fefe47cc6c556405d1f83411"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys"
|
||||
"sortkeys",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a"
|
||||
name = "github.com/golang/glog"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692"
|
||||
name = "github.com/google/gofuzz"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16"
|
||||
name = "github.com/googleapis/gnostic"
|
||||
packages = [
|
||||
"OpenAPIv2",
|
||||
"compiler",
|
||||
"extensions"
|
||||
"extensions",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
|
||||
version = "1.1.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d9e483f4b9e306facf126bd90b02d512bd22ea4471e1568867e32221a8abbb16"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter"
|
||||
"jwriter",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:420f9231f816eeca3ff5aab070caac3ed7f27e4d37ded96ce9de3d7a7a2e31ad"
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:74c32990510c9f188556aa17600313e867d1d06f5a9db244056a95d144ec34ce"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9e548233d0dc00e74be262e54a9d1bbe7e4c19e5951083520261740e37daeb02"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna"
|
||||
"idna",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "2491c5de3490fced2f6cff376127c667efeed857"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
@@ -187,25 +236,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 +290,14 @@
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1"
|
||||
"storage/v1beta1",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:bcb2285bb525712de7903a5d254c2789df65c8b58d2cfac5a26d950ad94c2079"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/resource",
|
||||
@@ -274,28 +331,51 @@
|
||||
"pkg/util/yaml",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/json",
|
||||
"third_party/forked/golang/reflect"
|
||||
"third_party/forked/golang/reflect",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:071cc2f032b701b9dba26568e040940f26931a49e3a3985f3375f17f7f6d9c5f"
|
||||
name = "k8s.io/client-go"
|
||||
packages = ["kubernetes/scheme"]
|
||||
pruneopts = ""
|
||||
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
|
||||
version = "v7.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:386c5d69077ce740614e8309ddf107dde91a5db25d3d779143f452fb4fbdfd1e"
|
||||
name = "k8s.io/kube-openapi"
|
||||
packages = [
|
||||
"pkg/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/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
|
||||
|
||||
@@ -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].
|
||||
|
||||
80
docs/eschewedFeatures.md
Normal file
80
docs/eschewedFeatures.md
Normal 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
|
||||
@@ -36,3 +36,5 @@ 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.
|
||||
127
examples/multibases/README.md
Normal file
127
examples/multibases/README.md
Normal 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).
|
||||
2
examples/multibases/base/kustomization.yaml
Normal file
2
examples/multibases/base/kustomization.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- pod.yaml
|
||||
10
examples/multibases/base/pod.yaml
Normal file
10
examples/multibases/base/pod.yaml
Normal 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
|
||||
4
examples/multibases/dev/kustomization.yaml
Normal file
4
examples/multibases/dev/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
bases:
|
||||
- ./../base
|
||||
|
||||
namePrefix: dev-
|
||||
6
examples/multibases/kustomization.yaml
Normal file
6
examples/multibases/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
bases:
|
||||
- ./dev
|
||||
- ./staging
|
||||
- ./production
|
||||
|
||||
namePrefix: cluster-a-
|
||||
115
examples/multibases/multi-namespace.md
Normal file
115
examples/multibases/multi-namespace.md
Normal 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 $?
|
||||
```
|
||||
4
examples/multibases/production/kustomization.yaml
Normal file
4
examples/multibases/production/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
bases:
|
||||
- ./../base
|
||||
|
||||
namePrefix: prod-
|
||||
4
examples/multibases/staging/kustomization.yaml
Normal file
4
examples/multibases/staging/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
bases:
|
||||
- ./../base
|
||||
|
||||
namePrefix: staging-
|
||||
@@ -138,7 +138,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"))
|
||||
}
|
||||
@@ -292,7 +292,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
|
||||
}
|
||||
for _, v := range vars {
|
||||
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
|
||||
if r, found := m[id]; found {
|
||||
if r, found := m.DemandOneMatchForId(id); found {
|
||||
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
|
||||
|
||||
@@ -91,7 +91,7 @@ var svc = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
|
||||
|
||||
func TestResources1(t *testing.T) {
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
||||
resource.NewResIdWithPrefixNamespace(deploy, "dply1", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
@@ -123,7 +123,7 @@ func TestResources1(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResId(cmap, "literalConfigMap"): resource.NewResourceFromMap(
|
||||
resource.NewResIdWithPrefixNamespace(cmap, "literalConfigMap", "foo-", "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -143,7 +143,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 +164,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",
|
||||
@@ -289,7 +289,7 @@ func makeLoader2(t *testing.T) loader.Loader {
|
||||
// perhaps it's not worth supporting the command.
|
||||
func TestRawResources2(t *testing.T) {
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
||||
resource.NewResIdWithPrefix(deploy, "dply1", "foo-"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
type buildOptions struct {
|
||||
kustomizationPath string
|
||||
outputPath string
|
||||
}
|
||||
|
||||
// newCmdBuild creates a new build command.
|
||||
@@ -52,6 +53,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,7 +75,7 @@ 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))
|
||||
l := loader.NewFileLoader(fSys)
|
||||
|
||||
absPath, err := filepath.Abs(o.kustomizationPath)
|
||||
if err != nil {
|
||||
@@ -98,6 +103,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func (o *diffOptions) Validate(args []string) error {
|
||||
// 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))
|
||||
l := loader.NewFileLoader(fSys)
|
||||
|
||||
absPath, err := filepath.Abs(o.kustomizationPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -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,27 @@ import (
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
kustomizationFields = []string{"resources", "bases", "namePrefix", "namespace", "crds", "commonLabels", "commonAnnotations", "patches", "configMapGenerator", "secretGenerator", "vars", "imageTags"}
|
||||
recognizedFields = regexp.MustCompile("^(" + strings.Join(kustomizationFields, "|") + "):")
|
||||
)
|
||||
|
||||
// commentedField records the comment associated with a kustomization field
|
||||
// field has to be a recognized kustomization field
|
||||
// comment can be empty
|
||||
type commentedField struct {
|
||||
field string
|
||||
comment []byte
|
||||
}
|
||||
|
||||
func (cf *commentedField) appendComment(comment []byte) {
|
||||
cf.comment = append(cf.comment, comment...)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -77,6 +99,10 @@ func (mf *kustomizationFile) read() (*types.Kustomization, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = mf.parseCommentedFields(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kustomization, err
|
||||
}
|
||||
|
||||
@@ -84,11 +110,10 @@ 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)
|
||||
bytes, err := mf.marshal(kustomization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mf.fsys.WriteFile(mf.path, bytes)
|
||||
}
|
||||
|
||||
@@ -100,3 +125,95 @@ func stringInSlice(str string, list []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
|
||||
buffer := bytes.NewBuffer(content)
|
||||
var comments [][]byte
|
||||
var currentfield string
|
||||
|
||||
line, err := buffer.ReadBytes('\n')
|
||||
for err == nil {
|
||||
if isCommentOrBlankLine(line) {
|
||||
comments = append(comments, line)
|
||||
} else if recognizedFields.Match(line) {
|
||||
fields := recognizedFields.FindSubmatch(line)
|
||||
currentfield = string(fields[1])
|
||||
mf.originalFields = append(mf.originalFields, &commentedField{field: currentfield, comment: bytes.Join(comments, []byte(``))})
|
||||
comments = [][]byte{}
|
||||
} else if len(comments) > 0 {
|
||||
mf.originalFields[len(mf.originalFields)-1].appendComment(bytes.Join(comments, []byte(``)))
|
||||
comments = [][]byte{}
|
||||
}
|
||||
line, err = buffer.ReadBytes('\n')
|
||||
}
|
||||
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
|
||||
output := []byte{}
|
||||
for _, comment := range mf.originalFields {
|
||||
output = append(output, comment.comment...)
|
||||
content, err := marshalField(comment.field, kustomization)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
output = append(output, content...)
|
||||
}
|
||||
for _, field := range kustomizationFields {
|
||||
if mf.hasField(field) {
|
||||
continue
|
||||
}
|
||||
content, err := marshalField(field, kustomization)
|
||||
if err != nil {
|
||||
return content, nil
|
||||
}
|
||||
output = append(output, content...)
|
||||
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (mf *kustomizationFile) hasField(name string) bool {
|
||||
for _, n := range mf.originalFields {
|
||||
if n.field == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
isCommentOrBlankLine determines if a line is a comment or blank line
|
||||
Return true for following lines
|
||||
# This line is a comment
|
||||
# This line is also a comment with several leading white spaces
|
||||
|
||||
(The line above is a blank line)
|
||||
*/
|
||||
func isCommentOrBlankLine(line []byte) bool {
|
||||
s := bytes.TrimRight(bytes.TrimLeft(line, " "), "\n")
|
||||
return len(s) == 0 || bytes.HasPrefix(s, []byte(`#`))
|
||||
}
|
||||
|
||||
// marshalField marshal a given field of a kustomization object into yaml format.
|
||||
// If the field wasn't in the original kustomization.yaml file or wasn't added,
|
||||
// an empty []byte is returned.
|
||||
func marshalField(field string, kustomization *types.Kustomization) ([]byte, error) {
|
||||
r := reflect.ValueOf(*kustomization)
|
||||
v := r.FieldByName(strings.Title(field))
|
||||
|
||||
if !v.IsValid() || v.Len() == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
k := &types.Kustomization{}
|
||||
kr := reflect.ValueOf(k)
|
||||
kv := kr.Elem().FieldByName(strings.Title(field))
|
||||
kv.Set(v)
|
||||
|
||||
return yaml.Marshal(k)
|
||||
}
|
||||
|
||||
@@ -88,3 +88,136 @@ func TestNewNotExist(t *testing.T) {
|
||||
t.Fatalf("expect an error contains %q, but got %v", contained, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreserveComments(t *testing.T) {
|
||||
kustomizationContentWithComments := []byte(
|
||||
`# shem qing some comments
|
||||
# This is some comment we should preserve
|
||||
# don't delete it
|
||||
resources:
|
||||
- pod.yaml
|
||||
- service.yaml
|
||||
# something you may want to keep
|
||||
vars:
|
||||
- fieldref:
|
||||
fieldPath: metadata.name
|
||||
name: MY_SERVICE_NAME
|
||||
objref:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
name: my-service
|
||||
bases:
|
||||
- ../namespaces
|
||||
# some descriptions for the patches
|
||||
patches:
|
||||
- service.yaml
|
||||
- pod.yaml
|
||||
`)
|
||||
fsys := fs.MakeFakeFS()
|
||||
fsys.Create(constants.KustomizationFileName)
|
||||
fsys.WriteFile(constants.KustomizationFileName, kustomizationContentWithComments)
|
||||
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %v", err)
|
||||
}
|
||||
kustomization, err := mf.read()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %v", err)
|
||||
}
|
||||
if err = mf.write(kustomization); err != nil {
|
||||
t.Fatalf("Unexpected Error: %v", err)
|
||||
}
|
||||
bytes, _ := fsys.ReadFile(mf.path)
|
||||
|
||||
if !reflect.DeepEqual(kustomizationContentWithComments, bytes) {
|
||||
t.Fatal("written kustomization with comments is not the same as original one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreserveCommentsWithAdjust(t *testing.T) {
|
||||
kustomizationContentWithComments := []byte(`
|
||||
|
||||
|
||||
|
||||
# shem qing some comments
|
||||
# This is some comment we should preserve
|
||||
# don't delete it
|
||||
resources:
|
||||
- pod.yaml
|
||||
# See which field this comment goes into
|
||||
- service.yaml
|
||||
|
||||
|
||||
# something you may want to keep
|
||||
vars:
|
||||
- fieldref:
|
||||
fieldPath: metadata.name
|
||||
name: MY_SERVICE_NAME
|
||||
objref:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
name: my-service
|
||||
|
||||
bases:
|
||||
- ../namespaces
|
||||
|
||||
# some descriptions for the patches
|
||||
|
||||
patches:
|
||||
- service.yaml
|
||||
- pod.yaml
|
||||
`)
|
||||
|
||||
expected := []byte(`
|
||||
|
||||
|
||||
|
||||
# shem qing some comments
|
||||
# This is some comment we should preserve
|
||||
# don't delete it
|
||||
# See which field this comment goes into
|
||||
resources:
|
||||
- pod.yaml
|
||||
- service.yaml
|
||||
|
||||
|
||||
# something you may want to keep
|
||||
vars:
|
||||
- fieldref:
|
||||
fieldPath: metadata.name
|
||||
name: MY_SERVICE_NAME
|
||||
objref:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
name: my-service
|
||||
|
||||
bases:
|
||||
- ../namespaces
|
||||
|
||||
# some descriptions for the patches
|
||||
|
||||
patches:
|
||||
- service.yaml
|
||||
- pod.yaml
|
||||
`)
|
||||
fsys := fs.MakeFakeFS()
|
||||
fsys.Create(constants.KustomizationFileName)
|
||||
fsys.WriteFile(constants.KustomizationFileName, kustomizationContentWithComments)
|
||||
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %v", err)
|
||||
}
|
||||
|
||||
kustomization, err := mf.read()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %v", err)
|
||||
}
|
||||
if err = mf.write(kustomization); err != nil {
|
||||
t.Fatalf("Unexpected Error: %v", err)
|
||||
}
|
||||
bytes, _ := fsys.ReadFile(mf.path)
|
||||
|
||||
if !reflect.DeepEqual(expected, bytes) {
|
||||
t.Fatal("written kustomization with comments is not the same as original one\n", string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -71,11 +73,11 @@ func (o *setImageTagOptions) Validate(args []string) error {
|
||||
}
|
||||
o.imageTagMap = make(map[string]string)
|
||||
for _, arg := range args {
|
||||
imagetag := strings.Split(arg, ":")
|
||||
if len(imagetag) != 2 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
39
pkg/commands/util.go
Normal 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
|
||||
}
|
||||
@@ -136,8 +136,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 {
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// SecretFactory makes Secrets.
|
||||
@@ -23,8 +42,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,16 +52,72 @@ 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)
|
||||
|
||||
pairs, err := f.keyValuesFromEnvFileCommand(args.EnvCommand)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||
"env source file: %s",
|
||||
args.EnvCommand))
|
||||
}
|
||||
all = append(all, pairs...)
|
||||
|
||||
pairs, err = f.keyValuesFromCommands(args.Commands)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||
"commands %v", args.Commands))
|
||||
}
|
||||
all = append(all, pairs...)
|
||||
|
||||
for _, kv := range all {
|
||||
err = addKvToSecret(s, kv.key, kv.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
|
||||
// Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap.
|
||||
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
|
||||
}
|
||||
if _, entryExists := secret.Data[keyName]; entryExists {
|
||||
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
|
||||
}
|
||||
secret.Data[keyName] = []byte(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *SecretFactory) keyValuesFromEnvFileCommand(cmd string) ([]kvPair, error) {
|
||||
content, err := f.createSecretKey(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keyValuesFromLines(content)
|
||||
}
|
||||
|
||||
func (f *SecretFactory) keyValuesFromCommands(sources map[string]string) ([]kvPair, error) {
|
||||
var kvs []kvPair
|
||||
for k, cmd := range sources {
|
||||
content, err := f.createSecretKey(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kvs = append(kvs, kvPair{key: k, value: string(content)})
|
||||
}
|
||||
return kvs, nil
|
||||
}
|
||||
|
||||
// Run a command, return its output as the secret.
|
||||
func (f *SecretFactory) createSecretKey(command string) ([]byte, error) {
|
||||
if !f.fSys.IsDir(f.wd) {
|
||||
|
||||
@@ -56,7 +56,7 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
|
||||
}
|
||||
|
||||
for id, obj := range in {
|
||||
f, err := dir.newFile(id.String())
|
||||
f, err := dir.newFile(id.GvknString())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,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}
|
||||
}
|
||||
@@ -63,8 +63,3 @@ func (f FakeLoader) New(newRoot string) (loader.Loader, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -26,32 +26,59 @@ 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) {
|
||||
if !l.IsAbsPath(l.root, newRoot) {
|
||||
return nil, fmt.Errorf("Not abs path: l.root='%s', loc='%s'\n", l.root, newRoot)
|
||||
}
|
||||
root, err := l.fullLocation(l.root, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newFileLoaderAtRoot(root, l.fSys), nil
|
||||
}
|
||||
|
||||
// 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 +101,11 @@ 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -17,8 +17,6 @@ limitations under the License.
|
||||
// Package loader has a data loading interface and various implementations.
|
||||
package loader
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Loader interface exposes methods to read bytes.
|
||||
type Loader interface {
|
||||
// Root returns the root location for this Loader.
|
||||
@@ -27,63 +25,4 @@ 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
root, err := l.fLoader.FullLocation(l.root, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &loaderImpl{root: root, fLoader: l.fLoader}, 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -217,10 +232,12 @@ func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
|
||||
// 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]
|
||||
for _, m := range maps[1:] {
|
||||
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 +253,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'")
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dply2
|
||||
---
|
||||
# some comment
|
||||
---
|
||||
---
|
||||
`
|
||||
|
||||
l := loadertest.NewFakeLoader("/home/seans/project")
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,6 +75,20 @@ 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)
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 that the patch %#v can apply", matchedIds, id)
|
||||
}
|
||||
id = matchedIds[0]
|
||||
base := baseResourceMap[id]
|
||||
merged := map[string]interface{}{}
|
||||
versionedObj, err := scheme.Scheme.New(id.Gvk())
|
||||
baseName := base.GetName()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -44,8 +44,8 @@ type Kustomization struct {
|
||||
// URLs and globs.
|
||||
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
|
||||
@@ -115,12 +115,20 @@ 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"`
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
Reference in New Issue
Block a user