Compare commits

...

68 Commits

Author SHA1 Message Date
Jeff Regan
81cac9b633 Merge pull request #3702 from monopole/pinToCmdConfig
Pin to cmd/config v0.9.7
2021-03-08 11:42:47 -08:00
Jeff Regan
43edc6dd7f Update README.md 2021-03-08 11:32:24 -08:00
Jeff Regan
f313cca52b Update go.sum 2021-03-08 11:30:52 -08:00
monopole
243e7cca1f Pin to cmd/config v0.9.7 2021-03-08 11:21:35 -08:00
Jeff Regan
b9c36caa1c Merge pull request #3701 from monopole/pinToKyamlAndCliUtils
Pin to kyaml v0.10.15
2021-03-08 11:19:42 -08:00
monopole
711b4ff4bb Pin to kyaml v0.10.15 2021-03-08 11:18:42 -08:00
Jeff Regan
8d72528eb5 Merge pull request #3700 from natasha41575/UpdateKustomizationWithEnvs
add env to kustomization openapi spec
2021-03-08 11:05:45 -08:00
Kubernetes Prow Robot
6590cce5c1 Merge pull request #3699 from Shell32-Natsu/image-transformer
cleanup image transformer
2021-03-08 10:56:12 -08:00
Natasha Sarkar
12c0360ba3 add env to kustomization openapi spec 2021-03-08 10:51:32 -08:00
Donny Xia
8e8fa5409d cleanup image transformer 2021-03-08 10:41:43 -08:00
Jeff Regan
5af35f4f1a Merge pull request #3695 from simster7/readlink
Use portable method to emulate 'readlink -f' behavior
2021-03-08 10:24:27 -08:00
Kubernetes Prow Robot
412e73cf76 Merge pull request #3697 from monopole/podTemplate
Add PodTemplate field to namereference config.
2021-03-08 10:07:43 -08:00
monopole
ec27642e2f Add PodTemplate field to namereference config. 2021-03-08 09:52:08 -08:00
Kubernetes Prow Robot
7165b1ec40 Merge pull request #3692 from monopole/reinstateEnv
Reinstate configmap/secret generator 'env' field.
2021-03-08 09:41:43 -08:00
Simon Behar
6dd50de7a4 Use portable method to emulate 'readlink -f' behavior
Signed-off-by: Simon Behar <simbeh7@gmail.com>
2021-03-08 09:27:38 -08:00
monopole
a8b851f84a Reinstate configmap/secret generator env field. 2021-03-07 20:42:41 -08:00
Jeff Regan
9c4966ccc8 Merge pull request #3691 from monopole/fixGoSum
Fix go.sum
2021-03-07 20:17:32 -08:00
monopole
d0bb1cd0fa Fix go.sum 2021-03-07 20:16:16 -08:00
Jeff Regan
102cf87f36 Merge pull request #3690 from monopole/pinToCmdConfig
Pin to cmd/config v0.9.6
2021-03-07 18:42:21 -08:00
monopole
584a6c2a86 Pin to cmd/config v0.9.6 2021-03-07 18:19:02 -08:00
Jeff Regan
03c6f8fff4 Merge pull request #3689 from kubernetes-sigs/pinToKyaml
Pin to kyaml v0.10.14
2021-03-07 18:17:08 -08:00
monopole
90de9b78df Pin to kyaml v0.10.14 2021-03-07 18:00:24 -08:00
Jeff Regan
34f1f2967e Merge pull request #3688 from monopole/undoreplace
Undo kyaml/go.mod lint replacements.
2021-03-07 15:01:59 -08:00
monopole
9a9df7436e Undo kyaml/go.mod lint replacements. 2021-03-07 14:36:05 -08:00
Jeff Regan
c036830c70 Merge pull request #3676 from rhtenhove/master
allow most recent release with specific path
2021-03-07 12:47:28 -08:00
Ruben ten Hove
ebbd0c7b5a check if version exists 2021-03-06 13:55:39 +01:00
Jeff Regan
7264a3a65d Merge pull request #3686 from monopole/extractFunctionEnablers
Extract flags that enable alpha function features.
2021-03-05 20:25:58 -08:00
monopole
f3a958bbf7 Extract flags that enable alpha function features. 2021-03-05 19:55:59 -08:00
Jeff Regan
14bf6f8a27 Merge pull request #3684 from monopole/gomodup
Full tree go mod tidy
2021-03-05 19:55:42 -08:00
monopole
60c8a0498b Full tree go mod tidy. 2021-03-05 18:22:32 -08:00
Jeff Regan
774d768e7b Merge pull request #3579 from KnVerey/framework_refactor
Functions Framework Revamp
2021-03-05 15:58:07 -08:00
Jeff Regan
efef397acf Merge pull request #3679 from natasha41575/MultibyteDataTest
add test for multibyte string
2021-03-05 11:56:46 -08:00
Jeff Regan
5793653630 Merge pull request #3673 from natasha41575/PanicDuplicateKeys
Return error instead of panicking for duplicate keys
2021-03-05 11:55:33 -08:00
Natasha Sarkar
4ee3d05bd8 add test for multibyte string 2021-03-04 17:41:44 -08:00
Kubernetes Prow Robot
a1df3e030f Merge pull request #3669 from justinsb/benchmark_swagger_unpack
Add benchmarks to measure impact of swagger parsing
2021-03-04 17:04:21 -08:00
Kubernetes Prow Robot
4e0332551a Merge pull request #3667 from natasha41575/UpgradeYaml.V2
upgraded to yaml.v2 v2.4.0
2021-03-04 16:48:24 -08:00
Ruben ten Hove
216ab488a6 allow most recent release with specific path 2021-03-04 17:19:39 +01:00
Natasha Sarkar
722b0131f0 return error for duplicate keys rather than panicking 2021-03-03 12:13:24 -08:00
Natasha Sarkar
93dd571df9 regression test for panic on duplicate keys 2021-03-03 11:18:42 -08:00
Katrina Verey
a7000dd9c6 Update unpinned pluginator to new framework 2021-03-03 08:27:19 -08:00
Katrina Verey
5c4b5b1bf0 Improvements to kyaml fn framework
This commit creates a new version of the alpha configuration functions framework. Goals include:
- Make it easy to build multi-version APIs with the framework (not previously facilitated at all).
- Simplify the framework's APIs where redundant configuration options exist (leaving the most powerful, replacing others with helpers to maintain usability they provided).
- Make the Framework's APIs more consistent (e.g. between the various template types, usage of kio.Filter, field names)
- Decouple responsibilities (e.g. command creation, resource list processing, generation of templating functions).
- Make the framework even more powerfully pluggable (e.g. any kio.Filter can be a selector, and the selector the framework provides is itself a filter built from reusable abstractions).
- Improve documentation.
- Make container patches merge fields (notably list fields like `env`) correctly.
2021-03-03 08:27:19 -08:00
Justin SB
8e57ee9111 Add benchmarks to measure impact of swagger parsing
Example results:

BenchmarkSwaggerParse-72               2         882910241 ns/op
BenchmarkAsssetUnpack-72              62          19654866 ns/op
2021-03-03 09:11:42 -05:00
Natasha Sarkar
60bd8d15d9 upgraded to yaml.v2 v2.4.0 2021-03-02 18:04:21 -08:00
Jeff Regan
1d524b6fbe Merge pull request #3666 from natasha41575/UpdateToGo1.16
updated go version to 1.16
2021-03-02 17:20:46 -08:00
Natasha Sarkar
e9c97a4c4e updated go version to 1.16 2021-03-02 16:40:08 -08:00
Kubernetes Prow Robot
48c89cb698 Merge pull request #3661 from natasha41575/TrimOpenApi
update openapi version to v1.20.4
2021-03-02 16:39:19 -08:00
Natasha Sarkar
af1e692a5e fix lint error 2021-03-02 16:03:26 -08:00
Natasha Sarkar
57e7db0423 update openapi version to v1.20.4 2021-03-02 13:46:11 -08:00
Jeff Regan
7fb6fa0f35 Merge pull request #3648 from lcostea/lcostea/smaller_docker_image
feat: Reduce docker image size
2021-02-28 13:01:00 -08:00
Jeff Regan
50c3875354 Merge pull request #3654 from monopole/unpinEverything
Back to development mode; unpin the modules
2021-02-28 13:00:44 -08:00
monopole
efc03bf329 Back to development mode; unpin the modules 2021-02-28 12:41:31 -08:00
Jeff Regan
9785bda7be Merge pull request #3653 from monopole/pinToApi
Pin to api v0.8.4
2021-02-28 12:20:17 -08:00
monopole
29bfdfc1ef Pin to api v0.8.4 2021-02-28 12:06:30 -08:00
Jeff Regan
4f72cb8d00 Merge pull request #3652 from monopole/pinToCmdConfig
Pin to cmd/config v0.9.5
2021-02-28 12:01:11 -08:00
monopole
a45e90b1e4 Pin to cmd/config v0.9.5 2021-02-28 11:40:30 -08:00
Jeff Regan
6b6bc45f2c Update go.sum 2021-02-28 11:29:19 -08:00
Jeff Regan
e4a34f2a48 Merge pull request #3651 from monopole/pinToKyamlAndCliUtils
Pin to kyaml v0.10.13
2021-02-28 11:26:39 -08:00
monopole
4a2ed901b3 Pin to kyaml v0.10.13 2021-02-28 11:04:30 -08:00
Jeff Regan
ba67bc0f18 Merge pull request #3650 from monopole/unpinEverything
Unpin kyaml, cmd/config and api.
2021-02-28 10:46:03 -08:00
monopole
be8d60fb9f Unpin kyaml, cmd/config and api. 2021-02-28 10:26:07 -08:00
Jeff Regan
d9d5bb83f0 Merge pull request #3649 from monopole/updateGeneratedFiles
Update generated files
2021-02-28 10:24:21 -08:00
monopole
cfa7645d3b Update generated files under cmd/config.
sed -i 's|version: v0.18.10|version: v0.19.8|' cmd/config/internal/commands/internal/k8sgen/k8scopy.yaml
(cd cmd/config/; make generate)
2021-02-28 10:11:39 -08:00
monopole
2e6ef91a7c Update generated files under kyaml.
sed -i 's|version: v0.17.0|version: v0.19.8|' kyaml/yaml/internal/k8sgen/k8scopy.yaml
rm kyaml/yaml/internal/k8sgen/pkg/labels/zz_generated.deepcopy.go
(cd kyaml; make generate)
2021-02-28 10:10:52 -08:00
Jeff Regan
508f294e0c k8scopy should rename zz_generated.foo.go
Files whose names start with zz_generated get special treatment from
https://github.com/kubernetes/kubernetes/blob/master/build/common.sh
 (and ./hack/verify-generated-files-remake.sh, etc.).
We don't want that, so modify those file names.
2021-02-28 09:21:35 -08:00
Liviu Costea
a81ebe9842 feat: Reduce docker image size 2021-02-28 12:45:57 +02:00
Jeff Regan
c92fb809c6 Delete older releasing program (replaced by gorepomod). 2021-02-27 06:52:25 -08:00
Jeff Regan
043e8c36e5 Merge pull request #3645 from monopole/pinToApi
Pin to api api/v0.8.3
2021-02-26 17:04:45 -08:00
monopole
7965195c29 Pin to api api/v0.8.3 2021-02-26 16:50:46 -08:00
220 changed files with 9455 additions and 356234 deletions

View File

@@ -4,10 +4,6 @@
package builtins
import (
"fmt"
"regexp"
"strings"
"sigs.k8s.io/kustomize/api/filters/imagetag"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
@@ -49,139 +45,6 @@ func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
return nil
}
func (p *ImageTagTransformerPlugin) mutateImage(in interface{}) (interface{}, error) {
original, ok := in.(string)
if !ok {
return nil, fmt.Errorf("image path is not of type string but %T", in)
}
if !isImageMatched(original, p.ImageTag.Name) {
return original, nil
}
name, tag := split(original)
if p.ImageTag.NewName != "" {
name = p.ImageTag.NewName
}
if p.ImageTag.NewTag != "" {
tag = ":" + p.ImageTag.NewTag
}
if p.ImageTag.Digest != "" {
tag = "@" + p.ImageTag.Digest
}
return name + tag, nil
}
// findAndReplaceImage replaces the image name and
// tags inside one object.
// It searches the object for container session
// then loops though all images inside containers
// session, finds matched ones and update the
// image name and tag name
func (p *ImageTagTransformerPlugin) findAndReplaceImage(obj map[string]interface{}) error {
paths := []string{"containers", "initContainers"}
updated := false
for _, path := range paths {
containers, found := obj[path]
if found && containers != nil {
if _, err := p.updateContainers(containers); err != nil {
return err
}
updated = true
}
}
if !updated {
return p.findContainers(obj)
}
return nil
}
func (p *ImageTagTransformerPlugin) updateContainers(in interface{}) (interface{}, error) {
containers, ok := in.([]interface{})
if !ok {
return nil, fmt.Errorf(
"containers path is not of type []interface{} but %T", in)
}
for i := range containers {
container := containers[i].(map[string]interface{})
containerImage, found := container["image"]
if !found {
continue
}
imageName := containerImage.(string)
if isImageMatched(imageName, p.ImageTag.Name) {
newImage, err := p.mutateImage(imageName)
if err != nil {
return nil, err
}
container["image"] = newImage
}
}
return containers, nil
}
func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) error {
for key := range obj {
switch typedV := obj[key].(type) {
case map[string]interface{}:
err := p.findAndReplaceImage(typedV)
if err != nil {
return err
}
case []interface{}:
for i := range typedV {
item := typedV[i]
typedItem, ok := item.(map[string]interface{})
if ok {
err := p.findAndReplaceImage(typedItem)
if err != nil {
return err
}
}
}
}
}
return nil
}
func isImageMatched(s, t string) bool {
// Tag values are limited to [a-zA-Z0-9_.{}-].
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.{}-]*)?$")
return pattern.MatchString(s)
}
// split separates and returns the name and tag parts
// from the image string using either colon `:` or at `@` separators.
// Note that the returned tag keeps its separator.
func split(imageName string) (name string, tag string) {
// check if image name contains a domain
// if domain is present, ignore domain and check for `:`
ic := -1
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
ic = strings.LastIndex(imageName, ":")
} else {
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
// set ic only if `:` is present
if lastIc > 0 {
ic = slashIndex + lastIc
}
}
ia := strings.LastIndex(imageName, "@")
if ic < 0 && ia < 0 {
return imageName, ""
}
i := ic
if ia > 0 {
i = ia
}
name = imageName[:i]
tag = imageName[i:]
return
}
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
return &ImageTagTransformerPlugin{}
}

View File

