mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-13 01:50:55 +00:00
Improvements to kyaml fn framework
This commit creates a new version of the alpha configuration functions framework. Goals include: - Make it easy to build multi-version APIs with the framework (not previously facilitated at all). - Simplify the framework's APIs where redundant configuration options exist (leaving the most powerful, replacing others with helpers to maintain usability they provided). - Make the Framework's APIs more consistent (e.g. between the various template types, usage of kio.Filter, field names) - Decouple responsibilities (e.g. command creation, resource list processing, generation of templating functions). - Make the framework even more powerfully pluggable (e.g. any kio.Filter can be a selector, and the selector the framework provides is itself a filter built from reusable abstractions). - Improve documentation. - Make container patches merge fields (notably list fields like `env`) correctly.
This commit is contained in:
@@ -5,362 +5,84 @@ package framework_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
||||
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/testutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestCommand_dockerfile(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "kustomize")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
// create a function
|
||||
|
||||
resourceList := &framework.ResourceList{}
|
||||
cmd := framework.Command(resourceList, func() error { return nil })
|
||||
|
||||
// generate the Dockerfile
|
||||
cmd.SetArgs([]string{"gen", d})
|
||||
if !assert.NoError(t, cmd.Execute()) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(filepath.Join(d, "Dockerfile"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
expected := `FROM golang:1.13-stretch
|
||||
ENV CGO_ENABLED=0
|
||||
WORKDIR /go/src/
|
||||
COPY . .
|
||||
RUN go build -v -o /usr/local/bin/function ./
|
||||
|
||||
FROM alpine:latest
|
||||
COPY --from=0 /usr/local/bin/function /usr/local/bin/function
|
||||
CMD ["function"]
|
||||
`
|
||||
if !assert.Equal(t, expected, string(b)) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommand_standalone tests the framework works in standalone mode
|
||||
func TestCommand_standalone(t *testing.T) {
|
||||
// TODO: make this test pass on windows -- currently failure seems spurious
|
||||
testutil.SkipWindows(t)
|
||||
|
||||
type api = struct {
|
||||
A string `json:"a" yaml:"a"`
|
||||
}
|
||||
var config api
|
||||
|
||||
resourceList := &framework.ResourceList{FunctionConfig: &config}
|
||||
cmdFn := func() *cobra.Command {
|
||||
return framework.Command(resourceList, func() error {
|
||||
resourceList.Items = append(resourceList.Items, yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
`))
|
||||
for i := range resourceList.Items {
|
||||
err := resourceList.Items[i].PipeE(yaml.SetAnnotation("a", config.A))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
frameworktestutil.ResultsChecker{Command: cmdFn}.Assert(t)
|
||||
}
|
||||
|
||||
func TestCommand_standalonestdin(t *testing.T) {
|
||||
// TODO: make this test pass on windows -- currently failure seems spurious
|
||||
testutil.SkipWindows(t)
|
||||
|
||||
type api = struct {
|
||||
A string `json:"a" yaml:"a"`
|
||||
}
|
||||
var config api
|
||||
|
||||
resourceList := &framework.ResourceList{FunctionConfig: &config}
|
||||
cmd := framework.Command(resourceList, func() error {
|
||||
resourceList.Items = append(resourceList.Items, yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar2
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar2
|
||||
`))
|
||||
for i := range resourceList.Items {
|
||||
err := resourceList.Items[i].PipeE(yaml.SetAnnotation("a", config.A))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func TestExecute_Result(t *testing.T) {
|
||||
p := framework.ResourceListProcessorFunc(func(rl *framework.ResourceList) error {
|
||||
err := &framework.Result{
|
||||
Name: "Incompatible config",
|
||||
Items: []framework.ResultItem{{
|
||||
Message: "bad value for replicas",
|
||||
Severity: framework.Error,
|
||||
ResourceRef: yaml.ResourceMeta{
|
||||
TypeMeta: yaml.TypeMeta{APIVersion: "v1", Kind: "Deployment"},
|
||||
ObjectMeta: yaml.ObjectMeta{
|
||||
NameMeta: yaml.NameMeta{Name: "tester", Namespace: "default"},
|
||||
},
|
||||
},
|
||||
Field: framework.Field{
|
||||
Path: ".spec.Replicas",
|
||||
CurrentValue: "0",
|
||||
SuggestedValue: "3",
|
||||
},
|
||||
File: framework.File{
|
||||
Path: "/path/to/deployment.yaml",
|
||||
Index: 0,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
return nil
|
||||
rl.Result = err
|
||||
return err
|
||||
})
|
||||
cmd.SetIn(bytes.NewBufferString(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
spec:
|
||||
replicas: 1
|
||||
`))
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetArgs([]string{filepath.Join("testdata", "command", "config.yaml"), "-"})
|
||||
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
require.Equal(t, strings.TrimSpace(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
a: 'b'
|
||||
spec:
|
||||
replicas: 1
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar2
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar2
|
||||
a: 'b'
|
||||
`), strings.TrimSpace(out.String()))
|
||||
}
|
||||
|
||||
func TestCommand_PatchTemplateFn(t *testing.T) {
|
||||
// TODO: make this test pass on windows -- currently failure seems spurious
|
||||
testutil.SkipWindows(t)
|
||||
|
||||
type api = struct {
|
||||
Spec struct {
|
||||
A string `json:"a" yaml:"a"`
|
||||
} `json:"spec" yaml:"spec"`
|
||||
}
|
||||
var config api
|
||||
|
||||
cmd := framework.TemplateCommand{
|
||||
API: &config,
|
||||
PatchTemplatesFn: func(_ *framework.ResourceList) ([]framework.PatchTemplate, error) {
|
||||
return []framework.PatchTemplate{{
|
||||
Selector: &framework.Selector{Names: []string{config.Spec.A}},
|
||||
Template: template.Must(template.New("test").Parse(`
|
||||
metadata:
|
||||
annotations:
|
||||
baz: buz
|
||||
`)),
|
||||
}}, nil
|
||||
},
|
||||
}.GetCommand()
|
||||
|
||||
cmd.SetIn(bytes.NewBufferString(`
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
spec:
|
||||
replicas: 1
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar2
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar2
|
||||
spec:
|
||||
replicas: 1
|
||||
functionConfig:
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: Example
|
||||
spec:
|
||||
a: "bar1"
|
||||
`))
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
require.Equal(t, strings.TrimSpace(`
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
baz: buz
|
||||
config.kubernetes.io/index: '0'
|
||||
spec:
|
||||
replicas: 1
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar2
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar2
|
||||
spec:
|
||||
replicas: 1
|
||||
functionConfig:
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: Example
|
||||
spec:
|
||||
a: "bar1"
|
||||
`), strings.TrimSpace(out.String()))
|
||||
}
|
||||
|
||||
func TestCommand_PatchContainerTemplatesFn(t *testing.T) {
|
||||
// TODO: make this test pass on windows -- currently failure seems spurious
|
||||
testutil.SkipWindows(t)
|
||||
|
||||
type api = struct {
|
||||
Spec struct {
|
||||
A string `json:"a" yaml:"a"`
|
||||
} `json:"spec" yaml:"spec"`
|
||||
}
|
||||
var config api
|
||||
|
||||
cmd := framework.TemplateCommand{
|
||||
API: &config,
|
||||
PatchContainerTemplatesFn: func(_ *framework.ResourceList) ([]framework.ContainerPatchTemplate, error) {
|
||||
return []framework.ContainerPatchTemplate{{
|
||||
PatchTemplate: framework.PatchTemplate{
|
||||
Selector: &framework.Selector{Names: []string{config.Spec.A}},
|
||||
Template: template.Must(template.New("test").Parse(`
|
||||
env:
|
||||
key: Foo
|
||||
value: Bar
|
||||
`))},
|
||||
}}, nil
|
||||
},
|
||||
}.GetCommand()
|
||||
|
||||
cmd.SetIn(bytes.NewBufferString(`
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
- name: bar
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar2
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
- name: bar
|
||||
functionConfig:
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: Example
|
||||
spec:
|
||||
a: "bar1"
|
||||
`))
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
require.Equal(t, strings.TrimSpace(`
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar1
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
env:
|
||||
key: Foo
|
||||
value: Bar
|
||||
- name: bar
|
||||
env:
|
||||
key: Foo
|
||||
value: Bar
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar2
|
||||
namespace: default
|
||||
annotations:
|
||||
foo: bar2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
- name: bar
|
||||
functionConfig:
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: Example
|
||||
spec:
|
||||
a: "bar1"
|
||||
`), strings.TrimSpace(out.String()))
|
||||
out := new(bytes.Buffer)
|
||||
source := &kio.ByteReadWriter{Reader: bytes.NewBufferString(`
|
||||
kind: ResourceList
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
items:
|
||||
- kind: Deployment
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: tester
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 0
|
||||
`), Writer: out}
|
||||
err := framework.Execute(p, source)
|
||||
assert.EqualError(t, err, "[error] v1/Deployment/default/tester .spec."+
|
||||
"Replicas: bad value for replicas")
|
||||
assert.Equal(t, 1, err.(*framework.Result).ExitCode())
|
||||
assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- kind: Deployment
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: tester
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 0
|
||||
results:
|
||||
name: Incompatible config
|
||||
items:
|
||||
- message: bad value for replicas
|
||||
severity: error
|
||||
resourceRef:
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: tester
|
||||
namespace: default
|
||||
field:
|
||||
path: .spec.Replicas
|
||||
currentValue: "0"
|
||||
suggestedValue: "3"
|
||||
file:
|
||||
path: /path/to/deployment.yaml`, strings.TrimSpace(out.String()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user