Compare commits

...

25 Commits

Author SHA1 Message Date
Kubernetes Prow Robot
c464fb0a81 Merge pull request #1436 from richardmarshall/kubectl_clarity
docs: Additional details for kubectl integration
2019-08-13 15:18:23 -07:00
Kubernetes Prow Robot
694cf23df8 Merge pull request #1432 from richardmarshall/lostreplicas
Retain replicas field in edit marshal path
2019-08-09 14:29:14 -07:00
Richard Marshall
e66656aa7f docs: Additional details for kubectl integration 2019-08-08 17:06:19 -07:00
Richard Marshall
eaae7af5fe Retain replicas field in edit marshal path 2019-08-08 15:45:56 -07:00
Kubernetes Prow Robot
f9fe138114 Merge pull request #1416 from anthonyho007/makefile
add Makefile for local development
2019-08-07 11:04:09 -07:00
Kubernetes Prow Robot
2a2a889c37 Merge pull request #1423 from sunny0826/master
Update zh-README.md & zh-example-README.md
2019-08-02 12:37:55 -07:00
郭旭东
34287e511f fix example-zh-README.md 2019-08-02 09:09:32 +08:00
Anthony Ho
e6fffc8ba4 add makefile 2019-08-01 11:23:38 -04:00
郭旭东
86f221611e Update zh-example-README.md 2019-08-01 15:30:11 +08:00
郭旭东
b4d6e89fa2 Update zh-README.md 2019-08-01 15:19:14 +08:00
Kubernetes Prow Robot
5937bd0259 Merge pull request #1394 from richardmarshall/namerefperformance
Simplify name reference candidate resmap building
2019-07-31 10:18:14 -07:00
Richard Marshall
ed3c29be12 Simplify name reference candidate resmap building
This patch removes a layer of looping in the name reference candiate
resmap building process by not checking if the resources already exist
in the new resmap.
2019-07-30 17:15:15 -07:00
Kubernetes Prow Robot
3d2e956b19 Merge pull request #1412 from richardmarshall/anchor_resmap_select
Automatically anchor resource selector patterns
2019-07-30 15:47:11 -07:00
Kubernetes Prow Robot
dd9d1f95e9 Merge pull request #1389 from Liujingfang1/repospec
make repospec memebers public
2019-07-30 15:27:51 -07:00
jingfangliu
a279c08f7d make repospec memebers public 2019-07-30 13:56:20 -07:00
Kubernetes Prow Robot
a798109161 Merge pull request #1413 from bai/fix-typo
Fix typo in patches definition
2019-07-30 10:14:51 -07:00
Vlad Gorodetsky
bafd6b5423 Fix typo in patches definition 2019-07-30 14:52:02 +03:00
Richard Marshall
963913f9ef Automatically anchor resource selector patterns 2019-07-29 17:57:33 -07:00
Jingfang Liu
46905588ac add document for inline patch (#1411) 2019-07-29 15:15:06 -07:00
Kubernetes Prow Robot
5426888df4 Merge pull request #1405 from Liujingfang1/inlinepatch
add inline patch support for Strategic Merge Patch and JSON patch
2019-07-29 14:28:49 -07:00
jingfangliu
35481ec6d9 add inline patch support for Strategic Merge Patch and JSON patch 2019-07-29 14:10:57 -07:00
Kubernetes Prow Robot
6c92c30e94 Merge pull request #1402 from damienr74/currentid-replicas
Allow replicas to find modified names.
2019-07-29 12:54:47 -07:00
Damien Robichaud
02f6b3ec98 Allow replicas to find modified names.
Also allows to test for modified resmaps instead of directly loading
them.
2019-07-26 18:00:59 -07:00
Kubernetes Prow Robot
a9848f2738 Merge pull request #1403 from Liujingfang1/inlinepatch
add testing for patch transformers
2019-07-26 15:05:57 -07:00
jingfangliu
b4038a6cd2 add testting for patch transformers 2019-07-26 14:02:52 -07:00
25 changed files with 725 additions and 75 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
/kustomize
# Test binary, build with `go test -c` # Test binary, build with `go test -c`
*.test *.test

28
Makefile Normal file
View File

@@ -0,0 +1,28 @@
BIN_NAME=kustomize
export GO111MODULE=on
all: test build
test: generate-code test-lint test-go
test-go:
go test -v ./...
test-lint:
golangci-lint run ./...
generate-code:
./plugin/generateBuiltins.sh $(GOPATH)
build:
go build -o $(BIN_NAME) cmd/kustomize/main.go
install:
go install $(PWD)/cmd/kustomize
clean:
go clean
rm -f $(BIN_NAME)
.PHONY: test build install clean generate-code test-go test-lint

View File

@@ -24,8 +24,16 @@ these [instructions](docs/INSTALL.md).
Browse the [docs](docs) or jump right into the Browse the [docs](docs) or jump right into the
tested [examples](examples). tested [examples](examples).
kustomize [v2.0.3] is available in [kubectl v1.14 and v1.15][kubectl]. ## kubectl integration
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
| kubectl version | kustomize version |
|---------|--------|
| v1.15.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
| v1.14.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
## Usage ## Usage
@@ -161,7 +169,9 @@ is governed by the [Kubernetes Code of Conduct].
[imageBase]: docs/images/base.jpg [imageBase]: docs/images/base.jpg
[imageOverlay]: docs/images/overlay.jpg [imageOverlay]: docs/images/overlay.jpg
[kind/feature]: https://github.com/kubernetes-sigs/kustomize/labels/kind%2Ffeature [kind/feature]: https://github.com/kubernetes-sigs/kustomize/labels/kind%2Ffeature
[kubectl]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement [kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
[kubectl book]: https://kubectl.docs.kubernetes.io/pages/app_customization/introduction.html
[kubernetes documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
[kubernetes style]: docs/glossary.md#kubernetes-style-object [kubernetes style]: docs/glossary.md#kubernetes-style-object
[kustomization]: docs/glossary.md#kustomization [kustomization]: docs/glossary.md#kustomization
[overlay]: docs/glossary.md#overlay [overlay]: docs/glossary.md#overlay

View File

@@ -38,6 +38,7 @@ What transformations (customizations) should be applied?
| [namePrefix](#nameprefix) | string | Prepends value to the names of all resources | | [namePrefix](#nameprefix) | string | Prepends value to the names of all resources |
| [nameSuffix](#namesuffix) | string | The value is appended to the names of all resources. | | [nameSuffix](#namesuffix) | string | The value is appended to the names of all resources. |
| [replicas](#replicas) | list | Replicas modifies the number of replicas of a resource. | | [replicas](#replicas) | list | Replicas modifies the number of replicas of a resource. |
| [patches](#patches) | list | Each entry should resolve to a patch that can be applied to multiple targets. |
|[patchesStrategicMerge](#patchesstrategicmerge)| list |Each entry in this list should resolve to a partial or complete resource definition file.| |[patchesStrategicMerge](#patchesstrategicmerge)| list |Each entry in this list should resolve to a partial or complete resource definition file.|
|[patchesJson6902](#patchesjson6902)| list |Each entry in this list should resolve to a kubernetes object and a JSON patch that will be applied to the object.| |[patchesJson6902](#patchesjson6902)| list |Each entry in this list should resolve to a kubernetes object and a JSON patch that will be applied to the object.|
|[transformers](#transformers)|list|[plugin](plugins) configuration files| |[transformers](#transformers)|list|[plugin](plugins) configuration files|
@@ -263,11 +264,47 @@ resource type is ConfigMap or Secret.
nameSuffix: -v2 nameSuffix: -v2
``` ```
### patches
Each entry in this list should resolve to an Patch object,
which includes a patch and a target selector.
The patch can be either a strategic merge patch or a JSON patch.
it can be either a patch file or an inline string.
The target selects
resources by group, version, kind, name, namespace,
labelSelector and annotationSelector. A resource
which matches all the specified fields is selected
to apply the patch.
```
patches:
- path: patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: deploy.*
labelSelector: "env=dev"
annotationSelector: "zone=west"
- patch: |-
- op: replace
path: some/existing/path
value: new value
target:
kind: MyKind
labelSelector: "env=dev"
```
The `name` and `namespace` fields of the patch target selector are
automatically anchored regular expressions. This means that the value `myapp`
is equivalent to `^myapp$`.
### patchesStrategicMerge ### patchesStrategicMerge
Each entry in this list should be a relative path Each entry in this list should be either a relative
file path or an inline content
resolving to a partial or complete resource resolving to a partial or complete resource
definition file. definition.
The names in these (possibly partial) resource The names in these (possibly partial) resource
files must match names already loaded via the files must match names already loaded via the
@@ -286,6 +323,22 @@ patchesStrategicMerge:
- deployment_increase_memory.yaml - deployment_increase_memory.yaml
``` ```
The patch content can be a inline string as well.
```
patchesStrategicMerge:
- |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
image: nignx:latest
```
### patchesJson6902 ### patchesJson6902
Each entry in this list should resolve to Each entry in this list should resolve to
@@ -330,6 +383,23 @@ patchesJson6902:
path: add_service_annotation.yaml path: add_service_annotation.yaml
``` ```
The patch content can be an inline string as well:
```
patchesJson6902:
- target:
version: v1
kind: Deployment
name: my-deployment
patch: |-
- op: add
path: /some/new/path
value: value
- op: replace
path: /some/existing/path
value: "new value"
```
### replicas ### replicas
Replicas modified the number of replicas for a resource. Replicas modified the number of replicas for a resource.

View File

@@ -19,6 +19,8 @@
## 发行说明 ## 发行说明
* [3.1](../v3.1.0.md) - 2019年7月下旬扩展 patches 和改进的资源匹配。
* [3.0](../v3.0.0.md) - 2019年6月下旬插件开发者发布。 * [3.0](../v3.0.0.md) - 2019年6月下旬插件开发者发布。
* [2.1](../v2.1.0.md) - 2019年6月18日 * [2.1](../v2.1.0.md) - 2019年6月18日

View File

@@ -26,6 +26,8 @@ go get sigs.k8s.io/kustomize/v3/cmd/kustomize
* [json patch](jsonpatch.md) -在 kustomization 中应用 json patch 。 * [json patch](jsonpatch.md) -在 kustomization 中应用 json patch 。
* [patch multiple objects](patchMultipleObjects.md) - 通过一个patch来修改多个资源。
高级用法 高级用法
- generator 插件: - generator 插件:
@@ -34,6 +36,10 @@ go get sigs.k8s.io/kustomize/v3/cmd/kustomize
* [secret generation](../secretGeneratorPlugin.md) - 生成 Secret。 * [secret generation](../secretGeneratorPlugin.md) - 生成 Secret。
- transformer 插件:
* [validation transformer](../validationTransformer/README.md) - 通过 transformer 验证资源。
- 定制内建 transformer 配置 - 定制内建 transformer 配置
* [transformer configs](../transformerconfigs/README.md) - 自定义 transformer 配置。 * [transformer configs](../transformerconfigs/README.md) - 自定义 transformer 配置。

1
go.sum
View File

@@ -149,7 +149,6 @@ k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c=
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco= k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco=
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -67,6 +67,7 @@ func determineFieldOrder() []string {
"GeneratorOptions", "GeneratorOptions",
"Vars", "Vars",
"Images", "Images",
"Replicas",
"Configurations", "Configurations",
"Generators", "Generators",
"Transformers", "Transformers",

View File

@@ -46,6 +46,7 @@ func TestFieldOrder(t *testing.T) {
"GeneratorOptions", "GeneratorOptions",
"Vars", "Vars",
"Images", "Images",
"Replicas",
"Configurations", "Configurations",
"Generators", "Generators",
"Transformers", "Transformers",

View File

@@ -35,14 +35,14 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
if err != nil { if err != nil {
return errors.Wrap(err, "no 'git' program on path") return errors.Wrap(err, "no 'git' program on path")
} }
repoSpec.cloneDir, err = fs.NewTmpConfirmedDir() repoSpec.Dir, err = fs.NewTmpConfirmedDir()
if err != nil { if err != nil {
return err return err
} }
cmd := exec.Command( cmd := exec.Command(
gitProgram, gitProgram,
"init", "init",
repoSpec.cloneDir.String()) repoSpec.Dir.String())
var out bytes.Buffer var out bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
err = cmd.Run() err = cmd.Run()
@@ -50,7 +50,7 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
return errors.Wrapf( return errors.Wrapf(
err, err,
"trouble initializing empty git repo in %s", "trouble initializing empty git repo in %s",
repoSpec.cloneDir.String()) repoSpec.Dir.String())
} }
cmd = exec.Command( cmd = exec.Command(
@@ -60,7 +60,7 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
"origin", "origin",
repoSpec.CloneSpec()) repoSpec.CloneSpec())
cmd.Stdout = &out cmd.Stdout = &out
cmd.Dir = repoSpec.cloneDir.String() cmd.Dir = repoSpec.Dir.String()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return errors.Wrapf( return errors.Wrapf(
@@ -68,20 +68,20 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
"trouble adding remote %s", "trouble adding remote %s",
repoSpec.CloneSpec()) repoSpec.CloneSpec())
} }
if repoSpec.ref == "" { if repoSpec.Ref == "" {
repoSpec.ref = "master" repoSpec.Ref = "master"
} }
cmd = exec.Command( cmd = exec.Command(
gitProgram, gitProgram,
"fetch", "fetch",
"--depth=1", "--depth=1",
"origin", "origin",
repoSpec.ref) repoSpec.Ref)
cmd.Stdout = &out cmd.Stdout = &out
cmd.Dir = repoSpec.cloneDir.String() cmd.Dir = repoSpec.Dir.String()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return errors.Wrapf(err, "trouble fetching %s", repoSpec.ref) return errors.Wrapf(err, "trouble fetching %s", repoSpec.Ref)
} }
cmd = exec.Command( cmd = exec.Command(
@@ -90,11 +90,11 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
"--hard", "--hard",
"FETCH_HEAD") "FETCH_HEAD")
cmd.Stdout = &out cmd.Stdout = &out
cmd.Dir = repoSpec.cloneDir.String() cmd.Dir = repoSpec.Dir.String()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return errors.Wrapf( return errors.Wrapf(
err, "trouble hard resetting empty repository to %s", repoSpec.ref) err, "trouble hard resetting empty repository to %s", repoSpec.Ref)
} }
return nil return nil
} }
@@ -105,7 +105,7 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
// used in a test. // used in a test.
func DoNothingCloner(dir fs.ConfirmedDir) Cloner { func DoNothingCloner(dir fs.ConfirmedDir) Cloner {
return func(rs *RepoSpec) error { return func(rs *RepoSpec) error {
rs.cloneDir = dir rs.Dir = dir
return nil return nil
} }
} }

View File

@@ -39,36 +39,36 @@ type RepoSpec struct {
raw string raw string
// Host, e.g. github.com // Host, e.g. github.com
host string Host string
// orgRepo name (organization/repoName), // orgRepo name (organization/repoName),
// e.g. kubernetes-sigs/kustomize // e.g. kubernetes-sigs/kustomize
orgRepo string OrgRepo string
// ConfirmedDir where the orgRepo is cloned to. // Dir where the orgRepo is cloned to.
cloneDir fs.ConfirmedDir Dir fs.ConfirmedDir
// Relative path in the repository, and in the cloneDir, // Relative path in the repository, and in the cloneDir,
// to a Kustomization. // to a Kustomization.
path string Path string
// Branch or tag reference. // Branch or tag reference.
ref string Ref string
// e.g. .git or empty in case of _git is present // e.g. .git or empty in case of _git is present
gitSuffix string GitSuffix string
} }
// CloneSpec returns a string suitable for "git clone {spec}". // CloneSpec returns a string suitable for "git clone {spec}".
func (x *RepoSpec) CloneSpec() string { func (x *RepoSpec) CloneSpec() string {
if isAzureHost(x.host) || isAWSHost(x.host) { if isAzureHost(x.Host) || isAWSHost(x.Host) {
return x.host + x.orgRepo return x.Host + x.OrgRepo
} }
return x.host + x.orgRepo + x.gitSuffix return x.Host + x.OrgRepo + x.GitSuffix
} }
func (x *RepoSpec) CloneDir() fs.ConfirmedDir { func (x *RepoSpec) CloneDir() fs.ConfirmedDir {
return x.cloneDir return x.Dir
} }
func (x *RepoSpec) Raw() string { func (x *RepoSpec) Raw() string {
@@ -76,11 +76,11 @@ func (x *RepoSpec) Raw() string {
} }
func (x *RepoSpec) AbsPath() string { func (x *RepoSpec) AbsPath() string {
return x.cloneDir.Join(x.path) return x.Dir.Join(x.Path)
} }
func (x *RepoSpec) Cleaner(fSys fs.FileSystem) func() error { func (x *RepoSpec) Cleaner(fSys fs.FileSystem) func() error {
return func() error { return fSys.RemoveAll(x.cloneDir.String()) } return func() error { return fSys.RemoveAll(x.Dir.String()) }
} }
// From strings like git@github.com:someOrg/someRepo.git or // From strings like git@github.com:someOrg/someRepo.git or
@@ -98,8 +98,8 @@ func NewRepoSpecFromUrl(n string) (*RepoSpec, error) {
return nil, fmt.Errorf("url lacks host: %s", n) return nil, fmt.Errorf("url lacks host: %s", n)
} }
return &RepoSpec{ return &RepoSpec{
raw: n, host: host, orgRepo: orgRepo, raw: n, Host: host, OrgRepo: orgRepo,
cloneDir: notCloned, path: path, ref: gitRef, gitSuffix: gitSuffix}, nil Dir: notCloned, Path: path, Ref: gitRef, GitSuffix: gitSuffix}, nil
} }
const ( const (

View File

@@ -72,17 +72,17 @@ func TestNewRepoSpecFromUrl(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("problem %v", err) t.Errorf("problem %v", err)
} }
if rs.host != hostSpec { if rs.Host != hostSpec {
bad = append(bad, []string{"host", uri, rs.host, hostSpec}) bad = append(bad, []string{"host", uri, rs.Host, hostSpec})
} }
if rs.orgRepo != orgRepo { if rs.OrgRepo != orgRepo {
bad = append(bad, []string{"orgRepo", uri, rs.orgRepo, orgRepo}) bad = append(bad, []string{"orgRepo", uri, rs.OrgRepo, orgRepo})
} }
if rs.path != pathName { if rs.Path != pathName {
bad = append(bad, []string{"path", uri, rs.path, pathName}) bad = append(bad, []string{"path", uri, rs.Path, pathName})
} }
if rs.ref != hrefArg { if rs.Ref != hrefArg {
bad = append(bad, []string{"ref", uri, rs.ref, hrefArg}) bad = append(bad, []string{"ref", uri, rs.Ref, hrefArg})
} }
} }
} }
@@ -201,9 +201,9 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) {
t.Errorf("AbsPath expected to be %v, but got %v on %s", t.Errorf("AbsPath expected to be %v, but got %v on %s",
testcase.absPath, rs.AbsPath(), testcase.input) testcase.absPath, rs.AbsPath(), testcase.input)
} }
if rs.ref != testcase.ref { if rs.Ref != testcase.ref {
t.Errorf("ref expected to be %v, but got %v on %s", t.Errorf("ref expected to be %v, but got %v on %s",
testcase.ref, rs.ref, testcase.input) testcase.ref, rs.Ref, testcase.input)
} }
} }
} }