@@ -30,12 +30,16 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
return nil
}
for _, r := range m.Resources() {
if r.IsEmpty() {
empty, err := r.IsEmpty()
if err != nil {
return err
}
if empty {
// Don't mutate empty objects?
continue
}
r.StorePreviousId()
err := r.ApplyFilter(namespace.Filter{
err = r.ApplyFilter(namespace.Filter{
Namespace: p.Namespace,
FsSlice: p.FieldSpecs,
})

View File

@@ -1,18 +1,18 @@
module sigs.k8s.io/kustomize/api
go 1.15
go 1.16
require (
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/go-errors/errors v1.0.1
github.com/go-openapi/spec v0.19.5
github.com/google/go-cmp v0.3.0
github.com/google/go-cmp v0.4.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/imdario/mergo v0.3.5
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.4.0
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
sigs.k8s.io/kustomize/kyaml v0.10.12
sigs.k8s.io/kustomize/kyaml v0.10.15
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -1,5 +1,4 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -36,7 +35,6 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -94,7 +92,6 @@ github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -103,8 +100,9 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -113,18 +111,14 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -133,7 +127,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -143,20 +136,17 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -177,18 +167,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -231,9 +215,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -247,7 +230,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c h1:Vco5b+cuG5NNfORVxZy6bYZQ7rsigisU1WQFkvQ0L5E=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -260,6 +242,8 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -276,12 +260,12 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.10.12 h1:1lUvuSVETthZspl4X+Xdic/FgPAfxUbc7MKtc1vcO2c=
sigs.k8s.io/kustomize/kyaml v0.10.12/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -76,7 +76,7 @@ type Kunstructured interface {
GetString(string) (string, error)
// Several uses.
Map() map[string]interface{}
Map() (map[string]interface{}, error)
// Used by Resource.AsYAML and Resource.String
MarshalJSON() ([]byte, error)

View File

@@ -399,7 +399,8 @@ func find(name string, resMap resmap.ResMap) *resource.Resource {
func getCommand(r *resource.Resource) string {
var m map[string]interface{}
var c []interface{}
m, _ = r.Map()["spec"].(map[string]interface{})
resourceMap, _ := r.Map()
m, _ = resourceMap["spec"].(map[string]interface{})
m, _ = m["template"].(map[string]interface{})
m, _ = m["spec"].(map[string]interface{})
c, _ = m["containers"].([]interface{})

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/api/internal/crawl
go 1.15
go 1.16
require (
github.com/elastic/go-elasticsearch/v6 v6.8.5

View File

@@ -103,8 +103,9 @@ github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNu
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -149,8 +150,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -167,7 +168,6 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -220,9 +220,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -248,6 +247,7 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -264,12 +264,12 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.10.12 h1:1lUvuSVETthZspl4X+Xdic/FgPAfxUbc7MKtc1vcO2c=
sigs.k8s.io/kustomize/kyaml v0.10.12/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -43,7 +43,11 @@ func (o *multiTransformer) transform(m resmap.ResMap) error {
}
}
for _, r := range m.Resources() {
if r.IsEmpty() {
empty, err := r.IsEmpty()
if err != nil {
return err
}
if empty {
err := m.Remove(r.CurId())
if err != nil {
return err

View File

@@ -306,13 +306,16 @@ items:
assert.False(t, tc.exp.isErr)
assert.Equal(t, len(tc.exp.out), len(rs))
for i := range rs {
rsMap, err := rs[i].Map()
assert.NoError(t, err)
assert.Equal(
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rs[i].Map()))
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
if n != "listWithAnchors" {
// https://github.com/kubernetes-sigs/kustomize/issues/3271
if !reflect.DeepEqual(tc.exp.out[i], rs[i].Map()) {
m, _ := rs[i].Map()
if !reflect.DeepEqual(tc.exp.out[i], m) {
t.Fatalf("%s:\nexpected: %v\n actual: %v",
n, tc.exp.out[i], rs[i].Map())
n, tc.exp.out[i], m)
}
}
}

View File

@@ -220,7 +220,7 @@ func (wn *WNode) GetString(path string) (string, error) {
}
// Map implements ifc.Kunstructured.
func (wn *WNode) Map() map[string]interface{} {
func (wn *WNode) Map() (map[string]interface{}, error) {
return wn.node.Map()
}

View File

@@ -559,7 +559,9 @@ func TestGetSlice(t *testing.T) {
}
func TestMapEmpty(t *testing.T) {
assert.Equal(t, 0, len(NewWNode().Map()))
newNodeMap, err := NewWNode().Map()
assert.NoError(t, err)
assert.Equal(t, 0, len(newNodeMap))
}
func TestMap(t *testing.T) {
@@ -577,7 +579,8 @@ func TestMap(t *testing.T) {
},
}
actual := wn.Map()
actual, err := wn.Map()
assert.NoError(t, err)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}

View File

@@ -50,6 +50,8 @@ nameReference:
- path: spec/volumes/projected/sources/configMap/name
version: v1
kind: Pod
- path: template/spec/volumes/configMap/name
kind: PodTemplate
- path: spec/template/spec/volumes/configMap/name
kind: Deployment
- path: spec/template/spec/containers/env/valueFrom/configMapKeyRef/name

View File

@@ -292,7 +292,8 @@ metadata:
---
apiVersion: v1
data:
nonsense: "Lorem ipsum dolor sit amet, consectetur\nadipiscing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua. \n"
nonsense: "Lorem ipsum dolor sit amet, consectetur\nadipiscing elit, sed do eiusmod
tempor\nincididunt ut labore et dolore magna aliqua. \n"
kind: ConfigMap
metadata:
annotations:

View File

@@ -132,6 +132,7 @@ configMapGenerator:
- vegetable=broccoli
envs:
- foo.env
env: bar.env
files:
- passphrase=phrase.dat
- forces.txt
@@ -153,10 +154,14 @@ secretGenerator:
files:
- passphrase=phrase.dat
- forces.txt
env: bar.env
`)
th.WriteF("foo.env", `
MOUNTAIN=everest
OCEAN=pacific
`)
th.WriteF("bar.env", `
BIRD=falcon
`)
th.WriteF("phrase.dat", `
Life is short.
@@ -173,6 +178,7 @@ weak nuclear
m := th.Run(".", opts)
expFmt := `apiVersion: v1
data:
BIRD: falcon
MOUNTAIN: everest
OCEAN: pacific
forces.txt: |2
@@ -190,11 +196,12 @@ data:
vegetable: broccoli
kind: ConfigMap
metadata:
name: blah-bob-d87t8m8tgm
name: blah-bob-g9df72cd5b
---
apiVersion: v1
data:
druid_segmentCache_locations: '[{"path": "var/druid/segment-cache", "maxSize": 32000000000, "freeSpacePercent": 1.0}]'
druid_segmentCache_locations: '[{"path": "var/druid/segment-cache", "maxSize":
32000000000, "freeSpacePercent": 1.0}]'
v2: '[{"path": "var/druid/segment-cache"}]'
kind: ConfigMap
metadata:
@@ -202,6 +209,7 @@ metadata:
---
apiVersion: v1
data:
BIRD: ZmFsY29u
MOUNTAIN: ZXZlcmVzdA==
OCEAN: cGFjaWZpYw==
forces.txt: %s
@@ -219,15 +227,17 @@ type: Opaque
opts.IfApiMachineryElseKyaml(
fmt.Sprintf(
expFmt,
`CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbnVjbGVhcgo=`,
`CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aGUgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`,
`CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
VjbGVhcgo=`,
`CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`,
`ftht6hfgmb`),
fmt.Sprintf(
expFmt, `|
CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
VjbGVhcgo=`, `|
CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`, `9t25t44gg4`)))
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`, `58g62h555c`)))
}
// TODO: These should be errors instead.

View File

@@ -0,0 +1,39 @@
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
func TestDuplicateKeys(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
- resources.yaml
`)
th.WriteF("resources.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: podinfo
spec:
selector:
matchLabels:
app: podinfo
template:
spec:
containers:
- name: podinfod
image: ghcr.io/stefanprodan/podinfo:5.0.3
command:
- ./podinfo
env:
- name: PODINFO_UI_COLOR
value: "#34577c"
env:
- name: PODINFO_UI_COLOR
value: "#34577c"
`)
th.RunWithErr(".", th.MakeDefaultOptions())
}

View File

@@ -0,0 +1,32 @@
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
func TestMultibyteCharInConfigMap(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
- resources.yaml
`)
th.WriteF("resources.yaml", `
apiVersion: v1
kind: ConfigMap
metadata:
name: game-config
data:
key: あ
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
data:
key: あ
kind: ConfigMap
metadata:
name: game-config
`)
}

View File

@@ -108,7 +108,7 @@ func TestCustomOpenApiFieldBothPathAndVersion(t *testing.T) {
resources:
- mycrd.yaml
openapi:
version: v1.18.8
version: v1.20.4
path: mycrd_schema.json
`+customSchemaPatch)
writeCustomResource(th, "mycrd.yaml")
@@ -193,7 +193,7 @@ openapi:
resources:
- ../base
openapi:
version: v1.19.1
version: v1.20.4
`+customSchemaPatch)
writeCustomResource(th, "base/mycrd.yaml")
writeTestSchema(th, "base/")
@@ -211,7 +211,7 @@ spec:
- image: nginx
name: server
`)
assert.Equal(t, "v1191", openapi.GetSchemaVersion())
assert.Equal(t, "v1204", openapi.GetSchemaVersion())
}
func TestCustomOpenAPIFieldFromComponent(t *testing.T) {
@@ -225,6 +225,6 @@ func TestCustomOpenAPIFieldFromComponent(t *testing.T) {
f(th)
}
openapi.ResetOpenAPI()
th.Run(runPath, th.MakeDefaultOptions())
th.Run("prod", th.MakeDefaultOptions())
assert.Equal(t, "using custom schema from file provided", openapi.GetSchemaVersion())
}

View File

@@ -13,7 +13,7 @@ func TestOpenApiFieldBasicUsage(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
openapi:
version: v1.18.8
version: v1.20.4
resources:
- deployment.yaml
`)
@@ -41,7 +41,7 @@ spec:
containers:
- image: whatever
`)
assert.Equal(t, "v1188", openapi.GetSchemaVersion())
assert.Equal(t, "v1204", openapi.GetSchemaVersion())
}
func TestOpenApiFieldNotBuiltin(t *testing.T) {
@@ -102,201 +102,3 @@ spec:
`)
assert.Equal(t, kubernetesapi.DefaultOpenAPI, openapi.GetSchemaVersion())
}
func TestOpenApiFieldFromBase(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
openapi:
version: v1.19.0
namePrefix: a-
resources:
- deployment.yaml
`)
th.WriteF("base/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeployment
spec:
template:
spec:
containers:
- image: whatever
`)
th.WriteK("overlay", `
namePrefix: b-
resources:
- ../base
patchesStrategicMerge:
- depPatch.yaml
`)
th.WriteF("overlay/depPatch.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeployment
spec:
replicas: 999
`)
m := th.Run("overlay", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: b-a-myDeployment
spec:
replicas: 999
template:
spec:
containers:
- image: whatever
`)
assert.Equal(t, "v1190", openapi.GetSchemaVersion())
}
func TestOpenApiFieldFromOverlay(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
namePrefix: a-
resources:
- deployment.yaml
`)
th.WriteF("base/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeployment
spec:
template:
spec:
containers:
- image: whatever
`)
th.WriteK("overlay", `
openapi:
version: v1.18.8
namePrefix: b-
resources:
- ../base
patchesStrategicMerge:
- depPatch.yaml
`)
th.WriteF("overlay/depPatch.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeployment
spec:
replicas: 999
`)
m := th.Run("overlay", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: b-a-myDeployment
spec:
replicas: 999
template:
spec:
containers:
- image: whatever
`)
assert.Equal(t, "v1188", openapi.GetSchemaVersion())
}
func TestOpenApiFieldOverlayTakesPrecedence(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
openapi:
version: v1.19.0
namePrefix: a-
resources:
- deployment.yaml
`)
th.WriteF("base/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeployment
spec:
template:
spec:
containers:
- image: whatever
`)
th.WriteK("overlay", `
openapi:
version: v1.18.8
namePrefix: b-
resources:
- ../base
patchesStrategicMerge:
- depPatch.yaml
`)
th.WriteF("overlay/depPatch.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: myDeployment
spec:
replicas: 999
`)
m := th.Run("overlay", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: b-a-myDeployment
spec:
replicas: 999
template:
spec:
containers:
- image: whatever
`)
assert.Equal(t, "v1188", openapi.GetSchemaVersion())
}
func TestOpenAPIFieldFromComponentDefault(t *testing.T) {
input := []FileGen{writeTestBase, writeTestComponent, writeOverlayProd}
runPath := "prod"
th := kusttest_test.MakeHarness(t)
for _, f := range input {
f(th)
}
th.Run(runPath, th.MakeDefaultOptions())
assert.Equal(t, kubernetesapi.DefaultOpenAPI, openapi.GetSchemaVersion())
}
func writeTestComponentWithOlderOpenAPIVersion(th kusttest_test.Harness) {
th.WriteC("comp", `
openapi:
version: v1.18.8
`)
th.WriteF("comp/stub.yaml", `
apiVersion: v1
kind: Deployment
metadata:
name: stub
spec:
replicas: 1
`)
}
const runPath = "prod"
func TestOpenAPIFieldFromComponent(t *testing.T) {
input := []FileGen{
writeTestBase,
writeTestComponentWithOlderOpenAPIVersion,
writeOverlayProd}
th := kusttest_test.MakeHarness(t)
for _, f := range input {
f(th)
}
th.Run(runPath, th.MakeDefaultOptions())
assert.Equal(t, "v1188", openapi.GetSchemaVersion())
}

View File

@@ -66,11 +66,14 @@ spec:
- name: SHORT_STRING_BLANK
value: short string
- name: LONG_STRING_BLANK
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
suscipit ex non molestie varius.
- name: LONG_STRING_BLANK_WITH_SINGLE_QUOTE
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
suscipit ex non molestie varius.
- name: LONG_STRING_BLANK_WITH_DOUBLE_QUOTE
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas suscipit ex non molestie varius.
value: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
suscipit ex non molestie varius.
- name: INVALID_PLAIN_STYLE_STRING
value: ': test'
image: test

View File

@@ -1025,7 +1025,8 @@ spec:
- -namespace=${POD_NAMESPACE}
- -certs-dir=/cockroach-certs
- -type=node
- -addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut -f 1-2 -d '.'),dev-base-cockroachdb-public
- -addresses=localhost,127.0.0.1,${POD_IP},$(hostname -f),$(hostname -f|cut
-f 1-2 -d '.'),dev-base-cockroachdb-public
- -symlink-ca-from=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
env:
- name: POD_IP

View File

@@ -90,9 +90,14 @@ func (m *merginator) makeError(cd resource.ConflictDetector, index int) error {
if conflict == nil {
return fmt.Errorf("expected conflict for %s", m.incoming[index].OrgId())
}
conflictMap, _ := conflict.Map()
incomingIndexMap, _ := m.incoming[index].Map()
return fmt.Errorf(
"conflict between %#v at index %d and %#v",
m.incoming[index].Map(), index, conflict.Map())
incomingIndexMap,
index,
conflictMap,
)
}
// findConflict looks for a conflict in a resource slice.

View File

@@ -119,7 +119,11 @@ func (m *resWrangler) Debug(title string) {
fmt.Println("---")
}
fmt.Printf("# %d %s\n", i, r.OrgId())
blob, err := yaml.Marshal(r.Map())
m, err := r.Map()
if err != nil {
panic(err)
}
blob, err := yaml.Marshal(m)
if err != nil {
panic(err)
}
@@ -272,7 +276,8 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
for _, res := range m.Resources() {
out, err := res.AsYAML()
if err != nil {
return nil, errors.Wrapf(err, "%#v", res.Map())
m, _ := res.Map()
return nil, errors.Wrapf(err, "%#v", m)
}
if firstObj {
firstObj = false
@@ -602,14 +607,23 @@ func (m *resWrangler) ApplySmPatch(
// Some unknown error, let it through.
return err
}
if !res.IsEmpty() {
empty, err := res.IsEmpty()
if err != nil {
return err
}
if !empty {
m, _ := res.Map()
return errors.Wrapf(
err, "with unexpectedly non-empty object map of size %d",
len(res.Map()))
len(m))
}
// Fall through to handle deleted object.
}
if !res.IsEmpty() {
empty, err := res.IsEmpty()
if err != nil {
return err
}
if !empty {
// IsEmpty means all fields have been removed from the object.
// This can happen if a patch required deletion of the
// entire resource (not just a part of it). This means

View File

@@ -114,7 +114,11 @@ func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
u := kunStructs[0]
kunStructs = kunStructs[1:]
if strings.HasSuffix(u.GetKind(), "List") {
items := u.Map()["items"]
m, err := u.Map()
if err != nil {
return nil, err
}
items := m["items"]
itemsSlice, ok := items.([]interface{})
if !ok {
if items == nil {

View File

@@ -98,11 +98,12 @@ func (r *Resource) GetString(p string) (string, error) {
return r.kunStr.GetString(p)
}
func (r *Resource) IsEmpty() bool {
return len(r.kunStr.Map()) == 0
func (r *Resource) IsEmpty() (bool, error) {
m, err := r.kunStr.Map()
return len(m) == 0, err
}
func (r *Resource) Map() map[string]interface{} {
func (r *Resource) Map() (map[string]interface{}, error) {
return r.kunStr.Map()
}
@@ -488,7 +489,11 @@ func (r *Resource) ApplySmPatch(patch *Resource) error {
if err != nil {
return err
}
if !r.IsEmpty() {
empty, err := r.IsEmpty()
if err != nil {
return err
}
if !empty {
r.SetName(n)
r.SetNamespace(ns)
}

View File

@@ -155,7 +155,6 @@ type Kustomization struct {
// moving content of deprecated fields to newer
// fields.
func (k *Kustomization) FixKustomizationPostUnmarshalling() {
if k.Kind == "" {
k.Kind = KustomizationKind
}
@@ -168,6 +167,20 @@ func (k *Kustomization) FixKustomizationPostUnmarshalling() {
}
k.Resources = append(k.Resources, k.Bases...)
k.Bases = nil
for i, g := range k.ConfigMapGenerator {
if g.EnvSource != "" {
k.ConfigMapGenerator[i].EnvSources =
append(g.EnvSources, g.EnvSource)
k.ConfigMapGenerator[i].EnvSource = ""
}
}
for i, g := range k.SecretGenerator {
if g.EnvSource != "" {
k.SecretGenerator[i].EnvSources =
append(g.EnvSources, g.EnvSource)
k.SecretGenerator[i].EnvSource = ""
}
}
}
// FixKustomizationPreMarshalling fixes things

View File

@@ -1,6 +1,7 @@
package types
import (
"reflect"
"testing"
)
@@ -15,6 +16,12 @@ func fixKustomizationPostUnmarshallingCheck(k, e *Kustomization) bool {
func TestFixKustomizationPostUnmarshalling(t *testing.T) {
var k Kustomization
k.Bases = append(k.Bases, "foo")
k.ConfigMapGenerator = []ConfigMapArgs{{GeneratorArgs{
KvPairSources: KvPairSources{
EnvSources: []string{"a", "b"},
EnvSource: "c",
},
}}}
k.FixKustomizationPostUnmarshalling()
expected := Kustomization{
@@ -23,8 +30,15 @@ func TestFixKustomizationPostUnmarshalling(t *testing.T) {
APIVersion: KustomizationVersion,
},
Resources: []string{"foo"},
ConfigMapGenerator: []ConfigMapArgs{{GeneratorArgs{
KvPairSources: KvPairSources{
EnvSources: []string{"a", "b", "c"},
},
}}},
}
if !reflect.DeepEqual(k, expected) {
t.Fatalf("unexpected output: %v", k)
}
if !fixKustomizationPostUnmarshallingCheck(&k, &expected) {
t.Fatalf("unexpected output: %v", k)
}

View File

@@ -28,4 +28,9 @@ type KvPairSources struct {
// or npm ".env" file or a ".ini" file
// (wikipedia.org/wiki/INI_file)
EnvSources []string `json:"envs,omitempty" yaml:"envs,omitempty"`
// Older, singular form of EnvSources.
// On edits (e.g. `kustomize fix`) this is merged into the plural form
// for consistency with LiteralSources and FileSources.
EnvSource string `json:"env,omitempty" yaml:"env,omitempty"`
}

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/cmd/config
go 1.15
go 1.16
require (
github.com/go-errors/errors v1.0.1
@@ -14,8 +14,7 @@ require (
github.com/stretchr/testify v1.6.1
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/inf.v0 v0.9.1
sigs.k8s.io/kustomize/kyaml v0.10.12
sigs.k8s.io/kustomize/kyaml v0.10.15
)

View File

@@ -108,6 +108,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -157,8 +158,9 @@ github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -234,7 +236,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -266,8 +267,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -286,10 +285,10 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/kyaml v0.10.12 h1:1lUvuSVETthZspl4X+Xdic/FgPAfxUbc7MKtc1vcO2c=
sigs.k8s.io/kustomize/kyaml v0.10.12/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=

View File

@@ -1,5 +1,5 @@
module sigs.k8s.io/kustomize/cmd/config/internal/commands/e2e/e2econtainerconfig
go 1.15
go 1.16
require sigs.k8s.io/kustomize/kyaml v0.1.10

View File

@@ -4,7 +4,7 @@
# The files to vendor (copy).
# See {repo}/cmd/k8scopy/main.go for more info.
module: k8s.io/apimachinery
version: v0.18.10
version: v0.19.8
packages:
- name: pkg/api/resource
files:

View File

@@ -1,5 +1,5 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.18.10; DO NOT EDIT.
// Copied from k8s.io/apimachinery@v0.18.10/pkg/api/resource/amount.go
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/api/resource/amount.go
/*
Copyright 2014 The Kubernetes Authors.

View File

@@ -1,5 +1,5 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.18.10; DO NOT EDIT.
// Copied from k8s.io/apimachinery@v0.18.10/pkg/api/resource/math.go
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/api/resource/math.go
/*
Copyright 2014 The Kubernetes Authors.

View File

@@ -1,5 +1,5 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.18.10; DO NOT EDIT.
// Copied from k8s.io/apimachinery@v0.18.10/pkg/api/resource/quantity.go
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/api/resource/quantity.go
/*
Copyright 2014 The Kubernetes Authors.

View File

@@ -1,5 +1,5 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.18.10; DO NOT EDIT.
// Copied from k8s.io/apimachinery@v0.18.10/pkg/api/resource/scale_int.go
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/api/resource/scale_int.go
/*
Copyright 2015 The Kubernetes Authors.

View File

@@ -1,5 +1,5 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.18.10; DO NOT EDIT.
// Copied from k8s.io/apimachinery@v0.18.10/pkg/api/resource/suffix.go
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/api/resource/suffix.go
/*
Copyright 2014 The Kubernetes Authors.

View File

@@ -1,5 +1,5 @@
module sigs.k8s.io/kustomize/cmd/gorepomod
go 1.15
go 1.16
require golang.org/x/mod v0.3.0

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/cmd/k8scopy
go 1.15
go 1.16
require (
github.com/stretchr/testify v1.4.0

View File

@@ -14,6 +14,13 @@ import (
const (
sigsK8sIo = "sigs.k8s.io"
// Files whose names start with prefixBad get special treatment from
// https://github.com/kubernetes/kubernetes/blob/master/build/common.sh
// (and ./hack/verify-generated-files-remake.sh, etc.).
// We don't want that, so modify those file names.
prefixBad = "zz_generated"
prefixGood = "copied"
)
type Copier struct {
@@ -64,16 +71,20 @@ func NewCopier(s *ModuleSpec, prefix, pgmName string) Copier {
return tmp
}
func (c Copier) CopyFile(dir, name string) error {
func (c Copier) CopyFile(dir, fName string) error {
inFile, err := os.Open(
filepath.Join(c.goModCache, c.spec.Name(), dir, name))
filepath.Join(c.goModCache, c.spec.Name(), dir, fName))
if err != nil {
return err
}
defer inFile.Close()
scanner := bufio.NewScanner(inFile)
w, err := newWriter(dir, name)
newName := fName
if strings.HasPrefix(fName, prefixBad) {
newName = prefixGood + fName[len(prefixBad):]
}
w, err := newWriter(dir, newName)
if err != nil {
return err
}
@@ -86,8 +97,8 @@ func (c Copier) CopyFile(dir, name string) error {
c.pgmName, c.spec.Name()))
w.write(
fmt.Sprintf(
"// Copied from %s\n",
filepath.Join(c.spec.Name(), dir, name)))
"// File content copied from %s\n",
filepath.Join(c.spec.Name(), dir, fName)))
for scanner.Scan() {
l := scanner.Text()
@@ -102,7 +113,9 @@ func (c Copier) CopyFile(dir, name string) error {
}
// Fix self-imports.
l = strings.Replace(l, c.spec.Module, c.replacementPath(), 1)
// Replace klog with generic log (eschewing k8s.io entirely).
// Replace k8s.io/klog with Go's log (we must avoid k8s.io entirely).
l = strings.Replace(l, "\"k8s.io/klog/v2\"", "\"log\"", 1)
l = strings.Replace(l, "\"k8s.io/klog\"", "\"log\"", 1)
l = strings.Replace(l, "klog.V(10).Infof(", "log.Printf(", 1)
w.write(l)

View File

@@ -1,3 +1,3 @@
module sigs.k8s.io/kustomize/cmd/mdtogo
go 1.15
go 1.16

View File

@@ -1,12 +1,13 @@
module sigs.k8s.io/kustomize/cmd/pluginator/v2
go 1.15
go 1.16
require (
github.com/rakyll/statik v0.1.7
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.4.0
sigs.k8s.io/kustomize/api v0.8.2
sigs.k8s.io/kustomize/kyaml v0.10.12
sigs.k8s.io/yaml v1.2.0
sigs.k8s.io/kustomize/api v0.8.4
sigs.k8s.io/kustomize/kyaml v0.10.15
)
replace sigs.k8s.io/kustomize/api => ../../api

View File

@@ -100,8 +100,9 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -144,8 +145,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -217,9 +218,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -245,6 +245,7 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -261,16 +262,12 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/api v0.8.2 h1:YrFKmhRgpkqa3Il2Geh+BflrRwN4vvyLH+DlKmREteU=
sigs.k8s.io/kustomize/api v0.8.2/go.mod h1:yHAkpXolD4n+m7YoostNPYTEyDjwdNNGRQ0Mitxihrc=
sigs.k8s.io/kustomize/kyaml v0.10.11 h1:7KzaMS6TtwhGQW6hkw8umC2zFtSGAQPdAMagfautnkw=
sigs.k8s.io/kustomize/kyaml v0.10.11/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/kyaml v0.10.12 h1:1lUvuSVETthZspl4X+Xdic/FgPAfxUbc7MKtc1vcO2c=
sigs.k8s.io/kustomize/kyaml v0.10.12/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -9,7 +9,6 @@ import (
"sigs.k8s.io/kustomize/api/provider"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/yaml"
)
//nolint
@@ -21,24 +20,17 @@ func main() {
pluginHelpers := resmap.NewPluginHelpers(
nil, p.GetFieldValidator(), resmapFactory)
resourceList := &framework.ResourceList{}
resourceList.FunctionConfig = map[string]interface{}{}
cmd := framework.Command(resourceList, func() error {
processor := framework.ResourceListProcessorFunc(func(resourceList *framework.ResourceList) error {
resMap, err := resmapFactory.NewResMapFromRNodeSlice(resourceList.Items)
if err != nil {
return err
}
dataField, err := getDataFromFunctionConfig(resourceList.FunctionConfig)
if err != nil {
return err
}
dataValue, err := yaml.Marshal(dataField)
dataValue, err := resourceList.FunctionConfig.Field("data").Value.String()
if err != nil {
return err
}
err = plugin.Config(pluginHelpers, dataValue)
err = plugin.Config(pluginHelpers, []byte(dataValue))
if err != nil {
return err
}
@@ -60,17 +52,8 @@ func main() {
}
return nil
})
if err := cmd.Execute(); err != nil {
if err := framework.Execute(&processor, nil); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
//nolint
func getDataFromFunctionConfig(fc interface{}) (interface{}, error) {
f, ok := fc.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("function config %#v is not valid", fc)
}
return f["data"], nil
}

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/cmd/prchecker
go 1.15
go 1.16
require (
github.com/google/go-github v17.0.0+incompatible

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/functions/examples/application-cr
go 1.15
go 1.16
require (
k8s.io/apimachinery v0.18.3

View File

@@ -78,7 +78,6 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -91,7 +90,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@@ -345,14 +343,11 @@ go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWK
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

View File

@@ -1,5 +1,5 @@
module sigs.k8s.io/kustomize/functions/examples/injection-tshirt-sizes
go 1.15
go 1.16
require sigs.k8s.io/kustomize/kyaml v0.7.1

View File

@@ -1,5 +1,5 @@
module sigs.k8s.io/kustomize/functions/examples/template-go-nginx
go 1.15
go 1.16
require sigs.k8s.io/kustomize/kyaml v0.7.1

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/functions/examples/validator-kubeval
go 1.15
go 1.16
require (
github.com/instrumenta/kubeval v0.0.0-20190918223246-8d013ec9fc56

View File

@@ -1,5 +1,5 @@
module sigs.k8s.io/kustomize/functions/examples/validator-resource-requests
go 1.15
go 1.16
require sigs.k8s.io/kustomize/kyaml v0.7.1

View File

@@ -1,6 +1,6 @@
module sigs.k8s.io/kustomize/hack
go 1.15
go 1.16
require (
github.com/golangci/golangci-lint v1.23.8

View File

@@ -75,7 +75,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=

View File

@@ -1,12 +1,15 @@
#!/bin/bash
#!/usr/bin/env bash
# If no argument is given -> Downloads the most recently released
# kustomize binary to your current working directory.
# (e.g. 'install_kustomize.sh')
#
# If an argument is given -> Downloads the specified version of the
# kustomize binary to your current working directory.
# (e.g. 'install_kustomize.sh 3.8.2')
# If one argument is given ->
# If that argument is in the format of #.#.#, downloads the specified
# version of the kustomize binary to your current working directory.
# If that argument is something else, downloads the most recently released
# kustomize binary to the specified directory.
# (e.g. 'install_kustomize.sh 3.8.2' or 'install_kustomize.sh $(go env GOPATH)/bin')
#
# If two arguments are given -> Downloads the specified version of the
# kustomize binary to the specified directory.
@@ -14,22 +17,62 @@
#
# Fails if the file already exists.
curl_timeout=600
version=""
release_url=https://api.github.com/repos/kubernetes-sigs/kustomize/releases
if [ -n "$1" ]; then
version=v$1
release_url=${release_url}/tags/kustomize%2F$version
fi
set -e
where=$PWD
if [ -n "$2" ]; then
where=$2
release_url=https://api.github.com/repos/kubernetes-sigs/kustomize/releases
if [ -n "$1" ]; then
if [[ "$1" =~ ^[0-9]+(\.[0-9]+){2}$ ]]; then
version=v$1
release_url=${release_url}/tags/kustomize%2F$version
elif [ -n "$2" ]; then
echo "The first argument should be the requested version."
exit 1
else
where="$1"
fi
fi
if [ -f $where/kustomize ]; then
echo "A file named $where/kustomize already exists (remove it first)."
if [ -n "$2" ]; then
where="$2"
fi
if ! test -d "$where"; then
echo "$where does not exist. Create it first."
exit 1
fi
# Emulates `readlink -f` behavior, as this is not available by default on MacOS
# See: https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac
function readlink_f {
TARGET_FILE=$1
cd "$(dirname "$TARGET_FILE")"
TARGET_FILE=$(basename "$TARGET_FILE")
# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
TARGET_FILE=$(readlink "$TARGET_FILE")
cd "$(dirname "$TARGET_FILE")"
TARGET_FILE=$(readlink "$TARGET_FILE")
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
PHYS_DIR=$(pwd -P)
RESULT=$PHYS_DIR/$TARGET_FILE
echo "$RESULT"
}
where="$(readlink_f $where)/"
if [ -f "${where}kustomize" ]; then
echo "${where}kustomize exists. Remove it first."
exit 1
elif [ -d "${where}kustomize" ]; then
echo "${where}kustomize exists and is a directory. Remove it first."
exit 1
fi
@@ -43,9 +86,9 @@ function cleanup {
rm -rf "$tmpDir"
}
trap cleanup EXIT
trap cleanup EXIT ERR
pushd $tmpDir >& /dev/null
pushd "$tmpDir" >& /dev/null
opsys=windows
arch=amd64
@@ -55,23 +98,24 @@ elif [[ "$OSTYPE" == darwin* ]]; then
opsys=darwin
fi
curl -m $curl_timeout -s $release_url |\
RELEASE_URL=$(curl -s $release_url |\
grep browser_download.*${opsys}_${arch} |\
cut -d '"' -f 4 |\
sort -V | tail -n 1 |\
xargs curl -m $curl_timeout -sLO
sort -V | tail -n 1)
if [ -e ./kustomize_v*_${opsys}_amd64.tar.gz ]; then
tar xzf ./kustomize_v*_${opsys}_amd64.tar.gz
else
echo "Error: kustomize binary with the version ${version#v} does not exist!"
exit 1
if [ ! -n "$RELEASE_URL" ]; then
echo "Version $version does not exist."
exit 1
fi
cp ./kustomize $where
curl -sLO $RELEASE_URL
tar xzf ./kustomize_v*_${opsys}_${arch}.tar.gz
cp ./kustomize "$where"
popd >& /dev/null
$where/kustomize version
${where}kustomize version
echo kustomize installed to $where
echo "kustomize installed to ${where}kustomize"

View File

@@ -14,8 +14,8 @@ RUN CGO_ENABLED=0 GO111MODULE=on go build \
# only copy binary
FROM alpine
# install dependencies
RUN apk add git openssh
COPY --from=builder /build/kustomize /app/
RUN apk add --no-cache git openssh
COPY --from=builder /build/kustomize/kustomize /app/
WORKDIR /app
ENV PATH "$PATH:/app"
ENTRYPOINT ["/app/kustomize"]

View File

@@ -98,20 +98,18 @@ func NewCmdBuild(
return err
},
}
AddFlagOutputPath(cmd.Flags())
AddFunctionFlags(cmd.Flags())
AddFunctionBasicsFlags(cmd.Flags())
AddFlagLoadRestrictor(cmd.Flags())
AddFlagEnablePlugins(cmd.Flags())
AddFlagReorderOutput(cmd.Flags())
AddFlagEnableManagedbyLabel(cmd.Flags())
AddFlagAllowResourceIdChanges(cmd.Flags())
return cmd
}
// Validate validates build command args and flags.
func Validate(args []string) (err error) {
func Validate(args []string) error {
if len(args) > 1 {
return fmt.Errorf(
"specify one path to " +
@@ -122,8 +120,7 @@ func Validate(args []string) (err error) {
} else {
theArgs.kustomizationPath = args[0]
}
err = validateFlagLoadRestrictor()
if err != nil {
if err := validateFlagLoadRestrictor(); err != nil {
return err
}
return validateFlagReorderOutput()

View File

@@ -7,13 +7,7 @@ import (
"github.com/spf13/pflag"
)
func AddFunctionFlags(set *pflag.FlagSet) {
set.BoolVar(
&theFlags.fnOptions.EnableExec, "enable-exec", false, /*do not change!*/
"enable support for exec functions -- note: exec functions run arbitrary code -- do not use for untrusted configs!!! (Alpha)")
set.BoolVar(
&theFlags.fnOptions.EnableStar, "enable-star", false,
"enable support for starlark functions. (Alpha)")
func AddFunctionBasicsFlags(set *pflag.FlagSet) {
set.BoolVar(
&theFlags.fnOptions.Network, "network", false,
"enable network access for functions that declare it")
@@ -27,3 +21,13 @@ func AddFunctionFlags(set *pflag.FlagSet) {
&theFlags.fnOptions.Env, "env", "e", []string{},
"a list of environment variables to be used by functions")
}
func AddFunctionAlphaEnablementFlags(set *pflag.FlagSet) {
set.BoolVar(
&theFlags.fnOptions.EnableExec, "enable-exec", false,
"enable support for exec functions (raw executables); "+
"do not use for untrusted configs! (Alpha)")
set.BoolVar(
&theFlags.fnOptions.EnableStar, "enable-star", false,
"enable support for starlark functions. (Alpha)")
}

View File

@@ -46,7 +46,11 @@ func (w Writer) WriteIndividualFiles(dirPath string, m resmap.ResMap) error {
}
func (w Writer) write(path, fName string, res *resource.Resource) error {
yml, err := yaml.Marshal(res.Map())
m, err := res.Map()
if err != nil {
return err
}
yml, err := yaml.Marshal(m)
if err != nil {
return err
}

View File

@@ -6,6 +6,7 @@ package commands
import (
"flag"
"io"
"os"
"github.com/spf13/cobra"
@@ -21,6 +22,14 @@ import (
"sigs.k8s.io/kustomize/kustomize/v4/commands/version"
)
func makeBuildCommand(fSys filesys.FileSystem, w io.Writer) *cobra.Command {
cmd := build.NewCmdBuild(
fSys, build.MakeHelp(konfig.ProgramName, "build"), w)
// Add build flags that don't appear in kubectl.
build.AddFunctionAlphaEnablementFlags(cmd.Flags())
return cmd
}
// NewDefaultCommand returns the default (aka root) command for kustomize command.
func NewDefaultCommand() *cobra.Command {
fSys := filesys.MakeFsOnDisk()
@@ -34,11 +43,11 @@ Manages declarative configuration of Kubernetes.
See https://sigs.k8s.io/kustomize
`,
}
pvd := provider.NewDefaultDepProvider()
c.AddCommand(
completion.NewCommand(),
build.NewCmdBuild(
fSys, build.MakeHelp(konfig.ProgramName, "build"), stdOut),
makeBuildCommand(fSys, stdOut),
edit.NewCmdEdit(
fSys, pvd.GetFieldValidator(), pvd.GetKunstructuredFactory()),
create.NewCmdCreate(fSys, pvd.GetKunstructuredFactory()),

View File

@@ -1,15 +1,15 @@
module sigs.k8s.io/kustomize/kustomize/v4
go 1.15
go 1.16
require (
github.com/google/go-cmp v0.5.2
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
sigs.k8s.io/kustomize/api v0.8.2
sigs.k8s.io/kustomize/cmd/config v0.9.4
sigs.k8s.io/kustomize/kyaml v0.10.12
sigs.k8s.io/kustomize/api v0.8.4
sigs.k8s.io/kustomize/cmd/config v0.9.7
sigs.k8s.io/kustomize/kyaml v0.10.15
sigs.k8s.io/yaml v1.2.0
)
@@ -17,3 +17,5 @@ exclude (
sigs.k8s.io/kustomize/api v0.2.0
sigs.k8s.io/kustomize/cmd/config v0.2.0
)
replace sigs.k8s.io/kustomize/api => ../api

View File

@@ -110,6 +110,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
@@ -164,7 +165,6 @@ github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FW
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -242,7 +242,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -273,9 +272,8 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -295,18 +293,14 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/kustomize/api v0.8.2 h1:YrFKmhRgpkqa3Il2Geh+BflrRwN4vvyLH+DlKmREteU=
sigs.k8s.io/kustomize/api v0.8.2/go.mod h1:yHAkpXolD4n+m7YoostNPYTEyDjwdNNGRQ0Mitxihrc=
sigs.k8s.io/kustomize/cmd/config v0.9.4 h1:YCOYjb2+TboucGP3IUgK7/v80FrfSE1aqtq7br08D9c=
sigs.k8s.io/kustomize/cmd/config v0.9.4/go.mod h1:sYbSd8IfPkx2VFWXuA/pqxHLwUItg+vi75lQa5DXKNE=
sigs.k8s.io/kustomize/kyaml v0.10.11 h1:7KzaMS6TtwhGQW6hkw8umC2zFtSGAQPdAMagfautnkw=
sigs.k8s.io/kustomize/kyaml v0.10.11/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/kyaml v0.10.12 h1:1lUvuSVETthZspl4X+Xdic/FgPAfxUbc7MKtc1vcO2c=
sigs.k8s.io/kustomize/kyaml v0.10.12/go.mod h1:uD7q6dIrZV02fBV6D7FOgdOBcLfUmhG3ExxDIGbUM3w=
sigs.k8s.io/kustomize/cmd/config v0.9.7 h1:xxvL/np/zYHVuCH1tNFehlyEtSW5oXjoI6ycejiyOwQ=
sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -3,6 +3,8 @@
run:
deadline: 5m
skip-dirs:
- yaml/internal/k8sgen/pkg
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
@@ -12,7 +14,7 @@ linters:
- bodyclose
- deadcode
- depguard
- dogsled
# - dogsled # uncomment after upgrading golangci-lint (Issue #3663)
- dupl
- errcheck
- gochecknoinits
@@ -39,7 +41,7 @@ linters:
- unparam
- unused
- varcheck
- whitespace
# - whitespace # uncomment after upgrading golangci-lint (Issue #3663)
linters-settings:

View File

@@ -9,8 +9,11 @@ export PATH := $(MYGOBIN):$(PATH)
$(MYGOBIN)/addlicense:
go get github.com/google/addlicense
# TODO: Issue #3663
# Update this version of golangci-lint
# Ideally use same version as in {REPO}/hack/go.mod
$(MYGOBIN)/golangci-lint:
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.17.0
$(MYGOBIN)/k8scopy:
( cd ../cmd/k8scopy; go install . )
@@ -18,7 +21,7 @@ $(MYGOBIN)/k8scopy:
$(MYGOBIN)/stringer:
go get golang.org/x/tools/cmd/stringer
all: license fix vet fmt test lint tidy
all: license fix vet fmt test tidy
k8sGenDir := yaml/internal/k8sgen/pkg
@@ -30,9 +33,9 @@ clean:
lint: $(MYGOBIN)/golangci-lint
$(MYGOBIN)/golangci-lint \
--skip-dirs $(k8sGenDir) \
run ./...
license: $(MYGOBIN)/addlicense
$(MYGOBIN)/addlicense \
-y 2021 \

View File

@@ -0,0 +1,166 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package command
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type CLIMode byte
const (
StandaloneEnabled CLIMode = iota
StandaloneDisabled
)
// Build returns a cobra.Command to run a function.
//
// The cobra.Command reads the input from STDIN, invokes the provided processor,
// and then writes the output to STDOUT.
//
// The cobra.Command has a boolean `--stack` flag to print stack traces on failure.
//
// By default, invoking the returned cobra.Command with arguments triggers "standalone" mode.
// In this mode:
// - The first argument must be the name of a file containing the FunctionConfig.
// - The remaining arguments must be filenames containing input resources for ResourceList.Items.
// - The argument "-", if present, will cause resources to be read from STDIN as well.
// The output will be a raw stream of resources (not wrapped in a List type).
// Example usage: `cat input1.yaml | go run main.go config.yaml input2.yaml input3.yaml -`
//
// If mode is `StandaloneDisabled`, all arguments are ignored, and STDIN must contain
// a Kubernetes List type. To pass a function config in this mode, use a ResourceList as the input.
// The output will be of the same type as the input (e.g. ResourceList).
// Example usage: `cat resource_list.yaml | go run main.go`
//
// By default, any error returned by the ResourceListProcessor will be printed to STDERR.
// Set noPrintError to true to suppress this.
func Build(p framework.ResourceListProcessor, mode CLIMode, noPrintError bool) *cobra.Command {
cmd := cobra.Command{}
var printStack bool
cmd.Flags().BoolVar(&printStack, "stack", false, "print the stack trace on failure")
cmd.Args = cobra.MinimumNArgs(0)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.RunE = func(cmd *cobra.Command, args []string) error {
var readers []io.Reader
rw := &kio.ByteReadWriter{
Writer: cmd.OutOrStdout(),
KeepReaderAnnotations: true,
}
if len(args) > 0 && mode == StandaloneEnabled {
// Don't keep the reader annotations if we are in standalone mode
rw.KeepReaderAnnotations = false
// Don't wrap the resources in a resourceList -- we are in
// standalone mode and writing to stdout to be applied
rw.NoWrap = true
for i := range args {
// the first argument is the resourceList.FunctionConfig
if i == 0 {
var err error
if rw.FunctionConfig, err = functionConfigFromFile(args[0]); err != nil {
return errors.Wrap(err)
}
continue
}
if args[i] == "-" {
readers = append([]io.Reader{cmd.InOrStdin()}, readers...)
} else {
readers = append(readers, &deferredFileReader{path: args[i]})
}
}
} else {
// legacy kustomize plugin input style
legacyPlugin := os.Getenv("KUSTOMIZE_PLUGIN_CONFIG_STRING")
if legacyPlugin != "" && rw.FunctionConfig != nil {
if err := yaml.Unmarshal([]byte(legacyPlugin), rw.FunctionConfig); err != nil {
return err
}
}
readers = append(readers, cmd.InOrStdin())
}
rw.Reader = io.MultiReader(readers...)
err := framework.Execute(p, rw)
if err != nil && !noPrintError {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v", err)
}
// print the stack if requested
if s := errors.GetStack(err); printStack && s != "" {
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), s)
}
return err
}
return &cmd
}
// AddGenerateDockerfile adds a "gen" subcommand to create a Dockerfile for building
// the function into a container image.
// The gen command takes one argument: the directory where the Dockerfile will be created.
//
// go run main.go gen DIR/
func AddGenerateDockerfile(cmd *cobra.Command) {
gen := &cobra.Command{
Use: "gen [DIR]",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return ioutil.WriteFile(filepath.Join(args[0], "Dockerfile"), []byte(`FROM golang:1.15-alpine as builder
ENV CGO_ENABLED=0
WORKDIR /go/src/
COPY . .
RUN go build -tags netgo -ldflags '-w' -v -o /usr/local/bin/function ./
FROM alpine:latest
COPY --from=builder /usr/local/bin/function /usr/local/bin/function
CMD ["function"]
`), 0600)
},
}
cmd.AddCommand(gen)
}
func functionConfigFromFile(file string) (*yaml.RNode, error) {
b, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to read configuration file %q", file)
}
fc, err := yaml.Parse(string(b))
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to parse configuration file %q", file)
}
return fc, nil
}
type deferredFileReader struct {
path string
srcReader io.Reader
}
func (fr *deferredFileReader) Read(dest []byte) (int, error) {
if fr.srcReader == nil {
src, err := ioutil.ReadFile(fr.path)
if err != nil {
return 0, errors.WrapPrefixf(err, "unable to read input file %s", fr.path)
}
fr.srcReader = bytes.NewReader(src)
}
return fr.srcReader.Read(dest)
}

