diff --git a/cmd/config/cmd/run-fns.go b/cmd/config/cmd/run-fns.go index 19f5eff25..6291b473b 100644 --- a/cmd/config/cmd/run-fns.go +++ b/cmd/config/cmd/run-fns.go @@ -14,6 +14,7 @@ func GetRunFnRunner(name string) *RunFnRunner { r := &RunFnRunner{} c := &cobra.Command{ Use: "run-fns DIR", + Aliases: []string{"run"}, Short: commands.RunFnsShort, Long: commands.RunFnsLong, Example: commands.RunFnsExamples, diff --git a/cmd/config/cmddocs/tutorials/docs.go b/cmd/config/cmddocs/tutorials/docs.go index 034d7d6ab..16b1fcd9b 100644 --- a/cmd/config/cmddocs/tutorials/docs.go +++ b/cmd/config/cmddocs/tutorials/docs.go @@ -278,3 +278,186 @@ var ConfigurationBasicsLong = ` │   ├── name: web │   └── image: ...` + +var FunctionBasicsShort = `### Synopsis` +var FunctionBasicsLong = ` + ` + "`" + `kustomize config` + "`" + ` enables encapsulating function for manipulating Resource + configuration inside containers, which are run using ` + "`" + `run-fns` + "`" + `. + + First fetch the kustomize repository, which contains a collection of example + functions + + git clone https://github.com/kubernetes-sigs/kustomize + cd kustomize/functions/examples/ + +### Templating -- CockroachDB + + This section demonstrates how to leverage templating based solutions from + ` + "`" + `kustomize config` + "`" + `. The templating function is implemented as a ` + "`" + `bash` + "`" + ` script + using a ` + "`" + `heredoc` + "`" + `. + + #### 1: Generate the Resources + + ` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/template-heredoc-cockroachdb/` + "`" + ` + directory, and invoke ` + "`" + `run-fns` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory. + + cd template-heredoc-cockroachdb/ + + # view the Resources + kustomize config tree local-resource/ --name --image --replicas + + # run the function + kustomize config run-fns local-resource/ + + # view the generated Resources + kustomize config tree local-resource/ --name --image --replicas + + ` + "`" + `run-fns` + "`" + ` generated the directory ` + "`" + ` local-resource/config` + "`" + ` containing the generated + Resources. + + #### 2. Modify the Generated Resources + + - modify the generated Resources by adding an annotation, sidecar container, etc. + - modify the ` + "`" + `local-resources/example-use.yaml` + "`" + ` by changing the replicas + + re-run ` + "`" + `run-fns` + "`" + `. this will apply the updated replicas to the generated Resources, + but keep the fields that you manually added to the generated Resource configuration. + + + # run the function + kustomize config run-fns local-resource/ + + ` + "`" + `run-fns` + "`" + ` facilitates a non-destructive *smart templating* approach that allows templating + to be composed with manual modifications directly to the template output, as well as + composition with other functions which may appy validation or injection of values. + + #### 3. Function Implementation + + the function implementation is located under the ` + "`" + `image/` + "`" + ` directory as a ` + "`" + `Dockerfile` + "`" + ` + and a ` + "`" + `bash` + "`" + ` script. + +### Templating -- Nginx + + The steps in this section are identical to the CockroachDB templating example, + but the function implementation is very different, and implemented as a ` + "`" + `go` + "`" + ` + program rather than a ` + "`" + `bash` + "`" + ` script. + + #### 1: Generate the Resources + + ` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/template-go-nginx/` + "`" + ` + directory, and invoke ` + "`" + `run-fns` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory. + + cd template-go-nginx/ + + # view the Resources + kustomize config tree local-resource/ --name --image --replicas + + # run the function + kustomize config run-fns local-resource/ + + # view the generated Resources + kustomize config tree local-resource/ --name --image --replicas + + ` + "`" + `run-fns` + "`" + ` generated the directory ` + "`" + ` local-resource/config` + "`" + ` containing the generated + Resources. this time it put the configuration in a single file rather than multiple + files. The mapping of Resources to files is controlled by the function itself through + annotations on the generated Resources. + + #### 2. Modify the Generated Resources + + - modify the generated Resources by adding an annotation, sidecar container, etc. + - modify the ` + "`" + `local-resources/example-use.yaml` + "`" + ` by changing the replicas + + re-run ` + "`" + `run-fns` + "`" + `. this will apply the updated replicas to the generated Resources, + but keep the fields that you manually added to the generated Resource configuration. + + # run the function + kustomize config run-fns local-resource/ + + Just like in the preceding section, the function is implemented using a non-destructive + approach which merges the generated Resources into previously generated instances. + + #### 3. Function Implementation + + the function implementation is located under the ` + "`" + `image/` + "`" + ` directory as a ` + "`" + `Dockerfile` + "`" + ` + and a ` + "`" + `go` + "`" + ` program. + +### Validation -- resource reservations + + This section uses ` + "`" + `run-fns` + "`" + ` to perform validation rather than generate Resources. + + #### 1: Run the Validator + + ` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/validator-resource-requests` + "`" + ` + directory, and invoke ` + "`" + `run-fns` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory. + + # run the function + kustomize config run-fns local-resource/ + cpu-requests missing for a container in Deployment nginx (example-use.yaml [1]) + Error: exit status 1 + Usage: + ... + + #### 2: Fix the validation issue + + The command will fail complaining that the nginx Deployment is missing ` + "`" + `cpu-requests` + "`" + `, + and print the name of the file + Resource index. Edit the file and uncomment the resources, + then re-run the functions. + + kustomize config run-fns local-resource/ + + The validation now passes. + +### Injection -- resource reservations + + This section uses ` + "`" + `run-fns` + "`" + ` to perform injection of field values based off annotations + on the Resource. + + #### 1: Run the Injector + + ` + "`" + `cd` + "`" + ` into the ` + "`" + `kustomize/functions/examples/inject-tshirt-sizes` + "`" + ` + directory, and invoke ` + "`" + `run-fns` + "`" + ` on the ` + "`" + `local-resource/` + "`" + ` directory. + + # print the resources + kustomize config tree local-resource --resources --name + local-resource + ├── [example-use.yaml] Validator + └── [example-use.yaml] Deployment nginx + └── spec.template.spec.containers + └── 0 + └── name: nginx + + # run the functions + kustomize config run-fns local-resource/ + + # print the new resources + kustomize config tree local-resource --resources --name + ├── [example-use.yaml] Validator + └── [example-use.yaml] Deployment nginx + └── spec.template.spec.containers + └── 0 + ├── name: nginx + └── resources: {requests: {cpu: 4, memory: 1GiB}} + + #### 2: Change the tshirt-size + + Change the ` + "`" + `tshirt-size` + "`" + ` annotation from ` + "`" + `medium` + "`" + ` to ` + "`" + `small` + "`" + ` and re-run the functions. + + kustomize config run-fns local-resource/ + kustomize config tree local-resource/ + local-resource + ├── [example-use.yaml] Validator + └── [example-use.yaml] Deployment nginx + └── spec.template.spec.containers + └── 0 + ├── name: nginx + └── resources: {requests: {cpu: 200m, memory: 50MiB}} + + The function has applied the reservations for the new tshirt-size + +### Function Composition + +Functions may be composed together. Try putting the Injection (tshirt-size) and +Validation functions together in the same .yaml file (separated by ` + "`" + `---` + "`" + `). Run +` + "`" + `run-fns` + "`" + ` and observe that the first function in the file is applied to the Resources, +and then the second function in the file is applied.` diff --git a/cmd/config/cmds/cmds.go b/cmd/config/cmds/cmds.go index ca24bcf81..3b010f0ec 100644 --- a/cmd/config/cmds/cmds.go +++ b/cmd/config/cmds/cmds.go @@ -77,7 +77,7 @@ func NewConfigCommand(name string) *cobra.Command { }) root.AddCommand(&cobra.Command{ Use: "docs-fn", - Short: "[Alpha] Documentation for writing containerized functions run by run-fns.", + Short: "[Alpha] Documentation for writing containerized functions run by run.", Long: api.ConfigFnLong, }) root.AddCommand(&cobra.Command{ @@ -87,10 +87,16 @@ func NewConfigCommand(name string) *cobra.Command { }) root.AddCommand(&cobra.Command{ - Use: "tutorials-basics", + Use: "tutorials-command-basics", Short: "[Alpha] Tutorials for using basic config commands.", Long: tutorials.ConfigurationBasicsLong, }) + root.AddCommand(&cobra.Command{ + Use: "tutorials-function-basics", + Short: "[Alpha] Tutorials for using functions.", + Long: tutorials.FunctionBasicsLong, + }) + return root } diff --git a/cmd/config/docs/tutorials/function-basics.md b/cmd/config/docs/tutorials/function-basics.md new file mode 100644 index 000000000..ba84c2cc9 --- /dev/null +++ b/cmd/config/docs/tutorials/function-basics.md @@ -0,0 +1,184 @@ +## Function Basics + +### Synopsis + + `kustomize config` enables encapsulating function for manipulating Resource + configuration inside containers, which are run using `run-fns`. + + First fetch the kustomize repository, which contains a collection of example + functions + + git clone https://github.com/kubernetes-sigs/kustomize + cd kustomize/functions/examples/ + +### Templating -- CockroachDB + + This section demonstrates how to leverage templating based solutions from + `kustomize config`. The templating function is implemented as a `bash` script + using a `heredoc`. + + #### 1: Generate the Resources + + `cd` into the `kustomize/functions/examples/template-heredoc-cockroachdb/` + directory, and invoke `run-fns` on the `local-resource/` directory. + + cd template-heredoc-cockroachdb/ + + # view the Resources + kustomize config tree local-resource/ --name --image --replicas + + # run the function + kustomize config run-fns local-resource/ + + # view the generated Resources + kustomize config tree local-resource/ --name --image --replicas + + `run-fns` generated the directory ` local-resource/config` containing the generated + Resources. + + #### 2. Modify the Generated Resources + + - modify the generated Resources by adding an annotation, sidecar container, etc. + - modify the `local-resources/example-use.yaml` by changing the replicas + + re-run `run-fns`. this will apply the updated replicas to the generated Resources, + but keep the fields that you manually added to the generated Resource configuration. + + + # run the function + kustomize config run-fns local-resource/ + + `run-fns` facilitates a non-destructive *smart templating* approach that allows templating + to be composed with manual modifications directly to the template output, as well as + composition with other functions which may appy validation or injection of values. + + #### 3. Function Implementation + + the function implementation is located under the `image/` directory as a `Dockerfile` + and a `bash` script. + +### Templating -- Nginx + + The steps in this section are identical to the CockroachDB templating example, + but the function implementation is very different, and implemented as a `go` + program rather than a `bash` script. + + #### 1: Generate the Resources + + `cd` into the `kustomize/functions/examples/template-go-nginx/` + directory, and invoke `run-fns` on the `local-resource/` directory. + + cd template-go-nginx/ + + # view the Resources + kustomize config tree local-resource/ --name --image --replicas + + # run the function + kustomize config run-fns local-resource/ + + # view the generated Resources + kustomize config tree local-resource/ --name --image --replicas + + `run-fns` generated the directory ` local-resource/config` containing the generated + Resources. this time it put the configuration in a single file rather than multiple + files. The mapping of Resources to files is controlled by the function itself through + annotations on the generated Resources. + + #### 2. Modify the Generated Resources + + - modify the generated Resources by adding an annotation, sidecar container, etc. + - modify the `local-resources/example-use.yaml` by changing the replicas + + re-run `run-fns`. this will apply the updated replicas to the generated Resources, + but keep the fields that you manually added to the generated Resource configuration. + + # run the function + kustomize config run-fns local-resource/ + + Just like in the preceding section, the function is implemented using a non-destructive + approach which merges the generated Resources into previously generated instances. + + #### 3. Function Implementation + + the function implementation is located under the `image/` directory as a `Dockerfile` + and a `go` program. + +### Validation -- resource reservations + + This section uses `run-fns` to perform validation rather than generate Resources. + + #### 1: Run the Validator + + `cd` into the `kustomize/functions/examples/validator-resource-requests` + directory, and invoke `run-fns` on the `local-resource/` directory. + + # run the function + kustomize config run-fns local-resource/ + cpu-requests missing for a container in Deployment nginx (example-use.yaml [1]) + Error: exit status 1 + Usage: + ... + + #### 2: Fix the validation issue + + The command will fail complaining that the nginx Deployment is missing `cpu-requests`, + and print the name of the file + Resource index. Edit the file and uncomment the resources, + then re-run the functions. + + kustomize config run-fns local-resource/ + + The validation now passes. + +### Injection -- resource reservations + + This section uses `run-fns` to perform injection of field values based off annotations + on the Resource. + + #### 1: Run the Injector + + `cd` into the `kustomize/functions/examples/inject-tshirt-sizes` + directory, and invoke `run-fns` on the `local-resource/` directory. + + # print the resources + kustomize config tree local-resource --resources --name + local-resource + ├── [example-use.yaml] Validator + └── [example-use.yaml] Deployment nginx + └── spec.template.spec.containers + └── 0 + └── name: nginx + + # run the functions + kustomize config run-fns local-resource/ + + # print the new resources + kustomize config tree local-resource --resources --name + ├── [example-use.yaml] Validator + └── [example-use.yaml] Deployment nginx + └── spec.template.spec.containers + └── 0 + ├── name: nginx + └── resources: {requests: {cpu: 4, memory: 1GiB}} + + #### 2: Change the tshirt-size + + Change the `tshirt-size` annotation from `medium` to `small` and re-run the functions. + + kustomize config run-fns local-resource/ + kustomize config tree local-resource/ + local-resource + ├── [example-use.yaml] Validator + └── [example-use.yaml] Deployment nginx + └── spec.template.spec.containers + └── 0 + ├── name: nginx + └── resources: {requests: {cpu: 200m, memory: 50MiB}} + + The function has applied the reservations for the new tshirt-size + +### Function Composition + +Functions may be composed together. Try putting the Injection (tshirt-size) and +Validation functions together in the same .yaml file (separated by `---`). Run +`run-fns` and observe that the first function in the file is applied to the Resources, +and then the second function in the file is applied. \ No newline at end of file diff --git a/functions/examples/injection-tshirt-sizes/LICENSE_TEMPLATE b/functions/examples/injection-tshirt-sizes/LICENSE_TEMPLATE new file mode 100644 index 000000000..0c2b3b655 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/LICENSE_TEMPLATE @@ -0,0 +1,2 @@ +Copyright {{.Year}} {{.Holder}} +SPDX-License-Identifier: Apache-2.0 diff --git a/functions/examples/injection-tshirt-sizes/Makefile b/functions/examples/injection-tshirt-sizes/Makefile new file mode 100644 index 000000000..8e3dcc300 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/Makefile @@ -0,0 +1,42 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +.PHONY: generate license fix vet fmt test build tidy image + +GOBIN := $(shell go env GOPATH)/bin + +build: + (cd image && go build -v -o $(GOBIN)/config-function .) + +all: generate license build fix vet fmt test lint tidy + +fix: + (cd image && go fix ./...) + +fmt: + (cd image && go fmt ./...) + +generate: + (which $(GOBIN)/mdtogo || go get sigs.k8s.io/kustomize/cmd/mdtogo) + (cd image && GOBIN=$(GOBIN) go generate ./...) + +license: + (which $(GOPATH)/bin/addlicense || go get github.com/google/addlicense) + $(GOPATH)/bin/addlicense -y 2019 -c "The Kubernetes Authors." -f LICENSE_TEMPLATE . + +tidy: + (cd image && go mod tidy) + +lint: + (which $(GOBIN)/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1) + (cd image && $(GOBIN)/golangci-lint run ./...) + +test: + (cd image && go test -cover ./...) + +vet: + (cd image && go vet ./...) + +image: + docker build image -t gcr.io/kustomize-functions/example-tshirt:v0.1.0 + docker push gcr.io/kustomize-functions/example-tshirt:v0.1.0 diff --git a/functions/examples/injection-tshirt-sizes/README.md b/functions/examples/injection-tshirt-sizes/README.md new file mode 100644 index 000000000..7ee9d1608 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/README.md @@ -0,0 +1,35 @@ +# Injection + +This is an example of implementing an injection function. + +This example is written in `go` and uses the `kyaml` libraries for parsing the +input and writing the output. Writing in `go` is not a requirement. + +## Function implementation + +The function is implemented as an [image](image), and built using `make image`. + +The template is implemented as a go program, which reads a collection of input +Resource configuration, and looks for invalid configuration. + +## Function invocation + +The function is invoked by authoring a [local Resource](local-resource) +with `metadata.configFn` and running: + + kustomize config run-fns local-resources/ + +This exists non-zero if there is an error. + +## Running the Example + +Run the validator with: + + kustomize config run-fns local-resource/ + +This will add resource reservations to the Deployment. Change the `tshirt-size` +annotation from `medium` to `small` and rerun: + + kustomize config run-fns local-resource/ + +Observe that the reservations have changed. \ No newline at end of file diff --git a/functions/examples/injection-tshirt-sizes/image/Dockerfile b/functions/examples/injection-tshirt-sizes/image/Dockerfile new file mode 100644 index 000000000..b773e4208 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/image/Dockerfile @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +FROM golang:1.13-stretch +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod . +COPY go.sum . +RUN go mod download +COPY main.go . +RUN go build -v -o /usr/local/bin/config-function ./ + +FROM alpine:latest +COPY --from=0 /usr/local/bin/config-function /usr/local/bin/config-function +CMD ["config-function"] \ No newline at end of file diff --git a/functions/examples/injection-tshirt-sizes/image/go.mod b/functions/examples/injection-tshirt-sizes/image/go.mod new file mode 100644 index 000000000..4b54a51ac --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/image/go.mod @@ -0,0 +1,5 @@ +module sigs.k8s.io/kustomize/functions/examples/injection-tshirt-sizes + +go 1.13 + +require sigs.k8s.io/kustomize/kyaml v0.0.0-20191126155111-73fb32c85ad4 diff --git a/functions/examples/injection-tshirt-sizes/image/go.sum b/functions/examples/injection-tshirt-sizes/image/go.sum new file mode 100644 index 000000000..dfe158f56 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/image/go.sum @@ -0,0 +1,27 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/kustomize/kyaml v0.0.0-20191126155111-73fb32c85ad4 h1:QfQuib3Xi0uyHUi/R5MgWmDScb5iy/+LjoaGIdpmMpE= +sigs.k8s.io/kustomize/kyaml v0.0.0-20191126155111-73fb32c85ad4/go.mod h1:rywm/rcR5LmCBghz9956tE45OdUPChFoXVVs+WmhMTI= diff --git a/functions/examples/injection-tshirt-sizes/image/main.go b/functions/examples/injection-tshirt-sizes/image/main.go new file mode 100644 index 000000000..3f480f0c2 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/image/main.go @@ -0,0 +1,116 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package main implements an injection function for resource reservations and +// is run with `kustomize config run-fns -- DIR/`. +package main + +import ( + "fmt" + "os" + + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func main() { + rw := &kio.ByteReadWriter{Reader: os.Stdin, Writer: os.Stdout, KeepReaderAnnotations: true} + p := kio.Pipeline{ + Inputs: []kio.Reader{rw}, // read the inputs into a slice + Filters: []kio.Filter{filter{}}, // run the inject into the inputs + Outputs: []kio.Writer{rw}} // copy the inputs to the output + if err := p.Execute(); err != nil { + os.Exit(1) + } +} + +// filter implements kio.Filter +type filter struct{} + +// Filter injects cpu and memory resource reservations into containers for +// Resources containing the `tshirt-size` annotation. +func (filter) Filter(in []*yaml.RNode) ([]*yaml.RNode, error) { + // inject the resource reservations into each Resource + for _, r := range in { + if err := inject(r); err != nil { + return nil, err + } + } + return in, nil +} + +// cpuSizes is the mapping from tshirt-size to cpu reservation quantity +var cpuSizes = map[string]string{ + "small": "200m", + "medium": "4", + "large": "16", +} + +// memorySizes is the mapping from tshirt-size to memory reservation quantity +var memorySizes = map[string]string{ + "small": "50MiB", + "medium": "1GiB", + "large": "32GiB", +} + +// inject sets the cpu and memory reservations on all containers for Resources annotated +// with `tshirt-size: small|medium|large` +func inject(r *yaml.RNode) error { + // lookup the containers field + containers, err := r.Pipe(yaml.Lookup("spec", "template", "spec", "containers")) + if err != nil { + s, _ := r.String() + return fmt.Errorf("%v: %s", err, s) + } + if containers == nil { + // doesn't have containers, skip the Resource + return nil + } + + // check for the tshirt-size annotations + meta, err := r.GetMeta() + if err != nil { + return err + } + var memorySize, cpuSize string + if size, found := meta.Annotations["tshirt-size"]; !found { + // not a tshirt-sized Resource, ignore it + return nil + } else { + // lookup the memory and cpu quantities based on the tshirt size + memorySize = memorySizes[size] + cpuSize = cpuSizes[size] + if memorySize == "" || cpuSize == "" { + return fmt.Errorf("unsupported tshirt-size: " + size) + } + } + + // visit each container and apply the cpu and memory reservations + return containers.VisitElements(func(node *yaml.RNode) error { + // set cpu + err := node.PipeE( + // lookup resources.requests.cpu, creating the field as a + // ScalarNode if it doesn't exist + yaml.LookupCreate(yaml.ScalarNode, "resources", "requests", "cpu"), + // set the field value to the cpuSize + yaml.Set(yaml.NewScalarRNode(cpuSize))) + if err != nil { + s, _ := r.String() + return fmt.Errorf("%v: %s", err, s) + } + + // set memory + err = node.PipeE( + // lookup resources.requests.memory, creating the field as a + // ScalarNode if it doesn't exist + yaml.LookupCreate(yaml.ScalarNode, "resources", "requests", "memory"), + // set the field value to the memorySize + yaml.Set(yaml.NewScalarRNode(memorySize))) + if err != nil { + s, _ := r.String() + return fmt.Errorf("%v: %s", err, s) + } + + return nil + }) +} diff --git a/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml b/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml new file mode 100644 index 000000000..19235b192 --- /dev/null +++ b/functions/examples/injection-tshirt-sizes/local-resource/example-use.yaml @@ -0,0 +1,30 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: examples.config.kubernetes.io/v1beta1 +kind: Validator +metadata: + configFn: + container: + image: gcr.io/kustomize-functions/example-tshirt:v0.1.0 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx + annotations: + tshirt-size: small # this injects the resource reservations +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx diff --git a/functions/examples/template-go-nginx/LICENSE_TEMPLATE b/functions/examples/template-go-nginx/LICENSE_TEMPLATE new file mode 100644 index 000000000..0c2b3b655 --- /dev/null +++ b/functions/examples/template-go-nginx/LICENSE_TEMPLATE @@ -0,0 +1,2 @@ +Copyright {{.Year}} {{.Holder}} +SPDX-License-Identifier: Apache-2.0 diff --git a/functions/examples/template-go-nginx/Makefile b/functions/examples/template-go-nginx/Makefile new file mode 100644 index 000000000..3149fd10c --- /dev/null +++ b/functions/examples/template-go-nginx/Makefile @@ -0,0 +1,42 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +.PHONY: generate license fix vet fmt test build tidy image + +GOBIN := $(shell go env GOPATH)/bin + +build: + (cd image && go build -v -o $(GOBIN)/config-function .) + +all: generate license build fix vet fmt test lint tidy + +fix: + (cd image && go fix ./...) + +fmt: + (cd image && go fmt ./...) + +generate: + (which $(GOBIN)/mdtogo || go get sigs.k8s.io/kustomize/cmd/mdtogo) + (cd image && GOBIN=$(GOBIN) go generate ./...) + +license: + (which $(GOPATH)/bin/addlicense || go get github.com/google/addlicense) + $(GOPATH)/bin/addlicense -y 2019 -c "The Kubernetes Authors." -f LICENSE_TEMPLATE . + +tidy: + (cd image && go mod tidy) + +lint: + (which $(GOBIN)/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1) + (cd image && $(GOBIN)/golangci-lint run ./...) + +test: + (cd image && go test -cover ./...) + +vet: + (cd image && go vet ./...) + +image: + docker build image -t gcr.io/kustomize-functions/example-nginx:v0.1.0 + docker push gcr.io/kustomize-functions/example-nginx:v0.1.0 diff --git a/functions/examples/template-go-nginx/README.md b/functions/examples/template-go-nginx/README.md new file mode 100644 index 000000000..a5076958c --- /dev/null +++ b/functions/examples/template-go-nginx/README.md @@ -0,0 +1,55 @@ +# Template GoTemplate + +This is an example of implementing a template function using a `go` template. + +This example uses a more sophisticated approach for building abstractions. + +## Function implementation + +The function is implemented as an [image](image), and built using `make image`. + +The template is implemented as a go program with a `go` template. It parses the +functionConfig into a `go` struct and uses the `kyaml` module for parsing the +function input, and writing the function output. + +1. read the inputs (stdin) +2. apply filters + - (custom filter) generate the nginx Deployment and Service from go templates using the + functionConfig as the template input. + - merge the generated Deployment and Service with the input Deployment and Service if + present + - set filenames on the Resources (`NAME.yaml`) + - format the Resources +3. write the outputs (stdout) + +## Function invocation + +The function is invoked by authoring a [local Resource](local-resource) +with `metadata.configFn` and running: + + kustomize config run-fns local-resources/ + +This generates the `local-resources/config` directory containing the template output. + +- the template output may be modified by adding fields -- such as initContainers, + sidecarConatiners, cpu resource limits, etc -- and these fields will be retained + when re-running `run-fns` +- the function input `example-use.yaml` may be changed and rerunning `run-fns` will update + only the parts changed in the template output. + +## Running the Example + +Run the config with: + + kustomize config run-fns local-resource/ + +This will create the directory + + local-resource/config + +Add an annotation to the StatefulSet Resource and change the replica count of the +`kind: Nginx` Resource in `example-use.yaml`. Rerun the template: + + kustomize config run-fns local-resource/ + +The replica count should be updated, but your annotation should remain. \ No newline at end of file diff --git a/functions/examples/template-go-nginx/image/Dockerfile b/functions/examples/template-go-nginx/image/Dockerfile new file mode 100644 index 000000000..407e37d3d --- /dev/null +++ b/functions/examples/template-go-nginx/image/Dockerfile @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +FROM golang:1.13-stretch +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod . +COPY go.sum . +RUN go mod download +COPY main.go . +RUN go build -v -o /usr/local/bin/config-function ./ + +FROM alpine:latest +COPY --from=0 /usr/local/bin/config-function /usr/local/bin/config-function +CMD ["config-function"] diff --git a/functions/examples/template-go-nginx/image/go.mod b/functions/examples/template-go-nginx/image/go.mod new file mode 100644 index 000000000..3256c4a3b --- /dev/null +++ b/functions/examples/template-go-nginx/image/go.mod @@ -0,0 +1,5 @@ +module sigs.k8s.io/kustomize/functions/examples/template-go-nginx + +go 1.12 + +require sigs.k8s.io/kustomize/kyaml v0.0.0-20191126203124-d1b33e746881 diff --git a/functions/examples/template-go-nginx/image/go.sum b/functions/examples/template-go-nginx/image/go.sum new file mode 100644 index 000000000..253ca77cf --- /dev/null +++ b/functions/examples/template-go-nginx/image/go.sum @@ -0,0 +1,27 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/kustomize/kyaml v0.0.0-20191126203124-d1b33e746881 h1:dmMlmv+TXMSAtCf6Za1GKNAjCssKtgnVkAYRiKdnjIY= +sigs.k8s.io/kustomize/kyaml v0.0.0-20191126203124-d1b33e746881/go.mod h1:rywm/rcR5LmCBghz9956tE45OdUPChFoXVVs+WmhMTI= diff --git a/functions/examples/template-go-nginx/image/main.go b/functions/examples/template-go-nginx/image/main.go new file mode 100644 index 000000000..0e5209ae7 --- /dev/null +++ b/functions/examples/template-go-nginx/image/main.go @@ -0,0 +1,169 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strconv" + "text/template" + + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/filters" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func main() { + rw := &kio.ByteReadWriter{ + Reader: os.Stdin, + Writer: os.Stdout, + KeepReaderAnnotations: true, + } + + err := kio.Pipeline{ + Inputs: []kio.Reader{rw}, + Filters: []kio.Filter{ + &filter{rw: rw}, // generate the Resources from the template + filters.MergeFilter{}, // merge the generated template + // set Resource filenames + &filters.FileSetter{FilenamePattern: filepath.Join("config", "%n.yaml")}, + filters.FormatFilter{}, // format the output + }, + Outputs: []kio.Writer{rw}, + }.Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +// define the input API schema as a struct +type API struct { + Metadata struct { + // Name is the Deployment Resource and Container name + Name string `yaml:"name"` + } `yaml:"metadata"` + + Spec struct { + // Replicas is the number of Deployment replicas + // Defaults to the REPLICAS env var, or 1 + Replicas *int `yaml:"replicas"` + } `yaml:"spec"` +} + +// filter implements kio.Filter +type filter struct { + rw *kio.ByteReadWriter +} + +// Filter checks each input and ensures that all containers have cpu and memory +// reservations set, otherwise it returns an error. +func (f *filter) Filter(in []*yaml.RNode) ([]*yaml.RNode, error) { + api := f.parseAPI() + + // execute the service template + buff := &bytes.Buffer{} + t := template.Must(template.New("nginx-service").Parse(serviceTemplate)) + if err := t.Execute(buff, api); err != nil { + return nil, err + } + s, err := yaml.Parse(buff.String()) + if err != nil { + return nil, err + } + + // execute the deployment template + buff = &bytes.Buffer{} + t = template.Must(template.New("nginx-deployment").Parse(deploymentTemplate)) + if err := t.Execute(buff, api); err != nil { + return nil, err + } + d, err := yaml.Parse(buff.String()) + if err != nil { + return nil, err + } + + // add the template generated Resources to the output -- these will get merged by the next + // filter + in = append(in, s, d) + return in, nil +} + +// parseAPI parses the functionConfig into an API struct, and validates the input +func (f *filter) parseAPI() API { + // parse the input function config -- TODO: simplify this + var api API + if err := yaml.Unmarshal([]byte(f.rw.FunctionConfig.MustString()), &api); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + + // Default functionConfig values from environment variables if they are not set + // in the functionConfig + r := os.Getenv("REPLICAS") + if r != "" && api.Spec.Replicas == nil { + replicas, err := strconv.Atoi(r) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + api.Spec.Replicas = &replicas + } + if api.Spec.Replicas == nil { + r := 1 + api.Spec.Replicas = &r + } + if api.Metadata.Name == "" { + fmt.Fprintf(os.Stderr, "must specify metadata.name\n") + os.Exit(1) + } + + return api +} + +var serviceTemplate = ` +apiVersion: v1 +kind: Service +metadata: + name: {{ .Metadata.Name }} + labels: + app: nginx + instance: {{ .Metadata.Name }} +spec: + ports: + - port: 80 + targetPort: 80 + name: http + selector: + app: nginx + instance: {{ .Metadata.Name }} +` + +var deploymentTemplate = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Metadata.Name }} + labels: + app: nginx + instance: {{ .Metadata.Name }} +spec: + replicas: {{ .Spec.Replicas }} + selector: + matchLabels: + app: nginx + instance: {{ .Metadata.Name }} + template: + metadata: + labels: + app: nginx + instance: {{ .Metadata.Name }} + spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 +` diff --git a/functions/examples/template-go-nginx/local-resource/example-use.yaml b/functions/examples/template-go-nginx/local-resource/example-use.yaml new file mode 100644 index 000000000..d0b06026c --- /dev/null +++ b/functions/examples/template-go-nginx/local-resource/example-use.yaml @@ -0,0 +1,12 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: examples.config.kubernetes.io/v1beta1 # call `kustomize config run-fns` on a directory containing this file +kind: Nginx +metadata: + name: demo + configFn: + container: + image: gcr.io/kustomize-functions/example-nginx:v0.1.0 +spec: + replicas: 4 diff --git a/functions/examples/template-heredoc-cockroachdb/LICENSE_TEMPLATE b/functions/examples/template-heredoc-cockroachdb/LICENSE_TEMPLATE new file mode 100644 index 000000000..0c2b3b655 --- /dev/null +++ b/functions/examples/template-heredoc-cockroachdb/LICENSE_TEMPLATE @@ -0,0 +1,2 @@ +Copyright {{.Year}} {{.Holder}} +SPDX-License-Identifier: Apache-2.0 diff --git a/functions/examples/template-heredoc-cockroachdb/Makefile b/functions/examples/template-heredoc-cockroachdb/Makefile new file mode 100644 index 000000000..fabbaeb52 --- /dev/null +++ b/functions/examples/template-heredoc-cockroachdb/Makefile @@ -0,0 +1,12 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +.PHONY: license image + +license: + (which $(GOPATH)/bin/addlicense || go get github.com/google/addlicense) + $(GOPATH)/bin/addlicense -y 2019 -c "The Kubernetes Authors." -f LICENSE_TEMPLATE . + +image: + docker build image -t gcr.io/kustomize-functions/example-cockroachdb:v0.1.0 + docker push gcr.io/kustomize-functions/example-cockroachdb:v0.1.0 diff --git a/functions/examples/template-heredoc-cockroachdb/README.md b/functions/examples/template-heredoc-cockroachdb/README.md new file mode 100644 index 000000000..24abd86b8 --- /dev/null +++ b/functions/examples/template-heredoc-cockroachdb/README.md @@ -0,0 +1,50 @@ +# Template HereDoc + +This is an example of implementing a template function using a heredoc. + +This example uses the simplest approach for building abstractions. + +## Function implementation + +The function is implemented as an [image](image), and built using `make image`. + +The template is implemented as a heredoc, which substitutes environment variables +into a static string. + +This simple implementation uses `kustomize config run-fns wrap --` to perform the +heavy lifting of implementing the function interface. + +- parse functionConfig from stdin into environment variables +- merge script output with items from stdin + +## Function invocation + +The function is invoked by authoring a [local Resource](local-resource) +with `metadata.configFn` and running: + + kustomize config run-fns local-resources/ + +This generates the `local-resources/config` directory containing the template output. + +- the template output may be modified by adding fields -- such as initContainers, + sidecarConatiners, cpu resource limits, etc -- and these fields will be retained + when re-running `run-fns` +- the function input `example-use.yaml` may be changed and rerunning `run-fns` will update + only the parts changed in the template output. + +## Running the Example + +Run the config with: + + kustomize config run-fns local-resource/ + +This will create the directory + + local-resource/config + +Add an annotation to the StatefulSet Resource and change the replica count of the +`kind: CockroachDB` Resource in `example-use.yaml`. Rerun the template: + + kustomize config run-fns local-resource/ + +The replica count should be updated, but your annotation should remain. \ No newline at end of file diff --git a/functions/examples/template-heredoc-cockroachdb/image/Dockerfile b/functions/examples/template-heredoc-cockroachdb/image/Dockerfile new file mode 100644 index 000000000..7544ee624 --- /dev/null +++ b/functions/examples/template-heredoc-cockroachdb/image/Dockerfile @@ -0,0 +1,12 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +FROM golang:1.13-stretch +ENV CGO_ENABLED=0 +RUN go get -v sigs.k8s.io/kustomize/kustomize + +FROM alpine:latest +RUN apk add --no-cache bash +COPY --from=0 /go/bin/kustomize /usr/local/bin +COPY cockroachdb-template.sh /usr/local/bin/config-function +CMD ["config-function"] \ No newline at end of file diff --git a/functions/examples/template-heredoc-cockroachdb/image/cockroachdb-template.sh b/functions/examples/template-heredoc-cockroachdb/image/cockroachdb-template.sh new file mode 100755 index 000000000..e560141b0 --- /dev/null +++ b/functions/examples/template-heredoc-cockroachdb/image/cockroachdb-template.sh @@ -0,0 +1,204 @@ +#!/bin/bash +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + + +# use `kustomize config run-fns wrap` to parse the container stdin into +# environment variables, and to merge the template output into the +# input Resources. +if [ -z ${WRAPPED} ]; then + export WRAPPED=true + kustomize config run-fns wrap -- $0 + exit $? +fi + +# this is the template for a cockroachdb instance +# environment variables are parsed from the input functionConfig by +# `kustomize config run-fns wrap` +cat <" + initContainers: + - name: bootstrap + image: cockroachdb/cockroach-k8s-init:0.1 + imagePullPolicy: IfNotPresent + args: + - "-on-start=/on-start.sh" + - "-service=cockroachdb" + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: datadir + mountPath: "/cockroach/cockroach-data" + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - cockroachdb + topologyKey: kubernetes.io/hostname + containers: + - name: ${NAME} + image: cockroachdb/cockroach:v1.1.0 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 26257 + name: grpc + - containerPort: 8080 + name: http + volumeMounts: + - name: datadir + mountPath: /cockroach/cockroach-data + command: + - "/bin/bash" + - "-ecx" + - | + # The use of qualified \`hostname -f\` is crucial: + # Other nodes aren't able to look up the unqualified hostname. + CRARGS=("start" "--logtostderr" "--insecure" "--host" "\$(hostname -f)" "--http-host" "0.0.0.0") + # We only want to initialize a new cluster (by omitting the join flag) + # if we're sure that we're the first node (i.e. index 0) and that + # there aren't any other nodes running as part of the cluster that + # this is supposed to be a part of (which indicates that a cluster + # already exists and we should make sure not to create a new one). + # It's fine to run without --join on a restart if there aren't any + # other nodes. + if [ ! "\$(hostname)" == "cockroachdb-0" ] || \ + [ -e "/cockroach/cockroach-data/cluster_exists_marker" ] + then + # We don't join cockroachdb in order to avoid a node attempting + # to join itself, which currently doesn't work + # (https://github.com/cockroachdb/cockroach/issues/9625). + CRARGS+=("--join" "cockroachdb-public") + fi + exec /cockroach/cockroach \${CRARGS[*]} + # No pre-stop hook is required, a SIGTERM plus some time is all that's + # needed for graceful shutdown of a node. + terminationGracePeriodSeconds: 60 + volumes: + - name: datadir + persistentVolumeClaim: + claimName: datadir + volumeClaimTemplates: + - metadata: + name: datadir + spec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi +End-of-message diff --git a/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml b/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml new file mode 100644 index 000000000..5f91bfa04 --- /dev/null +++ b/functions/examples/template-heredoc-cockroachdb/local-resource/example-use.yaml @@ -0,0 +1,13 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +# call `kustomize config run-fns` on a directory containing this file +apiVersion: examples.config.kubernetes.io/v1beta1 +kind: CockroachDB +metadata: + name: demo + configFn: + container: + image: gcr.io/kustomize-functions/example-cockroachdb:v0.1.0 +spec: + replicas: 3 diff --git a/functions/examples/validator-resource-requests/LICENSE_TEMPLATE b/functions/examples/validator-resource-requests/LICENSE_TEMPLATE new file mode 100644 index 000000000..0c2b3b655 --- /dev/null +++ b/functions/examples/validator-resource-requests/LICENSE_TEMPLATE @@ -0,0 +1,2 @@ +Copyright {{.Year}} {{.Holder}} +SPDX-License-Identifier: Apache-2.0 diff --git a/functions/examples/validator-resource-requests/Makefile b/functions/examples/validator-resource-requests/Makefile new file mode 100644 index 000000000..95fb12b89 --- /dev/null +++ b/functions/examples/validator-resource-requests/Makefile @@ -0,0 +1,42 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +.PHONY: generate license fix vet fmt test build tidy image + +GOBIN := $(shell go env GOPATH)/bin + +build: + (cd image && go build -v -o $(GOBIN)/config-function .) + +all: generate license build fix vet fmt test lint tidy + +fix: + (cd image && go fix ./...) + +fmt: + (cd image && go fmt ./...) + +generate: + (which $(GOBIN)/mdtogo || go get sigs.k8s.io/kustomize/cmd/mdtogo) + (cd image && GOBIN=$(GOBIN) go generate ./...) + +license: + (which $(GOPATH)/bin/addlicense || go get github.com/google/addlicense) + $(GOPATH)/bin/addlicense -y 2019 -c "The Kubernetes Authors." -f LICENSE_TEMPLATE . + +tidy: + (cd image && go mod tidy) + +lint: + (which $(GOBIN)/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1) + (cd image && $(GOBIN)/golangci-lint run ./...) + +test: + (cd image && go test -cover ./...) + +vet: + (cd image && go vet ./...) + +image: + docker build image -t gcr.io/kustomize-functions/example-validator:v0.1.0 + docker push gcr.io/kustomize-functions/example-validator:v0.1.0 diff --git a/functions/examples/validator-resource-requests/README.md b/functions/examples/validator-resource-requests/README.md new file mode 100644 index 000000000..a733d0720 --- /dev/null +++ b/functions/examples/validator-resource-requests/README.md @@ -0,0 +1,38 @@ +# Validation + +This is an example of implementing a validation function. + +This example is written in `go` and uses the `kyaml` libraries for parsing the +input and writing the output. Writing in `go` is not a requirement. + +## Function implementation + +The function is implemented as an [image](image), and built using `make image`. + +The template is implemented as a go program, which reads a collection of input +Resource configuration, and looks for invalid configuration. + +## Function invocation + +The function is invoked by authoring a [local Resource](local-resource) +with `metadata.configFn` and running: + + kustomize config run-fns local-resources/ + +This exists non-zero if there is an error. + +## Running the Example + +Run the validator with: + + kustomize config run-fns local-resource/ + +This will return an error: + + cpu-requests missing for container nginx + +Now uncomment the resource reservations and run again: + + kustomize config run-fns local-resource/ + +This will return success \ No newline at end of file diff --git a/functions/examples/validator-resource-requests/image/Dockerfile b/functions/examples/validator-resource-requests/image/Dockerfile new file mode 100644 index 000000000..407e37d3d --- /dev/null +++ b/functions/examples/validator-resource-requests/image/Dockerfile @@ -0,0 +1,15 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +FROM golang:1.13-stretch +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod . +COPY go.sum . +RUN go mod download +COPY main.go . +RUN go build -v -o /usr/local/bin/config-function ./ + +FROM alpine:latest +COPY --from=0 /usr/local/bin/config-function /usr/local/bin/config-function +CMD ["config-function"] diff --git a/functions/examples/validator-resource-requests/image/go.mod b/functions/examples/validator-resource-requests/image/go.mod new file mode 100644 index 000000000..0ca504273 --- /dev/null +++ b/functions/examples/validator-resource-requests/image/go.mod @@ -0,0 +1,5 @@ +module sigs.k8s.io/kustomize/functions/examples/validator-resource-requests + +go 1.13 + +require sigs.k8s.io/kustomize/kyaml v0.0.0-20191126155111-73fb32c85ad4 diff --git a/functions/examples/validator-resource-requests/image/go.sum b/functions/examples/validator-resource-requests/image/go.sum new file mode 100644 index 000000000..dfe158f56 --- /dev/null +++ b/functions/examples/validator-resource-requests/image/go.sum @@ -0,0 +1,27 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/kustomize/kyaml v0.0.0-20191126155111-73fb32c85ad4 h1:QfQuib3Xi0uyHUi/R5MgWmDScb5iy/+LjoaGIdpmMpE= +sigs.k8s.io/kustomize/kyaml v0.0.0-20191126155111-73fb32c85ad4/go.mod h1:rywm/rcR5LmCBghz9956tE45OdUPChFoXVVs+WmhMTI= diff --git a/functions/examples/validator-resource-requests/image/main.go b/functions/examples/validator-resource-requests/image/main.go new file mode 100644 index 000000000..892ac8c1e --- /dev/null +++ b/functions/examples/validator-resource-requests/image/main.go @@ -0,0 +1,94 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package main implements a validator function run by `kustomize config run-fns` +package main + +import ( + "fmt" + "os" + + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func main() { + rw := &kio.ByteReadWriter{Reader: os.Stdin, Writer: os.Stdout, KeepReaderAnnotations: true} + p := kio.Pipeline{ + Inputs: []kio.Reader{rw}, // read the inputs into a slice + Filters: []kio.Filter{filter{}}, // run the filter against the inputs + Outputs: []kio.Writer{rw}} // copy the inputs to the output + if err := p.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +// filter implements kio.Filter +type filter struct{} + +// Filter checks each input and ensures that all containers have cpu and memory +// reservations set, otherwise it returns an error. +func (filter) Filter(in []*yaml.RNode) ([]*yaml.RNode, error) { + // validate each Resource + for _, r := range in { + if err := validate(r); err != nil { + return nil, err + } + } + return in, nil +} + +func validate(r *yaml.RNode) error { + meta, err := r.GetMeta() + if err != nil { + return err + } + + // lookup the containers field in the Resource + containers, err := r.Pipe(yaml.Lookup("spec", "template", "spec", "containers")) + if err != nil { + s, _ := r.String() + return fmt.Errorf("%v: %s", err, s) + } + if containers == nil { + // doesn't have containers, ignore it + return nil + } + + // visit each container in the list and validate + return containers.VisitElements(func(node *yaml.RNode) error { + + // check cpu is non-nil + f, err := node.Pipe(yaml.Lookup("resources", "requests", "cpu")) + if err != nil { + s, _ := r.String() + return fmt.Errorf("%v: %s", err, s) + } + if f == nil { + return fmt.Errorf( + "cpu-requests missing for a container in %s %s (%s [%s])", + meta.Kind, meta.Name, + meta.Annotations[kioutil.PathAnnotation], + meta.Annotations[kioutil.IndexAnnotation]) + } + + // check memory is non-nil + f, err = node.Pipe(yaml.Lookup("resources", "requests", "memory")) + if err != nil { + s, _ := r.String() + return fmt.Errorf("%v: %s", err, s) + } + if f == nil { + return fmt.Errorf( + "memory-requests missing for a container in %s in %s (%s [%s])", + meta.Kind, meta.Name, + meta.Annotations[kioutil.PathAnnotation], + meta.Annotations[kioutil.IndexAnnotation]) + } + + // container is valid + return nil + }) +} diff --git a/functions/examples/validator-resource-requests/local-resource/example-use.yaml b/functions/examples/validator-resource-requests/local-resource/example-use.yaml new file mode 100644 index 000000000..c90e66fe9 --- /dev/null +++ b/functions/examples/validator-resource-requests/local-resource/example-use.yaml @@ -0,0 +1,33 @@ +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: examples.config.kubernetes.io/v1beta1 +kind: Validator +metadata: + configFn: + container: + image: gcr.io/kustomize-functions/example-validator:v0.1.0 +--- +apiVersion: apps/v1 # this should fail validation +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx + # uncomment these to pass validation +# resources: +# requests: +# cpu: 100m +# memory: 300MiB