View File

@@ -138,11 +138,16 @@ func (th *KustTestHarness) ErrorFromLoadAndRunTransformer(
func (th *KustTestHarness) RunTransformer( func (th *KustTestHarness) RunTransformer(
config, input string) (resmap.ResMap, error) { config, input string) (resmap.ResMap, error) {
transConfig, err := th.rf.RF().FromBytes([]byte(config)) resMap, err := th.rf.NewResMapFromBytes([]byte(input))
if err != nil { if err != nil {
th.t.Fatalf("Err: %v", err) th.t.Fatalf("Err: %v", err)
} }
resMap, err := th.rf.NewResMapFromBytes([]byte(input)) return th.RunTransformerFromResMap(config, resMap)
}
func (th *KustTestHarness) RunTransformerFromResMap(
config string, resMap resmap.ResMap) (resmap.ResMap, error) {
transConfig, err := th.rf.RF().FromBytes([]byte(config))
if err != nil { if err != nil {
th.t.Fatalf("Err: %v", err) th.t.Fatalf("Err: %v", err)
} }

View File

@@ -552,7 +552,7 @@ func (m *resWrangler) makeCopy(copier resCopier) ResMap {
// SubsetThatCouldBeReferencedByResource implements ResMap. // SubsetThatCouldBeReferencedByResource implements ResMap.
func (m *resWrangler) SubsetThatCouldBeReferencedByResource( func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
inputRes *resource.Resource) ResMap { inputRes *resource.Resource) ResMap {
result := New() result := newOne()
inputId := inputRes.CurId() inputId := inputRes.CurId()
isInputIdNamespaceable := inputId.IsNamespaceableKind() isInputIdNamespaceable := inputId.IsNamespaceableKind()
rctxm := inputRes.PrefixesSuffixesEquals rctxm := inputRes.PrefixesSuffixesEquals
@@ -563,15 +563,16 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
resId := r.CurId() resId := r.CurId()
if (!isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId)) && if (!isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId)) &&
r.InSameKustomizeCtx(rctxm) { r.InSameKustomizeCtx(rctxm) {
err := result.Append(r) result.append(r)
if err != nil {
panic(err)
}
} }
} }
return result return result
} }
func (m *resWrangler) append(res *resource.Resource) {
m.rList = append(m.rList, res)
}
// AppendAll implements ResMap. // AppendAll implements ResMap.
func (m *resWrangler) AppendAll(other ResMap) error { func (m *resWrangler) AppendAll(other ResMap) error {
if other == nil { if other == nil {
@@ -650,11 +651,18 @@ func (m *resWrangler) appendReplaceOrMerge(
return nil return nil
} }
func anchorRegex(pattern string) string {
if pattern == "" {
return pattern
}
return "^" + pattern + "$"
}
// Select returns a list of resources that // Select returns a list of resources that
// are selected by a Selector // are selected by a Selector
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) { func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
ns := regexp.MustCompile(s.Namespace) ns := regexp.MustCompile(anchorRegex(s.Namespace))
nm := regexp.MustCompile(s.Name) nm := regexp.MustCompile(anchorRegex(s.Name))
var result []*resource.Resource var result []*resource.Resource
for _, r := range m.Resources() { for _, r := range m.Resources() {
curId := r.CurId() curId := r.CurId()

View File

@@ -41,6 +41,12 @@ metadata:
app: name3 app: name3
annotations: annotations:
bar: baz bar: baz
---
apiVersion: group1/v1
kind: Kind2
metadata:
name: x-name1
namespace: x-default
`)) `))
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
@@ -56,13 +62,13 @@ func TestFindPatchTargets(t *testing.T) {
}{ }{
{ {
target: types.Selector{ target: types.Selector{
Name: "name*", Name: "name.*",
}, },
count: 3, count: 3,
}, },
{ {
target: types.Selector{ target: types.Selector{
Name: "name*", Name: "name.*",
AnnotationSelector: "foo=bar", AnnotationSelector: "foo=bar",
}, },
count: 2, count: 2,
@@ -78,7 +84,7 @@ func TestFindPatchTargets(t *testing.T) {
Gvk: gvk.Gvk{ Gvk: gvk.Gvk{
Kind: "Kind1", Kind: "Kind1",
}, },
Name: "name*", Name: "name.*",
}, },
count: 2, count: 2,
}, },
@@ -92,7 +98,7 @@ func TestFindPatchTargets(t *testing.T) {
target: types.Selector{ target: types.Selector{
Name: "", Name: "",
}, },
count: 3, count: 4,
}, },
{ {
target: types.Selector{ target: types.Selector{
@@ -104,18 +110,60 @@ func TestFindPatchTargets(t *testing.T) {
target: types.Selector{ target: types.Selector{
Namespace: "", Namespace: "",
}, },
count: 3, count: 4,
}, },
{ {
target: types.Selector{ target: types.Selector{
Namespace: "default", Namespace: "default",
Name: "name*", Name: "name.*",
Gvk: gvk.Gvk{ Gvk: gvk.Gvk{
Kind: "Kind1", Kind: "Kind1",
}, },
}, },
count: 1, count: 1,
}, },
{
target: types.Selector{
Name: "^name.*",
},
count: 3,
},
{
target: types.Selector{
Name: "name.*$",
},
count: 3,
},
{
target: types.Selector{
Name: "^name.*$",
},
count: 3,
},
{
target: types.Selector{
Namespace: "^def.*",
},
count: 2,
},
{
target: types.Selector{
Namespace: "def.*$",
},
count: 2,
},
{
target: types.Selector{
Namespace: "^def.*$",
},
count: 2,
},
{
target: types.Selector{
Namespace: "default",
},
count: 2,
},
} }
for _, testcase := range testcases { for _, testcase := range testcases {
actual, err := rm.Select(testcase.target) actual, err := rm.Select(testcase.target)

View File

@@ -0,0 +1,243 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package target_test
import (
"sigs.k8s.io/kustomize/v3/pkg/kusttest"
"testing"
)
func makeResourcesForPatchTest(th *kusttest_test.KustTestHarness) {
th.WriteF("/app/base/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: nginx-persistent-storage
mountPath: /tmp/ps
volumes:
- name: nginx-persistent-storage
emptyDir: {}
- configMap:
name: configmap-in-base
name: configmap-in-base
`)
}
func TestStrategicMergePatchInline(t *testing.T) {
th := kusttest_test.NewKustTestHarness(t, "/app/base")
makeResourcesForPatchTest(th)
th.WriteK("/app/base", `
resources:
- deployment.yaml
patchesStrategicMerge:
- |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
image: image1
`)
m, err := th.MakeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: image1
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
`)
}
func TestJSONPatchInline(t *testing.T) {
th := kusttest_test.NewKustTestHarness(t, "/app/base")
makeResourcesForPatchTest(th)
th.WriteK("/app/base", `
resources:
- deployment.yaml
patchesJson6902:
- target:
group: apps
version: v1
kind: Deployment
name: nginx
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: image1
`)
m, err := th.MakeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: image1
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
`)
}
func TestExtendedPatchInlineJSON(t *testing.T) {
th := kusttest_test.NewKustTestHarness(t, "/app/base")
makeResourcesForPatchTest(th)
th.WriteK("/app/base", `
resources:
- deployment.yaml
patches:
- target:
kind: Deployment
name: nginx
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: image1
`)
m, err := th.MakeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: image1
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
`)
}
func TestExtendedPatchInlineYAML(t *testing.T) {
th := kusttest_test.NewKustTestHarness(t, "/app/base")
makeResourcesForPatchTest(th)
th.WriteK("/app/base", `
resources:
- deployment.yaml
patches:
- target:
kind: Deployment
name: nginx
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
image: image1
`)
m, err := th.MakeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: image1
name: nginx
volumeMounts:
- mountPath: /tmp/ps
name: nginx-persistent-storage
volumes:
- emptyDir: {}
name: nginx-persistent-storage
- configMap:
name: configmap-in-base
name: configmap-in-base
`)
}

View File

@@ -156,7 +156,7 @@ func (kt *KustTarget) configureBuiltinPatchJson6902Transformer(
for _, args := range kt.kustomization.PatchesJson6902 { for _, args := range kt.kustomization.PatchesJson6902 {
c.Target = *args.Target c.Target = *args.Target
c.Path = args.Path c.Path = args.Path
c.JsonOp = "" // Not implemented for kustomization file yet. c.JsonOp = args.Patch
p := builtin.NewPatchJson6902TransformerPlugin() p := builtin.NewPatchJson6902TransformerPlugin()
err = kt.configureBuiltinPlugin(p, c, "patchJson6902") err = kt.configureBuiltinPlugin(p, c, "patchJson6902")
if err != nil { if err != nil {
@@ -178,7 +178,6 @@ func (kt *KustTarget) configureBuiltinPatchStrategicMergeTransformer(
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"` Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
} }
c.Paths = kt.kustomization.PatchesStrategicMerge c.Paths = kt.kustomization.PatchesStrategicMerge
c.Patches = "" // Not implemented for kustomization file yet
p := builtin.NewPatchStrategicMergeTransformerPlugin() p := builtin.NewPatchStrategicMergeTransformerPlugin()
err = kt.configureBuiltinPlugin(p, c, "patchStrategicMerge") err = kt.configureBuiltinPlugin(p, c, "patchStrategicMerge")
if err != nil { if err != nil {

View File

@@ -331,6 +331,9 @@ type PatchJson6902 struct {
// relative file path for a json patch file inside a kustomization // relative file path for a json patch file inside a kustomization
Path string `json:"path,omitempty" yaml:"path,omitempty"` Path string `json:"path,omitempty" yaml:"path,omitempty"`
// inline patch string
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
} }
// PatchTarget represents the kubernetes object that the patch is applied to // PatchTarget represents the kubernetes object that the patch is applied to

View File

@@ -15,7 +15,7 @@ type PatchStrategicMergeTransformerPlugin struct {
rf *resmap.Factory rf *resmap.Factory
loadedPatches []*resource.Resource loadedPatches []*resource.Resource
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
Patches string `json:patches,omitempty" yaml:"patches,omitempty"` Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
} }
//noinspection GoUnusedGlobalVariable //noinspection GoUnusedGlobalVariable
@@ -35,11 +35,18 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
return fmt.Errorf("empty file path and empty patch content") return fmt.Errorf("empty file path and empty patch content")
} }
if len(p.Paths) != 0 { if len(p.Paths) != 0 {
res, err := p.rf.RF().SliceFromPatches(ldr, p.Paths) for _, onePath := range p.Paths {
if err != nil { res, err := p.rf.RF().SliceFromBytes([]byte(onePath))
return err if err == nil {
p.loadedPatches = append(p.loadedPatches, res...)
continue
}
res, err = p.rf.RF().SliceFromPatches(ldr, []types.PatchStrategicMerge{onePath})
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, res...)
} }
p.loadedPatches = res
} }
if p.Patches != "" { if p.Patches != "" {
res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches)) res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches))