View File

@@ -0,0 +1,164 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package command_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestCommand_dockerfile(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(d)
// create a function
cmd := command.Build(&framework.SimpleProcessor{}, command.StandaloneEnabled, false)
// add the Dockerfile generator
command.AddGenerateDockerfile(cmd)
// generate the Dockerfile
cmd.SetArgs([]string{"gen", d})
if !assert.NoError(t, cmd.Execute()) {
t.FailNow()
}
b, err := ioutil.ReadFile(filepath.Join(d, "Dockerfile"))
if !assert.NoError(t, err) {
t.FailNow()
}
expected := `FROM golang:1.15-alpine as builder
ENV CGO_ENABLED=0
WORKDIR /go/src/
COPY . .
RUN go build -tags netgo -ldflags '-w' -v -o /usr/local/bin/function ./
FROM alpine:latest
COPY --from=builder /usr/local/bin/function /usr/local/bin/function
CMD ["function"]
`
if !assert.Equal(t, expected, string(b)) {
t.FailNow()
}
}
// TestCommand_standalone tests the framework works in standalone mode
func TestCommand_standalone(t *testing.T) {
var config struct {
A string `json:"a" yaml:"a"`
}
fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
items = append(items, yaml.MustParse(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
`))
for i := range items {
err := items[i].PipeE(yaml.SetAnnotation("a", config.A))
if err != nil {
return nil, err
}
}
return items, nil
}
cmdFn := func() *cobra.Command {
return command.Build(&framework.SimpleProcessor{Filter: kio.FilterFunc(fn), Config: &config}, command.StandaloneEnabled, false)
}
tc := frameworktestutil.CommandResultsChecker{Command: cmdFn}
tc.Assert(t)
}
func TestCommand_standalone_stdin(t *testing.T) {
var config struct {
A string `json:"a" yaml:"a"`
}
p := &framework.SimpleProcessor{
Config: &config,
Filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
items = append(items, yaml.MustParse(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
`))
for i := range items {
err := items[i].PipeE(yaml.SetAnnotation("a", config.A))
if err != nil {
return nil, err
}
}
return items, nil
}),
}
cmd := command.Build(p, command.StandaloneEnabled, false)
cmd.SetIn(bytes.NewBufferString(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
spec:
replicas: 1
`))
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetArgs([]string{filepath.Join("testdata", "standalone", "config.yaml"), "-"})
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
a: 'b'
spec:
replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
a: 'b'
`), strings.TrimSpace(out.String()))
}

View File

@@ -0,0 +1,90 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package command contains a builder for creating cobra.Commands based on configuration functions
// written using the kyaml function framework. The commands this package generates can be used as
// standalone executables or as part of a configuration management pipeline that complies with the
// Configuration Functions Specification (e.g. Kustomize generators or transformers):
// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
//
// Example standalone usage
//
// Function template input:
//
// # config.yaml -- this is the input to the template
// apiVersion: example.com/v1alpha1
// kind: Example
// Key: a
// Value: b
//
// Additional function inputs:
//
// # patch.yaml -- this will be applied as a patch
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// namespace: default
// annotations:
// patch-key: patch-value
//
// Manually run the function:
//
// # build the function
// $ go build example-fn/
//
// # run the function
// $ ./example-fn config.yaml patch.yaml
//
// Go implementation
//
// // example-fn/main.go
// func main() {
// // Define the template used to generate resources
// p := framework.TemplateProcessor{
// MergeResources: true, // apply inputs as patches to the template output
// TemplateData: new(struct {
// Key string `json:"key" yaml:"key"`
// Value string `json:"value" yaml:"value"`
// }),
// ResourceTemplates: []framework.ResourceTemplate{{
// Templates: framework.StringTemplates(`
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// namespace: default
// annotations:
// {{ .Key }}: {{ .Value }}
// `)}},
// }
//
// // Run the command
// if err := command.Build(p, command.StandaloneEnabled, true).Execute(); err != nil {
// fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
// os.Exit(1)
// }
// }
//
// Example function implementation using command.Build with flag input
//
// func main() {
// var value string
// fn := func(rl *framework.ResourceList) error {
// for i := range rl.Items {
// // set the annotation on each resource item
// if err := rl.Items[i].PipeE(yaml.SetAnnotation("value", value)); err != nil {
// return err
// }
// }
// return nil
// }
// cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneEnabled, false)
// cmd.Flags().StringVar(&value, "value", "", "annotation value")
//
// if err := cmd.Execute(); err != nil {
// fmt.Println(err)
// os.Exit(1)
// }
// }
package command

View File

@@ -0,0 +1,382 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package command_test
import (
"bytes"
"fmt"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const service = "Service"
// ExampleBuild_modify implements a function that sets an annotation on each resource.
// The annotation value is configured via ResourceList.FunctionConfig.
func ExampleBuild_modify() {
// create a struct matching the structure of ResourceList.FunctionConfig to hold its data
var config struct {
Data map[string]string `yaml:"data"`
}
fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range items {
// set the annotation on each resource item
err := items[i].PipeE(yaml.SetAnnotation("value", config.Data["value"]))
if err != nil {
return nil, err
}
}
return items, nil
}
p := framework.SimpleProcessor{Filter: kio.FilterFunc(fn), Config: &config}
cmd := command.Build(p, command.StandaloneDisabled, false)
// for testing purposes only -- normally read from stdin when Executing
cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
- apiVersion: v1
kind: Service
metadata:
name: foo
functionConfig:
apiVersion: v1
kind: ConfigMap
data:
value: baz
`))
// run the command
if err := cmd.Execute(); err != nil {
panic(err)
}
// Output:
// apiVersion: config.kubernetes.io/v1alpha1
// kind: ResourceList
// items:
// - apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// annotations:
// value: 'baz'
// - apiVersion: v1
// kind: Service
// metadata:
// name: foo
// annotations:
// value: 'baz'
// functionConfig:
// apiVersion: v1
// kind: ConfigMap
// data:
// value: baz
}
// ExampleBuild_generateReplace generates a resource from a FunctionConfig.
// If the resource already exists, it replaces the resource with a new copy.
func ExampleBuild_generateReplace() {
// function API definition which will be parsed from the ResourceList.FunctionConfig
// read from stdin
type Spec struct {
Name string `yaml:"name,omitempty"`
}
type ExampleServiceGenerator struct {
Spec Spec `yaml:"spec,omitempty"`
}
functionConfig := &ExampleServiceGenerator{}
// function implementation -- generate a Service resource
p := &framework.SimpleProcessor{
Config: functionConfig,
Filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
var newNodes []*yaml.RNode
for i := range items {
meta, err := items[i].GetMeta()
if err != nil {
return nil, err
}
// something we already generated, remove it from the list so we regenerate it
if meta.Name == functionConfig.Spec.Name &&
meta.Kind == service &&
meta.APIVersion == "v1" {
continue
}
newNodes = append(newNodes, items[i])
}
// generate the resource
n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
name: %s
`, functionConfig.Spec.Name))
if err != nil {
return nil, err
}
newNodes = append(newNodes, n)
return newNodes, nil
}),
}
cmd := command.Build(p, command.StandaloneDisabled, false)
// for testing purposes only -- normally read from stdin when Executing
cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
functionConfig:
apiVersion: example.com/v1alpha1
kind: ExampleServiceGenerator
spec:
name: bar
`))
// run the command
if err := cmd.Execute(); err != nil {
panic(err)
}
// Output:
// apiVersion: config.kubernetes.io/v1alpha1
// kind: ResourceList
// items:
// - apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// - apiVersion: v1
// kind: Service
// metadata:
// name: bar
// functionConfig:
// apiVersion: example.com/v1alpha1
// kind: ExampleServiceGenerator
// spec:
// name: bar
}
// ExampleBuild_generateUpdate generates a resource, updating the previously generated
// copy rather than replacing it.
//
// Note: This will keep manual edits to the previously generated copy.
func ExampleBuild_generateUpdate() {
// function API definition which will be parsed from the ResourceList.FunctionConfig
// read from stdin
type Spec struct {
Name string `yaml:"name,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty"`
}
type ExampleServiceGenerator struct {
Spec Spec `yaml:"spec,omitempty"`
}
functionConfig := &ExampleServiceGenerator{}
// function implementation -- generate or update a Service resource
fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
var found bool
for i := range items {
meta, err := items[i].GetMeta()
if err != nil {
return nil, err
}
// something we already generated, reconcile it to make sure it matches what
// is specified by the FunctionConfig
if meta.Name == functionConfig.Spec.Name &&
meta.Kind == service &&
meta.APIVersion == "v1" {
// set some values
for k, v := range functionConfig.Spec.Annotations {
err := items[i].PipeE(yaml.SetAnnotation(k, v))
if err != nil {
return nil, err
}
}
found = true
break
}
}
if found {
return items, nil
}
// generate the resource if not found
n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
name: %s
`, functionConfig.Spec.Name))
if err != nil {
return nil, err
}
for k, v := range functionConfig.Spec.Annotations {
err := n.PipeE(yaml.SetAnnotation(k, v))
if err != nil {
return nil, err
}
}
items = append(items, n)
return items, nil
}
p := &framework.SimpleProcessor{Config: functionConfig, Filter: kio.FilterFunc(fn)}
cmd := command.Build(p, command.StandaloneDisabled, false)
// for testing purposes only -- normally read from stdin when Executing
cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
- apiVersion: v1
kind: Service
metadata:
name: bar
functionConfig:
apiVersion: example.com/v1alpha1
kind: ExampleServiceGenerator
spec:
name: bar
annotations:
a: b
`))
// run the command
if err := cmd.Execute(); err != nil {
panic(err)
}
// Output:
// apiVersion: config.kubernetes.io/v1alpha1
// kind: ResourceList
// items:
// - apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// - apiVersion: v1
// kind: Service
// metadata:
// name: bar
// annotations:
// a: 'b'
// functionConfig:
// apiVersion: example.com/v1alpha1
// kind: ExampleServiceGenerator
// spec:
// name: bar
// annotations:
// a: b
}
// ExampleBuild_validate validates that all Deployment resources have the replicas field set.
// If any Deployments do not contain spec.replicas, then the function will return results
// which will be set on ResourceList.results
func ExampleBuild_validate() {
fn := func(rl *framework.ResourceList) error {
// validation results
var validationResults []framework.ResultItem
// validate that each Deployment resource has spec.replicas set
for i := range rl.Items {
// only check Deployment resources
meta, err := rl.Items[i].GetMeta()
if err != nil {
return err
}
if meta.Kind != "Deployment" {
continue
}
// lookup replicas field
r, err := rl.Items[i].Pipe(yaml.Lookup("spec", "replicas"))
if err != nil {
return err
}
// check replicas not specified
if r != nil {
continue
}
validationResults = append(validationResults, framework.ResultItem{
Severity: framework.Error,
Message: "field is required",
ResourceRef: meta,
Field: framework.Field{
Path: "spec.replicas",
SuggestedValue: "1",
},
})
}
if len(validationResults) > 0 {
rl.Result = &framework.Result{
Name: "replicas-validator",
Items: validationResults,
}
}
return rl.Result
}
cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneDisabled, true)
// for testing purposes only -- normally read from stdin when Executing
cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
`))
// run the command
if err := cmd.Execute(); err != nil {
// normally exit 1 here
}
// Output:
// apiVersion: config.kubernetes.io/v1alpha1
// kind: ResourceList
// items:
// - apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// results:
// name: replicas-validator
// items:
// - message: field is required
// severity: error
// resourceRef:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// field:
// path: spec.replicas
// suggestedValue: "1"
}

View File

@@ -1,66 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"bytes"
"text/template"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
)
// PatchContainersWithString executes t as a template and patches each container in each resource
// with the result.
func PatchContainersWithString(resources []*yaml.RNode, t string, input interface{}, containers ...string) error {
resourcePatch := template.Must(template.New("containers").Parse(t))
return PatchContainersWithTemplate(resources, resourcePatch, input, containers...)
}
// PatchContainersWithTemplate executes t and patches each container in each resource
// with the result.
func PatchContainersWithTemplate(resources []*yaml.RNode, t *template.Template, input interface{}, containers ...string) error {
var b bytes.Buffer
if err := t.Execute(&b, input); err != nil {
return errors.Wrap(err)
}
patch, err := yaml.Parse(b.String())
if err != nil {
return errors.WrapPrefixf(err, b.String())
}
return PatchContainers(resources, patch, containers...)
}
// PatchContainers applies patch to each container in each resource.
func PatchContainers(resources []*yaml.RNode, patch *yaml.RNode, containers ...string) error {
names := sets.String{}
names.Insert(containers...)
for i := range resources {
containers, err := resources[i].Pipe(yaml.Lookup("spec", "template", "spec", "containers"))
if err != nil {
return errors.Wrap(err)
}
if containers == nil {
continue
}
err = containers.VisitElements(func(node *yaml.RNode) error {
f := node.Field("name")
if f == nil {
return nil
}
if names.Len() > 0 && !names.Has(yaml.GetValue(f.Value)) {
return nil
}
_, err := merge2.Merge(patch, node, yaml.MergeOptions{})
return errors.Wrap(err)
})
if err != nil {
return errors.Wrap(err)
}
}
return nil
}

View File

