mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 09:24:23 +00:00
Refactor starlark runtime ontop of runtimeutil
This commit is contained in:
@@ -60,22 +60,6 @@ func env() (starlark.Value, error) {
|
|||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeToValue(node *yaml.RNode) (starlark.Value, error) {
|
|
||||||
s, err := node.String()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
var in map[string]interface{}
|
|
||||||
if err := yaml.Unmarshal([]byte(s), &in); err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
value, err := util.Marshal(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func interfaceToValue(i interface{}) (starlark.Value, error) {
|
func interfaceToValue(i interface{}) (starlark.Value, error) {
|
||||||
b, err := json.Marshal(i)
|
b, err := json.Marshal(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -11,8 +11,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/starlark"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/starlark"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ run(ctx.resource_list["items"])
|
|||||||
// name: deployment-1
|
// name: deployment-1
|
||||||
// annotations:
|
// annotations:
|
||||||
// foo: bar
|
// foo: bar
|
||||||
|
// config.kubernetes.io/path: 'deployment_deployment-1.yaml'
|
||||||
// spec:
|
// spec:
|
||||||
// template:
|
// template:
|
||||||
// spec:
|
// spec:
|
||||||
@@ -88,6 +90,7 @@ run(ctx.resource_list["items"])
|
|||||||
// name: deployment-2
|
// name: deployment-2
|
||||||
// annotations:
|
// annotations:
|
||||||
// foo: bar
|
// foo: bar
|
||||||
|
// config.kubernetes.io/path: 'deployment_deployment-2.yaml'
|
||||||
// spec:
|
// spec:
|
||||||
// template:
|
// template:
|
||||||
// spec:
|
// spec:
|
||||||
@@ -141,7 +144,7 @@ def run(items, value):
|
|||||||
|
|
||||||
run(ctx.resource_list["items"], ctx.resource_list["functionConfig"]["spec"]["value"])
|
run(ctx.resource_list["items"], ctx.resource_list["functionConfig"]["spec"]["value"])
|
||||||
`,
|
`,
|
||||||
FunctionConfig: fc,
|
FunctionFilter: runtimeutil.FunctionFilter{FunctionConfig: fc},
|
||||||
}
|
}
|
||||||
|
|
||||||
// output contains the transformed resources
|
// output contains the transformed resources
|
||||||
@@ -165,6 +168,7 @@ run(ctx.resource_list["items"], ctx.resource_list["functionConfig"]["spec"]["val
|
|||||||
// name: deployment-1
|
// name: deployment-1
|
||||||
// annotations:
|
// annotations:
|
||||||
// foo: hello world
|
// foo: hello world
|
||||||
|
// config.kubernetes.io/path: 'deployment_deployment-1.yaml'
|
||||||
// spec:
|
// spec:
|
||||||
// template:
|
// template:
|
||||||
// spec:
|
// spec:
|
||||||
@@ -178,6 +182,7 @@ run(ctx.resource_list["items"], ctx.resource_list["functionConfig"]["spec"]["val
|
|||||||
// name: deployment-2
|
// name: deployment-2
|
||||||
// annotations:
|
// annotations:
|
||||||
// foo: hello world
|
// foo: hello world
|
||||||
|
// config.kubernetes.io/path: 'deployment_deployment-2.yaml'
|
||||||
// spec:
|
// spec:
|
||||||
// template:
|
// template:
|
||||||
// spec:
|
// spec:
|
||||||
224
kyaml/fn/runtime/starlark/starlark.go
Normal file
224
kyaml/fn/runtime/starlark/starlark.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package starlark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qri-io/starlib/util"
|
||||||
|
"go.starlark.net/starlark"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/comments"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter transforms a set of resources through the provided program
|
||||||
|
type Filter struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Program is a starlark script which will be run against the resources
|
||||||
|
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
|
||||||
|
|
||||||
|
runtimeutil.FunctionFilter
|
||||||
|
|
||||||
|
ids map[string]*yaml.RNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *Filter) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"name: %v path: %v url: %v program: %v", sf.Name, sf.Path, sf.URL, sf.Program)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
err := sf.setup()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sf.FunctionFilter.Run = sf.Run
|
||||||
|
|
||||||
|
return sf.FunctionFilter.Filter(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *Filter) setup() error {
|
||||||
|
if sf.URL != "" && sf.Path != "" ||
|
||||||
|
sf.URL != "" && sf.Program != "" ||
|
||||||
|
sf.Path != "" && sf.Program != "" {
|
||||||
|
return 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 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 err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *Filter) Run(reader io.Reader, writer io.Writer) error {
|
||||||
|
// 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
|
||||||
|
value, err := sf.readResourceList(reader)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the starlark as program as transformation function
|
||||||
|
thread := &starlark.Thread{Name: sf.Name}
|
||||||
|
|
||||||
|
ctx := &Context{resourceList: value}
|
||||||
|
pd, err := ctx.predeclared()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
_, err = starlark.ExecFile(thread, sf.Name, sf.Program, pd)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sf.writeResourceList(value, writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputToResourceList transforms input into a starlark.Value
|
||||||
|
func (sf *Filter) readResourceList(reader io.Reader) (starlark.Value, error) {
|
||||||
|
// read and parse the inputs
|
||||||
|
rl := bytes.Buffer{}
|
||||||
|
_, err := rl.ReadFrom(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
rn, err := yaml.Parse(rl.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the id on each node to map inputs to outputs
|
||||||
|
var id int
|
||||||
|
sf.ids = map[string]*yaml.RNode{}
|
||||||
|
items, err := rn.Pipe(yaml.Lookup("items"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
err = items.VisitElements(func(node *yaml.RNode) error {
|
||||||
|
id++
|
||||||
|
idStr := fmt.Sprintf("%v", id)
|
||||||
|
sf.ids[idStr] = node
|
||||||
|
return node.PipeE(yaml.SetAnnotation("config.k8s.io/id", idStr))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to a starlark value
|
||||||
|
b, err := yaml.Marshal(rn.Document()) // convert to bytes
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
var in map[string]interface{}
|
||||||
|
err = yaml.Unmarshal(b, &in) // convert to map[string]interface{}
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
return util.Marshal(in) // convert to starlark value
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceListToOutput converts the output of the starlark program to the filter output
|
||||||
|
func (sf *Filter) writeResourceList(value starlark.Value, writer io.Writer) error {
|
||||||
|
// convert the modified resourceList back into a slice of RNodes
|
||||||
|
// by first converting to a map[string]interface{}
|
||||||
|
out, err := util.Unmarshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
b, err := yaml.Marshal(out)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := yaml.Parse(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// preserve the comments from the input
|
||||||
|
items, err := rl.Pipe(yaml.Lookup("items"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
err = items.VisitElements(func(node *yaml.RNode) error {
|
||||||
|
anID, err := node.Pipe(yaml.GetAnnotation("config.k8s.io/id"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
if anID == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var in *yaml.RNode
|
||||||
|
var found bool
|
||||||
|
if in, found = sf.ids[anID.YNode().Value]; !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := node.PipeE(yaml.ClearAnnotation("config.k8s.io/id")); err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
if err := comments.CopyComments(in, node); err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// starlark will serialize the resources sorting the fields alphabetically,
|
||||||
|
// format them to have a better ordering
|
||||||
|
fmtFltr := filters.FormatFilter{}
|
||||||
|
if _, err := fmtFltr.Filter([]*yaml.RNode{node}); err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := rl.String()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = writer.Write([]byte(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ metadata:
|
|||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
annotations:
|
annotations:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -95,6 +96,7 @@ metadata:
|
|||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
annotations:
|
annotations:
|
||||||
foo: annotation-value
|
foo: annotation-value
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -133,6 +135,7 @@ metadata:
|
|||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
annotations:
|
annotations:
|
||||||
foo: Deployment enables declarative updates for Pods and ReplicaSets.
|
foo: Deployment enables declarative updates for Pods and ReplicaSets.
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -174,6 +177,7 @@ metadata:
|
|||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
annotations:
|
annotations:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -213,6 +217,8 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -266,6 +272,7 @@ metadata:
|
|||||||
name: nginx-deployment-1
|
name: nginx-deployment-1
|
||||||
annotations:
|
annotations:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -280,6 +287,7 @@ metadata:
|
|||||||
name: nginx-deployment-2
|
name: nginx-deployment-2
|
||||||
annotations:
|
annotations:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment-2.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -318,13 +326,10 @@ run(ctx.resource_list["items"])
|
|||||||
expected: `
|
expected: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
|
||||||
name: nginx-deployment-2
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-deployment-1
|
name: nginx-deployment-1
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -332,6 +337,13 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
# head comment
|
# head comment
|
||||||
image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment-2
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment-2.yaml'
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -357,6 +369,8 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-deployment-1
|
name: nginx-deployment-1
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -395,6 +409,7 @@ metadata:
|
|||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
annotations:
|
annotations:
|
||||||
foo: hello world
|
foo: hello world
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -406,7 +421,7 @@ spec:
|
|||||||
expectedFunctionConfig: `
|
expectedFunctionConfig: `
|
||||||
kind: Script
|
kind: Script
|
||||||
spec:
|
spec:
|
||||||
value: hello world
|
value: "hello world"
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -447,6 +462,7 @@ metadata:
|
|||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
annotations:
|
annotations:
|
||||||
foo: hello world
|
foo: hello world
|
||||||
|
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -458,7 +474,7 @@ spec:
|
|||||||
expectedFunctionConfig: `
|
expectedFunctionConfig: `
|
||||||
kind: Script
|
kind: Script
|
||||||
spec:
|
spec:
|
||||||
value: updated
|
value: "hello world"
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -16,9 +16,9 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/runtime/container"
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/container"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/starlark"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||||
"sigs.k8s.io/kustomize/kyaml/starlark"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter
|
|||||||
return &starlark.Filter{
|
return &starlark.Filter{
|
||||||
Name: spec.Starlark.Name,
|
Name: spec.Starlark.Name,
|
||||||
Path: p,
|
Path: p,
|
||||||
FunctionConfig: api,
|
FunctionFilter: runtimeutil.FunctionFilter{FunctionConfig: api},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -1,251 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package starlark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qri-io/starlib/util"
|
|
||||||
"go.starlark.net/starlark"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/comments"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter transforms a set of resources through the provided program
|
|
||||||
type Filter struct {
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Program is a starlark script which will be run against the resources
|
|
||||||
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) String() string {
|
|
||||||
return fmt.Sprintf("name: %v path: %v url: %v program: %v", sf.Name, sf.Path, sf.URL, sf.Program)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// starlark program, we are able to match the same resources
|
|
||||||
value, ids, err := sf.inputToResourceList(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the starlark as program as transformation function
|
|
||||||
thread := &starlark.Thread{Name: sf.Name}
|
|
||||||
|
|
||||||
ctx := &Context{
|
|
||||||
resourceList: value,
|
|
||||||
}
|
|
||||||
pd, err := ctx.predeclared()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
_, err = starlark.ExecFile(thread, sf.Name, sf.Program, pd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := sf.resourceListToOutput(value, ids)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// starlark will serialize the resources sorting the fields alphabetically,
|
|
||||||
// format them to have a better ordering
|
|
||||||
return filters.FormatFilter{}.Filter(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tuple maps an input resource to the output resource
|
|
||||||
type tuple struct {
|
|
||||||
// in is the RNode provided to the starlark program
|
|
||||||
in *yaml.RNode
|
|
||||||
// out is the RNode emitted by the starlark program with the id matching in
|
|
||||||
out *yaml.RNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// inputToResourceList transforms input into a starlark.Value
|
|
||||||
func (sf *Filter) inputToResourceList(
|
|
||||||
input []*yaml.RNode) (starlark.Value, map[int]*tuple, error) {
|
|
||||||
var id int
|
|
||||||
ids := map[int]*tuple{}
|
|
||||||
|
|
||||||
// convert into a ResourceList which will be converted to a starlark dictionary
|
|
||||||
// create the ResourceList
|
|
||||||
resourceList, err := yaml.Parse(`kind: ResourceList`)
|
|
||||||
if err != nil {
|
|
||||||
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"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
// add the input as items, give each resource an id
|
|
||||||
for i := range input {
|
|
||||||
item := input[i]
|
|
||||||
|
|
||||||
// create an id for tracking the resource through the program
|
|
||||||
err := item.PipeE(yaml.SetAnnotation("config.k8s.io/id", fmt.Sprintf("%d", id)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
ids[id] = &tuple{in: item}
|
|
||||||
id++
|
|
||||||
|
|
||||||
items.YNode().Content = append(items.YNode().Content, item.YNode())
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert the ResourceList into a starlark dictionary by
|
|
||||||
// first converting it into a map[string]interface{}
|
|
||||||
value, err := nodeToValue(resourceList)
|
|
||||||
return value, ids, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// resourceListToOutput converts the output of the starlark program to the filter output
|
|
||||||
func (sf *Filter) resourceListToOutput(
|
|
||||||
value starlark.Value, ids map[int]*tuple) ([]*yaml.RNode, error) {
|
|
||||||
// convert the modified resourceList back into a slice of RNodes
|
|
||||||
// by first converting to a map[string]interface{}
|
|
||||||
out, err := util.Unmarshal(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
o := out.(map[string]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
|
|
||||||
var results []*yaml.RNode
|
|
||||||
it := (o["items"].([]interface{}))
|
|
||||||
for i := range it {
|
|
||||||
// convert the resource back to the native yaml form
|
|
||||||
b, err := yaml.Marshal(it[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
node, err := yaml.Parse(string(b))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// match it to an input
|
|
||||||
idS, err := node.Pipe(yaml.GetAnnotation("config.k8s.io/id"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
if idS == nil {
|
|
||||||
// no matching input -- new resource
|
|
||||||
results = append(results, node)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(idS.YNode().Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
if match, found := ids[id]; found {
|
|
||||||
// matching resources
|
|
||||||
match.out = node
|
|
||||||
} else {
|
|
||||||
// no matching input with the same id -- new resource
|
|
||||||
// this may be an error case, the outputs probably shouldn't have ids
|
|
||||||
// assigned by the starlark program
|
|
||||||
results = append(results, node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retain the comments instead of dropping them by copying them from the original inputs
|
|
||||||
for i := 0; i < len(ids); i++ {
|
|
||||||
v := ids[i]
|
|
||||||
if v.out == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := comments.CopyComments(v.in, v.out); err != nil {
|
|
||||||
return nil, errors.Wrap(err)
|
|
||||||
}
|
|
||||||
results = append(results, v.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the ids from resources, these were only to track through the starlark program
|
|
||||||
// and that is finished.
|
|
||||||
for i := range results {
|
|
||||||
err := results[i].PipeE(yaml.ClearAnnotation("config.k8s.io/id"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user