Add kyaml command for invoking container filters

This commit is contained in:
Phillip Wittrock
2019-11-12 17:45:20 -08:00
parent 912a9c3baa
commit c99278c67b
24 changed files with 1578 additions and 95 deletions

98
kyaml/runfn/runfn.go Normal file
View File

@@ -0,0 +1,98 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package runfn
import (
"io"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// RunFns runs the set of configuration functions in a local directory against
// the Resources in that directory
type RunFns struct {
// Path is the path to the directory containing functions
Path string
// FunctionPaths Paths allows functions to be specified outside the configuration
// directory
FunctionPaths []string
// Output can be set to write the result to Output rather than back to the directory
Output io.Writer
// containerFilterProvider may be override by tests to fake invoking containers
containerFilterProvider func(string, string, *yaml.RNode) kio.Filter
}
// Execute runs the command
func (r RunFns) Execute() error {
// make the path absolute so it works on mac
var err error
r.Path, err = filepath.Abs(r.Path)
if err != nil {
return errors.Wrap(err)
}
// default the containerFilterProvider if it hasn't been override. Split out for testing.
(&r).init()
// identify the configuration functions in the directory
buff := &kio.PackageBuffer{}
err = kio.Pipeline{
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: r.Path}},
Filters: []kio.Filter{&filters.IsReconcilerFilter{}},
Outputs: []kio.Writer{buff},
}.Execute()
if err != nil {
return err
}
// accept a
for i := range r.FunctionPaths {
err := kio.Pipeline{
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: r.FunctionPaths[i]}},
Outputs: []kio.Writer{buff},
}.Execute()
if err != nil {
return err
}
}
// reconcile each local API
var fltrs []kio.Filter
for i := range buff.Nodes {
api := buff.Nodes[i]
img, path := filters.GetContainerName(api)
fltrs = append(fltrs, r.containerFilterProvider(img, path, api))
}
pkgIO := &kio.LocalPackageReadWriter{PackagePath: r.Path}
inputs := []kio.Reader{pkgIO}
var outputs []kio.Writer
if r.Output == nil {
// write back to the package
outputs = append(outputs, pkgIO)
} else {
// write to the output instead of the directory
outputs = append(outputs, kio.ByteWriter{Writer: r.Output})
}
return kio.Pipeline{Inputs: inputs, Filters: fltrs, Outputs: outputs}.Execute()
}
// init initializes the RunFns with a containerFilterProvider.
func (r *RunFns) init() {
// if containerFilterProvider hasn't been set, use the default
if r.containerFilterProvider == nil {
r.containerFilterProvider = func(image, path string, api *yaml.RNode) kio.Filter {
cf := &filters.ContainerFilter{Image: image, Config: api}
cf.SetMountPath(filepath.Join(r.Path, path))
return cf
}
}
}

248
kyaml/runfn/runfn_test.go Normal file
View File

@@ -0,0 +1,248 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package runfn
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/copyutil"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestRunFns_Execute(t *testing.T) {
instance := RunFns{}
instance.init()
api, err := yaml.Parse(`apiVersion: apps/v1
kind:
`)
if !assert.NoError(t, err) {
return
}
filter := instance.containerFilterProvider("example.com:version", "", api)
assert.Equal(t, &filters.ContainerFilter{Image: "example.com:version", Config: api}, filter)
}
func TestCmd_Execute(t *testing.T) {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
return
}
// write a test filter
f := `apiVersion: v1
kind: ValueReplacer
metadata:
configFn:
container:
image: gcr.io/example.com/image:version
stringMatch: Deployment
replace: StatefulSet
`
if !assert.NoError(t, ioutil.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(f), 0600)) {
return
}
instance := RunFns{
Path: dir,
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}
},
}
if !assert.NoError(t, instance.Execute()) {
t.FailNow()
}
b, err := ioutil.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
func TestCmd_Execute_APIs(t *testing.T) {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
return
}
// write a test filter
f := `apiVersion: v1
kind: ValueReplacer
metadata:
configFn:
container:
image: gcr.io/example.com/image:version
stringMatch: Deployment
replace: StatefulSet
`
tmpF, err := ioutil.TempFile("", "filter*.yaml")
if !assert.NoError(t, err) {
return
}
os.RemoveAll(tmpF.Name())
if !assert.NoError(t, ioutil.WriteFile(tmpF.Name(), []byte(f), 0600)) {
return
}
instance := RunFns{
FunctionPaths: []string{tmpF.Name()},
Path: dir,
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}
},
}
err = instance.Execute()
if !assert.NoError(t, err) {
return
}
b, err := ioutil.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
return
}
assert.Contains(t, string(b), "kind: StatefulSet")
}
func TestCmd_Execute_Stdout(t *testing.T) {
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(dir)
_, filename, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
t.FailNow()
}
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
return
}
// write a test filter
f := `apiVersion: v1
kind: ValueReplacer
metadata:
configFn:
container:
image: gcr.io/example.com/image:version
stringMatch: Deployment
replace: StatefulSet
`
if !assert.NoError(t, ioutil.WriteFile(
filepath.Join(dir, "filter.yaml"), []byte(f), 0600)) {
return
}
out := &bytes.Buffer{}
instance := RunFns{
Output: out,
Path: dir,
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}
e := yaml.NewEncoder(b)
if !assert.NoError(t, e.Encode(node.YNode())) {
t.FailNow()
}
e.Close()
d := yaml.NewDecoder(b)
if !assert.NoError(t, d.Decode(&filter)) {
t.FailNow()
}
return filters.Modifier{
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
}
},
}
if !assert.NoError(t, instance.Execute()) {
return
}
b, err := ioutil.ReadFile(
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
if !assert.NoError(t, err) {
return
}
assert.NotContains(t, string(b), "kind: StatefulSet")
assert.Contains(t, out.String(), "kind: StatefulSet")
}

View File

@@ -0,0 +1,10 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
labels:
app.kubernetes.io/component: undefined
app.kubernetes.io/instance: undefined
data: {}

View File

@@ -0,0 +1,36 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: java
spec:
selector:
matchLabels:
app: java
template:
metadata:
labels:
app: java
spec:
restartPolicy: Always
containers:
- name: app
image: gcr.io/project/app:version
command:
- java
- -jar
- /app.jar
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: app-config
env:
- name: JAVA_OPTS
value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
-Djava.security.egd=file:/dev/./urandom
imagePullPolicy: Always
minReadySeconds: 5

View File

@@ -0,0 +1,15 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: v1
kind: Service
metadata:
name: app
labels:
app: java
spec:
selector:
app: java
ports:
- name: "8080"
port: 8080
targetPort: 8080