@@ -1,106 +1,39 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package framework contains a framework for writing functions in go. The function spec
// Package framework contains a framework for writing functions in Go. The function specification
// is defined at: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
//
// Functions are executables which generate, modify, delete or validate Kubernetes resources.
// Functions are executables that generate, modify, delete or validate Kubernetes resources.
// They are often used used to implement abstractions ("kind: JavaSpringBoot") and
// cross-cutting logic ("kind: SidecarInjector").
//
// Functions may be run as standalone executables or invoked as part of an orchestrated
// pipeline (e.g. kustomize).
//
// Example standalone usage
// Example function implementation using framework.SimpleProcessor with a struct input
//
// Function template input:
// type Spec struct {
// Value string `yaml:"value,omitempty"`
// }
// type Example struct {
// Spec Spec `yaml:"spec,omitempty"`
// }
//
// # config.yaml -- this is the input to the template
// apiVersion: example.com/v1alpha1
// kind: Example
// Key: a
// Value: b
// func runFunction(rlSource *kio.ByteReadWriter) error {
// functionConfig := &Example{}
//
// Additional function inputs:
// fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
// for i := range rl.Items {
// // modify the items...
// }
// return items, nil
// }
//
// # patch.yaml -- this will be applied as a patch
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// namespace: default
// annotations:
// patch-key: patch-value
//
// Manually run the function:
//
// # build the function
// $ go build example-fn/
//
// # run the function using the
// $ ./example-fn config.yaml patch.yaml
//
// Go implementation
//
// // example-fn/main.go
// func main() {
//
// // Define the template used to generate resources
// tc := framework.TemplateCommand{
// Merge: true, // apply inputs as patches to the template output
// API: &struct {
// Key string `json:"key" yaml:"key"`
// Value string `json:"value" yaml:"value"`
// }{},
// Template: template.Must(template.New("example").Parse(`
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// namespace: default
// annotations:
// {{ .Key }}: {{ .Value }}
// `))}
//
// // Run the command
// if err := tc.GetCommand().Execute(); err != nil {
// fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
// os.Exit(1)
// }
// }
//
// More Examples
//
// Example function implementation using framework.Command with flag input
//
// var value string
// resourceList := &framework.ResourceList{}
// cmd := framework.Command(resourceList, func() error {
// for i := range resourceList.Items {
// // modify the items...
// }
// return nil
// })
// cmd.Flags().StringVar(&value, "value", "", "annotation value")
// if err := cmd.Execute(); err != nil { return err }
//
// Example function implementation using framework.ResourceList with a struct input
//
// type Spec struct {
// Value string `yaml:"value,omitempty"`
// }
// type Example struct {
// Spec Spec `yaml:"spec,omitempty"`
// }
// functionConfig := &Example{}
//
// rl := framework.ResourceList{FunctionConfig: functionConfig}
// if err := rl.Read(); err != nil { return err }
//
// for i := range rl.Items {
// // modify the items...
// }
// if err := rl.Write(); err != nil { return err }
// p := framework.SimpleProcessor{Config: functionConfig, Filter: kio.FilterFunc(fn)}
// err := framework.Execute(p, rlSource)
// return errors.Wrap(err)
// }
//
// Architecture
//
@@ -108,7 +41,7 @@
// as output. The function itself may be configured through a functionConfig
// (ResourceList.FunctionConfig).
//
// Example Function Input:
// Example function input:
//
// kind: ResourceList
// items:
@@ -140,10 +73,9 @@
// The framework takes care of serializing and deserializing the ResourceList.
//
// Generated ResourceList.functionConfig -- ConfigMaps
//
// Functions may also be specified imperatively and run using:
//
// config run DIR/ --image image/containing/function:impl -- value=foo
// kpt fn run DIR/ --image image/containing/function:impl -- value=foo
//
// When run imperatively, a ConfigMap is generated for the functionConfig, and the command
// arguments are set as ConfigMap data entries.
@@ -165,15 +97,9 @@
//
// Configuring Functions
//
// Functions may be configured through a functionConfig (i.e. a client side custom resource),
// Functions may be configured through a functionConfig (i.e. a client-side custom resource),
// or through flags (which the framework parses from a ConfigMap provided as input).
//
// When using framework.Command, any flags registered on the cobra.Command will be parsed
// from the functionConfig input if they are defined as functionConfig.data entries.
//
// When using framework.ResourceList, any flags set on the ResourceList.Flags will be
// parsed from the functionConfig input if they are defined as functionConfig.data entries.
//
// Functions may also access environment variables set by the caller.
//
// Building a container image for the function

View File

@@ -2,8 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
// Package main contains an example using the the framework.
// The example annotates all resources in the input with the value provided as a flag.
//
// To execute the function, run:
//
// $ cat input/cm.yaml | go run ./main.go --value=foo
//
// To generate the Dockerfile for the function image run:
//
// $ go run ./main.go .
// $ go run ./main.go gen ./
package main

View File

@@ -0,0 +1,9 @@
# Copyright 2021 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
kind: ConfigMap
apiVersion: v1
metadata:
name: tester
data:
some: data

View File

@@ -4,28 +4,30 @@
package main
import (
"fmt"
"os"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func main() {
var value string
resourceList := framework.ResourceList{}
cmd := framework.Command(&resourceList, func() error {
for i := range resourceList.Items {
fn := func(rl *framework.ResourceList) error {
for i := range rl.Items {
// set the annotation on each resource item
err := resourceList.Items[i].PipeE(yaml.SetAnnotation("value", value))
if err != nil {
if err := rl.Items[i].PipeE(yaml.SetAnnotation("value", value)); err != nil {
return err
}
}
return nil
})
}
cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneEnabled, false)
cmd.Flags().StringVar(&value, "value", "", "annotation value")
if err := cmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@@ -1,4 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package example2

View File

@@ -1,241 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//go:generate go run github.com/markbates/pkger/cmd/pkger -o fn/framework/example2
package example2
import (
"bytes"
"strings"
"testing"
"github.com/markbates/pkger"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
)
func TestTemplate(t *testing.T) {
type API struct {
Image string `json:"image" yaml:"image"`
}
tpl, err := framework.TemplatesFromDir(pkger.Dir("/fn/framework/example2/data/templates"))(nil)
require.NoError(t, err)
cmd := framework.TemplateCommand{
API: &API{},
Templates: tpl,
}.GetCommand()
var in, out bytes.Buffer
cmd.SetIn(&in)
cmd.SetOut(&out)
in.WriteString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: v1
kind: Service
functionConfig:
image: baz
`)
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: v1
kind: Service
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
namespace: bar
annotations:
config.kubernetes.io/index: '0'
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
image: baz
`), strings.TrimSpace(out.String()))
}
func TestPatchTemplate(t *testing.T) {
type API struct {
Replicas int `json:"replicas" yaml:"replicas"`
}
cmd := framework.TemplateCommand{
API: &API{},
PatchTemplatesFn: framework.PatchTemplatesFromDir(
framework.PT{
Dir: pkger.Dir("/fn/framework/example2/data/patches"),
Selector: func() *framework.Selector {
return &framework.Selector{Names: []string{"foo"}}
},
},
),
}.GetCommand()
var in, out bytes.Buffer
cmd.SetIn(&in)
cmd.SetOut(&out)
in.WriteString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: foo
image: baz
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
replicas: 5
`)
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations:
config.kubernetes.io/index: '0'
spec:
template:
spec:
containers:
- name: foo
image: baz
replicas: 5
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
replicas: 5
`), strings.TrimSpace(out.String()))
}
func TestContainerPatchTemplate(t *testing.T) {
type API struct {
Key string `json:"key" yaml:"key"`
Value string `json:"value" yaml:"value"`
}
cmd := framework.TemplateCommand{
API: &API{},
PatchContainerTemplatesFn: framework.ContainerPatchTemplatesFromDir(
framework.CPT{
Dir: pkger.Dir("/fn/framework/example2/data/container-patches"),
Selector: func() *framework.Selector {
return &framework.Selector{Names: []string{"foo"}}
},
},
),
}.GetCommand()
var in, out bytes.Buffer
cmd.SetIn(&in)
cmd.SetOut(&out)
in.WriteString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: a
- name: b
- name: c
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
key: Hello
value: World
`)
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: a
env:
key: Hello
value: World
- name: b
env:
key: Hello
value: World
- name: c
env:
key: Hello
value: World
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
key: Hello
value: World
`), strings.TrimSpace(out.String()))
}

View File

