Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5563f1529b | ||
|
|
a95b26182a | ||
|
|
934e37d781 | ||
|
|
40bad7783f | ||
|
|
44696d5fb9 | ||
|
|
9d268de5a0 | ||
|
|
ab0c9b4118 | ||
|
|
37720765fc | ||
|
|
09e64e5991 | ||
|
|
e017d04a16 | ||
|
|
b6efde13eb | ||
|
|
f774172927 | ||
|
|
9459665c96 | ||
|
|
7fb4eaec60 | ||
|
|
211cda054e | ||
|
|
5f75564ff5 | ||
|
|
6b5569d5ed | ||
|
|
e20edaf41e | ||
|
|
d934a28caf | ||
|
|
e002b69ffa | ||
|
|
bdad67c3ef | ||
|
|
92864ba0f7 | ||
|
|
ba45b1366a | ||
|
|
d06620c74d | ||
|
|
4f3f76addd | ||
|
|
71b7e07b02 | ||
|
|
27f652ae8e | ||
|
|
5c8d82aed0 | ||
|
|
c994130005 | ||
|
|
ccd255f323 | ||
|
|
2b05d39067 | ||
|
|
de65503177 | ||
|
|
7c4ca21578 | ||
|
|
efcc167c35 | ||
|
|
1f8e56a210 | ||
|
|
fa8d6f08e2 | ||
|
|
900ac005dd | ||
|
|
a42c72b2e0 | ||
|
|
a82bd23f8f | ||
|
|
ea24b3795c | ||
|
|
95f568b857 | ||
|
|
708cd7ef17 | ||
|
|
ed7261ca01 | ||
|
|
8a27162d98 | ||
|
|
2e0e43cd76 | ||
|
|
ba2866645e | ||
|
|
fd2ee6034b | ||
|
|
86241dfd98 | ||
|
|
1a28f3a391 | ||
|
|
35344c163a | ||
|
|
526ba2df0c | ||
|
|
db15bc6372 | ||
|
|
c83b7019f5 | ||
|
|
08e2a1047b | ||
|
|
69d816d5ab | ||
|
|
ea2d2c9db1 | ||
|
|
c7ef8a7e4d | ||
|
|
9bda5cf092 | ||
|
|
815db033cb | ||
|
|
501e1a406d | ||
|
|
6e54814b6d | ||
|
|
df2407ad3f | ||
|
|
d0c6a824c8 | ||
|
|
57f3743be8 | ||
|
|
ef71cb478f | ||
|
|
9a5c0f5a25 | ||
|
|
0e2c71cd6f | ||
|
|
0b3ab31248 | ||
|
|
49f586af39 | ||
|
|
487a6ebee4 | ||
|
|
bee9490129 | ||
|
|
8afba0b56c | ||
|
|
16fa3403a4 | ||
|
|
bcb89ee908 | ||
|
|
37f03b4d01 | ||
|
|
bc144275b4 | ||
|
|
7086e4f974 | ||
|
|
4d111436aa | ||
|
|
1583486546 | ||
|
|
10b4c5db43 | ||
|
|
5a54c96203 | ||
|
|
4a5b82333b | ||
|
|
e3934ee69c | ||
|
|
24daa9e3dc | ||
|
|
d9b422cc54 | ||
|
|
d62fb53472 | ||
|
|
00e52095d6 | ||
|
|
9c6d31d70e | ||
|
|
a734b96560 | ||
|
|
8f3fe7d4bd | ||
|
|
5f950813c4 | ||
|
|
920f53853b | ||
|
|
aa88a0563b |
@@ -12,6 +12,7 @@ before_install:
|
||||
- go get -u golang.org/x/tools/cmd/goimports
|
||||
- go get -u github.com/onsi/ginkgo/ginkgo
|
||||
- go get -u github.com/monopole/mdrip
|
||||
- go get -u github.com/fzipp/gocyclo
|
||||
|
||||
# Install must be set to prevent default `go get` to run.
|
||||
# The dependencies have already been vendored by `dep` so
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
1. Fork the repo, develop and test your code.
|
||||
See the [github workflow guide].
|
||||
1. For _new features_, provide a markdown-based demo following
|
||||
the pattern established in the [demos](demos) directory.
|
||||
the pattern established in the [examples](examples) directory.
|
||||
Run `bin/pre-commit.sh` to test your demo.
|
||||
1. Submit a pull request.
|
||||
|
||||
|
||||
117
Gopkg.lock
generated
@@ -21,7 +21,10 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = ["proto","sortkeys"]
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys"
|
||||
]
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
@@ -33,7 +36,13 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
@@ -45,7 +54,11 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/googleapis/gnostic"
|
||||
packages = ["OpenAPIv2","compiler","extensions"]
|
||||
packages = [
|
||||
"OpenAPIv2",
|
||||
"compiler",
|
||||
"extensions"
|
||||
]
|
||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||
version = "v0.1.0"
|
||||
|
||||
@@ -73,6 +86,12 @@
|
||||
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
@@ -88,12 +107,32 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["http/httpguts","http2","http2/hpack","idna"]
|
||||
packages = [
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna"
|
||||
]
|
||||
revision = "2491c5de3490fced2f6cff376127c667efeed857"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
]
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
@@ -112,13 +151,75 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "k8s.io/api"
|
||||
packages = ["admissionregistration/v1alpha1","admissionregistration/v1beta1","apps/v1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","events/v1beta1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1alpha1","storage/v1beta1"]
|
||||
packages = [
|
||||
"admissionregistration/v1alpha1",
|
||||
"admissionregistration/v1beta1",
|
||||
"apps/v1",
|
||||
"apps/v1beta1",
|
||||
"apps/v1beta2",
|
||||
"authentication/v1",
|
||||
"authentication/v1beta1",
|
||||
"authorization/v1",
|
||||
"authorization/v1beta1",
|
||||
"autoscaling/v1",
|
||||
"autoscaling/v2beta1",
|
||||
"batch/v1",
|
||||
"batch/v1beta1",
|
||||
"batch/v2alpha1",
|
||||
"certificates/v1beta1",
|
||||
"core/v1",
|
||||
"events/v1beta1",
|
||||
"extensions/v1beta1",
|
||||
"networking/v1",
|
||||
"policy/v1beta1",
|
||||
"rbac/v1",
|
||||
"rbac/v1alpha1",
|
||||
"rbac/v1beta1",
|
||||
"scheduling/v1alpha1",
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1"
|
||||
]
|
||||
revision = "53d615ae3f440f957cb9989d989d597f047262d9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = ["pkg/api/resource","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/mergepatch","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/strategicpatch","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/watch","third_party/forked/golang/json","third_party/forked/golang/reflect"]
|
||||
packages = [
|
||||
"pkg/api/resource",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
"pkg/labels",
|
||||
"pkg/runtime",
|
||||
"pkg/runtime/schema",
|
||||
"pkg/runtime/serializer",
|
||||
"pkg/runtime/serializer/json",
|
||||
"pkg/runtime/serializer/protobuf",
|
||||
"pkg/runtime/serializer/recognizer",
|
||||
"pkg/runtime/serializer/versioning",
|
||||
"pkg/selection",
|
||||
"pkg/types",
|
||||
"pkg/util/errors",
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/mergepatch",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/strategicpatch",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/wait",
|
||||
"pkg/util/yaml",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/json",
|
||||
"third_party/forked/golang/reflect"
|
||||
]
|
||||
revision = "13b73596e4b63e03203e86f6d9c7bcc1b937c62f"
|
||||
|
||||
[[projects]]
|
||||
@@ -142,6 +243,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "6e36daa1798b0dae7f45160f9275ca6bbe6c5667de7cd808d0057cbaf19fc55e"
|
||||
inputs-digest = "e966d7880a29cf5669060d6564407f0f4c164e93eb844c22efec383383af2d3e"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
27
INSTALL.md
Normal file
@@ -0,0 +1,27 @@
|
||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||
[Go]: https://golang.org
|
||||
|
||||
## Installation
|
||||
|
||||
Download a binary from the [release page].
|
||||
|
||||
Or try this to grab the latest official release
|
||||
using the command line:
|
||||
|
||||
```
|
||||
opsys=linux # or darwin, or windows
|
||||
curl -s https://api.github.com/repos/kubernetes-sigs/kustomize/releases/latest |\
|
||||
grep browser_download |\
|
||||
grep $opsys |\
|
||||
cut -d '"' -f 4 |\
|
||||
xargs curl -O -L
|
||||
mv kustomize_*_${opsys}_amd64 kustomize
|
||||
chmod u+x kustomize
|
||||
```
|
||||
|
||||
To install from head with [Go] v1.10.1 or higher:
|
||||
|
||||
<!-- @installkustomize @test -->
|
||||
```
|
||||
go get github.com/kubernetes-sigs/kustomize
|
||||
```
|
||||
166
README.md
@@ -1,71 +1,141 @@
|
||||
# kustomize
|
||||
|
||||
[applied]: docs/glossary.md#apply
|
||||
[base]: docs/glossary.md#base
|
||||
[declarative configuration]: docs/glossary.md#declarative-application-management
|
||||
[demo]: demos/README.md
|
||||
[demos]: demos/README.md
|
||||
[imageBase]: docs/base.jpg
|
||||
[imageOverlay]: docs/overlay.jpg
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[KEP]: https://github.com/kubernetes/community/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
[resources]: docs/glossary.md#resource
|
||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||
[workflows]: docs/workflows.md
|
||||
`kustomize` lets you customize raw, template-free YAML
|
||||
files for multiple purposes, leaving the original YAML
|
||||
untouched and usable as is.
|
||||
|
||||
|
||||
`kustomize` is a command line tool supporting
|
||||
template-free customization of YAML (or JSON) objects
|
||||
that conform to the [kubernetes style]. If your
|
||||
objects have a `kind` and a `metadata` field,
|
||||
`kustomize` can patch them to support configuration
|
||||
sharing and re-use.
|
||||
|
||||
For more details, try a [demo].
|
||||
`kustomize` targets kubernetes; it understands and can
|
||||
patch [kubernetes style] API objects. It's like
|
||||
[`make`], in that what it does is declared in a file,
|
||||
and it's like [`sed`], in that it emits editted text.
|
||||
|
||||
[](https://travis-ci.org/kubernetes-sigs/kustomize)
|
||||
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
This assumes [Go](https://golang.org/) (v1.10.1 or higher)
|
||||
is installed and your `PATH` contains `$GOPATH/bin`:
|
||||
|
||||
<!-- @installkustomize @test -->
|
||||
```
|
||||
go get github.com/kubernetes-sigs/kustomize
|
||||
```
|
||||
|
||||
**Installation**: Download a binary from the [release
|
||||
page], or see these [install] notes. Then try one of
|
||||
the tested [examples].
|
||||
|
||||
## Usage
|
||||
|
||||
#### 1) Make a base
|
||||
|
||||
A [base] configuration is a [kustomization] file listing a set of
|
||||
k8s [resources] - deployments, services, configmaps,
|
||||
secrets that serve some common purpose.
|
||||
### 1) Make a [kustomization] file
|
||||
|
||||
In some directory containing your YAML [resource]
|
||||
files (deployments, services, configmaps, etc.), create a
|
||||
[kustomization] file.
|
||||
|
||||
This file should declare those resources, and any
|
||||
customization to apply to them, e.g. _add a common
|
||||
label_.
|
||||
|
||||
![base image][imageBase]
|
||||
|
||||
#### 2) Customize it with overlays
|
||||
File structure:
|
||||
|
||||
An [overlay] customizes your base along different dimensions
|
||||
for different purposes or different teams, e.g. for
|
||||
_development, staging and production_.
|
||||
> ```
|
||||
> ~/someApp
|
||||
> ├── deployment.yaml
|
||||
> ├── kustomization.yaml
|
||||
> └── service.yaml
|
||||
> ```
|
||||
|
||||
The resources in this directory could be a fork of
|
||||
someone else's configuration. If so, you can easily
|
||||
rebase from the source material to capture
|
||||
improvements, because you don't modify the resources
|
||||
directly.
|
||||
|
||||
Generate customized YAML with:
|
||||
|
||||
```
|
||||
kustomize build ~/someApp
|
||||
```
|
||||
|
||||
The YAML can be directly [applied] to a cluster:
|
||||
|
||||
> ```
|
||||
> kustomize build ~/someApp | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
|
||||
### 2) Create [variants] using [overlays]
|
||||
|
||||
Manage traditional [variants] of a configuration - like
|
||||
_development_, _staging_ and _production_ - using
|
||||
[overlays] that modify a common [base].
|
||||
|
||||
![overlay image][imageOverlay]
|
||||
|
||||
#### 3) Run kustomize
|
||||
File structure:
|
||||
> ```
|
||||
> ~/someApp
|
||||
> ├── base
|
||||
> │ ├── deployment.yaml
|
||||
> │ ├── kustomization.yaml
|
||||
> │ └── service.yaml
|
||||
> └── overlays
|
||||
> ├── development
|
||||
> │ ├── cpu_count.yaml
|
||||
> │ ├── kustomization.yaml
|
||||
> │ └── replica_count.yaml
|
||||
> └── production
|
||||
> ├── cpu_count.yaml
|
||||
> ├── kustomization.yaml
|
||||
> └── replica_count.yaml
|
||||
> ```
|
||||
|
||||
Run `kustomize` on your overlay. The result
|
||||
is printed to `stdout` as a set of complete
|
||||
resources, ready to be [applied] to a cluster.
|
||||
See the [demos].
|
||||
Take the work from step (1) above, move it into a
|
||||
`someApp` subdirectory called `base`, then
|
||||
place overlays in a sibling directory.
|
||||
|
||||
An overlay is just another kustomization, refering to
|
||||
the base, and referring to patches to apply to that
|
||||
base.
|
||||
|
||||
This arrangement makes it easy to manage your
|
||||
configuration with `git`. The base could have files
|
||||
from an upstream repository managed by someone else.
|
||||
The overlays could be in a repository you own.
|
||||
Arranging the repo clones as siblings on disk avoids
|
||||
the need for git submodules (though that works fine, if
|
||||
you are a submodule fan).
|
||||
|
||||
Generate YAML with
|
||||
|
||||
```sh
|
||||
kustomize build ~/someApp/overlays/production
|
||||
```
|
||||
|
||||
The YAML can be directly [applied] to a cluster:
|
||||
|
||||
> ```sh
|
||||
> kustomize build ~/someApp/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
## About
|
||||
|
||||
This project sponsored by [sig-cli] ([KEP]).
|
||||
This tool is sponsored by [sig-cli] ([KEP]).
|
||||
|
||||
|
||||
[KEP]: https://github.com/kubernetes/community/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[`make`]: https://www.gnu.org/software/make
|
||||
[`sed`]: https://www.gnu.org/software/sed
|
||||
[applied]: docs/glossary.md#apply
|
||||
[base]: docs/glossary.md#base
|
||||
[declarative configuration]: docs/glossary.md#declarative-application-management
|
||||
[examples]: examples/README.md
|
||||
[imageBase]: docs/base.jpg
|
||||
[imageOverlay]: docs/overlay.jpg
|
||||
[install]: INSTALL.md
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
[overlays]: docs/glossary.md#overlay
|
||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||
[resource]: docs/glossary.md#resource
|
||||
[resources]: docs/glossary.md#resource
|
||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||
[variant]: docs/glossary.md#variant
|
||||
[variants]: docs/glossary.md#variant
|
||||
[workflows]: docs/workflows.md
|
||||
|
||||
@@ -31,10 +31,19 @@ function testGoFmt {
|
||||
diff <(echo -n) <(go_dirs | xargs -0 gofmt -s -d -l)
|
||||
}
|
||||
|
||||
|
||||
function testGoCyclo {
|
||||
diff <(echo -n) <(go_dirs | xargs -0 gocyclo -over 15)
|
||||
}
|
||||
|
||||
function testGoImports {
|
||||
diff -u <(echo -n) <(go_dirs | xargs -0 goimports -l)
|
||||
}
|
||||
|
||||
function testGoLint {
|
||||
diff -u <(echo -n) <(go_dirs | xargs -0 golint --min_confidence 0.85 )
|
||||
}
|
||||
|
||||
function testGoVet {
|
||||
go vet -all ./...
|
||||
}
|
||||
@@ -43,14 +52,22 @@ function testGoTest {
|
||||
go test -v ./...
|
||||
}
|
||||
|
||||
function testDemos {
|
||||
mdrip --mode test --label test README.md ./demos
|
||||
function testExamples {
|
||||
mdrip --mode test --label test README.md ./examples
|
||||
}
|
||||
|
||||
runTest testGoFmt
|
||||
runTest testGoImports
|
||||
runTest testGoLint
|
||||
runTest testGoVet
|
||||
runTest testGoCyclo
|
||||
runTest testGoTest
|
||||
runTest testDemos
|
||||
runTest testExamples
|
||||
|
||||
if [ $rc -eq 0 ]; then
|
||||
echo "SUCCESS!"
|
||||
else
|
||||
echo "FAILURE; exit code $rc"
|
||||
fi
|
||||
|
||||
exit $rc
|
||||
|
||||
@@ -1,13 +1,45 @@
|
||||
## Overview
|
||||
[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
|
||||
|
||||
This directory contains scripts and configuration files for publishing a
|
||||
`kustomize` release on [release page](https://github.com/kubernetes-sigs/kustomize/releases)
|
||||
Scripts and configuration files for publishing a
|
||||
`kustomize` release on the [releases page].
|
||||
|
||||
## Steps to run build a release locally
|
||||
Install container-builder-local from [github](https://github.com/GoogleCloudPlatform/container-builder-local).
|
||||
### Build a release locally
|
||||
|
||||
```sh
|
||||
container-builder-local --config=build/cloudbuild_local.yaml --dryrun=false --write-workspace=/tmp/w .
|
||||
Install [`container-builder-local`], then run
|
||||
|
||||
```
|
||||
container-builder-local \
|
||||
--config=build/cloudbuild_local.yaml \
|
||||
--dryrun=false --write-workspace=/tmp/w .
|
||||
```
|
||||
|
||||
You will find the build artifacts under `/tmp/w/dist` directory
|
||||
to build artifacts under `/tmp/w/dist`.
|
||||
|
||||
### Publish a Release
|
||||
|
||||
Get on an up-to-date master branch:
|
||||
```
|
||||
git checkout master
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
```
|
||||
|
||||
Define the version (see [semver principles](https://semver.org)), e.g.:
|
||||
```
|
||||
version=v1.0.3
|
||||
```
|
||||
|
||||
Tag the repo:
|
||||
```
|
||||
git tag -a $version -m "$version release"
|
||||
```
|
||||
|
||||
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].
|
||||
|
||||
BIN
docs/base.jpg
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
@@ -68,25 +68,17 @@ management in k8s.
|
||||
|
||||
A _base_ is a [target] that some [overlay] modifies.
|
||||
|
||||
Any target, including an overlay, can be a base to
|
||||
Any target, including an [overlay], can be a base to
|
||||
another target.
|
||||
|
||||
A base has no knowledge of the overlays that refer to it.
|
||||
|
||||
A base is usable in isolation, i.e. one should
|
||||
be able to [apply] a base to a cluster directly.
|
||||
|
||||
For simple [gitops] management, a base configuration
|
||||
could be the _sole content of a git repository
|
||||
dedicated to that purpose_. Same with [overlays].
|
||||
Changes in a repo could generate a build, test and
|
||||
deploy cycle.
|
||||
|
||||
Some of the demos for [kustomize] will break from this
|
||||
idiom and store all demo config files in directories
|
||||
_next_ to the `kustomize` code so that the code and
|
||||
demos can be more easily maintained by the same group
|
||||
of people.
|
||||
|
||||
## bespoke configuration
|
||||
|
||||
@@ -162,10 +154,10 @@ It's often abbreviated as _k8s_.
|
||||
|
||||
An object, expressed in a YAML or JSON file, with the
|
||||
[fields required] by kubernetes. Basically just a
|
||||
`kind` field to identify the type, a `metadata/name`
|
||||
field to identify the variant, and an `apiVersion`
|
||||
field to identify the version (if there's more than one
|
||||
version).
|
||||
_kind_ field to identify the type, a _metadata/name_
|
||||
field to identify the particular instance, and an
|
||||
_apiVersion_ field to identify the version (if there's
|
||||
more than one version).
|
||||
|
||||
## kustomize
|
||||
|
||||
@@ -210,19 +202,24 @@ An _overlay_ is a [target] that modifies (and thus
|
||||
depends on) another target.
|
||||
|
||||
The [kustomization] in an overlay refers to (via file
|
||||
path, URI or other method) _some other kustomization_,
|
||||
path, URI or other method) some other kustomization,
|
||||
known as its [base].
|
||||
|
||||
An overlay is unusable without its base.
|
||||
|
||||
An overlay supports the typical notion of a
|
||||
_development_, _QA_, _staging_ and _production_
|
||||
environment variants.
|
||||
An overlay may act as a base to another overlay.
|
||||
|
||||
The configuration of these environments is specified in
|
||||
individual overlays (one per environment) that all
|
||||
refer to a common base that holds common configuration.
|
||||
One configures the cluster like this:
|
||||
Overlays make the most sense when there is _more than
|
||||
one_, because they create different [variants] of a
|
||||
common base - e.g. _development_, _QA_, _staging_ and
|
||||
_production_ environment variants.
|
||||
|
||||
These variants use the same overall resources, and vary
|
||||
in relatively simple ways, e.g. the number of replicas
|
||||
in a deployment, the CPU to a particular pod, the data
|
||||
source used in a configmap, etc.
|
||||
|
||||
One configures a cluster like this:
|
||||
|
||||
> ```
|
||||
> kustomize build someapp/overlays/staging |\
|
||||
@@ -232,10 +229,9 @@ One configures the cluster like this:
|
||||
> kubectl apply -f -
|
||||
> ```
|
||||
|
||||
Usage of the base is implicit (the overlay's kustomization
|
||||
points to the base).
|
||||
Usage of the base is implicit - the overlay's
|
||||
kustomization points to the base.
|
||||
|
||||
An overlay may act as a base to another overlay.
|
||||
|
||||
## package
|
||||
|
||||
@@ -267,7 +263,7 @@ configmap.
|
||||
|
||||
More generally, a resource can be any correct YAML file
|
||||
that [defines an object](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields)
|
||||
with a `kind` and a `metadata/name` field.
|
||||
with a _kind_ and a _metadata/name_ field.
|
||||
|
||||
|
||||
A _resource_ in the content of a REST-ful API is the
|
||||
@@ -290,8 +286,7 @@ The _target_ is the argument to `kustomize build`, e.g.:
|
||||
> ```
|
||||
|
||||
`$target` must be a path to a directory that
|
||||
immediately contains a file called
|
||||
`kustomization.yaml` (i.e. a [kustomization]).
|
||||
immediately contains a [kustomization].
|
||||
|
||||
The target contains, or refers to, all the information
|
||||
needed to create customized resources to send to the
|
||||
@@ -304,15 +299,15 @@ A target is a [base] or an [overlay].
|
||||
A _variant_ is the outcome, in a cluster, of applying
|
||||
an [overlay] to a [base].
|
||||
|
||||
> E.g., a _staging_ and _production_ overlay both modify some
|
||||
> common base to create distinct variants.
|
||||
>
|
||||
> The _staging_ variant is the set of resources
|
||||
> exposed to quality assurance testing, or to some
|
||||
> external users who'd like to see what the next
|
||||
> version of production will look like.
|
||||
>
|
||||
> The _production_ variant is the set of resources
|
||||
> exposed to production traffic, and thus may employ
|
||||
> deployments with a large number of replicas and higher
|
||||
> cpu and memory requests.
|
||||
E.g., a _staging_ and _production_ overlay both modify
|
||||
some common base to create distinct variants.
|
||||
|
||||
The _staging_ variant is the set of resources exposed
|
||||
to quality assurance testing, or to some external users
|
||||
who'd like to see what the next version of production
|
||||
will look like.
|
||||
|
||||
The _production_ variant is the set of resources
|
||||
exposed to production traffic, and thus may employ
|
||||
deployments with a large number of replicas and higher
|
||||
cpu and memory requests.
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
# visible in configuration reviews.
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Adds namespace to all resources.
|
||||
namespace: my-namespace
|
||||
|
||||
# Value of this field is prepended to the
|
||||
# names of all resources, e.g. a deployment named
|
||||
|
||||
BIN
docs/overlay.jpg
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
@@ -10,7 +10,7 @@
|
||||
[patch]: glossary.md#patch
|
||||
[patches]: glossary.md#patch
|
||||
[rebase]: https://git-scm.com/docs/git-rebase
|
||||
[resources]: glossary.md#resources
|
||||
[resources]: glossary.md#resource
|
||||
[workflowBespoke]: workflowBespoke.jpg
|
||||
[workflowOts]: workflowOts.jpg
|
||||
|
||||
@@ -64,8 +64,8 @@ specified in the base.
|
||||
Run kustomize, and pipe the output to [apply].
|
||||
|
||||
> ```
|
||||
> kustomize ~/ldap/overlays/staging | kubectl apply -f -
|
||||
> kustomize ~/ldap/overlays/production | kubectl apply -f -
|
||||
> kustomize build ~/ldap/overlays/staging | kubectl apply -f -
|
||||
> kustomize build ~/ldap/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
|
||||
@@ -85,13 +85,13 @@ is periodically consulted for updates.
|
||||
|
||||
The [base] directory is maintained in a repo whose
|
||||
upstream is an [OTS] configuration, in this case
|
||||
https://github.com/kinflate/ldap.
|
||||
some user's `ldap` repo:
|
||||
|
||||
> ```
|
||||
> mkdir ~/ldap
|
||||
> git clone https://github.com/$USER/ldap ~/ldap/base
|
||||
> cd ~/ldap/base
|
||||
> git remote add upstream git@github.com:kustomize/ldap
|
||||
> git remote add upstream git@github.com:$USER/ldap
|
||||
> ```
|
||||
|
||||
#### 3) create [overlays]
|
||||
@@ -107,17 +107,19 @@ The [overlays] are siblings to each other and to the
|
||||
> mkdir -p ~/ldap/overlays/production
|
||||
> ```
|
||||
|
||||
The user can maintain the `overlays` directory in a
|
||||
distinct repository.
|
||||
|
||||
#### 4) bring up [variants]
|
||||
|
||||
> ```
|
||||
> kustomize ~/ldap/overlays/staging | kubectl apply -f -
|
||||
> kustomize ~/ldap/overlays/production | kubectl apply -f -
|
||||
> kustomize build ~/ldap/overlays/staging | kubectl apply -f -
|
||||
> kustomize build ~/ldap/overlays/production | kubectl apply -f -
|
||||
> ```
|
||||
|
||||
#### 5) (optionally) capture changes from upstream
|
||||
|
||||
The user can optionally [rebase] their [base] to
|
||||
The user can periodically [rebase] their [base] to
|
||||
capture changes made in the upstream repository.
|
||||
|
||||
> ```
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
# Demos
|
||||
# Examples
|
||||
|
||||
These demos assume that `kustomize` is on your `$PATH`.
|
||||
They are covered by pre-submit tests.
|
||||
These examples assume that `kustomize` is on your `$PATH`.
|
||||
|
||||
They are covered by [pre-commit](../bin/pre-commit.sh)
|
||||
tests, and should work with HEAD
|
||||
|
||||
<!-- @installkustomize @test -->
|
||||
```
|
||||
go get github.com/kubernetes-sigs/kustomize
|
||||
```
|
||||
|
||||
* [hello world](helloWorld/README.md) - Deploy multiple
|
||||
(differently configured) variants of a simple Hello
|
||||
@@ -22,3 +29,5 @@ They are covered by pre-submit tests.
|
||||
|
||||
* [breakfast](breakfast.md) - Customize breakfast for
|
||||
Alice and Bob.
|
||||
|
||||
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
|
||||
@@ -51,7 +51,7 @@ mkdir -p $BASE
|
||||
|
||||
curl -s -o "$BASE/#1.yaml" "https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/demos/helloWorld\
|
||||
/master/examples/helloWorld\
|
||||
/{configMap,deployment,kustomization,service}.yaml"
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#
|
||||
# This script assumes that the process running it has
|
||||
# checked out the kubernetes-sigs/kustomize repo, and
|
||||
# has cd'ed into it (i.e. the directory above "demos")
|
||||
# has cd'ed into it (i.e. the directory above "examples")
|
||||
# before running it.
|
||||
#
|
||||
# At time of writing, its 'call point' was in
|
||||
@@ -79,6 +79,6 @@ function runTest {
|
||||
|
||||
setUpEnv
|
||||
|
||||
pushd demos
|
||||
pushd examples
|
||||
runTest ldap/integration_test.sh ldap/base
|
||||
popd
|
||||
@@ -45,7 +45,7 @@ mkdir -p $BASE
|
||||
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/demos/ldap"
|
||||
/master/examples/ldap"
|
||||
|
||||
curl -s -o "$BASE/#1" "$CONTENT/base\
|
||||
/{deployment.yaml,kustomization.yaml,service.yaml,env.startup.txt}"
|
||||
@@ -29,7 +29,7 @@ Download them:
|
||||
```
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" "https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/demos/mySql\
|
||||
/master/examples/mySql\
|
||||
/{deployment,secret,service}.yaml"
|
||||
```
|
||||
|
||||
@@ -31,7 +31,7 @@ Download them:
|
||||
```
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/demos/springboot"
|
||||
/master/examples/springboot"
|
||||
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" \
|
||||
"$CONTENT/base/{deployment,service}.yaml"
|
||||
144
examples/wordpress/README.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Demo: Injecting k8s runtime data into containers
|
||||
|
||||
In this tutorial, you will learn how to use `kustomize` to declare a variable reference and substitute it in container's command.
|
||||
|
||||
To run WordPress, it's necessary to
|
||||
|
||||
- connect WordPress with a MySQL database
|
||||
- access the service name of MySQL database from WordPress container
|
||||
|
||||
First make a place to work:
|
||||
<!-- @makeDemoHome @test -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
MYSQL_HOME=$DEMO_HOME/mysql
|
||||
mkdir -p $MYSQL_HOME
|
||||
WORDPRESS_HOME=$DEMO_HOME/wordpress
|
||||
mkdir -p $WORDPRESS_HOME
|
||||
```
|
||||
|
||||
### Download resources
|
||||
|
||||
Download the resources and `kustomization.yaml` for WordPress.
|
||||
|
||||
<!-- @downloadResources @test -->
|
||||
```
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/examples/wordpress/wordpress"
|
||||
|
||||
curl -s -o "$WORDPRESS_HOME/#1.yaml" \
|
||||
"$CONTENT/{deployment,service,kustomization}.yaml"
|
||||
```
|
||||
|
||||
Download the resources and `kustomization.yaml` for MySQL.
|
||||
|
||||
<!-- @downloadResources @test -->
|
||||
```
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/examples/wordpress/mysql"
|
||||
|
||||
curl -s -o "$MYSQL_HOME/#1.yaml" \
|
||||
"$CONTENT/{deployment,service,secret,kustomization}.yaml"
|
||||
```
|
||||
|
||||
### Create kustomization.yaml
|
||||
Create a new kustomization with two bases:
|
||||
|
||||
<!-- @createKustomization @test -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||
bases:
|
||||
- wordpress
|
||||
- mysql
|
||||
namePrefix: demo-
|
||||
EOF
|
||||
```
|
||||
|
||||
### Download patch for WordPress
|
||||
In the new kustomization, apply a patch for wordpress deployment. The patch does two things
|
||||
- Add an initial container to show the mysql service name
|
||||
- Add environment variable that allow wordpress to find the mysql database
|
||||
|
||||
<!-- @downloadPatch @test -->
|
||||
```
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/examples/patch.yaml"
|
||||
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" \
|
||||
"$CONTENT/{patch}.yaml"
|
||||
```
|
||||
The patch has following content
|
||||
> ```
|
||||
> apiVersion: apps/v1beta2
|
||||
> kind: Deployment
|
||||
> metadata:
|
||||
> name: wordpress
|
||||
> spec:
|
||||
> template:
|
||||
> spec:
|
||||
> initContainers:
|
||||
> - name: init-command
|
||||
> image: debian
|
||||
> command:
|
||||
> - "echo $(WORDPRESS_SERVICE)"
|
||||
> - "echo $(MYSQL_SERVICE)"
|
||||
> containers:
|
||||
> - name: wordpress
|
||||
> env:
|
||||
> - name: WORDPRESS_DB_HOST
|
||||
> value: mysql
|
||||
> - name: WORDPRESS_DB_PASSWORD
|
||||
> valueFrom:
|
||||
> secretKeyRef:
|
||||
> name: mysql-pass
|
||||
> key: password
|
||||
> ```
|
||||
The init container's command requires information that depends on k8s resource object fields, represented by the placeholder variables
|
||||
$(WORDPRESS_SERVICE) and $(MYSQL_SERVICE).
|
||||
|
||||
### Bind the Variables to k8s Object Fields
|
||||
|
||||
<!-- @addVarRef @test -->
|
||||
```
|
||||
cat <<EOF >>$DEMO_HOME/kustomization.yaml
|
||||
vars:
|
||||
- name: WORDPRESS_SERVICE
|
||||
objref:
|
||||
kind: Service
|
||||
name: wordpress
|
||||
apiVersion: v1
|
||||
fieldref:
|
||||
fieldpath: metadata.name
|
||||
- name: MYSQL_SERVICE
|
||||
objref:
|
||||
kind: Service
|
||||
name: mysql
|
||||
apiVersion: v1
|
||||
EOF
|
||||
```
|
||||
`WORDPRESS_SERVICE` is from the field `metadata.name` of Service `wordpress`. If we don't specify `fieldref`, the default is `metadata.name`. So `MYSQL_SERVICE` is from the field `metadata.name` of Service `mysql`.
|
||||
|
||||
### Substitution
|
||||
Confirm the variable substitution:
|
||||
|
||||
<!-- @kustomizeBuild @test -->
|
||||
```
|
||||
kustomize build $DEMO_HOME
|
||||
```
|
||||
|
||||
Expect this in the output:
|
||||
|
||||
> ```
|
||||
> (truncated)
|
||||
> ...
|
||||
> initContainers:
|
||||
> - command:
|
||||
> - echo demo-wordpress
|
||||
> - echo demo-mysql
|
||||
> image: debian
|
||||
> name: init-command
|
||||
>
|
||||
> ```
|
||||
19
examples/wordpress/kustomization.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
bases:
|
||||
- wordpress
|
||||
- mysql
|
||||
patches:
|
||||
- patch.yaml
|
||||
namePrefix: demo-
|
||||
|
||||
vars:
|
||||
- name: WORDPRESS_SERVICE
|
||||
objref:
|
||||
kind: Service
|
||||
name: wordpress
|
||||
apiVersion: v1
|
||||
- name: MYSQL_SERVICE
|
||||
objref:
|
||||
kind: Service
|
||||
name: mysql
|
||||
apiVersion: v1
|
||||
|
||||
35
examples/wordpress/mysql/deployment.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mysql
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mysql
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
containers:
|
||||
- image: mysql:5.6
|
||||
name: mysql
|
||||
env:
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-pass
|
||||
key: password
|
||||
ports:
|
||||
- containerPort: 3306
|
||||
name: mysql
|
||||
volumeMounts:
|
||||
- name: mysql-persistent-storage
|
||||
mountPath: /var/lib/mysql
|
||||
volumes:
|
||||
- name: mysql-persistent-storage
|
||||
emptyDir: {}
|
||||
4
examples/wordpress/mysql/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- secret.yaml
|
||||
8
examples/wordpress/mysql/secret.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mysql-pass
|
||||
type: Opaque
|
||||
data:
|
||||
# Default password is "admin".
|
||||
password: YWRtaW4=
|
||||
11
examples/wordpress/mysql/service.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mysql
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
ports:
|
||||
- port: 3306
|
||||
selector:
|
||||
app: mysql
|
||||
23
examples/wordpress/patch.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: wordpress
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: init-command
|
||||
image: debian
|
||||
command:
|
||||
- "echo $(WORDPRESS_SERVICE)"
|
||||
- "echo $(MYSQL_SERVICE)"
|
||||
containers:
|
||||
- name: wordpress
|
||||
env:
|
||||
- name: WORDPRESS_DB_HOST
|
||||
value: mysql
|
||||
- name: WORDPRESS_DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-pass
|
||||
key: password
|
||||
29
examples/wordpress/wordpress/deployment.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
apiVersion: apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: wordpress
|
||||
labels:
|
||||
app: wordpress
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: wordpress
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: wordpress
|
||||
spec:
|
||||
containers:
|
||||
- image: wordpress:4.8-apache
|
||||
name: wordpress
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: wordpress
|
||||
volumeMounts:
|
||||
- name: wordpress-persistent-storage
|
||||
mountPath: /var/www/html
|
||||
volumes:
|
||||
- name: wordpress-persistent-storage
|
||||
emptyDir: {}
|
||||
3
examples/wordpress/wordpress/kustomization.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
12
examples/wordpress/wordpress/service.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wordpress
|
||||
labels:
|
||||
app: wordpress
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
selector:
|
||||
app: wordpress
|
||||
type: LoadBalancer
|
||||
@@ -14,44 +14,39 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package app implements state for the set of all resources being customized.
|
||||
// Should rename this - there's nothing "app"y about it.
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/transformers"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Application interface {
|
||||
// Resources computes and returns the resources for the app.
|
||||
Resources() (resource.ResourceCollection, error)
|
||||
// SemiResources computes and returns the resources without name hash and name reference for the app
|
||||
SemiResources() (resource.ResourceCollection, error)
|
||||
// RawResources computes and returns the raw resources from the kustomization file.
|
||||
// It contains resources from
|
||||
// 1) untransformed resources from current kustomization file
|
||||
// 2) transformed resources from sub packages
|
||||
RawResources() (resource.ResourceCollection, error)
|
||||
}
|
||||
|
||||
var _ Application = &applicationImpl{}
|
||||
|
||||
// Private implementation of the Application interface
|
||||
type applicationImpl struct {
|
||||
// Application implements the guts of the kustomize 'build' command.
|
||||
// TODO: Change name, as "application" is overloaded and somewhat
|
||||
// misleading (one can customize an RBAC policy). Perhaps "Target"
|
||||
// https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#target
|
||||
type Application struct {
|
||||
kustomization *types.Kustomization
|
||||
loader loader.Loader
|
||||
}
|
||||
|
||||
// NewApp parses the kustomization file at the path using the loader.
|
||||
func New(loader loader.Loader) (Application, error) {
|
||||
// NewApplication returns a new instance of Application primed with a Loader.
|
||||
func NewApplication(loader loader.Loader) (*Application, error) {
|
||||
content, err := loader.Load(constants.KustomizationFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -62,186 +57,7 @@ func New(loader loader.Loader) (Application, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &applicationImpl{kustomization: &m, loader: loader}, nil
|
||||
}
|
||||
|
||||
// Resources computes and returns the resources from the kustomization file.
|
||||
// The namehashing for configmap/secrets and resolving name reference is only done
|
||||
// in the most top overlay once at the end of getting resources.
|
||||
func (a *applicationImpl) Resources() (resource.ResourceCollection, error) {
|
||||
res, err := a.SemiResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := a.getHashAndReferenceTransformer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.Transform(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SemiResources computes and returns the resources without name hash and name reference for the app
|
||||
func (a *applicationImpl) SemiResources() (resource.ResourceCollection, error) {
|
||||
errs := &interror.KustomizationErrors{}
|
||||
raw, err := a.rawResources()
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
|
||||
cms, err := resource.NewFromConfigMaps(a.loader, a.kustomization.ConfigMapGenerator)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
secrets, err := resource.NewFromSecretGenerators(a.loader.Root(), a.kustomization.SecretGenerator)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
res, err := resource.Merge(cms, secrets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allRes, err := resource.MergeWithOverride(raw, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patches, err := resource.NewFromPatches(a.loader, a.kustomization.Patches)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
t, err := a.getTransformer(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.Transform(allRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return allRes, nil
|
||||
}
|
||||
|
||||
// RawResources computes and returns the raw resources from the kustomization file.
|
||||
// The namehashing for configmap/secrets and resolving name reference is only done
|
||||
// in the most top overlay once at the end of getting resources.
|
||||
func (a *applicationImpl) RawResources() (resource.ResourceCollection, error) {
|
||||
res, err := a.rawResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := a.getHashAndReferenceTransformer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.Transform(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a *applicationImpl) rawResources() (resource.ResourceCollection, error) {
|
||||
subAppResources, errs := a.subAppResources()
|
||||
resources, err := resource.NewFromResources(a.loader, a.kustomization.Resources)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return resource.Merge(resources, subAppResources)
|
||||
}
|
||||
|
||||
func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *interror.KustomizationErrors) {
|
||||
sliceOfSubAppResources := []resource.ResourceCollection{}
|
||||
errs := &interror.KustomizationErrors{}
|
||||
for _, pkgPath := range a.kustomization.Bases {
|
||||
subloader, err := a.loader.New(pkgPath)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
subapp, err := New(subloader)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
// Gather all transformed resources from subpackages.
|
||||
subAppResources, err := subapp.SemiResources()
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
sliceOfSubAppResources = append(sliceOfSubAppResources, subAppResources)
|
||||
}
|
||||
allResources, err := resource.Merge(sliceOfSubAppResources...)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
return allResources, errs
|
||||
}
|
||||
|
||||
// getTransformer generates the following transformers:
|
||||
// 1) apply overlay
|
||||
// 2) name prefix
|
||||
// 3) apply labels
|
||||
// 4) apply annotations
|
||||
func (a *applicationImpl) getTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
|
||||
ts := []transformers.Transformer{}
|
||||
|
||||
ot, err := transformers.NewOverlayTransformer(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, ot)
|
||||
|
||||
npt, err := transformers.NewDefaultingNamePrefixTransformer(string(a.kustomization.NamePrefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, npt)
|
||||
|
||||
lt, err := transformers.NewDefaultingLabelsMapTransformer(a.kustomization.CommonLabels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, lt)
|
||||
|
||||
at, err := transformers.NewDefaultingAnnotationsMapTransformer(a.kustomization.CommonAnnotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, at)
|
||||
|
||||
return transformers.NewMultiTransformer(ts), nil
|
||||
}
|
||||
|
||||
// getHashAndReferenceTransformer generates the following transformers:
|
||||
// 1) name hash for configmap and secrests
|
||||
// 2) apply name reference
|
||||
func (a *applicationImpl) getHashAndReferenceTransformer() (transformers.Transformer, error) {
|
||||
ts := []transformers.Transformer{}
|
||||
nht := transformers.NewNameHashTransformer()
|
||||
ts = append(ts, nht)
|
||||
|
||||
nrt, err := transformers.NewDefaultingNameReferenceTransformer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, nrt)
|
||||
return transformers.NewMultiTransformer(ts), nil
|
||||
return &Application{kustomization: &m, loader: loader}, nil
|
||||
}
|
||||
|
||||
func unmarshal(y []byte, o interface{}) error {
|
||||
@@ -254,3 +70,241 @@ func unmarshal(y []byte, o interface{}) error {
|
||||
dec.DisallowUnknownFields()
|
||||
return dec.Decode(o)
|
||||
}
|
||||
|
||||
// MakeCustomizedResMap creates a ResMap per kustomization instructions.
|
||||
// The Resources in the returned ResMap are fully customized.
|
||||
func (a *Application) MakeCustomizedResMap() (resmap.ResMap, error) {
|
||||
m, err := a.loadCustomizedResMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.resolveRefsToGeneratedResources(m)
|
||||
}
|
||||
|
||||
// MakeUncustomizedResMap purports to create a ResMap without customization.
|
||||
// The Resources in the returned ResMap include all resources mentioned
|
||||
// in the kustomization file and transitively reachable via its Bases,
|
||||
// and all generated secrets and configMaps.
|
||||
// Meant for use in generating a diff against customized resources.
|
||||
// TODO: See https://github.com/kubernetes-sigs/kustomize/issues/85
|
||||
func (a *Application) MakeUncustomizedResMap() (resmap.ResMap, error) {
|
||||
m, err := a.loadResMapFromBasesAndResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.resolveRefsToGeneratedResources(m)
|
||||
}
|
||||
|
||||
// resolveRefsToGeneratedResources fixes all name references.
|
||||
func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.ResMap, error) {
|
||||
r := []transformers.Transformer{transformers.NewNameHashTransformer()}
|
||||
|
||||
t, err := transformers.NewDefaultingNameReferenceTransformer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
|
||||
refVars, err := a.resolveRefVars(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err = transformers.NewRefVarTransformer(refVars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
|
||||
err = transformers.NewMultiTransformer(r).Transform(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// loadCustomizedResMap loads and customizes resources to build a ResMap.
|
||||
func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
|
||||
errs := &interror.KustomizationErrors{}
|
||||
result, err := a.loadResMapFromBasesAndResources()
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "rawResources"))
|
||||
}
|
||||
|
||||
cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "NewResMapFromConfigMapArgs"))
|
||||
}
|
||||
secrets, err := resmap.NewResMapFromSecretArgs(a.loader.Root(), a.kustomization.SecretGenerator)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "NewResMapFromSecretArgs"))
|
||||
}
|
||||
res, err := resmap.MergeWithoutOverride(cms, secrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Merge")
|
||||
}
|
||||
|
||||
result, err = resmap.MergeWithOverride(result, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patches, err := resmap.NewResourceSliceFromPatches(a.loader, a.kustomization.Patches)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))
|
||||
}
|
||||
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
t, err := a.newTransformer(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.Transform(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Gets Bases and Resources as advertised.
|
||||
func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
|
||||
bases, errs := a.loadCustomizedBases()
|
||||
resources, err := resmap.NewResMapFromFiles(a.loader, a.kustomization.Resources)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "rawResources failed to read Resources"))
|
||||
}
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return resmap.MergeWithoutOverride(resources, bases)
|
||||
}
|
||||
|
||||
// Loop through the Bases of this kustomization recursively loading resources.
|
||||
// Combine into one ResMap, demanding unique Ids for each resource.
|
||||
func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.KustomizationErrors) {
|
||||
list := []resmap.ResMap{}
|
||||
errs := &interror.KustomizationErrors{}
|
||||
for _, path := range a.kustomization.Bases {
|
||||
loader, err := a.loader.New(path)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "couldn't make loader for "+path))
|
||||
continue
|
||||
}
|
||||
app, err := NewApplication(loader)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "couldn't make app for "+path))
|
||||
continue
|
||||
}
|
||||
resMap, err := app.loadCustomizedResMap()
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "SemiResources"))
|
||||
continue
|
||||
}
|
||||
list = append(list, resMap)
|
||||
}
|
||||
result, err := resmap.MergeWithoutOverride(list...)
|
||||
if err != nil {
|
||||
errs.Append(errors.Wrap(err, "Merge failed"))
|
||||
}
|
||||
return result, errs
|
||||
}
|
||||
|
||||
func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
|
||||
var result []*Application
|
||||
errs := &interror.KustomizationErrors{}
|
||||
for _, path := range a.kustomization.Bases {
|
||||
loader, err := a.loader.New(path)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
a, err := NewApplication(loader)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
result = append(result, a)
|
||||
}
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTransformer makes a Transformer that does everything except resolve generated names.
|
||||
func (a *Application) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
|
||||
r := []transformers.Transformer{}
|
||||
t, err := transformers.NewPatchTransformer(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
r = append(r, transformers.NewNamespaceTransformer(string(a.kustomization.Namespace)))
|
||||
t, err = transformers.NewDefaultingNamePrefixTransformer(string(a.kustomization.NamePrefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
t, err = transformers.NewDefaultingLabelsMapTransformer(a.kustomization.CommonLabels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
t, err = transformers.NewDefaultingAnnotationsMapTransformer(a.kustomization.CommonAnnotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, t)
|
||||
return transformers.NewMultiTransformer(r), nil
|
||||
}
|
||||
|
||||
func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
vars, err := a.getAllVars()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for _, v := range vars {
|
||||
id := resource.NewResId(v.ObjRef.GroupVersionKind(), v.ObjRef.Name)
|
||||
if r, found := m[id]; found {
|
||||
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve referred var: %+v", v)
|
||||
}
|
||||
result[v.Name] = s
|
||||
} else {
|
||||
glog.Infof("couldn't resolve v: %v", v)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getAllVars returns all the "environment" style Var instances defined in the app.
|
||||
func (a *Application) getAllVars() ([]types.Var, error) {
|
||||
result := []types.Var{}
|
||||
errs := &interror.KustomizationErrors{}
|
||||
|
||||
bases, err := a.loadBasesAsFlatList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: computing vars and resources for bases can be combined
|
||||
for _, b := range bases {
|
||||
vars, err := b.getAllVars()
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
continue
|
||||
}
|
||||
result = append(result, vars...)
|
||||
}
|
||||
for _, v := range a.kustomization.Vars {
|
||||
v.Defaulting()
|
||||
result = append(result, v)
|
||||
}
|
||||
if len(errs.Get()) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -18,29 +18,30 @@ package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/loader/loadertest"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func setupTest(t *testing.T) loader.Loader {
|
||||
kustomizationContent := []byte(`
|
||||
const (
|
||||
kustomizationContent1 = `
|
||||
namePrefix: foo-
|
||||
namespace: ns1
|
||||
commonLabels:
|
||||
app: nginx
|
||||
commonAnnotations:
|
||||
note: This is a test annotation
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- namespace.yaml
|
||||
configMapGenerator:
|
||||
- name: literalConfigMap
|
||||
literals:
|
||||
@@ -52,184 +53,288 @@ secretGenerator:
|
||||
DB_USERNAME: "printf admin"
|
||||
DB_PASSWORD: "printf somepw"
|
||||
type: Opaque
|
||||
`)
|
||||
deploymentContent := []byte(`apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
`
|
||||
deploymentContent = `apiVersion: apps/v1
|
||||
metadata:
|
||||
name: dply1
|
||||
`)
|
||||
kind: Deployment
|
||||
`
|
||||
namespaceContent = `apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ns1
|
||||
`
|
||||
)
|
||||
|
||||
func makeLoader1(t *testing.T) loader.Loader {
|
||||
loader := loadertest.NewFakeLoader("/testpath")
|
||||
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, kustomizationContent)
|
||||
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
err = loader.AddFile("/testpath/deployment.yaml", deploymentContent)
|
||||
err = loader.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
err = loader.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
return loader
|
||||
}
|
||||
|
||||
func TestResources(t *testing.T) {
|
||||
expected := resource.ResourceCollection{
|
||||
types.GroupVersionKindName{
|
||||
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||
Name: "dply1",
|
||||
}: &resource.Resource{
|
||||
Data: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-dply1",
|
||||
"labels": map[string]interface{}{
|
||||
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
|
||||
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
|
||||
var ns = schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}
|
||||
var svc = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
|
||||
|
||||
func TestResources1(t *testing.T) {
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-dply1",
|
||||
"namespace": "ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
resource.NewResId(cmap, "literalConfigMap"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-literalConfigMap-mc92bgcbh5",
|
||||
"namespace": "ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"DB_USERNAME": "admin",
|
||||
"DB_PASSWORD": "somepw",
|
||||
},
|
||||
}),
|
||||
resource.NewResId(secret, "secret"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-secret-877fcfhgt5",
|
||||
"namespace": "ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"type": string(corev1.SecretTypeOpaque),
|
||||
"data": map[string]interface{}{
|
||||
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||
},
|
||||
}),
|
||||
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Namespace",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-ns1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
l := makeLoader1(t)
|
||||
app, err := NewApplication(l)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected construction error %v", err)
|
||||
}
|
||||
actual, err := app.MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Resources error %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
err = expected.ErrorIfNotEqual(actual)
|
||||
t.Fatalf("unexpected inequality: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawResources1(t *testing.T) {
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "dply1",
|
||||
},
|
||||
}),
|
||||
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Namespace",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "ns1",
|
||||
},
|
||||
}),
|
||||
}
|
||||
l := makeLoader1(t)
|
||||
app, err := NewApplication(l)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected construction error %v", err)
|
||||
}
|
||||
actual, err := app.MakeUncustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected RawResources error %v", err)
|
||||
}
|
||||
|
||||
if err := expected.ErrorIfNotEqual(actual); err != nil {
|
||||
t.Fatalf("unexpected inequality: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
kustomizationContentBase = `
|
||||
namePrefix: foo-
|
||||
commonLabels:
|
||||
app: banana
|
||||
resources:
|
||||
- deployment.yaml
|
||||
`
|
||||
kustomizationContentOverlay = `
|
||||
commonLabels:
|
||||
env: staging
|
||||
resources:
|
||||
- service.yaml
|
||||
bases:
|
||||
- base
|
||||
`
|
||||
serviceContent = `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: svc
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
`
|
||||
)
|
||||
|
||||
func makeLoader2(t *testing.T) loader.Loader {
|
||||
loader := loadertest.NewFakeLoader("/testpath")
|
||||
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = loader.AddFile("/testpath/service.yaml", []byte(serviceContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
err = loader.AddDirectory("/testpath/base", os.ModeDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
err = loader.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
err = loader.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup fake loader.")
|
||||
}
|
||||
return loader
|
||||
}
|
||||
|
||||
// TODO: This test covers incorrect behavior; it should not pass.
|
||||
// It asks for raw resources. The Service resource is returned in raw form,
|
||||
// but the resources in the base are modified to have the banana label,
|
||||
// the 'foo' name prefix, etc. This method exists only to support the
|
||||
// diff command comparing customized to non-customized resources;
|
||||
// perhaps it's not worth supporting the command.
|
||||
func TestRawResources2(t *testing.T) {
|
||||
expected := resmap.ResMap{
|
||||
resource.NewResId(deploy, "dply1"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-dply1",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "banana",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app": "banana",
|
||||
},
|
||||
},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app": "banana",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
types.GroupVersionKindName{
|
||||
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
||||
Name: "literalConfigMap",
|
||||
}: &resource.Resource{
|
||||
Data: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-literalConfigMap-mc92bgcbh5",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"DB_USERNAME": "admin",
|
||||
"DB_PASSWORD": "somepw",
|
||||
},
|
||||
}),
|
||||
resource.NewResId(svc, "svc"): resource.NewResourceFromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "svc",
|
||||
},
|
||||
},
|
||||
},
|
||||
types.GroupVersionKindName{
|
||||
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
|
||||
Name: "secret",
|
||||
}: &resource.Resource{
|
||||
Data: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-secret-877fcfhgt5",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
"annotations": map[string]interface{}{
|
||||
"note": "This is a test annotation",
|
||||
},
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"type": string(corev1.SecretTypeOpaque),
|
||||
"data": map[string]interface{}{
|
||||
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"type": "LoadBalancer",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
l := setupTest(t)
|
||||
app, err := New(l)
|
||||
l := makeLoader2(t)
|
||||
app, err := NewApplication(l)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
t.Fatalf("Unexpected construction error %v", err)
|
||||
}
|
||||
actual, err := app.Resources()
|
||||
actual, err := app.MakeUncustomizedResMap()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
t.Fatalf("Unexpected RawResources error %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
err = compareMap(actual, expected)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
if err := expected.ErrorIfNotEqual(actual); err != nil {
|
||||
t.Fatalf("unexpected inequality: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawResources(t *testing.T) {
|
||||
expected := resource.ResourceCollection{
|
||||
types.GroupVersionKindName{
|
||||
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||
Name: "dply1",
|
||||
}: &resource.Resource{
|
||||
Data: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "dply1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
l := setupTest(t)
|
||||
app, err := New(l)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
actual, err := app.RawResources()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
if err := compareMap(actual, expected); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func compareMap(m1, m2 resource.ResourceCollection) error {
|
||||
if len(m1) != len(m2) {
|
||||
keySet1 := []types.GroupVersionKindName{}
|
||||
keySet2 := []types.GroupVersionKindName{}
|
||||
for GVKn := range m1 {
|
||||
keySet1 = append(keySet1, GVKn)
|
||||
}
|
||||
for GVKn := range m1 {
|
||||
keySet2 = append(keySet2, GVKn)
|
||||
}
|
||||
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
|
||||
}
|
||||
for GVKn, obj1 := range m1 {
|
||||
obj2, found := m2[GVKn]
|
||||
if !found {
|
||||
return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2)
|
||||
}
|
||||
if !reflect.DeepEqual(obj1, obj2) {
|
||||
return fmt.Errorf("%#v doesn't match %#v", obj1, obj2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
type addPatchOptions struct {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
type addResourceOptions struct {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -26,9 +26,8 @@ import (
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/app"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||
kutil "github.com/kubernetes-sigs/kustomize/pkg/util"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
)
|
||||
|
||||
type buildOptions struct {
|
||||
@@ -82,19 +81,19 @@ func (o *buildOptions) RunBuild(out, errOut io.Writer, fs fs.FileSystem) error {
|
||||
return err
|
||||
}
|
||||
|
||||
application, err := app.New(rootLoader)
|
||||
application, err := app.NewApplication(rootLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allResources, err := application.Resources()
|
||||
allResources, err := application.MakeCustomizedResMap()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Output the objects.
|
||||
res, err := kutil.Encode(allResources)
|
||||
res, err := allResources.EncodeAsYaml()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
@@ -104,49 +104,51 @@ func TestBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testcaseName := range testcases.List() {
|
||||
t.Run(testcaseName, func(t *testing.T) {
|
||||
name := testcaseName
|
||||
testcase := buildTestCase{}
|
||||
testcaseDir := filepath.Join("testdata", "testcase-"+name)
|
||||
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
|
||||
ops := &buildOptions{
|
||||
kustomizationPath: testcase.Filename,
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = ops.RunBuild(buf, os.Stderr, fs)
|
||||
switch {
|
||||
case err != nil && len(testcase.ExpectedError) == 0:
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
case err != nil && len(testcase.ExpectedError) != 0:
|
||||
if !strings.Contains(err.Error(), testcase.ExpectedError) {
|
||||
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
|
||||
}
|
||||
return
|
||||
case err == nil && len(testcase.ExpectedError) != 0:
|
||||
t.Errorf("unexpected no error")
|
||||
}
|
||||
|
||||
actualBytes := buf.Bytes()
|
||||
if !updateKustomizeExpected {
|
||||
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedStdout)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actualBytes, expectedBytes) {
|
||||
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
|
||||
}
|
||||
} else {
|
||||
ioutil.WriteFile(testcase.ExpectedStdout, actualBytes, 0644)
|
||||
}
|
||||
|
||||
})
|
||||
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fs) })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fs fs.FileSystem) {
|
||||
name := testcaseName
|
||||
testcase := buildTestCase{}
|
||||
testcaseDir := filepath.Join("testdata", "testcase-"+name)
|
||||
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
|
||||
ops := &buildOptions{
|
||||
kustomizationPath: testcase.Filename,
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = ops.RunBuild(buf, os.Stderr, fs)
|
||||
switch {
|
||||
case err != nil && len(testcase.ExpectedError) == 0:
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
case err != nil && len(testcase.ExpectedError) != 0:
|
||||
if !strings.Contains(err.Error(), testcase.ExpectedError) {
|
||||
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
|
||||
}
|
||||
return
|
||||
case err == nil && len(testcase.ExpectedError) != 0:
|
||||
t.Errorf("unexpected no error")
|
||||
}
|
||||
|
||||
actualBytes := buf.Bytes()
|
||||
if !updateKustomizeExpected {
|
||||
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedStdout)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actualBytes, expectedBytes) {
|
||||
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
|
||||
}
|
||||
} else {
|
||||
ioutil.WriteFile(testcase.ExpectedStdout, actualBytes, 0644)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package commands holds the CLI glue mapping textual commands/args to method calls.
|
||||
package commands
|
||||
|
||||
import (
|
||||
@@ -21,7 +22,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -37,7 +38,7 @@ func NewDefaultCommand() *cobra.Command {
|
||||
Long: `
|
||||
kustomize manages declarative configuration of Kubernetes.
|
||||
|
||||
More info at https://github.com/kubernetes/kubectl/tree/master/cmd/kustomize
|
||||
See https://github.com/kubernetes-sigs/kustomize
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -81,15 +82,12 @@ func newCmdEdit(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
|
||||
func newCmdAdd(stdOut, stdErr io.Writer, fsys fs.FileSystem) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Adds configmap/resource/secret to the kustomization file.",
|
||||
Short: "Adds configmap/resource/patch to the kustomization file.",
|
||||
Long: "",
|
||||
Example: `
|
||||
# Adds a configmap to the kustomization file
|
||||
kustomize edit add configmap NAME --from-literal=k=v
|
||||
|
||||
# Adds a secret to the kustomization file
|
||||
kustomize edit add secret NAME --from-literal=k=v
|
||||
|
||||
# Adds a resource to the kustomization
|
||||
kustomize edit add resource <filepath>
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
)
|
||||
|
||||
func newCmdAddConfigMap(errOut io.Writer, fsys fs.FileSystem) *cobra.Command {
|
||||
|
||||
@@ -19,8 +19,8 @@ package commands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
)
|
||||
|
||||
func TestNewAddConfigMapIsNotNil(t *testing.T) {
|
||||
|
||||
@@ -25,10 +25,9 @@ import (
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/app"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/diff"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/loader"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
type diffOptions struct {
|
||||
@@ -66,14 +65,8 @@ func (o *diffOptions) Validate(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunDiff gets the differences between Application.Resources() and Application.RawResources().
|
||||
// RunDiff gets the differences between Application.MakeCustomizedResMap() and Application.MakeUncustomizedResMap().
|
||||
func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
|
||||
printer := util.Printer{}
|
||||
diff := util.DiffProgram{
|
||||
Exec: exec.New(),
|
||||
Stdout: out,
|
||||
Stderr: errOut,
|
||||
}
|
||||
|
||||
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
|
||||
|
||||
@@ -87,30 +80,18 @@ func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
|
||||
return err
|
||||
}
|
||||
|
||||
application, err := app.New(rootLoader)
|
||||
application, err := app.NewApplication(rootLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources, err := application.Resources()
|
||||
transformedResources, err := application.MakeCustomizedResMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawResources, err := application.RawResources()
|
||||
rawResources, err := application.MakeUncustomizedResMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transformedDir, err := util.WriteToDir(resources, "transformed", printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer transformedDir.Delete()
|
||||
|
||||
noopDir, err := util.WriteToDir(rawResources, "noop", printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer noopDir.Delete()
|
||||
|
||||
return diff.Run(noopDir.Name, transformedDir.Name)
|
||||
return diff.RunDiff(rawResources, transformedResources, out, errOut)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
@@ -74,51 +74,57 @@ func TestDiff(t *testing.T) {
|
||||
|
||||
for _, testcaseName := range testcases.List() {
|
||||
t.Run(testcaseName, func(t *testing.T) {
|
||||
name := testcaseName
|
||||
testcase := DiffTestCase{}
|
||||
testcaseDir := filepath.Join("testdata", "testcase-"+name)
|
||||
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
|
||||
diffOps := &diffOptions{
|
||||
kustomizationPath: testcase.Filename,
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = diffOps.RunDiff(buf, os.Stderr, fs)
|
||||
switch {
|
||||
case err != nil && len(testcase.ExpectedError) == 0:
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
case err != nil && len(testcase.ExpectedError) != 0:
|
||||
if !strings.Contains(err.Error(), testcase.ExpectedError) {
|
||||
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
|
||||
}
|
||||
return
|
||||
case err == nil && len(testcase.ExpectedError) != 0:
|
||||
t.Errorf("unexpected no error")
|
||||
}
|
||||
|
||||
actualString := string(buf.Bytes())
|
||||
actualString = noopDir.ReplaceAllString(actualString, "/tmp/noop/")
|
||||
actualString = transformedDir.ReplaceAllString(actualString, "/tmp/transformed/")
|
||||
actualString = timestamp.ReplaceAllString(actualString, "YYYY-MM-DD HH:MM:SS")
|
||||
actualBytes := []byte(actualString)
|
||||
if !updateKustomizeExpected {
|
||||
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedDiff)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actualBytes, expectedBytes) {
|
||||
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
|
||||
}
|
||||
} else {
|
||||
ioutil.WriteFile(testcase.ExpectedDiff, actualBytes, 0644)
|
||||
}
|
||||
|
||||
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fs,
|
||||
noopDir, transformedDir, timestamp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runDiffTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fs fs.FileSystem,
|
||||
noopDir, transformedDir, timestamp *regexp.Regexp) {
|
||||
name := testcaseName
|
||||
testcase := DiffTestCase{}
|
||||
testcaseDir := filepath.Join("testdata", "testcase-"+name)
|
||||
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
|
||||
diffOps := &diffOptions{
|
||||
kustomizationPath: testcase.Filename,
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = diffOps.RunDiff(buf, os.Stderr, fs)
|
||||
switch {
|
||||
case err != nil && len(testcase.ExpectedError) == 0:
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
case err != nil && len(testcase.ExpectedError) != 0:
|
||||
if !strings.Contains(err.Error(), testcase.ExpectedError) {
|
||||
t.Errorf("expected error to contain %q but got: %v", testcase.ExpectedError, err)
|
||||
}
|
||||
return
|
||||
case err == nil && len(testcase.ExpectedError) != 0:
|
||||
t.Errorf("unexpected no error")
|
||||
}
|
||||
|
||||
actualString := string(buf.Bytes())
|
||||
actualString = noopDir.ReplaceAllString(actualString, "/tmp/noop/")
|
||||
actualString = transformedDir.ReplaceAllString(actualString, "/tmp/transformed/")
|
||||
actualString = timestamp.ReplaceAllString(actualString, "YYYY-MM-DD HH:MM:SS")
|
||||
actualBytes := []byte(actualString)
|
||||
if !updateKustomizeExpected {
|
||||
expectedBytes, err := ioutil.ReadFile(testcase.ExpectedDiff)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actualBytes, expectedBytes) {
|
||||
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
|
||||
}
|
||||
} else {
|
||||
ioutil.WriteFile(testcase.ExpectedDiff, actualBytes, 0644)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
)
|
||||
|
||||
type kustomizationFile struct {
|
||||
@@ -61,7 +61,7 @@ func (mf *kustomizationFile) validate() error {
|
||||
}
|
||||
} else {
|
||||
if !strings.HasSuffix(mf.path, constants.KustomizationFileName) {
|
||||
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n", mf.path, constants.KustomizationSuffix)
|
||||
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n", mf.path, constants.KustomizationFileSuffix)
|
||||
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
|
||||
return merr
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
)
|
||||
|
||||
func TestWriteAndRead(t *testing.T) {
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
type setNamePrefixOptions struct {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/constants"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/util/fs"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
6
pkg/commands/testdata/testcase-configmaps/base/myapp/mycomponent/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
namePrefix: p1-
|
||||
configMapGenerator:
|
||||
- name: com1
|
||||
behavior: create
|
||||
literals:
|
||||
- from=base
|
||||
6
pkg/commands/testdata/testcase-configmaps/base/myapp/mycomponent2/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
namePrefix: p2-
|
||||
configMapGenerator:
|
||||
- name: com2
|
||||
behavior: create
|
||||
literals:
|
||||
- from=base
|
||||
16
pkg/commands/testdata/testcase-configmaps/expected.diff
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
diff -u -N /tmp/noop/v1_ConfigMap_com1.yaml /tmp/transformed/v1_ConfigMap_com1.yaml
|
||||
--- /tmp/noop/v1_ConfigMap_com1.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/v1_ConfigMap_com1.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -1,9 +1,11 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
+ baz: qux
|
||||
+ foo: bar
|
||||
from: overlay
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations: {}
|
||||
creationTimestamp: null
|
||||
labels: {}
|
||||
- name: p1-com1-cmdb776d5b
|
||||
+ name: p1-com1-dhbbm922gd
|
||||
21
pkg/commands/testdata/testcase-configmaps/expected.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
baz: qux
|
||||
foo: bar
|
||||
from: overlay
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations: {}
|
||||
creationTimestamp: null
|
||||
labels: {}
|
||||
name: p1-com1-dhbbm922gd
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
from: overlay
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations: {}
|
||||
creationTimestamp: null
|
||||
labels: {}
|
||||
name: p2-com2-c4b8md75k9
|
||||
9
pkg/commands/testdata/testcase-configmaps/overlay/dev/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
bases:
|
||||
- myapp/mycomponent
|
||||
- myapp/mycomponent2
|
||||
configMapGenerator:
|
||||
- name: com1
|
||||
behavior: merge
|
||||
literals:
|
||||
- foo=bar
|
||||
- baz=qux
|
||||
7
pkg/commands/testdata/testcase-configmaps/overlay/dev/myapp/mycomponent/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
bases:
|
||||
- ../../../../base/myapp/mycomponent
|
||||
configMapGenerator:
|
||||
- name: com1
|
||||
behavior: merge
|
||||
literals:
|
||||
- from=overlay
|
||||
7
pkg/commands/testdata/testcase-configmaps/overlay/dev/myapp/mycomponent2/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
bases:
|
||||
- ../../../../base/myapp/mycomponent2
|
||||
configMapGenerator:
|
||||
- name: com2
|
||||
behavior: merge
|
||||
literals:
|
||||
- from=overlay
|
||||
5
pkg/commands/testdata/testcase-configmaps/test.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
description: configmap generator overlay
|
||||
args: []
|
||||
filename: testdata/testcase-configmaps/overlay/dev
|
||||
expectedStdout: testdata/testcase-configmaps/expected.yaml
|
||||
expectedDiff: testdata/testcase-configmaps/expected.diff
|
||||
150
pkg/commands/testdata/testcase-variable-ref/expected.diff
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
diff -u -N /tmp/noop/apps_v1beta1_StatefulSet_cockroachdb.yaml /tmp/transformed/apps_v1beta1_StatefulSet_cockroachdb.yaml
|
||||
--- /tmp/noop/apps_v1beta1_StatefulSet_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/apps_v1beta1_StatefulSet_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -1,10 +1,10 @@
|
||||
apiVersion: apps/v1beta1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
spec:
|
||||
replicas: 3
|
||||
- serviceName: base-cockroachdb
|
||||
+ serviceName: dev-base-cockroachdb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
@@ -27,7 +27,7 @@
|
||||
- /bin/bash
|
||||
- -ecx
|
||||
- exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs
|
||||
- --host $(hostname -f) --http-host 0.0.0.0 --join base-cockroachdb-0.base-cockroachdb,base-cockroachdb-1.base-cockroachdb,base-cockroachdb-2.base-cockroachdb
|
||||
+ --host $(hostname -f) --http-host 0.0.0.0 --join dev-base-cockroachdb-0.dev-base-cockroachdb,dev-base-cockroachdb-1.dev-base-cockroachdb,dev-base-cockroachdb-2.dev-base-cockroachdb
|
||||
--cache 25% --max-sql-memory 25%
|
||||
image: cockroachdb/cockroach:v1.1.5
|
||||
imagePullPolicy: IfNotPresent
|
||||
@@ -48,7 +48,7 @@
|
||||
- -ecx
|
||||
- /request-cert -namespace=${POD_NAMESPACE} -certs-dir=/cockroach-certs -type=node
|
||||
-addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut
|
||||
- -f 1-2 -d '.'),base-cockroachdb-public -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
+ -f 1-2 -d '.'),dev-base-cockroachdb-public -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
@@ -64,7 +64,7 @@
|
||||
volumeMounts:
|
||||
- mountPath: /cockroach-certs
|
||||
name: certs
|
||||
- serviceAccountName: base-cockroachdb
|
||||
+ serviceAccountName: dev-base-cockroachdb
|
||||
terminationGracePeriodSeconds: 60
|
||||
volumes:
|
||||
- name: datadir
|
||||
diff -u -N /tmp/noop/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml /tmp/transformed/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml
|
||||
--- /tmp/noop/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/policy_v1beta1_PodDisruptionBudget_cockroachdb-budget.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,7 +3,7 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb-budget
|
||||
+ name: dev-base-cockroachdb-budget
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml
|
||||
--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,12 +3,12 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
namespace: default
|
||||
diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml
|
||||
--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_ClusterRole_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,7 +3,7 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
rules:
|
||||
- apiGroups:
|
||||
- certificates.k8s.io
|
||||
diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml
|
||||
--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_RoleBinding_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,12 +3,12 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
namespace: default
|
||||
diff -u -N /tmp/noop/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml /tmp/transformed/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml
|
||||
--- /tmp/noop/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/rbac.authorization.k8s.io_v1beta1_Role_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,7 +3,7 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
diff -u -N /tmp/noop/v1_ServiceAccount_cockroachdb.yaml /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml
|
||||
--- /tmp/noop/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/v1_ServiceAccount_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,4 +3,4 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
diff -u -N /tmp/noop/v1_Service_cockroachdb-public.yaml /tmp/transformed/v1_Service_cockroachdb-public.yaml
|
||||
--- /tmp/noop/v1_Service_cockroachdb-public.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/v1_Service_cockroachdb-public.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -3,7 +3,7 @@
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb-public
|
||||
+ name: dev-base-cockroachdb-public
|
||||
spec:
|
||||
ports:
|
||||
- name: grpc
|
||||
diff -u -N /tmp/noop/v1_Service_cockroachdb.yaml /tmp/transformed/v1_Service_cockroachdb.yaml
|
||||
--- /tmp/noop/v1_Service_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
+++ /tmp/transformed/v1_Service_cockroachdb.yaml YYYY-MM-DD HH:MM:SS
|
||||
@@ -8,7 +8,7 @@
|
||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
|
||||
labels:
|
||||
app: cockroachdb
|
||||
- name: base-cockroachdb
|
||||
+ name: dev-base-cockroachdb
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
205
pkg/commands/testdata/testcase-variable-ref/expected.yaml
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/path: _status/vars
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/scrape: "true"
|
||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 26257
|
||||
targetPort: 26257
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb-public
|
||||
spec:
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 26257
|
||||
targetPort: 26257
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: dev-base-cockroachdb
|
||||
spec:
|
||||
replicas: 3
|
||||
serviceName: dev-base-cockroachdb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- cockroachdb
|
||||
topologyKey: kubernetes.io/hostname
|
||||
weight: 100
|
||||
containers:
|
||||
- command:
|
||||
- /bin/bash
|
||||
- -ecx
|
||||
- exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs
|
||||
--host $(hostname -f) --http-host 0.0.0.0 --join dev-base-cockroachdb-0.dev-base-cockroachdb,dev-base-cockroachdb-1.dev-base-cockroachdb,dev-base-cockroachdb-2.dev-base-cockroachdb
|
||||
--cache 25% --max-sql-memory 25%
|
||||
image: cockroachdb/cockroach:v1.1.5
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: cockroachdb
|
||||
ports:
|
||||
- containerPort: 26257
|
||||
name: grpc
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
volumeMounts:
|
||||
- mountPath: /cockroach/cockroach-data
|
||||
name: datadir
|
||||
- mountPath: /cockroach/cockroach-certs
|
||||
name: certs
|
||||
initContainers:
|
||||
- command:
|
||||
- /bin/ash
|
||||
- -ecx
|
||||
- /request-cert -namespace=${POD_NAMESPACE} -certs-dir=/cockroach-certs -type=node
|
||||
-addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut
|
||||
-f 1-2 -d '.'),dev-base-cockroachdb-public -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: cockroachdb/cockroach-k8s-request-cert:0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: init-certs
|
||||
volumeMounts:
|
||||
- mountPath: /cockroach-certs
|
||||
name: certs
|
||||
serviceAccountName: dev-base-cockroachdb
|
||||
terminationGracePeriodSeconds: 60
|
||||
volumes:
|
||||
- name: datadir
|
||||
persistentVolumeClaim:
|
||||
claimName: datadir
|
||||
- emptyDir: {}
|
||||
name: certs
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: datadir
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb-budget
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb
|
||||
rules:
|
||||
- apiGroups:
|
||||
- certificates.k8s.io
|
||||
resources:
|
||||
- certificatesigningrequests
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: dev-base-cockroachdb
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: dev-base-cockroachdb
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
name: dev-base-cockroachdb
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: dev-base-cockroachdb
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: dev-base-cockroachdb
|
||||
namespace: default
|
||||
4
pkg/commands/testdata/testcase-variable-ref/in/overlay/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
namePrefix: dev-
|
||||
bases:
|
||||
- ../package
|
||||
|
||||
235
pkg/commands/testdata/testcase-variable-ref/in/package/cockroachdb-statefulset-secure.yaml
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: cockroachdb
|
||||
labels:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: cockroachdb
|
||||
labels:
|
||||
app: cockroachdb
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: cockroachdb
|
||||
labels:
|
||||
app: cockroachdb
|
||||
rules:
|
||||
- apiGroups:
|
||||
- certificates.k8s.io
|
||||
resources:
|
||||
- certificatesigningrequests
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: cockroachdb
|
||||
labels:
|
||||
app: cockroachdb
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: cockroachdb
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cockroachdb
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: cockroachdb
|
||||
labels:
|
||||
app: cockroachdb
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cockroachdb
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cockroachdb
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
# This service is meant to be used by clients of the database. It exposes a ClusterIP that will
|
||||
# automatically load balance connections to the different database pods.
|
||||
name: cockroachdb-public
|
||||
labels:
|
||||
app: cockroachdb
|
||||
spec:
|
||||
ports:
|
||||
# The main port, served by gRPC, serves Postgres-flavor SQL, internode
|
||||
# traffic and the cli.
|
||||
- port: 26257
|
||||
targetPort: 26257
|
||||
name: grpc
|
||||
# The secondary port serves the UI as well as health and debug endpoints.
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
name: http
|
||||
selector:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
# This service only exists to create DNS entries for each pod in the stateful
|
||||
# set such that they can resolve each other's IP addresses. It does not
|
||||
# create a load-balanced ClusterIP and should not be used directly by clients
|
||||
# in most circumstances.
|
||||
name: cockroachdb
|
||||
labels:
|
||||
app: cockroachdb
|
||||
annotations:
|
||||
# This is needed to make the peer-finder work properly and to help avoid
|
||||
# edge cases where instance 0 comes up after losing its data and needs to
|
||||
# decide whether it should create a new cluster or try to join an existing
|
||||
# one. If it creates a new cluster when it should have joined an existing
|
||||
# one, we'd end up with two separate clusters listening at the same service
|
||||
# endpoint, which would be very bad.
|
||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
|
||||
# Enable automatic monitoring of all instances when Prometheus is running in the cluster.
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/path: "_status/vars"
|
||||
prometheus.io/port: "8080"
|
||||
spec:
|
||||
ports:
|
||||
- port: 26257
|
||||
targetPort: 26257
|
||||
name: grpc
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
name: http
|
||||
clusterIP: None
|
||||
selector:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: cockroachdb-budget
|
||||
labels:
|
||||
app: cockroachdb
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cockroachdb
|
||||
maxUnavailable: 1
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: cockroachdb
|
||||
spec:
|
||||
serviceName: "cockroachdb"
|
||||
replicas: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cockroachdb
|
||||
spec:
|
||||
serviceAccountName: cockroachdb
|
||||
# Init containers are run only once in the lifetime of a pod, before
|
||||
# it's started up for the first time. It has to exit successfully
|
||||
# before the pod's main containers are allowed to start.
|
||||
initContainers:
|
||||
# The init-certs container sends a certificate signing request to the
|
||||
# kubernetes cluster.
|
||||
# You can see pending requests using: kubectl get csr
|
||||
# CSRs can be approved using: kubectl certificate approve <csr name>
|
||||
#
|
||||
# All addresses used to contact a node must be specified in the --addresses arg.
|
||||
#
|
||||
# In addition to the node certificate and key, the init-certs entrypoint will symlink
|
||||
# the cluster CA to the certs directory.
|
||||
- name: init-certs
|
||||
image: cockroachdb/cockroach-k8s-request-cert:0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "/bin/ash"
|
||||
- "-ecx"
|
||||
- "/request-cert -namespace=${POD_NAMESPACE} -certs-dir=/cockroach-certs -type=node -addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut -f 1-2 -d '.'),$(CDB_PUBLIC_SVC) -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
volumeMounts:
|
||||
- name: certs
|
||||
mountPath: /cockroach-certs
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- cockroachdb
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- name: cockroachdb
|
||||
image: cockroachdb/cockroach:v1.1.5
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 26257
|
||||
name: grpc
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: datadir
|
||||
mountPath: /cockroach/cockroach-data
|
||||
- name: certs
|
||||
mountPath: /cockroach/cockroach-certs
|
||||
command:
|
||||
- "/bin/bash"
|
||||
- "-ecx"
|
||||
# The use of qualified `hostname -f` is crucial:
|
||||
# Other nodes aren't able to look up the unqualified hostname.
|
||||
# Once 2.0 is out, we should be able to switch from --host to --advertise-host to make port-forwarding work to the main port.
|
||||
- "exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs --host $(hostname -f) --http-host 0.0.0.0 --join $(CDB_STATEFULSET_NAME)-0.$(CDB_STATEFULSET_SVC),$(CDB_STATEFULSET_NAME)-1.$(CDB_STATEFULSET_SVC),$(CDB_STATEFULSET_NAME)-2.$(CDB_STATEFULSET_SVC) --cache 25% --max-sql-memory 25%"
|
||||
# No pre-stop hook is required, a SIGTERM plus some time is all that's
|
||||
# needed for graceful shutdown of a node.
|
||||
terminationGracePeriodSeconds: 60
|
||||
volumes:
|
||||
- name: datadir
|
||||
persistentVolumeClaim:
|
||||
claimName: datadir
|
||||
- name: certs
|
||||
emptyDir: {}
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: datadir
|
||||
spec:
|
||||
accessModes:
|
||||
- "ReadWriteOnce"
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
26
pkg/commands/testdata/testcase-variable-ref/in/package/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
namePrefix: base-
|
||||
resources:
|
||||
- cockroachdb-statefulset-secure.yaml
|
||||
vars:
|
||||
- name: CDB_PUBLIC_SVC
|
||||
objref:
|
||||
kind: Service
|
||||
name: cockroachdb-public
|
||||
apiVersion: v1
|
||||
fieldref:
|
||||
fieldpath: metadata.name
|
||||
- name: CDB_STATEFULSET_NAME
|
||||
objref:
|
||||
kind: StatefulSet
|
||||
name: cockroachdb
|
||||
apiVersion: apps/v1beta1
|
||||
fieldref:
|
||||
fieldpath: metadata.name
|
||||
- name: CDB_STATEFULSET_SVC
|
||||
objref:
|
||||
kind: Service
|
||||
name: cockroachdb
|
||||
apiVersion: v1
|
||||
fieldref:
|
||||
fieldpath: metadata.name
|
||||
|
||||
5
pkg/commands/testdata/testcase-variable-ref/test.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
description: variable reference and substitution
|
||||
args: []
|
||||
filename: testdata/testcase-variable-ref/in/overlay/
|
||||
expectedStdout: testdata/testcase-variable-ref/expected.yaml
|
||||
expectedDiff: testdata/testcase-variable-ref/expected.diff
|
||||
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package configmapandsecret generates configmaps and secrets per generator rules.
|
||||
package configmapandsecret
|
||||
|
||||
import (
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
|
||||
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/hash"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/resource"
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -122,51 +122,6 @@ func makeSecret(secret types.SecretArgs, path string) (*corev1.Secret, error) {
|
||||
return corev1secret, nil
|
||||
}
|
||||
|
||||
func populateMap(m resource.ResourceCollection, obj *unstructured.Unstructured, newName string) error {
|
||||
oldName := obj.GetName()
|
||||
gvk := obj.GroupVersionKind()
|
||||
gvkn := types.GroupVersionKindName{GVK: gvk, Name: oldName}
|
||||
|
||||
if _, found := m[gvkn]; found {
|
||||
return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
|
||||
}
|
||||
obj.SetName(newName)
|
||||
m[gvkn] = &resource.Resource{Data: obj}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeConfigMapsResourceCollection returns a map of <GVK, oldName> -> unstructured object.
|
||||
func MakeConfigMapsResourceCollection(maps []types.ConfigMapArgs) (resource.ResourceCollection, error) {
|
||||
m := resource.ResourceCollection{}
|
||||
for _, cm := range maps {
|
||||
unstructuredConfigMap, nameWithHash, err := MakeConfigmapAndGenerateName(cm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = populateMap(m, unstructuredConfigMap, nameWithHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// MakeSecretsResourceCollection returns a map of <GVK, oldName> -> unstructured object.
|
||||
func MakeSecretsResourceCollection(secrets []types.SecretArgs, path string) (resource.ResourceCollection, error) {
|
||||
m := resource.ResourceCollection{}
|
||||
for _, secret := range secrets {
|
||||
unstructuredSecret, nameWithHash, err := MakeSecretAndGenerateName(secret, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = populateMap(m, unstructuredSecret, nameWithHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func createSecretKey(wd string, command string) ([]byte, error) {
|
||||
fi, err := os.Stat(wd)
|
||||
if err != nil || !fi.IsDir() {
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// handleConfigMapFromLiteralSources adds the specified literal source
|
||||
// HandleConfigMapFromLiteralSources adds the specified literal source
|
||||
// information into the provided configMap.
|
||||
func HandleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources []string) error {
|
||||
for _, literalSource := range literalSources {
|
||||
@@ -43,7 +43,7 @@ func HandleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources [
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConfigMapFromFileSources adds the specified file source information
|
||||
// HandleConfigMapFromFileSources adds the specified file source information
|
||||
// into the provided configMap
|
||||
func HandleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []string) error {
|
||||
for _, fileSource := range fileSources {
|
||||
@@ -88,7 +88,7 @@ func HandleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []strin
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConfigMapFromEnvFileSource adds the specified env file source information
|
||||
// HandleConfigMapFromEnvFileSource adds the specified env file source information
|
||||
// into the provided configMap
|
||||
func HandleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource string) error {
|
||||
info, err := os.Stat(envFileSource)
|
||||
|
||||
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package util offers Configmap and Secret generation utilities.
|
||||
package util
|
||||
|
||||
import (
|
||||
@@ -40,6 +41,7 @@ func ParseRFC3339(s string, nowFn func() metav1.Time) (metav1.Time, error) {
|
||||
return metav1.Time{Time: t}, nil
|
||||
}
|
||||
|
||||
// HashObject encodes object using given codec and returns MD5 sum of the result.
|
||||
func HashObject(obj runtime.Object, codec runtime.Codec) (string, error) {
|
||||
data, err := runtime.Encode(codec, obj)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,14 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package constants holds global constants for the kustomize tool.
|
||||
package constants
|
||||
|
||||
const KustomizationSuffix = ".yaml"
|
||||
// KustomizationFileSuffix is expected suffix for KustomizationFileName.
|
||||
const KustomizationFileSuffix = ".yaml"
|
||||
|
||||
// KustomizationFileName is the Well-Known File Name for a kustomize configuration file.
|
||||
const KustomizationFileName = "kustomization" + KustomizationSuffix
|
||||
|
||||
// Configmap behaviors
|
||||
const CreateBehavior = "create"
|
||||
const ReplaceBehavior = "replace"
|
||||
const MergeBehavior = "merge"
|
||||
const KustomizationFileName = "kustomization" + KustomizationFileSuffix
|
||||
|
||||
38
pkg/diff/directory.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// directory represents a new temp directory and lets one create files in it.
|
||||
type directory struct {
|
||||
n string
|
||||
}
|
||||
|
||||
// newDirectory makes a directory instance holding a new temp directory on disk.
|
||||
// The directory name is the given prefix following by a random string.
|
||||
func newDirectory(prefix string) (*directory, error) {
|
||||
name, err := ioutil.TempDir("", prefix+"-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &directory{n: name}, nil
|
||||
}
|
||||
|
||||
// newFile creates a new file in the directory.
|
||||
func (d *directory) newFile(name string) (*os.File, error) {
|
||||
return os.OpenFile(filepath.Join(d.n, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
|
||||
}
|
||||
|
||||
// delete removes the directory recursively.
|
||||
func (d *directory) delete() error {
|
||||
return os.RemoveAll(d.n)
|
||||
}
|
||||
|
||||
// name is the name of the directory.
|
||||
func (d *directory) name() string {
|
||||
return d.n
|
||||
}
|
||||
59
pkg/diff/program.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// program wraps the system diff program.
|
||||
// If specified, the value of KUBERNETES_EXTERNAL_DIFF environment variable
|
||||
// will be used instead of simply `diff(1)`.
|
||||
type program struct {
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func newProgram(out, errOut io.Writer) *program {
|
||||
return &program{
|
||||
stdout: out,
|
||||
stderr: errOut,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *program) makeCommand(args ...string) exec.Cmd {
|
||||
diff := ""
|
||||
if envDiff := os.Getenv("KUBERNETES_EXTERNAL_DIFF"); envDiff != "" {
|
||||
diff = envDiff
|
||||
} else {
|
||||
diff = "diff"
|
||||
args = append([]string{"-u", "-N"}, args...)
|
||||
}
|
||||
cmd := exec.New().Command(diff, args...)
|
||||
cmd.SetStdout(d.stdout)
|
||||
cmd.SetStderr(d.stderr)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// run runs the detected diff program. `from` and `to` are the directory to diff.
|
||||
func (d *program) run(from, to string) error {
|
||||
d.makeCommand(from, to).Run() // Ignore diff return code
|
||||
return nil
|
||||
}
|
||||
79
pkg/diff/rundiff.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package diff runs system `diff` to compare resource collections.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
|
||||
)
|
||||
|
||||
// RunDiff runs system diff program to compare two Maps.
|
||||
func RunDiff(raw, transformed resmap.ResMap, out, errOut io.Writer) error {
|
||||
transformedDir, err := writeYamlToNewDir(transformed, "transformed")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer transformedDir.delete()
|
||||
|
||||
noopDir, err := writeYamlToNewDir(raw, "noop")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer noopDir.delete()
|
||||
|
||||
return newProgram(out, errOut).run(noopDir.name(), transformedDir.name())
|
||||
}
|
||||
|
||||
// writeYamlToNewDir writes each obj in ResMap to a file in a new directory.
|
||||
// The directory's name will begin with the given prefix.
|
||||
// Each file is named with GroupVersionKindName.
|
||||
func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
|
||||
dir, err := newDirectory(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for id, obj := range in {
|
||||
f, err := dir.newFile(id.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = print(obj, f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Print the object as YAML.
|
||||
func print(obj interface{}, w io.Writer) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
data, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return err
|
||||
}
|
||||
119
pkg/expansion/expand.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
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 expansion provides functions find and replace $(FOO) style variables in strings.
|
||||
package expansion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
const (
|
||||
operator = '$'
|
||||
referenceOpener = '('
|
||||
referenceCloser = ')'
|
||||
)
|
||||
|
||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||
func syntaxWrap(input string) string {
|
||||
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
|
||||
}
|
||||
|
||||
// MappingFuncFor returns a mapping function for use with Expand that
|
||||
// implements the expansion semantics defined in the expansion spec; it
|
||||
// returns the input string wrapped in the expansion syntax if no mapping
|
||||
// for the input is found.
|
||||
func MappingFuncFor(context ...map[string]string) func(string) string {
|
||||
return func(input string) string {
|
||||
for _, vars := range context {
|
||||
val, ok := vars[input]
|
||||
if ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxWrap(input)
|
||||
}
|
||||
}
|
||||
|
||||
// Expand replaces variable references in the input string according to
|
||||
// the expansion spec using the given mapping function to resolve the
|
||||
// values of variables.
|
||||
func Expand(input string, mapping func(string) string) string {
|
||||
var buf bytes.Buffer
|
||||
checkpoint := 0
|
||||
for cursor := 0; cursor < len(input); cursor++ {
|
||||
if input[cursor] == operator && cursor+1 < len(input) {
|
||||
// Copy the portion of the input string since the last
|
||||
// checkpoint into the buffer
|
||||
buf.WriteString(input[checkpoint:cursor])
|
||||
|
||||
// Attempt to read the variable name as defined by the
|
||||
// syntax from the input string
|
||||
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
||||
|
||||
if isVar {
|
||||
// We were able to read a variable name correctly;
|
||||
// apply the mapping to the variable name and copy the
|
||||
// bytes into the buffer
|
||||
buf.WriteString(mapping(read))
|
||||
} else {
|
||||
// Not a variable name; copy the read bytes into the buffer
|
||||
buf.WriteString(read)
|
||||
}
|
||||
|
||||
// Advance the cursor in the input string to account for
|
||||
// bytes consumed to read the variable name expression
|
||||
cursor += advance
|
||||
|
||||
// Advance the checkpoint in the input string
|
||||
checkpoint = cursor + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Return the buffer and any remaining unwritten bytes in the
|
||||
// input string.
|
||||
return buf.String() + input[checkpoint:]
|
||||
}
|
||||
|
||||
// tryReadVariableName attempts to read a variable name from the input
|
||||
// string and returns the content read from the input, whether that content
|
||||
// represents a variable name to perform mapping on, and the number of bytes
|
||||
// consumed in the input string.
|
||||
//
|
||||
// The input string is assumed not to contain the initial operator.
|
||||
func tryReadVariableName(input string) (string, bool, int) {
|
||||
switch input[0] {
|
||||
case operator:
|
||||
// Escaped operator; return it.
|
||||
return input[0:1], false, 1
|
||||
case referenceOpener:
|
||||
// Scan to expression closer
|
||||
for i := 1; i < len(input); i++ {
|
||||
if input[i] == referenceCloser {
|
||||
return input[1:i], true, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Incomplete reference; return it.
|
||||
return string(operator) + string(referenceOpener), false, 1
|
||||
default:
|
||||
// Not the beginning of an expression, ie, an operator
|
||||
// that doesn't begin an expression. Return the operator
|
||||
// and the first rune in the string.
|
||||
return (string(operator) + string(input[0])), false, 1
|
||||
}
|
||||
}
|
||||
301
pkg/expansion/expand_test.go
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
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 expansion
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestMapReference(t *testing.T) {
|
||||
envs := []api.EnvVar{
|
||||
{
|
||||
Name: "FOO",
|
||||
Value: "bar",
|
||||
},
|
||||
{
|
||||
Name: "ZOO",
|
||||
Value: "$(FOO)-1",
|
||||
},
|
||||
{
|
||||
Name: "BLU",
|
||||
Value: "$(ZOO)-2",
|
||||
},
|
||||
}
|
||||
|
||||
declaredEnv := map[string]string{
|
||||
"FOO": "bar",
|
||||
"ZOO": "$(FOO)-1",
|
||||
"BLU": "$(ZOO)-2",
|
||||
}
|
||||
|
||||
serviceEnv := map[string]string{}
|
||||
|
||||
mapping := MappingFuncFor(declaredEnv, serviceEnv)
|
||||
|
||||
for _, env := range envs {
|
||||
declaredEnv[env.Name] = Expand(env.Value, mapping)
|
||||
}
|
||||
|
||||
expectedEnv := map[string]string{
|
||||
"FOO": "bar",
|
||||
"ZOO": "bar-1",
|
||||
"BLU": "bar-1-2",
|
||||
}
|
||||
|
||||
for k, v := range expectedEnv {
|
||||
if e, a := v, declaredEnv[k]; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
} else {
|
||||
delete(declaredEnv, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(declaredEnv) != 0 {
|
||||
t.Errorf("Unexpected keys in declared env: %v", declaredEnv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapping(t *testing.T) {
|
||||
context := map[string]string{
|
||||
"VAR_A": "A",
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
mapping := MappingFuncFor(context)
|
||||
|
||||
doExpansionTest(t, mapping)
|
||||
}
|
||||
|
||||
func TestMappingDual(t *testing.T) {
|
||||
context := map[string]string{
|
||||
"VAR_A": "A",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
context2 := map[string]string{
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
}
|
||||
mapping := MappingFuncFor(context, context2)
|
||||
|
||||
doExpansionTest(t, mapping)
|
||||
}
|
||||
|
||||
func doExpansionTest(t *testing.T, mapping func(string) string) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "whole string",
|
||||
input: "$(VAR_A)",
|
||||
expected: "A",
|
||||
},
|
||||
{
|
||||
name: "repeat",
|
||||
input: "$(VAR_A)-$(VAR_A)",
|
||||
expected: "A-A",
|
||||
},
|
||||
{
|
||||
name: "beginning",
|
||||
input: "$(VAR_A)-1",
|
||||
expected: "A-1",
|
||||
},
|
||||
{
|
||||
name: "middle",
|
||||
input: "___$(VAR_B)___",
|
||||
expected: "___B___",
|
||||
},
|
||||
{
|
||||
name: "end",
|
||||
input: "___$(VAR_C)",
|
||||
expected: "___C",
|
||||
},
|
||||
{
|
||||
name: "compound",
|
||||
input: "$(VAR_A)_$(VAR_B)_$(VAR_C)",
|
||||
expected: "A_B_C",
|
||||
},
|
||||
{
|
||||
name: "escape & expand",
|
||||
input: "$$(VAR_B)_$(VAR_A)",
|
||||
expected: "$(VAR_B)_A",
|
||||
},
|
||||
{
|
||||
name: "compound escape",
|
||||
input: "$$(VAR_A)_$$(VAR_B)",
|
||||
expected: "$(VAR_A)_$(VAR_B)",
|
||||
},
|
||||
{
|
||||
name: "mixed in escapes",
|
||||
input: "f000-$$VAR_A",
|
||||
expected: "f000-$VAR_A",
|
||||
},
|
||||
{
|
||||
name: "backslash escape ignored",
|
||||
input: "foo\\$(VAR_C)bar",
|
||||
expected: "foo\\Cbar",
|
||||
},
|
||||
{
|
||||
name: "backslash escape ignored",
|
||||
input: "foo\\\\$(VAR_C)bar",
|
||||
expected: "foo\\\\Cbar",
|
||||
},
|
||||
{
|
||||
name: "lots of backslashes",
|
||||
input: "foo\\\\\\\\$(VAR_A)bar",
|
||||
expected: "foo\\\\\\\\Abar",
|
||||
},
|
||||
{
|
||||
name: "nested var references",
|
||||
input: "$(VAR_A$(VAR_B))",
|
||||
expected: "$(VAR_A$(VAR_B))",
|
||||
},
|
||||
{
|
||||
name: "nested var references second type",
|
||||
input: "$(VAR_A$(VAR_B)",
|
||||
expected: "$(VAR_A$(VAR_B)",
|
||||
},
|
||||
{
|
||||
name: "value is a reference",
|
||||
input: "$(VAR_REF)",
|
||||
expected: "$(VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "value is a reference x 2",
|
||||
input: "%%$(VAR_REF)--$(VAR_REF)%%",
|
||||
expected: "%%$(VAR_A)--$(VAR_A)%%",
|
||||
},
|
||||
{
|
||||
name: "empty var",
|
||||
input: "foo$(VAR_EMPTY)bar",
|
||||
expected: "foobar",
|
||||
},
|
||||
{
|
||||
name: "unterminated expression",
|
||||
input: "foo$(VAR_Awhoops!",
|
||||
expected: "foo$(VAR_Awhoops!",
|
||||
},
|
||||
{
|
||||
name: "expression without operator",
|
||||
input: "f00__(VAR_A)__",
|
||||
expected: "f00__(VAR_A)__",
|
||||
},
|
||||
{
|
||||
name: "shell special vars pass through",
|
||||
input: "$?_boo_$!",
|
||||
expected: "$?_boo_$!",
|
||||
},
|
||||
{
|
||||
name: "bare operators are ignored",
|
||||
input: "$VAR_A",
|
||||
expected: "$VAR_A",
|
||||
},
|
||||
{
|
||||
name: "undefined vars are passed through",
|
||||
input: "$(VAR_DNE)",
|
||||
expected: "$(VAR_DNE)",
|
||||
},
|
||||
{
|
||||
name: "multiple (even) operators, var undefined",
|
||||
input: "$$$$$$(BIG_MONEY)",
|
||||
expected: "$$$(BIG_MONEY)",
|
||||
},
|
||||
{
|
||||
name: "multiple (even) operators, var defined",
|
||||
input: "$$$$$$(VAR_A)",
|
||||
expected: "$$$(VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "multiple (odd) operators, var undefined",
|
||||
input: "$$$$$$$(GOOD_ODDS)",
|
||||
expected: "$$$$(GOOD_ODDS)",
|
||||
},
|
||||
{
|
||||
name: "multiple (odd) operators, var defined",
|
||||
input: "$$$$$$$(VAR_A)",
|
||||
expected: "$$$A",
|
||||
},
|
||||
{
|
||||
name: "missing open expression",
|
||||
input: "$VAR_A)",
|
||||
expected: "$VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "shell syntax ignored",
|
||||
input: "${VAR_A}",
|
||||
expected: "${VAR_A}",
|
||||
},
|
||||
{
|
||||
name: "trailing incomplete expression not consumed",
|
||||
input: "$(VAR_B)_______$(A",
|
||||
expected: "B_______$(A",
|
||||
},
|
||||
{
|
||||
name: "trailing incomplete expression, no content, is not consumed",
|
||||
input: "$(VAR_C)_______$(",
|
||||
expected: "C_______$(",
|
||||
},
|
||||
{
|
||||
name: "operator at end of input string is preserved",
|
||||
input: "$(VAR_A)foobarzab$",
|
||||
expected: "Afoobarzab$",
|
||||
},
|
||||
{
|
||||
name: "shell escaped incomplete expr",
|
||||
input: "foo-\\$(VAR_A",
|
||||
expected: "foo-\\$(VAR_A",
|
||||
},
|
||||
{
|
||||
name: "lots of $( in middle",
|
||||
input: "--$($($($($--",
|
||||
expected: "--$($($($($--",
|
||||
},
|
||||
{
|
||||
name: "lots of $( in beginning",
|
||||
input: "$($($($($--foo$(",
|
||||
expected: "$($($($($--foo$(",
|
||||
},
|
||||
{
|
||||
name: "lots of $( at end",
|
||||
input: "foo0--$($($($(",
|
||||
expected: "foo0--$($($($(",
|
||||
},
|
||||
{
|
||||
name: "escaped operators in variable names are not escaped",
|
||||
input: "$(foo$$var)",
|
||||
expected: "$(foo$$var)",
|
||||
},
|
||||
{
|
||||
name: "newline not expanded",
|
||||
input: "\n",
|
||||
expected: "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
expanded := Expand(tc.input, mapping)
|
||||
if e, a := tc.expected, expanded; e != a {
|
||||
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||