Compare commits

...

45 Commits

Author SHA1 Message Date
Kubernetes Prow Robot
b67179e951 Merge pull request #820 from st1t/remove-imagetag
Change imagetag to image in docs/eschewedFeatures.md
2019-02-25 08:50:55 -08:00
Kubernetes Prow Robot
47237aa7a2 Merge pull request #813 from yujunz/varref
Add Pod initContainer to var reference
2019-02-25 08:48:49 -08:00
Shota Ito
5e6c06fb61 Change imagetag to image in docs/eschewedFeatures.md 2019-02-25 20:23:36 +09:00
Yujun Zhang
901455eb0b Add Pod initContainer to var reference 2019-02-24 11:36:09 +08:00
Jeff Regan
f8c80b7335 Merge pull request #812 from monopole/moreGeneratorTests
More generator tests.
2019-02-23 04:48:37 -08:00
Kubernetes Prow Robot
8db82d27e9 Merge pull request #811 from narg95/fix_new_tmp_confirmdir
fix absolute path for temp folder in MacOs
2019-02-22 17:31:12 -08:00
Nestor
1eab47b63f fix abs path with symlinks
Signed-off-by: Nestor <nesterran@gmail.com>
2019-02-22 18:29:53 +01:00
Kubernetes Prow Robot
c4656b71e5 Merge pull request #786 from ChrsMark/master
Change ExpandFileSource to work with key=val patterns
2019-02-22 07:14:07 -08:00
Kubernetes Prow Robot
711d3d3515 Merge pull request #809 from narg95/prevent_panic_image_trasformer
prevent panic on image transformer
2019-02-22 07:05:16 -08:00
Jeffrey Regan
0488f570cb More generator tests. 2019-02-22 06:52:41 -08:00
Nestor
0e459ebac8 prevent panic on image transformer
Signed-off-by: Nestor <nesterran@gmail.com>
2019-02-22 08:55:38 +01:00
Kubernetes Prow Robot
70719a8f65 Merge pull request #807 from Agilicus/add-generator-options
Add doc indicating existing of 'behavior' in configMapGenerator
2019-02-21 09:20:31 -08:00
Don Bowman
773c1f2199 Make requested wording changes from PR for behavior document
Signed-off-by: Don Bowman <don@agilicus.com>
2019-02-21 11:53:12 -05:00
Don Bowman
bf1c801a5e Add doc indicating existing of 'behavior' in configMapGenerator
It appears from the code that configMapGenerator can take
a parameter of 'behavior' which is 'merge','create','replace'.
Add note to the 'docs/kustomization.yaml' indicating so.

Signed-off-by: Don Bowman <don@agilicus.com>
2019-02-21 11:31:54 -05:00
Kubernetes Prow Robot
e1420b408c Merge pull request #762 from dimitropoulos/patch-1
typo: makes verb number agree with subject
2019-02-19 16:26:03 -08:00
Kubernetes Prow Robot
88a7471039 Merge pull request #796 from monopole/moreResIDTests
Add more resid test coverage.
2019-02-19 13:44:12 -08:00
Kubernetes Prow Robot
3c58cf0bf0 Merge pull request #789 from mrbrownt/runtime-version
Pulling goos and goarch from runtime
2019-02-19 09:07:27 -08:00
Chris Mark
77eebb89fd Review changes
Signed-off-by: Chris Mark <chrismarkou92@gmail.com>
2019-02-18 09:28:04 +02:00
jregan
d4d993a53c Add more resid test coverage. 2019-02-16 14:26:43 -08:00
Jeff Regan
ef3b0672c5 Merge pull request #795 from monopole/deleteExtraCopyright
Delete extraneous copyright.
2019-02-16 11:56:40 -08:00
jregan
0f30c09cbf Delete extraneous copyright. 2019-02-16 11:52:12 -08:00
Kubernetes Prow Robot
6f670a8f38 Merge pull request #782 from narg95/varref_in_maps_values
add support for varref in maps values
2019-02-15 14:53:21 -08:00
Nestor
8c93f7ba74 add support for varref in maps values 2019-02-14 08:02:32 +01:00
Todd Brown
7d3735b19e Adding goos and goarch from runtime 2019-02-13 10:59:35 -06:00
Chris Mark
f5f8e49fa3 Add explanatory comments and format
Signed-off-by: Chris Mark <chrismarkou92@gmail.com>
2019-02-13 09:38:13 +02:00
Chris
1382d87d7f Change ExpandFileSource to work with key=val patterns
Signed-off-by: Chris <chrismarkou92@gmail.com>
2019-02-13 09:30:06 +02:00
Kubernetes Prow Robot
e65b45f969 Merge pull request #790 from laverya/allow-all-kind-List-types
Allow all kind list types
2019-02-12 15:18:33 -08:00
Andrew Lavery
d72b16235a add a test for a list with no 'items:' provided 2019-02-12 15:07:46 -08:00
Andrew Lavery
3118ccfd05 add tests for *List kinds and empty lists 2019-02-12 12:45:28 -08:00
Andrew Lavery
fdba7df3c1 if the kind matches '*List$', treat it as a list 2019-02-12 12:28:08 -08:00
Kubernetes Prow Robot
02d753027a Merge pull request #788 from Liujingfang1/doc
fix invalid relative path in kustomization.yaml
2019-02-12 09:22:32 -08:00
Jingfang Liu
1a43759ac3 fix invalid relative path in kustomization.yaml 2019-02-12 08:57:56 -08:00
Kubernetes Prow Robot
7574f07be3 Merge pull request #785 from monopole/blackBoxTests
Switch to black box testing of KustTarget and Resource
2019-02-11 18:36:51 -08:00
Jeffrey Regan
48717f3f30 Switch to black box testing of KustTarget and Resource 2019-02-11 16:40:09 -08:00
Jeff Regan
74d3e92b55 Merge pull request #783 from monopole/testKustFileMissingMessage
Test missing kust file message
2019-02-11 10:18:54 -08:00
Kubernetes Prow Robot
f66024b1c1 Merge pull request #781 from saromanov/loop-refactoring
transformers/image: loop refactoring
2019-02-11 10:15:11 -08:00
Kubernetes Prow Robot
bf4e09a400 Merge pull request #761 from narg95/varref_mountpath
add volumeMounts/mountPath to varreference
2019-02-11 08:16:36 -08:00
Nestor
d968c0b4b1 add varref mountpath test case 2019-02-11 07:52:45 +01:00
Nestor
9837b5b429 add volumeMounts/mountPath to varreference
Signed-off-by: Nestor <nesterran@gmail.com>
2019-02-11 07:52:45 +01:00
jregan
1a03dcabde Test missing file report 2019-02-10 15:37:11 -08:00
Kubernetes Prow Robot
6fb11493ad Merge pull request #780 from monopole/addGitUrl
Add more git url regression coverage
2019-02-08 15:08:29 -08:00
Jeffrey Regan
1f063d6712 Add more git url regression coverage 2019-02-08 14:58:34 -08:00
Sergey
cebcd8a44d transformers/image: loop refactoring 2019-02-08 18:42:47 +05:00
Dimitri Mitropoulos
b15b20467c typo: changes verb number to agree with subject 2019-02-04 14:48:43 -05:00
Dimitri Mitropoulos
1d005d47b5 typo: makes verb number agree with subject
also changed `one` to `someone` to be more English-as-a-Second-Language friendly
2019-02-04 14:44:04 -05:00
53 changed files with 1275 additions and 315 deletions

View File

@@ -96,7 +96,7 @@ For example, to set the tag used on an image to match an
environment variable, run
```
kustomize edit set imagetag nginx:$MY_NGINX_VERSION
kustomize edit set image nginx:$MY_NGINX_VERSION
```
as part of some encapsulating work flow executed before

View File

@@ -69,13 +69,16 @@ commonAnnotations:
# markers ("---").
resources:
- some-service.yaml
- ../some-dir/some-deployment.yaml
- sub-dir/some-deployment.yaml
# Each entry in this list results in the creation of
# one ConfigMap resource (it's a generator of n maps).
# The example below creates two ConfigMaps. One with the
# names and contents of the given files, the other with
# key/value as data.
# Each configMapGenerator item accepts a parameter of
# behavior: [create|replace|merge]. This allows an overlay to modify or
# replace an existing configMap from the parent.
configMapGenerator:
- name: myJavaServerProps
files:
@@ -215,7 +218,7 @@ crds:
# Vars are used to capture text from one resource's field
# and insert that text elsewhere.
#
# For example, suppose one specify the name of a k8s Service
# For example, suppose someone specifies the name of a k8s Service
# object in a container's command line, and the name of a
# k8s Secret object in a container's environment variable,
# so that the following would work:
@@ -255,7 +258,7 @@ vars:
# reference within that object. That's where the text is found.
#
# The field reference is optional; it defaults to `metadata.name`,
# a normal default, since kustomize is used to generates or
# a normal default, since kustomize is used to generate or
# modify the names of resources.
#
# At time of writing, only string type fields are supported.