View File

@@ -34,8 +34,15 @@ func (p *ReplicaCountTransformerPlugin) Config(
} }
func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error { func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
found := false
for i, replicaSpec := range p.FieldSpecs { for i, replicaSpec := range p.FieldSpecs {
for _, res := range m.GetMatchingResourcesByOriginalId(p.createMatcher(i)) { matcher := p.createMatcher(i)
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
for _, res := range append(matchOriginal, matchCurrent...) {
found = true
err := transformers.MutateField( err := transformers.MutateField(
res.Map(), replicaSpec.PathSlice(), res.Map(), replicaSpec.PathSlice(),
replicaSpec.CreateIfNotPresent, p.addReplicas) replicaSpec.CreateIfNotPresent, p.addReplicas)
@@ -45,6 +52,15 @@ func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
} }
} }
if !found {
gvks := make([]string, len(p.FieldSpecs))
for i, replicaSpec := range p.FieldSpecs {
gvks[i] = replicaSpec.Gvk.String()
}
return fmt.Errorf("Resource with name %s does not match a config with the following GVK %v",
p.Replica.Name, gvks)
}
return nil return nil
} }

View File

@@ -250,7 +250,7 @@ spec:
`) `)
} }
func TestPatchJson6902TransformerWithInline(t *testing.T) { func TestPatchJson6902TransformerWithInlineJSON(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
@@ -290,3 +290,47 @@ spec:
dnsPolicy: ClusterFirst dnsPolicy: ClusterFirst
`) `)
} }
func TestPatchJson6902TransformerWithInlineYAML(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "PatchJson6902Transformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchJson6902Transformer
metadata:
name: notImportantHere
target:
group: apps
version: v1
kind: Deployment
name: myDeploy
jsonOp: |-
- op: add
path: /spec/template/spec/dnsPolicy
value: ClusterFirst
`, target)
th.AssertActualEqualsExpected(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeploy
spec:
replica: 2
template:
metadata:
labels:
old-label: old-value
spec:
containers:
- image: nginx
name: nginx
dnsPolicy: ClusterFirst
`)
}