@@ -1,12 +0,0 @@
// Code generated by pkger; DO NOT EDIT.
// +build !skippkger
package example2
import (
"github.com/markbates/pkger"
"github.com/markbates/pkger/pkging/mem"
)
var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec985d53dac01a80ff4ae6bd8e64f369c84c2f8c968816aca004e8749c4db284954d9649362a38fcf7330902adf5b4da0f7b3ccd15d937fbeef73e3c937ba0e984e7e0dc434ee3bc31b3f306e5caacc8054fe89228b3054e58f9fa8866e08032e50951ae3312e35489f91c8ba99267a1f2bd6419dac99c67e223165370bedb8f0c5d9c107060533ce22138f01187331c13a98a4a214f05a6692e311a6438a32497263c933282239ac6124e23e936a3a27c3e2d0292a544905cea919c175948caf4098d8b0c0bca5309e752d9680364b8c0594c04380032f438173f3ddd0e16e1149c4fd080cf32f40566041c9115e4a1d02338e72938907221d13417983112494121247c8329c30123124da5a0a02c92421c4e09c8e0f11665242fdb8d78d88879d9f87a657370d28231198ec87cfb7c4172b1cdd9851e6574785494c3bb87676d4f07d37433975f39111eeff0e8e7b2959837121e558d0c4896d36a29d5866ac26ab59261b29ef1770fb4a34c526592e184dcf26ca6903b9ccc19d194080bac3c9c2f92edcdcb8d5c37565e93f2372202535685d2f551fdb6ba0c395d12700cd4b464487844c0d15463dfb00dd5dcaf22578256c91ad2d43da4eea1fd0bd57074cb319b0ddb6e1aa8695bfb7bc876100219687e15958bbd5ef77c51757f446ec0b12c5d356568a71c1ccdd20dcb6c2219ba8ca63370b46a7b0938aa653775192e69048e8a1092c1db3d0eafaee63842e020197a51d92892a1ffc5045c36cbbf2af270968363cb702068520ea54fc2b21355b36da499860cddbc8c3475cd3235cb345732741e5545c86c22b5b9a9ba9df34a86c3e7571d5e5d15699193089c4f484632fa5c1d8129c96a64d5c87a1bc892615e4de31e3ecee2e71eb697f16b2543f966b380739c9154ec7adc355b0de7b7717317690892cc1916a4b1d18967f0f451d286aaaa666ca06ae8d62fd0748259fe639caa5b9caa1b9ceaba868c9fc06935f297d054552ddbda704fb755c3b65403bd359a2a137af798a8bb23b77eb986e6d344dbe1697d7c1fe8f4b07d5fe3e94be8ac6bd7b8796ddcfcd76bbfc510b417ee51a0bb6698b0141f9fc79deb83bbb3be7b89bdcbb8af0fe87878528c873d162e5c371af678a09f2c3fc4fcbaddea9ef75ab1b8f0d8f5d83797e3be7a32f60645e4b104fb83c51975ddd06b5d63ef5274a8717b18cf59908eac437a10636d609e51779f2c0e8abe3630dbc76ab38c47492b8ffc4bab7d2cf6db87a63ff2efd471df6d4ecedfbd833f06cb67a9e5ff86506ab550d642590be5bf4bf8d7d5c8cdbf48f5fb12717c2a612b8daafe9ad2a8ff46692c47fe0bd2681ab6850ce3ad11b396c67f01294f5ef2bfa289cbd01b5c9fc53c6e1f9fb0d0bb9b8fb4d672a38b97c9e036f0d8f568d8892743f407b570b3123f10c35db5bfab8646ad86b51ad66af8ef727c07a2d791c36d7f4a44e68c2f5ee2874f666cf8a919f66b0aa2f9fb04b11a792d88b520fe7f82e5e98bfe571c711a7acc1f0f4f96d86f1667d49d86c7eef2833eba3b4cc43c48ceadf6fb5ee98e37c45759909e9f06fe008dfcde34f2de579f2083a425c6173c1e27cd9b6d79d8bd1df95d76465d3a1ab64f43dd65a3253f6d1fba68ecabb781d742e38bb27c10b78fbbb763bf53b55595bdee4d90f6a6d83759987e113f44dff4b58e1fc4d857a7636dfbb9f324f05be9fa7327ffa1d7aeaf2da97da7f69db781a5d57f000000ffff010000ffff6b4abe49e0240000`)))

File diff suppressed because it is too large Load Diff

View File

@@ -4,34 +4,22 @@
package framework
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// ResourceList reads the function input and writes the function output.
//
// Adheres to the spec: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
// ResourceList is a Kubernetes list type used as the primary data interchange format
// in the Configuration Functions Specification:
// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
// This framework facilitates building functions that receive and emit ResourceLists,
// as required by the specification.
type ResourceList struct {
// FunctionConfig is the ResourceList.functionConfig input value. If FunctionConfig
// is set to a value such as a struct or map[string]interface{} before ResourceList.Read()
// is called, then the functionConfig will be parsed into that value.
// If it is nil, the functionConfig will be set to a map[string]interface{}
// before it is parsed.
// FunctionConfig is the ResourceList.functionConfig input value.
//
// e.g. given the function input:
// e.g. given the input:
//
// kind: ResourceList
// functionConfig:
@@ -39,11 +27,13 @@ type ResourceList struct {
// spec:
// foo: var
//
// FunctionConfig will contain the Example unmarshalled into its value.
FunctionConfig interface{}
// FunctionConfig will contain the RNodes for the Example:
// kind: Example
// spec:
// foo: var
FunctionConfig *yaml.RNode `yaml:"functionConfig" json:"functionConfig"`
// Items is the ResourceList.items input and output value. Items will be set by
// ResourceList.Read() and written by ResourceList.Write().
// Items is the ResourceList.items input and output value.
//
// e.g. given the function input:
//
@@ -55,565 +45,102 @@ type ResourceList struct {
// ...
//
// Items will be a slice containing the Deployment and Service resources
Items []*yaml.RNode
// Mutating functions will alter this field during processing.
Items []*yaml.RNode `yaml:"items" json:"items"`
// Result is ResourceList.result output value. Result will be written by
// ResourceList.Write()
Result *Result
// DisableStandalone if set will not support standalone mode
DisableStandalone bool
// Args are the command args used for standalone mode
Args []string
// Flags are an optional set of flags to parse the ResourceList.functionConfig.data.
// If non-nil, ResourceList.Read() will set the flag value for each flag name matching
// a ResourceList.functionConfig.data map entry.
//
// e.g. given the function input:
//
// kind: ResourceList
// functionConfig:
// data:
// foo: bar
// a: b
//
// The flags --a=b and --foo=bar will be set in Flags.
Flags *pflag.FlagSet
// Reader is used to read the function input (ResourceList).
// Defaults to os.Stdin.
Reader io.Reader
// Writer is used to write the function output (ResourceList)
// Defaults to os.Stdout.
Writer io.Writer
// ReadWriter reads function input and writes function output
// If set, it will take precedence over the Reader and Writer fields
ReadWriter *kio.ByteReadWriter
// NoPrintError if set will prevent the error from being printed
NoPrintError bool
Command *cobra.Command
// Result is ResourceList.result output value.
// Validating functions can optionally use this field to communicate structured
// validation error data to downstream functions.
Result *Result `yaml:"results" json:"results"`
}
func (r *ResourceList) defaultReadWriter() *kio.ByteReadWriter {
rw := kio.ByteReadWriter{
KeepReaderAnnotations: true,
Reader: r.Reader,
Writer: r.Writer,
}
// Default reader source precedence: r.Reader > r.Command.InOrStdin > os.Stdin
if rw.Reader == nil {
if r.Command != nil {
rw.Reader = r.Command.InOrStdin()
} else {
rw.Reader = os.Stdin
}
}
// Default writer source precedence: r.Writer > r.Command.OutOrStdout > os.Stdout
if rw.Writer == nil {
if r.Command != nil {
rw.Writer = r.Command.OutOrStdout()
} else {
rw.Writer = os.Stdout
}
}
return &rw
}
// Read reads the ResourceList
func (r *ResourceList) Read() error {
// legacy kustomize plugin input style
legacyPlugin := os.Getenv("KUSTOMIZE_PLUGIN_CONFIG_STRING")
if legacyPlugin != "" && r.FunctionConfig != nil {
err := yaml.Unmarshal([]byte(legacyPlugin), r.FunctionConfig)
if err != nil {
return err
}
}
// parse the inputs from the args
var readStdinStandalone bool
if len(r.Args) > 0 && !r.DisableStandalone {
// write the files as input
var buf bytes.Buffer
for i := range r.Args {
// the first argument is the resourceList.FunctionConfig and will be parsed
// separately later, the rest of the arguments are the resourceList.items
if i == 0 {
continue
}
if r.Args[i] == "-" {
// Read stdin separately
readStdinStandalone = true
continue
}
b, err := ioutil.ReadFile(r.Args[i])
if err != nil {
return errors.WrapPrefixf(err, "unable to read input file %s", r.Args[i])
}
buf.WriteString(string(b))
buf.WriteString("\n---\n")
}
r.Reader = &buf
}
// Default the ReadWriter and ensure related fields are in a consistent state
if r.ReadWriter == nil {
r.ReadWriter = r.defaultReadWriter()
}
r.Reader = r.ReadWriter.Reader
r.Writer = r.ReadWriter.Writer
// parse the resourceList.FunctionConfig from the first arg
if len(r.Args) > 0 && !r.DisableStandalone {
// Don't keep the reader annotations if we are in standalone mode
r.ReadWriter.KeepReaderAnnotations = false
// Don't wrap the resources in a resourceList -- we are in
// standalone mode and writing to stdout to be applied
r.ReadWriter.NoWrap = true
b, err := ioutil.ReadFile(r.Args[0])
if err != nil {
return errors.WrapPrefixf(err, "unable to read configuration file %s", r.Args[0])
}
fc, err := yaml.Parse(string(b))
if err != nil {
return errors.WrapPrefixf(err, "unable to parse configuration file %s", r.Args[0])
}
// use this as the function config used to configure the function
r.ReadWriter.FunctionConfig = fc
}
var err error
r.Items, err = r.ReadWriter.Read()
if err != nil {
return errors.Wrap(err)
}
if readStdinStandalone {
var in io.Reader
if r.Command != nil {
in = r.Command.InOrStdin()
} else {
in = os.Stdin
}
br := kio.ByteReader{Reader: in}
items, err := br.Read()
if err != nil {
return errors.Wrap(err)
}
// stdin always comes first so files are patches
r.Items = append(items, r.Items...)
}
// parse the functionConfig
return func() error {
if r.ReadWriter.FunctionConfig == nil {
// no function config exists
return nil
}
if r.FunctionConfig == nil {
// set directly from r.rw
r.FunctionConfig = r.ReadWriter.FunctionConfig
} else {
// unmarshal the functionConfig into the provided value
err := yaml.Unmarshal([]byte(r.ReadWriter.FunctionConfig.MustString()), r.FunctionConfig)
if err != nil {
return errors.Wrap(err)
}
}
// set the functionConfig values as flags so they are easy to access
if r.Flags == nil || !r.Flags.HasFlags() {
return nil
}
// flags are always set from the "data" field
data, err := r.ReadWriter.FunctionConfig.Pipe(yaml.Lookup("data"))
if err != nil || data == nil {
return err
}
return data.VisitFields(func(node *yaml.MapNode) error {
f := r.Flags.Lookup(node.Key.YNode().Value)
if f == nil {
return nil
}
return f.Value.Set(node.Value.YNode().Value)
})
}()
}
// Write writes the ResourceList
func (r *ResourceList) Write() error {
// set the ResourceList.results for validating functions
if r.Result != nil {
if len(r.Result.Items) > 0 {
b, err := yaml.Marshal(r.Result)
if err != nil {
return errors.Wrap(err)
}
y, err := yaml.Parse(string(b))
if err != nil {
return errors.Wrap(err)
}
r.ReadWriter.Results = y
}
}
// write the results
return r.ReadWriter.Write(r.Items)
}
// Command returns a cobra.Command to run a function.
// ResourceListProcessor is implemented by configuration functions built with this framework
// to conform to the Configuration Functions Specification:
// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
// To invoke a processor, pass it to framework.Execute, which will also handle ResourceList IO.
//
// The cobra.Command will use the provided ResourceList to Read() the input,
// run the provided function, and then Write() the output.
//
// The returned cobra.Command will have a "gen" subcommand which can be used to generate
// a Dockerfile to build the function into a container image
//
// go run main.go gen DIR/
func Command(resourceList *ResourceList, function Function) *cobra.Command {
cmd := cobra.Command{}
resourceList.Command = &cmd
AddGenerateDockerfile(&cmd)
var printStack bool
cmd.RunE = func(cmd *cobra.Command, args []string) error {
resourceList.Reader = cmd.InOrStdin()
resourceList.Writer = cmd.OutOrStdout()
resourceList.Flags = cmd.Flags()
resourceList.Args = args
err := WrapInResourceListIO(resourceList, function)()
if err != nil && !resourceList.NoPrintError {
fmt.Fprintf(cmd.ErrOrStderr(), "%v", err)
}
// print the stack if requested
if s := errors.GetStack(err); printStack && s != "" {
fmt.Fprintln(cmd.ErrOrStderr(), s)
}
return err
}
cmd.Flags().BoolVar(&printStack, "stack", false, "print the stack trace on failure")
cmd.Args = cobra.MinimumNArgs(0)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
return &cmd
// This framework provides several ready-to-use ResourceListProcessors, including
// SimpleProcessor, VersionedAPIProcessor and TemplateProcessor.
// You can also build your own by implementing this interface.
type ResourceListProcessor interface {
Process(rl *ResourceList) error
}
// TemplateCommand provides a cobra command to invoke a template
type TemplateCommand struct {
// API is the function API provide to the template as input
API interface{}
// ResourceListProcessorFunc converts a compatible function to a ResourceListProcessor.
type ResourceListProcessorFunc func(rl *ResourceList) error
// Template is a go template to render and is appended to Templates.
Template *template.Template
// Templates is a list of templates to render.
Templates []*template.Template
// TemplatesFn returns a list of templates
TemplatesFn func(*ResourceList) ([]*template.Template, error)
// PatchTemplates is a list of templates to render into Patches and apply.
PatchTemplates []PatchTemplate
// PatchTemplateFn returns a list of templates to render into Patches and apply.
// PatchTemplateFn is called after the ResourceList has been parsed.
PatchTemplatesFn func(*ResourceList) ([]PatchTemplate, error)
// PatchContainerTemplates applies patches to matching container fields
PatchContainerTemplates []ContainerPatchTemplate
// PatchContainerTemplates returns a list of PatchContainerTemplates
PatchContainerTemplatesFn func(*ResourceList) ([]ContainerPatchTemplate, error)
// TemplateFiles list of templates to read from disk which are appended
// to Templates.
TemplatesFiles []string
// MergeResources if set to true will apply input resources
// as patches to the templates
MergeResources bool
// PreProcess is run on the ResourceList before the template is invoked
PreProcess func(*ResourceList) error
PreProcessFilters []kio.Filter
// PostProcess is run on the ResourceList after the template is invoked
PostProcess func(*ResourceList) error
PostProcessFilters []kio.Filter
// Process makes ResourceListProcessorFunc implement the ResourceListProcessor interface.
func (p ResourceListProcessorFunc) Process(rl *ResourceList) error {
return p(rl)
}
// ContainerPatchTemplate defines a patch to be applied to containers
type ContainerPatchTemplate struct {
PatchTemplate
ContainerNames []string
}
func (tc TemplateCommand) doTemplate(t *template.Template, rl *ResourceList) error {
// invoke the template
var b bytes.Buffer
err := t.Execute(&b, tc.API)
if err != nil {
return errors.WrapPrefixf(err, "failed to render template %v", t.DefinedTemplates())
}
// split the resources so the error messaging is better
for _, s := range strings.Split(b.String(), "\n---\n") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
nodes, err := (&kio.ByteReader{Reader: bytes.NewBufferString(s)}).Read()
if err != nil {
// create the debug string
lines := strings.Split(s, "\n")
for j := range lines {
lines[j] = fmt.Sprintf("%03d %s", j+1, lines[j])
}
s = strings.Join(lines, "\n")
return errors.WrapPrefixf(err, "failed to parse rendered template into a resource:\n%s\n", s)
}
if tc.MergeResources {
// apply inputs as patches -- add the
rl.Items = append(nodes, rl.Items...)
} else {
// add to the inputs list
rl.Items = append(rl.Items, nodes...)
}
}
return nil
}
// Defaulter is implemented by APIs to have Default invoked
// Defaulter is implemented by APIs to have Default invoked.
// The standard application is to create a type to hold your FunctionConfig data, and
// implement Defaulter on that type. All of the framework's processors will invoke Default()
// on your type after unmarshalling the FunctionConfig data into it.
type Defaulter interface {
Default() error
}
// Validator is implemented by APIs to have Validate invoked
// Validator is implemented by APIs to have Validate invoked.
// The standard application is to create a type to hold your FunctionConfig data, and
// implement Validator on that type. All of the framework's processors will invoke Validate()
// on your type after unmarshalling the FunctionConfig data into it.
type Validator interface {
Validate() error
}
func (tc *TemplateCommand) doPreProcess(rl *ResourceList) error {
// do any preprocessing
if tc.PreProcess != nil {
if err := tc.PreProcess(rl); err != nil {
return err
}
// Execute is the entrypoint for invoking configuration functions built with this framework
// from code. See framework/command#Build for a Cobra-based command-line equivalent.
// Execute reads a ResourceList from the given source, passes it to a ResourceListProcessor,
// and then writes the result to the target.
// STDIN and STDOUT will be used if no reader or writer respectively is provided.
func Execute(p ResourceListProcessor, rlSource *kio.ByteReadWriter) error {
// Prepare the resource list source
if rlSource == nil {
rlSource = &kio.ByteReadWriter{KeepReaderAnnotations: true}
}
if rlSource.Reader == nil {
rlSource.Reader = os.Stdin
}
if rlSource.Writer == nil {
rlSource.Writer = os.Stdout
}
// TODO: test this
if tc.PreProcessFilters != nil {
for i := range tc.PreProcessFilters {
fltr := tc.PreProcessFilters[i]
var err error
rl.Items, err = fltr.Filter(rl.Items)
if err != nil {
return err
}
}
}
return nil
}
func (tc *TemplateCommand) doMerge(rl *ResourceList) error {
// Read the input
rl := ResourceList{}
var err error
if tc.MergeResources {
rl.Items, err = filters.MergeFilter{}.Filter(rl.Items)
if rl.Items, err = rlSource.Read(); err != nil {
return errors.Wrap(err)
}
return err
}
rl.FunctionConfig = rlSource.FunctionConfig
func (tc *TemplateCommand) doPostProcess(rl *ResourceList) error {
// finish up
if tc.PostProcess != nil {
if err := tc.PostProcess(rl); err != nil {
return err
}
}
// TODO: test this
if tc.PostProcessFilters != nil {
for i := range tc.PostProcessFilters {
fltr := tc.PostProcessFilters[i]
var err error
rl.Items, err = fltr.Filter(rl.Items)
if err != nil {
return err
}
}
}
return nil
}
retErr := p.Process(&rl)
func (tc *TemplateCommand) doTemplates(rl *ResourceList) error {
if tc.Template != nil {
tc.Templates = append(tc.Templates, tc.Template)
}
// TODO: test this
if tc.TemplatesFn != nil {
t, err := tc.TemplatesFn(rl)
// Write the results
// Set the ResourceList.results for validating functions
if rl.Result != nil && len(rl.Result.Items) > 0 {
b, err := yaml.Marshal(rl.Result)
if err != nil {
return err
return errors.Wrap(err)
}
tc.Templates = append(tc.Templates, t...)
}
for i := range tc.TemplatesFiles {
tbytes, err := ioutil.ReadFile(tc.TemplatesFiles[i])
y, err := yaml.Parse(string(b))
if err != nil {
return errors.WrapPrefixf(err, "unable to read template file")
return errors.Wrap(err)
}
t, err := template.New("files").Parse(string(tbytes))
if err != nil {
return errors.WrapPrefixf(err, "unable to parse template files %v", tc.TemplatesFiles)
}
tc.Templates = append(tc.Templates, t)
rlSource.Results = y
}
for i := range tc.Templates {
if err := tc.doTemplate(tc.Templates[i], rl); err != nil {
return err
}
}
return nil
}
func (tc *TemplateCommand) doPatchTemplates(rl *ResourceList) error {
if tc.PatchTemplatesFn != nil {
pt, err := tc.PatchTemplatesFn(rl)
if err != nil {
return err
}
tc.PatchTemplates = append(tc.PatchTemplates, pt...)
}
for i := range tc.PatchTemplates {
if err := tc.PatchTemplates[i].Apply(rl); err != nil {
return err
}
}
return nil
}
func (tc *TemplateCommand) doPatchContainerTemplates(rl *ResourceList) error {
if tc.PatchContainerTemplatesFn != nil {
ct, err := tc.PatchContainerTemplatesFn(rl)
if err != nil {
return err
}
tc.PatchContainerTemplates = append(tc.PatchContainerTemplates, ct...)
}
for i := range tc.PatchContainerTemplates {
ct := tc.PatchContainerTemplates[i]
matches, err := ct.Selector.GetMatches(rl)
if err != nil {
return err
}
err = PatchContainersWithTemplate(matches, ct.Template, rl.FunctionConfig, ct.ContainerNames...)
if err != nil {
return err
}
}
return nil
}
// GetCommand returns a new cobra command
func (tc TemplateCommand) GetCommand() *cobra.Command {
rl := &ResourceList{
FunctionConfig: tc.API,
NoPrintError: true,
}
return Command(rl, func() error { return tc.Execute(rl) })
}
func (tc TemplateCommand) Execute(rl *ResourceList) error {
if d, ok := rl.FunctionConfig.(Defaulter); ok {
if err := d.Default(); err != nil {
return err
}
}
if v, ok := rl.FunctionConfig.(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
if err := tc.doPreProcess(rl); err != nil {
return err
}
if err := tc.doTemplates(rl); err != nil {
return err
}
if err := tc.doPatchTemplates(rl); err != nil {
return err
}
if err := tc.doPatchContainerTemplates(rl); err != nil {
return err
}
if err := tc.doMerge(rl); err != nil {
return err
}
if err := tc.doPostProcess(rl); err != nil {
if err := rlSource.Write(rl.Items); err != nil {
return err
}
return nil
return retErr
}
// AddGenerateDockerfile adds a "gen" subcommand to create a Dockerfile for building
// the function as a container.
func AddGenerateDockerfile(cmd *cobra.Command) {
gen := &cobra.Command{
Use: "gen",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return ioutil.WriteFile(filepath.Join(args[0], "Dockerfile"), []byte(`FROM golang:1.13-stretch
ENV CGO_ENABLED=0
WORKDIR /go/src/
COPY . .
RUN go build -v -o /usr/local/bin/function ./
FROM alpine:latest
COPY --from=0 /usr/local/bin/function /usr/local/bin/function
CMD ["function"]
`), 0600)
},
}
cmd.AddCommand(gen)
}
func WrapInResourceListIO(rl *ResourceList, function Function) Function {
return func() error {
if err := rl.Read(); err != nil {
return err
}
retErr := function()
if err := rl.Write(); err != nil {
return err
}
return retErr
}
}
// Filters returns a function which returns the provided Filters
func Filters(fltrs ...kio.Filter) func(*ResourceList) []kio.Filter {
return func(*ResourceList) []kio.Filter {
return fltrs
}
// Filter executes the given kio.Filter and replaces the ResourceList's items with the result.
// This can be used to help implement ResourceListProcessors. See SimpleProcessor for example.
func (rl *ResourceList) Filter(api kio.Filter) error {
var err error
rl.Items, err = api.Filter(rl.Items)
return errors.Wrap(err)
}

View File

@@ -5,362 +5,84 @@ package framework_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"text/template"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
"sigs.k8s.io/kustomize/kyaml/testutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestCommand_dockerfile(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(d)
// create a function
resourceList := &framework.ResourceList{}
cmd := framework.Command(resourceList, func() error { return nil })
// generate the Dockerfile
cmd.SetArgs([]string{"gen", d})
if !assert.NoError(t, cmd.Execute()) {
t.FailNow()
}
b, err := ioutil.ReadFile(filepath.Join(d, "Dockerfile"))
if !assert.NoError(t, err) {
t.FailNow()
}
expected := `FROM golang:1.13-stretch
ENV CGO_ENABLED=0
WORKDIR /go/src/
COPY . .
RUN go build -v -o /usr/local/bin/function ./
FROM alpine:latest
COPY --from=0 /usr/local/bin/function /usr/local/bin/function
CMD ["function"]
`
if !assert.Equal(t, expected, string(b)) {
t.FailNow()
}
}
// TestCommand_standalone tests the framework works in standalone mode
func TestCommand_standalone(t *testing.T) {
// TODO: make this test pass on windows -- currently failure seems spurious
testutil.SkipWindows(t)
type api = struct {
A string `json:"a" yaml:"a"`
}
var config api
resourceList := &framework.ResourceList{FunctionConfig: &config}
cmdFn := func() *cobra.Command {
return framework.Command(resourceList, func() error {
resourceList.Items = append(resourceList.Items, yaml.MustParse(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
`))
for i := range resourceList.Items {
err := resourceList.Items[i].PipeE(yaml.SetAnnotation("a", config.A))
if err != nil {
return err
}
}
return nil
})
}
frameworktestutil.ResultsChecker{Command: cmdFn}.Assert(t)
}
func TestCommand_standalonestdin(t *testing.T) {
// TODO: make this test pass on windows -- currently failure seems spurious
testutil.SkipWindows(t)
type api = struct {
A string `json:"a" yaml:"a"`
}
var config api
resourceList := &framework.ResourceList{FunctionConfig: &config}
cmd := framework.Command(resourceList, func() error {
resourceList.Items = append(resourceList.Items, yaml.MustParse(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
`))
for i := range resourceList.Items {
err := resourceList.Items[i].PipeE(yaml.SetAnnotation("a", config.A))
if err != nil {
return err
}
func TestExecute_Result(t *testing.T) {
p := framework.ResourceListProcessorFunc(func(rl *framework.ResourceList) error {
err := &framework.Result{
Name: "Incompatible config",
Items: []framework.ResultItem{{
Message: "bad value for replicas",
Severity: framework.Error,
ResourceRef: yaml.ResourceMeta{
TypeMeta: yaml.TypeMeta{APIVersion: "v1", Kind: "Deployment"},
ObjectMeta: yaml.ObjectMeta{
NameMeta: yaml.NameMeta{Name: "tester", Namespace: "default"},
},
},
Field: framework.Field{
Path: ".spec.Replicas",
CurrentValue: "0",
SuggestedValue: "3",
},
File: framework.File{
Path: "/path/to/deployment.yaml",
Index: 0,
},
}},
}
return nil
rl.Result = err
return err
})
cmd.SetIn(bytes.NewBufferString(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
spec:
replicas: 1
`))
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetArgs([]string{filepath.Join("testdata", "command", "config.yaml"), "-"})
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
a: 'b'
spec:
replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
a: 'b'
`), strings.TrimSpace(out.String()))
}
func TestCommand_PatchTemplateFn(t *testing.T) {
// TODO: make this test pass on windows -- currently failure seems spurious
testutil.SkipWindows(t)
type api = struct {
Spec struct {
A string `json:"a" yaml:"a"`
} `json:"spec" yaml:"spec"`
}
var config api
cmd := framework.TemplateCommand{
API: &config,
PatchTemplatesFn: func(_ *framework.ResourceList) ([]framework.PatchTemplate, error) {
return []framework.PatchTemplate{{
Selector: &framework.Selector{Names: []string{config.Spec.A}},
Template: template.Must(template.New("test").Parse(`
metadata:
annotations:
baz: buz
`)),
}}, nil
},
}.GetCommand()
cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
spec:
replicas: 1
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
spec:
replicas: 1
functionConfig:
apiVersion: example.com/v1alpha1
kind: Example
spec:
a: "bar1"
`))
var out bytes.Buffer
cmd.SetOut(&out)
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
baz: buz
config.kubernetes.io/index: '0'
spec:
replicas: 1
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
spec:
replicas: 1
functionConfig:
apiVersion: example.com/v1alpha1
kind: Example
spec:
a: "bar1"
`), strings.TrimSpace(out.String()))
}
func TestCommand_PatchContainerTemplatesFn(t *testing.T) {
// TODO: make this test pass on windows -- currently failure seems spurious
testutil.SkipWindows(t)
type api = struct {
Spec struct {
A string `json:"a" yaml:"a"`
} `json:"spec" yaml:"spec"`
}
var config api
cmd := framework.TemplateCommand{
API: &config,
PatchContainerTemplatesFn: func(_ *framework.ResourceList) ([]framework.ContainerPatchTemplate, error) {
return []framework.ContainerPatchTemplate{{
PatchTemplate: framework.PatchTemplate{
Selector: &framework.Selector{Names: []string{config.Spec.A}},
Template: template.Must(template.New("test").Parse(`
env:
key: Foo
value: Bar
`))},
}}, nil
},
}.GetCommand()
cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
spec:
template:
spec:
containers:
- name: foo
- name: bar
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
spec:
template:
spec:
containers:
- name: foo
- name: bar
functionConfig:
apiVersion: example.com/v1alpha1
kind: Example
spec:
a: "bar1"
`))
var out bytes.Buffer
cmd.SetOut(&out)
require.NoError(t, cmd.Execute())
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar1
namespace: default
annotations:
foo: bar1
spec:
template:
spec:
containers:
- name: foo
env:
key: Foo
value: Bar
- name: bar
env:
key: Foo
value: Bar
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar2
namespace: default
annotations:
foo: bar2
spec:
template:
spec:
containers:
- name: foo
- name: bar
functionConfig:
apiVersion: example.com/v1alpha1
kind: Example
spec:
a: "bar1"
`), strings.TrimSpace(out.String()))
out := new(bytes.Buffer)
source := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
kind: ResourceList
apiVersion: config.kubernetes.io/v1alpha1
items:
- kind: Deployment
apiVersion: v1
metadata:
name: tester
namespace: default
spec:
replicas: 0
`), Writer: out}
err := framework.Execute(p, source)
assert.EqualError(t, err, "[error] v1/Deployment/default/tester .spec."+
"Replicas: bad value for replicas")
assert.Equal(t, 1, err.(*framework.Result).ExitCode())
assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
apiVersion: v1
metadata:
name: tester
namespace: default
spec:
replicas: 0
results:
name: Incompatible config
items:
- message: bad value for replicas
severity: error
resourceRef:
apiVersion: v1
kind: Deployment
metadata:
name: tester
namespace: default
field:
path: .spec.Replicas
currentValue: "0"
suggestedValue: "3"
file:
path: /path/to/deployment.yaml`, strings.TrimSpace(out.String()))
}

View File

@@ -14,13 +14,17 @@ import (
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// ResultsChecker tests a function by running it with predefined inputs and comparing
// CommandResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
type ResultsChecker struct {
type CommandResultsChecker struct {
// TestDataDirectory is the directory containing the testdata subdirectories.
// ResultsChecker will recurse into each test directory and run the Command
// CommandResultsChecker will recurse into each test directory and run the Command
// if the directory contains both the ConfigInputFilename and at least one
// of ExpectedOutputFilname or ExpectedErrorFilename.
// Defaults to "testdata"
@@ -37,12 +41,12 @@ type ResultsChecker struct {
// ExpectedOutputFilename is the file with the expected output of the function
// Defaults to "expected.yaml". Directories containing neither this file
// nore ExpectedErrorFilename will be skipped.
// nor ExpectedErrorFilename will be skipped.
ExpectedOutputFilename string
// ExpectedErrorFilename is the file containing part of an expected error message
// Defaults to "error.yaml". Directories containing neither this file
// nore ExpectedOutputFilname will be skipped.
// nor ExpectedOutputFilename will be skipped.
ExpectedErrorFilename string
// Command provides the function to run.
@@ -51,10 +55,12 @@ type ResultsChecker struct {
// UpdateExpectedFromActual if set to true will write the actual results to the
// expected testdata files. This is useful for updating test data.
UpdateExpectedFromActual bool
testsCasesRun int
}
// Assert asserts the results for functions
func (rc ResultsChecker) Assert(t *testing.T) bool {
func (rc *CommandResultsChecker) Assert(t *testing.T) bool {
if rc.TestDataDirectory == "" {
rc.TestDataDirectory = "testdata"
}
@@ -71,10 +77,8 @@ func (rc ResultsChecker) Assert(t *testing.T) bool {
rc.InputFilenameGlob = "input*.yaml"
}
_ = filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
if err != nil {
t.FailNow()
}
err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if !info.IsDir() {
// skip non-directories
return nil
@@ -82,21 +86,21 @@ func (rc ResultsChecker) Assert(t *testing.T) bool {
rc.compare(t, path)
return nil
})
require.NoError(t, err)
require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)
return true
}
func (rc ResultsChecker) compare(t *testing.T, path string) {
func (rc *CommandResultsChecker) compare(t *testing.T, path string) {
// cd into the directory so we can test functions that refer
// local files by relative paths
d, err := os.Getwd()
if !assert.NoError(t, err) {
t.FailNow()
}
defer func() { _ = os.Chdir(d) }()
if !assert.NoError(t, os.Chdir(path)) {
t.FailNow()
}
require.NoError(t, err)
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
// make sure this directory contains test data
_, err = os.Stat(rc.ConfigInputFilename)
@@ -106,22 +110,19 @@ func (rc ResultsChecker) compare(t *testing.T, path string) {
}
args := []string{rc.ConfigInputFilename}
expectedOutput, expectedError := rc.getExpected(t)
expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
if expectedError == "" && expectedOutput == "" {
// missing expected
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
require.NoError(t, err)
// run the test
t.Run(path, func(t *testing.T) {
rc.testsCasesRun += 1
if rc.InputFilenameGlob != "" {
inputs, err := filepath.Glob(rc.InputFilenameGlob)
if !assert.NoError(t, err) {
t.FailNow()
}
require.NoError(t, err)
args = append(args, inputs...)
}
@@ -133,64 +134,196 @@ func (rc ResultsChecker) compare(t *testing.T, path string) {
err = cmd.Execute()
// Compae the results
if expectedError != "" && !assert.Error(t, err, actualOutput.String()) {
if !rc.UpdateExpectedFromActual {
t.FailNow()
// Update the fixtures if configured to
if rc.UpdateExpectedFromActual {
if actualError.String() != "" {
assert.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, actualError.Bytes(), 0600))
}
if actualOutput.String() != "" {
assert.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
}
return
}
if expectedError == "" && !assert.NoError(t, err, actualError.String()) {
if !rc.UpdateExpectedFromActual {
t.FailNow()
}
// Compare the results
if expectedError != "" {
// We expected an error, so make sure there was one and it matches
require.Error(t, err, actualOutput.String())
require.Contains(t,
standardizeSpacing(actualError.String()),
standardizeSpacing(expectedError), actualOutput.String())
} else {
// We didn't expect an error, and the output should match
require.NoError(t, err, actualError.String())
require.Equal(t,
standardizeSpacing(expectedOutput),
standardizeSpacing(actualOutput.String()), actualError.String())
}
if !assert.Equal(t,
strings.TrimSpace(expectedOutput),
strings.TrimSpace(actualOutput.String()), actualError.String()) {
if !rc.UpdateExpectedFromActual {
t.FailNow()
}
// update test results
assert.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
})
}
func standardizeSpacing(s string) string {
// remove extra whitespace and convert Windows line endings
return strings.ReplaceAll(strings.TrimSpace(s), "\r\n", "\n")
}
// ProcessorResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
type ProcessorResultsChecker struct {
// TestDataDirectory is the directory containing the testdata subdirectories.
// CommandResultsChecker will recurse into each test directory and run the Processor
// if the directory contains both the InputFilename and at least one
// of ExpectedOutputFilename or ExpectedErrorFilename.
// Defaults to "testdata"
TestDataDirectory string
// InputFilename is the name of the file containing the ResourceList input.
// Directories without this file will be skipped.
// Defaults to "input.yaml"
InputFilename string
// ExpectedOutputFilename is the file with the expected output of the function
// Defaults to "expected.yaml". Directories containing neither this file
// nor ExpectedErrorFilename will be skipped.
ExpectedOutputFilename string
// ExpectedErrorFilename is the file containing part of an expected error message
// Defaults to "error.yaml". Directories containing neither this file
// nor ExpectedOutputFilename will be skipped.
ExpectedErrorFilename string
// Processor returns a ResourceListProcessor to run.
Processor func() framework.ResourceListProcessor
// UpdateExpectedFromActual if set to true will write the actual results to the
// expected testdata files. This is useful for updating test data.
UpdateExpectedFromActual bool
testsCasesRun int
}
// Assert asserts the results for functions
func (rc *ProcessorResultsChecker) Assert(t *testing.T) bool {
if rc.TestDataDirectory == "" {
rc.TestDataDirectory = "testdata"
}
if rc.InputFilename == "" {
rc.InputFilename = "input.yaml"
}
if rc.ExpectedOutputFilename == "" {
rc.ExpectedOutputFilename = "expected.yaml"
}
if rc.ExpectedErrorFilename == "" {
rc.ExpectedErrorFilename = "error.yaml"
}
err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if !info.IsDir() {
// skip non-directories
return nil
}
if !assert.Contains(t,
strings.TrimSpace(actualError.String()),
strings.TrimSpace(expectedError), actualOutput.String()) {
if !rc.UpdateExpectedFromActual {
t.FailNow()
rc.compare(t, path)
return nil
})
require.NoError(t, err)
require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)
return true
}
func (rc *ProcessorResultsChecker) compare(t *testing.T, path string) {
// cd into the directory so we can test functions that refer
// local files by relative paths
d, err := os.Getwd()
require.NoError(t, err)
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
// make sure this directory contains test data
_, err = os.Stat(rc.InputFilename)
if os.IsNotExist(err) {
// missing input
return
}
require.NoError(t, err)
expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
if expectedError == "" && expectedOutput == "" {
// missing expected
return
}
// run the test
t.Run(path, func(t *testing.T) {
rc.testsCasesRun += 1
actualOutput := bytes.NewBuffer([]byte{})
rlBytes, err := ioutil.ReadFile(rc.InputFilename)
require.NoError(t, err)
rw := kio.ByteReadWriter{
Reader: bytes.NewBuffer(rlBytes),
Writer: actualOutput,
}
err = framework.Execute(rc.Processor(), &rw)
// Update the fixtures if configured to
if rc.UpdateExpectedFromActual {
if err != nil {
require.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, []byte(err.Error()), 0600))
}
// update test results
assert.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, actualError.Bytes(), 0600))
if len(actualOutput.String()) > 0 {
require.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
}
return
}
// Compare the results
if expectedError != "" {
// We expected an error, so make sure there was one and it matches
require.Error(t, err, actualOutput.String())
require.Contains(t,
standardizeSpacing(err.Error()),
standardizeSpacing(expectedError), actualOutput.String())
} else {
// We didn't expect an error, and the output should match
require.NoError(t, err)
require.Equal(t,
standardizeSpacing(expectedOutput),
standardizeSpacing(actualOutput.String()))
}
})
}
// getExpected reads the expected results and error files
func (rc ResultsChecker) getExpected(t *testing.T) (string, string) {
func getExpected(t *testing.T, expectedOutFilename, expectedErrFilename string) (string, string) {
// read the expected results
var expectedOutput, expectedError string
if rc.ExpectedOutputFilename != "" {
_, err := os.Stat(rc.ExpectedOutputFilename)
if expectedOutFilename != "" {
_, err := os.Stat(expectedOutFilename)
if !os.IsNotExist(err) && err != nil {
t.FailNow()
}
if err == nil {
// only read the file if it exists
b, err := ioutil.ReadFile(rc.ExpectedOutputFilename)
b, err := ioutil.ReadFile(expectedOutFilename)
if !assert.NoError(t, err) {
t.FailNow()
}
expectedOutput = string(b)
}
}
if rc.ExpectedErrorFilename != "" {
_, err := os.Stat(rc.ExpectedErrorFilename)
if expectedErrFilename != "" {
_, err := os.Stat(expectedErrFilename)
if !os.IsNotExist(err) && err != nil {
t.FailNow()
}
if err == nil {
// only read the file if it exists
b, err := ioutil.ReadFile(rc.ExpectedErrorFilename)
b, err := ioutil.ReadFile(expectedErrFilename)
if !assert.NoError(t, err) {
t.FailNow()
}

View File

@@ -0,0 +1,349 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"bytes"
"strings"
"text/template"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// ResourceMatcher is implemented by types designed for use in or as selectors.
type ResourceMatcher interface {
// kio.Filter applies the matcher to multiple resources.
// This makes individual matchers usable as selectors directly.
kio.Filter
// Match returns true if the given resource matches the matcher's configuration.
Match(node *yaml.RNode) bool
}
// ResourceMatcherFunc converts a compliant function into a ResourceMatcher
type ResourceMatcherFunc func(node *yaml.RNode) bool
// Match runs the ResourceMatcherFunc on the given node.
func (m ResourceMatcherFunc) Match(node *yaml.RNode) bool {
return m(node)
}
// Filter applies ResourceMatcherFunc to a list of items, returning only those that match.
func (m ResourceMatcherFunc) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
// MatchAll or MatchAny doesn't really matter here since there is only one matcher (m).
return MatchAll(m).Filter(items)
}
// ResourceTemplateMatcher is implemented by ResourceMatcher types that accept text templates as
// part of their configuration.
type ResourceTemplateMatcher interface {
// ResourceMatcher makes matchers usable in or as selectors.
ResourceMatcher
// DefaultTemplateData is used to pass default template values down a chain of matchers.
DefaultTemplateData(interface{})
// InitTemplates is used to render the templates in selectors that support
// ResourceTemplateMatcher. The selector should call this exactly once per filter
// operation, before beginning match comparisons.
InitTemplates() error
}
// ContainerNameMatcher returns a function that returns true if the "name" field
// of the provided container node matches one of the given container names.
// If no names are provided, the function always returns true.
// Note that this is not a ResourceMatcher, since the node it matches against must be
// container-level (e.g. "name", "env" and "image" would be top level fields).
func ContainerNameMatcher(names ...string) func(node *yaml.RNode) bool {
namesSet := sets.String{}
namesSet.Insert(names...)
return func(node *yaml.RNode) bool {
if len(namesSet) == 0 {
return true
}
f := node.Field("name")
if f == nil {
return false
}
return namesSet.Has(yaml.GetValue(f.Value))
}
}
// NameMatcher matches resources whose metadata.name is equal to one of the provided values.
// e.g. `NameMatcher("foo", "bar")` matches if `metadata.name` is either "foo" or "bar".
//
// NameMatcher supports templating.
// e.g. `NameMatcher("{{.AppName}}")` will match `metadata.name` "foo" if TemplateData is
// `struct{ AppName string }{ AppName: "foo" }`
func NameMatcher(names ...string) ResourceTemplateMatcher {
return &TemplatedMetaSliceMatcher{
Templates: names,
MetaMatcher: func(names sets.String, meta yaml.ResourceMeta) bool {
return names.Has(meta.Name)
},
}
}
// NamespaceMatcher matches resources whose metadata.namespace is equal to one of the provided values.
// e.g. `NamespaceMatcher("foo", "bar")` matches if `metadata.namespace` is either "foo" or "bar".
//
// NamespaceMatcher supports templating.
// e.g. `NamespaceMatcher("{{.AppName}}")` will match `metadata.namespace` "foo" if TemplateData is
// `struct{ AppName string }{ AppName: "foo" }`
func NamespaceMatcher(names ...string) ResourceTemplateMatcher {
return &TemplatedMetaSliceMatcher{
Templates: names,
MetaMatcher: func(names sets.String, meta yaml.ResourceMeta) bool {
return names.Has(meta.Namespace)
},
}
}
// KindMatcher matches resources whose kind is equal to one of the provided values.
// e.g. `KindMatcher("foo", "bar")` matches if `kind` is either "foo" or "bar".
//
// KindMatcher supports templating.
// e.g. `KindMatcher("{{.TargetKind}}")` will match `kind` "foo" if TemplateData is
// `struct{ TargetKind string }{ TargetKind: "foo" }`
func KindMatcher(names ...string) ResourceTemplateMatcher {
return &TemplatedMetaSliceMatcher{
Templates: names,
MetaMatcher: func(names sets.String, meta yaml.ResourceMeta) bool {
return names.Has(meta.Kind)
},
}
}
// APIVersionMatcher matches resources whose kind is equal to one of the provided values.
// e.g. `APIVersionMatcher("foo/v1", "bar/v1")` matches if `apiVersion` is either "foo/v1" or
// "bar/v1".
//
// APIVersionMatcher supports templating.
// e.g. `APIVersionMatcher("{{.TargetAPI}}")` will match `apiVersion` "foo/v1" if TemplateData is
// `struct{ TargetAPI string }{ TargetAPI: "foo/v1" }`
func APIVersionMatcher(names ...string) ResourceTemplateMatcher {
return &TemplatedMetaSliceMatcher{
Templates: names,
MetaMatcher: func(names sets.String, meta yaml.ResourceMeta) bool {
return names.Has(meta.APIVersion)
},
}
}
// GVKMatcher matches resources whose API group, version and kind match one of the provided values.
// e.g. `GVKMatcher("foo/v1/Widget", "bar/v1/App")` matches if `apiVersion` concatenated with `kind`
// is either "foo/v1/Widget" or "bar/v1/App".
//
// GVKMatcher supports templating.
// e.g. `GVKMatcher("{{.TargetAPI}}")` will match "foo/v1/Widget" if TemplateData is
// `struct{ TargetAPI string }{ TargetAPI: "foo/v1/Widget" }`
func GVKMatcher(names ...string) ResourceTemplateMatcher {
return &TemplatedMetaSliceMatcher{
Templates: names,
MetaMatcher: func(names sets.String, meta yaml.ResourceMeta) bool {
gvk := strings.Join([]string{meta.APIVersion, meta.Kind}, "/")
return names.Has(gvk)
},
}
}
// TemplatedMetaSliceMatcher is a utility type for constructing matchers that compare resource
// metadata to a slice of (possibly templated) strings.
type TemplatedMetaSliceMatcher struct {
// Templates is the list of possibly templated strings to compare to.
Templates []string
// values is the set of final (possibly rendered) strings to compare to.
values sets.String
// TemplateData is the data to use in template rendering.
// Rendering will not take place if it is nil when InitTemplates is called.
TemplateData interface{}
// MetaMatcher is a function that returns true if the given resource metadata matches at
// least one of the given names.
// The matcher implemented using TemplatedMetaSliceMatcher can compare names to any meta field.
MetaMatcher func(names sets.String, meta yaml.ResourceMeta) bool
}
// Match parses the resource node's metadata and delegates matching logic to the provided
// MetaMatcher func. This allows ResourceMatchers build with TemplatedMetaSliceMatcher to match
// against any field in resource metadata.
func (m *TemplatedMetaSliceMatcher) Match(node *yaml.RNode) bool {
var err error
meta, err := node.GetMeta()
if err != nil {
return false
}
return m.MetaMatcher(m.values, meta)
}
// Filter applies the matcher to a list of items, returning only those that match.
func (m *TemplatedMetaSliceMatcher) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
// AndSelector or OrSelector doesn't really matter here since there is only one matcher (m).
s := AndSelector{Matchers: []ResourceMatcher{m}, TemplateData: m.TemplateData}
return s.Filter(items)
}
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
// been set.
func (m *TemplatedMetaSliceMatcher) DefaultTemplateData(data interface{}) {
if m.TemplateData == nil {
m.TemplateData = data
}
}
// InitTemplates is used to render any templates the selector's list of strings may contain
// before the selector is applied. It should be called exactly once per filter
// operation, before beginning match comparisons.
func (m *TemplatedMetaSliceMatcher) InitTemplates() error {
values, err := templatizeSlice(m.Templates, m.TemplateData)
if err != nil {
return errors.Wrap(err)
}
m.values = sets.String{}
m.values.Insert(values...)
return nil
}
var _ ResourceTemplateMatcher = &TemplatedMetaSliceMatcher{}
// LabelMatcher matches resources that are labelled with all of the provided key-value pairs.
// e.g. `LabelMatcher(map[string]string{"app": "foo", "env": "prod"})` matches resources labelled
// app=foo AND env=prod.
//
// LabelMatcher supports templating.
// e.g. `LabelMatcher(map[string]string{"app": "{{ .AppName}}"})` will match label app=foo if
// TemplateData is `struct{ AppName string }{ AppName: "foo" }`
func LabelMatcher(labels map[string]string) ResourceTemplateMatcher {
return &TemplatedMetaMapMatcher{
Templates: labels,
MetaMatcher: func(labels map[string]string, meta yaml.ResourceMeta) bool {
return compareMaps(labels, meta.Labels)
},
}
}
func compareMaps(desired, actual map[string]string) bool {
for k := range desired {
// actual either doesn't have the key or has the wrong value for it
if actual[k] != desired[k] {
return false
}
}
return true
}
// AnnotationMatcher matches resources that are annotated with all of the provided key-value pairs.
// e.g. `AnnotationMatcher(map[string]string{"app": "foo", "env": "prod"})` matches resources
// annotated app=foo AND env=prod.
//
// AnnotationMatcher supports templating.
// e.g. `AnnotationMatcher(map[string]string{"app": "{{ .AppName}}"})` will match label app=foo if
// TemplateData is `struct{ AppName string }{ AppName: "foo" }`
func AnnotationMatcher(ann map[string]string) ResourceTemplateMatcher {
return &TemplatedMetaMapMatcher{
Templates: ann,
MetaMatcher: func(ann map[string]string, meta yaml.ResourceMeta) bool {
return compareMaps(ann, meta.Annotations)
},
}
}
// TemplatedMetaMapMatcher is a utility type for constructing matchers that compare resource
// metadata to a map of (possibly templated) key-value pairs.
type TemplatedMetaMapMatcher struct {
// Templates is the list of possibly templated strings to compare to.
Templates map[string]string
// values is the map of final (possibly rendered) strings to compare to.
values map[string]string
// TemplateData is the data to use in template rendering.
// Rendering will not take place if it is nil when InitTemplates is called.
TemplateData interface{}
// MetaMatcher is a function that returns true if the given resource metadata matches at
// least one of the given names.
// The matcher implemented using TemplatedMetaSliceMatcher can compare names to any meta field.
MetaMatcher func(names map[string]string, meta yaml.ResourceMeta) bool
}
// Match parses the resource node's metadata and delegates matching logic to the provided
// MetaMatcher func. This allows ResourceMatchers build with TemplatedMetaMapMatcher to match
// against any field in resource metadata.
func (m *TemplatedMetaMapMatcher) Match(node *yaml.RNode) bool {
var err error
meta, err := node.GetMeta()
if err != nil {
return false
}
return m.MetaMatcher(m.values, meta)
}
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
// been set.
func (m *TemplatedMetaMapMatcher) DefaultTemplateData(data interface{}) {
if m.TemplateData == nil {
m.TemplateData = data
}
}
// InitTemplates is used to render any templates the selector's key-value pairs may contain
// before the selector is applied. It should be called exactly once per filter
// operation, before beginning match comparisons.
func (m *TemplatedMetaMapMatcher) InitTemplates() error {
var err error
m.values, err = templatizeMap(m.Templates, m.TemplateData)
return errors.Wrap(err)
}
// Filter applies the matcher to a list of items, returning only those that match.
func (m *TemplatedMetaMapMatcher) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
// AndSelector or OrSelector doesn't really matter here since there is only one matcher (m).
s := AndSelector{Matchers: []ResourceMatcher{m}, TemplateData: m.TemplateData}
return s.Filter(items)
}
var _ ResourceTemplateMatcher = &TemplatedMetaMapMatcher{}
func templatizeSlice(values []string, data interface{}) ([]string, error) {
if data == nil {
return values, nil
}
var err error
results := make([]string, len(values))
for i := range values {
results[i], err = templatize(values[i], data)
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to render template %s", values[i])
}
}
return results, nil
}
func templatizeMap(values map[string]string, data interface{}) (map[string]string, error) {
if data == nil {
return values, nil
}
var err error
results := make(map[string]string, len(values))
for k := range values {
results[k], err = templatize(values[k], data)
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to render template for %s=%s", k, values[k])
}
}
return results, nil
}
// templatize renders the value as a template, using the provided data
func templatize(value string, data interface{}) (string, error) {
t, err := template.New("kinds").Parse(value)
if err != nil {
return "", errors.Wrap(err)
}
var b bytes.Buffer
err = t.Execute(&b, data)
if err != nil {
return "", errors.Wrap(err)
}
return b.String(), nil
}

View File

@@ -11,50 +11,205 @@ import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
)
// Applier applies some modification to a ResourceList
type Applier interface {
Apply(rl *ResourceList) error
// ResourcePatchTemplate applies a patch to a collection of resources
type ResourcePatchTemplate struct {
// Templates is a function that returns a list of templates to render into one or more patches.
Templates TemplatesFunc
// Selector targets the rendered patches to specific resources. If no Selector is provided,
// all resources will be patched.
//
// Although any Filter can be used, this framework provides several especially for Selector use:
// framework.Selector, framework.AndSelector, framework.OrSelector. You can also use any of the
// framework's ResourceMatchers here directly.
Selector kio.Filter
// TemplateData is the data to use when rendering the templates provided by the Templates field.
TemplateData interface{}
}
var _ Applier = PatchTemplate{}
// PatchTemplate applies a patch to a collection of Resources
type PatchTemplate struct {
// Template is a template to render into one or more patches.
Template *template.Template
// Selector targets the rendered patch to specific resources.
Selector *Selector
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
// been set.
func (t *ResourcePatchTemplate) DefaultTemplateData(data interface{}) {
if t.TemplateData == nil {
t.TemplateData = data
}
}
// Apply applies the patch to all matching resources in the list. The rl.FunctionConfig
// is provided to the template as input.
func (p PatchTemplate) Apply(rl *ResourceList) error {
if p.Selector == nil {
// programming error -- user shouldn't see this
return errors.Errorf("must specify PatchTemplate.Selector")
// Filter applies the ResourcePatchTemplate to the appropriate resources in the input.
// First, it applies the Selector to identify target resources. Then, it renders the Templates
// into patches using TemplateData. Finally, it identifies applies the patch to each resource.
func (t ResourcePatchTemplate) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
var err error
target := items
if t.Selector != nil {
target, err = t.Selector.Filter(items)
if err != nil {
return nil, err
}
}
if len(target) == 0 {
// nothing to do
return items, nil
}
matches, err := p.Selector.GetMatches(rl)
if err := t.apply(target); err != nil {
return nil, errors.Wrap(err)
}
return items, nil
}
func (t *ResourcePatchTemplate) apply(matches []*yaml.RNode) error {
templates, err := t.Templates()
if err != nil {
return err
return errors.Wrap(err)
}
if len(matches) == 0 {
return nil
var patches []*yaml.RNode
for i := range templates {
newP, err := renderPatches(templates[i], t.TemplateData)
if err != nil {
return errors.Wrap(err)
}
patches = append(patches, newP...)
}
return p.apply(rl, p.Template, matches)
// apply the patches to the matching resources
for j := range matches {
for i := range patches {
matches[j], err = merge2.Merge(patches[i], matches[j], yaml.MergeOptions{})
if err != nil {
return errors.WrapPrefixf(err, "failed to apply templated patch")
}
}
}
return nil
}
func (p *PatchTemplate) apply(rl *ResourceList, t *template.Template, matches []*yaml.RNode) error {
// ContainerPatchTemplate defines a patch to be applied to containers
type ContainerPatchTemplate struct {
// Templates is a function that returns a list of templates to render into one or more
// patches that apply at the container level. For example, "name", "env" and "image" would be
// top-level fields in container patches.
Templates TemplatesFunc
// Selector targets the rendered patches to containers within specific resources.
// If no Selector is provided, all resources with containers will be patched (subject to
// ContainerMatcher, if provided).
//
// Although any Filter can be used, this framework provides several especially for Selector use:
// framework.Selector, framework.AndSelector, framework.OrSelector. You can also use any of the
// framework's ResourceMatchers here directly.
Selector kio.Filter
// TemplateData is the data to use when rendering the templates provided by the Templates field.
TemplateData interface{}
// ContainerMatcher targets the rendered patch to only those containers it matches.
// For example, it can be used with ContainerNameMatcher to patch only containers with
// specific names. If no ContainerMatcher is provided, all containers will be patched.
//
// The node passed to ContainerMatcher will be container-level, not a full resource node.
// For example, "name", "env" and "image" would be top level fields.
// To filter based on resource-level context, use the Selector field.
ContainerMatcher func(node *yaml.RNode) bool
}
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
// been set.
func (cpt *ContainerPatchTemplate) DefaultTemplateData(data interface{}) {
if cpt.TemplateData == nil {
cpt.TemplateData = data
}
}
// Filter applies the ContainerPatchTemplate to the appropriate resources in the input.
// First, it applies the Selector to identify target resources. Then, it renders the Templates
// into patches using TemplateData. Finally, it identifies target containers and applies the
// patches.
func (cpt ContainerPatchTemplate) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
var err error
target := items
if cpt.Selector != nil {
target, err = cpt.Selector.Filter(items)
if err != nil {
return nil, err
}
}
if len(target) == 0 {
// nothing to do
return items, nil
}
if err := cpt.apply(target); err != nil {
return nil, err
}
return items, nil
}
// PatchContainers applies the patch to each matching container in each resource.
func (cpt ContainerPatchTemplate) apply(matches []*yaml.RNode) error {
templates, err := cpt.Templates()
if err != nil {
return errors.Wrap(err)
}
var patches []*yaml.RNode
for i := range templates {
newP, err := renderPatches(templates[i], cpt.TemplateData)
if err != nil {
return errors.Wrap(err)
}
patches = append(patches, newP...)
}
for i := range matches {
// TODO(knverey): Make this work for more Kinds and expose the helper for doing so.
containers, err := matches[i].Pipe(yaml.Lookup("spec", "template", "spec", "containers"))
if err != nil {
return errors.Wrap(err)
}
if containers == nil {
continue
}
err = containers.VisitElements(func(node *yaml.RNode) error {
if cpt.ContainerMatcher != nil && !cpt.ContainerMatcher(node) {
return nil
}
for j := range patches {
merger := walk.Walker{
Sources: []*yaml.RNode{node, patches[j]}, // dest, src
Visitor: merge2.Merger{},
MergeOptions: yaml.MergeOptions{},
Schema: openapi.SchemaForResourceType(yaml.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
}).Lookup("spec", "containers").Elements(),
}
_, err = merger.Walk()
if err != nil {
return errors.WrapPrefixf(err, "failed to apply templated patch")
}
}
return nil
})
if err != nil {
return errors.Wrap(err)
}
}
return nil
}
func renderPatches(t *template.Template, data interface{}) ([]*yaml.RNode, error) {
// render the patches
var b bytes.Buffer
if err := t.Execute(&b, rl.FunctionConfig); err != nil {
return errors.WrapPrefixf(err, "failed to render patch template %v", t.DefinedTemplates())
if err := t.Execute(&b, data); err != nil {
return nil, errors.WrapPrefixf(err, "failed to render patch template %v", t.DefinedTemplates())
}
// parse the patches into RNodes
@@ -64,201 +219,25 @@ func (p *PatchTemplate) apply(rl *ResourceList, t *template.Template, matches []
if s == "" {
continue
}
newNodes, err := (&kio.ByteReader{Reader: bytes.NewBufferString(s)}).Read()
r := &kio.ByteReader{Reader: bytes.NewBufferString(s), OmitReaderAnnotations: true}
newNodes, err := r.Read()
if err != nil {
// create the debug string
lines := strings.Split(s, "\n")
for j := range lines {
lines[j] = fmt.Sprintf("%03d %s", j+1, lines[j])
}
s = strings.Join(lines, "\n")
return errors.WrapPrefixf(err, "failed to parse rendered patch template into a resource:\n%s\n", s)
return nil, errors.WrapPrefixf(err,
"failed to parse rendered patch template into a resource:\n%s\n", addLineNumbers(s))
}
if err := yaml.ErrorIfAnyInvalidAndNonNull(yaml.MappingNode, newNodes...); err != nil {
return nil, errors.WrapPrefixf(err,
"failed to parse rendered patch template into a resource:\n%s\n", addLineNumbers(s))
}
nodes = append(nodes, newNodes...)
}
// apply the patches to the matching resources
var err error
for j := range matches {
for i := range nodes {
matches[j], err = merge2.Merge(nodes[i], p.Selector.matches[j], yaml.MergeOptions{})
if err != nil {
return errors.WrapPrefixf(err, "failed to merge templated patch")
}
}
}
return nil
return nodes, nil
}
// Selector matches resources. A resource matches if and only if ALL of the Selector fields
// match the resource. An empty Selector matches all resources.
type Selector struct {
// Names is a list of metadata.names to match. If empty match all names.
// e.g. Names: ["foo", "bar"] matches if `metadata.name` is either "foo" or "bar".
Names []string `json:"names" yaml:"names"`
namesSet sets.String
// Namespaces is a list of metadata.namespaces to match. If empty match all namespaces.
// e.g. Namespaces: ["foo", "bar"] matches if `metadata.namespace` is either "foo" or "bar".
Namespaces []string `json:"namespaces" yaml:"namespaces"`
namespaceSet sets.String
// Kinds is a list of kinds to match. If empty match all kinds.
// e.g. Kinds: ["foo", "bar"] matches if `kind` is either "foo" or "bar".
Kinds []string `json:"kinds" yaml:"kinds"`
kindsSet sets.String
// APIVersions is a list of apiVersions to match. If empty apply match all apiVersions.
// e.g. APIVersions: ["foo/v1", "bar/v1"] matches if `apiVersion` is either "foo/v1" or "bar/v1".
APIVersions []string `json:"apiVersions" yaml:"apiVersions"`
apiVersionsSet sets.String
// Labels is a collection of labels to match. All labels must match exactly.
// e.g. Labels: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" labels match.
Labels map[string]string `json:"labels" yaml:"labels"`
// Annotations is a collection of annotations to match. All annotations must match exactly.
// e.g. Annotations: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" annotations match.
Annotations map[string]string `json:"annotations" yaml:"annotations"`
// Filter is an arbitrary filter function to match a resource.
// Selector matches if the function returns true.
Filter func(*yaml.RNode) bool
// matches contains a list of matching reosurces.
matches []*yaml.RNode
// TemplatizeValues if set to true will parse the selector values as templates
// and execute them with the functionConfig
TemplatizeValues bool
}
// GetMatches returns them matching resources from rl
func (s *Selector) GetMatches(rl *ResourceList) ([]*yaml.RNode, error) {
if err := s.init(rl); err != nil {
return nil, err
}
return s.matches, nil
}
// templatize templatizes the value
func (s *Selector) templatize(value string, api interface{}) (string, error) {
t, err := template.New("kinds").Parse(value)
if err != nil {
return "", errors.Wrap(err)
}
var b bytes.Buffer
err = t.Execute(&b, api)
if err != nil {
return "", errors.Wrap(err)
}
return b.String(), nil
}
func (s *Selector) templatizeSlice(values []string, api interface{}) error {
var err error
for i := range values {
values[i], err = s.templatize(values[i], api)
if err != nil {
return err
}
}
return nil
}
func (s *Selector) templatizeMap(values map[string]string, api interface{}) error {
var err error
for k := range values {
values[k], err = s.templatize(values[k], api)
if err != nil {
return err
}
}
return nil
}
func (s *Selector) init(rl *ResourceList) error {
if s.TemplatizeValues {
// templatize the selector values from the input configuration
if err := s.templatizeSlice(s.Kinds, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeSlice(s.APIVersions, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeSlice(s.Names, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeSlice(s.Namespaces, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeMap(s.Labels, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeMap(s.Annotations, rl.FunctionConfig); err != nil {
return err
}
}
// index the selectors
s.matches = nil
s.kindsSet = sets.String{}
s.kindsSet.Insert(s.Kinds...)
s.apiVersionsSet = sets.String{}
s.apiVersionsSet.Insert(s.APIVersions...)
s.namesSet = sets.String{}
s.namesSet.Insert(s.Names...)
s.namespaceSet = sets.String{}
s.namespaceSet.Insert(s.Namespaces...)
// check each resource that matches the patch selector
for i := range rl.Items {
if match, err := s.isMatch(rl.Items[i]); err != nil {
return err
} else if !match {
continue
}
s.matches = append(s.matches, rl.Items[i])
}
return nil
}
// isMatch returns true if r matches the patch selector
func (s *Selector) isMatch(r *yaml.RNode) (bool, error) {
m, err := r.GetMeta()
if err != nil {
return false, errors.Wrap(err)
}
if s.kindsSet.Len() > 0 && !s.kindsSet.Has(m.Kind) {
return false, nil
}
if s.apiVersionsSet.Len() > 0 && !s.apiVersionsSet.Has(m.APIVersion) {
return false, nil
}
if s.namesSet.Len() > 0 && !s.namesSet.Has(m.Name) {
return false, nil
}
if s.namespaceSet.Len() > 0 && !s.namespaceSet.Has(m.Namespace) {
return false, nil
}
for k := range s.Labels {
if m.Labels == nil || m.Labels[k] != s.Labels[k] {
return false, nil
}
}
for k := range s.Annotations {
if m.Annotations == nil || m.Annotations[k] != s.Annotations[k] {
return false, nil
}
}
if s.Filter != nil {
if match := s.Filter(r); !match {
return false, nil
}
}
return true, nil
func addLineNumbers(s string) string {
lines := strings.Split(s, "\n")
for j := range lines {
lines[j] = fmt.Sprintf("%03d %s", j+1, lines[j])
}
return strings.Join(lines, "\n")
}

View File

@@ -4,23 +4,18 @@
package framework_test
import (
"bytes"
"strings"
"testing"
"text/template"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
"sigs.k8s.io/kustomize/kyaml/testutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestPatchTemplate(t *testing.T) {
// TODO: make this test pass on windows -- current failure seems spurious
testutil.SkipWindows(t)
func TestResourcePatchTemplate_ComplexSelectors(t *testing.T) {
cmdFn := func() *cobra.Command {
type api struct {
Selector framework.Selector `json:"selector" yaml:"selector"`
@@ -33,22 +28,14 @@ func TestPatchTemplate(t *testing.T) {
filter := framework.Selector{
// this is a special manual filter for the Selector for when the built-in matchers
// are insufficient
Filter: func(rn *yaml.RNode) bool {
ResourceMatcher: func(rn *yaml.RNode) bool {
m, _ := rn.GetMeta()
return config.Special != "" && m.Annotations["foo"] == config.Special
},
}
return framework.TemplateCommand{
API: &config,
PreProcess: func(rl *framework.ResourceList) error {
// do some extra processing based on the inputs
config.LongList = len(rl.Items) > 2
return nil
},
PatchTemplates: []framework.PatchTemplate{
{
// Apply these rendered patches
Template: template.Must(template.New("test").Parse(`
pt1 := framework.ResourcePatchTemplate{
// Apply these rendered patches
Templates: framework.StringTemplates(`
spec:
template:
spec:
@@ -62,211 +49,36 @@ metadata:
{{- if .LongList }}
long: 'true'
{{- end }}
`)),
// Use the selector from the input
Selector: &config.Selector,
},
{
// Apply these rendered patches
Template: template.Must(template.New("test").Parse(`
`),
// Use the selector from the input
Selector: &config.Selector,
}
pt2 := framework.ResourcePatchTemplate{
// Apply these rendered patches
Templates: framework.StringTemplates(`
metadata:
annotations:
filterPatched: '{{ .A }}'
`)),
// Use an explicit selector
Selector: &filter,
},
},
}.GetCommand()
`),
// Use an explicit selector
Selector: &filter,
}
fn := framework.TemplateProcessor{
TemplateData: &config,
PreProcessFilters: []kio.Filter{kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
// do some extra processing based on the inputs
config.LongList = len(items) > 2
return items, nil
})},
PatchTemplates: []framework.PatchTemplate{&pt1, &pt2},
}
return command.Build(fn, command.StandaloneEnabled, false)
}
frameworktestutil.ResultsChecker{Command: cmdFn, TestDataDirectory: "patchtestdata"}.Assert(t)
}
func TestSelector(t *testing.T) {
type Test struct {
// Name is the name of the test
Name string
// Fn configures the selector
Fn func(*framework.Selector)
// ValueFoo is the value to substitute to select the foo resource
ValueFoo string
// ValueBar is the value to substitute to select the bar resource
ValueBar string
// Value is set by the test to either ValueFoo or ValueBar
// and substituted into the selector
Value string
}
tests := []Test{
// Test the name template
{
Name: "names",
Fn: func(s *framework.Selector) {
s.Names = []string{"{{ .Value }}"}
},
ValueFoo: "foo",
ValueBar: "bar",
},
// Test the kind template
{
Name: "kinds",
Fn: func(s *framework.Selector) {
s.Kinds = []string{"{{ .Value }}"}
},
ValueFoo: "StatefulSet",
ValueBar: "Deployment",
},
// Test the apiVersion template
{
Fn: func(s *framework.Selector) {
s.APIVersions = []string{"{{ .Value }}"}
},
ValueFoo: "apps/v1beta1",
ValueBar: "apps/v1",
},
// Test the namespace template
{
Name: "namespaces",
Fn: func(s *framework.Selector) {
s.Namespaces = []string{"{{ .Value }}"}
},
ValueFoo: "foo-default",
ValueBar: "bar-default",
},
// Test the annotations template
{
Name: "annotations",
Fn: func(s *framework.Selector) {
s.Annotations = map[string]string{"key": "{{ .Value }}"}
},
ValueFoo: "foo-a",
ValueBar: "bar-a",
},
// Test the labels template
{
Name: "labels",
Fn: func(s *framework.Selector) {
s.Labels = map[string]string{"key": "{{ .Value }}"}
},
ValueFoo: "foo-l",
ValueBar: "bar-l",
},
}
// input is the input resources that are selected
input := `
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: foo
namespace: foo-default
annotations:
key: foo-a
labels:
key: foo-l
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: bar-default
annotations:
key: bar-a
labels:
key: bar-l
`
// expectedFoo is the expected output when the FooValue is substituted
expectedFoo := `
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: foo
namespace: foo-default
annotations:
key: foo-a
config.kubernetes.io/index: '0'
labels:
key: foo-l
`
// expectedFoo is the expected output when the BarValue is substituted
expectedBar := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: bar-default
annotations:
key: bar-a
config.kubernetes.io/index: '1'
labels:
key: bar-l
`
// Run the tests by substituting the FooValues
var err error
for i := range tests {
test := tests[i]
t.Run(tests[i].Name+"-foo", func(t *testing.T) {
test.Value = test.ValueFoo
var out bytes.Buffer
rl := &framework.ResourceList{
FunctionConfig: test,
Reader: bytes.NewBufferString(input),
Writer: &out,
}
if !assert.NoError(t, rl.Read()) {
t.FailNow()
}
s := &framework.Selector{TemplatizeValues: true}
test.Fn(s)
rl.Items, err = s.GetMatches(rl)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, rl.Write()) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(expectedFoo), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
// Run the tests by substituting the BarValues
for i := range tests {
test := tests[i]
t.Run(tests[i].Name+"-bar", func(t *testing.T) {
test.Value = test.ValueBar
var out bytes.Buffer
rl := &framework.ResourceList{
FunctionConfig: test,
Reader: bytes.NewBufferString(input),
Writer: &out,
}
if !assert.NoError(t, rl.Read()) {
t.FailNow()
}
s := &framework.Selector{TemplatizeValues: true}
test.Fn(s)
rl.Items, err = s.GetMatches(rl)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, rl.Write()) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(expectedBar), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
tc := frameworktestutil.CommandResultsChecker{Command: cmdFn,
TestDataDirectory: "testdata/patch-selector"}
tc.Assert(t)
}

View File

@@ -1,154 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"io/ioutil"
"os"
"path"
"strings"
"text/template"
"github.com/markbates/pkger"
)
type TemplatesFn func(*ResourceList) ([]*template.Template, error)
// TemplatesFromDir applies a directory of templates as generated resources.
func TemplatesFromDir(dirs ...pkger.Dir) TemplatesFn {
return func(_ *ResourceList) ([]*template.Template, error) {
var pt []*template.Template
for i := range dirs {
d := dirs[i]
err := pkger.Walk(string(d), func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(info.Name(), ".template.yaml") {
return nil
}
name := path.Join(string(d), info.Name())
f, err := pkger.Open(name)
if err != nil {
return err
}
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
t, err := template.New(info.Name()).Parse(string(b))
if err != nil {
return err
}
pt = append(pt, t)
return nil
})
if err != nil {
return nil, err
}
}
return pt, nil
}
}
// PatchTemplatesFn returns a slice of PatchTemplate
type PatchTemplatesFn func(*ResourceList) ([]PatchTemplate, error)
// PT applies a directory of patches using the Selector
type PT struct {
Selector func() *Selector
Dir pkger.Dir
}
// PatchTemplatesFromDir applies a directory of templates as patches.
func PatchTemplatesFromDir(templates ...PT) PatchTemplatesFn {
return func(*ResourceList) ([]PatchTemplate, error) {
var pt []PatchTemplate
for i := range templates {
v := templates[i]
err := pkger.Walk(string(v.Dir), func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
name := path.Join(string(v.Dir), info.Name())
if !strings.HasSuffix(info.Name(), ".template.yaml") {
return nil
}
f, err := pkger.Open(name)
if err != nil {
return err
}
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
t, err := template.New(info.Name()).Parse(string(b))
if err != nil {
return err
}
pt = append(pt, PatchTemplate{Template: t, Selector: v.Selector()})
return nil
})
if err != nil {
return nil, err
}
}
return pt, nil
}
}
// ContainerPatchTemplateFn returns a slice of ContainerPatchTemplate
type ContainerPatchTemplateFn func(*ResourceList) ([]ContainerPatchTemplate, error)
// CPT applies a directory of container patches using the Selector
type CPT struct {
Selector func() *Selector
Dir pkger.Dir
Names []string
}
// ContainerPatchTemplatesFromDir applies a directory of templates as container patches.
func ContainerPatchTemplatesFromDir(templates ...CPT) ContainerPatchTemplateFn {
return func(*ResourceList) ([]ContainerPatchTemplate, error) {
var cpt []ContainerPatchTemplate
for i := range templates {
v := templates[i]
err := pkger.Walk(string(v.Dir), func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(info.Name(), ".template.yaml") {
return nil
}
name := path.Join(string(v.Dir), info.Name())
f, err := pkger.Open(name)
if err != nil {
return err
}
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
t, err := template.New(info.Name()).Parse(string(b))
if err != nil {
return err
}
cpt = append(cpt, ContainerPatchTemplate{
PatchTemplate: PatchTemplate{Template: t, Selector: v.Selector()},
ContainerNames: v.Names,
})
return nil
})
if err != nil {
return nil, err
}
}
return cpt, nil
}
}

