mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-15 02:48:16 +00:00
Compare commits
78 Commits
api/v0.8.2
...
api/v0.8.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81cac9b633 | ||
|
|
43edc6dd7f | ||
|
|
f313cca52b | ||
|
|
243e7cca1f | ||
|
|
b9c36caa1c | ||
|
|
711b4ff4bb | ||
|
|
8d72528eb5 | ||
|
|
6590cce5c1 | ||
|
|
12c0360ba3 | ||
|
|
8e8fa5409d | ||
|
|
5af35f4f1a | ||
|
|
412e73cf76 | ||
|
|
ec27642e2f | ||
|
|
7165b1ec40 | ||
|
|
6dd50de7a4 | ||
|
|
a8b851f84a | ||
|
|
9c4966ccc8 | ||
|
|
d0bb1cd0fa | ||
|
|
102cf87f36 | ||
|
|
584a6c2a86 | ||
|
|
03c6f8fff4 | ||
|
|
90de9b78df | ||
|
|
34f1f2967e | ||
|
|
9a9df7436e | ||
|
|
c036830c70 | ||
|
|
ebbd0c7b5a | ||
|
|
7264a3a65d | ||
|
|
f3a958bbf7 | ||
|
|
14bf6f8a27 | ||
|
|
60c8a0498b | ||
|
|
774d768e7b | ||
|
|
efef397acf | ||
|
|
5793653630 | ||
|
|
4ee3d05bd8 | ||
|
|
a1df3e030f | ||
|
|
4e0332551a | ||
|
|
216ab488a6 | ||
|
|
722b0131f0 | ||
|
|
93dd571df9 | ||
|
|
a7000dd9c6 | ||
|
|
5c4b5b1bf0 | ||
|
|
8e57ee9111 | ||
|
|
60bd8d15d9 | ||
|
|
1d524b6fbe | ||
|
|
e9c97a4c4e | ||
|
|
48c89cb698 | ||
|
|
af1e692a5e | ||
|
|
57e7db0423 | ||
|
|
7fb6fa0f35 | ||
|
|
50c3875354 | ||
|
|
efc03bf329 | ||
|
|
9785bda7be | ||
|
|
29bfdfc1ef | ||
|
|
4f72cb8d00 | ||
|
|
a45e90b1e4 | ||
|
|
6b6bc45f2c | ||
|
|
e4a34f2a48 | ||
|
|
4a2ed901b3 | ||
|
|
ba67bc0f18 | ||
|
|
be8d60fb9f | ||
|
|
d9d5bb83f0 | ||
|
|
cfa7645d3b | ||
|
|
2e6ef91a7c | ||
|
|
508f294e0c | ||
|
|
a81ebe9842 | ||
|
|
c92fb809c6 | ||
|
|
043e8c36e5 | ||
|
|
7965195c29 | ||
|
|
4263d18c1a | ||
|
|
827fb1e1da | ||
|
|
03c77cee9b | ||
|
|
2db34e7127 | ||
|
|
821b14bfd1 | ||
|
|
33b4735f98 | ||
|
|
bbebd1e56a | ||
|
|
c9d9348944 | ||
|
|
555c4cb279 | ||
|
|
3da90dbde7 |
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
10
api/go.mod
10
api/go.mod
@@ -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.11
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.15
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
38
api/go.sum
38
api/go.sum
@@ -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.11 h1:7KzaMS6TtwhGQW6hkw8umC2zFtSGAQPdAMagfautnkw=
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.11/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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.11 h1:7KzaMS6TtwhGQW6hkw8umC2zFtSGAQPdAMagfautnkw=
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.11/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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
39
api/krusty/duplicatekeys_test.go
Normal file
39
api/krusty/duplicatekeys_test.go
Normal 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())
|
||||
}
|
||||
32
api/krusty/multibytecharacter_test.go
Normal file
32
api/krusty/multibytecharacter_test.go
Normal 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
|
||||
`)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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.11
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.15
|
||||
)
|
||||
|
||||
@@ -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.11 h1:7KzaMS6TtwhGQW6hkw8umC2zFtSGAQPdAMagfautnkw=
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.11/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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -57,7 +57,6 @@ var (
|
||||
|
||||
// int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster
|
||||
// than operations on inf.Dec for values that can be represented as int64.
|
||||
// +k8s:openapi-gen=true
|
||||
type int64Amount struct {
|
||||
value int64
|
||||
scale Scale
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -86,8 +86,6 @@ import (
|
||||
// +protobuf.embed=string
|
||||
// +protobuf.options.marshal=false
|
||||
// +protobuf.options.(gogoproto.goproto_stringer)=false
|
||||
// +k8s:deepcopy-gen=true
|
||||
// +k8s:openapi-gen=true
|
||||
type Quantity struct {
|
||||
// i is the quantity in int64 scaled form, if d.Dec == nil
|
||||
i int64Amount
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,22 +97,25 @@ 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()
|
||||
// Disallow recursive generation.
|
||||
if strings.HasPrefix(l, "//go:generate") {
|
||||
if strings.HasPrefix(l, "//go:generate") ||
|
||||
strings.HasPrefix(l, "// +k8s:") {
|
||||
continue
|
||||
}
|
||||
// Don't want it to appear double generated.
|
||||
// When copying generated code, drop the old 'generated' message.
|
||||
if strings.HasPrefix(l, "// Code generated") {
|
||||
continue
|
||||
}
|
||||
// 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)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module sigs.k8s.io/kustomize/cmd/mdtogo
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
@@ -1,14 +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.1
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.11
|
||||
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
|
||||
|
||||
@@ -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,12 +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/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.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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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.1
|
||||
sigs.k8s.io/kustomize/cmd/config v0.9.3
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.11
|
||||
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
|
||||
)
|
||||
|
||||
@@ -19,5 +19,3 @@ exclude (
|
||||
)
|
||||
|
||||
replace sigs.k8s.io/kustomize/api => ../api
|
||||
|
||||
replace sigs.k8s.io/kustomize/kustomize/v4/commands => ./commands
|
||||
|
||||
@@ -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,14 +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/cmd/config v0.9.3 h1:hzpeK4tiWUsGhazh8HT9fWU5/wj0RTZmH2RcCJ1c18w=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.9.3/go.mod h1:UvkycBEfyyVK49Xiy546K2y9GsxFgWYQ/sGZQ8eCQAM=
|
||||
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/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=
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 \
|
||||
|
||||
166
kyaml/fn/framework/command/command.go
Normal file
166
kyaml/fn/framework/command/command.go
Normal 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)
|
||||
}
|
||||
164
kyaml/fn/framework/command/command_test.go
Normal file
164
kyaml/fn/framework/command/command_test.go
Normal 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()))
|
||||
}
|
||||
90
kyaml/fn/framework/command/doc.go
Normal file
90
kyaml/fn/framework/command/doc.go
Normal 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
|
||||
382
kyaml/fn/framework/command/example_test.go
Normal file
382
kyaml/fn/framework/command/example_test.go
Normal 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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
9
kyaml/fn/framework/example/input/cm.yaml
Normal file
9
kyaml/fn/framework/example/input/cm.yaml
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package example2
|
||||
@@ -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()))
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
349
kyaml/fn/framework/matchers.go
Normal file
349
kyaml/fn/framework/matchers.go
Normal 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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
410
kyaml/fn/framework/processors.go
Normal file
410
kyaml/fn/framework/processors.go
Normal 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
|
||||
}
|
||||
}
|
||||
527
kyaml/fn/framework/processors_test.go
Normal file
527
kyaml/fn/framework/processors_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
219
kyaml/fn/framework/selector.go
Normal file
219
kyaml/fn/framework/selector.go
Normal 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{}
|
||||
465
kyaml/fn/framework/selector_test.go
Normal file
465
kyaml/fn/framework/selector_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
80
kyaml/fn/framework/template.go
Normal file
80
kyaml/fn/framework/template.go
Normal 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
Reference in New Issue
Block a user