Compare commits

...

93 Commits

Author SHA1 Message Date
Jeff Regan
5563f1529b Update README.md 2018-06-11 15:20:20 -07:00
Jeff Regan
a95b26182a Merge pull request #93 from Liujingfang1/configmaps
make generated configmaps composable
2018-06-11 15:04:43 -07:00
Jingfang Liu
934e37d781 add unit test and address comments 2018-06-11 14:52:31 -07:00
Jeff Regan
40bad7783f Merge pull request #94 from monopole/addTODO
Add TODO referencing bug
2018-06-11 14:44:32 -07:00
Jeffrey Regan
44696d5fb9 Add TODO referencing bug 2018-06-11 14:43:22 -07:00
Jeff Regan
9d268de5a0 Merge pull request #90 from Liujingfang1/master
Add README.md for wordpress to show variable reference usage
2018-06-11 14:13:23 -07:00
Jingfang Liu
ab0c9b4118 address comments 2018-06-11 13:52:18 -07:00
Jingfang Liu
37720765fc Add a test case 2018-06-11 12:18:26 -07:00
Jingfang Liu
09e64e5991 Merge pull request #81 from sethpollack/skip
refactor namespaces to use PathConfig
2018-06-11 12:08:35 -07:00
Jingfang Liu
e017d04a16 make generated configmaps composable 2018-06-11 12:07:16 -07:00
Jingfang Liu
b6efde13eb Add README.md for wordpress to show variable reference usage 2018-06-11 11:41:09 -07:00
Seth Pollack
f774172927 refactor namespaces to use PathConfig 2018-06-11 14:16:04 -04:00
Jeff Regan
9459665c96 Merge pull request #70 from Liujingfang1/crds
Skip adding nameprefix for CRD
2018-06-11 11:05:05 -07:00
Jeff Regan
7fb4eaec60 Merge pull request #89 from monopole/maybeReduceNameConfusion
Some cleanup refactoring in app package.
2018-06-11 10:34:32 -07:00
jregan
211cda054e Some cleanup refactoring in app package. 2018-06-11 09:51:03 -07:00
Jingfang Liu
5f75564ff5 Skip adding nameprefix for CRD 2018-06-11 09:22:18 -07:00
Jeff Regan
6b5569d5ed Merge pull request #82 from vreon/ingress-tls-secretname
Add Ingress spec.tls.secretName to Secret namerefs
2018-06-10 20:15:52 -07:00
Jeff Regan
e20edaf41e Merge pull request #83 from uthark/returnerror
Return error along with a resource
2018-06-10 20:14:48 -07:00
Jeff Regan
d934a28caf Merge pull request #84 from monopole/troubleWithRawResources
Add more test coverage for RawResources.
2018-06-10 20:14:22 -07:00
jregan
e002b69ffa Add more test coverage for RawResources. 2018-06-10 20:09:31 -07:00
Oleg Atamanenko
bdad67c3ef Return error along with a resource 2018-06-10 21:57:30 -04:00
Jesse Dubay
92864ba0f7 Add Ingress spec.tls.secretName to Secret namerefs 2018-06-10 16:26:50 -07:00
Jeff Regan
ba45b1366a Merge pull request #80 from monopole/fixNameOfPatchTransformer
Rename OverlayTranformer to PatchTransformer.
2018-06-10 11:29:37 -07:00
jregan
d06620c74d Rename OverlayTranformer to PatchTransformer. 2018-06-10 11:23:47 -07:00
Jeff Regan
4f3f76addd Merge pull request #78 from uthark/gocyclo
Fixed gocyclo warnings and added pre-commit hook
2018-06-10 11:11:48 -07:00
Jeff Regan
71b7e07b02 Merge pull request #77 from uthark/golint
Fix go lint error and add golint checks to a pre-commit hook
2018-06-10 10:35:56 -07:00
Jeff Regan
27f652ae8e Merge pull request #76 from monopole/reduceUnstructNoise
Reduce unstruct notation noise in Resource construction.
2018-06-10 09:46:53 -07:00
Oleg Atamanenko
5c8d82aed0 Fixed gocyclo warnings and added pre-commit hook 2018-06-09 23:43:42 -04:00
Oleg Atamanenko
c994130005 Fix go lint error and add golint checks to a pre-commit hook 2018-06-09 23:05:26 -04:00
jregan
ccd255f323 Reduce unstruct notation noise in Resource construction. 2018-06-09 08:10:57 -07:00
Jeff Regan
2b05d39067 Merge pull request #75 from monopole/yada
Handle arb length paths in GetFieldName
2018-06-08 16:24:59 -07:00
Jeffrey Regan
de65503177 Handle arb length paths in GetFieldName 2018-06-08 15:07:57 -07:00
Jeff Regan
7c4ca21578 Merge pull request #71 from sethpollack/namespaces
add namespace to the doc
2018-06-08 12:58:31 -07:00
Jeff Regan
efcc167c35 Merge pull request #72 from Liujingfang1/master
Add wordpress example for demonstrating variable reference
2018-06-08 12:58:01 -07:00
Jingfang Liu
1f8e56a210 Add wordpress example for demonstrating variable reference 2018-06-08 11:49:42 -07:00
Seth Pollack
fa8d6f08e2 add namespace to the doc 2018-06-08 12:47:52 -04:00
Jeff Regan
900ac005dd Merge pull request #66 from monopole/reduceIndirection
Reduce indirection in code w/r to the unstruct types.
2018-06-08 08:59:47 -07:00
jregan
a42c72b2e0 Reduce indirection. 2018-06-07 21:53:55 -07:00
Jeff Regan
a82bd23f8f Merge pull request #52 from Liujingfang1/master
variable reference support
2018-06-06 16:43:48 -07:00
Jeff Regan
ea24b3795c Merge pull request #64 from monopole/docErrorPackage
Doc the error package.
2018-06-06 14:43:56 -07:00
Jingfang Liu
95f568b857 add comments in refvar transformer
Refactor change
2018-06-06 14:42:18 -07:00
Jeffrey Regan
708cd7ef17 Doc the error package. 2018-06-06 14:41:43 -07:00
Jeff Regan
ed7261ca01 Merge pull request #63 from monopole/packageDoc
Improve package structure and documentation.
2018-06-06 14:18:31 -07:00
Jeff Regan
8a27162d98 Merge pull request #62 from sethpollack/ingress
add namerefs for ingress
2018-06-06 14:17:29 -07:00
Jeffrey Regan
2e0e43cd76 Improve package doc. 2018-06-06 14:15:50 -07:00
Jeff Regan
ba2866645e Merge pull request #60 from sethpollack/initContainers
add namerefs for initContainers
2018-06-06 14:04:42 -07:00
Jeff Regan
fd2ee6034b Merge pull request #59 from sethpollack/hpa
add namerefs for hpa
2018-06-06 14:03:15 -07:00
Jeff Regan
86241dfd98 Merge pull request #58 from sethpollack/labels
add PodDisruptionBudget labels
2018-06-06 14:01:11 -07:00
Jingfang Liu
1a28f3a391 Address comments: add unit tests, defaulting and update comment 2018-06-06 10:21:15 -07:00
Jingfang Liu
35344c163a Add test case for variable reference 2018-06-06 09:30:12 -07:00
Jingfang Liu
526ba2df0c add varialber reference support 2018-06-06 09:30:12 -07:00
Seth Pollack
db15bc6372 add namerefs for ingress 2018-06-06 12:17:54 -04:00
Seth Pollack
c83b7019f5 add namerefs for initContainers 2018-06-06 12:04:49 -04:00
Seth Pollack
08e2a1047b add namerefs for hpa 2018-06-06 11:24:12 -04:00
Seth Pollack
69d816d5ab add PodDisruptionBudget labels 2018-06-05 22:02:35 -04:00
Jeff Regan
ea2d2c9db1 Merge pull request #56 from Liujingfang1/expansion
Add expansion package
2018-06-05 17:31:40 -07:00
Jingfang Liu
c7ef8a7e4d Add expansion package 2018-06-05 17:04:53 -07:00
Jeff Regan
9bda5cf092 Merge pull request #54 from sethpollack/namespace
kustomize namespace
2018-06-05 16:22:48 -07:00
Seth Pollack
815db033cb add namespace.yaml 2018-06-05 18:41:48 -04:00
Seth Pollack
501e1a406d add regression coverage 2018-06-05 15:32:22 -04:00
Seth Pollack
6e54814b6d kustomize namespace 2018-06-04 22:35:03 -04:00
Jeff Regan
df2407ad3f Merge pull request #49 from monopole/removeAnotherUtil
Remove util and privatize selectByGvk
2018-06-01 14:20:29 -07:00
Jeffrey Regan
d0c6a824c8 Remove util and privatize selectByGvk 2018-06-01 14:17:56 -07:00
Jeff Regan
57f3743be8 Merge pull request #47 from monopole/introduceResourceMap
Introduce ResId and ResMap in resmap package.
2018-06-01 14:02:46 -07:00
jregan
ef71cb478f Introduce ResId and ResMap. 2018-06-01 13:56:43 -07:00
Jeff Regan
9a5c0f5a25 Merge pull request #46 from monopole/dropUtilPackage
Drop the util package.
2018-05-31 17:24:36 -07:00
Jeffrey Regan
0e2c71cd6f Drop the util package. 2018-05-31 17:17:41 -07:00
Jeff Regan
0b3ab31248 Merge pull request #45 from monopole/createDiffPackage
Create and document diff package.
2018-05-31 16:57:11 -07:00
Jeffrey Regan
49f586af39 Create and document diff package. 2018-05-31 16:50:34 -07:00
Jeff Regan
487a6ebee4 Merge pull request #43 from monopole/fixLinkInHelp
Fix URL in help message
2018-05-31 14:18:11 -07:00
Jeff Regan
bee9490129 Merge pull request #44 from monopole/constantsCleanup
Document constants package.
2018-05-31 14:17:54 -07:00
Jeffrey Regan
8afba0b56c Document constants package. 2018-05-31 13:42:03 -07:00
jregan
16fa3403a4 Fix kubernetes-sigs/kustomize#40 2018-05-30 20:28:46 -07:00
Jeff Regan
bcb89ee908 Merge pull request #39 from guineveresaenger/no-secret
Removes help text for `kustomize edit add secret`
2018-05-30 10:11:01 -07:00
Jeff Regan
37f03b4d01 Merge pull request #37 from ahmetb/mv-examples
demos --> examples
2018-05-30 10:08:44 -07:00
guineveresaenger
bc144275b4 Removes help text for kustomize edit add secret
There is currently no support for adding a secret with kustomize. The help text should not show such an option.
Fixes #14.
2018-05-29 10:27:18 -07:00
Ahmet Alp Balkan
7086e4f974 demos --> examples
examples/ is pretty standard whereas demos/ isn't. So I just refactored that.

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
2018-05-25 10:07:46 -07:00
Jeff Regan
4d111436aa Merge pull request #35 from monopole/baseOverlayNotes
Base overlay notes
2018-05-24 17:08:24 -07:00
Jeffrey Regan
1583486546 base overlay notes 2018-05-24 17:07:19 -07:00
Jeff Regan
10b4c5db43 Merge pull request #34 from monopole/updateReadme
update readmes
2018-05-24 14:16:13 -07:00
Jeffrey Regan
5a54c96203 update readmes 2018-05-24 14:13:10 -07:00
Jeff Regan
4a5b82333b Merge pull request #31 from droot/release-instructions
add instructions for publishing a release
2018-05-24 10:51:08 -07:00
Sunil Arora
e3934ee69c add instructions for publishing a release 2018-05-23 14:20:09 -07:00
Jeff Regan
24daa9e3dc Merge pull request #29 from monopole/updateReadme
Update README with file layout
2018-05-23 10:06:04 -07:00
Jeffrey Regan
d9b422cc54 Update README with file layout 2018-05-23 10:05:11 -07:00
Jeff Regan
d62fb53472 Merge pull request #28 from monopole/updateworkflow
Update workflow doc
2018-05-22 16:53:47 -07:00
Jeffrey Regan
00e52095d6 Update workflow doc 2018-05-22 16:53:07 -07:00
Jeff Regan
9c6d31d70e Merge pull request #2 from rajatjindal/patch-1
Update README.md
2018-05-22 14:40:14 -07:00
Jeff Regan
a734b96560 Merge pull request #26 from monopole/newFigs
betterFigs
2018-05-22 12:21:25 -07:00
Jeffrey Regan
8f3fe7d4bd betterFigs 2018-05-22 12:21:02 -07:00
Jeff Regan
5f950813c4 Merge pull request #25 from monopole/removeLastInstanceUsage
use variant in figure
2018-05-22 12:15:12 -07:00
Jeffrey Regan
920f53853b use variant in figure 2018-05-22 12:14:33 -07:00
Rajat Jindal
aa88a0563b Update README.md 2018-05-14 13:53:13 -07:00
177 changed files with 7279 additions and 3251 deletions

