Merge branch 'master' into container-filter-network

This commit is contained in:
Phillip Wittrock
2019-11-14 11:56:49 -08:00
committed by GitHub
30 changed files with 1687 additions and 267 deletions

View File

@@ -35,7 +35,7 @@ install: true
script:
- ./travis/verify-deps.sh
- ./travis/pre-commit.sh
- make verify-kustomize
- ./travis/kyaml-pre-commit.sh
# TBD. Suppressing for now.

View File

@@ -1,25 +1,21 @@
# This Makefile is (and must be) used by
# travis/pre-commit.sh to qualify pull requests.
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
#
# That script generates all the code that needs
# to be generated, and runs all the tests.
#
# Functionality in that script is gradually moving here.
# Makefile for the kustomize repo.
MYGOBIN := $(shell go env GOPATH)/bin
PATH := $(PATH):$(MYGOBIN)
SHELL := env PATH=$(PATH) /bin/bash
.PHONY: all
all: pre-commit
all: verify-kustomize
# The pre-commit.sh script generates, lints and tests.
# It uses this makefile. For more clarity, would like
# to stop that - any scripts invoked by targets here
# shouldn't "call back" to the makefile.
.PHONY: pre-commit
pre-commit:
./travis/pre-commit.sh
.PHONY: verify-kustomize
verify-kustomize: \
lint-kustomize \
test-unit-kustomize-all \
test-examples-kustomize-against-HEAD \
test-examples-kustomize-against-latest
# Version pinned by api/go.mod
$(MYGOBIN)/golangci-lint:
@@ -46,6 +42,11 @@ $(MYGOBIN)/pluginator:
cd api; \
go install sigs.k8s.io/kustomize/pluginator/v2
# Install kustomize from whatever is checked out.
$(MYGOBIN)/kustomize:
cd kustomize; \
go install .
.PHONY: install-tools
install-tools: \
$(MYGOBIN)/goimports \
@@ -72,8 +73,8 @@ builtinplugins = \
api/builtins/replicacounttransformer.go \
api/builtins/secretgenerator.go
.PHONY: lint
lint: install-tools $(builtinplugins)
.PHONY: lint-kustomize
lint-kustomize: install-tools $(builtinplugins)
cd api; $(MYGOBIN)/golangci-lint run ./...
cd kustomize; $(MYGOBIN)/golangci-lint run ./...
cd pluginator; $(MYGOBIN)/golangci-lint run ./...
@@ -85,23 +86,36 @@ api/builtins/%.go: $(MYGOBIN)/pluginator
cd ../../../api/builtins; \
$(MYGOBIN)/goimports -w $*.go
.PHONY: generate
generate: $(builtinplugins)
.PHONY: unit-test-api
unit-test-api: $(builtinplugins)
.PHONY: test-unit-kustomize-api
test-unit-kustomize-api: $(builtinplugins)
cd api; go test ./...
.PHONY: unit-test-plugins
unit-test-plugins:
./hack/runPluginUnitTests.sh
.PHONY: test-unit-kustomize-plugins
test-unit-kustomize-plugins:
./hack/testUnitKustomizePlugins.sh
.PHONY: unit-test-kustomize
unit-test-kustomize:
.PHONY: test-unit-kustomize-cli
test-unit-kustomize-cli:
cd kustomize; go test ./...
.PHONY: unit-test-all
unit-test-all: unit-test-api unit-test-kustomize unit-test-plugins
.PHONY: test-unit-kustomize-all
test-unit-kustomize-all: \
test-unit-kustomize-api \
test-unit-kustomize-cli \
test-unit-kustomize-plugins
.PHONY:
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh HEAD
.PHONY:
test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip
/bin/rm -f $(MYGOBIN)/kustomize; \
echo "Installing kustomize from latest."; \
go install sigs.k8s.io/kustomize/kustomize/v3; \
./hack/testExamplesAgainstKustomize.sh latest; \
echo "Reinstalling kustomize from HEAD."; \
cd kustomize; go install .
# linux only.
# This is for testing an example plugin that
@@ -133,6 +147,7 @@ $(MYGOBIN)/helm:
clean:
rm -f $(builtinplugins)
rm -f $(MYGOBIN)/pluginator
rm -f $(MYGOBIN)/kustomize
.PHONY: nuke
nuke: clean

View File