View File

@@ -81,8 +81,8 @@ func (f *ConfigMapFactory) MakeConfigMap(
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToConfigMap(cm, kv.Key, kv.Value)
for _, p := range all {
err = addKvToConfigMap(cm, p.Key, p.Value)
if err != nil {
return nil, err
}

View File

@@ -80,8 +80,8 @@ func (f *SecretFactory) MakeSecret(args *types.SecretArgs, options *types.Genera
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToSecret(s, kv.Key, kv.Value)
for _, p := range all {
err = addKvToSecret(s, p.Key, p.Value)
if err != nil {
return nil, err
}

View File

@@ -108,7 +108,7 @@ func (kf *KunstructuredFactoryImpl) validate(u unstructured.Unstructured) error
kind := u.GetKind()
if kind == "" {
return fmt.Errorf("missing kind in object %v", u)
} else if kind == "List" {
} else if strings.HasSuffix(kind, "List") {
return nil
}
if u.GetName() == "" {

View File

@@ -42,6 +42,15 @@ func TestSliceFromBytes(t *testing.T) {
testConfigMap.Map(),
},
})
testConfigMapList := factory.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMapList",
"items": []interface{}{
testConfigMap.Map(),
testConfigMap.Map(),
},
})
tests := []struct {
name string
@@ -151,6 +160,24 @@ items:
expectedOut: []ifc.Kunstructured{testList},
expectedErr: false,
},
{
name: "ConfigMapList",
input: []byte(`
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
expectedOut: []ifc.Kunstructured{testConfigMapList},
expectedErr: false,
},
}
for _, test := range tests {

View File

@@ -60,7 +60,7 @@ func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
for _, patch := range patches {
// Merge patches with base resource.
id := patch.Id()
matchedIds := baseResourceMap.FindByGVKN(id)
matchedIds := baseResourceMap.GetMatchingIds(id.GvknEquals)
if len(matchedIds) == 0 {
return fmt.Errorf("failed to find an object with %s to apply the patch", id.GvknString())
}

View File

@@ -108,7 +108,7 @@ func (o *Options) RunBuild(
return err
}
defer ldr.Cleanup()
kt, err := target.NewKustTarget(ldr, fSys, rf, ptf)
kt, err := target.NewKustTarget(ldr, rf, ptf)
if err != nil {
return err
}

View File

@@ -18,6 +18,7 @@ package add
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/pkg/fs"
)
@@ -53,11 +54,54 @@ func (a *flagsAndArgs) Validate(args []string) error {
return nil
}
// ExpandFileSource normalizes a string list, possibly
// containing globs, into a validated, globless list.
// For example, this list:
// some/path
// some/dir/a*
// bfile=some/dir/b*
// becomes:
// some/path
// some/dir/airplane
// some/dir/ant
// some/dir/apple
// bfile=some/dir/banana
// i.e. everything is converted to a key=value pair,
// where the value is always a relative file path,
// and the key, if missing, is the same as the value.
// In the case where the key is explicitly declared,
// the globbing, if present, must have exactly one match.
func (a *flagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error {
result, err := globPatterns(fSys, a.FileSources)
if err != nil {
return err
var results []string
for _, pattern := range a.FileSources {
var patterns []string
key := ""
// check if the pattern is in `--from-file=[key=]source` format
// and if so split it to send only the file-pattern to glob function
s := strings.Split(pattern, "=")
if len(s) == 2 {
patterns = append(patterns, s[1])
key = s[0]
} else {
patterns = append(patterns, s[0])
}
result, err := globPatterns(fSys, patterns)
if err != nil {
return err
}
// if the format is `--from-file=[key=]source` accept only one result
// and extend it with the `key=` prefix
if key != "" {
if len(result) != 1 {
return fmt.Errorf(
"'pattern '%s' catches files %v, should catch only one.", pattern, result)
}
fileSource := fmt.Sprintf("%s=%s", key, result[0])
results = append(results, fileSource)
} else {
results = append(results, result...)
}
}
a.FileSources = result
a.FileSources = results
return nil
}

View File

@@ -89,7 +89,7 @@ func TestExpandFileSource(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/fa1")
fakeFS.Create("dir/fa2")
fakeFS.Create("dir/reademe")
fakeFS.Create("dir/readme")
fa := flagsAndArgs{
FileSources: []string{"dir/fa*"},
}
@@ -102,3 +102,37 @@ func TestExpandFileSource(t *testing.T) {
t.Fatalf("FileSources is not correctly expanded: %v", fa.FileSources)
}
}
func TestExpandFileSourceWithKey(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc")
fakeFS.Create("dir/foobar")
fakeFS.Create("dir/simplebar")
fakeFS.Create("dir/readme")
fa := flagsAndArgs{
FileSources: []string{"foo-key=dir/fa*", "bar-key=dir/foobar", "dir/simplebar"},
}
fa.ExpandFileSource(fakeFS)
expected := []string{
"foo-key=dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc",
"bar-key=dir/foobar",
"dir/simplebar",
}
if !reflect.DeepEqual(fa.FileSources, expected) {
t.Fatalf("FileSources is not correctly expanded: %v", fa.FileSources)
}
}
func TestExpandFileSourceWithKeyAndError(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.Create("dir/fa1")
fakeFS.Create("dir/fa2")
fakeFS.Create("dir/readme")
fa := flagsAndArgs{
FileSources: []string{"foo-key=dir/fa*"},
}
err := fa.ExpandFileSource(fakeFS)
if err == nil {
t.Fatalf("FileSources should not be correctly expanded: %v", fa.FileSources)
}
}

View File

@@ -19,14 +19,15 @@ package misc
import (
"fmt"
"io"
"runtime"
"github.com/spf13/cobra"
)
var (
kustomizeVersion = "unknown"
goos = "unknown"
goarch = "unknown"
goos = runtime.GOOS
goarch = runtime.GOARCH
gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD)
buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')

View File

@@ -26,7 +26,7 @@ import (
// that was confirmed to point to an existing directory.
type ConfirmedDir string
// Return a temporary dir, else error.
// NewTmpConfirmedDir returns a temporary dir, else error.
// The directory is cleaned, no symlinks, etc. so its
// returned as a ConfirmedDir.
func NewTmpConfirmedDir() (ConfirmedDir, error) {
@@ -34,7 +34,15 @@ func NewTmpConfirmedDir() (ConfirmedDir, error) {
if err != nil {
return "", err
}
return ConfirmedDir(n), nil
// In MacOs `ioutil.TempDir` creates a directory
// with root in the `/var` folder, which is in turn a symlinked path
// to `/private/var`.
// Function `filepath.EvalSymlinks`is used to
// resolve the real absolute path.
deLinked, err := filepath.EvalSymlinks(n)
return ConfirmedDir(deLinked), err
}
// HasPrefix returns true if the directory argument

View File

@@ -17,6 +17,7 @@ limitations under the License.
package fs
import (
"path/filepath"
"testing"
)
@@ -101,3 +102,18 @@ func TestHasPrefix_SlashFooBar(t *testing.T) {
t.Fatalf("/foo/bar should have prefix /")
}
}
func TestNewTempConfirmDir(t *testing.T) {
tmp, err := NewTmpConfirmedDir()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
delinked, err := filepath.EvalSymlinks(string(tmp))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(tmp) != delinked {
t.Fatalf("unexpected path containing symlinks")
}
}

View File

@@ -27,7 +27,11 @@ import (
func makeTestDir(t *testing.T) (FileSystem, string) {
x := MakeRealFS()
testDir, err := ioutil.TempDir("", "kustomize_testing_dir")
td, err := ioutil.TempDir("", "kustomize_testing_dir")
if err != nil {
t.Fatalf("unexpected error %s", err)
}
testDir, err := filepath.EvalSymlinks(td)
if err != nil {
t.Fatalf("unexpected error %s", err)
}

View File

@@ -24,6 +24,13 @@ import (
"sigs.k8s.io/kustomize/pkg/fs"
)
// Used as a temporary non-empty occupant of the cloneDir
// field, as something distinguishable from the empty string
// in various outputs (especially tests). Not using an
// actual directory name here, as that's a temporary directory
// with a unique name that isn't created until clone time.
const notCloned = fs.ConfirmedDir("/notCloned")
// RepoSpec specifies a git repository and a branch and path therein.
type RepoSpec struct {
// Raw, original spec, used to look for cycles.
@@ -88,7 +95,7 @@ func NewRepoSpecFromUrl(n string) (*RepoSpec, error) {
}
return &RepoSpec{
raw: n, host: host, orgRepo: orgRepo,
path: path, ref: gitRef}, nil
cloneDir: notCloned, path: path, ref: gitRef}, nil
}
const (

View File

@@ -122,46 +122,52 @@ func TestNewRepoSpecFromUrlErrors(t *testing.T) {
func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) {
testcases := []struct {
input string
repo string
path string
ref string
input string
cloneSpec string
absPath string
ref string
}{
{
input: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somedir",
repo: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
path: "somedir",
ref: "",
input: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somedir",
cloneSpec: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
absPath: notCloned.Join("somedir"),
ref: "",
},
{
input: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somedir?ref=testbranch",
repo: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
path: "somedir",
ref: "testbranch",
input: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somedir?ref=testbranch",
cloneSpec: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
absPath: notCloned.Join("somedir"),
ref: "testbranch",
},
{
input: "https://fabrikops2.visualstudio.com/someorg/somerepo?ref=master",
repo: "https://fabrikops2.visualstudio.com/someorg/somerepo",
path: "",
ref: "master",
input: "https://fabrikops2.visualstudio.com/someorg/somerepo?ref=master",
cloneSpec: "https://fabrikops2.visualstudio.com/someorg/somerepo",
absPath: notCloned.String(),
ref: "master",
},
{
input: "http://github.com/someorg/somerepo/somedir",
repo: "https://github.com/someorg/somerepo.git",
path: "somedir",
ref: "",
input: "http://github.com/someorg/somerepo/somedir",
cloneSpec: "https://github.com/someorg/somerepo.git",
absPath: notCloned.Join("somedir"),
ref: "",
},
{
input: "git@github.com:someorg/somerepo/somedir",
repo: "git@github.com:someorg/somerepo.git",
path: "somedir",
ref: "",
input: "git@github.com:someorg/somerepo/somedir",
cloneSpec: "git@github.com:someorg/somerepo.git",
absPath: notCloned.Join("somedir"),
ref: "",
},
{
input: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git?ref=v0.1.0",
repo: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git",
path: "",
ref: "v0.1.0",
input: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git?ref=v0.1.0",
cloneSpec: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git",
absPath: notCloned.String(),
ref: "v0.1.0",
},
{
input: "git@bitbucket.org:company/project.git//path?ref=branch",
cloneSpec: "git@bitbucket.org:company/project.git",
absPath: notCloned.Join("path"),
ref: "branch",
},
}
for _, testcase := range testcases {
@@ -169,13 +175,13 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if rs.CloneSpec() != testcase.repo {
if rs.CloneSpec() != testcase.cloneSpec {
t.Errorf("CloneSpec expected to be %v, but got %v on %s",
testcase.repo, rs.CloneSpec(), testcase.input)
testcase.cloneSpec, rs.CloneSpec(), testcase.input)
}
if rs.path != testcase.path {
t.Errorf("path expected to be %v, but got %v on %s",
testcase.path, rs.path, testcase.input)
if rs.AbsPath() != testcase.absPath {
t.Errorf("AbsPath expected to be %v, but got %v on %s",
testcase.absPath, rs.AbsPath(), testcase.input)
}
if rs.ref != testcase.ref {
t.Errorf("ref expected to be %v, but got %v on %s",

View File

@@ -17,9 +17,12 @@ limitations under the License.
package transformer
import (
"fmt"
"github.com/evanphx/json-patch"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
)
@@ -32,17 +35,18 @@ type patchJson6902JSONTransformer struct {
var _ transformers.Transformer = &patchJson6902JSONTransformer{}
// newPatchJson6902JSONTransformer constructs a PatchJson6902 transformer.
func newPatchJson6902JSONTransformer(t resid.ResId, p jsonpatch.Patch) (transformers.Transformer, error) {
func newPatchJson6902JSONTransformer(
id resid.ResId, p jsonpatch.Patch) (transformers.Transformer, error) {
if len(p) == 0 {
return transformers.NewNoOpTransformer(), nil
}
return &patchJson6902JSONTransformer{target: t, patch: p}, nil
return &patchJson6902JSONTransformer{target: id, patch: p}, nil
}
// Transform apply the json patches on top of the base resources.
func (t *patchJson6902JSONTransformer) Transform(baseResourceMap resmap.ResMap) error {
obj, err := findTargetObj(baseResourceMap, t.target)
if obj == nil {
func (t *patchJson6902JSONTransformer) Transform(m resmap.ResMap) error {
obj, err := t.findTargetObj(m)
if err != nil {
return err
}
rawObj, err := obj.MarshalJSON()
@@ -59,3 +63,30 @@ func (t *patchJson6902JSONTransformer) Transform(baseResourceMap resmap.ResMap)
}
return nil
}
func (t *patchJson6902JSONTransformer) findTargetObj(
m resmap.ResMap) (*resource.Resource, error) {
var matched []resid.ResId
// TODO(monopole): namespace bug in json patch?
// Since introduction in PR #300
// (see pkg/patch/transformer/util.go),
// this code has treated an empty namespace like a wildcard
// rather than like an additional restriction to match
// only the empty namespace. No test coverage to confirm.
// Not sure if desired, keeping it for now.
if t.target.Namespace() != "" {
matched = m.GetMatchingIds(t.target.NsGvknEquals)
} else {
matched = m.GetMatchingIds(t.target.GvknEquals)
}
if len(matched) == 0 {
return nil, fmt.Errorf(
"couldn't find target %v for json patch", t.target)
}
if len(matched) > 1 {
return nil, fmt.Errorf(
"found multiple targets %v matching %v for json patch",
matched, t.target)
}
return m[matched[0]], nil
}

View File

@@ -1,46 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package transformer
import (
"fmt"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
)
func findTargetObj(m resmap.ResMap, targetId resid.ResId) (*resource.Resource, error) {
matchedIds := m.FindByGVKN(targetId)
if targetId.Namespace() != "" {
var ids []resid.ResId
for _, id := range matchedIds {
if id.Namespace() == targetId.Namespace() {
ids = append(ids, id)
}
}
matchedIds = ids
}
if len(matchedIds) == 0 {
return nil, fmt.Errorf("couldn't find any object to apply the json patch %v", targetId)
}
if len(matchedIds) > 1 {
return nil, fmt.Errorf("found multiple objects that the patch can apply %v", matchedIds)
}
return m[matchedIds[0]], nil
}

View File

@@ -22,23 +22,30 @@ import (
"sigs.k8s.io/kustomize/pkg/gvk"
)
// ResId conflates GroupVersionKind with a textual name to uniquely identify a kubernetes resource (object).
// ResId is an immutable identifier of a k8s resource object.
type ResId struct {
// Gvk of the resource.
gvKind gvk.Gvk
// original name of the resource before transformation.
// name of the resource before transformation.
name string
// namePrefix of the resource
// an untransformed resource has no prefix, fully transformed resource has an arbitrary number of prefixes
// concatenated together.
// namePrefix of the resource.
// An untransformed resource has no prefix.
// A fully transformed resource has an arbitrary
// number of prefixes concatenated together.
prefix string
// nameSuffix of the resource
// an untransformed resource has no suffix, fully transformed resource has an arbitrary number of suffixes
// concatenated together.
// nameSuffix of the resource.
// An untransformed resource has no suffix.
// A fully transformed resource has an arbitrary
// number of suffixes concatenated together.
suffix string
// namespace the resource belongs to
// an untransformed resource has no namespace, fully transformed resource has the namespace from
// the top most overlay
// Namespace the resource belongs to.
// An untransformed resource has no namespace.
// A fully transformed resource has the namespace
// from the top most overlay.
namespace string
}
@@ -108,10 +115,16 @@ func (n ResId) GvknString() string {
return n.gvKind.String() + separator + n.name
}
// GvknEquals return if two ResId have the same Group/Version/Kind and name
// The comparison excludes prefix and suffix
// GvknEquals returns true if the other id matches
// Group/Version/Kind/name.
func (n ResId) GvknEquals(id ResId) bool {
return n.gvKind.Equals(id.gvKind) && n.name == id.name
return n.name == id.name && n.gvKind.Equals(id.gvKind)
}
// NsGvknEquals returns true if the other id matches
// namespace/Group/Version/Kind/name.
func (n ResId) NsGvknEquals(id ResId) bool {
return n.namespace == id.namespace && n.GvknEquals(id)
}
// Gvk returns Group/Version/Kind of the resource.

View File

@@ -10,31 +10,80 @@ var stringTests = []struct {
x ResId
s string
}{
{ResId{gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"g_v_k|ns|p|nm|s"},
{ResId{gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"~G_v_k|ns|p|nm|s"},
{ResId{gvKind: gvk.Gvk{Kind: "k"},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"~G_~V_k|ns|p|nm|s"},
{ResId{gvKind: gvk.Gvk{},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"~G_~V_~K|ns|p|nm|s"},
{ResId{gvKind: gvk.Gvk{},
name: "nm", prefix: "p", suffix: "s"},
"~G_~V_~K|~X|p|nm|s"},
{ResId{gvKind: gvk.Gvk{},
name: "nm", suffix: "s"},
"~G_~V_~K|~X|~P|nm|s"},
{ResId{gvKind: gvk.Gvk{},
suffix: "s"},
"~G_~V_~K|~X|~P|~N|s"},
{ResId{gvKind: gvk.Gvk{}},
"~G_~V_~K|~X|~P|~N|~S"},
{ResId{},
"~G_~V_~K|~X|~P|~N|~S"},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "p",
suffix: "s",
},
"g_v_k|ns|p|nm|s",
},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_v_k|ns|p|nm|s",
},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{Kind: "k"},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_~V_k|ns|p|nm|s",
},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_~V_~K|ns|p|nm|s",
},
{
ResId{
gvKind: gvk.Gvk{},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_~V_~K|~X|p|nm|s",
},
{
ResId{
gvKind: gvk.Gvk{},
name: "nm",
suffix: "s",
},
"~G_~V_~K|~X|~P|nm|s",
},
{
ResId{
gvKind: gvk.Gvk{},
suffix: "s",
},
"~G_~V_~K|~X|~P|~N|s",
},
{
ResId{
gvKind: gvk.Gvk{},
},
"~G_~V_~K|~X|~P|~N|~S",
},
{
ResId{},
"~G_~V_~K|~X|~P|~N|~S",
},
}
func TestString(t *testing.T) {
@@ -49,31 +98,80 @@ var gvknStringTests = []struct {
x ResId
s string
}{
{ResId{gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"g_v_k|nm"},
{ResId{gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"~G_v_k|nm"},
{ResId{gvKind: gvk.Gvk{Kind: "k"},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"~G_~V_k|nm"},
{ResId{gvKind: gvk.Gvk{},
name: "nm", prefix: "p", suffix: "s", namespace: "ns"},
"~G_~V_~K|nm"},
{ResId{gvKind: gvk.Gvk{},
name: "nm", prefix: "p", suffix: "s"},
"~G_~V_~K|nm"},
{ResId{gvKind: gvk.Gvk{},
name: "nm", suffix: "s"},
"~G_~V_~K|nm"},
{ResId{gvKind: gvk.Gvk{},
suffix: "s"},
"~G_~V_~K|"},
{ResId{gvKind: gvk.Gvk{}},
"~G_~V_~K|"},
{ResId{},
"~G_~V_~K|"},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "p",
suffix: "s",
},
"g_v_k|nm",
},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_v_k|nm",
},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{Kind: "k"},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_~V_k|nm",
},
{
ResId{
namespace: "ns",
gvKind: gvk.Gvk{},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_~V_~K|nm",
},
{
ResId{
gvKind: gvk.Gvk{},
name: "nm",
prefix: "p",
suffix: "s",
},
"~G_~V_~K|nm",
},
{
ResId{
gvKind: gvk.Gvk{},
name: "nm",
suffix: "s",
},
"~G_~V_~K|nm",
},
{
ResId{
gvKind: gvk.Gvk{},
suffix: "s",
},
"~G_~V_~K|",
},
{
ResId{
gvKind: gvk.Gvk{},
},
"~G_~V_~K|",
},
{
ResId{},
"~G_~V_~K|",
},
}
func TestGvknString(t *testing.T) {
@@ -85,47 +183,147 @@ func TestGvknString(t *testing.T) {
}
var GvknEqualsTest = []struct {
x1 ResId
x2 ResId
id1 ResId
id2 ResId
gVknResult bool
nSgVknResult bool
}{
{ResId{gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm", prefix: "AA", suffix: "aa", namespace: "X"},
ResId{gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm", prefix: "BB", suffix: "bb", namespace: "Z"}},
{ResId{gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm", prefix: "AA", suffix: "aa", namespace: "X"},
ResId{gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm", prefix: "BB", suffix: "bb", namespace: "Z"}},
{ResId{gvKind: gvk.Gvk{Kind: "k"},
name: "nm", prefix: "AA", suffix: "aa", namespace: "X"},
ResId{gvKind: gvk.Gvk{Kind: "k"},
name: "nm", prefix: "BB", suffix: "bb", namespace: "Z"}},
{ResId{name: "nm", prefix: "AA", suffix: "aa", namespace: "X"},
ResId{name: "nm", prefix: "BB", suffix: "bb", namespace: "Z"}},
{
id1: ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "AA",
suffix: "aa",
},
id2: ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "BB",
suffix: "bb",
},
gVknResult: true,
nSgVknResult: true,
},
{
id1: ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "AA",
suffix: "aa",
},
id2: ResId{
namespace: "Z",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "BB",
suffix: "bb",
},
gVknResult: true,
nSgVknResult: false,
},
{
id1: ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "AA",
suffix: "aa",
},
id2: ResId{
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "BB",
suffix: "bb",
},
gVknResult: true,
nSgVknResult: false,
},
{
id1: ResId{
namespace: "X",
gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm",
prefix: "AA",
suffix: "aa",
},
id2: ResId{
namespace: "Z",
gvKind: gvk.Gvk{Version: "v", Kind: "k"},
name: "nm",
prefix: "BB",
suffix: "bb",
},
gVknResult: true,
nSgVknResult: false,
},
{
id1: ResId{
namespace: "X",
gvKind: gvk.Gvk{Kind: "k"},
name: "nm",
prefix: "AA",
suffix: "aa",
},
id2: ResId{
namespace: "Z",
gvKind: gvk.Gvk{Kind: "k"},
name: "nm",
prefix: "BB",
suffix: "bb",
},
gVknResult: true,
nSgVknResult: false,
},
{
id1: ResId{
namespace: "X",
name: "nm",
prefix: "AA",
suffix: "aa",
},
id2: ResId{
namespace: "Z",
name: "nm",
prefix: "BB",
suffix: "bb",
},
gVknResult: true,
nSgVknResult: false,
},
}
func TestEquals(t *testing.T) {
for _, hey := range GvknEqualsTest {
if !hey.x1.GvknEquals(hey.x2) {
t.Fatalf("%v should equal %v", hey.x1, hey.x2)
for _, tst := range GvknEqualsTest {
if tst.id1.GvknEquals(tst.id2) != tst.gVknResult {
t.Fatalf("GvknEquals(\n%v,\n%v\n) should be %v",
tst.id1, tst.id2, tst.gVknResult)
}
if tst.id1.NsGvknEquals(tst.id2) != tst.nSgVknResult {
t.Fatalf("NsGvknEquals(\n%v,\n%v\n) should be %v",
tst.id1, tst.id2, tst.nSgVknResult)
}
}
}
func TestCopyWithNewPrefixSuffix(t *testing.T) {
r1 := ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "a",
suffix: "b",
namespace: "X"}
}
r2 := r1.CopyWithNewPrefixSuffix("p-", "-s")
expected := ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "p-a",
suffix: "b-s",
namespace: "X"}
}
if !r2.GvknEquals(expected) {
t.Fatalf("%v should equal %v", r2, expected)
}
@@ -133,18 +331,20 @@ func TestCopyWithNewPrefixSuffix(t *testing.T) {
func TestCopyWithNewNamespace(t *testing.T) {
r1 := ResId{
namespace: "X",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "a",
suffix: "b",
namespace: "X"}
}
r2 := r1.CopyWithNewNamespace("zzz")
expected := ResId{
namespace: "zzz",
gvKind: gvk.Gvk{Group: "g", Version: "v", Kind: "k"},
name: "nm",
prefix: "a",
suffix: "b",
namespace: "zzz"}
}
if !r2.GvknEquals(expected) {
t.Fatalf("%v should equal %v", r2, expected)
}

View File

@@ -50,7 +50,7 @@ func (rmF *Factory) FromFiles(
if err != nil {
return nil, errors.Wrap(err, "Load from path "+path+" failed")
}
res, err := rmF.newResMapFromBytes(content)
res, err := rmF.NewResMapFromBytes(content)
if err != nil {
return nil, internal.Handler(err, path)
}
@@ -60,7 +60,7 @@ func (rmF *Factory) FromFiles(
}
// newResMapFromBytes decodes a list of objects in byte array format.
func (rmF *Factory) newResMapFromBytes(b []byte) (ResMap, error) {
func (rmF *Factory) NewResMapFromBytes(b []byte) (ResMap, error) {
resources, err := rmF.resF.SliceFromBytes(b)
if err != nil {
return nil, err

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
package resmap_test
import (
"encoding/base64"
@@ -28,6 +28,7 @@ import (
"sigs.k8s.io/kustomize/pkg/internal/loadertest"
"sigs.k8s.io/kustomize/pkg/loader"
"sigs.k8s.io/kustomize/pkg/resid"
. "sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/types"
)
@@ -124,7 +125,7 @@ metadata:
},
}),
}
m, err := rmF.newResMapFromBytes(encoded)
m, err := rmF.NewResMapFromBytes(encoded)
fmt.Printf("%v\n", m)
if err != nil {
t.Fatalf("unexpected error: %v", err)

View File

@@ -32,20 +32,23 @@ import (
// ResMap is a map from ResId to Resource.
type ResMap map[resid.ResId]*resource.Resource
// FindByGVKN find the matched ResIds by Group/Version/Kind and Name
func (m ResMap) FindByGVKN(inputId resid.ResId) []resid.ResId {
type IdMatcher func(resid.ResId) bool
// GetMatchingIds returns a slice of ResId keys from the map
// that all satisfy the given matcher function.
func (m ResMap) GetMatchingIds(matches IdMatcher) []resid.ResId {
var result []resid.ResId
for id := range m {
if id.GvknEquals(inputId) {
if matches(id) {
result = append(result, id)
}
}
return result
}
// DemandOneMatchForId find the matched resource by Group/Version/Kind and Name
func (m ResMap) DemandOneMatchForId(inputId resid.ResId) (*resource.Resource, bool) {
result := m.FindByGVKN(inputId)
// DemandOneGvknMatchForId find the matched resource by Group/Version/Kind and Name
func (m ResMap) DemandOneGvknMatchForId(inputId resid.ResId) (*resource.Resource, bool) {
result := m.GetMatchingIds(inputId.GvknEquals)
if len(result) == 1 {
return m[result[0]], true
}
@@ -177,7 +180,7 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) {
continue
}
for id, r := range m {
matchedId := result.FindByGVKN(id)
matchedId := result.GetMatchingIds(id.GvknEquals)
if len(matchedId) == 1 {
id = matchedId[0]
switch r.Behavior() {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
package resmap_test
import (
"reflect"
@@ -23,6 +23,7 @@ import (
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/resid"
. "sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/types"
)
@@ -71,7 +72,7 @@ metadata:
}
}
func TestDemandOneMatchForId(t *testing.T) {
func TestDemandOneGvknMatchForId(t *testing.T) {
rm1 := ResMap{
resid.NewResIdWithPrefixNamespace(cmap, "cm1", "prefix1", "ns1"): rf.FromMap(
map[string]interface{}{
@@ -91,19 +92,22 @@ func TestDemandOneMatchForId(t *testing.T) {
}),
}
_, ok := rm1.DemandOneMatchForId(resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix1", "ns1"))
_, ok := rm1.DemandOneGvknMatchForId(
resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix1", "ns1"))
if !ok {
t.Fatal("Expected single map entry but got none")
}
// confirm that ns and prefix are not included in match
_, ok = rm1.DemandOneMatchForId(resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix", "ns"))
_, ok = rm1.DemandOneGvknMatchForId(
resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix", "ns"))
if !ok {
t.Fatal("Expected single map entry but got none")
}
// confirm that name is matched correctly
result, ok := rm1.DemandOneMatchForId(resid.NewResIdWithPrefixNamespace(cmap, "cm3", "prefix1", "ns1"))
result, ok := rm1.DemandOneGvknMatchForId(
resid.NewResIdWithPrefixNamespace(cmap, "cm3", "prefix1", "ns1"))
if ok {
t.Fatalf("Expected no map entries but got %v", result)
}
@@ -111,7 +115,8 @@ func TestDemandOneMatchForId(t *testing.T) {
cmap2 := gvk.Gvk{Version: "v2", Kind: "ConfigMap"}
// confirm that gvk is matched correctly
result, ok = rm1.DemandOneMatchForId(resid.NewResIdWithPrefixNamespace(cmap2, "cm2", "prefix1", "ns1"))
result, ok = rm1.DemandOneGvknMatchForId(
resid.NewResIdWithPrefixNamespace(cmap2, "cm2", "prefix1", "ns1"))
if ok {
t.Fatalf("Expected no map entries but got %v", result)
}
@@ -291,8 +296,73 @@ func TestDeepCopy(t *testing.T) {
}
}
func TestErrorIfNotEqual(t *testing.T) {
func TestGetMatchingIds(t *testing.T) {
// These ids used as map keys.
// They must be different to avoid overwriting
// map entries during construction.
ids := []resid.ResId{
resid.NewResId(
gvk.Gvk{Kind: "vegetable"},
"bedlam"),
resid.NewResId(
gvk.Gvk{Group: "g1", Version: "v1", Kind: "vegetable"},
"domino"),
resid.NewResIdWithPrefixNamespace(
gvk.Gvk{Kind: "vegetable"},
"peter", "p", "happy"),
resid.NewResIdWithPrefixNamespace(
gvk.Gvk{Version: "v1", Kind: "fruit"},
"shatterstar", "p", "happy"),
}
m := ResMap{}
for _, id := range ids {
// Resources values don't matter in this test.
m[id] = nil
}
if len(m) != len(ids) {
t.Fatalf("unexpected map len %d presumably due to duplicate keys", len(m))
}
tests := []struct {
name string
matcher IdMatcher
count int
}{
{
"match everything",
func(resid.ResId) bool { return true },
4,
},
{
"match nothing",
func(resid.ResId) bool { return false },
0,
},
{
"name is peter",
func(x resid.ResId) bool { return x.Name() == "peter" },
1,
},
{
"happy vegetable",
func(x resid.ResId) bool {
return x.Namespace() == "happy" &&
x.Gvk().Kind == "vegetable"
},
1,
},
}
for _, tst := range tests {
result := m.GetMatchingIds(tst.matcher)
if len(result) != tst.count {
t.Fatalf("test '%s'; actual: %d, expected: %d",
tst.name, len(result), tst.count)
}
}
}
func TestErrorIfNotEqual(t *testing.T) {
rm1 := ResMap{
resid.NewResId(cmap, "cm1"): rf.FromMap(
map[string]interface{}{
@@ -436,7 +506,6 @@ func TestMergeWithoutOverride(t *testing.T) {
}
func generateMergeFixtures(b types.GenerationBehavior) []ResMap {
input1 := ResMap{
resid.NewResId(cmap, "cmap"): rf.FromMapAndOption(
map[string]interface{}{

View File

@@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"log"
"strings"
"sigs.k8s.io/kustomize/pkg/ifc"
internal "sigs.k8s.io/kustomize/pkg/internal/error"
@@ -94,10 +95,14 @@ func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
for len(kunStructs) > 0 {
u := kunStructs[0]
kunStructs = kunStructs[1:]
if u.GetKind() == "List" {
if strings.HasSuffix(u.GetKind(), "List") {
items := u.Map()["items"]
itemsSlice, ok := items.([]interface{})
if !ok {
if items == nil {
// an empty list
continue
}
return nil, fmt.Errorf("items in List is type %T, expected array", items)
}
for _, item := range itemsSlice {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resource
package resource_test
import (
"reflect"
@@ -22,6 +22,7 @@ import (
"sigs.k8s.io/kustomize/pkg/internal/loadertest"
"sigs.k8s.io/kustomize/pkg/patch"
. "sigs.k8s.io/kustomize/pkg/resource"
)
func TestSliceFromPatches(t *testing.T) {
@@ -67,7 +68,7 @@ items:
patchList2 := patch.StrategicMerge("patch5.yaml")
patch5 := `
apiVersion: v1
kind: List
kind: DeploymentList
items:
- apiVersion: apps/v1
kind: Deployment
@@ -86,6 +87,17 @@ items:
name: deployment-b
spec:
<<: *hostAliases
`
patchList3 := patch.StrategicMerge("patch6.yaml")
patch6 := `
apiVersion: v1
kind: List
items:
`
patchList4 := patch.StrategicMerge("patch7.yaml")
patch7 := `
apiVersion: v1
kind: List
`
testDeploymentSpec := map[string]interface{}{
"template": map[string]interface{}{
@@ -125,6 +137,8 @@ items:
l.AddFile("/"+string(patchBad), []byte(patch3))
l.AddFile("/"+string(patchList), []byte(patch4))
l.AddFile("/"+string(patchList2), []byte(patch5))
l.AddFile("/"+string(patchList3), []byte(patch6))
l.AddFile("/"+string(patchList4), []byte(patch7))
tests := []struct {
name string
@@ -162,6 +176,18 @@ items:
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
expectedErr: false,
},
{
name: "listWithNoEntries",
input: []patch.StrategicMerge{patchList3},
expectedOut: []*Resource{},
expectedErr: false,
},
{
name: "listWithNo'items:'",
input: []patch.StrategicMerge{patchList4},
expectedOut: []*Resource{},
expectedErr: false,
},
}
for _, test := range tests {
rs, err := factory.SliceFromPatches(l, test.input)

View File

@@ -38,7 +38,7 @@ func (r *Resource) String() string {
if err != nil {
return "<" + err.Error() + ">"
}
return strings.TrimSpace(string(bs))
return strings.TrimSpace(string(bs)) + r.options.String()
}
// DeepCopy returns a new copy of resource

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resource
package resource_test
import (
"testing"
@@ -22,6 +22,7 @@ import (
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/resid"
. "sigs.k8s.io/kustomize/pkg/resource"
)
var factory = NewFactory(
@@ -37,7 +38,9 @@ var testConfigMap = factory.FromMap(
},
})
const testConfigMapString = `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie","namespace":"hundred-acre-wood"}}`
const genArgOptions = "{nsfx:false,beh:unspecified}"
const configMapAsString = `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie","namespace":"hundred-acre-wood"}}`
var testDeployment = factory.FromMap(
map[string]interface{}{
@@ -48,7 +51,7 @@ var testDeployment = factory.FromMap(
},
})
const testDeploymentString = `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"pooh"}}`
const deploymentAsString = `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"pooh"}}`
func TestResourceString(t *testing.T) {
tests := []struct {
@@ -57,11 +60,11 @@ func TestResourceString(t *testing.T) {
}{
{
in: testConfigMap,
s: testConfigMapString,
s: configMapAsString + genArgOptions,
},
{
in: testDeployment,
s: testDeploymentString,
s: deploymentAsString + genArgOptions,
},
}
for _, test := range tests {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -14,17 +14,152 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"
)
func TestGenerator1(t *testing.T) {
// Generate a Secret and a ConfigMap from the same data
// to compare the result.
func TestGeneratorBasics(t *testing.T) {
th := NewKustTestHarness(t, "/app")
th.writeK("/app", `
namePrefix: blah-
configMapGenerator:
- name: bob
literals:
- fruit=apple
- vegetable=broccoli
env: foo.env
files:
- passphrase=phrase.dat
- forces.txt
secretGenerator:
- name: bob
literals:
- fruit=apple
- vegetable=broccoli
env: foo.env
files:
- passphrase=phrase.dat
- forces.txt
`)
th.writeF("/app/foo.env", `
MOUNTAIN=everest
OCEAN=pacific
`)
th.writeF("/app/phrase.dat", `
Life is short.
But the years are long.
Not while the evil days come not.
`)
th.writeF("/app/forces.txt", `
gravitational
electromagnetic
strong nuclear
weak nuclear
`)
m, err := th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.assertActualEqualsExpected(m, `
apiVersion: v1
data:
MOUNTAIN: everest
OCEAN: pacific
forces.txt: |2
gravitational
electromagnetic
strong nuclear
weak nuclear
fruit: apple
passphrase: |2
Life is short.
But the years are long.
Not while the evil days come not.
vegetable: broccoli
kind: ConfigMap
metadata:
name: blah-bob-k772g5db55
---
apiVersion: v1
data:
MOUNTAIN: ZXZlcmVzdA==
OCEAN: cGFjaWZpYw==
forces.txt: CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbnVjbGVhcgo=
fruit: YXBwbGU=
passphrase: CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aGUgZXZpbCBkYXlzIGNvbWUgbm90Lgo=
vegetable: YnJvY2NvbGk=
kind: Secret
metadata:
name: blah-bob-gmc2824f4b
type: Opaque
`)
}
// TODO: These should be errors instead.
func TestGeneratorRepeatsInKustomization(t *testing.T) {
th := NewKustTestHarness(t, "/app")
th.writeK("/app", `
namePrefix: blah-
configMapGenerator:
- name: bob
behavior: create
literals:
- bean=pinto
- star=wolf-rayet
literals:
- fruit=apple
- vegetable=broccoli
files:
- forces.txt
files:
- nobles=nobility.txt
`)
th.writeF("/app/forces.txt", `
gravitational
electromagnetic
strong nuclear
weak nuclear
`)
th.writeF("/app/nobility.txt", `
helium
neon
argon
krypton
xenon
radon
`)
m, err := th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.assertActualEqualsExpected(m, `
apiVersion: v1
data:
fruit: apple
nobles: |2
helium
neon
argon
krypton
xenon
radon
vegetable: broccoli
kind: ConfigMap
metadata:
name: blah-bob-gfkcbk5ckf
`)
}
func TestGeneratorOverlays(t *testing.T) {
th := NewKustTestHarness(t, "/app/overlay")
th.writeK("/app/base1", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: p1-
configMapGenerator:
- name: com1
@@ -33,8 +168,6 @@ configMapGenerator:
- from=base
`)
th.writeK("/app/base2", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: p2-
configMapGenerator:
- name: com2
@@ -43,8 +176,6 @@ configMapGenerator:
- from=base
`)
th.writeK("/app/overlay/o1", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base1
configMapGenerator:
@@ -54,8 +185,6 @@ configMapGenerator:
- from=overlay
`)
th.writeK("/app/overlay/o2", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base2
configMapGenerator:
@@ -65,8 +194,6 @@ configMapGenerator:
- from=overlay
`)
th.writeK("/app/overlay", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- o1
- o2

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -26,7 +26,6 @@ import (
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/ifc/transformer"
interror "sigs.k8s.io/kustomize/pkg/internal/error"
@@ -42,7 +41,6 @@ import (
type KustTarget struct {
kustomization *types.Kustomization
ldr ifc.Loader
fSys fs.FileSystem
rFactory *resmap.Factory
tFactory transformer.Factory
}
@@ -50,7 +48,6 @@ type KustTarget struct {
// NewKustTarget returns a new instance of KustTarget primed with a Loader.
func NewKustTarget(
ldr ifc.Loader,
fSys fs.FileSystem,
rFactory *resmap.Factory,
tFactory transformer.Factory) (*KustTarget, error) {
content, err := loadKustFile(ldr)
@@ -70,12 +67,23 @@ func NewKustTarget(
return &KustTarget{
kustomization: &k,
ldr: ldr,
fSys: fSys,
rFactory: rFactory,
tFactory: tFactory,
}, nil
}
func quoted(l []string) []string {
r := make([]string, len(l))
for i, v := range l {
r[i] = "'" + v + "'"
}
return r
}
func commaOr(q []string) string {
return strings.Join(q[:len(q)-1], ", ") + " or " + q[len(q)-1]
}
func loadKustFile(ldr ifc.Loader) ([]byte, error) {
var content []byte
match := 0
@@ -88,8 +96,9 @@ func loadKustFile(ldr ifc.Loader) ([]byte, error) {
}
switch match {
case 0:
return nil, fmt.Errorf("No kustomization file found in %s. Kustomize supports the following kustomization files: %s",
ldr.Root(), strings.Join(constants.KustomizationFileNames, ", "))
return nil, fmt.Errorf(
"unable to find one of %v in directory '%s'",
commaOr(quoted(constants.KustomizationFileNames)), ldr.Root())
case 1:
return content, nil
default:
@@ -110,7 +119,7 @@ func unmarshal(y []byte, o interface{}) error {
// MakeCustomizedResMap creates a ResMap per kustomization instructions.
// The Resources in the returned ResMap are fully customized.
func (kt *KustTarget) MakeCustomizedResMap() (resmap.ResMap, error) {
ra, err := kt.accumulateTarget()
ra, err := kt.AccumulateTarget()
if err != nil {
return nil, err
}
@@ -134,11 +143,11 @@ func (kt *KustTarget) shouldAddHashSuffixesToGeneratedResources() bool {
!kt.kustomization.GeneratorOptions.DisableNameSuffixHash
}
// accumulateTarget returns a new ResAccumulator,
// AccumulateTarget returns a new ResAccumulator,
// holding customized resources and the data/rules used
// to do so. The name back references and vars are
// not yet fixed.
func (kt *KustTarget) accumulateTarget() (
func (kt *KustTarget) AccumulateTarget() (
ra *ResAccumulator, err error) {
// TODO(monopole): Get rid of the KustomizationErrors accumulator.
// It's not consistently used, and complicates tests.
@@ -236,15 +245,15 @@ func (kt *KustTarget) accumulateBases() (
continue
}
subKt, err := NewKustTarget(
ldr, kt.fSys, kt.rFactory, kt.tFactory)
ldr, kt.rFactory, kt.tFactory)
if err != nil {
errs.Append(errors.Wrap(err, "couldn't make target for "+path))
ldr.Cleanup()
continue
}
subRa, err := subKt.accumulateTarget()
subRa, err := subKt.AccumulateTarget()
if err != nil {
errs.Append(errors.Wrap(err, "accumulateTarget"))
errs.Append(errors.Wrap(err, "AccumulateTarget"))
ldr.Cleanup()
continue
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"encoding/base64"
@@ -25,14 +25,16 @@ import (
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/internal/loadertest"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
. "sigs.k8s.io/kustomize/pkg/target"
"sigs.k8s.io/kustomize/pkg/types"
)
const (
kustomizationContent1 = `
kustomizationContent = `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: foo-
@@ -45,6 +47,8 @@ commonAnnotations:
resources:
- deployment.yaml
- namespace.yaml
generatorOptions:
disableNameSuffixHash: false
configMapGenerator:
- name: literalConfigMap
literals:
@@ -81,9 +85,9 @@ metadata:
]`
)
func TestResources1(t *testing.T) {
func TestResources(t *testing.T) {
th := NewKustTestHarness(t, "/whatever")
th.writeK("/whatever/", kustomizationContent1)
th.writeK("/whatever/", kustomizationContent)
th.writeF("/whatever/deployment.yaml", deploymentContent)
th.writeF("/whatever/namespace.yaml", namespaceContent)
th.writeF("/whatever/jsonpatch.json", jsonpatchContent)
@@ -144,7 +148,9 @@ func TestResources1(t *testing.T) {
"DB_USERNAME": "admin",
"DB_PASSWORD": "somepw",
},
}, &types.GeneratorArgs{}, nil),
},
&types.GeneratorArgs{},
&types.GeneratorOptions{}),
resid.NewResIdWithPrefixSuffixNamespace(
gvk.Gvk{Version: "v1", Kind: "Secret"},
"secret", "foo-", "-bar", "ns1"): th.fromMapAndOption(
@@ -166,7 +172,9 @@ func TestResources1(t *testing.T) {
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
}, &types.GeneratorArgs{}, nil),
},
&types.GeneratorArgs{},
&types.GeneratorOptions{}),
resid.NewResIdWithPrefixSuffixNamespace(
gvk.Gvk{Version: "v1", Kind: "Namespace"},
"ns1", "foo-", "-bar", ""): th.fromMap(
@@ -195,9 +203,20 @@ func TestResources1(t *testing.T) {
}
}
func TestKustomizationNotFound(t *testing.T) {
_, err := NewKustTarget(loadertest.NewFakeLoader("/foo"), nil, nil)
if err == nil {
t.Fatalf("expected an error")
}
if err.Error() !=
`unable to find one of 'kustomization.yaml', 'kustomization.yml' or 'Kustomization' in directory '/foo'` {
t.Fatalf("unexpected error: %q", err)
}
}
func TestResourceNotFound(t *testing.T) {
th := NewKustTestHarness(t, "/whatever")
th.writeK("/whatever", kustomizationContent1)
th.writeK("/whatever", kustomizationContent)
_, err := th.makeKustTarget().MakeCustomizedResMap()
if err == nil {
t.Fatalf("Didn't get the expected error for an unknown resource")
@@ -218,13 +237,12 @@ func findSecret(m resmap.ResMap) *resource.Resource {
func TestDisableNameSuffixHash(t *testing.T) {
th := NewKustTestHarness(t, "/whatever")
th.writeK("/whatever/", kustomizationContent1)
th.writeK("/whatever/", kustomizationContent)
th.writeF("/whatever/deployment.yaml", deploymentContent)
th.writeF("/whatever/namespace.yaml", namespaceContent)
th.writeF("/whatever/jsonpatch.json", jsonpatchContent)
kt := th.makeKustTarget()
m, err := kt.MakeCustomizedResMap()
m, err := th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("unexpected Resources error %v", err)
}
@@ -236,9 +254,11 @@ func TestDisableNameSuffixHash(t *testing.T) {
t.Errorf("unexpected secret resource name: %s", secret.GetName())
}
kt.kustomization.GeneratorOptions = &types.GeneratorOptions{
DisableNameSuffixHash: true}
m, err = kt.MakeCustomizedResMap()
th.writeK("/whatever/",
strings.Replace(kustomizationContent,
"disableNameSuffixHash: false",
"disableNameSuffixHash: true", -1))
m, err = th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("unexpected Resources error %v", err)
}
@@ -324,11 +344,11 @@ vars:
name: heron
apiVersion: v300
`)
ra, err := th.makeKustTarget().accumulateTarget()
ra, err := th.makeKustTarget().AccumulateTarget()
if err != nil {
t.Fatalf("Err: %v", err)
}
vars := ra.varSet.Set()
vars := ra.Vars()
if len(vars) != 2 {
t.Fatalf("unexpected size %d", len(vars))
}
@@ -374,11 +394,11 @@ vars:
bases:
- ../o1
`)
ra, err := th.makeKustTarget().accumulateTarget()
ra, err := th.makeKustTarget().AccumulateTarget()
if err != nil {
t.Fatalf("Err: %v", err)
}
vars := ra.varSet.Set()
vars := ra.Vars()
if len(vars) != 4 {
for i, v := range vars {
fmt.Printf("%v: %v\n", i, v)
@@ -427,7 +447,7 @@ vars:
bases:
- ../o1
`)
_, err := th.makeKustTarget().accumulateTarget()
_, err := th.makeKustTarget().AccumulateTarget()
if err == nil {
t.Fatalf("expected var collision")
}

View File

@@ -14,24 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
// A collection of utilities used in target tests.
import (
"fmt"
"path/filepath"
"sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig"
"strings"
"testing"
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/k8sdeps/transformer"
"sigs.k8s.io/kustomize/pkg/constants"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/internal/loadertest"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
. "sigs.k8s.io/kustomize/pkg/target"
"sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig"
"sigs.k8s.io/kustomize/pkg/types"
)
@@ -50,17 +50,8 @@ func NewKustTestHarness(t *testing.T, path string) *KustTestHarness {
}
func (th *KustTestHarness) makeKustTarget() *KustTarget {
// Warning: the following filesystem - a fake - must be rooted at /.
// This fs root is used as the working directory for the shell spawned by
// the secretgenerator, and has nothing to do with the filesystem used
// to load relative paths from the fake filesystem.
// This trick only works for secret generator commands that don't actually
// try to read the file system, because these tests don't write to the
// real "/" directory. See use of exec package in the secretfactory.
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir("/")
kt, err := NewKustTarget(
th.ldr, fakeFs, th.rf, transformer.NewFactoryImpl())
th.ldr, th.rf, transformer.NewFactoryImpl())
if err != nil {
th.t.Fatalf("Unexpected construction error %v", err)
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"strings"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"

View File

@@ -55,6 +55,11 @@ func (ra *ResAccumulator) ResMap() resmap.ResMap {
return result
}
// Vars returns a copy of underlying vars.
func (ra *ResAccumulator) Vars() []types.Var {
return ra.varSet.Set()
}
func (ra *ResAccumulator) MergeResourcesWithErrorOnIdCollision(
resources resmap.ResMap) (err error) {
ra.resMap, err = resmap.MergeWithErrorOnIdCollision(
@@ -98,7 +103,7 @@ func (ra *ResAccumulator) makeVarReplacementMap() (map[string]string, error) {
result := map[string]string{}
for _, v := range ra.varSet.Set() {
id := resid.NewResId(v.ObjRef.GVK(), v.ObjRef.Name)
if r, found := ra.resMap.DemandOneMatchForId(id); found {
if r, found := ra.resMap.DemandOneGvknMatchForId(id); found {
s, err := r.GetFieldValue(v.FieldRef.FieldPath)
if err != nil {
return nil, fmt.Errorf("field path err for var: %+v", v)

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"strings"
@@ -25,6 +25,7 @@ import (
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
. "sigs.k8s.io/kustomize/pkg/target"
"sigs.k8s.io/kustomize/pkg/transformers/config"
"sigs.k8s.io/kustomize/pkg/types"
)

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"strings"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package target
package target_test
import (
"testing"
@@ -23,8 +23,6 @@ import (
func TestVariableRef(t *testing.T) {
th := NewKustTestHarness(t, "/app/overlay/staging")
th.writeK("/app/base", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: base-
resources:
- cockroachdb-statefulset-secure.yaml
@@ -323,8 +321,6 @@ spec:
storage: 1Gi
`)
th.writeK("/app/overlay/staging", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: dev-
bases:
- ../../base
@@ -583,8 +579,6 @@ spec:
func TestVariableRefIngress(t *testing.T) {
th := NewKustTestHarness(t, "/app/overlay")
th.writeK("/app/base", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- ingress.yaml
@@ -657,8 +651,6 @@ spec:
targetPort: http
`)
th.writeK("/app/overlay", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
nameprefix: kustomized-
bases:
- ../base
@@ -723,3 +715,137 @@ spec:
- kustomized-nginx.example.com
`)
}
func TestVariableRefMounthPath(t *testing.T) {
th := NewKustTestHarness(t, "/app/base")
th.writeK("/app/base", `
resources:
- deployment.yaml
- namespace.yaml
vars:
- name: NAMESPACE
objref:
apiVersion: v1
kind: Namespace
name: my-namespace
`)
th.writeF("/app/base/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
template:
spec:
containers:
- name: app
image: busybox
volumeMounts:
- name: my-volume
mountPath: "/$(NAMESPACE)"
env:
- name: NAMESPACE
value: $(NAMESPACE)
volumes:
- name: my-volume
emptyDir: {}
`)
th.writeF("/app/base/namespace.yaml", `
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
`)
m, err := th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.assertActualEqualsExpected(m, `
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
template:
spec:
containers:
- env:
- name: NAMESPACE
value: my-namespace
image: busybox
name: app
volumeMounts:
- mountPath: /my-namespace
name: my-volume
volumes:
- emptyDir: {}
name: my-volume
`)
}
func TestVariableRefMaps(t *testing.T) {
th := NewKustTestHarness(t, "/app/base")
th.writeK("/app/base", `
resources:
- deployment.yaml
- namespace.yaml
vars:
- name: NAMESPACE
objref:
apiVersion: v1
kind: Namespace
name: my-namespace
`)
th.writeF("/app/base/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
my-label: $(NAMESPACE)
spec:
template:
spec:
containers:
- name: app
image: busybox
`)
th.writeF("/app/base/namespace.yaml", `
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
`)
m, err := th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.assertActualEqualsExpected(m, `
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
my-label: my-namespace
name: my-deployment
spec:
template:
spec:
containers:
- image: busybox
name: app
`)
}

View File

@@ -100,10 +100,63 @@ varReference:
- path: spec/containers/env/value
kind: Pod
- path: spec/initContainers/command
kind: Pod
- path: spec/initContainers/args
kind: Pod
- path: spec/initContainers/env/value
kind: Pod
- path: spec/rules/host
kind: Ingress
- path: spec/tls/hosts
kind: Ingress
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: StatefulSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: StatefulSet
- path: spec/containers/volumeMounts/mountPath
kind: Pod
- path: spec/initContainers/volumeMounts/mountPath
kind: Pod
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: ReplicaSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: ReplicaSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: Job
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: Job
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: CronJob
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: CronJob
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: DaemonSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: DaemonSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: Deployment
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: Deployment
- path: metadata/labels
`
)

View File

@@ -17,6 +17,7 @@ limitations under the License.
package transformers
import (
"fmt"
"regexp"
"strings"
@@ -75,7 +76,10 @@ func (pt *imageTransformer) findAndReplaceImage(obj map[string]interface{}) erro
}
func (pt *imageTransformer) updateContainers(obj map[string]interface{}, path string) error {
containers := obj[path].([]interface{})
containers, ok := obj[path].([]interface{})
if !ok {
return fmt.Errorf("containers path is not of type []interface{} but %T", obj[path])
}
for i := range containers {
container := containers[i].(map[string]interface{})
containerImage, found := container["image"]
@@ -85,20 +89,21 @@ func (pt *imageTransformer) updateContainers(obj map[string]interface{}, path st
imageName := containerImage.(string)
for _, img := range pt.images {
if isImageMatched(imageName, img.Name) {
name, tag := split(imageName)
if img.NewName != "" {
name = img.NewName
}
if img.NewTag != "" {
tag = ":" + img.NewTag
}
if img.Digest != "" {
tag = "@" + img.Digest
}
container["image"] = name + tag
break
if !isImageMatched(imageName, img.Name) {
continue
}
name, tag := split(imageName)
if img.NewName != "" {
name = img.NewName
}
if img.NewTag != "" {
tag = ":" + img.NewTag
}
if img.Digest != "" {
tag = "@" + img.Digest
}
container["image"] = name + tag
break
}
}
return nil

View File

@@ -89,7 +89,7 @@ func (o *nameReferenceTransformer) updateNameReference(
s, _ := in.(string)
for id, res := range m {
if id.Gvk().IsSelected(&backRef) && id.Name() == s {
matchedIds := m.FindByGVKN(id)
matchedIds := m.GetMatchingIds(id.GvknEquals)
// If there's more than one match, there's no way
// to know which one to pick, so emit error.
if len(matchedIds) > 1 {
@@ -115,7 +115,7 @@ func (o *nameReferenceTransformer) updateNameReference(
for id, res := range m {
indexes := indexOf(id.Name(), names)
if id.Gvk().IsSelected(&backRef) && len(indexes) > 0 {
matchedIds := m.FindByGVKN(id)
matchedIds := m.GetMatchingIds(id.GvknEquals)
if len(matchedIds) > 1 {
return nil, fmt.Errorf(
"Multiple matches for name %s:\n %v", id, matchedIds)

View File

@@ -39,6 +39,17 @@ func (rv *refvarTransformer) replaceVars(in interface{}) (interface{}, error) {
xs = append(xs, expansion.Expand(a.(string), rv.mappingFunc))
}
return xs, nil
case map[string]interface{}:
inMap := in.(map[string]interface{})
xs := make(map[string]interface{}, len(inMap))
for k, v := range inMap {
s, ok := v.(string)
if !ok {
return nil, fmt.Errorf("%#v is expected to be %T", v, s)
}
xs[k] = expansion.Expand(s, rv.mappingFunc)
}
return xs, nil
case interface{}:
s, ok := in.(string)
if !ok {

View File

@@ -0,0 +1,91 @@
package transformers
import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/transformers/config"
)
func TestVarRef(t *testing.T) {
type given struct {
varMap map[string]string
fs []config.FieldSpec
res resmap.ResMap
}
type expected struct {
res resmap.ResMap
}
testCases := []struct {
description string
given given
expected expected
}{
{
description: "var replacement in map[string]",
given: given{
varMap: map[string]string{
"FOO": "BAR",
},
fs: []config.FieldSpec{
{Gvk: cmap, Path: "data"},
},
res: resmap.ResMap{
resid.NewResId(cmap, "cm1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
"data": map[string]interface{}{
"item1": "$(FOO)",
"item2": "bla",
},
}),
},
},
expected: expected{
res: resmap.ResMap{
resid.NewResId(cmap, "cm1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
"data": map[string]interface{}{
"item1": "BAR",
"item2": "bla",
},
}),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
// arrange
tr := NewRefVarTransformer(tc.given.varMap, tc.given.fs)
// act
err := tr.Transform(tc.given.res)
// assert
if err != nil {
t.Errorf("unexpected error: %v", err)
}
a, e := tc.given.res, tc.expected.res
if !reflect.DeepEqual(a, e) {
err = e.ErrorIfNotEqual(a)
t.Fatalf("actual doesn't match expected: \nACTUAL:\n%v\nEXPECTED:\n%v\nERR: %v", a, e, err)
}
})
}
}

View File

@@ -16,6 +16,11 @@ limitations under the License.
package types
import (
"strconv"
"strings"
)
// GenArgs contains both generator args and options
type GenArgs struct {
args *GeneratorArgs
@@ -30,6 +35,18 @@ func NewGenArgs(args *GeneratorArgs, opts *GeneratorOptions) *GenArgs {
}
}
func (g *GenArgs) String() string {
if g == nil {
return "{nilGenArgs}"
}
return "{" +
strings.Join([]string{
"nsfx:" + strconv.FormatBool(g.NeedsHashSuffix()),
"beh:" + g.Behavior().String()},
",") +
"}"
}
// NeedHashSuffix returns true if the hash suffix is needed.
// It is needed when the two conditions are both met
// 1) GenArgs is not nil

47
pkg/types/genargs_test.go Normal file
View File

@@ -0,0 +1,47 @@
/*
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 types_test
import (
"testing"
. "sigs.k8s.io/kustomize/pkg/types"
)
func TestGenArgs_String(t *testing.T) {
tests := []struct {
ga *GenArgs
expected string
}{
{
ga: nil,
expected: "{nilGenArgs}",
},
{
ga: &GenArgs{},
expected: "{nsfx:false,beh:unspecified}",
},
{
ga: NewGenArgs(
&GeneratorArgs{Behavior: "merge"},
&GeneratorOptions{DisableNameSuffixHash: false}),
expected: "{nsfx:true,beh:merge}",
},
}
for _, test := range tests {
if test.ga.String() != test.expected {
t.Fatalf("Expected '%s', got '%s'", test.expected, test.ga.String())
}
}
}

View File

@@ -75,9 +75,11 @@ type VarSet struct {
set []Var
}
// Set returns the var set.
// Set returns a copy of the var set.
func (vs *VarSet) Set() []Var {
return vs.set
s := make([]Var, len(vs.set))
copy(s, vs.set)
return s
}
// MergeSet absorbs other vars with error on name collision.