View File

@@ -0,0 +1,410 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/markbates/pkger"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SimpleProcessor processes a ResourceList by loading the FunctionConfig into
// the given Config type and then running the provided Filter on the ResourceList.
// The provided Config MAY implement Defaulter and Validator to have Default and Validate
// respectively called between unmarshalling and filter execution.
//
// Typical uses include functions that do not actually require config, and simple functions built
// with a filter that closes over the Config instance to access ResourceList.functionConfig values.
type SimpleProcessor struct {
// Filter is the kio.Filter that will be used to process the ResourceList's items.
// Note that kio.FilterFunc is available to transform a compatible func into a kio.Filter.
Filter kio.Filter
// Config must be a struct capable of receiving the data from ResourceList.functionConfig.
// Filter functions may close over this struct to access its data.
Config interface{}
}
// Process makes SimpleProcessor implement the ResourceListProcessor interface.
// It loads the ResourceList.functionConfig into the provided Config type, applying
// defaulting and validation if supported by Config. It then executes the processor's filter.
func (p SimpleProcessor) Process(rl *ResourceList) error {
if err := LoadFunctionConfig(rl.FunctionConfig, p.Config); err != nil {
return errors.Wrap(err)
}
return errors.Wrap(rl.Filter(p.Filter))
}
// GVKFilterMap is a FilterProvider that resolves Filters through a simple lookup in a map.
// It is intended for use in VersionedAPIProcessor.
type GVKFilterMap map[string]map[string]kio.Filter
// ProviderFor makes GVKFilterMap implement the FilterProvider interface.
// It uses the given apiVersion and kind to do a simple lookup in the map and
// returns an error if no exact match is found.
func (m GVKFilterMap) ProviderFor(apiVersion, kind string) (kio.Filter, error) {
if kind == "" {
return nil, errors.Errorf("kind is required")
}
if apiVersion == "" {
return nil, errors.Errorf("apiVersion is required")
}
var ok bool
var versionMap map[string]kio.Filter
if versionMap, ok = m[kind]; !ok {
return nil, errors.Errorf("kind %q is not supported", kind)
}
var p kio.Filter
if p, ok = versionMap[apiVersion]; !ok {
return nil, errors.Errorf("apiVersion %q is not supported for kind %q", apiVersion, kind)
}
return p, nil
}
// FilterProvider is implemented by types that provide a way to look up which Filter
// should be used to process a ResourceList based on the ApiVersion and Kind of the
// ResourceList.functionConfig in the input. FilterProviders are intended to be used
// as part of VersionedAPIProcessor.
type FilterProvider interface {
// ProviderFor returns the appropriate filter for the given APIVersion and Kind.
ProviderFor(apiVersion, kind string) (kio.Filter, error)
}
// FilterProviderFunc converts a compatible function to a FilterProvider.
type FilterProviderFunc func(apiVersion, kind string) (kio.Filter, error)
// ProviderFor makes FilterProviderFunc implement FilterProvider.
func (f FilterProviderFunc) ProviderFor(apiVersion, kind string) (kio.Filter, error) {
return f(apiVersion, kind)
}
// VersionedAPIProcessor selects the appropriate kio.Filter based on the ApiVersion
// and Kind of the ResourceList.functionConfig in the input.
// It can be used to implement configuration function APIs that evolve over time,
// or create processors that support multiple configuration APIs with a single entrypoint.
// All provided Filters MUST be structs capable of receiving ResourceList.functionConfig data.
// Provided Filters MAY implement Defaulter and Validator to have Default and Validate
// respectively called between unmarshalling and filter execution.
type VersionedAPIProcessor struct {
// FilterProvider resolves a kio.Filter for each supported API, based on its APIVersion and Kind.
// GVKFilterMap is a simple FilterProvider implementation for use here.
FilterProvider FilterProvider
}
// Process makes VersionedAPIProcessor implement the ResourceListProcessor interface.
// It looks up the configuration object to use based on the ApiVersion and Kind of the
// input ResourceList.functionConfig, loads ResourceList.functionConfig into that object,
// invokes Validate and Default if supported, and finally invokes Filter.
func (p *VersionedAPIProcessor) Process(rl *ResourceList) error {
api, err := p.FilterProvider.ProviderFor(extractGVK(rl.FunctionConfig))
if err != nil {
return errors.WrapPrefixf(err, "unable to identify provider for resource")
}
if err := LoadFunctionConfig(rl.FunctionConfig, api); err != nil {
return errors.Wrap(err)
}
return errors.Wrap(rl.Filter(api))
}
// extractGVK returns the apiVersion and kind fields from the given RNodes if it contains
// valid TypeMeta. It returns an empty string if a value is not found.
func extractGVK(src *yaml.RNode) (apiVersion, kind string) {
if src == nil {
return "", ""
}
if versionNode := src.Field("apiVersion"); versionNode != nil {
if a, err := versionNode.Value.String(); err == nil {
apiVersion = strings.TrimSpace(a)
}
}
if kindNode := src.Field("kind"); kindNode != nil {
if k, err := kindNode.Value.String(); err == nil {
kind = strings.TrimSpace(k)
}
}
return apiVersion, kind
}
// LoadFunctionConfig reads a configuration resource from YAML into the provided data structure
// and then prepares it for use by running defaulting and validation on it, if supported.
// ResourceListProcessors should use this function to load ResourceList.functionConfig.
func LoadFunctionConfig(src *yaml.RNode, api interface{}) error {
if api == nil {
return nil
}
if err := yaml.Unmarshal([]byte(src.MustString()), api); err != nil {
return errors.Wrap(err)
}
if d, ok := api.(Defaulter); ok {
if err := d.Default(); err != nil {
return err
}
}
if v, ok := api.(Validator); ok {
return v.Validate()
}
return nil
}
// TemplateProcessor is a ResourceListProcessor based on rendering templates with the data in
// ResourceList.functionConfig. It works as follows:
// - loads ResourceList.functionConfig into TemplateData
// - runs PreProcessFilters
// - renders ResourceTemplates and adds them to ResourceList.items
// - renders PatchTemplates and applies them to ResourceList.items
// - executes a merge on ResourceList.items if configured to
// - runs PostProcessFilters
// The TemplateData struct MAY implement Defaulter and Validator to have Default and Validate
// respectively called between unmarshalling and filter execution.
//
// TemplateProcessor also implements kio.Filter directly and can be used in the construction of
// higher-level processors. For example, you might use TemplateProcessors as the filters for each
// API supported by a VersionedAPIProcessor (see VersionedAPIProcessor examples).
type TemplateProcessor struct {
// TemplateData will will be exposed to all the templates in the processor (unless explicitly
// overridden for a template).
// If TemplateProcessor is used directly as a ResourceListProcessor, TemplateData will contain the
// value of ResourceList.functionConfig.
TemplateData interface{}
// ResourceTemplates returns a list of templates to render into resources.
// If MergeResources is set, any matching resources in ResourceList.items will be used as patches
// modifying the rendered templates. Otherwise, the rendered resources will be appended to
// the input resources as-is.
ResourceTemplates []ResourceTemplate
// PatchTemplates is a list of templates to render into patches that apply to ResourceList.items.
// ResourcePatchTemplate can be used here to patch entire resources.
// ContainerPatchTemplate can be used here to patch specific containers within resources.
PatchTemplates []PatchTemplate
// MergeResources, if set to true, will cause the resources in ResourceList.items to be
// will be applied as patches on any matching resources generated by ResourceTemplates.
MergeResources bool
// PreProcessFilters provides a hook to manipulate the ResourceList's items or config after
// TemplateData has been populated but before template-based filters are applied.
PreProcessFilters []kio.Filter
// PostProcessFilters provides a hook to manipulate the ResourceList's items after template
// filters are applied.
PostProcessFilters []kio.Filter
}
// Filter implements the kio.Filter interface, enabling you to use TemplateProcessor
// as part of a higher-level ResourceListProcessor like VersionedAPIProcessor.
// It sets up all the features of TemplateProcessors as a pipeline of filters and executes them.
func (tp TemplateProcessor) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
buf := &kio.PackageBuffer{Nodes: items}
pipeline := kio.Pipeline{
Inputs: []kio.Reader{buf},
Filters: []kio.Filter{
kio.FilterFunc(tp.doPreProcess),
kio.FilterFunc(tp.doResourceTemplates),
kio.FilterFunc(tp.doPatchTemplates),
kio.FilterFunc(tp.doMerge),
kio.FilterFunc(tp.doPostProcess),
},
Outputs: []kio.Writer{buf},
ContinueOnEmptyResult: true,
}
if err := pipeline.Execute(); err != nil {
return nil, err
}
return buf.Nodes, nil
}
// Process implements the ResourceListProcessor interface, enabling you to use TemplateProcessor
// directly as a processor. As a Processor, it loads the ResourceList.functionConfig into the
// TemplateData field, exposing it to all templates by default.
func (tp TemplateProcessor) Process(rl *ResourceList) error {
if err := LoadFunctionConfig(rl.FunctionConfig, tp.TemplateData); err != nil {
return errors.Wrap(err)
}
return errors.Wrap(rl.Filter(tp))
}
// TemplatesFunc is a function that provides a list of templates.
// TemplateProcessor uses this to defer loading of templates to the point where they are used.
type TemplatesFunc func() ([]*template.Template, error)
// PatchTemplate is implemented by kio.Filters that work by rendering patches and applying them to
// the given resource nodes.
type PatchTemplate interface {
// Filter is a kio.Filter-compliant function that applies PatchTemplate's templates as patches
// on the given resource nodes.
Filter(items []*yaml.RNode) ([]*yaml.RNode, error)
// DefaultTemplateData accepts default data to be used in template rendering when no template
// data was explicitly provided to the PatchTemplate.
DefaultTemplateData(interface{})
}
func (tp *TemplateProcessor) doPreProcess(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.PreProcessFilters == nil {
return items, nil
}
for i := range tp.PreProcessFilters {
filter := tp.PreProcessFilters[i]
var err error
items, err = filter.Filter(items)
if err != nil {
return nil, err
}
}
return items, nil
}
func (tp *TemplateProcessor) doMerge(items []*yaml.RNode) ([]*yaml.RNode, error) {
var err error
if tp.MergeResources {
items, err = filters.MergeFilter{}.Filter(items)
}
return items, err
}
func (tp *TemplateProcessor) doPostProcess(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.PostProcessFilters == nil {
return items, nil
}
for i := range tp.PostProcessFilters {
filter := tp.PostProcessFilters[i]
var err error
items, err = filter.Filter(items)
if err != nil {
return nil, err
}
}
return items, nil
}
func (tp *TemplateProcessor) doResourceTemplates(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.ResourceTemplates == nil {
return items, nil
}
for i := range tp.ResourceTemplates {
tp.ResourceTemplates[i].DefaultTemplateData(tp.TemplateData)
newItems, err := tp.ResourceTemplates[i].Render()
if err != nil {
return nil, err
}
if tp.MergeResources {
// apply inputs as patches -- add the new items to the front of the list
items = append(newItems, items...)
} else {
// assume these are new unique resources--append to the list
items = append(items, newItems...)
}
}
return items, nil
}
func (tp *TemplateProcessor) doPatchTemplates(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.PatchTemplates == nil {
return items, nil
}
for i := range tp.PatchTemplates {
// Default the template data for the patch to the processor's data
tp.PatchTemplates[i].DefaultTemplateData(tp.TemplateData)
var err error
if items, err = tp.PatchTemplates[i].Filter(items); err != nil {
return nil, err
}
}
return items, nil
}
// StringTemplates returns a TemplatesFunc that will generate templates from the provided strings.
// This is a helper to facilitate providing ResourceTemplates, PatchTemplates and
// ContainerPatchTemplates to a TemplateProcessor.
func StringTemplates(data ...string) TemplatesFunc {
return func() ([]*template.Template, error) {
var templates []*template.Template
for i := range data {
t, err := template.New(fmt.Sprintf("inline%d", i)).Parse(data[i])
if err != nil {
return nil, err
}
templates = append(templates, t)
}
return templates, nil
}
}
// TemplatesFromFile returns a TemplatesFunc that will generate templates from the provided files.
// This is a helper to facilitate providing ResourceTemplates, PatchTemplates and
// ContainerPatchTemplates to a TemplateProcessor.
func TemplatesFromFile(files ...string) TemplatesFunc {
return func() ([]*template.Template, error) {
var templates []*template.Template
for i := range files {
n := filepath.Base(files[i])
t, err := template.New(n).ParseFiles(files[i])
if err != nil {
return nil, err
}
templates = append(templates, t)
}
return templates, nil
}
}
// TemplatesFromDir returns a TemplatesFunc that will generate templates from the provided
// directories. Only files suffixed with .template.yaml will be included.
// This is a helper to facilitate providing ResourceTemplates, PatchTemplates and
// ContainerPatchTemplates to a TemplateProcessor.
func TemplatesFromDir(dirs ...pkger.Dir) TemplatesFunc {
return func() ([]*template.Template, error) {
var pt []*template.Template
for i := range dirs {
dir := string(dirs[i])
err := pkger.Walk(dir, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(info.Name(), ".template.yaml") {
return nil
}
name := path.Join(dir, info.Name())
f, err := pkger.Open(name)
if err != nil {
return err
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
t, err := template.New(info.Name()).Parse(string(b))
if err != nil {
return err
}
pt = append(pt, t)
return nil
})
if err != nil {
return nil, err
}
}
return pt, nil
}
}