View File

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

View File

@@ -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
View File

@@ -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
View 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
View File

@@ -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.
[![Build Status](https://travis-ci.org/kubernetes-sigs/kustomize.svg?branch=master)](https://travis-ci.org/kubernetes-sigs/kustomize)
[![Go Report Card](https://goreportcard.com/badge/github.com/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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

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

View File

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

View File

@@ -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"
```

View File

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

View File

@@ -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}"

View File

@@ -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"
```

View File

@@ -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"

View 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
>
> ```

View 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

View 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: {}

View File

@@ -0,0 +1,4 @@
resources:
- deployment.yaml
- service.yaml
- secret.yaml

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
type: Opaque
data:
# Default password is "admin".
password: YWRtaW4=

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
selector:
app: mysql

View 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

View 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: {}

View File

@@ -0,0 +1,3 @@
resources:
- deployment.yaml
- service.yaml

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
type: LoadBalancer

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -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
}

View File

@@ -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)
}
}

View File

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

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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"
)

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -0,0 +1,6 @@
namePrefix: p1-
configMapGenerator:
- name: com1
behavior: create
literals:
- from=base

View File

@@ -0,0 +1,6 @@
namePrefix: p2-
configMapGenerator:
- name: com2
behavior: create
literals:
- from=base

View 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

View 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

View File

@@ -0,0 +1,9 @@
bases:
- myapp/mycomponent
- myapp/mycomponent2
configMapGenerator:
- name: com1
behavior: merge
literals:
- foo=bar
- baz=qux

View File

@@ -0,0 +1,7 @@
bases:
- ../../../../base/myapp/mycomponent
configMapGenerator:
- name: com1
behavior: merge
literals:
- from=overlay

View File

@@ -0,0 +1,7 @@
bases:
- ../../../../base/myapp/mycomponent2
configMapGenerator:
- name: com2
behavior: merge
literals:
- from=overlay

View 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

View 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:

View 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

View File

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

View 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

View 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

View 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

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
View 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
View 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
View 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
View 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
}
}

View 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)
}
}
}

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