mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Support writing results from container filter
This commit is contained in:
@@ -51,6 +51,9 @@ func GetRunFnRunner(name string) *RunFnRunner {
|
|||||||
&r.StarName, "star-name", "", "name of starlark program.")
|
&r.StarName, "star-name", "", "name of starlark program.")
|
||||||
r.Command.Flags().MarkHidden("star-name")
|
r.Command.Flags().MarkHidden("star-name")
|
||||||
|
|
||||||
|
r.Command.Flags().StringVar(
|
||||||
|
&r.ResultsDir, "results-dir", "", "write function results to this dir")
|
||||||
|
|
||||||
r.Command.Flags().BoolVar(
|
r.Command.Flags().BoolVar(
|
||||||
&r.Network, "network", false, "enable network access for functions that declare it")
|
&r.Network, "network", false, "enable network access for functions that declare it")
|
||||||
r.Command.Flags().StringVar(
|
r.Command.Flags().StringVar(
|
||||||
@@ -77,6 +80,7 @@ type RunFnRunner struct {
|
|||||||
StarPath string
|
StarPath string
|
||||||
StarName string
|
StarName string
|
||||||
RunFns runfn.RunFns
|
RunFns runfn.RunFns
|
||||||
|
ResultsDir string
|
||||||
Network bool
|
Network bool
|
||||||
NetworkName string
|
NetworkName string
|
||||||
Mounts []string
|
Mounts []string
|
||||||
@@ -267,6 +271,7 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
|
|||||||
NetworkName: r.NetworkName,
|
NetworkName: r.NetworkName,
|
||||||
EnableStarlark: r.EnableStar,
|
EnableStarlark: r.EnableStar,
|
||||||
StorageMounts: storageMounts,
|
StorageMounts: storageMounts,
|
||||||
|
ResultsDir: r.ResultsDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't consider args for the function
|
// don't consider args for the function
|
||||||
|
|||||||
@@ -11,23 +11,25 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/runfn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
|
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
|
||||||
// flags and arguments into the RunFns structure to be executed.
|
// flags and arguments into the RunFns structure to be executed.
|
||||||
func TestRunFnCommand_preRunE(t *testing.T) {
|
func TestRunFnCommand_preRunE(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
expected string
|
expected string
|
||||||
err string
|
expectedStruct *runfn.RunFns
|
||||||
path string
|
err string
|
||||||
input io.Reader
|
path string
|
||||||
output io.Writer
|
input io.Reader
|
||||||
functionPaths []string
|
output io.Writer
|
||||||
network bool
|
functionPaths []string
|
||||||
networkName string
|
network bool
|
||||||
mount []string
|
networkName string
|
||||||
|
mount []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "config map",
|
name: "config map",
|
||||||
@@ -234,6 +236,26 @@ metadata:
|
|||||||
data: {g: h, i: j=k}
|
data: {g: h, i: j=k}
|
||||||
kind: Foo
|
kind: Foo
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "results_dir",
|
||||||
|
args: []string{"run", "dir", "--results-dir", "foo/", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||||
|
path: "dir",
|
||||||
|
expectedStruct: &runfn.RunFns{
|
||||||
|
Path: "dir",
|
||||||
|
NetworkName: "bridge",
|
||||||
|
ResultsDir: "foo/",
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {a: b, c: d, e: f}
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -324,6 +346,10 @@ apiVersion: v1
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(t, r.RunFns, r.RunFns) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) {
|
if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@@ -339,6 +365,14 @@ apiVersion: v1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.expectedStruct != nil {
|
||||||
|
r.RunFns.Functions = nil
|
||||||
|
tt.expectedStruct.FunctionPaths = tt.functionPaths
|
||||||
|
if !assert.Equal(t, *tt.expectedStruct, r.RunFns) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -146,6 +147,13 @@ type ContainerFilter struct {
|
|||||||
// nodes instead of only nodes scoped under the function.
|
// nodes instead of only nodes scoped under the function.
|
||||||
GlobalScope bool
|
GlobalScope bool
|
||||||
|
|
||||||
|
ResultsFile string
|
||||||
|
|
||||||
|
Results *yaml.RNode
|
||||||
|
|
||||||
|
// 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 may be specified by tests to override how a container is spawned
|
||||||
args []string
|
args []string
|
||||||
|
|
||||||
@@ -257,10 +265,7 @@ func (c *ContainerFilter) scope(dir string, nodes []*yaml.RNode) ([]*yaml.RNode,
|
|||||||
// GrepFilter implements kio.GrepFilter
|
// GrepFilter implements kio.GrepFilter
|
||||||
func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
// get the command to filter the Resources
|
// get the command to filter the Resources
|
||||||
cmd, err := c.getCommand()
|
cmd := c.getCommand()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
in := &bytes.Buffer{}
|
in := &bytes.Buffer{}
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
@@ -296,7 +301,16 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
cmd.Stdin = in
|
cmd.Stdin = in
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, err
|
// write the results file on failure
|
||||||
|
results, e := r.Read()
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
if e = c.doResults(r); e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
// return the results from the function even on failure
|
||||||
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := r.Read()
|
output, err := r.Read()
|
||||||
@@ -304,6 +318,10 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.doResults(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// annotate any generated Resources with a path and index if they don't already have one
|
// annotate any generated Resources with a path and index if they don't already have one
|
||||||
if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil {
|
if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -314,6 +332,25 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
return append(output, saved...), nil
|
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
|
// getArgs returns the command + args to run to spawn the container
|
||||||
func (c *ContainerFilter) getArgs() []string {
|
func (c *ContainerFilter) getArgs() []string {
|
||||||
// run the container using docker. this is simpler than using the docker
|
// run the container using docker. this is simpler than using the docker
|
||||||
@@ -341,6 +378,9 @@ func (c *ContainerFilter) getArgs() []string {
|
|||||||
args = append(args, "--mount", storageMount.String())
|
args = append(args, "--mount", storageMount.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tell functions to write error messages to stderr as well as results
|
||||||
|
os.Setenv("LOG_TO_STDERR", "true")
|
||||||
|
|
||||||
// export the local environment vars to the container
|
// export the local environment vars to the container
|
||||||
for _, pair := range os.Environ() {
|
for _, pair := range os.Environ() {
|
||||||
tokens := strings.Split(pair, "=")
|
tokens := strings.Split(pair, "=")
|
||||||
@@ -353,17 +393,9 @@ func (c *ContainerFilter) getArgs() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getCommand returns a command which will apply the Filter using the container image
|
// getCommand returns a command which will apply the Filter using the container image
|
||||||
func (c *ContainerFilter) getCommand() (*exec.Cmd, error) {
|
func (c *ContainerFilter) getCommand() *exec.Cmd {
|
||||||
// encode the filter command API configuration
|
if c.SetFlowStyleForConfig {
|
||||||
cfg := &bytes.Buffer{}
|
|
||||||
if err := func() error {
|
|
||||||
e := yaml.NewEncoder(cfg)
|
|
||||||
defer e.Close()
|
|
||||||
// make it fit on a single line
|
|
||||||
c.Config.YNode().Style = yaml.FlowStyle
|
c.Config.YNode().Style = yaml.FlowStyle
|
||||||
return e.Encode(c.Config.YNode())
|
|
||||||
}(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.args) == 0 {
|
if len(c.args) == 0 {
|
||||||
@@ -375,7 +407,7 @@ func (c *ContainerFilter) getCommand() (*exec.Cmd, error) {
|
|||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
// set stderr for err messaging
|
// set stderr for err messaging
|
||||||
return cmd, nil
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource.
|
// IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -15,6 +16,367 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestContainerFilter_Filter(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expectedOutput []string
|
||||||
|
expectedError 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_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 !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) {
|
func TestFilter_command(t *testing.T) {
|
||||||
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -29,10 +391,7 @@ metadata:
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
os.Setenv("KYAML_TEST", "FOO")
|
os.Setenv("KYAML_TEST", "FOO")
|
||||||
cmd, err := instance.getCommand()
|
cmd := instance.getCommand()
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"docker", "run",
|
"docker", "run",
|
||||||
@@ -78,10 +437,7 @@ metadata:
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
StorageMounts: []StorageMount{bindMount, localVol, tmpfs},
|
StorageMounts: []StorageMount{bindMount, localVol, tmpfs},
|
||||||
}
|
}
|
||||||
cmd, err := instance.getCommand()
|
cmd := instance.getCommand()
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"docker", "run",
|
"docker", "run",
|
||||||
@@ -116,10 +472,7 @@ metadata:
|
|||||||
Network: "test-net",
|
Network: "test-net",
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
cmd, err := instance.getCommand()
|
cmd := instance.getCommand()
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"docker", "run",
|
"docker", "run",
|
||||||
@@ -168,9 +521,10 @@ metadata:
|
|||||||
|
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
Config: cfg,
|
||||||
|
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
@@ -252,9 +606,10 @@ metadata:
|
|||||||
|
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sh", "-c", "cat <&0"},
|
Config: cfg,
|
||||||
|
args: []string{"sh", "-c", "cat <&0"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
@@ -597,8 +952,9 @@ metadata:
|
|||||||
|
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
|
Config: cfg,
|
||||||
args: []string{"echo", `apiVersion: apps/v1
|
args: []string{"echo", `apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -671,8 +1027,9 @@ metadata:
|
|||||||
|
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
|
Config: cfg,
|
||||||
args: []string{"echo", `apiVersion: apps/v1
|
args: []string{"echo", `apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -756,9 +1113,10 @@ metadata:
|
|||||||
// no resources match the scope
|
// no resources match the scope
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
Config: cfg,
|
||||||
|
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
@@ -831,10 +1189,11 @@ metadata:
|
|||||||
// no resources match the scope
|
// no resources match the scope
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
GlobalScope: true,
|
SetFlowStyleForConfig: true,
|
||||||
Image: "example.com:version",
|
GlobalScope: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
Config: cfg,
|
||||||
|
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
@@ -926,9 +1285,10 @@ metadata:
|
|||||||
// no resources match the scope
|
// no resources match the scope
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
Config: cfg,
|
||||||
|
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
@@ -1022,9 +1382,10 @@ metadata:
|
|||||||
// no resources match the scope
|
// no resources match the scope
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
Config: cfg,
|
||||||
|
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
@@ -1118,9 +1479,10 @@ metadata:
|
|||||||
// no resources match the scope
|
// no resources match the scope
|
||||||
called := false
|
called := false
|
||||||
result, err := (&ContainerFilter{
|
result, err := (&ContainerFilter{
|
||||||
Image: "example.com:version",
|
SetFlowStyleForConfig: true,
|
||||||
Config: cfg,
|
Image: "example.com:version",
|
||||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
Config: cfg,
|
||||||
|
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||||
checkInput: func(s string) {
|
checkInput: func(s string) {
|
||||||
called = true
|
called = true
|
||||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
package runfn
|
package runfn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
@@ -64,6 +66,12 @@ type RunFns struct {
|
|||||||
// DisableContainers will disable functions run as containers
|
// DisableContainers will disable functions run as containers
|
||||||
DisableContainers bool
|
DisableContainers bool
|
||||||
|
|
||||||
|
// ResultsDir is where to write each functions results
|
||||||
|
ResultsDir string
|
||||||
|
|
||||||
|
// resultsCount is used to generate the results filename for each container
|
||||||
|
resultsCount uint32
|
||||||
|
|
||||||
// functionFilterProvider provides a filter to perform the function.
|
// functionFilterProvider provides a filter to perform the function.
|
||||||
// this is a variable so it can be mocked in tests
|
// this is a variable so it can be mocked in tests
|
||||||
functionFilterProvider func(
|
functionFilterProvider func(
|
||||||
@@ -219,6 +227,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == nil {
|
if c == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -304,12 +313,21 @@ func (r *RunFns) init() {
|
|||||||
// ffp provides function filters
|
// ffp provides function filters
|
||||||
func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) {
|
func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) {
|
||||||
if !r.DisableContainers && spec.Container.Image != "" {
|
if !r.DisableContainers && spec.Container.Image != "" {
|
||||||
|
var resultsFile string
|
||||||
|
// TODO: Add a test for this behavior
|
||||||
|
|
||||||
|
if r.ResultsDir != "" {
|
||||||
|
resultsFile = filepath.Join(r.ResultsDir, fmt.Sprintf(
|
||||||
|
"results-%v.yaml", r.resultsCount))
|
||||||
|
atomic.AddUint32(&r.resultsCount, 1)
|
||||||
|
}
|
||||||
return &filters.ContainerFilter{
|
return &filters.ContainerFilter{
|
||||||
Image: spec.Container.Image,
|
Image: spec.Container.Image,
|
||||||
Config: api,
|
Config: api,
|
||||||
Network: spec.Network,
|
Network: spec.Network,
|
||||||
StorageMounts: r.StorageMounts,
|
StorageMounts: r.StorageMounts,
|
||||||
GlobalScope: r.GlobalScope,
|
GlobalScope: r.GlobalScope,
|
||||||
|
ResultsFile: resultsFile,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
if r.EnableStarlark && spec.Starlark.Path != "" {
|
if r.EnableStarlark && spec.Starlark.Path != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user