View File

@@ -0,0 +1,527 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework_test
import (
"bytes"
"strings"
"testing"
"github.com/markbates/pkger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func TestTemplateProcessor_ResourceTemplates(t *testing.T) {
type API struct {
Image string `json:"image" yaml:"image"`
}
p := framework.TemplateProcessor{
TemplateData: &API{},
ResourceTemplates: []framework.ResourceTemplate{{
Templates: framework.TemplatesFromDir(pkger.Dir(
"/fn/framework/testdata/template-processor/templates")),
}},
}
out := new(bytes.Buffer)
rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: v1
kind: Service
functionConfig:
image: baz
`),
Writer: out}
require.NoError(t, framework.Execute(p, rw))
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: v1
kind: Service
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
namespace: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
image: baz
`), strings.TrimSpace(out.String()))
}
func TestTemplateProcessor_PatchTemplates(t *testing.T) {
type API struct {
Spec struct {
Replicas int `json:"replicas" yaml:"replicas"`
A string `json:"a" yaml:"a"`
} `json:"spec" yaml:"spec"`
}
config := &API{}
p := framework.TemplateProcessor{
TemplateData: config,
PatchTemplates: []framework.PatchTemplate{
// Patch from dir with no selector templating
&framework.ResourcePatchTemplate{
Templates: framework.TemplatesFromDir(pkger.Dir(
"/fn/framework/testdata/template-processor/patches")),
Selector: &framework.Selector{Names: []string{"foo"}},
},
// Patch from string with selector templating
&framework.ResourcePatchTemplate{
Selector: &framework.Selector{Names: []string{"{{.Spec.A}}"}, TemplateData: &config},
Templates: framework.StringTemplates(`
metadata:
annotations:
baz: buz
`)},
},
}
out := new(bytes.Buffer)
rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: foo
image: baz
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
spec:
replicas: 5
a: bar
`),
Writer: out}
require.NoError(t, framework.Execute(p, rw))
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: foo
image: baz
replicas: 5
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
annotations:
baz: buz
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
spec:
replicas: 5
a: bar
`), strings.TrimSpace(out.String()))
}
func TestTemplateProcessor_ContainerPatchTemplates(t *testing.T) {
type API struct {
Spec struct {
Key string `json:"key" yaml:"key"`
Value string `json:"value" yaml:"value"`
A string `json:"a" yaml:"a"`
}
}
config := &API{}
p := framework.TemplateProcessor{
TemplateData: config,
PatchTemplates: []framework.PatchTemplate{
// patch from dir with no selector templating
&framework.ContainerPatchTemplate{
Templates: framework.TemplatesFromDir(pkger.Dir(
"/fn/framework/testdata/template-processor/container-patches")),
Selector: &framework.Selector{Names: []string{"foo"}},
},
// patch from string with selector templating
&framework.ContainerPatchTemplate{
Selector: &framework.Selector{Names: []string{"{{.Spec.A}}"}, TemplateData: &config},
Templates: framework.StringTemplates(`
env:
- name: Foo
value: Bar
`)},
},
}
out := new(bytes.Buffer)
rw := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: a
env:
- name: EXISTING
value: variable
- name: b
- name: c
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
functionConfig:
spec:
key: Hello
value: World
a: bar
`),
Writer: out}
require.NoError(t, framework.Execute(p, rw))
require.Equal(t, strings.TrimSpace(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
template:
spec:
containers:
- name: a
env:
- name: EXISTING
value: variable
- name: Hello
value: World
- name: b
env:
- name: Hello
value: World
- name: c
env:
- name: Hello
value: World
- apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
spec:
template:
spec:
containers:
- name: foo
image: baz
env:
- name: Foo
value: Bar
functionConfig:
spec:
key: Hello
value: World
a: bar
`), strings.TrimSpace(out.String()))
}
func TestSimpleProcessor_Process_loads_config(t *testing.T) {
cfg := new(struct {
Value string `yaml:"value"`
})
p := framework.SimpleProcessor{
Filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
if cfg.Value != "dataFromResourceList" {
return nil, errors.Errorf("got incorrect config value %q", cfg.Value)
}
return items, nil
}),
Config: &cfg,
}
rl := framework.ResourceList{
FunctionConfig: yaml.NewMapRNode(&map[string]string{
"value": "dataFromResourceList",
}),
}
require.NoError(t, p.Process(&rl))
}
func TestSimpleProcessor_Process_Error(t *testing.T) {
tests := []struct {
name string
filter kio.Filter
config interface{}
wantErr string
}{
{
name: "error when given func as Config",
config: func() {},
wantErr: "cannot unmarshal !!map into func()",
},
{
name: "error in filter",
wantErr: "err from filter",
filter: kio.FilterFunc(func(_ []*yaml.RNode) ([]*yaml.RNode, error) {
return nil, errors.Errorf("err from filter")
}),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
p := framework.SimpleProcessor{
Filter: tt.filter,
Config: tt.config,
}
rl := framework.ResourceList{
FunctionConfig: yaml.NewMapRNode(&map[string]string{
"value": "dataFromResourceList",
}),
}
err := p.Process(&rl)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestVersionedAPIProcessor_Process_Error(t *testing.T) {
tests := []struct {
name string
filterProvider framework.FilterProvider
apiVersion string
kind string
wantErr string
}{
{
name: "error when given FilterFunc as Filter",
filterProvider: framework.FilterProviderFunc(func(_, _ string) (kio.Filter, error) {
return kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
return items, nil
}), nil
}),
wantErr: "cannot unmarshal !!map into kio.FilterFunc",
},
{
name: "error in filter",
filterProvider: framework.FilterProviderFunc(func(_, _ string) (kio.Filter, error) {
return &framework.AndSelector{FailOnEmptyMatch: true}, nil
}),
wantErr: "selector did not select any items",
},
{
name: "error GVKFilterMap no filter for kind",
filterProvider: framework.GVKFilterMap{
"puppy": {
"pets.example.com/v1beta1": &framework.Selector{},
},
},
kind: "kitten",
apiVersion: "pets.example.com/v1beta1",
wantErr: "kind \"kitten\" is not supported",
},
{
name: "error GVKFilterMap no filter for version",
filterProvider: framework.GVKFilterMap{
"kitten": {
"pets.example.com/v1alpha1": &framework.Selector{},
},
},
kind: "kitten",
apiVersion: "pets.example.com/v1beta1",
wantErr: "apiVersion \"pets.example.com/v1beta1\" is not supported for kind \"kitten\"",
},
{
name: "error GVKFilterMap blank kind",
filterProvider: framework.GVKFilterMap{},
kind: "",
apiVersion: "pets.example.com/v1beta1",
wantErr: "unable to identify provider for resource: kind is required",
},
{
name: "error GVKFilterMap blank version",
filterProvider: framework.GVKFilterMap{},
kind: "kitten",
apiVersion: "",
wantErr: "unable to identify provider for resource: apiVersion is required",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
p := framework.VersionedAPIProcessor{
FilterProvider: tt.filterProvider,
}
rl := framework.ResourceList{
FunctionConfig: yaml.NewMapRNode(&map[string]string{
"apiVersion": tt.apiVersion,
"kind": tt.kind,
}),
}
err := p.Process(&rl)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestTemplateProcessor_Process_Error(t *testing.T) {
tests := []struct {
name string
processor framework.TemplateProcessor
wantErr string
}{
{
name: "ResourcePatchTemplate is not a resource",
processor: framework.TemplateProcessor{
PatchTemplates: []framework.PatchTemplate{
&framework.ResourcePatchTemplate{
Templates: framework.StringTemplates(`aString
another`),
}},
},
wantErr: `failed to parse rendered patch template into a resource:
001 aString
002 another
: wrong Node Kind for expected: MappingNode was ScalarNode: value: {aString another}`,
},
{
name: "ResourcePatchTemplate is invalid template",
processor: framework.TemplateProcessor{
PatchTemplates: []framework.PatchTemplate{
&framework.ResourcePatchTemplate{
Templates: framework.StringTemplates("foo: {{ .OOPS }}"),
}},
},
wantErr: "can't evaluate field OOPS",
},
{
name: "ContainerPatchTemplate is not a resource",
processor: framework.TemplateProcessor{
PatchTemplates: []framework.PatchTemplate{
&framework.ContainerPatchTemplate{
Templates: framework.StringTemplates(`aString
another`),
}},
},
wantErr: `failed to parse rendered patch template into a resource:
001 aString
002 another
: wrong Node Kind for expected: MappingNode was ScalarNode: value: {aString another}`,
},
{
name: "ContainerPatchTemplate is invalid template",
processor: framework.TemplateProcessor{
PatchTemplates: []framework.PatchTemplate{
&framework.ContainerPatchTemplate{
Templates: framework.StringTemplates("foo: {{ .OOPS }}"),
}},
},
wantErr: "can't evaluate field OOPS",
},
{
name: "ResourceTemplate is not a resource",
processor: framework.TemplateProcessor{
ResourceTemplates: []framework.ResourceTemplate{{
Templates: framework.StringTemplates(`aString
another`),
}},
},
wantErr: `failed to parse rendered template into a resource:
001 aString
002 another
: wrong Node Kind for expected: MappingNode was ScalarNode: value: {aString another}`,
},
{
name: "ResourceTemplate is invalid template",
processor: framework.TemplateProcessor{
ResourceTemplates: []framework.ResourceTemplate{{
Templates: framework.StringTemplates("foo: {{ .OOPS }}"),
}},
},
wantErr: "can't evaluate field OOPS",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
rl := framework.ResourceList{
Items: []*yaml.RNode{
yaml.MustParse(`
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo
spec:
replicas: 5
template:
spec:
containers:
- name: foo
`),
},
FunctionConfig: yaml.NewMapRNode(&map[string]string{
"value": "dataFromResourceList",
}),
}
tt.processor.TemplateData = new(struct {
Value string `yaml:"value"`
})
err := tt.processor.Process(&rl)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}

View File

@@ -4,25 +4,13 @@
package framework
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Function defines a function which mutates or validates a collection of configuration
// To create a structured validation result, return a Result as the error.
type Function func() error
// Result defines a function result which will be set on the emitted ResourceList
type Result struct {
// Name is the name of the function creating the result
Name string `yaml:"name,omitempty"`
// Items are the individual results
Items []Item `yaml:"items,omitempty"`
}
// Severity indicates the severity of the result
// Severity indicates the severity of the Result
type Severity string
const (
@@ -34,22 +22,31 @@ const (
Info Severity = "info"
)
// Item defines a validation result
type Item struct {
// ResultItem defines a validation result
type ResultItem struct {
// Message is a human readable message
Message string `yaml:"message,omitempty"`
// Severity is the severity of the
// Severity is the severity of this result
Severity Severity `yaml:"severity,omitempty"`
// ResourceRef is a reference to a resource
ResourceRef yaml.ResourceMeta `yaml:"resourceRef,omitempty"`
// Field is a reference to the field in a resource this result refers to
Field Field `yaml:"field,omitempty"`
// File references a file containing the resource this result refers to
File File `yaml:"file,omitempty"`
}
// String provides a human-readable message for the result item
func (i ResultItem) String() string {
identifier := i.ResourceRef.GetIdentifier()
idString := strings.Join([]string{identifier.GetAPIVersion(), identifier.GetKind(), identifier.GetNamespace(), identifier.GetName()}, "/")
return fmt.Sprintf("[%s] %s %s: %s", i.Severity, idString, i.Field.Path, i.Message)
}
// File references a file containing a resource
type File struct {
// Path is relative path to the file containing the resource
@@ -72,16 +69,25 @@ type Field struct {
SuggestedValue string `yaml:"suggestedValue,omitempty"`
}
// Error implement error
// Result defines a function result which will be set on the emitted ResourceList
type Result struct {
// Name is the name of the function creating the result
Name string `yaml:"name,omitempty"`
// Items are the individual results
Items []ResultItem `yaml:"items,omitempty"`
}
// Error enables a Result to be returned as an error
func (e Result) Error() string {
var msgs []string
for _, i := range e.Items {
msgs = append(msgs, i.Message)
msgs = append(msgs, i.String())
}
return strings.Join(msgs, "\n\n")
}
// ExitCode provides the exit code based on the result
// ExitCode provides the exit code based on the result's severity
func (e Result) ExitCode() int {
for _, i := range e.Items {
if i.Severity == Error {

View File

@@ -0,0 +1,219 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Selector matches resources. A resource matches if and only if ALL of the Selector fields
// match the resource. An empty Selector matches all resources.
type Selector struct {
// Names is a list of metadata.names to match. If empty match all names.
// e.g. Names: ["foo", "bar"] matches if `metadata.name` is either "foo" or "bar".
Names []string `json:"names" yaml:"names"`
// Namespaces is a list of metadata.namespaces to match. If empty match all namespaces.
// e.g. Namespaces: ["foo", "bar"] matches if `metadata.namespace` is either "foo" or "bar".
Namespaces []string `json:"namespaces" yaml:"namespaces"`
// Kinds is a list of kinds to match. If empty match all kinds.
// e.g. Kinds: ["foo", "bar"] matches if `kind` is either "foo" or "bar".
Kinds []string `json:"kinds" yaml:"kinds"`
// APIVersions is a list of apiVersions to match. If empty apply match all apiVersions.
// e.g. APIVersions: ["foo/v1", "bar/v1"] matches if `apiVersion` is either "foo/v1" or "bar/v1".
APIVersions []string `json:"apiVersions" yaml:"apiVersions"`
// Labels is a collection of labels to match. All labels must match exactly.
// e.g. Labels: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" labels match.
Labels map[string]string `json:"labels" yaml:"labels"`
// Annotations is a collection of annotations to match. All annotations must match exactly.
// e.g. Annotations: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" annotations match.
Annotations map[string]string `json:"annotations" yaml:"annotations"`
// ResourceMatcher is an arbitrary function used to match resources.
// Selector matches if the function returns true.
ResourceMatcher func(*yaml.RNode) bool
// TemplateData if present will cause the selector values to be parsed as templates
// and rendered using TemplateData before they are used.
TemplateData interface{}
// FailOnEmptyMatch makes the selector return an error when no items are selected.
FailOnEmptyMatch bool
}
// Filter implements kio.Filter, returning only those items from the list that the selector
// matches.
func (s *Selector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
andSel := AndSelector{TemplateData: s.TemplateData, FailOnEmptyMatch: s.FailOnEmptyMatch}
if s.Names != nil {
andSel.Matchers = append(andSel.Matchers, NameMatcher(s.Names...))
}
if s.Namespaces != nil {
andSel.Matchers = append(andSel.Matchers, NamespaceMatcher(s.Namespaces...))
}
if s.Kinds != nil {
andSel.Matchers = append(andSel.Matchers, KindMatcher(s.Kinds...))
}
if s.APIVersions != nil {
andSel.Matchers = append(andSel.Matchers, APIVersionMatcher(s.APIVersions...))
}
if s.Labels != nil {
andSel.Matchers = append(andSel.Matchers, LabelMatcher(s.Labels))
}
if s.Annotations != nil {
andSel.Matchers = append(andSel.Matchers, AnnotationMatcher(s.Annotations))
}
if s.ResourceMatcher != nil {
andSel.Matchers = append(andSel.Matchers, ResourceMatcherFunc(s.ResourceMatcher))
}
return andSel.Filter(items)
}
// MatchAll is a shorthand for building an AndSelector from a list of ResourceMatchers.
func MatchAll(matchers ...ResourceMatcher) *AndSelector {
return &AndSelector{Matchers: matchers}
}
// MatchAny is a shorthand for building an OrSelector from a list of ResourceMatchers.
func MatchAny(matchers ...ResourceMatcher) *OrSelector {
return &OrSelector{Matchers: matchers}
}
// OrSelector is a kio.Filter that selects resources when that match at least one of its embedded
// matchers.
type OrSelector struct {
// Matchers is the list of ResourceMatchers to try on the input resources.
Matchers []ResourceMatcher
// TemplateData, if present, is used to initialize any matchers that implement
// ResourceTemplateMatcher.
TemplateData interface{}
// FailOnEmptyMatch makes the selector return an error when no items are selected.
FailOnEmptyMatch bool
}
// Match implements ResourceMatcher so that OrSelectors can be composed
func (s *OrSelector) Match(item *yaml.RNode) bool {
for _, matcher := range s.Matchers {
if matcher.Match(item) {
return true
}
}
return false
}
// Filter implements kio.Filter, returning only those items from the list that the selector
// matches.
func (s *OrSelector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
if err := initMatcherTemplates(s.Matchers, s.TemplateData); err != nil {
return nil, err
}
var selectedItems []*yaml.RNode
for i := range items {
for _, matcher := range s.Matchers {
if matcher.Match(items[i]) {
selectedItems = append(selectedItems, items[i])
break
}
}
}
if s.FailOnEmptyMatch && len(selectedItems) == 0 {
return nil, errors.Errorf("selector did not select any items")
}
return selectedItems, nil
}
// DefaultTemplateData makes OrSelector a ResourceTemplateMatcher.
// Although it does not contain templates itself, this allows it to support ResourceTemplateMatchers
// when being used as a matcher itself.
func (s *OrSelector) DefaultTemplateData(data interface{}) {
if s.TemplateData == nil {
s.TemplateData = data
}
}
func (s *OrSelector) InitTemplates() error {
return initMatcherTemplates(s.Matchers, s.TemplateData)
}
func initMatcherTemplates(matchers []ResourceMatcher, data interface{}) error {
for _, matcher := range matchers {
if tm, ok := matcher.(ResourceTemplateMatcher); ok {
tm.DefaultTemplateData(data)
if err := tm.InitTemplates(); err != nil {
return err
}
}
}
return nil
}
var _ ResourceTemplateMatcher = &OrSelector{}
// OrSelector is a kio.Filter that selects resources when that match all of its embedded
// matchers.
type AndSelector struct {
// Matchers is the list of ResourceMatchers to try on the input resources.
Matchers []ResourceMatcher
// TemplateData, if present, is used to initialize any matchers that implement
// ResourceTemplateMatcher.
TemplateData interface{}
// FailOnEmptyMatch makes the selector return an error when no items are selected.
FailOnEmptyMatch bool
}
// Match implements ResourceMatcher so that AndSelectors can be composed
func (s *AndSelector) Match(item *yaml.RNode) bool {
for _, matcher := range s.Matchers {
if !matcher.Match(item) {
return false
}
}
return true
}
// Filter implements kio.Filter, returning only those items from the list that the selector
// matches.
func (s *AndSelector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
if err := initMatcherTemplates(s.Matchers, s.TemplateData); err != nil {
return nil, err
}
var selectedItems []*yaml.RNode
for i := range items {
isSelected := true
for _, matcher := range s.Matchers {
if !matcher.Match(items[i]) {
isSelected = false
break
}
}
if isSelected {
selectedItems = append(selectedItems, items[i])
}
}
if s.FailOnEmptyMatch && len(selectedItems) == 0 {
return nil, errors.Errorf("selector did not select any items")
}
return selectedItems, nil
}
// DefaultTemplateData makes AndSelector a ResourceTemplateMatcher.
// Although it does not contain templates itself, this allows it to support ResourceTemplateMatchers
// when being used as a matcher itself.
func (s *AndSelector) DefaultTemplateData(data interface{}) {
if s.TemplateData == nil {
s.TemplateData = data
}
}
func (s *AndSelector) InitTemplates() error {
return initMatcherTemplates(s.Matchers, s.TemplateData)
}
var _ ResourceTemplateMatcher = &AndSelector{}

View File

@@ -0,0 +1,465 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework_test
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func TestSelector(t *testing.T) {
type Test struct {
// Name is the name of the test
Name string
// Filter configures the selector
Fn func(*framework.Selector)
// ValueFoo is the value to substitute to select the foo resource
ValueFoo string
// ValueBar is the value to substitute to select the bar resource
ValueBar string
// Value is set by the test to either ValueFoo or ValueBar
// and substituted into the selector
Value string
}
tests := []Test{
// Test the name template
{
Name: "names",
Fn: func(s *framework.Selector) {
s.Names = []string{"{{ .Value }}"}
},
ValueFoo: "foo",
ValueBar: "bar",
},
// Test the kind template
{
Name: "kinds",
Fn: func(s *framework.Selector) {
s.Kinds = []string{"{{ .Value }}"}
},
ValueFoo: "StatefulSet",
ValueBar: "Deployment",
},
// Test the apiVersion template
{
Name: "apiVersion",
Fn: func(s *framework.Selector) {
s.APIVersions = []string{"{{ .Value }}"}
},
ValueFoo: "apps/v1beta1",
ValueBar: "apps/v1",
},
// Test the namespace template
{
Name: "namespaces",
Fn: func(s *framework.Selector) {
s.Namespaces = []string{"{{ .Value }}"}
},
ValueFoo: "foo-default",
ValueBar: "bar-default",
},
// Test the annotations template
{
Name: "annotations",
Fn: func(s *framework.Selector) {
s.Annotations = map[string]string{"key": "{{ .Value }}"}
},
ValueFoo: "foo-a",
ValueBar: "bar-a",
},
// Test the labels template
{
Name: "labels",
Fn: func(s *framework.Selector) {
s.Labels = map[string]string{"key": "{{ .Value }}"}
},
ValueFoo: "foo-l",
ValueBar: "bar-l",
},
}
// input is the input resources that are selected
input := `
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: foo
namespace: foo-default
annotations:
key: foo-a
labels:
key: foo-l
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: bar-default
annotations:
key: bar-a
labels:
key: bar-l
`
// expectedFoo is the expected output when the FooValue is substituted
expectedFoo := `
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: foo
namespace: foo-default
annotations:
key: foo-a
config.kubernetes.io/index: '0'
labels:
key: foo-l
`
// expectedFoo is the expected output when the BarValue is substituted
expectedBar := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: bar-default
annotations:
key: bar-a
config.kubernetes.io/index: '1'
labels:
key: bar-l
`
// Run the tests by substituting the FooValues
var err error
for i := range tests {
test := tests[i]
t.Run(tests[i].Name+"-foo", func(t *testing.T) {
test.Value = test.ValueFoo
var out bytes.Buffer
rw := &kio.ByteReadWriter{
Reader: bytes.NewBufferString(input),
Writer: &out,
KeepReaderAnnotations: true,
}
p := func(rl *framework.ResourceList) error {
s := &framework.Selector{TemplateData: test}
test.Fn(s)
rl.Items, err = s.Filter(rl.Items)
return err
}
require.NoError(t, framework.Execute(framework.ResourceListProcessorFunc(p), rw))
require.Equal(t, strings.TrimSpace(expectedFoo), strings.TrimSpace(out.String()))
})
}
// Run the tests by substituting the BarValues
for i := range tests {
test := tests[i]
t.Run(tests[i].Name+"-bar", func(t *testing.T) {
test.Value = test.ValueBar
var out bytes.Buffer
rw := &kio.ByteReadWriter{
Reader: bytes.NewBufferString(input),
Writer: &out,
KeepReaderAnnotations: true,
}
p := func(rl *framework.ResourceList) error {
s := &framework.Selector{TemplateData: test}
test.Fn(s)
rl.Items, err = s.Filter(rl.Items)
return err
}
require.NoError(t, framework.Execute(framework.ResourceListProcessorFunc(p), rw))
require.Equal(t, strings.TrimSpace(expectedBar), strings.TrimSpace(out.String()))
})
}
}
func TestAndOrSelector_Composition(t *testing.T) {
// This selector should pick the "prime-target" deployment by name
// as well as any resources with the given labels or annotations regardless of kind
s := framework.MatchAny(
framework.MatchAll(
framework.GVKMatcher("apps/v1/Deployment"),
framework.NameMatcher("prime-target"),
),
framework.MatchAny(
framework.LabelMatcher(map[string]string{
"select": "yes",
}),
framework.AnnotationMatcher(map[string]string{
"example.io/select": "yes",
}),
),
)
input, err := kio.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: prime-target
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: exclude-one
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: exclude-two
labels:
select: no
---
apiVersion: v1
kind: ConfigMap
metadata:
name: extra-target
labels:
select: yes
---
apiVersion: apps/v1
kind: ConfigMap
metadata:
name: prime-target
data:
shouldSelect: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-one
labels:
select: yes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-two
annotations:
example.io/select: yes
`))
require.NoError(t, err)
result, err := s.Filter(input)
require.NoError(t, err)
expected := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: prime-target
---
apiVersion: v1
kind: ConfigMap
metadata:
name: extra-target
labels:
select: yes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-one
labels:
select: yes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-two
annotations:
example.io/select: yes
`
resultStr, err := kio.StringAll(result)
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(resultStr))
}
func TestAndOrSelector_CompositionTemplated(t *testing.T) {
// This selector should pick the "prime-target" deployment by name
// as well as any resources with the given labels or annotations regardless of kind
// Note: very similar to above test, but uses verbose expression to access templating
type templateStruct struct {
GVK string
Name string
LabelValue string
AnnotationValue string
}
s := framework.OrSelector{
// This should get propagated to matchers without explicit data
TemplateData: &templateStruct{
GVK: "apps/v1/Oops",
Name: "extra-target",
LabelValue: "yes",
AnnotationValue: "yes",
},
Matchers: []framework.ResourceMatcher{
&framework.AndSelector{
TemplateData: &templateStruct{
GVK: "apps/v1/Deployment",
Name: "prime-target",
},
Matchers: []framework.ResourceMatcher{
framework.GVKMatcher("{{.GVK}}"),
framework.NameMatcher("{{.Name}}"),
},
},
&framework.OrSelector{
Matchers: []framework.ResourceMatcher{
framework.LabelMatcher(map[string]string{
"select": "{{.LabelValue}}",
}),
framework.AnnotationMatcher(map[string]string{
"example.io/select": "{{.AnnotationValue}}",
}),
},
},
},
}
input, err := kio.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: prime-target
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: exclude-one
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: exclude-two
labels:
select: no
---
apiVersion: v1
kind: ConfigMap
metadata:
name: extra-target
labels:
select: yes
---
apiVersion: apps/v1
kind: ConfigMap
metadata:
name: prime-target
data:
shouldSelect: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-one
labels:
select: yes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-two
annotations:
example.io/select: yes
`))
require.NoError(t, err)
result, err := s.Filter(input)
require.NoError(t, err)
expected := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: prime-target
---
apiVersion: v1
kind: ConfigMap
metadata:
name: extra-target
labels:
select: yes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-one
labels:
select: yes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: extra-target-two
annotations:
example.io/select: yes
`
resultStr, err := kio.StringAll(result)
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(resultStr))
}
func TestMatchersAsFilters(t *testing.T) {
input, err := kio.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: target
labels:
select: me
---
apiVersion: extensions/v1beta2
kind: Deployment
metadata:
name: exclude
labels:
select: no
`))
require.NoError(t, err)
expected := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: target
labels:
select: me
`
matchers := map[string]framework.ResourceMatcher{
"slice": framework.NameMatcher("target"),
"map": framework.LabelMatcher(map[string]string{"select": "me"}),
"func": framework.ResourceMatcherFunc(func(node *yaml.RNode) bool {
v := node.Field("apiVersion").Value
return strings.TrimSpace(v.MustString()) == "apps/v1"
}),
}
for desc, m := range matchers {
matcher := m
t.Run(desc, func(t *testing.T) {
result, err := matcher.Filter(input)
require.NoError(t, err)
resultStr, err := kio.StringAll(result)
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(resultStr))
})
}
}

View File

@@ -0,0 +1,80 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
"bytes"
"strings"
"text/template"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// ResourceTemplate generates resources from templates.
type ResourceTemplate struct {
// Templates is a function that returns a list of templates to render into one or more resources.
Templates TemplatesFunc
// TemplateData is the data to use when rendering the templates provided by the Templates field.
TemplateData interface{}
}
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
// been set.
func (rt *ResourceTemplate) DefaultTemplateData(data interface{}) {
if rt.TemplateData == nil {
rt.TemplateData = data
}
}
// Render renders the Templates into resource nodes using TemplateData.
func (rt *ResourceTemplate) Render() ([]*yaml.RNode, error) {
var items []*yaml.RNode
if rt.Templates == nil {
return items, nil
}
templates, err := rt.Templates()
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to retrieve ResourceTemplates")
}
for i := range templates {
newItems, err := rt.doTemplate(templates[i])
if err != nil {
return nil, err
}
items = append(items, newItems...)
}
return items, nil
}
func (rt *ResourceTemplate) doTemplate(t *template.Template) ([]*yaml.RNode, error) {
// invoke the template
var b bytes.Buffer
err := t.Execute(&b, rt.TemplateData)
if err != nil {
return nil, errors.WrapPrefixf(err, "failed to render template %v", t.DefinedTemplates())
}
var items []*yaml.RNode
// split the resources so the error messaging is better
for _, s := range strings.Split(b.String(), "\n---\n") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
newItems, err := (&kio.ByteReader{Reader: bytes.NewBufferString(s)}).Read()
if err != nil {
return nil, errors.WrapPrefixf(err,
"failed to parse rendered template into a resource:\n%s\n", addLineNumbers(s))
}
items = append(items, newItems...)
}
return items, nil
}

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