mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
starlark fn support for functionConfig input
This commit is contained in:
@@ -5,6 +5,8 @@ package starlark
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/qri-io/starlib/util"
|
"github.com/qri-io/starlib/util"
|
||||||
@@ -21,9 +23,54 @@ type Filter struct {
|
|||||||
|
|
||||||
// Program is a starlark script which will be run against the resources
|
// Program is a starlark script which will be run against the resources
|
||||||
Program string
|
Program string
|
||||||
|
|
||||||
|
// URL is the url of a starlark program to fetch and run
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// Path is the path to a starlark program to read and run
|
||||||
|
Path string
|
||||||
|
|
||||||
|
// FunctionConfig is the value to be provided for resourceList.functionConfig as specified by
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md.
|
||||||
|
FunctionConfig *yaml.RNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sf *Filter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (sf *Filter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
if sf.URL != "" && sf.Path != "" ||
|
||||||
|
sf.URL != "" && sf.Program != "" ||
|
||||||
|
sf.Path != "" && sf.Program != "" {
|
||||||
|
return nil, errors.Errorf("Filter Path, Program and URL are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the program from a file
|
||||||
|
if sf.Path != "" {
|
||||||
|
b, err := ioutil.ReadFile(sf.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sf.Program = string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the program from a URL
|
||||||
|
if sf.URL != "" {
|
||||||
|
err := func() error {
|
||||||
|
resp, err := http.Get(sf.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sf.Program = string(b)
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// retain map of inputs to outputs by id so if the name is changed by the
|
// retain map of inputs to outputs by id so if the name is changed by the
|
||||||
// starlark program, we are able to match the same resources
|
// starlark program, we are able to match the same resources
|
||||||
value, ids, err := sf.inputToResourceList(input)
|
value, ids, err := sf.inputToResourceList(input)
|
||||||
@@ -69,6 +116,16 @@ func (sf *Filter) inputToResourceList(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err)
|
return nil, nil, errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the functionConfig
|
||||||
|
if sf.FunctionConfig != nil {
|
||||||
|
if err := resourceList.PipeE(
|
||||||
|
yaml.FieldSetter{Name: "functionConfig", Value: sf.FunctionConfig}); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the inputs should be provided as the list "items"
|
||||||
items, err := resourceList.Pipe(yaml.LookupCreate(yaml.SequenceNode, "items"))
|
items, err := resourceList.Pipe(yaml.LookupCreate(yaml.SequenceNode, "items"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err)
|
return nil, nil, errors.Wrap(err)
|
||||||
@@ -115,10 +172,24 @@ func (sf *Filter) resourceListToOutput(
|
|||||||
return nil, errors.Wrap(err)
|
return nil, errors.Wrap(err)
|
||||||
}
|
}
|
||||||
o := out.(map[string]interface{})
|
o := out.(map[string]interface{})
|
||||||
it := (o["items"].([]interface{}))
|
|
||||||
|
|
||||||
|
// parse the function config
|
||||||
|
if _, found := o["functionConfig"]; found {
|
||||||
|
fc := (o["functionConfig"].(map[string]interface{}))
|
||||||
|
b, err := yaml.Marshal(fc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
sf.FunctionConfig, err = yaml.Parse(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the items
|
||||||
// copy the items out of the ResourceList, and into the Filter output
|
// copy the items out of the ResourceList, and into the Filter output
|
||||||
var results []*yaml.RNode
|
var results []*yaml.RNode
|
||||||
|
it := (o["items"].([]interface{}))
|
||||||
for i := range it {
|
for i := range it {
|
||||||
// convert the resource back to the native yaml form
|
// convert the resource back to the native yaml form
|
||||||
b, err := yaml.Marshal(it[i])
|
b, err := yaml.Marshal(it[i])
|
||||||
|
|||||||
@@ -5,19 +5,25 @@ package starlark
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-errors/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStarlarkFilter_Filter(t *testing.T) {
|
func TestStarlarkFilter_Filter(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
functionConfig string
|
||||||
script string
|
script string
|
||||||
expected string
|
expected string
|
||||||
|
expectedFunctionConfig string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "add_annotation",
|
name: "add_annotation",
|
||||||
@@ -234,16 +240,127 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-deployment-1
|
name: nginx-deployment-1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "functionConfig",
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
# head comment
|
||||||
|
image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
||||||
|
`,
|
||||||
|
functionConfig: `
|
||||||
|
kind: Script
|
||||||
|
spec:
|
||||||
|
value: "hello world"
|
||||||
|
`,
|
||||||
|
script: `
|
||||||
|
# set the foo annotation on each resource
|
||||||
|
def run(r, an):
|
||||||
|
for resource in r:
|
||||||
|
resource["metadata"]["annotations"]["foo"] = an
|
||||||
|
|
||||||
|
an = resourceList["functionConfig"]["spec"]["value"]
|
||||||
|
run(resourceList["items"], an)
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment
|
||||||
|
annotations:
|
||||||
|
foo: hello world
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
# head comment
|
||||||
|
image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
||||||
|
`,
|
||||||
|
expectedFunctionConfig: `
|
||||||
|
kind: Script
|
||||||
|
spec:
|
||||||
|
value: hello world
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "functionConfig_update_functionConfig",
|
||||||
|
input: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
# head comment
|
||||||
|
image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
||||||
|
`,
|
||||||
|
functionConfig: `
|
||||||
|
kind: Script
|
||||||
|
spec:
|
||||||
|
value: "hello world"
|
||||||
|
`,
|
||||||
|
script: `
|
||||||
|
# set the foo annotation on each resource
|
||||||
|
def run(r, an):
|
||||||
|
for resource in r:
|
||||||
|
resource["metadata"]["annotations"]["foo"] = an
|
||||||
|
|
||||||
|
an = resourceList["functionConfig"]["spec"]["value"]
|
||||||
|
run(resourceList["items"], an)
|
||||||
|
resourceList["functionConfig"]["spec"]["value"] = "updated"
|
||||||
|
`,
|
||||||
|
expected: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment
|
||||||
|
annotations:
|
||||||
|
foo: hello world
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
# head comment
|
||||||
|
image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
||||||
|
`,
|
||||||
|
expectedFunctionConfig: `
|
||||||
|
kind: Script
|
||||||
|
spec:
|
||||||
|
value: updated
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
test := tests[i]
|
test := tests[i]
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
f := &Filter{Name: test.name, Program: test.script}
|
||||||
|
|
||||||
|
if test.functionConfig != "" {
|
||||||
|
fc, err := yaml.Parse(test.functionConfig)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
f.FunctionConfig = fc
|
||||||
|
}
|
||||||
|
|
||||||
r := &kio.ByteReader{Reader: bytes.NewBufferString(test.input)}
|
r := &kio.ByteReader{Reader: bytes.NewBufferString(test.input)}
|
||||||
o := &bytes.Buffer{}
|
o := &bytes.Buffer{}
|
||||||
w := &kio.ByteWriter{Writer: o}
|
w := &kio.ByteWriter{Writer: o}
|
||||||
f := &Filter{Name: test.name, Program: test.script}
|
|
||||||
p := kio.Pipeline{
|
p := kio.Pipeline{
|
||||||
Inputs: []kio.Reader{r},
|
Inputs: []kio.Reader{r},
|
||||||
Filters: []kio.Filter{f},
|
Filters: []kio.Filter{f},
|
||||||
@@ -251,11 +368,22 @@ metadata:
|
|||||||
}
|
}
|
||||||
err := p.Execute()
|
err := p.Execute()
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
|
if e, ok := err.(*errors.Error); ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", e.Stack())
|
||||||
|
}
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if !assert.Equal(t, strings.TrimSpace(test.expected), strings.TrimSpace(o.String())) {
|
if !assert.Equal(t, strings.TrimSpace(test.expected), strings.TrimSpace(o.String())) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if test.expectedFunctionConfig != "" {
|
||||||
|
if !assert.Equal(t,
|
||||||
|
strings.TrimSpace(test.expectedFunctionConfig),
|
||||||
|
strings.TrimSpace(f.FunctionConfig.MustString())) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user