View File

@@ -18,7 +18,7 @@ type plugin struct {
rf *resmap.Factory rf *resmap.Factory
loadedPatches []*resource.Resource loadedPatches []*resource.Resource
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
Patches string `json:patches,omitempty" yaml:"patches,omitempty"` Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
} }
//noinspection GoUnusedGlobalVariable //noinspection GoUnusedGlobalVariable
@@ -36,11 +36,18 @@ func (p *plugin) Config(
return fmt.Errorf("empty file path and empty patch content") return fmt.Errorf("empty file path and empty patch content")
} }
if len(p.Paths) != 0 { if len(p.Paths) != 0 {
res, err := p.rf.RF().SliceFromPatches(ldr, p.Paths) for _, onePath := range p.Paths {
if err != nil { res, err := p.rf.RF().SliceFromBytes([]byte(onePath))
return err if err == nil {
p.loadedPatches = append(p.loadedPatches, res...)
continue
}
res, err = p.rf.RF().SliceFromPatches(ldr, []types.PatchStrategicMerge{onePath})
if err != nil {
return err
}
p.loadedPatches = append(p.loadedPatches, res...)
} }
p.loadedPatches = res
} }
if p.Patches != "" { if p.Patches != "" {
res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches)) res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches))

View File

@@ -78,7 +78,9 @@ paths:
t.Fatalf("expected error") t.Fatalf("expected error")
} }
if !strings.Contains(err.Error(), if !strings.Contains(err.Error(),
"cannot read file \"/app/patch.yaml\"") { "cannot read file \"/app/patch.yaml\"") &&
!strings.Contains(err.Error(),
"cannot unmarshal string") {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
} }
@@ -181,7 +183,7 @@ spec:
`) `)
} }
func TestPatchStrategicMergeTransformerWithInline(t *testing.T) { func TestPatchStrategicMergeTransformerWithInlineJSON(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
@@ -216,6 +218,58 @@ spec:
`) `)
} }
func TestPatchStrategicMergeTransformerWithInlineYAML(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "PatchStrategicMergeTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PatchStrategicMergeTransformer
metadata:
name: notImportantHere
patches: |-
apiVersion: apps/v1
metadata:
name: myDeploy
kind: Deployment
spec:
replica: 3
---
apiVersion: apps/v1
metadata:
name: myDeploy
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx:latest
`, target)
th.AssertActualEqualsExpected(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeploy
spec:
replica: 3
template:
metadata:
labels:
old-label: old-value
spec:
containers:
- image: nginx:latest
name: nginx
`)
}
func TestPatchStrategicMergeTransformerMultiplePatches(t *testing.T) { func TestPatchStrategicMergeTransformerMultiplePatches(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set() tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset() defer tc.Reset()
@@ -1226,4 +1280,4 @@ paths:
`, target) `, target)
th.AssertActualEqualsExpected(rm, ``) th.AssertActualEqualsExpected(rm, ``)
} }

