diff --git a/cmd/config/go.mod b/cmd/config/go.mod index 3743484ce..4aaa5a493 100644 --- a/cmd/config/go.mod +++ b/cmd/config/go.mod @@ -3,15 +3,12 @@ module sigs.k8s.io/kustomize/cmd/config go 1.13 require ( - github.com/coreos/go-etcd v2.0.0+incompatible // indirect - github.com/cpuguy83/go-md2man v1.0.10 // indirect github.com/go-errors/errors v1.0.1 github.com/olekukonko/tablewriter v0.0.4 github.com/posener/complete/v2 v2.0.1-alpha.12 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.4.0 - github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect k8s.io/apimachinery v0.17.0 sigs.k8s.io/kustomize/kyaml v0.0.0 ) diff --git a/cmd/config/go.sum b/cmd/config/go.sum index 3dc08d7c2..39bcb6a44 100644 --- a/cmd/config/go.sum +++ b/cmd/config/go.sum @@ -23,11 +23,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -161,7 +159,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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= @@ -171,15 +168,13 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +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/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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= @@ -191,12 +186,10 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= @@ -205,12 +198,9 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -218,29 +208,24 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -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-20190620200207-3b0461eec859/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 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -254,11 +239,6 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200430040329-4b814e061378/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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= diff --git a/cmd/config/internal/commands/run-fns.go b/cmd/config/internal/commands/run-fns.go index 38ba38190..4dfbc3e05 100644 --- a/cmd/config/internal/commands/run-fns.go +++ b/cmd/config/internal/commands/run-fns.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/kio/filters" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" "sigs.k8s.io/kustomize/kyaml/runfn" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -208,10 +208,10 @@ data: {} return []*yaml.RNode{rc}, nil } -func toStorageMounts(mounts []string) []filters.StorageMount { - var sms []filters.StorageMount +func toStorageMounts(mounts []string) []runtimeutil.StorageMount { + var sms []runtimeutil.StorageMount for _, mount := range mounts { - sms = append(sms, filters.StringToStorageMount(mount)) + sms = append(sms, runtimeutil.StringToStorageMount(mount)) } return sms } diff --git a/kyaml/fn/runtime/container/container.go b/kyaml/fn/runtime/container/container.go new file mode 100644 index 000000000..5e205f368 --- /dev/null +++ b/kyaml/fn/runtime/container/container.go @@ -0,0 +1,202 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package container + +import ( + "fmt" + "os" + "strings" + + runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// Filter filters Resources using a container image. +// The container must start a process that reads the list of +// input Resources from stdin, reads the Configuration from the env +// API_CONFIG, and writes the filtered Resources to stdout. +// If there is a error or validation failure, the process must exit +// non-zero. +// The full set of environment variables from the parent process +// are passed to the container. +// +// Function Scoping: +// Filter applies the function only to Resources to which it is scoped. +// +// Resources are scoped to a function if any of the following are true: +// - the Resource were read from the same directory as the function config +// - the Resource were read from a subdirectory of the function config directory +// - the function config is in a directory named "functions" and +// they were read from a subdirectory of "functions" parent +// - the function config doesn't have a path annotation (considered globally scoped) +// - the Filter has GlobalScope == true +// +// In Scope Examples: +// +// Example 1: deployment.yaml and service.yaml in function.yaml scope +// same directory as the function config directory +// . +// ├── function.yaml +// ├── deployment.yaml +// └── service.yaml +// +// Example 2: apps/deployment.yaml and apps/service.yaml in function.yaml scope +// subdirectory of the function config directory +// . +// ├── function.yaml +// └── apps +//    ├── deployment.yaml +//    └── service.yaml +// +// Example 3: apps/deployment.yaml and apps/service.yaml in functions/function.yaml scope +// function config is in a directory named "functions" +// . +// ├── functions +// │   └── function.yaml +// └── apps +//    ├── deployment.yaml +//    └── service.yaml +// +// Out of Scope Examples: +// +// Example 1: apps/deployment.yaml and apps/service.yaml NOT in stuff/function.yaml scope +// . +// ├── stuff +// │   └── function.yaml +// └── apps +//    ├── deployment.yaml +//    └── service.yaml +// +// Example 2: apps/deployment.yaml and apps/service.yaml NOT in stuff/functions/function.yaml scope +// . +// ├── stuff +// │   └── functions +// │    └── function.yaml +// └── apps +//    ├── deployment.yaml +//    └── service.yaml +// +// Default Paths: +// Resources emitted by functions will have default path applied as annotations +// if none is present. +// The default path will be the function-dir/ (or parent directory in the case of "functions") +// + function-file-name/ + namespace/ + kind_name.yaml +// +// Example 1: Given a function in fn.yaml that produces a Deployment name foo and a Service named bar +// dir +// └── fn.yaml +// +// Would default newly generated Resources to: +// +// dir +// ├── fn.yaml +// └── fn +//    ├── deployment_foo.yaml +//    └── service_bar.yaml +// +// Example 2: Given a function in functions/fn.yaml that produces a Deployment name foo and a Service named bar +// dir +// └── fn.yaml +// +// Would default newly generated Resources to: +// +// dir +// ├── functions +// │   └── fn.yaml +// └── fn +//    ├── deployment_foo.yaml +//    └── service_bar.yaml +// +// Example 3: Given a function in fn.yaml that produces a Deployment name foo, namespace baz and a Service named bar namespace baz +// dir +// └── fn.yaml +// +// Would default newly generated Resources to: +// +// dir +// ├── fn.yaml +// └── fn +// └── baz +//    ├── deployment_foo.yaml +//    └── service_bar.yaml +type Filter struct { + + // Image is the container image to use to create a container. + Image string `yaml:"image,omitempty"` + + // Network is the container network to use. + Network string `yaml:"network,omitempty"` + + // StorageMounts is a list of storage options that the container will have mounted. + StorageMounts []runtimeutil.StorageMount `yaml:"mounts,omitempty"` + + Exec runtimeexec.Filter +} + +func (c Filter) String() string { + if c.Exec.DeferFailure { + return fmt.Sprintf("%s deferFailure: %v", c.Image, c.Exec.DeferFailure) + } + return c.Image +} +func (c Filter) GetExit() error { + return c.Exec.GetExit() +} + +func (c *Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { + c.setupExec() + return c.Exec.Filter(nodes) +} + +func (c *Filter) setupExec() { + // don't init 2x + if c.Exec.Path != "" { + return + } + + path, args := c.getCommand() + c.Exec.Path = path + c.Exec.Args = args +} + +// getArgs returns the command + args to run to spawn the container +func (c *Filter) getCommand() (string, []string) { + // run the container using docker. this is simpler than using the docker + // libraries, and ensures things like auth work the same as if the container + // was run from the cli. + + network := "none" + if c.Network != "" { + network = c.Network + } + + args := []string{"run", + "--rm", // delete the container afterward + "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", // attach stdin, stdout, stderr + "--network", network, + + // added security options + "--user", "nobody", // run as nobody + "--security-opt=no-new-privileges", // don't allow the user to escalate privileges + // note: don't make fs readonly because things like heredoc rely on writing tmp files + } + + // TODO(joncwong): Allow StorageMount fields to have default values. + for _, storageMount := range c.StorageMounts { + args = append(args, "--mount", storageMount.String()) + } + + os.Setenv("LOG_TO_STDERR", "true") + os.Setenv("STRUCTURED_RESULTS", "true") + + // export the local environment vars to the container + for _, pair := range os.Environ() { + args = append(args, "-e", strings.Split(pair, "=")[0]) + } + a := append(args, c.Image) + + return "docker", a +} diff --git a/kyaml/fn/runtime/container/container_test.go b/kyaml/fn/runtime/container/container_test.go new file mode 100644 index 000000000..1c84eda6c --- /dev/null +++ b/kyaml/fn/runtime/container/container_test.go @@ -0,0 +1,203 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package container + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestFilter_setupExec(t *testing.T) { + var tests = []struct { + name string + functionConfig string + expectedArgs []string + instance Filter + }{ + { + name: "command", + functionConfig: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo +`, + expectedArgs: []string{ + "run", + "--rm", + "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", + "--network", "none", + "--user", "nobody", + "--security-opt=no-new-privileges", + }, + instance: Filter{Image: "example.com:version"}, + }, + + { + name: "network", + functionConfig: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo +`, + expectedArgs: []string{ + "run", + "--rm", + "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", + "--network", "test-1", + "--user", "nobody", + "--security-opt=no-new-privileges", + }, + instance: Filter{Image: "example.com:version", Network: "test-1"}, + }, + + { + name: "storage_mounts", + functionConfig: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo +`, + expectedArgs: []string{ + "run", + "--rm", + "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", + "--network", "none", + "--user", "nobody", + "--security-opt=no-new-privileges", + "--mount", fmt.Sprintf("type=%s,src=%s,dst=%s:ro", "bind", "/mount/path", "/local/"), + "--mount", fmt.Sprintf("type=%s,src=%s,dst=%s:ro", "volume", "myvol", "/local/"), + "--mount", fmt.Sprintf("type=%s,src=%s,dst=%s:ro", "tmpfs", "", "/local/"), + }, + instance: Filter{ + Image: "example.com:version", + StorageMounts: []runtimeutil.StorageMount{ + {MountType: "bind", Src: "/mount/path", DstPath: "/local/"}, + {MountType: "volume", Src: "myvol", DstPath: "/local/"}, + {MountType: "tmpfs", Src: "", DstPath: "/local/"}, + }, + }, + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + cfg, err := yaml.Parse(tt.functionConfig) + if !assert.NoError(t, err) { + t.FailNow() + } + tt.instance.Exec.FunctionConfig = cfg + + os.Setenv("KYAML_TEST", "FOO") + tt.instance.setupExec() + + // configure expected env + for _, e := range os.Environ() { + // the process env + tt.expectedArgs = append(tt.expectedArgs, "-e", strings.Split(e, "=")[0]) + } + tt.expectedArgs = append(tt.expectedArgs, tt.instance.Image) + + if !assert.Equal(t, "docker", tt.instance.Exec.Path) { + t.FailNow() + } + if !assert.Equal(t, tt.expectedArgs, tt.instance.Exec.Args) { + t.FailNow() + } + }) + } +} + +func TestFilter_Filter(t *testing.T) { + cfg, err := yaml.Parse(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo +`) + if !assert.NoError(t, err) { + return + } + + input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-foo +--- +apiVersion: v1 +kind: Service +metadata: + name: service-foo +`)}).Read() + if !assert.NoError(t, err) { + return + } + + instance := Filter{} + instance.Exec.FunctionConfig = cfg + instance.Exec.Path = "sed" + instance.Exec.Args = []string{"s/Deployment/StatefulSet/g"} + output, err := instance.Filter(input) + if !assert.NoError(t, err) { + t.FailNow() + } + + b := &bytes.Buffer{} + err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(output) + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, `apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: deployment-foo + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'statefulset_deployment-foo.yaml' +--- +apiVersion: v1 +kind: Service +metadata: + name: service-foo + annotations: + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'service_service-foo.yaml' +`, b.String()) { + t.FailNow() + } +} +func TestFilter_String(t *testing.T) { + instance := Filter{Image: "foo"} + if !assert.Equal(t, "foo", instance.String()) { + t.FailNow() + } + + instance.Exec.DeferFailure = true + if !assert.Equal(t, "foo deferFailure: true", instance.String()) { + t.FailNow() + } +} + +func TestFilter_ExitCode(t *testing.T) { + instance := Filter{} + instance.Exec.Path = "/not/real/command" + instance.Exec.DeferFailure = true + _, err := instance.Filter(nil) + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.EqualError(t, instance.GetExit(), "fork/exec /not/real/command: no such file or directory") { + t.FailNow() + } +} diff --git a/kyaml/kio/filters/functiontypes.go b/kyaml/fn/runtime/runtimeutil/functiontypes.go similarity index 70% rename from kyaml/kio/filters/functiontypes.go rename to kyaml/fn/runtime/runtimeutil/functiontypes.go index 212cb589c..2bec8786f 100644 --- a/kyaml/kio/filters/functiontypes.go +++ b/kyaml/fn/runtime/runtimeutil/functiontypes.go @@ -1,9 +1,12 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package filters +package runtimeutil import ( + "fmt" + "strings" + "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -72,6 +75,10 @@ type StorageMount struct { DstPath string `json:"dst,omitempty" yaml:"dst,omitempty"` } +func (s *StorageMount) String() string { + return fmt.Sprintf("type=%s,src=%s,dst=%s:ro", s.MountType, s.Src, s.DstPath) +} + // GetFunctionSpec returns the FunctionSpec for a resource. Returns // nil if the resource does not have a FunctionSpec. // @@ -119,3 +126,52 @@ func getFunctionSpecFromAnnotation(n *yaml.RNode, meta yaml.ResourceMeta) *Funct _ = yaml.Unmarshal([]byte(s), &fs) return &fs } + +func StringToStorageMount(s string) StorageMount { + m := make(map[string]string) + options := strings.Split(s, ",") + for _, option := range options { + keyVal := strings.SplitN(option, "=", 2) + m[keyVal[0]] = keyVal[1] + } + var sm StorageMount + for key, value := range m { + switch { + case key == "type": + sm.MountType = value + case key == "src": + sm.Src = value + case key == "dst": + sm.DstPath = value + } + } + return sm +} + +// IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource. +// Resources with an apiVersion starting with '*.gcr.io', 'gcr.io' or 'docker.io' are considered +// Reconciler Resources. +type IsReconcilerFilter struct { + // ExcludeReconcilers if set to true, then Reconcilers will be excluded -- e.g. + // Resources with a reconcile container through the apiVersion (gcr.io prefix) or + // through the annotations + ExcludeReconcilers bool `yaml:"excludeReconcilers,omitempty"` + + // IncludeNonReconcilers if set to true, the NonReconciler will be included. + IncludeNonReconcilers bool `yaml:"includeNonReconcilers,omitempty"` +} + +// Filter implements kio.Filter +func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) { + var out []*yaml.RNode + for i := range inputs { + isFnResource := GetFunctionSpec(inputs[i]) != nil + if isFnResource && !c.ExcludeReconcilers { + out = append(out, inputs[i]) + } + if !isFnResource && c.IncludeNonReconcilers { + out = append(out, inputs[i]) + } + } + return out, nil +} diff --git a/kyaml/fn/runtime/runtimeutil/runtimeutil_test.go b/kyaml/fn/runtime/runtimeutil/runtimeutil_test.go index dc12fa762..883264d89 100644 --- a/kyaml/fn/runtime/runtimeutil/runtimeutil_test.go +++ b/kyaml/fn/runtime/runtimeutil/runtimeutil_test.go @@ -996,3 +996,272 @@ metadata: }) } } + +func Test_GetFunction(t *testing.T) { + var tests = []struct { + name string + resource string + expectedFn string + missingFn bool + }{ + + // fn annotation + { + name: "fn annotation", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + container: + image: foo:v1.0.0 +`, + expectedFn: ` +container: + image: foo:v1.0.0`, + }, + + { + name: "storage mounts json style", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + container: + image: foo:v1.0.0 + mounts: [ {type: bind, src: /mount/path, dst: /local/}, {src: myvol, dst: /local/, type: volume}, {dst: /local/, type: tmpfs} ] +`, + expectedFn: ` +container: + image: foo:v1.0.0 + mounts: + - type: bind + src: /mount/path + dst: /local/ + - type: volume + src: myvol + dst: /local/ + - type: tmpfs + dst: /local/ +`, + }, + + { + name: "storage mounts yaml style", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + container: + image: foo:v1.0.0 + mounts: + - src: /mount/path + type: bind + dst: /local/ + - dst: /local/ + src: myvol + type: volume + - type: tmpfs + dst: /local/ +`, + expectedFn: ` +container: + image: foo:v1.0.0 + mounts: + - type: bind + src: /mount/path + dst: /local/ + - type: volume + src: myvol + dst: /local/ + - type: tmpfs + dst: /local/ +`, + }, + + { + name: "network", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + container: + image: foo:v1.0.0 + network: + required: true +`, + expectedFn: ` +container: + image: foo:v1.0.0 + network: + required: true +`, + }, + + { + name: "path", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + path: foo + container: + image: foo:v1.0.0 +`, + // path should be erased + expectedFn: ` +container: + image: foo:v1.0.0 +`, + }, + + { + name: "network", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + network: foo + container: + image: foo:v1.0.0 +`, + // network should be erased + expectedFn: ` +container: + image: foo:v1.0.0 +`, + }, + + // legacy fn style + {name: "legacy fn meta", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + configFn: + container: + image: foo:v1.0.0 +`, + expectedFn: ` +container: + image: foo:v1.0.0 +`, + }, + + // no fn + {name: "no fn", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: {} +`, + missingFn: true, + }, + + // test network, etc... + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + resource := yaml.MustParse(tt.resource) + fn := GetFunctionSpec(resource) + if tt.missingFn { + if !assert.Nil(t, fn) { + t.FailNow() + } + } else { + b, err := yaml.Marshal(fn) + if !assert.NoError(t, err) { + t.FailNow() + } + if !assert.Equal(t, + strings.TrimSpace(tt.expectedFn), + strings.TrimSpace(string(b))) { + t.FailNow() + } + } + }) + } +} + +func Test_GetContainerNetworkRequired(t *testing.T) { + tests := []struct { + input string + required bool + }{ + { + input: `apiVersion: v1 +kind: Foo +metadata: + name: foo + configFn: + container: + image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 + network: + required: true +`, + required: true, + }, + { + input: `apiVersion: v1 +kind: Foo +metadata: + name: foo + configFn: + container: + image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 + network: + required: false +`, + required: false, + }, + { + + input: `apiVersion: v1 +kind: Foo +metadata: + name: foo + configFn: + container: + image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 +`, + required: false, + }, + { + input: `apiVersion: v1 +kind: Foo +metadata: + name: foo + annotations: + config.kubernetes.io/function: | + container: + image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 + network: + required: true +`, + required: true, + }, + } + + for _, tc := range tests { + cfg, err := yaml.Parse(tc.input) + if !assert.NoError(t, err) { + return + } + fn := GetFunctionSpec(cfg) + assert.Equal(t, tc.required, fn.Container.Network.Required) + } +} diff --git a/kyaml/kio/filters/container.go b/kyaml/kio/filters/container.go deleted file mode 100644 index 8c30d0225..000000000 --- a/kyaml/kio/filters/container.go +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package filters - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path" - "strings" - - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/kio/kioutil" - - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// ContainerFilter filters Resources using a container image. -// The container must start a process that reads the list of -// input Resources from stdin, reads the Configuration from the env -// API_CONFIG, and writes the filtered Resources to stdout. -// If there is a error or validation failure, the process must exit -// non-zero. -// The full set of environment variables from the parent process -// are passed to the container. -// -// Function Scoping: -// ContainerFilter applies the function only to Resources to which it is scoped. -// -// Resources are scoped to a function if any of the following are true: -// - the Resource were read from the same directory as the function config -// - the Resource were read from a subdirectory of the function config directory -// - the function config is in a directory named "functions" and -// they were read from a subdirectory of "functions" parent -// - the function config doesn't have a path annotation (considered globally scoped) -// - the ContainerFilter has GlobalScope == true -// -// In Scope Examples: -// -// Example 1: deployment.yaml and service.yaml in function.yaml scope -// same directory as the function config directory -// . -// ├── function.yaml -// ├── deployment.yaml -// └── service.yaml -// -// Example 2: apps/deployment.yaml and apps/service.yaml in function.yaml scope -// subdirectory of the function config directory -// . -// ├── function.yaml -// └── apps -//    ├── deployment.yaml -//    └── service.yaml -// -// Example 3: apps/deployment.yaml and apps/service.yaml in functions/function.yaml scope -// function config is in a directory named "functions" -// . -// ├── functions -// │   └── function.yaml -// └── apps -//    ├── deployment.yaml -//    └── service.yaml -// -// Out of Scope Examples: -// -// Example 1: apps/deployment.yaml and apps/service.yaml NOT in stuff/function.yaml scope -// . -// ├── stuff -// │   └── function.yaml -// └── apps -//    ├── deployment.yaml -//    └── service.yaml -// -// Example 2: apps/deployment.yaml and apps/service.yaml NOT in stuff/functions/function.yaml scope -// . -// ├── stuff -// │   └── functions -// │    └── function.yaml -// └── apps -//    ├── deployment.yaml -//    └── service.yaml -// -// Default Paths: -// Resources emitted by functions will have default path applied as annotations -// if none is present. -// The default path will be the function-dir/ (or parent directory in the case of "functions") -// + function-file-name/ + namespace/ + kind_name.yaml -// -// Example 1: Given a function in fn.yaml that produces a Deployment name foo and a Service named bar -// dir -// └── fn.yaml -// -// Would default newly generated Resources to: -// -// dir -// ├── fn.yaml -// └── fn -//    ├── deployment_foo.yaml -//    └── service_bar.yaml -// -// Example 2: Given a function in functions/fn.yaml that produces a Deployment name foo and a Service named bar -// dir -// └── fn.yaml -// -// Would default newly generated Resources to: -// -// dir -// ├── functions -// │   └── fn.yaml -// └── fn -//    ├── deployment_foo.yaml -//    └── service_bar.yaml -// -// Example 3: Given a function in fn.yaml that produces a Deployment name foo, namespace baz and a Service named bar namespace baz -// dir -// └── fn.yaml -// -// Would default newly generated Resources to: -// -// dir -// ├── fn.yaml -// └── fn -// └── baz -//    ├── deployment_foo.yaml -//    └── service_bar.yaml -type ContainerFilter struct { - - // Image is the container image to use to create a container. - Image string `yaml:"image,omitempty"` - - // Network is the container network to use. - Network string `yaml:"network,omitempty"` - - // StorageMounts is a list of storage options that the container will have mounted. - StorageMounts []StorageMount `yaml:"mounts,omitempty"` - - // Config is the API configuration for the container and passed through the - // API_CONFIG env var to the container. - // Typically a Kubernetes style Resource Config. - Config *yaml.RNode `yaml:"config,omitempty"` - - // GlobalScope will cause the function to be run against all input - // nodes instead of only nodes scoped under the function. - GlobalScope bool - - ResultsFile string - - Results *yaml.RNode - - DeferFailure bool - - Exit error - - // SetFlowStyleForConfig sets the style for config to Flow when serializing it - SetFlowStyleForConfig bool - - // args may be specified by tests to override how a container is spawned - args []string - - checkInput func(string) -} - -func (c ContainerFilter) GetExit() error { - return c.Exit -} - -type DeferFailureFunction interface { - GetExit() error -} - -func (c ContainerFilter) String() string { - if c.DeferFailure { - return fmt.Sprintf("%s deferFailure: %v", c.Image, c.DeferFailure) - } - return c.Image -} - -func (s *StorageMount) String() string { - return fmt.Sprintf("type=%s,src=%s,dst=%s:ro", s.MountType, s.Src, s.DstPath) -} - -func StringToStorageMount(s string) StorageMount { - m := make(map[string]string) - options := strings.Split(s, ",") - for _, option := range options { - keyVal := strings.SplitN(option, "=", 2) - m[keyVal[0]] = keyVal[1] - } - var sm StorageMount - for key, value := range m { - switch { - case key == "type": - sm.MountType = value - case key == "src": - sm.Src = value - case key == "dst": - sm.DstPath = value - } - } - return sm -} - -// functionsDirectoryName is keyword directory name for functions scoped 1 directory higher -const functionsDirectoryName = "functions" - -// getFunctionScope returns the path of the directory containing the function config, -// or its parent directory if the base directory is named "functions" -func (c *ContainerFilter) getFunctionScope() (string, error) { - m, err := c.Config.GetMeta() - if err != nil { - return "", errors.Wrap(err) - } - p, found := m.Annotations[kioutil.PathAnnotation] - if !found { - return "", nil - } - - functionDir := path.Clean(path.Dir(p)) - - if path.Base(functionDir) == functionsDirectoryName { - // the scope of functions in a directory called "functions" is 1 level higher - // this is similar to how the golang "internal" directory scoping works - functionDir = path.Dir(functionDir) - } - return functionDir, nil -} - -// scope partitions the input nodes into 2 slices. The first slice contains only Resources -// which are scoped under dir, and the second slice contains the Resources which are not. -func (c *ContainerFilter) scope(dir string, nodes []*yaml.RNode) ([]*yaml.RNode, []*yaml.RNode, error) { - // scope container filtered Resources to Resources under that directory - var input, saved []*yaml.RNode - if c.GlobalScope { - return nodes, nil, nil - } - - // global function - if dir == "" || dir == "." { - return nodes, nil, nil - } - - // identify Resources read from directories under the function configuration - for i := range nodes { - m, err := nodes[i].GetMeta() - if err != nil { - return nil, nil, err - } - p, found := m.Annotations[kioutil.PathAnnotation] - if !found { - // this Resource isn't scoped under the function -- don't know where it came from - // consider it out of scope - saved = append(saved, nodes[i]) - continue - } - - resourceDir := path.Clean(path.Dir(p)) - if path.Base(resourceDir) == functionsDirectoryName { - // Functions in the `functions` directory are scoped to - // themselves, and should see themselves as input - resourceDir = path.Dir(resourceDir) - } - if !strings.HasPrefix(resourceDir, dir) { - // this Resource doesn't fall under the function scope if it - // isn't in a subdirectory of where the function lives - saved = append(saved, nodes[i]) - continue - } - - // this input is scoped under the function - input = append(input, nodes[i]) - } - - return input, saved, nil -} - -// GrepFilter implements kio.GrepFilter -func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { - // get the command to filter the Resources - cmd := c.getCommand() - - in := &bytes.Buffer{} - out := &bytes.Buffer{} - - // only process Resources scoped to this function, save the others - functionDir, err := c.getFunctionScope() - if err != nil { - return nil, err - } - input, saved, err := c.scope(functionDir, nodes) - if err != nil { - return nil, err - } - - // write the input - err = kio.ByteWriter{ - WrappingAPIVersion: kio.ResourceListAPIVersion, - WrappingKind: kio.ResourceListKind, - Writer: in, - KeepReaderAnnotations: true, - FunctionConfig: c.Config}.Write(input) - if err != nil { - return nil, err - } - - // capture the command stdout for the return value - r := &kio.ByteReader{Reader: out} - - // do the filtering - if c.checkInput != nil { - c.checkInput(in.String()) - } - cmd.Stdin = in - cmd.Stdout = out - - // don't exit immediately if the function fails -- write out the validation - c.Exit = cmd.Run() - - output, err := r.Read() - if err != nil { - return nil, err - } - - if err := c.doResults(r); err != nil { - return nil, err - } - - if c.Exit != nil && !c.DeferFailure { - return append(output, saved...), c.Exit - } - - // annotate any generated Resources with a path and index if they don't already have one - if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil { - return nil, err - } - - // emit both the Resources output from the function, and the out-of-scope Resources - // which were not provided to the function - return append(output, saved...), nil -} - -func (c *ContainerFilter) doResults(r *kio.ByteReader) error { - // Write the results to a file if configured to do so - if c.ResultsFile != "" && r.Results != nil { - results, err := r.Results.String() - if err != nil { - return err - } - err = ioutil.WriteFile(c.ResultsFile, []byte(results), 0600) - if err != nil { - return err - } - } - - if r.Results != nil { - c.Results = r.Results - } - return nil -} - -// getArgs returns the command + args to run to spawn the container -func (c *ContainerFilter) getArgs() []string { - // run the container using docker. this is simpler than using the docker - // libraries, and ensures things like auth work the same as if the container - // was run from the cli. - - network := "none" - if c.Network != "" { - network = c.Network - } - - args := []string{"docker", "run", - "--rm", // delete the container afterward - "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", // attach stdin, stdout, stderr - - // added security options - "--network", network, - "--user", "nobody", // run as nobody - // don't make fs readonly because things like heredoc rely on writing tmp files - "--security-opt=no-new-privileges", // don't allow the user to escalate privileges - } - - // TODO(joncwong): Allow StorageMount fields to have default values. - for _, storageMount := range c.StorageMounts { - args = append(args, "--mount", storageMount.String()) - } - - // tell functions to write error messages to stderr as well as results - os.Setenv("LOG_TO_STDERR", "true") - os.Setenv("STRUCTURED_RESULTS", "true") - - // export the local environment vars to the container - for _, pair := range os.Environ() { - tokens := strings.Split(pair, "=") - if tokens[0] == "" { - continue - } - args = append(args, "-e", tokens[0]) - } - return append(args, c.Image) -} - -// getCommand returns a command which will apply the Filter using the container image -func (c *ContainerFilter) getCommand() *exec.Cmd { - if c.SetFlowStyleForConfig { - c.Config.YNode().Style = yaml.FlowStyle - } - - if len(c.args) == 0 { - c.args = c.getArgs() - } - - cmd := exec.Command(c.args[0], c.args[1:]...) - cmd.Stderr = os.Stderr - cmd.Env = os.Environ() - - // set stderr for err messaging - return cmd -} - -// IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource. -// Resources with an apiVersion starting with '*.gcr.io', 'gcr.io' or 'docker.io' are considered -// Reconciler Resources. -type IsReconcilerFilter struct { - // ExcludeReconcilers if set to true, then Reconcilers will be excluded -- e.g. - // Resources with a reconcile container through the apiVersion (gcr.io prefix) or - // through the annotations - ExcludeReconcilers bool `yaml:"excludeReconcilers,omitempty"` - - // IncludeNonReconcilers if set to true, the NonReconciler will be included. - IncludeNonReconcilers bool `yaml:"includeNonReconcilers,omitempty"` -} - -// Filter implements kio.Filter -func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) { - var out []*yaml.RNode - for i := range inputs { - isFnResource := GetFunctionSpec(inputs[i]) != nil - if isFnResource && !c.ExcludeReconcilers { - out = append(out, inputs[i]) - } - if !isFnResource && c.IncludeNonReconcilers { - out = append(out, inputs[i]) - } - } - return out, nil -} diff --git a/kyaml/kio/filters/container_test.go b/kyaml/kio/filters/container_test.go deleted file mode 100644 index c960b8b66..000000000 --- a/kyaml/kio/filters/container_test.go +++ /dev/null @@ -1,1654 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package filters - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -func TestContainerFilter_Filter(t *testing.T) { - var tests = []struct { - name string - input []string - expectedOutput []string - expectedError string - expectedSavedError string - expectedResults string - noMakeResultsFile bool - instance ContainerFilter - }{ - { - name: "add_path_annotation", - instance: ContainerFilter{args: []string{ - "echo", ` -apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo -- apiVersion: v1 - kind: Service - metadata: - name: service-foo -`, - }, - }, - expectedOutput: []string{ - ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'deployment_deployment-foo.yaml' -`, - ` -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'service_service-foo.yaml' -`, - }, - }, - - { - name: "write_results", - instance: ContainerFilter{args: []string{ - "echo", ` -apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo -- apiVersion: v1 - kind: Service - metadata: - name: service-foo -results: -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -`, - }, - }, - expectedOutput: []string{ - ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'deployment_deployment-foo.yaml' -`, - ` -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'service_service-foo.yaml' -`, - }, - expectedResults: ` -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -`, - }, - - { - name: "write_results_non_0_exit", - expectedError: "exit status 1", - instance: ContainerFilter{args: []string{"sh", "-c", - `echo ' -apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo -- apiVersion: v1 - kind: Service - metadata: - name: service-foo -results: -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -' && cat not-real-dir -`, - }, - }, - expectedOutput: []string{ - ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'deployment_deployment-foo.yaml' -`, - ` -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'service_service-foo.yaml' -`, - }, - expectedResults: ` -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -`, - }, - - { - name: "write_results_defer_failure", - expectedSavedError: "exit status 1", - instance: ContainerFilter{ - DeferFailure: true, - args: []string{"sh", "-c", - `echo ' -apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo -- apiVersion: v1 - kind: Service - metadata: - name: service-foo -results: -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -' && cat not-real-dir -`, - }, - }, - expectedOutput: []string{ - ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'deployment_deployment-foo.yaml' -`, - ` -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'service_service-foo.yaml' -`, - }, - expectedResults: ` -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -`, - }, - - { - name: "write_results_non_0_exit_missing_file", - expectedError: "open /not/real/file: no such file or directory", - noMakeResultsFile: true, - instance: ContainerFilter{args: []string{"sh", "-c", - `echo ' -apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo -- apiVersion: v1 - kind: Service - metadata: - name: service-foo -results: -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -' && cat not-real-dir -`, - }, - }, - expectedOutput: []string{ - ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'deployment_deployment-foo.yaml' -`, - ` -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'service_service-foo.yaml' -`, - }, - expectedResults: ` -- apiVersion: config.k8s.io/v1alpha1 - kind: ObjectError - name: "some-validator" - items: - - type: error - message: "some message" - resourceRef: - apiVersion: apps/v1 - kind: Deployment - name: foo - namespace: bar - file: - path: deploy.yaml - index: 0 - field: - path: "spec.template.spec.containers[3].resources.limits.cpu" - currentValue: "200" - suggestedValue: "2" -`, - }, - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - if len(tt.expectedResults) > 0 && !tt.noMakeResultsFile { - f, err := ioutil.TempFile("", "test-kyaml-*.yaml") - if !assert.NoError(t, err) { - t.FailNow() - } - defer os.RemoveAll(f.Name()) - tt.instance.ResultsFile = f.Name() - } else if len(tt.expectedResults) > 0 { - tt.instance.ResultsFile = "/not/real/file" - } - - var inputs []*yaml.RNode - for i := range tt.input { - node, err := yaml.Parse(tt.input[i]) - if !assert.NoError(t, err) { - t.FailNow() - } - inputs = append(inputs, node) - } - - output, err := tt.instance.Filter(inputs) - if tt.expectedError != "" { - if !assert.EqualError(t, err, tt.expectedError) { - t.FailNow() - } - return - } - - if tt.expectedSavedError != "" { - if !assert.EqualError(t, tt.instance.Exit, tt.expectedSavedError) { - t.FailNow() - } - return - } - - if !assert.NoError(t, err) { - t.FailNow() - } - - var actual []string - for i := range output { - s, err := output[i].String() - if !assert.NoError(t, err) { - t.FailNow() - } - actual = append(actual, strings.TrimSpace(s)) - } - var expected []string - for i := range tt.expectedOutput { - expected = append(expected, strings.TrimSpace(tt.expectedOutput[i])) - } - - if !assert.Equal(t, expected, actual) { - t.FailNow() - } - - if len(tt.instance.ResultsFile) > 0 { - tt.expectedResults = strings.TrimSpace(tt.expectedResults) - - results, err := tt.instance.Results.String() - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, tt.expectedResults, strings.TrimSpace(results)) { - t.FailNow() - } - - b, err := ioutil.ReadFile(tt.instance.ResultsFile) - writtenResults := strings.TrimSpace(string(b)) - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, tt.expectedResults, writtenResults) { - t.FailNow() - } - } - }) - } -} - -func TestFilter_command(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo -`) - if !assert.NoError(t, err) { - return - } - instance := &ContainerFilter{ - Image: "example.com:version", - Config: cfg, - } - os.Setenv("KYAML_TEST", "FOO") - cmd := instance.getCommand() - - expected := []string{ - "docker", "run", - "--rm", - "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", - "--network", "none", - "--user", "nobody", - "--security-opt=no-new-privileges", - } - for _, e := range os.Environ() { - // the process env - expected = append(expected, "-e", strings.Split(e, "=")[0]) - } - expected = append(expected, "example.com:version") - assert.Equal(t, expected, cmd.Args) - - foundKyaml := false - for _, e := range cmd.Env { - // verify the command has the right environment variables to pass to the container - split := strings.Split(e, "=") - if split[0] == "KYAML_TEST" { - assert.Equal(t, "FOO", split[1]) - foundKyaml = true - } - } - assert.True(t, foundKyaml) -} - -func TestFilter_command_StorageMount(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo -`) - if !assert.NoError(t, err) { - return - } - bindMount := StorageMount{"bind", "/mount/path", "/local/"} - localVol := StorageMount{"volume", "myvol", "/local/"} - tmpfs := StorageMount{"tmpfs", "", "/local/"} - instance := &ContainerFilter{ - Image: "example.com:version", - Config: cfg, - StorageMounts: []StorageMount{bindMount, localVol, tmpfs}, - } - cmd := instance.getCommand() - - expected := []string{ - "docker", "run", - "--rm", - "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", - "--network", "none", - "--user", "nobody", - "--security-opt=no-new-privileges", - "--mount", fmt.Sprintf("type=%s,src=%s,dst=%s:ro", "bind", "/mount/path", "/local/"), - "--mount", fmt.Sprintf("type=%s,src=%s,dst=%s:ro", "volume", "myvol", "/local/"), - "--mount", fmt.Sprintf("type=%s,src=%s,dst=%s:ro", "tmpfs", "", "/local/"), - } - for _, e := range os.Environ() { - // the process env - expected = append(expected, "-e", strings.Split(e, "=")[0]) - } - expected = append(expected, "example.com:version") - assert.Equal(t, expected, cmd.Args) -} - -func TestFilter_command_network(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo -`) - if !assert.NoError(t, err) { - return - } - instance := &ContainerFilter{ - Image: "example.com:version", - Network: "test-net", - Config: cfg, - } - cmd := instance.getCommand() - - expected := []string{ - "docker", "run", - "--rm", - "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", - "--network", "test-net", - "--user", "nobody", - "--security-opt=no-new-privileges", - } - for _, e := range os.Environ() { - // the process env - tokens := strings.Split(e, "=") - if tokens[0] == "" { - continue - } - expected = append(expected, "-e", tokens[0]) - } - expected = append(expected, "example.com:version") - assert.Equal(t, expected, cmd.Args) -} - -func TestFilter_Filter(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo -`)}).Read() - if !assert.NoError(t, err) { - return - } - - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sed", "s/Deployment/StatefulSet/g"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' -- apiVersion: v1 - kind: Service - metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - assert.Equal(t, `apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'statefulset_deployment-foo.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'service_service-foo.yaml' -`, b.String()) -} - -func TestFilter_Filter_noChange(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo -`)}).Read() - if !assert.NoError(t, err) { - return - } - - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sh", "-c", "cat <&0"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' -- apiVersion: v1 - kind: Service - metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - assert.Equal(t, `apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'deployment_deployment-foo.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'service_service-foo.yaml' -`, b.String()) -} - -func Test_GetFunction(t *testing.T) { - var tests = []struct { - name string - resource string - expectedFn string - missingFn bool - }{ - - // fn annotation - { - name: "fn annotation", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: - config.kubernetes.io/function: |- - container: - image: foo:v1.0.0 -`, - expectedFn: ` -container: - image: foo:v1.0.0`, - }, - - { - name: "storage mounts json style", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: - config.kubernetes.io/function: |- - container: - image: foo:v1.0.0 - mounts: [ {type: bind, src: /mount/path, dst: /local/}, {src: myvol, dst: /local/, type: volume}, {dst: /local/, type: tmpfs} ] -`, - expectedFn: ` -container: - image: foo:v1.0.0 - mounts: - - type: bind - src: /mount/path - dst: /local/ - - type: volume - src: myvol - dst: /local/ - - type: tmpfs - dst: /local/ -`, - }, - - { - name: "storage mounts yaml style", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: - config.kubernetes.io/function: |- - container: - image: foo:v1.0.0 - mounts: - - src: /mount/path - type: bind - dst: /local/ - - dst: /local/ - src: myvol - type: volume - - type: tmpfs - dst: /local/ -`, - expectedFn: ` -container: - image: foo:v1.0.0 - mounts: - - type: bind - src: /mount/path - dst: /local/ - - type: volume - src: myvol - dst: /local/ - - type: tmpfs - dst: /local/ -`, - }, - - { - name: "network", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: - config.kubernetes.io/function: |- - container: - image: foo:v1.0.0 - network: - required: true -`, - expectedFn: ` -container: - image: foo:v1.0.0 - network: - required: true -`, - }, - - { - name: "path", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: - config.kubernetes.io/function: |- - path: foo - container: - image: foo:v1.0.0 -`, - // path should be erased - expectedFn: ` -container: - image: foo:v1.0.0 -`, - }, - - { - name: "network", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: - config.kubernetes.io/function: |- - network: foo - container: - image: foo:v1.0.0 -`, - // network should be erased - expectedFn: ` -container: - image: foo:v1.0.0 -`, - }, - - // legacy fn style - {name: "legacy fn meta", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - configFn: - container: - image: foo:v1.0.0 -`, - expectedFn: ` -container: - image: foo:v1.0.0 -`, - }, - - // no fn - {name: "no fn", - resource: ` -apiVersion: v1beta1 -kind: Example -metadata: - annotations: {} -`, - missingFn: true, - }, - - // test network, etc... - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - resource := yaml.MustParse(tt.resource) - fn := GetFunctionSpec(resource) - if tt.missingFn { - if !assert.Nil(t, fn) { - t.FailNow() - } - } else { - b, err := yaml.Marshal(fn) - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, - strings.TrimSpace(tt.expectedFn), - strings.TrimSpace(string(b))) { - t.FailNow() - } - } - }) - } -} - -func Test_GetContainerNetworkRequired(t *testing.T) { - tests := []struct { - input string - required bool - }{ - { - input: `apiVersion: v1 -kind: Foo -metadata: - name: foo - configFn: - container: - image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 - network: - required: true -`, - required: true, - }, - { - input: `apiVersion: v1 -kind: Foo -metadata: - name: foo - configFn: - container: - image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 - network: - required: false -`, - required: false, - }, - { - - input: `apiVersion: v1 -kind: Foo -metadata: - name: foo - configFn: - container: - image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 -`, - required: false, - }, - { - input: `apiVersion: v1 -kind: Foo -metadata: - name: foo - annotations: - config.kubernetes.io/function: | - container: - image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 - network: - required: true -`, - required: true, - }, - } - - for _, tc := range tests { - cfg, err := yaml.Parse(tc.input) - if !assert.NoError(t, err) { - return - } - fn := GetFunctionSpec(cfg) - assert.Equal(t, tc.required, fn.Container.Network.Required) - } -} - -func TestFilter_Filter_defaultNaming(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'foo/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(``)}).Read() - if !assert.NoError(t, err) { - return - } - - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"echo", `apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo -`}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: [] -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'foo/bar.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - assert.Equal(t, `apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'foo/deployment_deployment-foo.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'foo/service_service-foo.yaml' -`, b.String()) -} - -func TestFilter_Filter_defaultNamingFunctions(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'foo/functions/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(``)}).Read() - if !assert.NoError(t, err) { - return - } - - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"echo", `apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo -`}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: [] -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'foo/functions/bar.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - assert.Equal(t, `apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'foo/deployment_deployment-foo.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'foo/service_service-foo.yaml' -`, b.String()) -} - -func TestFilter_Filter_scopeMissingFromResource(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'foo/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo -`)}).Read() - if !assert.NoError(t, err) { - return - } - - // no resources match the scope - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sed", "s/Deployment/StatefulSet/g"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: [] -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'foo/bar.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - // Resources should be preserved -- paths shouldn't be set by container - assert.Equal(t, `apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' -`, b.String()) -} - -func TestFilter_Filter_globalScope(t *testing.T) { - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'foo/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo -`)}).Read() - if !assert.NoError(t, err) { - return - } - - // no resources match the scope - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - GlobalScope: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sed", "s/Deployment/StatefulSet/g"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' -- apiVersion: v1 - kind: Service - metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'foo/bar.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - // Resources should be preserved -- paths shouldn't be set by container - assert.Equal(t, `apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'foo/statefulset_deployment-foo.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'foo/service_service-foo.yaml' -`, b.String()) -} - -func TestFilter_Filter_scopeFunctionsDir(t *testing.T) { - // functions under "functions/" dir should be scoped to parent dir - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'foo/functions/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' -`)}).Read() - if !assert.NoError(t, err) { - return - } - - // no resources match the scope - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sed", "s/Deployment/StatefulSet/g"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' - config.kubernetes.io/index: '0' -- apiVersion: v1 - kind: Service - metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' - config.kubernetes.io/index: '1' -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'foo/functions/bar.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - // Resources should be modified - assert.Equal(t, `apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' - config.kubernetes.io/index: '0' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' - config.kubernetes.io/index: '1' -`, b.String()) -} - -func TestFilter_Filter_scope_nested_resource(t *testing.T) { - // functions under "functions/" dir should be scoped to parent dir - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'baz.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' -`)}).Read() - if !assert.NoError(t, err) { - return - } - - // no resources match the scope - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sed", "s/Deployment/StatefulSet/g"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' - config.kubernetes.io/index: '0' -- apiVersion: v1 - kind: Service - metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' - config.kubernetes.io/index: '1' -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'baz.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - // Resources should be modified - assert.Equal(t, `apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' - config.kubernetes.io/index: '0' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' - config.kubernetes.io/index: '1' -`, b.String()) -} - -func TestFilter_Filter_scopeDir(t *testing.T) { - // functions under "functions/" dir should be scoped to parent dir - cfg, err := yaml.Parse(`apiVersion: apps/v1 -kind: Deployment -metadata: - name: foo - annotations: - config.kubernetes.io/path: 'foo/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' -`)}).Read() - if !assert.NoError(t, err) { - return - } - - // no resources match the scope - called := false - result, err := (&ContainerFilter{ - SetFlowStyleForConfig: true, - Image: "example.com:version", - Config: cfg, - args: []string{"sed", "s/Deployment/StatefulSet/g"}, - checkInput: func(s string) { - called = true - if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 -kind: ResourceList -items: -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' - config.kubernetes.io/index: '0' -- apiVersion: v1 - kind: Service - metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' - config.kubernetes.io/index: '1' -functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: { - config.kubernetes.io/path: 'foo/bar.yaml'}}} -`, s) { - t.FailNow() - } - }, - }).Filter(input) - if !assert.NoError(t, err) { - return - } - if !assert.True(t, called) { - return - } - - b := &bytes.Buffer{} - err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result) - if !assert.NoError(t, err) { - return - } - - // Resources should be preserved - assert.Equal(t, `apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: deployment-foo - annotations: - config.kubernetes.io/path: 'foo/bar/d.yaml' - config.kubernetes.io/index: '0' ---- -apiVersion: v1 -kind: Service -metadata: - name: service-foo - annotations: - config.kubernetes.io/path: 'foo/bar/s.yaml' - config.kubernetes.io/index: '1' -`, b.String()) -} - -func TestContainerFilter_scope(t *testing.T) { - cf := &ContainerFilter{} - - fnR, err := yaml.Parse(`apiVersion: config.kubernetes.io/v1beta1 -kind: ConfigFunction -metadata: - name: config-function - annotations: - config.kubernetes.io/path: 'functions/bar.yaml' -`) - if !assert.NoError(t, err) { - return - } - - inRs := []*yaml.RNode{fnR} - inScopeRs, notInScopeRs, err := cf.scope(".", inRs) - if !assert.NoError(t, err) { - return - } - assert.Len(t, inScopeRs, 1, "Number of in-scope Resources") - assert.Len(t, notInScopeRs, 0, "Number of out-of-scope Resources") -} diff --git a/kyaml/runfn/runfn.go b/kyaml/runfn/runfn.go index 6802259b7..436ced6c7 100644 --- a/kyaml/runfn/runfn.go +++ b/kyaml/runfn/runfn.go @@ -14,8 +14,9 @@ import ( "sync/atomic" "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/container" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/kio/filters" "sigs.k8s.io/kustomize/kyaml/kio/kioutil" "sigs.k8s.io/kustomize/kyaml/starlark" "sigs.k8s.io/kustomize/kyaml/yaml" @@ -24,7 +25,7 @@ import ( // RunFns runs the set of configuration functions in a local directory against // the Resources in that directory type RunFns struct { - StorageMounts []filters.StorageMount + StorageMounts []runtimeutil.StorageMount // Path is the path to the directory containing functions Path string @@ -75,7 +76,7 @@ type RunFns struct { // functionFilterProvider provides a filter to perform the function. // this is a variable so it can be mocked in tests functionFilterProvider func( - filter filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) + filter runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) } // Execute runs the command @@ -173,7 +174,7 @@ func (r RunFns) runFunctions( // check for deferred function errors var errs []string for i := range fltrs { - cf, ok := fltrs[i].(filters.DeferFailureFunction) + cf, ok := fltrs[i].(runtimeutil.DeferFailureFunction) if !ok { continue } @@ -196,7 +197,7 @@ func (r RunFns) getFunctionsFromInput(nodes []*yaml.RNode) ([]kio.Filter, error) buff := &kio.PackageBuffer{} err := kio.Pipeline{ Inputs: []kio.Reader{&kio.PackageBuffer{Nodes: nodes}}, - Filters: []kio.Filter{&filters.IsReconcilerFilter{}}, + Filters: []kio.Filter{&runtimeutil.IsReconcilerFilter{}}, Outputs: []kio.Writer{buff}, }.Execute() if err != nil { @@ -235,7 +236,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) ( var fltrs []kio.Filter for i := range fns { api := fns[i] - spec := filters.GetFunctionSpec(api) + spec := runtimeutil.GetFunctionSpec(api) if spec.Container.Network.Required { if !r.Network { // TODO(eddiezane): Provide error info about which function needs the network @@ -251,9 +252,9 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) ( if c == nil { continue } - cf, ok := c.(*filters.ContainerFilter) + cf, ok := c.(*container.Filter) if global && ok { - cf.GlobalScope = true + cf.Exec.GlobalScope = true } fltrs = append(fltrs, c) } @@ -331,7 +332,7 @@ func (r *RunFns) init() { } // ffp provides function filters -func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) { +func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) { if !r.DisableContainers && spec.Container.Image != "" { var resultsFile string // TODO: Add a test for this behavior @@ -341,15 +342,17 @@ func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, er "results-%v.yaml", r.resultsCount)) atomic.AddUint32(&r.resultsCount, 1) } - return &filters.ContainerFilter{ + + cf := &container.Filter{ Image: spec.Container.Image, - Config: api, Network: spec.Network, StorageMounts: r.StorageMounts, - GlobalScope: r.GlobalScope, - ResultsFile: resultsFile, - DeferFailure: spec.DeferFailure, - }, nil + } + cf.Exec.FunctionConfig = api + cf.Exec.GlobalScope = r.GlobalScope + cf.Exec.ResultsFile = resultsFile + cf.Exec.DeferFailure = spec.DeferFailure + return cf, nil } if r.EnableStarlark && spec.Starlark.Path != "" { // the script path is relative to the function config file diff --git a/kyaml/runfn/runfn_test.go b/kyaml/runfn/runfn_test.go index e725bcb58..787641333 100644 --- a/kyaml/runfn/runfn_test.go +++ b/kyaml/runfn/runfn_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" "sigs.k8s.io/kustomize/kyaml/copyutil" "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/container" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio/filters" "sigs.k8s.io/kustomize/kyaml/yaml" @@ -48,8 +50,8 @@ func TestRunFns_init(t *testing.T) { api, err := yaml.Parse(`apiVersion: apps/v1 kind: `) - spec := filters.FunctionSpec{ - Container: filters.ContainerSpec{ + spec := runtimeutil.FunctionSpec{ + Container: runtimeutil.ContainerSpec{ Image: "example.com:version", }, } @@ -57,7 +59,9 @@ kind: return } filter, _ := instance.functionFilterProvider(spec, api) - assert.Equal(t, &filters.ContainerFilter{Image: "example.com:version", Config: api}, filter) + cf := &container.Filter{Image: "example.com:version"} + cf.Exec.FunctionConfig = api + assert.Equal(t, cf, filter) } func TestRunFns_Execute__initGlobalScope(t *testing.T) { @@ -76,8 +80,8 @@ kind: return } - spec := filters.FunctionSpec{ - Container: filters.ContainerSpec{ + spec := runtimeutil.FunctionSpec{ + Container: runtimeutil.ContainerSpec{ Image: "example.com:version", }, } @@ -85,8 +89,10 @@ kind: return } filter, _ := instance.functionFilterProvider(spec, api) - assert.Equal(t, &filters.ContainerFilter{ - Image: "example.com:version", Config: api, GlobalScope: true}, filter) + cf := &container.Filter{Image: "example.com:version"} + cf.Exec.FunctionConfig = api + cf.Exec.GlobalScope = true + assert.Equal(t, cf, filter) } func TestRunFns_Execute__initDefault(t *testing.T) { @@ -143,12 +149,12 @@ func TestRunFns_Execute__initDefault(t *testing.T) { }, { name: "explicit directories in mounts", - instance: RunFns{StorageMounts: []filters.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}}, + instance: RunFns{StorageMounts: []runtimeutil.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}}, expected: RunFns{ Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse(), - StorageMounts: []filters.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}, + StorageMounts: []runtimeutil.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}, }, }, } @@ -751,7 +757,7 @@ replace: StatefulSet var fltrs []*TestFilter instance := RunFns{ Path: dir, - functionFilterProvider: func(f filters.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { + functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { tf := &TestFilter{ Exit: errors.Errorf("message: %s", f.Container.Image), } @@ -927,8 +933,8 @@ func setupTest(t *testing.T) string { // getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with // a filter to s/kind: Deployment/kind: StatefulSet/g. // this can be used to simulate running a filter. -func getFilterProvider(t *testing.T) func(filters.FunctionSpec, *yaml.RNode) (kio.Filter, error) { - return func(f filters.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { +func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode) (kio.Filter, error) { + return func(f runtimeutil.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { // parse the filter from the input filter := yaml.YFilter{} b := &bytes.Buffer{}