@@ -27,7 +27,7 @@ func GetCatRunner() *CatRunner {
kyaml cat my-dir/
# wrap Resource config from a directory in an ResourceList
kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version kyaml.kustomize.dev/v1alpha1 --function-config fn.yaml
kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version config.kubernetes.io/v1alpha1 --function-config fn.yaml
# unwrap Resource config from a directory in an ResourceList
... | kyaml cat
@@ -51,10 +51,10 @@ kyaml cat my-dir/ --wrap-kind ResourceList --wrap-version kyaml.kustomize.dev/v1
"'FoldedStyle', 'FlowStyle'.")
c.Flags().BoolVar(&r.StripComments, "strip-comments", false,
"remove comments from yaml.")
c.Flags().BoolVar(&r.IncludeReconcilers, "include-reconcilers", false,
"if true, include reconciler Resources in the output.")
c.Flags().BoolVar(&r.ExcludeNonReconcilers, "exclude-non-reconcilers", false,
"if true, exclude non-reconciler Resources in the output.")
c.Flags().BoolVar(&r.IncludeLocal, "include-local", false,
"if true, include local-config in the output.")
c.Flags().BoolVar(&r.ExcludeNonLocal, "exclude-non-local", false,
"if true, exclude non-local-config in the output.")
r.Command = c
return r
}
@@ -65,17 +65,17 @@ func CatCommand() *cobra.Command {
// CatRunner contains the run function
type CatRunner struct {
IncludeSubpackages bool
Format bool
KeepAnnotations bool
WrapKind string
WrapApiVersion string
FunctionConfig string
Styles []string
StripComments bool
IncludeReconcilers bool
ExcludeNonReconcilers bool
Command *cobra.Command
IncludeSubpackages bool
Format bool
KeepAnnotations bool
WrapKind string
WrapApiVersion string
FunctionConfig string
Styles []string
StripComments bool
IncludeLocal bool
ExcludeNonLocal bool
Command *cobra.Command
}
func (r *CatRunner) runE(c *cobra.Command, args []string) error {
@@ -105,9 +105,9 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error {
}
var fltr []kio.Filter
// don't include reconcilers
fltr = append(fltr, &filters.IsReconcilerFilter{
ExcludeReconcilers: !r.IncludeReconcilers,
IncludeNonReconcilers: !r.ExcludeNonReconcilers,
fltr = append(fltr, &filters.IsLocalConfig{
IncludeLocalConfig: r.IncludeLocal,
ExcludeNonLocalConfig: r.ExcludeNonLocal,
})
if r.Format {
fltr = append(fltr, filters.FormatFilter{})

View File

@@ -47,10 +47,15 @@ spec:
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: gcr.io/example/image:version
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/reconciler:v1
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
@@ -149,10 +154,15 @@ spec:
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: gcr.io/example/image:version
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/image:version
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
@@ -173,7 +183,7 @@ spec:
// fmt the files
b := &bytes.Buffer{}
r := cmd.GetCatRunner()
r.Command.SetArgs([]string{d, "--include-reconcilers"})
r.Command.SetArgs([]string{d, "--include-local"})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
@@ -202,13 +212,17 @@ spec:
selector:
app: nginx
---
apiVersion: gcr.io/example/image:version
apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/package: .
config.kubernetes.io/path: f2.yaml
configFn:
container:
image: gcr.io/example/image:version
spec:
replicas: 3
---
@@ -259,10 +273,15 @@ spec:
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: gcr.io/example/image:version
apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/local-config: "true"
configFn:
container:
image: gcr.io/example/reconciler:v1
spec:
replicas: 3
---
@@ -283,19 +302,23 @@ spec:
// fmt the files
b := &bytes.Buffer{}
r := cmd.GetCatRunner()
r.Command.SetArgs([]string{d, "--include-reconcilers", "--exclude-non-reconcilers"})
r.Command.SetArgs([]string{d, "--include-local", "--exclude-non-local"})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `apiVersion: gcr.io/example/image:version
if !assert.Equal(t, `apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/package: .
config.kubernetes.io/path: f2.yaml
configFn:
container:
image: gcr.io/example/reconciler:v1
spec:
replicas: 3
`, b.String()) {

143
cmd/kyaml/cmd/cmdwrap.go Normal file
View File

@@ -0,0 +1,143 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"io"
"os"
"path/filepath"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
)
// GetWrapRunner returns a command runner.
func GetWrapRunner() *WrapRunner {
r := &WrapRunner{}
c := &cobra.Command{
Use: "wrap CMD...",
Short: "Wrap an executable so it implements the config fn interface",
Long: `Wrap an executable so it implements the config fn interface
wrap simplifies writing config functions by:
- invoking an executable command converting an input ResourceList into environment
- merging the output onto the original input as a set of patches
- setting filenames on any Resources missing them
config function authors may use wrap by using it to invoke a command from a container image
The following are equivalent:
kyaml wrap -- CMD
kyaml xargs -- CMD | kyaml merge | kyaml fmt --set-filenames
Environment Variables:
KUST_OVERRIDE_DIR:
Path to a directory containing patches to apply to after merging.
`,
Example: `
`,
RunE: r.runE,
SilenceUsage: true,
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
Args: cobra.MinimumNArgs(1),
}
r.Command = c
r.XArgs = GetXArgsRunner()
c.Flags().BoolVar(&r.XArgs.EnvOnly,
"env-only", true, "only set env vars, not arguments.")
c.Flags().StringVar(&r.XArgs.WrapKind,
"wrap-kind", "List", "wrap the input xargs give to the command in this type.")
c.Flags().StringVar(&r.XArgs.WrapVersion,
"wrap-version", "v1", "wrap the input xargs give to the command in this type.")
return r
}
// WrapRunner contains the run function
type WrapRunner struct {
Command *cobra.Command
XArgs *XArgsRunner
getEnv func(key string) string
}
const (
KustMergeEnv = "KUST_MERGE"
KustOverrideDirEnv = "KUST_OVERRIDE_DIR"
)
func WrapCommand() *cobra.Command {
return GetWrapRunner().Command
}
func (r *WrapRunner) runE(c *cobra.Command, args []string) error {
if r.getEnv == nil {
r.getEnv = os.Getenv
}
xargsIn := &bytes.Buffer{}
if _, err := io.Copy(xargsIn, c.InOrStdin()); err != nil {
return err
}
mergeInput := bytes.NewBuffer(xargsIn.Bytes())
// Run the command
xargsOut := &bytes.Buffer{}
r.XArgs.Command.SetArgs(args)
r.XArgs.Command.SetIn(xargsIn)
r.XArgs.Command.SetOut(xargsOut)
r.XArgs.Command.SetErr(os.Stderr)
if err := r.XArgs.Command.Execute(); err != nil {
return err
}
// merge the results
buff := &kio.PackageBuffer{}
var fltrs []kio.Filter
var inputs []kio.Reader
if r.getEnv(KustMergeEnv) == "" || r.getEnv(KustMergeEnv) == "true" || r.getEnv(KustMergeEnv) == "1" {
inputs = append(inputs, &kio.ByteReader{Reader: mergeInput})
fltrs = append(fltrs, &filters.MergeFilter{})
}
inputs = append(inputs, &kio.ByteReader{Reader: xargsOut})
if err := (kio.Pipeline{Inputs: inputs, Filters: fltrs, Outputs: []kio.Writer{buff}}).
Execute(); err != nil {
return err
}
inputs, fltrs = []kio.Reader{buff}, nil
if r.getEnv(KustOverrideDirEnv) != "" {
// merge the overrides on top of the output
fltrs = append(fltrs, filters.MergeFilter{})
inputs = append(inputs,
kio.LocalPackageReader{
OmitReaderAnnotations: true, // don't set path annotations, as they would override
PackagePath: r.getEnv(KustOverrideDirEnv)})
}
fltrs = append(fltrs,
&filters.FileSetter{
FilenamePattern: filepath.Join("config", filters.DefaultFilenamePattern)},
&filters.FormatFilter{})
err := kio.Pipeline{
Inputs: inputs,
Filters: fltrs,
Outputs: []kio.Writer{kio.ByteWriter{
Sort: true,
KeepReaderAnnotations: true,
Writer: c.OutOrStdout(),
WrappingKind: kio.ResourceListKind,
WrappingApiVersion: kio.ResourceListApiVersion}}}.Execute()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,307 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
const (
input = `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
functionConfig:
metadata:
name: test
spec:
replicas: 11
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test
labels:
app: nginx
name: test
spec:
replicas: 5
selector:
matchLabels:
app: nginx
name: test
template:
metadata:
labels:
app: nginx
name: test
spec:
containers:
- name: test
image: nginx:v1.7
ports:
- containerPort: 8080
name: http
resources:
limits:
cpu: 500m
- apiVersion: v1
kind: Service
metadata:
name: test
labels:
app: nginx
name: test
spec:
ports:
# This i the port.
- port: 8080
targetPort: 8080
name: http
selector:
app: nginx
name: test
`
output = `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test
labels:
name: test
app: nginx
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: config/test_deployment.yaml
spec:
replicas: 11
selector:
matchLabels:
name: test
app: nginx
template:
metadata:
labels:
name: test
app: nginx
spec:
containers:
- name: test
image: nginx:v1.7
ports:
- name: http
containerPort: 8080
resources:
limits:
cpu: 500m
- apiVersion: v1
kind: Service
metadata:
name: test
labels:
name: test
app: nginx
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: config/test_service.yaml
spec:
selector:
name: test
app: nginx
ports:
- name: http
# This i the port.
port: 8080
targetPort: 8080
`
outputNoMerge = `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test
labels:
name: test
app: nginx
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: config/test_deployment.yaml
spec:
replicas: 11
selector:
matchLabels:
name: test
app: nginx
template:
metadata:
labels:
name: test
app: nginx
spec:
containers:
- name: test
image: nginx:v1.7
ports:
- name: http
containerPort: 8080
- apiVersion: v1
kind: Service
metadata:
name: test
labels:
name: test
app: nginx
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: config/test_service.yaml
spec:
selector:
name: test
app: nginx
ports:
- name: http
# This i the port.
port: 8080
targetPort: 8080
`
outputOverride = `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test
labels:
name: test
app: nginx
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: config/test_deployment.yaml
spec:
replicas: 11
selector:
matchLabels:
name: test
app: nginx
template:
metadata:
labels:
name: test
app: nginx
spec:
containers:
- name: test
image: nginx:v1.9
ports:
- name: http
containerPort: 8080
resources:
limits:
cpu: 500m
- apiVersion: v1
kind: Service
metadata:
name: test
labels:
name: test
app: nginx
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: config/test_service.yaml
spec:
selector:
name: test
app: nginx
ports:
- name: http
# This i the port.
port: 8080
targetPort: 8080
`
)
func TestCmd_wrap(t *testing.T) {
_, dir, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
dir = filepath.Dir(dir)
c := GetWrapRunner()
c.Command.SetIn(bytes.NewBufferString(input))
out := &bytes.Buffer{}
c.Command.SetOut(out)
args := []string{"--", filepath.Join(dir, "test", "test.sh")}
c.Command.SetArgs(args)
c.XArgs.Args = args
if !assert.NoError(t, c.Command.Execute()) {
t.FailNow()
}
assert.Equal(t, output, out.String())
}
func TestCmd_wrapNoMerge(t *testing.T) {
_, dir, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
dir = filepath.Dir(dir)
c := GetWrapRunner()
c.getEnv = func(key string) string {
if key == KustMergeEnv {
return "false"
}
return ""
}
c.Command.SetIn(bytes.NewBufferString(input))
out := &bytes.Buffer{}
c.Command.SetOut(out)
args := []string{"--", filepath.Join(dir, "test", "test.sh")}
c.Command.SetArgs(args)
c.XArgs.Args = args
if !assert.NoError(t, c.Command.Execute()) {
t.FailNow()
}
assert.Equal(t, outputNoMerge, out.String())
}
func TestCmd_wrapOverride(t *testing.T) {
_, dir, _, ok := runtime.Caller(0)
if !assert.True(t, ok) {
t.FailNow()
}
dir = filepath.Dir(dir)
c := GetWrapRunner()
c.getEnv = func(key string) string {
if key == KustOverrideDirEnv {
return filepath.Join(dir, "test")
}
return ""
}
c.Command.SetIn(bytes.NewBufferString(input))
out := &bytes.Buffer{}
c.Command.SetOut(out)
args := []string{"--", filepath.Join(dir, "test", "test.sh")}
c.Command.SetArgs(args)
c.XArgs.Args = args
if !assert.NoError(t, c.Command.Execute()) {
t.FailNow()
}
assert.Equal(t, outputOverride, out.String())
}

226
cmd/kyaml/cmd/cmdxargs.go Normal file
View File

@@ -0,0 +1,226 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"fmt"
"os"
"os/exec"
"strings"
"unicode"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// GetXArgsRunner returns a command runner.
func GetXArgsRunner() *XArgsRunner {
r := &XArgsRunner{}
c := &cobra.Command{
Use: "xargs -- CMD...",
Short: "Convert functionConfig to commandline flags and envs",
Long: `Convert functionConfig to commandline flags and envs.
xargs reads a ResourceList from stdin and parses the functionConfig field. xargs then
reads each of the fields under .spec and parses them as flags. If the fields have non-scalar
values, then xargs encoded the values as yaml strings.
CMD:
The command to run and pass the functionConfig as arguments.
`,
Example: `
# given this example functionConfig in config.yaml
kind: Foo
spec:
flag1: value1
flag2: value2
items:
- 2
- 1
# this command:
$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml run-fns xargs -- app
# is equivalent to this command:
$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | app --flag1=value1 --flag2=value2 2 1
# echo: prints the app arguments
$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml reconcile xargs -- echo
--flag1=value1 --flag2=value2 2 1
# env: prints the app env
$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml reconcile xargs -- env
# cat: prints the app stdin -- prints the package contents and functionConfig wrapped in a
# ResourceList
$ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml reconcile xargs --no-flags -- env
`,
RunE: r.runE,
SilenceUsage: true,
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
Args: cobra.MinimumNArgs(1),
}
r.Command = c
r.Command.Flags().BoolVar(&r.EnvOnly, "env-only", false, "only add env vars, not flags")
c.Flags().StringVar(&r.WrapKind, "wrap-kind", "List", "wrap the input xargs give to the command in this type.")
c.Flags().StringVar(&r.WrapVersion, "wrap-version", "v1", "wrap the input xargs give to the command in this type.")
return r
}
// Runner contains the run function
type XArgsRunner struct {
Command *cobra.Command
Args []string
EnvOnly bool
WrapKind string
WrapVersion string
}
func XArgsCommand() *cobra.Command {
return GetXArgsRunner().Command
}
func (r *XArgsRunner) runE(c *cobra.Command, _ []string) error {
if len(r.Args) == 0 {
r.Args = os.Args
}
cmdIndex := -1
for i := range r.Args {
if r.Args[i] == "--" {
cmdIndex = i + 1
break
}
}
if cmdIndex < 0 {
return fmt.Errorf("must specify -- before command")
}
r.Args = r.Args[cmdIndex:]
run := exec.Command(r.Args[0])
if len(r.Args) > 1 {
r.Args = r.Args[cmdIndex+1:]
} else {
r.Args = []string{}
}
run.Stdout = c.OutOrStdout()
run.Stderr = c.ErrOrStderr()
rw := &kio.ByteReadWriter{
Reader: c.InOrStdin(),
}
nodes, err := rw.Read()
if err != nil {
return err
}
env := os.Environ()
// append the config to the flags
if err = func() error {
if rw.FunctionConfig == nil {
return nil
}
str, err := rw.FunctionConfig.String()
if err != nil {
return err
}
// add the API object to the env
env = append(env, fmt.Sprintf("KUST_FUNCTION_CONFIG=%s", str))
// parse the fields
meta := rw.FunctionConfig.Field("metadata")
if meta != nil {
err = meta.Value.VisitFields(func(node *yaml.MapNode) error {
if !r.EnvOnly {
r.Args = append(r.Args, fmt.Sprintf("--%s=%s",
node.Key.YNode().Value, parseYNode(node.Value.YNode())))
}
env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(node.Key.YNode().Value),
node.Value.YNode().Value))
return nil
})
if err != nil {
return err
}
}
spec := rw.FunctionConfig.Field("spec")
if spec != nil {
err = spec.Value.VisitFields(func(node *yaml.MapNode) error {
if !r.EnvOnly {
r.Args = append(r.Args, fmt.Sprintf("--%s=%s",
node.Key.YNode().Value, parseYNode(node.Value.YNode())))
}
env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(node.Key.YNode().Value),
node.Value.YNode().Value))
return nil
})
if err != nil {
return err
}
}
if !r.EnvOnly {
items := rw.FunctionConfig.Field("items")
if items != nil {
err = items.Value.VisitElements(func(node *yaml.RNode) error {
r.Args = append(r.Args, parseYNode(node.YNode()))
return nil
})
if err != nil {
return err
}
}
}
if r.WrapKind != "" {
if kind := rw.FunctionConfig.Field("kind"); !yaml.IsFieldEmpty(kind) {
kind.Value.YNode().Value = r.WrapKind
}
rw.WrappingKind = r.WrapKind
}
if r.WrapVersion != "" {
if version := rw.FunctionConfig.Field("apiVersion"); !yaml.IsFieldEmpty(version) {
version.Value.YNode().Value = r.WrapVersion
}
rw.WrappingApiVersion = r.WrapVersion
}
return nil
}(); err != nil {
return err
}
run.Args = append(run.Args, r.Args...)
run.Env = append(run.Env, env...)
// write ResourceList to stdin
if err = func() error {
in, err := run.StdinPipe()
if err != nil {
return err
}
defer in.Close()
rw.Writer = in
if r.WrapKind != kio.ResourceListKind {
rw.FunctionConfig = nil
}
return rw.Write(nodes)
}(); err != nil {
return err
}
return run.Run()
}
func parseYNode(node *yaml.Node) string {
node.Value = strings.TrimSpace(node.Value)
for _, b := range node.Value {
if unicode.IsSpace(b) {
// wrap in '' -- contains whitespace
return fmt.Sprintf("'%s'", node.Value)
}
}
return node.Value
}

View File

@@ -0,0 +1,117 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/kyaml/cmd"
)
const (
flagsInput = `kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx
- apiVersion: apps/v1
kind: Service
spec: {}
functionConfig:
kind: Foo
spec:
a: b
c: d
e: f
items:
- 1
- 3
- 2
- 4
`
resourceInput = `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx
- apiVersion: apps/v1
kind: Service
spec: {}
functionConfig:
kind: Foo
`
resourceOutput = `apiVersion: v1
kind: List
items:
- apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx
- apiVersion: apps/v1
kind: Service
spec: {}
`
)
func TestXArgs_flags(t *testing.T) {
c := cmd.GetXArgsRunner()
c.Command.SetIn(bytes.NewBufferString(flagsInput))
out := &bytes.Buffer{}
c.Command.SetOut(out)
c.Command.SetArgs([]string{"--", "echo"})
c.Args = []string{"--", "echo"}
if !assert.NoError(t, c.Command.Execute()) {
t.FailNow()
}
assert.Equal(t, `--a=b --c=d --e=f 1 3 2 4
`, out.String())
}
func TestXArgs_input(t *testing.T) {
c := cmd.GetXArgsRunner()
c.Command.SetIn(bytes.NewBufferString(resourceInput))
out := &bytes.Buffer{}
c.Command.SetOut(out)
c.Command.SetArgs([]string{"--", "cat"})
c.Args = []string{"--", "cat"}
if !assert.NoError(t, c.Command.Execute()) {
t.FailNow()
}
assert.Equal(t, resourceOutput, out.String())
}
func TestCmd_env(t *testing.T) {
c := cmd.GetXArgsRunner()
c.Command.SetIn(bytes.NewBufferString(flagsInput))
out := &bytes.Buffer{}
c.Command.SetOut(out)
c.Command.SetArgs([]string{"--env-only", "--", "env"})
c.Args = []string{"--", "env"}
if !assert.NoError(t, c.Command.Execute()) {
t.FailNow()
}
assert.Contains(t, out.String(), "\nA=b\nC=d\nE=f\n")
}

98
cmd/kyaml/cmd/run-fns.go Normal file
View File

@@ -0,0 +1,98 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/runfn"
)
// GetCatRunner returns a RunFnRunner.
func GetRunFnRunner() *RunFnRunner {
r := &RunFnRunner{}
c := &cobra.Command{
Use: "run-fns DIR",
Short: "Apply config functions to Resources.",
Long: `Apply config functions to Resources.
run-fns sequentially invokes all config functions in the directly, providing Resources
in the directory as input to the first function, and writing the output of the last
function back to the directory.
The ordering of functions is determined by the order they are encountered when walking the
directory. To clearly specify an ordering of functions, multiple functions may be
declared in the same file, separated by '---' (the functions will be invoked in the
order they appear in the file).
### Arguments:
DIR:
Path to local directory.
### Config Functions:
Config functions are specified as Kubernetes types containing a metadata.configFn.container.image
field. This fields tells run-fns how to invoke the container.
Example config function:
# in file example/fn.yaml
apiVersion: fn.example.com/v1beta1
kind: ExampleFunctionKind
metadata:
configFn:
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
annotations:
config.kubernetes.io/local-config: "true" # tools should ignore this
spec:
configField: configValue
In the preceding example, 'kyaml run-fns example/' would identify the function by
the metadata.configFn field. It would then write all Resources in the directory to
a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It
would then writer the container stdout back to example/, replacing the directory
file contents.
`,
Example: `
kyaml run-fns example/
`,
RunE: r.runE,
Args: cobra.ExactArgs(1),
}
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
r.Command = c
r.Command.Flags().BoolVar(
&r.DryRun, "dry-run", false, "print results to stdout")
r.Command.Flags().StringSliceVar(
&r.FnPaths, "fn-path", []string{},
"directories containing functions without configuration")
r.Command.AddCommand(XArgsCommand())
r.Command.AddCommand(WrapCommand())
return r
}
func RunFnCommand() *cobra.Command {
return GetRunFnRunner().Command
}
// RunFnRunner contains the run function
type RunFnRunner struct {
IncludeSubpackages bool
Command *cobra.Command
DryRun bool
FnPaths []string
}
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
rec := runfn.RunFns{Path: args[0], FunctionPaths: r.FnPaths}
if r.DryRun {
rec.Output = c.OutOrStdout()
}
return rec.Execute()
}

View File

@@ -0,0 +1,15 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
containers:
- name: test
image: nginx:v1.9

51
cmd/kyaml/cmd/test/test.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
cat <<End-of-message
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
name: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
name: ${NAME}
template:
metadata:
labels:
app: nginx
name: ${NAME}
spec:
containers:
- name: ${NAME}
image: nginx:v1.7
ports:
- containerPort: 8080
name: http
---
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
name: ${NAME}
spec:
ports:
# This i the port.
- port: 8080
targetPort: 8080
name: http
selector:
app: nginx
name: ${NAME}
End-of-message

View File

@@ -84,10 +84,10 @@ kubectl get all,applications,releasetracks -o yaml | kyaml tree --structure=grap
c.Flags().BoolVar(&r.env, "env", false, "print env field")
c.Flags().BoolVar(&r.all, "all", false, "print all field infos")
c.Flags().StringSliceVar(&r.fields, "field", []string{}, "print field")
c.Flags().BoolVar(&r.includeReconcilers, "include-reconcilers", false,
"if true, include reconciler Resources in the output.")
c.Flags().BoolVar(&r.excludeNonReconcilers, "exclude-non-reconcilers", false,
"if true, exclude non-reconciler Resources in the output.")
c.Flags().BoolVar(&r.includeLocal, "include-local", false,
"if true, include local-config in the output.")
c.Flags().BoolVar(&r.excludeNonLocal, "exclude-non-local", false,
"if true, exclude non-local-config in the output.")
c.Flags().StringVar(&r.structure, "graph-structure", "directory",
"Graph structure to use for printing the tree. may be 'directory' or 'owners'.")
@@ -101,21 +101,21 @@ func TreeCommand() *cobra.Command {
// TreeRunner contains the run function
type TreeRunner struct {
IncludeSubpackages bool
Command *cobra.Command
name bool
resources bool
ports bool
images bool
replicas bool
all bool
env bool
args bool
cmd bool
fields []string
includeReconcilers bool
excludeNonReconcilers bool
structure string
IncludeSubpackages bool
Command *cobra.Command
name bool
resources bool
ports bool
images bool
replicas bool
all bool
env bool
args bool
cmd bool
fields []string
includeLocal bool
excludeNonLocal bool
structure string
}
func (r *TreeRunner) runE(c *cobra.Command, args []string) error {
@@ -189,9 +189,9 @@ func (r *TreeRunner) runE(c *cobra.Command, args []string) error {
}
// show reconcilers in tree
fltrs := []kio.Filter{&filters.IsReconcilerFilter{
ExcludeReconcilers: !r.includeReconcilers,
IncludeNonReconcilers: !r.excludeNonReconcilers,
fltrs := []kio.Filter{&filters.IsLocalConfig{
IncludeLocalConfig: r.includeLocal,
ExcludeNonLocalConfig: r.excludeNonLocal,
}}
return handleError(c, kio.Pipeline{

View File

@@ -24,10 +24,15 @@ func TestTreeCommand_files(t *testing.T) {
}
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
apiVersion: gcr.io/example/reconciler:v1
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/reconciler:v1
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 1
---
@@ -259,7 +264,7 @@ spec:
// fmt the files
b := &bytes.Buffer{}
r := cmd.GetTreeRunner()
r.Command.SetArgs([]string{d, "--include-reconcilers"})
r.Command.SetArgs([]string{d, "--include-local"})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
@@ -306,10 +311,15 @@ spec:
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: gcr.io/example/reconciler:v1
apiVersion: v1
kind: Abstraction
metadata:
name: foo
configFn:
container:
image: gcr.io/example/reconciler:v1
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 1
---
@@ -331,7 +341,7 @@ spec:
// fmt the files
b := &bytes.Buffer{}
r := cmd.GetTreeRunner()
r.Command.SetArgs([]string{d, "--include-reconcilers", "--exclude-non-reconcilers"})
r.Command.SetArgs([]string{d, "--include-local", "--exclude-non-local"})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return

View File

@@ -32,6 +32,7 @@ func main() {
root.AddCommand(cmd.FmtCommand())
root.AddCommand(cmd.MergeCommand())
root.AddCommand(cmd.CountCommand())
root.AddCommand(cmd.RunFnCommand())
root.AddCommand(&cobra.Command{Use: "merge", Long: merge2.Help})
root.AddCommand(&cobra.Command{Use: "merge3", Long: merge3.Help})

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
#
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
set -o nounset
set -o errexit
set -o pipefail
version=$1
function onLinuxAndNotOnTravis {
[[ ("linux" == "$(go env GOOS)") && (-z ${TRAVIS+x}) ]] && return
false
}
# TODO: change the label?
# We test against the latest release, and HEAD, and presumably
# any branch using this label, so it should probably get
# a new value.
mdrip --mode test \
--label testAgainstLatestRelease examples
# TODO: make work for non-linux
if onLinuxAndNotOnTravis; then
echo "On linux, and not on travis, so running the notravis example tests."
# Requires helm.
make $(go env GOPATH)/bin/helm
mdrip --mode test \
--label helmtest examples/chart.md
fi
echo "Example tests passed against ${version}."

View File

@@ -1,10 +1,8 @@
#!/usr/bin/env bash
#
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
# TODO: get rid of this script, make individual targets
# in the makefile.
# Run this script with no arguments from the repo root
# to test all the plugins.

View File

@@ -17,7 +17,7 @@ import (
const (
ResourceListKind = "ResourceList"
ResourceListApiVersion = "kyaml.kustomize.dev/v1alpha1"
ResourceListApiVersion = "config.kubernetes.io/v1alpha1"
)
// ByteReadWriter reads from an input and writes to an output.

View File

@@ -34,7 +34,7 @@ i: j
}
func TestByteReader_Read_wrappedResourceßßList(t *testing.T) {
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: kyaml.kustomize.dev/v1alpha1
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
functionConfig:
foo: bar

View File

@@ -45,7 +45,7 @@ g:
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `apiVersion: kyaml.kustomize.dev/v1alpha1
assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- c: d # second

View File

@@ -5,12 +5,13 @@ package filters
import (
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -24,6 +25,8 @@ import (
// The full set of environment variables from the parent process
// are passed to the container.
type ContainerFilter struct {
mountPath string
// Image is the container image to use to create a container.
Image string `yaml:"image,omitempty"`
@@ -41,6 +44,10 @@ type ContainerFilter struct {
checkInput func(string)
}
func (c *ContainerFilter) SetMountPath(path string) {
c.mountPath = path
}
// GrepFilter implements kio.GrepFilter
func (c *ContainerFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
// get the command to filter the Resources
@@ -98,6 +105,10 @@ func (c *ContainerFilter) getArgs() []string {
// don't make fs readonly because things like heredoc rely on writing tmp files
"--security-opt=no-new-privileges", // don't allow the user to escalate privileges
}
// mount the directory containing the function as read-only
if c.mountPath != "" {
args = append(args, "-v", fmt.Sprintf("%s:/local/:ro", c.mountPath))
}
// export the local environment vars to the container
for _, pair := range os.Environ() {
@@ -150,7 +161,8 @@ type IsReconcilerFilter struct {
func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
var out []*yaml.RNode
for i := range inputs {
isContainerResource := GetContainerName(inputs[i]) != ""
img, _ := GetContainerName(inputs[i])
isContainerResource := img != ""
if isContainerResource && !c.ExcludeReconcilers {
out = append(out, inputs[i])
}
@@ -162,19 +174,20 @@ func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error)
}
// GetContainerName returns the container image for an API if one exists
func GetContainerName(n *yaml.RNode) string {
func GetContainerName(n *yaml.RNode) (string, string) {
meta, _ := n.GetMeta()
container := meta.Annotations["kyaml.kustomize.dev/container"]
// path to the function, this will be mounted into the container
path := meta.Annotations[kioutil.PathAnnotation]
container := meta.Annotations["config.kubernetes.io/container"]
if container != "" {
return container
return container, path
}
if match.MatchString(meta.ApiVersion) {
return meta.ApiVersion
image, err := n.Pipe(yaml.Lookup("metadata", "configFn", "container", "image"))
if err != nil || yaml.IsMissingOrNull(image) {
return "", path
}
return ""
return image.YNode().Value, path
}
// match specifies the set of apiVersions to recognize as being container images
var match = regexp.MustCompile(`(docker\.io|.*\.?gcr\.io)/.*(:.*)?`)

View File

@@ -5,7 +5,9 @@ package filters
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
@@ -60,6 +62,43 @@ metadata:
assert.True(t, foundKyaml)
}
func TestFilter_commandMountPath(t *testing.T) {
cfg, err := yaml.Parse(`apiversion: apps/v1
kind: Deployment
metadata:
name: foo
`)
if !assert.NoError(t, err) {
return
}
instance := &ContainerFilter{
Image: "example.com:version",
Config: cfg,
mountPath: filepath.Join("mount", "path"),
}
os.Setenv("KYAML_TEST", "FOO")
cmd, err := instance.getCommand()
if !assert.NoError(t, err) {
return
}
expected := []string{
"docker", "run",
"--rm",
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR",
"--network", "none",
"--user", "nobody",
"--security-opt=no-new-privileges",
"-v", fmt.Sprintf("%s:/local/:ro", filepath.Join("mount", "path")),
}
for _, e := range os.Environ() {
// the process env
expected = append(expected, "-e", strings.Split(e, "=")[0])
}
expected = append(expected, "example.com:version")
assert.Equal(t, expected, cmd.Args)
}
func TestFilter_command_network(t *testing.T) {
cfg, err := yaml.Parse(`apiversion: apps/v1
kind: Deployment
@@ -127,7 +166,7 @@ metadata:
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) {
called = true
if !assert.Equal(t, `apiVersion: kyaml.kustomize.dev/v1alpha1
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
@@ -209,7 +248,7 @@ metadata:
args: []string{"sh", "-c", "cat <&0"},
checkInput: func(s string) {
called = true
if !assert.Equal(t, `apiVersion: kyaml.kustomize.dev/v1alpha1
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiversion: apps/v1
@@ -261,36 +300,30 @@ metadata:
func Test_GetContainerName(t *testing.T) {
// make sure gcr.io works
n, err := yaml.Parse(`apiVersion: gcr.io/foo/bar:something
n, err := yaml.Parse(`apiVersion: v1beta1
kind: MyThing
metadata:
configFn:
container:
image: gcr.io/foo/bar:something
`)
if !assert.NoError(t, err) {
return
}
c := GetContainerName(n)
c, _ := GetContainerName(n)
assert.Equal(t, "gcr.io/foo/bar:something", c)
// make sure regional gcr.io works
n, err = yaml.Parse(`apiVersion: us.gcr.io/foo/bar:something
kind: MyThing
`)
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
assert.Equal(t, "us.gcr.io/foo/bar:something", c)
// container from annotation
n, err = yaml.Parse(`apiVersion: v1
kind: MyThing
metadata:
annotations:
kyaml.kustomize.dev/container: gcr.io/foo/bar:something
config.kubernetes.io/container: gcr.io/foo/bar:something
`)
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
c, _ = GetContainerName(n)
assert.Equal(t, "gcr.io/foo/bar:something", c)
// doesn't have a container
@@ -301,16 +334,6 @@ metadata:
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
c, _ = GetContainerName(n)
assert.Equal(t, "", c)
// make sure docker.io works
n, err = yaml.Parse(`apiVersion: docker.io/foo/bar:something
kind: MyThing
`)
if !assert.NoError(t, err) {
return
}
c = GetContainerName(n)
assert.Equal(t, "docker.io/foo/bar:something", c)
}

View File

@@ -0,0 +1,38 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const LocalConfigAnnotation = "config.kubernetes.io/local-config"
// IsLocalConfig filters Resources using the config.kubernetes.io/local-config annotation
type IsLocalConfig struct {
// IncludeLocalConfig will include local-config if set to true
IncludeLocalConfig bool `yaml:"includeLocalConfig,omitempty"`
// ExcludeNonLocalConfig will exclude non local-config if set to true
ExcludeNonLocalConfig bool `yaml:"excludeNonLocalConfig,omitempty"`
}
// Filter implements kio.Filter
func (c *IsLocalConfig) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
var out []*yaml.RNode
for i := range inputs {
meta, err := inputs[i].GetMeta()
if err != nil {
return nil, err
}
_, local := meta.Annotations[LocalConfigAnnotation]
if local && c.IncludeLocalConfig {
out = append(out, inputs[i])
} else if !local && !c.ExcludeNonLocalConfig {
out = append(out, inputs[i])
}
}
return out, nil
}

View File

@@ -12,7 +12,7 @@ import (
)
const (
mergeSourceAnnotation = "kyaml.kustomize.dev/merge-source"
mergeSourceAnnotation = "config.kubernetes.io/merge-source"
mergeSourceOriginal = "original"
mergeSourceUpdated = "updated"
mergeSourceDest = "dest"

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

View File

@@ -1,36 +1,57 @@
# Pseudo Modules
# Pseudo k8s
This package contains dependencies copied from kubernetes/kubernetes repos which
are synced out of staging.
[kubernetes/api]: https://github.com/kubernetes/api
[kubernetes/apimachinery]: https://github.com/kubernetes/apimachinery
[kubernetes/client-go]: https://github.com/kubernetes/client-go
[init-pseudo-module.sh]: init-pseudo-module.sh
The long term plan is to move off of the staging libraries entirely in favor of
more suitable libraries developed in the Kustomize repo.
The module defined below in `pseudo/k8s` contains code generated by the
[init-pseudo-module.sh] script.
The module contains clones (see the [script][init-pseudo-module.sh] for the specific clone tag) of code from the following repositories:
- [kubernetes/api]
- [kubernetes/apimachinery]
- [kubernetes/client-go]
These repositories are in turn snapshots of code being developed
in [kubernetes staging](https://github.com/kubernetes/kubernetes/tree/master/staging).
## Why?
1. Vendoring the Kustomize API in other tools
[`cli-runtime`]: https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/cli-runtime
[`apimachinery`]: https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/apimachinery
The Kubernetes staging packages do not have stable APIs, and frequently break compatibility.
This makes it difficult for other tools to vendor the Kustomize APIs, as they may depend
on incompatible versions of the staging APIs. By forking the staging libraries, we
ensure that we are using our own copy which will not conflict with other versions.
In what follows, let the word `apimachinery` generally represent one of more of the modules [kubernetes/api], [kubernetes/apimachinery], [kubernetes/client-go].
2. Vendoring into kubectl
- kubectl depends on `apimachinery` in the k/k repo via a `go.mod` replacement.
Packages that depend upon staging may not be vendored into kubernetes/kubernetes. By forking
the staging packages, we break this circular dependency so that the kustomize packages may
be vendored into kubernetes/kubernetes without depending on code originating out of
kubernetes/kubernetes.
- kubectl must depend the on kustomize API module.
## Who?
- `apimachinery` doesnt yet do releases distinguished by major version number. I.e., one cannot write a `go.mod` file containing the lines
> ```
> require k8s.io/apimachinery v1.1.4
> require k8s.io/apimachinery/v2 v2.1.0
> require k8s.io/apimachinery/v3 v3.2.1
> ```
The only option at this time are pseudo-versions of the form `v0.0.0-20181127025237-2b1284ed4c93` and everything in the k/k repo must depend on exactly one pseudo version, which is typically a v.0.0.0 with a replace directive specifying an in-repo relative path.
While it is possible to depend upon them from modules outside the Kustomize repository,
there is not guarantee that this will continue to work in the future.
- kustomize depends on `apimachinery`.
The pseudo modules may be removed at anytime in the future without warning and no
This creates a problem, since kubectl and kustomize have no means to declare a dependence on different versions of `apimachinery`, and no means to sync on the same version since kubectl and kustomize live in different repositories.
A solution to this is a _manual vendor_: either the kustomize API module source code must be manually snapshotted into the k/k repo at some invented path that could never be confused with a normal module import path, or the `apimachinery` module (and friends) must be snapshotted into the kustomize repo using the same notion of import path replacement.
This module is the result of taking the latter option. kustomize can depend on this snapshot of `apimachinery`, and both can be vendored into the k/k repo safely.
## Caveats
This pseudo k8s module may be removed at anytime in the future without warning and no
support will be given for these modules.
It's best to try to encourage semver development in `apimachinery` directly.
## How?
## How was this module made?
These libraries were forked by running `git clone` to clone the repos.
@@ -41,6 +62,4 @@ These libraries were forked by running `git clone` to clone the repos.
2. Run the [init-pseudo-module.sh](init-pseudo-module.sh) script to clone and configure pseudo deps
- From the root directory -- `$ psuedo/init-pseudo-module.sh`
### Using the Pseudo Modules in Kustomize
TODO(pwittrock): Write this once it has been done successfully

View File

@@ -1,118 +0,0 @@
#!/bin/bash
set -e
# Tracks success or failure of various operations.
# 0==success, any other value is a failure.
rcAccumulator=0
function removeBin {
local d=$(go env GOPATH)/bin/$1
echo "Removing binary $d"
/bin/rm -f $d
}
function installTools {
make install-tools
MDRIP=$(go env GOPATH)/bin/mdrip
ls -l $(go env GOPATH)/bin
}
function runFunc {
local name=$1
local result="SUCCESS"
printf "============== begin %s\n" "$name"
$name
local code=$?
rcAccumulator=$((rcAccumulator || $code))
if [ $code -ne 0 ]; then
result="FAILURE"
fi
printf "============== end %s : %s; exit code=%d\n\n\n" "$name" "$result" $code
}
function runLint {
make lint
}
function runUnitTests {
make unit-test-all
}
function onLinuxAndNotOnTravis {
[[ ("linux" == "$(go env GOOS)") && (-z ${TRAVIS+x}) ]] && return
false
}
function testExamplesAgainstLatestKustomizeRelease {
removeBin kustomize
local latest=sigs.k8s.io/kustomize/kustomize/v3
echo "Installing latest kustomize from $latest"
(cd ~; GO111MODULE=on go install $latest)
$MDRIP --mode test \
--label testAgainstLatestRelease examples
# TODO: make work for non-linux
if onLinuxAndNotOnTravis; then
echo "On linux, and not on travis, so running the notravis example tests."
# Requires helm.
make $(go env GOPATH)/bin/helm
$MDRIP --mode test \
--label helmtest examples/chart.md
fi
echo "Example tests passed against $latest"
}
function testExamplesAgainstLocalHead {
removeBin kustomize
echo "Installing kustomize from HEAD"
(cd kustomize; go install .)
# To test examples of unreleased features, add
# examples with code blocks annotated with some
# label _other than_ @testAgainstLatestRelease.
$MDRIP --mode test \
--label testAgainstLatestRelease examples
echo "Example tests passed against HEAD"
}
# Having this set might contradict the Makefile,
# so assure it's unset.
unset GOPATH
# With GOPATH now undefined, this most likely
# puts $HOME/go/bin on the path. Regardless,
# subsequent go tool installs will be placing
# binaries in this location.
PATH=$(go env GOPATH)/bin:$PATH
# Make sure we run in the root of the repo and
# therefore run the tests on all packages
base_dir="$( cd "$(dirname "$0")/.." && pwd )"
cd "$base_dir" || {
echo "Cannot cd to '$base_dir'. Aborting." >&2
exit 1
}
echo "HOME=$HOME"
echo "PATH=$PATH"
echo pwd=`pwd`
echo " "
echo "Working..."
runFunc installTools
runFunc runLint
runFunc runUnitTests
runFunc testExamplesAgainstLatestKustomizeRelease
runFunc testExamplesAgainstLocalHead
if [ $rcAccumulator -eq 0 ]; then
echo "SUCCESS!"
else
echo "FAILURE; exit code $rcAccumulator"
fi
exit $rcAccumulator