View File

@@ -35,8 +35,15 @@ func (p *plugin) Config(
} }
func (p *plugin) Transform(m resmap.ResMap) error { func (p *plugin) Transform(m resmap.ResMap) error {
found := false
for i, replicaSpec := range p.FieldSpecs { for i, replicaSpec := range p.FieldSpecs {
for _, res := range m.GetMatchingResourcesByOriginalId(p.createMatcher(i)) { matcher := p.createMatcher(i)
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
for _, res := range append(matchOriginal, matchCurrent...) {
found = true
err := transformers.MutateField( err := transformers.MutateField(
res.Map(), replicaSpec.PathSlice(), res.Map(), replicaSpec.PathSlice(),
replicaSpec.CreateIfNotPresent, p.addReplicas) replicaSpec.CreateIfNotPresent, p.addReplicas)
@@ -46,6 +53,15 @@ func (p *plugin) Transform(m resmap.ResMap) error {
} }
} }
if !found {
gvks := make([]string, len(p.FieldSpecs))
for i, replicaSpec := range p.FieldSpecs {
gvks[i] = replicaSpec.Gvk.String()
}
return fmt.Errorf("Resource with name %s does not match a config with the following GVK %v",
p.Replica.Name, gvks)
}
return nil return nil
} }

View File

@@ -151,3 +151,85 @@ spec:
app: app app: app
`) `)
} }
func TestMatchesCurrentID(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin("builtin", "", "PrefixSuffixTransformer")
tc.BuildGoPlugin("builtin", "", "ReplicaCountTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: PrefixSuffixTransformer
metadata:
name: notImportantHere
suffix: -test
fieldSpecs:
- path: metadata/name
`, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment`)
rm, _ = th.RunTransformerFromResMap(`
apiVersion: builtin
kind: ReplicaCountTransformer
metadata:
name: notImportantHere
replica:
name: deployment-test
count: 23
fieldSpecs:
- path: spec/replicas
create: true
kind: Deployment`, rm)
th.AssertActualEqualsExpected(rm, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test
spec:
replicas: 23
`)
}
func TestNoMatch(t *testing.T) {
tc := plugins_test.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin("builtin", "", "ReplicaCountTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
err := th.ErrorFromLoadAndRunTransformer(`
apiVersion: builtin
kind: ReplicaCountTransformer
metadata:
name: notImportantHere
replica:
name: service
count: 3
fieldSpecs:
- path: spec/replicas
create: true
kind: Deployment
`, `
kind: Service
metadata:
name: service
spec:
`)
if err == nil {
t.Fatalf("No match should return an error")
}
if err.Error() !=
"Resource with name service does not match a config with the following GVK [~G_~V_Deployment]" {
t.Fatalf("Unexpected error: %v", err)
}
}