Compare commits

...

66 Commits

Author SHA1 Message Date
Jeff Regan
9f5697b154 Update OWNERS_ALIASES 2021-06-08 16:54:20 -07:00
Jeff Regan
b54093ebca Merge pull request #3972 from KnVerey/contributor_ladder
Document contributor roles
2021-06-08 16:43:18 -07:00
Katrina Verey
db307a7084 Document contributor roles 2021-06-08 16:39:37 -07:00
Jeff Regan
a0c7997b66 Merge pull request #3963 from natasha41575/KustomizeEditFix
small `api` changes for kustomize fix
2021-06-08 13:52:06 -07:00
Natasha Sarkar
7458a53a73 copy method for selector 2021-06-08 11:18:38 -07:00
Natasha Sarkar
cf6e6ca4db omitempty for replacement type 2021-06-08 11:18:27 -07:00
Natasha Sarkar
e847ec7474 ReadDir method for filesys 2021-06-08 11:18:11 -07:00
Jeff Regan
440026b9b3 Merge pull request #3965 from mengqiy/listcomments
handle comments in list correctly
2021-06-07 17:32:53 -07:00
Kubernetes Prow Robot
64331ad845 Merge pull request #3964 from ash2k/ash2k/handle-errors2
Handle errors
2021-06-07 16:26:03 -07:00
Mengqi Yu
294070b3ab address comments 2021-06-07 15:06:57 -07:00
Mengqi Yu
cabbea0d97 handle comments in list correctly 2021-06-07 14:57:49 -07:00
Jeff Regan
732a8522df Merge pull request #3968 from monopole/fixFailingHelmTest
Don't run helm inflator test against released version.
2021-06-07 13:19:02 -07:00
monopole
8f82c4c748 Don't run helm inflator test against released version. 2021-06-07 12:26:12 -07:00
Jeff Regan
d0bc25f339 Update byteio_writer.go 2021-06-07 12:25:04 -07:00
Jeff Regan
ed3200e4f5 Remove bad TODO 2021-06-04 17:56:42 -07:00
Mikhail Mazurskiy
a3ed120efb Handle errors 2021-06-05 09:43:13 +10:00
Jeff Regan
f1b191c02f Update README.md 2021-06-04 16:22:59 -07:00
Jeff Regan
1493b24b46 Update README.md 2021-06-04 16:21:25 -07:00
Kubernetes Prow Robot
5993eae1aa Merge pull request #3934 from yhrn/fix-json-sink
Sink: Force JSON encoding for .json files
2021-06-04 11:38:38 -07:00
Mattias Öhrn
3e506eae02 PR feedback - more tests and some cleanup 2021-06-04 11:06:22 +02:00
Kubernetes Prow Robot
0305860078 Merge pull request #3962 from natasha41575/resourceRef
remove metadata field from resourceRef schema
2021-06-03 15:57:02 -07:00
Natasha Sarkar
0205090e0d remove metadata field from resourceRef schema 2021-06-03 12:46:51 -07:00
Jeff Regan
da1bd901b4 Merge pull request #3939 from KnVerey/framework_test_helpers
[kyaml] Improvements to frameworktestutil
2021-06-03 11:14:54 -07:00
Jeff Regan
636b9c7aeb Merge pull request #3931 from natasha41575/ReplacementOverwritesSource
Fix issues with replacements
2021-06-03 10:57:01 -07:00
Mattias Öhrn
942f112ef5 Fix kyaml tests 2021-06-03 08:31:03 +02:00
Mattias Öhrn
03bbb076bf plugin/builtin/iampolicygenerator/go.sum tidy 2021-06-03 08:30:38 +02:00
Mattias Öhrn
e468d6b4d2 Fixing cmd/config/internal/commands/e2e/e2e_test.go 2021-06-03 08:29:53 +02:00
Kubernetes Prow Robot
57206a628d Merge pull request #3955 from mengqiy/betterresults
Check for empty GKNN when formatting results
2021-06-02 18:05:38 -07:00
Mengqi Yu
f061bb887b Check for empty GKNN when formatting results 2021-06-02 16:32:18 -07:00
Kubernetes Prow Robot
75fd9a43a3 Merge pull request #3925 from frankfarzan/krm-functions-spec
Fully specify KRM Functions Spec and graduate it to v1.
2021-06-02 12:10:42 -07:00
Frank Farzan
58165dfc89 comments 2021-06-02 10:53:49 -07:00
Frank Farzan
0e8257c387 comments 2021-06-02 10:50:39 -07:00
Kubernetes Prow Robot
62e78f8349 Merge pull request #3940 from Shell32-Natsu/resource-factory
add an option to include local configs
2021-06-01 17:20:12 -07:00
Natasha Sarkar
84724a3ebf smarter path splitter for replacements 2021-06-01 17:15:54 -07:00
Donny Xia
23544e0431 code review 2021-06-01 16:25:41 -07:00
Donny Xia
b1fda3d62e add an option to include local configs 2021-06-01 13:38:36 -07:00
Natasha Sarkar
b8ae69b748 copy target rnode in replacements 2021-06-01 13:24:51 -07:00
Natasha Sarkar
4014440d06 test to demonstrate '.' in list element path issue 2021-06-01 13:24:51 -07:00
Natasha Sarkar
74b0b3adc6 test to demonstrate multiple fieldpaths issue in replacements 2021-06-01 13:24:51 -07:00
Katrina Verey
382f09a126 Make frameworktestutil assertions configurable
Also refactor to share common code between results checkers
2021-06-01 12:20:23 -07:00
Katrina Verey
f9afdc5c95 Improve frameworktestutil usability with complex error messages 2021-06-01 12:20:17 -07:00
Mattias Öhrn
5e4fb4796e Only encoding non-wrapped single .json items as JSON 2021-06-01 20:44:21 +02:00
Katrina Verey
76f8988865 Mark updated tests as skipped instead of passed 2021-05-31 10:56:32 -07:00
Mattias Öhrn
fa3e829eb6 Sink: Force JSON encoding for .json files 2021-05-31 08:00:54 +02:00
Jeff Regan
d9435bd1b1 Merge pull request #3898 from dmizelle/add-helm-include-crds
Add includeCRDs Field to HelmChart
2021-05-27 16:04:07 -07:00
Kubernetes Prow Robot
af96bb4bda Merge pull request #3914 from natasha41575/GkeServiceAccountGenerator
Gke service account generator
2021-05-27 15:36:23 -07:00
Kubernetes Prow Robot
8607e0adec Merge pull request #3930 from monopole/demo3929
Demonstrate issue 3929
2021-05-26 17:04:06 -07:00
Natasha Sarkar
5a2a7709a4 add IAMPolicyGenerator 2021-05-26 16:54:38 -07:00
monopole
437e8f90f6 Demonstrate issue 3929 2021-05-26 16:29:55 -07:00
Jeff Regan
06ac670951 Merge pull request #3911 from ash2k/ash2k/handle-errors
Handle errors
2021-05-25 21:05:26 -07:00
Frank Farzan
3ee1579688 Fully specify KRM Functions Spec and graduate it to v1.
- Define the OpenAPI schema for ResourceList
- Graduate `ResourceList` to apiVersion `v1`. Many functions have been implemented
  and we are effectively treating the spec as backwards compatible.
- Add `results` field to the spec. This has been implemented in the
  orchestrator and couple of functions libraries for a while, but hasn't been
  yet standardized leading to minor discrepencies. Concurrent to this PR,
  we need to udpate the fn framework (kyaml) to be compatible with
  the standard and update the orchestrator to be backwards compatible.
- Include per-resource annotations (previously a seperate doc)
- Remove support for non-ResourceList wire formats. In practice, this is
  not widely used and complicates the standard. The orchestrators can
  easily covert a List to a ResourceList.
2021-05-25 16:09:32 -07:00
Jeff Regan
5954314b98 Merge pull request #3917 from zhouhao3/clean-tempfile
Cleanup tempfiles
2021-05-24 20:01:33 -07:00
Jeff Regan
c0324456a7 Merge pull request #3921 from monopole/deleteScriptsDir
Consolidate scripts into k8s-traditional hack dir.
2021-05-24 17:25:01 -07:00
monopole
172adc404f Consolidate scripts into k8s-traditional hack dir. 2021-05-24 17:09:11 -07:00
Jeff Regan
501748192b Merge pull request #3920 from monopole/unpinEverything
Back to development mode; unpin the modules
2021-05-24 09:42:57 -07:00
monopole
f6e6ac0320 Back to development mode; unpin the modules 2021-05-24 09:30:04 -07:00
Zhou Hao
a10ce1d787 cleanup tempfiles for runfn_test
Signed-off-by: Zhou Hao <zhouhao@fujitsu.com>
2021-05-24 17:01:44 +08:00
Zhou Hao
839cc2467c cleanup tempfiles for fmtr_test
Signed-off-by: Zhou Hao <zhouhao@fujitsu.com>
2021-05-24 15:52:28 +08:00
Mikhail Mazurskiy
dbc11ed29f Handle errors 2021-05-21 16:53:14 +10:00
Jeff Regan
0f614e92f7 Merge pull request #3909 from monopole/pinToApi
Pin to api v0.8.10
2021-05-20 13:44:39 -07:00
monopole
afaf7c62bc Pin to api v0.8.10 2021-05-20 13:34:27 -07:00
Jeff Regan
78d22069d7 Merge pull request #3910 from monopole/noMorePluginPin
No more plugin pinning.
2021-05-20 13:32:25 -07:00
monopole
22720a8b7a No more plugin pinning. 2021-05-20 13:31:50 -07:00
Jeff Regan
38c66d213a Update README.md 2021-05-20 13:14:28 -07:00
Devon Mizelle
73da51d0ac Apply suggestions from code review
Co-authored-by: Steven E. Harris <seh@panix.com>
2021-05-17 12:18:33 -04:00
Devon Mizelle
df10d5a17d Add includeCRDs Field to HelmChart
This commit adds functionality for a user to specify that `helm` should
include CRDs when inflating a Helm Chart.

As of Helm v3, the `install-crd` hook is no more, with
`CustomResourceDefinitions` existing in the root of the chart, under the
`crds` directory.

When calling `helm template`, `helm` does not output these CRDs unless
the user passes the `--include-crds` flag to the command.

With this commit, users can set `includeCRDs: true` as part of their helm
chart definition in `kustomize.yaml` to have these included as part of
the output.

Signed-off-by: Devon Mizelle <devon.mizelle@onepeloton.com>
2021-05-14 16:46:30 -04:00
102 changed files with 2998 additions and 728 deletions

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v2
- name: Lint
run: ./scripts/kyaml-pre-commit.sh
run: ./hack/kyaml-pre-commit.sh
env:
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting

View File

@@ -20,6 +20,20 @@ We have full documentation on how to get started contributing here:
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
## Contributor Ladder
Kustomize generally follows the [Kubernetes Community Membership](https://github.com/kubernetes/community/blob/master/community-membership.md) contributor ladder. Roles are as follows:
1. Contributor: Anyone who actively contributes code, issues or reviews to the project. There are no Kustomize-specific requirements for this status. All contributors must [sign the CLA](https://github.com/kubernetes/community/tree/master/contributors/guide#prerequisites).
1. Member/Reviewer: All Kubernetes-SIGs org members have LGTM rights on the Kustomize repo. There are no Kustomize-specific requirements. Kustomize does not currently have any formal reviewers, but the role will be created if there is interest.
1. Maintainer/Approver: Highly experienced active reviewer and contributor to Kustomize. Has both LTGM and approval rights on the Kustomize repo, as well as [Github "maintain" rights](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level).
1. Admin/Owner: Maintainer who sets technical direction and makes or approves design decisions for the project. Has LGTM and approval rights on the Kustomize repo as well as [Github "admin" rights](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level).
Administrative notes:
- Maintainers and admins must be added to the appropriate list both [in the Kustomize repo](https://github.com/kubernetes-sigs/kustomize/blob/8049f7b1af52e8a7ec26faf6cf714f560d0043c5/OWNERS_ALIASES) and [in the community repo](https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml). If this isn't done, the individual in question will lack either PR approval rights (Kustomize list) or the appropriate Github repository permissions (community list).
- The spec for the OWNERS file is [in the community repo](https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md).
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)

View File

@@ -131,6 +131,7 @@ pSrc=plugin/builtin
_builtinplugins = \
AnnotationsTransformer.go \
ConfigMapGenerator.go \
IAMPolicyGenerator.go \
HashTransformer.go \
ImageTagTransformer.go \
LabelTransformer.go \
@@ -158,6 +159,7 @@ builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
# that file, will be recreated.
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
$(pGen)/GkeSaGenerator.go: $(pSrc)/gkesagenerator/GkeSaGenerator.go
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
@@ -241,10 +243,10 @@ test-unit-kustomize-all: \
test-unit-kustomize-plugins
test-unit-cmd-all:
./scripts/kyaml-pre-commit.sh
./hack/kyaml-pre-commit.sh
test-go-mod:
./scripts/check-go-mod.sh
./hack/check-go-mod.sh
# Environment variables are defined at
# https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md#job-environment-variables
@@ -256,7 +258,7 @@ test-multi-module: $(MYGOBIN)/prchecker
export REPO_NAME=$(REPO_NAME); \
export PULL_NUMBER=$(PULL_NUMBER); \
export MODULES=$(MODULES); \
./scripts/check-multi-module.sh; \
./hack/check-multi-module.sh; \
)
.PHONY:

View File

@@ -1,17 +1,14 @@
aliases:
kustomize-admins:
- monopole
- pwittrock
kustomize-maintainers:
- droot
- justinsb
kustomize-admins: # Please keep in sync with kustomize-admins in https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
- knverey
- monopole
- pwittrock
kustomize-maintainers: # Please keep in sync with kustomize-maintainers in https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
- justinsb
- mortent
- natasha41575
- phanimarupaka
- pwittrock
- Shell32-Natsu
# emeritus
# - liujingfang1
# - mengqiy
emeritus-maintainers:
- liujingfang1
- mengqiy

View File

@@ -280,6 +280,9 @@ func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string {
// I've tried placing the flag before and after the name argument.
args = append(args, "--generate-name")
}
if p.IncludeCRDs {
args = append(args, "--include-crds")
}
return args
}

View File

@@ -0,0 +1,33 @@
// Code generated by pluginator on IAMPolicyGenerator; DO NOT EDIT.
// pluginator {unknown 1970-01-01T00:00:00Z }
package builtins
import (
"sigs.k8s.io/kustomize/api/filters/iampolicygenerator"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
type IAMPolicyGeneratorPlugin struct {
types.IAMPolicyGeneratorArgs
}
func (p *IAMPolicyGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
p.IAMPolicyGeneratorArgs = types.IAMPolicyGeneratorArgs{}
err = yaml.Unmarshal(config, p)
return
}
func (p *IAMPolicyGeneratorPlugin) Generate() (resmap.ResMap, error) {
r := resmap.New()
err := r.ApplyFilter(iampolicygenerator.Filter{
IAMPolicyGenerator: p.IAMPolicyGeneratorArgs,
})
return r, err
}
func NewIAMPolicyGeneratorPlugin() resmap.GeneratorPlugin {
return &IAMPolicyGeneratorPlugin{}
}

7
api/filesys/doc.go Normal file
View File

@@ -0,0 +1,7 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package filesys provides a file system abstraction,
// a subset of that provided by golang.org/pkg/os,
// with an on-disk and in-memory representation.
package filesys

View File

@@ -1,7 +1,6 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package filesys provides a file system abstraction layer.
package filesys
import (
@@ -29,6 +28,8 @@ type FileSystem interface {
Open(path string) (File, error)
// IsDir returns true if the path is a directory.
IsDir(path string) bool
// ReadDir returns a list of files and directories within a directory.
ReadDir(path string) ([]string, error)
// CleanedAbs converts the given path into a
// directory and a file name, where the directory
// is represented as a ConfirmedDir and all that implies.

View File

@@ -349,6 +349,29 @@ func (n *fsNode) IsDir(path string) bool {
return result.isNodeADir()
}
// ReadDir implements FileSystem.
func (n *fsNode) ReadDir(path string) ([]string, error) {
if !n.IsDir(path) {
return nil, fmt.Errorf("%s is not a directory", path)
}
dir, err := n.Find(path)
if err != nil {
return nil, err
}
if dir == nil {
return nil, fmt.Errorf("could not find directory %s", path)
}
keys := make([]string, len(dir.dir))
i := 0
for k := range dir.dir {
keys[i] = k
i++
}
return keys, nil
}
// Size returns the size of the node.
func (n *fsNode) Size() int64 {
if n.isNodeADir() {
@@ -560,7 +583,7 @@ func isLegalFileNameForCreation(n string) bool {
func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
var result []string
var expression = regexp.MustCompile(pattern)
n.WalkMe(func(path string, info os.FileInfo, err error) error {
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
@@ -571,6 +594,9 @@ func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
}
return nil
})
if err != nil {
return nil, err
}
sort.Strings(result)
return result, nil
}
@@ -582,7 +608,7 @@ func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
// This is how /bin/ls behaves.
func (n *fsNode) Glob(pattern string) ([]string, error) {
var result []string
n.WalkMe(func(path string, info os.FileInfo, err error) error {
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
@@ -597,6 +623,9 @@ func (n *fsNode) Glob(pattern string) ([]string, error) {
}
return nil
})
if err != nil {
return nil, err
}
sort.Strings(result)
return result, nil
}

View File

@@ -100,6 +100,19 @@ func (fsOnDisk) IsDir(name string) bool {
return info.IsDir()
}
// ReadDir delegates to os.ReadDir
func (fsOnDisk) ReadDir(name string) ([]string, error) {
dirEntries, err := os.ReadDir(name)
if err != nil {
return nil, err
}
result := make([]string, len(dirEntries))
for i := range dirEntries {
result[i] = dirEntries[i].Name()
}
return result, nil
}
// ReadFile delegates to ioutil.ReadFile.
func (fsOnDisk) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }

5
api/filters/doc.go Normal file
View File

@@ -0,0 +1,5 @@
package filters
// Package filters collects various implementations
// sigs.k8s.io/kustomize/kyaml/kio.Filter used by kustomize
// transformers to modify kubernetes objects.

View File

@@ -49,7 +49,7 @@ func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
return obj, nil
}
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path)
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
if err := fltr.filter(obj); err != nil {
s, _ := obj.String()
return nil, errors.WrapPrefixf(err,

View File

@@ -0,0 +1,3 @@
// Package gkesagenerator contains a kio.Filter that that generates a
// iampolicy-related resources for a given cloud provider
package iampolicygenerator

View File

@@ -0,0 +1,46 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package iampolicygenerator
import (
"log"
"os"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func ExampleFilter() {
f := Filter{}
var err = yaml.Unmarshal([]byte(`
cloud: gke
kubernetesService:
namespace: k8s-namespace
name: k8s-sa-name
serviceAccount:
name: gsa-name
projectId: project-id
`), &f)
if err != nil {
log.Fatal(err)
}
err = kio.Pipeline{
Inputs: []kio.Reader{},
Filters: []kio.Filter{f},
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
}.Execute()
if err != nil {
log.Fatal(err)
}
// Output:
// apiVersion: v1
// kind: ServiceAccount
// metadata:
// annotations:
// iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
// name: k8s-sa-name
// namespace: k8s-namespace
}

View File

@@ -0,0 +1,55 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package iampolicygenerator
import (
"fmt"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type Filter struct {
IAMPolicyGenerator types.IAMPolicyGeneratorArgs `json:",inline,omitempty" yaml:",inline,omitempty"`
}
// Filter adds a GKE service account object to nodes
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
switch f.IAMPolicyGenerator.Cloud {
case types.GKE:
IAMPolicyResources, err := f.generateGkeIAMPolicyResources()
if err != nil {
return nil, err
}
nodes = append(nodes, IAMPolicyResources...)
default:
return nil, fmt.Errorf("cloud provider %s not supported yet", f.IAMPolicyGenerator.Cloud)
}
return nodes, nil
}
func (f Filter) generateGkeIAMPolicyResources() ([]*yaml.RNode, error) {
var result []*yaml.RNode
input := fmt.Sprintf(`
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: %s@%s.iam.gserviceaccount.com
name: %s
`, f.IAMPolicyGenerator.ServiceAccount.Name,
f.IAMPolicyGenerator.ProjectId,
f.IAMPolicyGenerator.KubernetesService.Name)
if f.IAMPolicyGenerator.Namespace != "" {
input = input + fmt.Sprintf("\n namespace: %s", f.IAMPolicyGenerator.Namespace)
}
sa, err := yaml.Parse(input)
if err != nil {
return nil, err
}
return append(result, sa), nil
}

View File

@@ -0,0 +1,75 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package iampolicygenerator
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
"sigs.k8s.io/kustomize/api/types"
)
func TestFilter(t *testing.T) {
testCases := map[string]struct {
args types.IAMPolicyGeneratorArgs
expected string
}{
"with namespace": {
args: types.IAMPolicyGeneratorArgs{
Cloud: types.GKE,
KubernetesService: types.KubernetesService{
Namespace: "k8s-namespace",
Name: "k8s-sa-name",
},
ServiceAccount: types.ServiceAccount{
Name: "gsa-name",
ProjectId: "project-id",
},
},
expected: `
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
name: k8s-sa-name
namespace: k8s-namespace
`,
},
"without namespace": {
args: types.IAMPolicyGeneratorArgs{
Cloud: types.GKE,
KubernetesService: types.KubernetesService{
Name: "k8s-sa-name",
},
ServiceAccount: types.ServiceAccount{
Name: "gsa-name",
ProjectId: "project-id",
},
},
expected: `
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
name: k8s-sa-name
`,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
f := Filter{
IAMPolicyGenerator: tc.args,
}
actual := filtertest.RunFilter(t, "", f)
if !assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual)) {
t.FailNow()
}
})
}
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/api/internal/utils"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/kustomize/kyaml/yaml"
@@ -66,7 +67,7 @@ func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
for _, fp := range target.FieldPaths {
fieldPath := strings.Split(fp, ".")
fieldPath := utils.SmarterPathSplitter(fp, ".")
var t *yaml.RNode
var err error
if target.Options != nil && target.Options.Create {
@@ -87,12 +88,11 @@ func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelect
}
func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
value = value.Copy()
if options != nil && options.Delimiter != "" {
if t.YNode().Kind != yaml.ScalarNode {
return fmt.Errorf("delimiter option can only be used with scalar nodes")
}
tv := strings.Split(t.YNode().Value, options.Delimiter)
v := yaml.GetValue(value)
// TODO: Add a way to remove an element
@@ -119,7 +119,7 @@ func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, err
if r.Source.FieldPath == "" {
r.Source.FieldPath = types.DefaultReplacementFieldPath
}
fieldPath := strings.Split(r.Source.FieldPath, ".")
fieldPath := utils.SmarterPathSplitter(r.Source.FieldPath, ".")
rn, err := source.Pipe(yaml.Lookup(fieldPath...))
if err != nil {

View File

@@ -1338,6 +1338,148 @@ spec:
`,
expectedErr: "delimiter option can only be used with scalar nodes",
},
"list index contains '.' character": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: source
data:
value: example
---
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: some-secret
spec:
backendType: secretsManager
data:
- key: some-prefix-replaceme
name: .first
version: latest
property: first
- key: some-prefix-replaceme
name: second
version: latest
property: second
`,
replacements: `replacements:
- source:
kind: ConfigMap
version: v1
name: source
fieldPath: data.value
targets:
- select:
group: kubernetes-client.io
version: v1
kind: ExternalSecret
name: some-secret
fieldPaths:
- spec.data.[name=.first].key
- spec.data.[name=second].key
options:
delimiter: "-"
index: 2
`,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: source
data:
value: example
---
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: some-secret
spec:
backendType: secretsManager
data:
- key: some-prefix-example
name: .first
version: latest
property: first
- key: some-prefix-example
name: second
version: latest
property: second`,
},
"multiple field paths in target": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: source
data:
value: example
---
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: some-secret
spec:
backendType: secretsManager
data:
- key: some-prefix-replaceme
name: first
version: latest
property: first
- key: some-prefix-replaceme
name: second
version: latest
property: second
- key: some-prefix-replaceme
name: third
version: latest
property: third
`,
replacements: `replacements:
- source:
kind: ConfigMap
version: v1
name: source
fieldPath: data.value
targets:
- select:
group: kubernetes-client.io
version: v1
kind: ExternalSecret
name: some-secret
fieldPaths:
- spec.data.0.key
- spec.data.1.key
- spec.data.2.key
options:
delimiter: "-"
index: 2
`,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: source
data:
value: example
---
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: some-secret
spec:
backendType: secretsManager
data:
- key: some-prefix-example
name: first
version: latest
property: first
- key: some-prefix-example
name: second
version: latest
property: second
- key: some-prefix-example
name: third
version: latest
property: third
`,
},
}
for tn, tc := range testCases {
@@ -1353,7 +1495,7 @@ spec:
t.Errorf("unexpected error: %s\n", err.Error())
t.FailNow()
}
if !assert.Equal(t, tc.expectedErr, err.Error()) {
if !assert.Contains(t, err.Error(), tc.expectedErr) {
t.FailNow()
}
}

View File

@@ -16,3 +16,5 @@ require (
)
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
replace sigs.k8s.io/kustomize/kyaml => ../kyaml

View File

@@ -224,8 +224,6 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/kustomize/kyaml v0.10.20 h1:L9JNKvJfCBpmYFr4tP0igpfj/pXP7nW2aXOWNtF5k1g=
sigs.k8s.io/kustomize/kyaml v0.10.20/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -178,9 +178,12 @@ func loadCrdIntoConfig(
}
}
if property.Ref.GetURL() != nil {
loadCrdIntoConfig(
err = loadCrdIntoConfig(
theConfig, theGvk, theMap,
property.Ref.String(), append(path, propName))
if err != nil {
return
}
}
}
return nil

View File

@@ -7,6 +7,7 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
. "sigs.k8s.io/kustomize/api/internal/accumulator"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
@@ -162,16 +163,13 @@ func TestLoadCRDs(t *testing.T) {
}
fSys := filesys.MakeFsInMemory()
fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
err := fSys.WriteFile("/testpath/crd.json", []byte(crdContent))
require.NoError(t, err)
ldr, err := loader.NewLoader(loader.RestrictionRootOnly, "/testpath", fSys)
if err != nil {
t.Fatalf("unexpected error:%v", err)
}
require.NoError(t, err)
actualTc, err := LoadConfigFromCRDs(ldr, []string{"crd.json"})
if err != nil {
t.Fatalf("unexpected error:%v", err)
}
require.NoError(t, err)
if !reflect.DeepEqual(actualTc, expectedTc) {
t.Fatalf("expected\n %v\n but got\n %v\n", expectedTc, actualTc)
}

View File

@@ -10,6 +10,7 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/require"
. "sigs.k8s.io/kustomize/api/internal/accumulator"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/provider"
@@ -224,20 +225,26 @@ func TestResolveVarConflicts(t *testing.T) {
// create accumulators holding apparently conflicting vars that are not
// actually in conflict because they point to the same concrete value.
rm0 := resmap.New()
rm0.Append(rf.FromMap(fooAws))
err := rm0.Append(rf.FromMap(fooAws))
require.NoError(t, err)
ac0 := MakeEmptyAccumulator()
ac0.AppendAll(rm0)
ac0.MergeVars([]types.Var{varFoo})
err = ac0.AppendAll(rm0)
require.NoError(t, err)
err = ac0.MergeVars([]types.Var{varFoo})
require.NoError(t, err)
rm1 := resmap.New()
rm1.Append(rf.FromMap(barAws))
err = rm1.Append(rf.FromMap(barAws))
require.NoError(t, err)
ac1 := MakeEmptyAccumulator()
ac1.AppendAll(rm1)
ac1.MergeVars([]types.Var{varBar})
err = ac1.AppendAll(rm1)
require.NoError(t, err)
err = ac1.MergeVars([]types.Var{varBar})
require.NoError(t, err)
// validate that two vars of the same name which reference the same concrete
// value do not produce a conflict.
err := ac0.MergeAccumulator(ac1)
err = ac0.MergeAccumulator(ac1)
if err == nil {
t.Fatalf("see bug gh-1600")
}
@@ -246,10 +253,13 @@ func TestResolveVarConflicts(t *testing.T) {
// two above (because it contains a variable whose name is used in the other
// accumulators AND whose concrete values are different).
rm2 := resmap.New()
rm2.Append(rf.FromMap(barGcp))
err = rm2.Append(rf.FromMap(barGcp))
require.NoError(t, err)
ac2 := MakeEmptyAccumulator()
ac2.AppendAll(rm2)
ac2.MergeVars([]types.Var{varBar})
err = ac2.AppendAll(rm2)
require.NoError(t, err)
err = ac2.MergeVars([]types.Var{varBar})
require.NoError(t, err)
err = ac1.MergeAccumulator(ac2)
if err == nil {
t.Fatalf("dupe vars w/ different concrete values should conflict")

View File

@@ -40,7 +40,13 @@ func MakeConfigMap(
if err = rn.LoadMapIntoConfigMapData(m); err != nil {
return nil, err
}
copyLabelsAndAnnotations(rn, args.Options)
setImmutable(rn, args.Options)
err = copyLabelsAndAnnotations(rn, args.Options)
if err != nil {
return nil, err
}
err = setImmutable(rn, args.Options)
if err != nil {
return nil, err
}
return rn, nil
}

View File

@@ -11,25 +11,26 @@ func _() {
_ = x[Unknown-0]
_ = x[AnnotationsTransformer-1]
_ = x[ConfigMapGenerator-2]
_ = x[HashTransformer-3]
_ = x[ImageTagTransformer-4]
_ = x[LabelTransformer-5]
_ = x[LegacyOrderTransformer-6]
_ = x[NamespaceTransformer-7]
_ = x[PatchJson6902Transformer-8]
_ = x[PatchStrategicMergeTransformer-9]
_ = x[PatchTransformer-10]
_ = x[PrefixSuffixTransformer-11]
_ = x[ReplicaCountTransformer-12]
_ = x[SecretGenerator-13]
_ = x[ValueAddTransformer-14]
_ = x[HelmChartInflationGenerator-15]
_ = x[ReplacementTransformer-16]
_ = x[IAMPolicyGenerator-3]
_ = x[HashTransformer-4]
_ = x[ImageTagTransformer-5]
_ = x[LabelTransformer-6]
_ = x[LegacyOrderTransformer-7]
_ = x[NamespaceTransformer-8]
_ = x[PatchJson6902Transformer-9]
_ = x[PatchStrategicMergeTransformer-10]
_ = x[PatchTransformer-11]
_ = x[PrefixSuffixTransformer-12]
_ = x[ReplicaCountTransformer-13]
_ = x[SecretGenerator-14]
_ = x[ValueAddTransformer-15]
_ = x[HelmChartInflationGenerator-16]
_ = x[ReplacementTransformer-17]
}
const _BuiltinPluginType_name = "UnknownAnnotationsTransformerConfigMapGeneratorHashTransformerImageTagTransformerLabelTransformerLegacyOrderTransformerNamespaceTransformerPatchJson6902TransformerPatchStrategicMergeTransformerPatchTransformerPrefixSuffixTransformerReplicaCountTransformerSecretGeneratorValueAddTransformerHelmChartInflationGeneratorReplacementTransformer"
const _BuiltinPluginType_name = "UnknownAnnotationsTransformerConfigMapGeneratorIAMPolicyGeneratorHashTransformerImageTagTransformerLabelTransformerLegacyOrderTransformerNamespaceTransformerPatchJson6902TransformerPatchStrategicMergeTransformerPatchTransformerPrefixSuffixTransformerReplicaCountTransformerSecretGeneratorValueAddTransformerHelmChartInflationGeneratorReplacementTransformer"
var _BuiltinPluginType_index = [...]uint16{0, 7, 29, 47, 62, 81, 97, 119, 139, 163, 193, 209, 232, 255, 270, 289, 316, 338}
var _BuiltinPluginType_index = [...]uint16{0, 7, 29, 47, 65, 80, 99, 115, 137, 157, 181, 211, 227, 250, 273, 288, 307, 334, 356}
func (i BuiltinPluginType) String() string {
if i < 0 || i >= BuiltinPluginType(len(_BuiltinPluginType_index)-1) {

View File

@@ -15,6 +15,7 @@ const (
Unknown BuiltinPluginType = iota
AnnotationsTransformer
ConfigMapGenerator
IAMPolicyGenerator
HashTransformer
ImageTagTransformer
LabelTransformer
@@ -58,6 +59,7 @@ func GetBuiltinPluginType(n string) BuiltinPluginType {
var GeneratorFactories = map[BuiltinPluginType]func() resmap.GeneratorPlugin{
ConfigMapGenerator: builtins.NewConfigMapGeneratorPlugin,
IAMPolicyGenerator: builtins.NewIAMPolicyGeneratorPlugin,
SecretGenerator: builtins.NewSecretGeneratorPlugin,
HelmChartInflationGenerator: builtins.NewHelmChartInflationGeneratorPlugin,
}

View File

@@ -94,9 +94,6 @@ TO GENERATE CODE
cd $repo/plugin/builtin
go generate ./...
See scripts/kyaml-pre-commit.sh for canonical way
to execute the above.
This creates
$repo/api/plugins/builtins/SecretGenerator.go

View File

@@ -89,7 +89,10 @@ type argsConfig struct {
func (p *ExecPlugin) processOptionalArgsFields() error {
var c argsConfig
yaml.Unmarshal(p.cfg, &c)
err := yaml.Unmarshal(p.cfg, &c)
if err != nil {
return err
}
if c.ArgsOneLiner != "" {
p.args, _ = shlex.Split(c.ArgsOneLiner)
}

View File

@@ -9,6 +9,7 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
@@ -21,11 +22,12 @@ import (
func TestExecPluginConfig(t *testing.T) {
fSys := filesys.MakeFsInMemory()
fSys.WriteFile("sed-input.txt", []byte(`
err := fSys.WriteFile("sed-input.txt", []byte(`
s/$FOO/foo/g
s/$BAR/bar baz/g
\ \ \
`))
require.NoError(t, err)
ldr, err := fLdr.NewLoader(
fLdr.RestrictionRootOnly, filesys.Separator, fSys)
if err != nil {
@@ -62,9 +64,10 @@ s/$BAR/bar baz/g
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
p.Config(
err = p.Config(
resmap.NewPluginHelpers(ldr, pvd.GetFieldValidator(), rf, pc),
yaml)
require.NoError(t, err)
expected := "someteam.example.com/v1/sedtransformer/SedTransformer"
if !strings.HasSuffix(p.Path(), expected) {

View File

@@ -192,7 +192,9 @@ func UpdateResMapValues(pluginName string, h *resmap.PluginHelpers, output []byt
for _, id := range rm.AllIds() {
newIdx, _ := newMap.GetIndexOfCurrentId(id)
if newIdx == -1 {
rm.Remove(id)
if err = rm.Remove(id); err != nil {
return err
}
}
}

View File

@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/provider"
@@ -86,8 +87,10 @@ func TestUpdateResourceOptions(t *testing.T) {
}
for i, c := range cases {
name := fmt.Sprintf("test%d", i)
in.Append(makeConfigMap(rf, name, c.behavior, c.hashValue))
expected.Append(makeConfigMapOptions(rf, name, c.behavior, !c.needsHash))
err := in.Append(makeConfigMap(rf, name, c.behavior, c.hashValue))
require.NoError(t, err)
err = expected.Append(makeConfigMapOptions(rf, name, c.behavior, !c.needsHash))
require.NoError(t, err)
}
actual, err := UpdateResourceOptions(in)
assert.NoError(t, err)
@@ -105,10 +108,9 @@ func TestUpdateResourceOptionsWithInvalidHashAnnotationValues(t *testing.T) {
for i, c := range cases {
name := fmt.Sprintf("test%d", i)
in := resmap.New()
in.Append(makeConfigMap(rf, name, "", &c))
_, err := UpdateResourceOptions(in)
if err == nil {
t.Errorf("expected error from value %q", c)
}
err := in.Append(makeConfigMap(rf, name, "", &c))
require.NoError(t, err)
_, err = UpdateResourceOptions(in)
require.Error(t, err)
}
}

View File

@@ -5,18 +5,50 @@ package utils
import "strings"
// PathSplitter splits a slash delimited string, permitting escaped slashes.
func PathSplitter(path string) []string {
ps := strings.Split(path, "/")
// TODO: Move these to kyaml
// PathSplitter splits a delimited string, permitting escaped delimiters.
func PathSplitter(path string, delimiter string) []string {
ps := strings.Split(path, delimiter)
var res []string
res = append(res, ps[0])
for i := 1; i < len(ps); i++ {
last := len(res) - 1
if strings.HasSuffix(res[last], `\`) {
res[last] = strings.TrimSuffix(res[last], `\`) + "/" + ps[i]
res[last] = strings.TrimSuffix(res[last], `\`) + delimiter + ps[i]
} else {
res = append(res, ps[i])
}
}
return res
}
// SmarterPathSplitter splits a path, retaining bracketed list entry identifiers.
// E.g. [name=com.foo.someapp] survives as one thing after splitting
// "spec.template.spec.containers.[name=com.foo.someapp].image"
// See kyaml/yaml/match.go for use of list entry identifiers.
// This function uses `PathSplitter`, so it respects list entry identifiers
// and escaped delimiters.
func SmarterPathSplitter(path string, delimiter string) []string {
var result []string
split := PathSplitter(path, delimiter)
for i := 0; i < len(split); i++ {
elem := split[i]
if strings.HasPrefix(elem, "[") && !strings.HasSuffix(elem, "]") {
// continue until we find the matching "]"
bracketed := []string{elem}
for i < len(split)-1 {
i++
bracketed = append(bracketed, split[i])
if strings.HasSuffix(split[i], "]") {
break
}
}
result = append(result, strings.Join(bracketed, delimiter))
} else {
result = append(result, elem)
}
}
return result
}

View File

@@ -44,6 +44,47 @@ func TestPathSplitter(t *testing.T) {
"nginx.ingress.kubernetes.io/auth-secret"},
},
} {
assert.Equal(t, tc.exp, PathSplitter(tc.path))
assert.Equal(t, tc.exp, PathSplitter(tc.path, "/"))
}
}
func TestSmarterPathSplitter(t *testing.T) {
testCases := map[string]struct {
input string
expected []string
}{
"simple": {
input: "spec.replicas",
expected: []string{"spec", "replicas"},
},
"sequence": {
input: "spec.data.[name=first].key",
expected: []string{"spec", "data", "[name=first]", "key"},
},
"key, value with . prefix": {
input: "spec.data.[.name=.first].key",
expected: []string{"spec", "data", "[.name=.first]", "key"},
},
"key, value with . suffix": {
input: "spec.data.[name.=first.].key",
expected: []string{"spec", "data", "[name.=first.]", "key"},
},
"multiple '.' in value": {
input: "spec.data.[name=f.i.r.s.t.].key",
expected: []string{"spec", "data", "[name=f.i.r.s.t.]", "key"},
},
"with escaped delimiter": {
input: `spec\.replicas`,
expected: []string{`spec.replicas`},
},
"unmatched bracket": {
input: "spec.data.[name=f.i.[r.s.t..key",
expected: []string{"spec", "data", "[name=f.i.[r.s.t..key"},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
assert.Equal(t, tc.expected, SmarterPathSplitter(tc.input, "."))
})
}
}

View File

@@ -3,9 +3,6 @@
package builtinpluginconsts
// TODO: rename 'fieldSpecs' to 'referrers' for clarity.
// This will, however, break anyone using a custom config.
const (
nameReferenceFieldSpecs = `
nameReference:

View File

@@ -2,5 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// Package konfig provides configuration methods and constants
// for the kustomize API.
// for the kustomize API, e.g. the set of file names to look for
// to identify a kustomization root.
package konfig

View File

@@ -9,7 +9,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/types"
)
@@ -18,11 +18,11 @@ func TestDefaultAbsPluginHome_NoKustomizePluginHomeEnv(t *testing.T) {
fSys := filesys.MakeFsInMemory()
keep, isSet := os.LookupEnv(KustomizePluginHomeEnv)
if isSet {
_ = os.Unsetenv(KustomizePluginHomeEnv)
unsetenv(t, KustomizePluginHomeEnv)
}
_, err := DefaultAbsPluginHome(fSys)
if isSet {
os.Setenv(KustomizePluginHomeEnv, keep)
setenv(t, KustomizePluginHomeEnv, keep)
}
if err == nil {
t.Fatalf("expected err")
@@ -43,13 +43,13 @@ func TestDefaultAbsPluginHome_NoKustomizePluginHomeEnv(t *testing.T) {
func TestDefaultAbsPluginHome_EmptyKustomizePluginHomeEnv(t *testing.T) {
keep, isSet := os.LookupEnv(KustomizePluginHomeEnv)
os.Setenv(KustomizePluginHomeEnv, "")
setenv(t, KustomizePluginHomeEnv, "")
_, err := DefaultAbsPluginHome(filesys.MakeFsInMemory())
if !isSet {
_ = os.Unsetenv(KustomizePluginHomeEnv)
unsetenv(t, KustomizePluginHomeEnv)
} else {
_ = os.Setenv(KustomizePluginHomeEnv, keep)
setenv(t, KustomizePluginHomeEnv, keep)
}
if err == nil {
t.Fatalf("expected err")
@@ -65,16 +65,15 @@ func TestDefaultAbsPluginHome_WithKustomizePluginHomeEnv(t *testing.T) {
keep, isSet := os.LookupEnv(KustomizePluginHomeEnv)
if !isSet {
keep = "whatever"
os.Setenv(KustomizePluginHomeEnv, keep)
setenv(t, KustomizePluginHomeEnv, keep)
}
fSys.Mkdir(keep)
err := fSys.Mkdir(keep)
require.NoError(t, err)
h, err := DefaultAbsPluginHome(fSys)
if !isSet {
_ = os.Unsetenv(KustomizePluginHomeEnv)
}
if err != nil {
t.Fatalf("unexpected err: %v", err)
unsetenv(t, KustomizePluginHomeEnv)
}
require.NoError(t, err)
if h != keep {
t.Fatalf("unexpected config dir: %s", h)
}
@@ -85,13 +84,14 @@ func TestDefaultAbsPluginHomeWithXdg(t *testing.T) {
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
if !isSet {
keep = "whatever"
os.Setenv(XdgConfigHomeEnv, keep)
setenv(t, XdgConfigHomeEnv, keep)
}
configDir := filepath.Join(keep, ProgramName, RelPluginHome)
fSys.Mkdir(configDir)
err := fSys.Mkdir(configDir)
require.NoError(t, err)
h, err := DefaultAbsPluginHome(fSys)
if !isSet {
_ = os.Unsetenv(XdgConfigHomeEnv)
unsetenv(t, XdgConfigHomeEnv)
}
if err != nil {
t.Fatalf("unexpected err: %v", err)
@@ -105,11 +105,11 @@ func TestDefaultAbsPluginHomeNoConfig(t *testing.T) {
fSys := filesys.MakeFsInMemory()
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
if isSet {
_ = os.Unsetenv(XdgConfigHomeEnv)
unsetenv(t, XdgConfigHomeEnv)
}
_, err := DefaultAbsPluginHome(fSys)
if isSet {
os.Setenv(XdgConfigHomeEnv, keep)
setenv(t, XdgConfigHomeEnv, keep)
}
if err == nil {
t.Fatalf("expected err")
@@ -121,13 +121,13 @@ func TestDefaultAbsPluginHomeNoConfig(t *testing.T) {
func TestDefaultAbsPluginHomeEmptyXdgConfig(t *testing.T) {
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
os.Setenv(XdgConfigHomeEnv, "")
setenv(t, XdgConfigHomeEnv, "")
if isSet {
_ = os.Unsetenv(XdgConfigHomeEnv)
unsetenv(t, XdgConfigHomeEnv)
}
_, err := DefaultAbsPluginHome(filesys.MakeFsInMemory())
if isSet {
os.Setenv(XdgConfigHomeEnv, keep)
setenv(t, XdgConfigHomeEnv, keep)
}
if err == nil {
t.Fatalf("expected err")
@@ -142,14 +142,16 @@ func TestDefaultAbsPluginHomeNoXdgWithDotConfig(t *testing.T) {
fSys := filesys.MakeFsInMemory()
configDir := filepath.Join(
HomeDir(), XdgConfigHomeEnvDefault, ProgramName, RelPluginHome)
fSys.Mkdir(configDir)
err := fSys.Mkdir(configDir)
require.NoError(t, err)
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
if isSet {
_ = os.Unsetenv(XdgConfigHomeEnv)
unsetenv(t, XdgConfigHomeEnv)
}
s, _ := DefaultAbsPluginHome(fSys)
s, err := DefaultAbsPluginHome(fSys)
require.NoError(t, err)
if isSet {
os.Setenv(XdgConfigHomeEnv, keep)
setenv(t, XdgConfigHomeEnv, keep)
}
if s != configDir {
t.Fatalf("unexpected config dir: %s", s)
@@ -160,16 +162,26 @@ func TestDefaultAbsPluginHomeNoXdgJustHomeDir(t *testing.T) {
fSys := filesys.MakeFsInMemory()
configDir := filepath.Join(
HomeDir(), ProgramName, RelPluginHome)
fSys.Mkdir(configDir)
err := fSys.Mkdir(configDir)
require.NoError(t, err)
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
if isSet {
_ = os.Unsetenv(XdgConfigHomeEnv)
unsetenv(t, XdgConfigHomeEnv)
}
s, _ := DefaultAbsPluginHome(fSys)
s, err := DefaultAbsPluginHome(fSys)
require.NoError(t, err)
if isSet {
os.Setenv(XdgConfigHomeEnv, keep)
setenv(t, XdgConfigHomeEnv, keep)
}
if s != configDir {
t.Fatalf("unexpected config dir: %s", s)
}
}
func setenv(t *testing.T, key, value string) {
require.NoError(t, os.Setenv(key, value))
}
func unsetenv(t *testing.T, key string) {
require.NoError(t, os.Unsetenv(key))
}

View File

@@ -0,0 +1,124 @@
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
func TestGkeGenerator(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
th.WriteK(".", `
generators:
- |-
apiVersion: builtin
kind: IAMPolicyGenerator
metadata:
name: my-gke-generator
cloud: gke
kubernetesService:
name: k8s-sa-name
serviceAccount:
name: gsa-name
projectId: project-id
`)
expected := `
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
name: k8s-sa-name
`
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, expected)
}
func TestGkeGeneratorWithNamespace(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
th.WriteK(".", `
generators:
- |-
apiVersion: builtin
kind: IAMPolicyGenerator
metadata:
name: my-gke-generator
cloud: gke
kubernetesService:
namespace: k8s-namespace
name: k8s-sa-name
serviceAccount:
name: gsa-name
projectId: project-id
`)
expected := `
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
name: k8s-sa-name
namespace: k8s-namespace
`
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, expected)
}
func TestGkeGeneratorWithTwo(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
th.WriteK(".", `
generators:
- gkegenerator1.yaml
- gkegenerator2.yaml
`)
th.WriteF("gkegenerator1.yaml", `
apiVersion: builtin
kind: IAMPolicyGenerator
metadata:
name: my-gke-generator1
cloud: gke
kubernetesService:
namespace: k8s-namespace-1
name: k8s-sa-name-1
serviceAccount:
name: gsa-name-1
projectId: project-id-1
`)
th.WriteF("gkegenerator2.yaml", `
apiVersion: builtin
kind: IAMPolicyGenerator
metadata:
name: my-gke-generator2
cloud: gke
kubernetesService:
name: k8s-sa-name-2
serviceAccount:
name: gsa-name-2
projectId: project-id-2
`)
expected := `
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: gsa-name-1@project-id-1.iam.gserviceaccount.com
name: k8s-sa-name-1
namespace: k8s-namespace-1
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
iam.gke.io/gcp-service-account: gsa-name-2@project-id-2.iam.gserviceaccount.com
name: k8s-sa-name-2
`
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, expected)
}

View File

@@ -90,19 +90,25 @@ func (b *Kustomizer) Run(
return nil, err
}
if b.options.DoLegacyResourceSort {
builtins.NewLegacyOrderTransformerPlugin().Transform(m)
err = builtins.NewLegacyOrderTransformerPlugin().Transform(m)
if err != nil {
return nil, err
}
}
if b.options.AddManagedbyLabel {
t := builtins.LabelTransformerPlugin{
Labels: map[string]string{
konfig.ManagedbyLabelKey: fmt.Sprintf(
"kustomize-%s", provenance.GetProvenance().Semver())},
konfig.ManagedbyLabelKey: fmt.Sprintf("kustomize-%s", provenance.GetProvenance().Semver()),
},
FieldSpecs: []types.FieldSpec{{
Path: "metadata/labels",
CreateIfNotPresent: true,
}},
}
t.Transform(m)
err = t.Transform(m)
if err != nil {
return nil, err
}
}
m.RemoveBuildAnnotations()
return m, nil

View File

@@ -6,9 +6,319 @@ package krusty_test
import (
"testing"
"github.com/stretchr/testify/assert"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
func TestPatchesInOneFile(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK("base", `
resources:
- namespace.yaml
- deployment-controller-manager.yaml
- deployment-audit-manager.yaml
`)
th.WriteF("base/namespace.yaml", `
apiVersion: v1
kind: Namespace
metadata:
labels:
control-plane: controller-manager
admission.gatekeeper.sh/ignore: no-self-managing
name: system
`)
th.WriteF("base/deployment-controller-manager.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
labels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
spec:
selector:
matchLabels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
replicas: 3
template:
metadata:
annotations:
container.seccomp.security.alpha.kubernetes.io/manager: runtime/default
labels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
spec:
containers:
- command:
- /manager
args:
- "--port=8443"
- "--logtostderr"
- "--exempt-namespace=gatekeeper-system"
- "--operation=webhook"
image: openpolicyagent/gatekeeper:v3.4.0
imagePullPolicy: Always
name: manager
terminationGracePeriodSeconds: 60
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
`)
th.WriteF("base/deployment-audit-manager.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
name: audit
namespace: system
labels:
control-plane: audit-controller
gatekeeper.sh/operation: audit
spec:
selector:
matchLabels:
control-plane: audit-controller
gatekeeper.sh/operation: audit
replicas: 1
template:
metadata:
labels:
control-plane: audit-controller
gatekeeper.sh/operation: audit
annotations:
container.seccomp.security.alpha.kubernetes.io/manager: runtime/default
spec:
automountServiceAccountToken: true
containers:
- args:
- --operation=audit
- --operation=status
- --logtostderr
command:
- /manager
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
image: openpolicyagent/gatekeeper:v3.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 9090
name: manager
serviceAccountName: gatekeeper-admin
terminationGracePeriodSeconds: 60
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
`)
const imagePatchAuditManager = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: audit
namespace: system
spec:
template:
spec:
containers:
- image: AUDIT_IMAGE
name: manager
args:
- --port=8443
- --logtostderr
- --emit-admission-events
- --exempt-namespace=gatekeeper-system
- --operation=webhook
- --disable-opa-builtin=http.send
`
const imagePatchControllerManager = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
spec:
containers:
- image: CONTROLLER_IMAGE
name: manager
args:
- --emit-audit-events
- --operation=audit
- --operation=status
- --logtostderr
`
th.WriteF(
"overlay/image_patch_audit_manager.yaml",
imagePatchAuditManager)
th.WriteF(
"overlay/image_patch_controller_manager.yaml",
imagePatchControllerManager)
const expected = `
apiVersion: v1
kind: Namespace
metadata:
labels:
admission.gatekeeper.sh/ignore: no-self-managing
control-plane: controller-manager
name: system
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
name: controller-manager
namespace: system
spec:
replicas: 3
selector:
matchLabels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
template:
metadata:
annotations:
container.seccomp.security.alpha.kubernetes.io/manager: runtime/default
labels:
control-plane: controller-manager
gatekeeper.sh/operation: webhook
spec:
containers:
- args:
- --emit-audit-events
- --operation=audit
- --operation=status
- --logtostderr
command:
- /manager
image: CONTROLLER_IMAGE
imagePullPolicy: Always
name: manager
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
terminationGracePeriodSeconds: 60
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
control-plane: audit-controller
gatekeeper.sh/operation: audit
name: audit
namespace: system
spec:
replicas: 1
selector:
matchLabels:
control-plane: audit-controller
gatekeeper.sh/operation: audit
template:
metadata:
annotations:
container.seccomp.security.alpha.kubernetes.io/manager: runtime/default
labels:
control-plane: audit-controller
gatekeeper.sh/operation: audit
spec:
automountServiceAccountToken: true
containers:
- args:
- --port=8443
- --logtostderr
- --emit-admission-events
- --exempt-namespace=gatekeeper-system
- --operation=webhook
- --disable-opa-builtin=http.send
command:
- /manager
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
image: AUDIT_IMAGE
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 9090
name: manager
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: gatekeeper-admin
terminationGracePeriodSeconds: 60
`
// Technique 1: "patchesStrategicMerge:" field, two patch files.
th.WriteK("overlay", `
resources:
- ../base
patchesStrategicMerge:
- image_patch_controller_manager.yaml
- image_patch_audit_manager.yaml
`)
m := th.Run("overlay", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, expected)
// Technique 2: "patches:" field, two patch files.
th.WriteK("overlay", `
resources:
- ../base
patches:
- path: image_patch_controller_manager.yaml
- path: image_patch_audit_manager.yaml
`)
m = th.Run("overlay", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, expected)
// Technique 3: "patchesStrategicMerge:" field, one patch file.
th.WriteK("overlay", `
resources:
- ../base
patchesStrategicMerge:
- twoPatchesInOneFile.yaml
`)
th.WriteF(
"overlay/twoPatchesInOneFile.yaml",
imagePatchAuditManager+"\n---\n"+imagePatchControllerManager)
m = th.Run("overlay", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, expected)
// Technique 4: "patches:" field, one patch file. Fails.
th.WriteK("overlay", `
resources:
- ../base
patches:
- path: twoPatchesInOneFile.yaml
`)
err := th.RunWithErr("overlay", th.MakeDefaultOptions())
assert.Error(t, err)
// This should fail, because the semantics of the `patches` field.
// That field allows specific patch targeting to a list of targets,
// while the `patchesStrategicMerge` field accepts patches that
// implicitly identify their targets via GVKN.
assert.Contains(t, err.Error(), "unable to parse SM or JSON patch from ")
}
func TestRemoveEmptyDirWithNullFieldInSmp(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `

View File

@@ -7,6 +7,7 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
ldr "sigs.k8s.io/kustomize/api/loader"
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
@@ -83,7 +84,8 @@ func TestKeyValuesFromFileSources(t *testing.T) {
}
fSys := filesys.MakeFsInMemory()
fSys.WriteFile("/files/app-init.ini", []byte("FOO=bar"))
err := fSys.WriteFile("/files/app-init.ini", []byte("FOO=bar"))
require.NoError(t, err)
kvl := makeKvLoader(fSys)
for _, tc := range tests {
kvs, err := kvl.keyValuesFromFileSources(tc.sources)

View File

@@ -22,6 +22,12 @@ import (
// Factory makes instances of Resource.
type Factory struct {
hasher ifc.KustHasher
// When set to true, IncludeLocalConfigs indicates
// that Factory should include resources with the
// annotation 'config.kubernetes.io/local-config'.
// By default these resources are ignored.
IncludeLocalConfigs bool
}
// NewFactory makes an instance of Factory.
@@ -221,13 +227,15 @@ func (rf *Factory) shouldIgnore(n *yaml.RNode) (bool, error) {
if n.IsNilOrEmpty() {
return true, nil
}
md, err := n.GetValidatedMetadata()
if err != nil {
return true, err
}
_, ignore := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
if ignore {
return true, nil
if !rf.IncludeLocalConfigs {
md, err := n.GetValidatedMetadata()
if err != nil {
return true, err
}
_, ignore := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
if ignore {
return true, nil
}
}
if foundNil, path := n.HasNilEntryInList(); foundNil {
return true, fmt.Errorf("empty item at %v in object %v", path, n)

View File

@@ -39,7 +39,7 @@ func (g *GenArgs) ShouldAddHashSuffixToName() bool {
// Behavior returns Behavior field of GeneratorArgs
func (g *GenArgs) Behavior() GenerationBehavior {
if g.args == nil {
if g == nil || g.args == nil {
return BehaviorUnspecified
}
return NewGenerationBehavior(g.args.Behavior)

View File

@@ -68,6 +68,10 @@ type HelmChart struct {
// Legal values: 'merge', 'override', 'replace'.
// Defaults to 'override'.
ValuesMerge string `json:"valuesMerge,omitempty" yaml:"valuesMerge,omitempty"`
// IncludeCRDs specifies if Helm should also generate CustomResourceDefinitions.
// Defaults to 'false'.
IncludeCRDs bool `json:"includeCRDs,omitempty" yaml:"includeCRDs,omitempty"`
}
// HelmChartArgs contains arguments to helm.

View File

@@ -0,0 +1,36 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package types
type Cloud string
const GKE Cloud = "gke"
// IAMPolicyGeneratorArgs contains arguments to generate a GKE service account resource.
type IAMPolicyGeneratorArgs struct {
// which cloud provider to generate for (e.g. "gke")
Cloud `json:"cloud" yaml:"cloud"`
// information about the kubernetes cluster for this object
KubernetesService `json:"kubernetesService" yaml:"kubernetesService"`
// information about the service account and project
ServiceAccount `json:"serviceAccount" yaml:"serviceAccount"`
}
type KubernetesService struct {
// the name used for the Kubernetes service account
Name string `json:"name" yaml:"name"`
// the name of the Kubernetes namespace for this object
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
}
type ServiceAccount struct {
// the name of the new cloud provider service account
Name string `json:"name" yaml:"name"`
// The ID of the project
ProjectId string `json:"projectId" yaml:"projectId"`
}

View File

@@ -28,10 +28,10 @@ type SourceSelector struct {
resid.ResId `json:",inline,omitempty" yaml:",inline,omitempty"`
// Structured field path expected in the allowed object.
FieldPath string `json:"fieldPath" yaml:"fieldPath"`
FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
// Used to refine the interpretation of the field.
Options *FieldOptions `json:"options" yaml:"options"`
Options *FieldOptions `json:"options,omitempty" yaml:"options,omitempty"`
}
func (s *SourceSelector) String() string {
@@ -54,34 +54,34 @@ type TargetSelector struct {
Select *Selector `json:"select" yaml:"select"`
// From the allowed set, remove objects that match this.
Reject []*Selector `json:"reject" yaml:"reject"`
Reject []*Selector `json:"reject,omitempty" yaml:"reject,omitempty"`
// Structured field paths expected in each allowed object.
FieldPaths []string `json:"fieldPaths" yaml:"fieldPaths"`
FieldPaths []string `json:"fieldPaths,omitempty" yaml:"fieldPaths,omitempty"`
// Used to refine the interpretation of the field.
Options *FieldOptions `json:"options" yaml:"options"`
Options *FieldOptions `json:"options,omitempty" yaml:"options,omitempty"`
}
// FieldOptions refine the interpretation of FieldPaths.
type FieldOptions struct {
// Used to split/join the field.
Delimiter string `json:"delimiter" yaml:"delimiter"`
Delimiter string `json:"delimiter,omitempty" yaml:"delimiter,omitempty"`
// Which position in the split to consider.
Index int `json:"index" yaml:"index"`
Index int `json:"index,omitempty" yaml:"index,omitempty"`
// TODO (#3492): Implement use of this option
// None, Base64, URL, Hex, etc
Encoding string `json:"encoding" yaml:"encoding"`
Encoding string `json:"encoding,omitempty" yaml:"encoding,omitempty"`
// If field missing, add it.
Create bool `json:"create" yaml:"create"`
Create bool `json:"create,omitempty" yaml:"create,omitempty"`
}
func (fo *FieldOptions) String() string {
if fo == nil || fo.Delimiter == "" {
if fo == nil || (fo.Delimiter == "" && !fo.Create) {
return ""
}
return fmt.Sprintf("%s(%d)", fo.Delimiter, fo.Index)
return fmt.Sprintf("%s(%d), create=%t", fo.Delimiter, fo.Index, fo.Create)
}

View File

@@ -28,6 +28,10 @@ type Selector struct {
LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"`
}
func (s *Selector) Copy() Selector {
return *s
}
func (s *Selector) String() string {
return fmt.Sprintf(
"%s:a=%s:l=%s", s.ResId, s.AnnotationSelector, s.LabelSelector)

View File

@@ -17,17 +17,18 @@ func NewCommand() *cobra.Command {
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletion(os.Stdout)
return cmd.Root().GenPowerShellCompletion(os.Stdout)
}
return nil
},
}
}

View File

@@ -1,65 +0,0 @@
# Configuration IO API Semantics
Resource Configuration may be read / written from / to sources such as directories,
stdin|out or network. Tools may be composed using pipes such that the tools writing
Resource Configuration may be a different tool from the one that read the configuration.
In order for tools to be composed in this way, while preserving origin information --
such as the original file, index, etc.:
Tools **SHOULD** insert the following annotations when reading from sources,
and **SHOULD** delete the annotations when writing to sinks.
### `config.kubernetes.io/path`
Records the slash-delimited, OS-agnostic, relative file path to a Resource.
This annotation **SHOULD** be set when reading Resources from files.
It **SHOULD** be unset when writing Resources to files.
When writing Resources to a directory, the Resource **SHOULD** be written to the corresponding
path relative to that directory.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
```
### `config.kubernetes.io/index`
Records the index of a Resource in file. In a multi-object YAML file, Resources are separated
by three dashes (`---`), and the index represents the position of the Resource starting from zero.
This annotation **SHOULD** be set when reading Resources from files.
It **SHOULD** be unset when writing Resources to files.
When writing multiple Resources to the same file, the Resource **SHOULD** be written in the
relative order matching the index.
When this annotation is not specified, it implies a value of `0`.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
config.kubernetes.io/index: 2
```
This represents the third Resource in the file.
### `config.kubernetes.io/local-config`
`config.kubernetes.io/local-config` declares that the configuration is to local tools
rather than a remote Resource. e.g. The `Kustomization` config in a `kustomization.yaml`
**SHOULD** contain this annotation so that tools know it is not intended to be sent to
the Kubernetes api server.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/local-config: "true"
```

View File

@@ -1,13 +1,18 @@
# Configuration Functions Specification
# KRM Functions Specification
_apiVersion: v1_
## Overview
This document specifies a standard for client-side functions that operate on
Kubernetes declarative configurations. This standard enables creating
small, interoperable, and language-independent executable programs packaged as
containers that can be chained together as part of a configuration management pipeline.
The end result of such a pipeline are fully rendered configurations that can then be
applied to a control plane (e.g. Using kubectl apply for Kubernetes control plane).
As such, although this document references Kubernetes Resource Model and API conventions,
it is completely decoupled from Kubernetes API machinery and does not depend on any
Kubernetes declarative configurations referred to as _KRM Functions_. This
standard enables creating small, interoperable, and language-independent
executable programs packaged as containers that can be chained together as part
of a configuration management pipeline. The end result of such a pipeline are
fully rendered configurations that can then be applied to a control plane (e.g.
Using kubectl apply for Kubernetes control plane). As such, although this
document references Kubernetes Resource Model and API conventions, it is
completely decoupled from Kubernetes API machinery and does not depend on any
in-cluster components.
This document references terms described in [Kubernetes API Conventions][1].
@@ -18,168 +23,359 @@ interpreted as described in [RFC 2119][2].
## Use Cases
_Configuration functions_ enable shift-left practices (client-side) through:
KRM functions enable shift-left practices (client-side) through:
- Pre-commit / delivery validation and linting of configuration
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory limits
- Implementation of abstractions as client actuated APIs (e.g. templating)
- e.g. Create a client-side _"CRD"_ for generating configuration checked into git
- Aspect Orient configuration / Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating Resources with `small`, `medium`, `large`
and inject the cpu and memory resources into containers accordingly.
- e.g. Inject `init` and `side-car` containers into Resources based off of Resource
Type, annotations, etc.
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory
limits
- Implementation of abstractions as client actuated APIs
- e.g. Create a client-side _"CRD"_ for generating configuration checked into
git
- Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating resources with `small`, `medium`,
`large` and inject the cpu and memory resources into containers accordingly.
- e.g. Inject `init` and `side-car` containers into resources based off of
resource type, annotations, etc.
Performing these on the client rather than the server enables:
- Configuration to be reviewed prior to being sent to the API server
- Configuration to be validated as part of the CI/CD pipeline
- Configuration for Resources to validated holistically rather than individually
per-Resource
- Configuration for resources to validated holistically rather than individually
per-resource
- e.g. ensure the `Service.selector` and `Deployment.spec.template` labels
match.
- e.g. MutatingWebHooks are scoped to a single Resource instance at a time.
- e.g. MutatingWebHooks are scoped to a single resource instance at a time.
- Low-level tweaks to the output of high-level abstractions
- e.g. add an `init container` to a client _"CRD"_ Resource after it was generated.
- e.g. add an `init container` to a client _"CRD"_ resource after it was
generated.
- Composition and layering of multiple functions together
- Compose generation, injection, validation together
## Spec
## Definitions
### Input Type
- **function:** A containerized program conforming to the spec described in this
document.
- **orchestrator:** A program that invokes the function container, passing
arguments and processing its output.
A function MUST accept as input a single [Kubernetes List type][3].
The `items` field in the input will contain a sequence of [Object types][3].
A function MAY not support [Simple types][3] and List types.
## Interface
An example using `v1/ConfigMapList` as input:
The inter-process communication between the orchestrator and a function works as
follows:
1. Orchestrator runs the function container and provides the input on `stdin`.
The input is a Kubernetes object of kind `ResourceList` as described below.
2. Function reads the input from `stdin`, performs computations, and provides
the output as a `ResourceList` to `stdout`. The function MAY also emit
non-structured error message on `stderr`.
3. Orchestrator uses the `stdout`, `stderr`, and the exit code of the function
as it sees fit following to the semantics described below.
### Schema
A function MUST accept input from `stdin` and MUST output to `stdout` a
Kubernetes object of kind `ResourceList` with the following OpenAPI schema:
```yaml
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: config1
data:
p1: v1
p2: v2
- apiVersion: v1
kind: ConfigMap
metadata:
name: config2
swagger: "2.0"
info:
title: KRM Functions Specification (ResourceList)
version: v1
definitions:
ResourceList:
type: object
description: ResourceList is the input/output wire format for KRM functions.
x-kubernetes-group-version-kind:
- group: config.kubernetes.io
kind: ResourceList
version: v1
- group: config.kubernetes.io
kind: ResourceList
version: v1beta1
required:
- items
properties:
apiVersion:
description: apiVersion of ResourceList
type: string
kind:
description: kind of ResourceList i.e. `ResourceList`
type: string
items:
type: array
description: |
[input/output]
Items is a list of Kubernetes objects:
https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#types-kinds).
A function will read this field in the input ResourceList and populate
this field in the output ResourceList.
items:
type: object
functionConfig:
type: object
description: |
[input]
FunctionConfig is an optional Kubernetes object for passing arguments to a
function invocation.
results:
type: array
description: |
[output]
Results is an optional list that can be used by function to emit results
for observability and debugging purposes.
items:
"$ref": "#/definitions/Result"
Result:
type: object
required:
- message
properties:
message:
type: string
description: Message is a human readable message.
severity:
type: string
enum:
- error
- warning
- info
default: error
description: |
Severity is the severity of a result:
"error": indicates an error result.
"warning": indicates a warning result.
"info": indicates an informational result.
resourceRef:
type: object
description: |
ResourceRef is the metadata for referencing a Kubernetes object
associated with a result.
required:
- apiVersion
- kind
- name
properties:
apiVersion:
description:
APIVersion refers to the `apiVersion` field of the object
manifest.
type: string
kind:
description: Kind refers to the `kind` field of the object.
type: string
namespace:
description:
Namespace refers to the `metadata.namespace` field of the object
manifest.
type: string
name:
description:
Name refers to the `metadata.name` field of the object manifest.
type: string
field:
type: object
description: |
Field is the reference to a field in the object.
If defined, `ResourceRef` must also be provided.
required:
- path
properties:
path:
type: string
description: |
Path is the JSON path of the field
e.g. `spec.template.spec.containers[3].resources.limits.cpu`
currentValue:
description: |
CurrrentValue is the current value of the field.
Can be any value - string, number, boolean, array or object.
proposedValue:
description: |
PropposedValue is the proposed value of the field to fix an issue.
Can be any value - string, number, boolean, array or object.
file:
type: object
description: File references a file containing the resource.
required:
- path
properties:
path:
type: string
description: |
Path is the OS agnostic, slash-delimited, relative path.
e.g. `some-dir/some-file.yaml`.
index:
type: number
default: 0
description: Index of the object in a multi-object YAML file.
tags:
type: object
additionalProperties:
type: string
description: |
Tags is an unstructured key value map stored with a result that may be set
by external tools to store and retrieve arbitrary metadata.
paths: {}
```
An example using `v1/List` as input:
#### Examples
The following is an example input, where the custom resource of kind
`FulfillmentCenter` is provided as `functionConfig`. The function will operate
on one resource of kind `Service`.
```yaml
apiVersion: v1
kind: List
items:
- apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
name: staging
address: "100 Main St."
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-reader
rules:
- resources:
- namespaces
apiGroups:
- ""
verbs:
- get
- watch
- list
```
In addition, a function MUST accept as input a List of kind `ResourceList` where the
`functionConfig` field, if present, will contain the invocation-specific configuration passed to the function
by the orchestrator.
Functions MAY consider this field optional so that they can be triggered in an ad-hoc fashion.
An example using `config.kubernetes.io/v1beta1/ResourceList` as input:
```yaml
apiVersion: config.kubernetes.io/v1beta1
apiVersion: config.kubernetes.io/v1
kind: ResourceList
functionConfig:
apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
name: staging
metadata:
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/foo:v1.0.0
spec:
address: "100 Main St."
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
- apiVersion: v1
kind: Service
metadata:
name: namespace-reader
rules:
- resources:
- namespaces
apiGroups:
- ""
verbs:
- get
- watch
- list
name: wordpress
labels:
app: wordpress
annotations:
config.kubernetes.io/index: "0"
config.kubernetes.io/path: "service.yaml"
spec: # Example comment
type: LoadBalancer
selector:
app: wordpress
tier: frontend
ports:
- protocol: TCP
port: 80
```
Here `FulfillmentCenter` kind with name `staging` is passed as the invocation-specific configuration
to the function.
The following is an example output containing one result representing a
validation error:
### Output Type
A functions output MUST be the same as the input specification above
-- i.e. `ResourceList` or `List`.
This is necessary to enable chaining two or more functions together in a pipeline.
The serialization format of the output SHOULD match that of its input on each invocation
-- e.g. if the input was a `ResourceList`, the output should also be a `ResourceList`.
```yaml
apiVersion: config.kubernetes.io/v1
kind: ResourceList
items:
- apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
annotations:
config.kubernetes.io/index: "0"
config.kubernetes.io/path: "service.yaml"
spec: # Example comment
type: LoadBalancer
selector:
app: wordpress
tier: frontend
ports:
- protocol: TCP
port: 80
results:
- message: "Invalid type. Expected: integer, given: string"
severity: error
resourceRef:
apiVersion: v1
kind: Service
name: wordpress
field:
path: spec.ports.0.port
file:
path: service.yaml
```
### Serialization Format
A function MUST support YAML as a serialization format for the input and output.
A function MUST use utf8 encoding (as YAML is a superset of JSON, JSON will also be supported
by any conforming function).
### Operations
A function MAY Create, Update, or Delete any number of items in the `items` field and output the
resultant list.
A function MAY modify annotations with prefix `config.kubernetes.io`, but must be careful about
doing so since theyre used for orchestration purposes and will likely impact subsequent functions
in the pipeline.
A function SHOULD preserve comments when input serialization format is YAML.
This allows for human authoring of configuration to coexist with changes made by functions.
A function MUST use utf8 encoding (as YAML is a superset of JSON, JSON will also
be supported by any conforming function).
### Containerization
A function MUST be implemented as a container.
A function container MUST be capable of running as a non-root user if it does not require
access to host filesystem or makes network calls.
A function container MUST be capable of running as a non-root user `nobody` if
it does not require access to host filesystem.
### stdin/stdout/stderr and Exit Codes
### stderr
A function MUST accept input from stdin and emit output to stdout.
Any non-structured error messages MUST be emitted to `stderr`. `stdout` is
reserved for `ResourceList` as described above.
Any error messages MUST be emitted to stderr.
### Exit Code
An exit code of zero indicates function execution was successful.
A non-zero exit code indicates a failure.
An exit code of zero indicates function execution was successful. A non-zero
exit code indicates a failure.
[1]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
### Operations
A function MAY Create, Update, or Delete any number of items in the `items`
field and output the resultant list in the corresponding `items` field of the
output.
A function SHOULD preserve comments when input serialization format is YAML.
This allows for human authoring of configuration to coexist with changes made by
functions.
### Annotations
The orchestrator annotates resources in the wire format with annotation prefix
`config.kubernetes.io`. These annotations are not persisted when the
orchestrator writes the resources to the filesystem. The orchestrator sets this
annotation when reading files from the local filesystem and removes the
annotation when writing the output of functions back to the filesystem.
In general, a function MUST NOT modify these annotations except the ones
explicitly listed below.
#### `config.kubernetes.io/path`
Records the slash-delimited, OS-agnostic, relative file path to a resource. The
path is relative to a fix location on the filesystem. Different orchestrator
implementations can choose different fixed points.
A function SHOULD NOT modify this annotation.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
```
#### `config.kubernetes.io/index`
Records the index of a Resource in file. In a multi-object YAML file, resources
are separated by three dashes (`---`), and the index represents the position of
the Resource starting from zero. When this annotation is not specified, it
implies a value of `0`.
A function SHOULD NOT modify this annotation.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
config.kubernetes.io/index: 2
```
This represents the third resource in the file.
[1]:
https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
[2]: https://tools.ietf.org/html/rfc2119
[3]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#types-kinds
[3]:
https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#types-kinds

View File

@@ -20,3 +20,5 @@ require (
)
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
replace sigs.k8s.io/kustomize/kyaml => ../../kyaml

View File

@@ -244,7 +244,5 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/kustomize/kyaml v0.10.20 h1:L9JNKvJfCBpmYFr4tP0igpfj/pXP7nW2aXOWNtF5k1g=
sigs.k8s.io/kustomize/kyaml v0.10.20/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -104,9 +104,9 @@ func (r *AnnotateRunner) ExecuteCmd(w io.Writer, pkgPath string) error {
return err
}
// print error message and continue if there are multiple packages to annotate
fmt.Fprintf(w, "%s\n", err.Error())
_, _ = fmt.Fprintf(w, "%s\n", err.Error())
} else {
fmt.Fprint(w, "added annotations in the package\n")
_, _ = fmt.Fprint(w, "added annotations in the package\n")
}
return nil
}

View File

@@ -87,8 +87,18 @@ metadata:
expectedFiles: func(d string) map[string]string {
return map[string]string{
"deployment.json": `
{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "foo", annotations: {
a-string-value: '', a-int-value: '0', a-bool-value: 'false'}}}
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"annotations": {
"a-bool-value": "false",
"a-int-value": "0",
"a-string-value": ""
},
"name": "foo"
}
}
`,
}
},

View File

@@ -1,7 +1,23 @@
## Checking openapi build issues
# Troubles with dependencies on openapi
If you see the error
> cannot use api.Schema.SchemaProps.Properties
then you have a set of incompatible dependencies. This doc describes the problem and a fix.
tl;dr A mix of old and new is bad.
Anyone depending on k8s.io `v0.20.x` or _older_ packages must avoid depending on anything that depends
on `k8s.io/kube-openapi` newer than v0.0.0-20210323165736-1a6458611d18.
> More context in https://github.com/kubernetes/cli-runtime/issues/19
This dir exists to test the problem.
Edit the `main.go` and `go.mod` in this dir to see what builds
with various combinations of cli-runtime kube-openapi.
with various combinations of `cli-runtime` and `kube-openapi`.
####
@@ -16,9 +32,9 @@ means that anyone depending on
and _any other package that imports kube-openapi_ (e.g. kyaml)
may see a build error like
~/go/pkg/mod/sigs.k8s.io/kustomize@v2.0.3+incompatible/pkg/transformers/config/factorycrd.go:71:47:
cannot use api.Schema.SchemaProps.Properties (type map[string]"k8s.io/kube-openapi/pkg/validation/spec".Schema)
as type myProperties in argument to looksLikeAk8sType
> ~/go/pkg/mod/sigs.k8s.io/kustomize@v2.0.3+incompatible/pkg/transformers/config/factorycrd.go:71:47:
> cannot use api.Schema.SchemaProps.Properties (type map[string]"k8s.io/kube-openapi/pkg/validation/spec".Schema)
> as type myProperties in argument to looksLikeAk8sType
## Why?

View File

@@ -26,14 +26,14 @@ var (
// TODO: make this a PATH-like flag
// e.g.: --excludes ".git:.idea:site:docs"
excSlice = []string{
exclusions = []string{
".git",
".github",
".idea",
"docs",
"examples",
"hack",
// "plugin",
"plugin",
"releasing",
"site",
}
@@ -94,7 +94,7 @@ func (a *Args) ConditionalModule() misc.ModuleShortName {
func (a *Args) Exclusions() (result []string) {
// Make sure the list has no repeats.
for k := range utils.SliceToSet(excSlice) {
for k := range utils.SliceToSet(exclusions) {
result = append(result, k)
}
return

View File

@@ -6,8 +6,10 @@ require (
github.com/rakyll/statik v0.1.7
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.5.1
sigs.k8s.io/kustomize/api v0.8.9
sigs.k8s.io/kustomize/api v0.8.10
sigs.k8s.io/kustomize/kyaml v0.10.20
)
replace sigs.k8s.io/kustomize/api => ../../api
replace sigs.k8s.io/kustomize/kyaml => ../../kyaml

View File

@@ -229,8 +229,6 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/kustomize/kyaml v0.10.20 h1:L9JNKvJfCBpmYFr4tP0igpfj/pXP7nW2aXOWNtF5k1g=
sigs.k8s.io/kustomize/kyaml v0.10.20/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -27,7 +27,7 @@ const (
// ConvertToBuiltInPlugin converts the input plugin file to
// kustomize builtin plugin and writes it to proper directory
func ConvertToBuiltInPlugin() error {
func ConvertToBuiltInPlugin() (retErr error) {
root, err := inputFileRoot()
if err != nil {
return err
@@ -47,7 +47,12 @@ func ConvertToBuiltInPlugin() error {
if err != nil {
return err
}
defer w.close()
defer func() {
closeErr := w.Close()
if retErr == nil {
retErr = closeErr
}
}()
// This particular phrasing is required.
w.write(
@@ -139,10 +144,10 @@ func makeOutputFileName(root string) string {
"..", "..", "..", "api", packageForGeneratedCode, root+".go")
}
func (w *writer) close() {
func (w *writer) Close() error {
// Do this for debugging.
// fmt.Println("Generated " + makeOutputFileName(w.root))
w.f.Close()
return w.f.Close()
}
func (w *writer) write(line string) {

View File

@@ -5,15 +5,15 @@ English | [简体中文](zh/README.md)
To run these examples, your `$PATH` must contain `kustomize`.
See the [installation instructions](../docs/INSTALL.md).
These examples are [tested](../scripts/kyaml-pre-commit.sh)
These examples are [tested](../hack/testExamplesAgainstKustomize.sh)
to work with the latest _released_ version of kustomize.
Basic Usage
* [valueAdd](valueAdd.md) -
* [valueAdd](valueAdd.md) -
Add a simple string value easily to various fields, including
fields that happen to hold file paths.
* [configGenerations](configGeneration.md) -
Rolling update when ConfigMapGenerator changes.

View File

@@ -88,6 +88,7 @@ Define this base the usual way by creating a
cat <<'EOF' >$DEMO_HOME/base/kustomization.yaml
helmCharts:
- name: minecraft
includeCRDs: false
valuesInline:
minecraftServer:
eula: true
@@ -105,6 +106,10 @@ field, specifying a single chart.
The `valuesInline` field overrides some native chart values.
The `includeCRDs` field instructs Helm to generate
`CustomResourceDefinitions`.
See [the Helm documentation](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/) for details.
Check the directory layout:
<!-- @tree -->

View File

@@ -4,7 +4,7 @@
这些示例默认 `kustomize` 在您的 `$PATH` 中。
这些示例通过了 [pre-commit](../../scripts/kyaml-pre-commit.sh) 测试,并且应该与 HEAD 一起使用。
这些示例通过了 [pre-commit](../../hack/testExamplesAgainstKustomize.sh) 测试,并且应该与 HEAD 一起使用。
```
go get sigs.k8s.io/kustomize/v3/cmd/kustomize
@@ -58,4 +58,4 @@ go get sigs.k8s.io/kustomize/v3/cmd/kustomize
* [multibases](multibases.md) - 使用相同的 base 生成三个 variantsdevstagingproduction
>声明:部分文档可能稍微滞后于英文版本,同步工作持续进行中
>声明:部分文档可能稍微滞后于英文版本,同步工作持续进行中

View File

@@ -34,9 +34,14 @@ mdrip --mode test --blockTimeOut 15m \
# TODO: make work for non-linux
if onLinuxAndNotOnRemoteCI; then
echo "On linux, and not on remote CI. Running expensive tests."
make $MYGOBIN/helmV3
mdrip --mode test --label testHelm examples/chart.md
if [ "$version" == "HEAD" ]; then
echo "On linux, and not on remote CI. Running helm tests."
make $MYGOBIN/helmV3
mdrip --mode test --label testHelm examples/chart.md
else
echo "Skipping helm tests against $version."
echo "Helm chart inflator has new features (includeCRD) only in HEAD."
fi
fi
# Force outside logic to rebuild kustomize rather than

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
@@ -21,7 +22,8 @@ func TestAddBaseHappyPath(t *testing.T) {
fSys := filesys.MakeFsInMemory()
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fSys.Mkdir(base)
err := fSys.Mkdir(base)
require.NoError(t, err)
}
testutils_test.WriteTestKustomization(fSys)
@@ -41,7 +43,8 @@ func TestAddBaseAlreadyThere(t *testing.T) {
// Create fake directories
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fSys.Mkdir(base)
err := fSys.Mkdir(base)
require.NoError(t, err)
}
testutils_test.WriteTestKustomization(fSys)

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
)
@@ -21,8 +22,10 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
func TestAddComponentHappyPath(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(componentFileName, []byte(componentFileContent))
fSys.WriteFile(componentFileName+"another", []byte(componentFileContent))
err := fSys.WriteFile(componentFileName, []byte(componentFileContent))
require.NoError(t, err)
err = fSys.WriteFile(componentFileName+"another", []byte(componentFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddComponent(fSys)
@@ -36,7 +39,8 @@ func TestAddComponentHappyPath(t *testing.T) {
func TestAddComponentAlreadyThere(t *testing.T) {
fSys := filesys.MakeFsInMemory()
fSys.WriteFile(componentFileName, []byte(componentFileContent))
err := fSys.WriteFile(componentFileName, []byte(componentFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddComponent(fSys)
@@ -49,15 +53,16 @@ func TestAddComponentAlreadyThere(t *testing.T) {
func TestAddKustomizationFileAsComponent(t *testing.T) {
fSys := filesys.MakeFsInMemory()
fSys.WriteFile(componentFileName, []byte(componentFileContent))
err := fSys.WriteFile(componentFileName, []byte(componentFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddComponent(fSys)
args := []string{componentFileName}
assert.NoError(t, cmd.RunE(cmd, args))
require.NoError(t, cmd.RunE(cmd, args))
content, err := testutils_test.ReadTestKustomization(fSys)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotContains(t, string(content), componentFileName)
}
@@ -66,6 +71,5 @@ func TestAddComponentNoArgs(t *testing.T) {
cmd := newCmdAddComponent(fSys)
err := cmd.Execute()
assert.Error(t, err)
assert.Equal(t, "must specify a component file", err.Error())
assert.EqualError(t, err, "must specify a component file")
}

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
"sigs.k8s.io/kustomize/api/types"
@@ -163,8 +164,9 @@ func TestAddAnnotationForce(t *testing.T) {
// but trying to add it with --force should
v = valtest_test.MakeHappyMapValidator(t)
cmd = newCmdAddAnnotation(fSys, v.Validator)
cmd.Flag("force").Value.Set("true")
assert.NoError(t, cmd.RunE(cmd, args))
err = cmd.Flag("force").Value.Set("true")
require.NoError(t, err)
require.NoError(t, cmd.RunE(cmd, args))
v.VerifyCall()
}
@@ -266,7 +268,8 @@ func TestAddLabelForce(t *testing.T) {
// but trying to add it with --force should
v = valtest_test.MakeHappyMapValidator(t)
cmd = newCmdAddLabel(fSys, v.Validator)
cmd.Flag("force").Value.Set("true")
err = cmd.Flag("force").Value.Set("true")
require.NoError(t, err)
assert.NoError(t, cmd.RunE(cmd, args))
v.VerifyCall()
}

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
)
@@ -28,7 +29,8 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
func TestAddPatchWithFilePath(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(patchFileName, []byte(patchFileContent))
err := fSys.WriteFile(patchFileName, []byte(patchFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddPatch(fSys)
@@ -53,7 +55,8 @@ func TestAddPatchWithFilePath(t *testing.T) {
func TestAddPatchWithPatchContent(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(patchFileName, []byte(patchFileContent))
err := fSys.WriteFile(patchFileName, []byte(patchFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddPatch(fSys)
@@ -78,7 +81,8 @@ func TestAddPatchWithPatchContent(t *testing.T) {
func TestAddPatchAlreadyThere(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(patchFileName, []byte(patchFileContent))
err := fSys.WriteFile(patchFileName, []byte(patchFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddPatch(fSys)

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
)
@@ -21,8 +22,10 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
func TestAddResourceHappyPath(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(resourceFileName, []byte(resourceFileContent))
fSys.WriteFile(resourceFileName+"another", []byte(resourceFileContent))
err := fSys.WriteFile(resourceFileName, []byte(resourceFileContent))
require.NoError(t, err)
err = fSys.WriteFile(resourceFileName+"another", []byte(resourceFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddResource(fSys)
@@ -36,7 +39,8 @@ func TestAddResourceHappyPath(t *testing.T) {
func TestAddResourceAlreadyThere(t *testing.T) {
fSys := filesys.MakeFsInMemory()
fSys.WriteFile(resourceFileName, []byte(resourceFileContent))
err := fSys.WriteFile(resourceFileName, []byte(resourceFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddResource(fSys)
@@ -49,7 +53,8 @@ func TestAddResourceAlreadyThere(t *testing.T) {
func TestAddKustomizationFileAsResource(t *testing.T) {
fSys := filesys.MakeFsInMemory()
fSys.WriteFile(resourceFileName, []byte(resourceFileContent))
err := fSys.WriteFile(resourceFileName, []byte(resourceFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddResource(fSys)

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile"
testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils"
@@ -22,8 +23,10 @@ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
func TestAddTransformerHappyPath(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(transformerFileName, []byte(transformerFileContent))
fSys.WriteFile(transformerFileName+"another", []byte(transformerFileContent))
err := fSys.WriteFile(transformerFileName, []byte(transformerFileContent))
require.NoError(t, err)
err = fSys.WriteFile(transformerFileName+"another", []byte(transformerFileContent))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddTransformer(fSys)
@@ -37,7 +40,8 @@ func TestAddTransformerHappyPath(t *testing.T) {
func TestAddTransformerAlreadyThere(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(transformerFileName, []byte(transformerFileName))
err := fSys.WriteFile(transformerFileName, []byte(transformerFileName))
require.NoError(t, err)
testutils_test.WriteTestKustomization(fSys)
cmd := newCmdAddTransformer(fSys)
@@ -61,18 +65,18 @@ func TestAddTransformerNoArgs(t *testing.T) {
cmd := newCmdAddTransformer(fSys)
err := cmd.Execute()
assert.Error(t, err)
assert.Equal(t, "must specify a transformer file", err.Error())
assert.EqualError(t, err, "must specify a transformer file")
}
func TestAddTransformerMissingKustomizationYAML(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.WriteFile(transformerFileName, []byte(transformerFileContent))
fSys.WriteFile(transformerFileName+"another", []byte(transformerFileContent))
err := fSys.WriteFile(transformerFileName, []byte(transformerFileContent))
require.NoError(t, err)
err = fSys.WriteFile(transformerFileName+"another", []byte(transformerFileContent))
require.NoError(t, err)
cmd := newCmdAddTransformer(fSys)
args := []string{transformerFileName + "*"}
err := cmd.RunE(cmd, args)
assert.Error(t, err)
assert.Equal(t, "Missing kustomization file 'kustomization.yaml'.\n", err.Error())
err = cmd.RunE(cmd, args)
assert.EqualError(t, err, "Missing kustomization file 'kustomization.yaml'.\n")
}

View File

@@ -8,7 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
)
@@ -88,13 +88,17 @@ func TestDataConfigValidation_Flags(t *testing.T) {
func TestExpandFileSource(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.Create("dir/fa1")
fSys.Create("dir/fa2")
fSys.Create("dir/readme")
_, err := fSys.Create("dir/fa1")
require.NoError(t, err)
_, err = fSys.Create("dir/fa2")
require.NoError(t, err)
_, err = fSys.Create("dir/readme")
require.NoError(t, err)
fa := flagsAndArgs{
FileSources: []string{"dir/fa*"},
}
fa.ExpandFileSource(fSys)
err = fa.ExpandFileSource(fSys)
require.NoError(t, err)
expected := []string{
"dir/fa1",
"dir/fa2",
@@ -106,14 +110,19 @@ func TestExpandFileSource(t *testing.T) {
func TestExpandFileSourceWithKey(t *testing.T) {
fSys := filesys.MakeEmptyDirInMemory()
fSys.Create("dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc")
fSys.Create("dir/foobar")
fSys.Create("dir/simplebar")
fSys.Create("dir/readme")
_, err := fSys.Create("dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc")
require.NoError(t, err)
_, err = fSys.Create("dir/foobar")
require.NoError(t, err)
_, err = fSys.Create("dir/simplebar")
require.NoError(t, err)
_, err = fSys.Create("dir/readme")
require.NoError(t, err)
fa := flagsAndArgs{
FileSources: []string{"foo-key=dir/fa*", "bar-key=dir/foobar", "dir/simplebar"},
}
fa.ExpandFileSource(fSys)
err = fa.ExpandFileSource(fSys)
require.NoError(t, err)
expected := []string{
"foo-key=dir/faaaaaaaaaabbbbbbbbbccccccccccccccccc",
"bar-key=dir/foobar",
@@ -126,13 +135,16 @@ func TestExpandFileSourceWithKey(t *testing.T) {
func TestExpandFileSourceWithKeyAndError(t *testing.T) {
fSys := filesys.MakeFsInMemory()
fSys.Create("dir/fa1")
fSys.Create("dir/fa2")
fSys.Create("dir/readme")
_, err := fSys.Create("dir/fa1")
require.NoError(t, err)
_, err = fSys.Create("dir/fa2")
require.NoError(t, err)
_, err = fSys.Create("dir/readme")
require.NoError(t, err)
fa := flagsAndArgs{
FileSources: []string{"foo-key=dir/fa*"},
}
err := fa.ExpandFileSource(fSys)
err = fa.ExpandFileSource(fSys)
if err == nil {
t.Fatalf("FileSources should not be correctly expanded: %v", fa.FileSources)
}

View File

@@ -9,7 +9,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/types"
@@ -125,7 +125,8 @@ configMapGenerator:
`
for _, n := range konfig.RecognizedKustomizationFileNames() {
fSys := filesys.MakeFsInMemory()
fSys.WriteFile(n, []byte(kcontent))
err := fSys.WriteFile(n, []byte(kcontent))
require.NoError(t, err)
k, err := NewKustomizationFile(fSys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)

View File

@@ -8,7 +8,7 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
sigs.k8s.io/kustomize/api v0.8.9
sigs.k8s.io/kustomize/api v0.8.10
sigs.k8s.io/kustomize/cmd/config v0.9.12
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/yaml v1.2.0
@@ -20,3 +20,7 @@ exclude (
)
replace sigs.k8s.io/kustomize/api => ../api
replace sigs.k8s.io/kustomize/cmd/config => ../cmd/config
replace sigs.k8s.io/kustomize/kyaml => ../kyaml

View File

@@ -253,10 +253,6 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/kustomize/cmd/config v0.9.12 h1:hDLY4mxEqDLVSqxlJoamZZEhOM6TBysYR2wDlmW+iEo=
sigs.k8s.io/kustomize/cmd/config v0.9.12/go.mod h1:hVG/nPSqccrnogZ4ehzw3cTSR2fT6hdWeyojILHZivA=
sigs.k8s.io/kustomize/kyaml v0.10.20 h1:L9JNKvJfCBpmYFr4tP0igpfj/pXP7nW2aXOWNtF5k1g=
sigs.k8s.io/kustomize/kyaml v0.10.20/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -54,7 +54,10 @@ func (c *copier) VisitList(s walk.Sources, _ *openapi.ResourceSchema, _ walk.Lis
origin := originItems[i]
if dest.Value == origin.Value {
copy(yaml.NewRNode(dest), yaml.NewRNode(origin))
// We copy the comments recursively on each node in the list.
if err := CopyComments(yaml.NewRNode(dest), yaml.NewRNode(origin)); err != nil {
return nil, err
}
}
}

View File

@@ -85,6 +85,48 @@ spec:
`,
},
{
name: "associative_list_2",
from: `
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: EnforceFoo
metadata:
name: enforce-foo
spec:
parameters:
naming_rules:
- kind: Bar
patterns:
# comment 1
- ^(dev|prod|staging|qa|shared)$
`,
to: `
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: EnforceFoo
metadata:
name: enforce-foo
spec:
parameters:
naming_rules:
- kind: Bar
patterns:
- ^(dev|prod|staging|qa|shared)$
`,
expected: `
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: EnforceFoo
metadata:
name: enforce-foo
spec:
parameters:
naming_rules:
- kind: Bar
patterns:
# comment 1
- ^(dev|prod|staging|qa|shared)$
`,
},
{
name: "keep_comments",
from: `# A

View File

@@ -320,9 +320,12 @@ func ExampleBuild_validate() {
continue
}
validationResults = append(validationResults, framework.ResultItem{
Severity: framework.Error,
Message: "field is required",
ResourceRef: meta,
Severity: framework.Error,
Message: "field is required",
ResourceRef: yaml.ResourceIdentifier{
TypeMeta: meta.TypeMeta,
NameMeta: meta.ObjectMeta.NameMeta,
},
Field: framework.Field{
Path: "spec.replicas",
SuggestedValue: "1",
@@ -374,8 +377,7 @@ items:
// resourceRef:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// name: foo
// field:
// path: spec.replicas
// suggestedValue: "1"

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
@@ -962,10 +963,27 @@ func (a *v1alpha1JavaSpringBoot) Default() error {
}
func (a *v1alpha1JavaSpringBoot) Validate() error {
var messages []string
if a.Metadata.Name == "" {
return errors.Errorf("Name is required")
messages = append(messages, "name is required")
}
return nil
if a.Spec.Replicas > 10 {
messages = append(messages, "replicas must be less than 10")
}
if !strings.HasSuffix(a.Spec.Domain, "example.com") {
messages = append(messages, "domain must be a subdomain of example.com")
}
if strings.HasSuffix(a.Spec.Image, ":latest") {
messages = append(messages, "image should not have latest tag")
}
if len(messages) == 0 {
return nil
}
errMsg := fmt.Sprintf("JavaSpringBoot had %d errors:\n", len(messages))
for i, msg := range messages {
errMsg += fmt.Sprintf(" [%d] %s\n", i+1, msg)
}
return errors.Errorf(errMsg)
}
// ExampleVersionedAPIProcessor shows how to use the VersionedAPIProcessor and TemplateProcessor to

View File

@@ -18,25 +18,29 @@ func TestExecute_Result(t *testing.T) {
p := framework.ResourceListProcessorFunc(func(rl *framework.ResourceList) error {
err := &framework.Result{
Name: "Incompatible config",
Items: []framework.ResultItem{{
Message: "bad value for replicas",
Severity: framework.Error,
ResourceRef: yaml.ResourceMeta{
TypeMeta: yaml.TypeMeta{APIVersion: "v1", Kind: "Deployment"},
ObjectMeta: yaml.ObjectMeta{
Items: []framework.ResultItem{
{
Message: "bad value for replicas",
Severity: framework.Error,
ResourceRef: yaml.ResourceIdentifier{
TypeMeta: yaml.TypeMeta{APIVersion: "v1", Kind: "Deployment"},
NameMeta: yaml.NameMeta{Name: "tester", Namespace: "default"},
},
Field: framework.Field{
Path: ".spec.Replicas",
CurrentValue: "0",
SuggestedValue: "3",
},
File: framework.File{
Path: "/path/to/deployment.yaml",
Index: 0,
},
},
Field: framework.Field{
Path: ".spec.Replicas",
CurrentValue: "0",
SuggestedValue: "3",
{
Message: "some error",
Severity: framework.Error,
},
File: framework.File{
Path: "/path/to/deployment.yaml",
Index: 0,
},
}},
},
}
rl.Result = err
return err
@@ -55,8 +59,9 @@ items:
replicas: 0
`), Writer: out}
err := framework.Execute(p, source)
assert.EqualError(t, err, "[error] v1/Deployment/default/tester .spec."+
"Replicas: bad value for replicas")
assert.EqualError(t, err, `[error] v1/Deployment/default/tester .spec.Replicas: bad value for replicas
[error] : some error`)
assert.Equal(t, 1, err.(*framework.Result).ExitCode())
assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
@@ -76,13 +81,14 @@ results:
resourceRef:
apiVersion: v1
kind: Deployment
metadata:
name: tester
namespace: default
name: tester
namespace: default
field:
path: .spec.Replicas
currentValue: "0"
suggestedValue: "3"
file:
path: /path/to/deployment.yaml`, strings.TrimSpace(out.String()))
path: /path/to/deployment.yaml
- message: some error
severity: error`, strings.TrimSpace(out.String()))
}

View File

@@ -9,27 +9,60 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// CommandResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
const (
DefaultTestDataDirectory = "testdata"
DefaultConfigInputFilename = "config.yaml"
DefaultInputFilename = "input.yaml"
DefaultInputFilenameGlob = "input*.yaml"
DefaultOutputFilename = "expected.yaml"
DefaultErrorFilename = "errors.txt"
)
// CommandResultsChecker tests a command-wrapped function by running it with predefined inputs
// and comparing the outputs to expected results.
type CommandResultsChecker struct {
// TestDataDirectory is the directory containing the testdata subdirectories.
// CommandResultsChecker will recurse into each test directory and run the Command
// if the directory contains both the ConfigInputFilename and at least one
// of ExpectedOutputFilname or ExpectedErrorFilename.
// if the directory contains at least one of ExpectedOutputFilename or ExpectedErrorFilename.
// Defaults to "testdata"
TestDataDirectory string
// ExpectedOutputFilename is the file with the expected output of the function
// Defaults to "expected.yaml". Directories containing neither this file
// nor ExpectedErrorFilename will be skipped.
ExpectedOutputFilename string
// ExpectedErrorFilename is the file containing elements of an expected error message.
// Each line of the file will be treated as a regex that must match the actual error.
// Defaults to "errors.txt". Directories containing neither this file
// nor ExpectedOutputFilename will be skipped.
ExpectedErrorFilename string
// UpdateExpectedFromActual if set to true will write the actual results to the
// expected testdata files. This is useful for updating test data.
UpdateExpectedFromActual bool
// OutputAssertionFunc allows you to swap out the logic used to compare the expected output
// from the fixture file to the actual output.
// By default, it performs a string comparison after normalizing whitespace.
OutputAssertionFunc AssertionFunc
// ErrorAssertionFunc allows you to swap out the logic used to compare the expected error
// message from the fixture file to the actual error message.
// By default, it interprets each line of the fixture as a regex that the actual error must match.
ErrorAssertionFunc AssertionFunc
// ConfigInputFilename is the name of the config file provided as the first
// argument to the function. Directories without this file will be skipped.
// Defaults to "config.yaml"
@@ -39,226 +72,114 @@ type CommandResultsChecker struct {
// Defaults to "input*.yaml"
InputFilenameGlob string
// ExpectedOutputFilename is the file with the expected output of the function
// Defaults to "expected.yaml". Directories containing neither this file
// nor ExpectedErrorFilename will be skipped.
ExpectedOutputFilename string
// ExpectedErrorFilename is the file containing part of an expected error message
// Defaults to "error.yaml". Directories containing neither this file
// nor ExpectedOutputFilename will be skipped.
ExpectedErrorFilename string
// Command provides the function to run.
Command func() *cobra.Command
// UpdateExpectedFromActual if set to true will write the actual results to the
// expected testdata files. This is useful for updating test data.
UpdateExpectedFromActual bool
testsCasesRun int
}
// Assert asserts the results for functions
// Assert runs the command with the input provided in each valid test directory
// and verifies that the actual output and error match the fixtures in the directory.
func (rc *CommandResultsChecker) Assert(t *testing.T) bool {
if rc.TestDataDirectory == "" {
rc.TestDataDirectory = "testdata"
}
if rc.ConfigInputFilename == "" {
rc.ConfigInputFilename = "config.yaml"
}
if rc.ExpectedOutputFilename == "" {
rc.ExpectedOutputFilename = "expected.yaml"
}
if rc.ExpectedErrorFilename == "" {
rc.ExpectedErrorFilename = "error.yaml"
rc.ConfigInputFilename = DefaultConfigInputFilename
}
if rc.InputFilenameGlob == "" {
rc.InputFilenameGlob = "input*.yaml"
rc.InputFilenameGlob = DefaultInputFilenameGlob
}
err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if !info.IsDir() {
// skip non-directories
return nil
checker := newResultsChecker(
rc.TestDataDirectory, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename,
rc.OutputAssertionFunc, rc.ErrorAssertionFunc,
rc.UpdateExpectedFromActual,
)
checker.assert(t, func() (string, string) {
_, err := os.Stat(rc.ConfigInputFilename)
if os.IsNotExist(err) {
t.Errorf("Test case is missing FunctionConfig input file (default: %s)", DefaultConfigInputFilename)
}
rc.compare(t, path)
return nil
})
require.NoError(t, err)
require.NoError(t, err)
args := []string{rc.ConfigInputFilename}
require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)
return true
}
func (rc *CommandResultsChecker) compare(t *testing.T, path string) {
// cd into the directory so we can test functions that refer
// local files by relative paths
d, err := os.Getwd()
require.NoError(t, err)
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
// make sure this directory contains test data
_, err = os.Stat(rc.ConfigInputFilename)
if os.IsNotExist(err) {
// missing input
return
}
args := []string{rc.ConfigInputFilename}
expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
if expectedError == "" && expectedOutput == "" && !rc.UpdateExpectedFromActual {
// missing expected and UpdateExpectedFromActual == false, return and skip this test
return
}
require.NoError(t, err)
// run the test
t.Run(path, func(t *testing.T) {
rc.testsCasesRun += 1
if rc.InputFilenameGlob != "" {
inputs, err := filepath.Glob(rc.InputFilenameGlob)
require.NoError(t, err)
args = append(args, inputs...)
}
var actualOutput, actualError bytes.Buffer
var stdOut, stdErr bytes.Buffer
cmd := rc.Command()
cmd.SetArgs(args)
cmd.SetOut(&actualOutput)
cmd.SetErr(&actualError)
cmd.SetOut(&stdOut)
cmd.SetErr(&stdErr)
err = cmd.Execute()
// Update the fixtures if configured to
if rc.UpdateExpectedFromActual {
if actualError.String() != "" {
assert.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, actualError.Bytes(), 0600))
}
if actualOutput.String() != "" {
assert.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
}
return
}
// Compare the results
if expectedError != "" {
// We expected an error, so make sure there was one and it matches
require.Error(t, err, actualOutput.String())
require.Contains(t,
standardizeSpacing(actualError.String()),
standardizeSpacing(expectedError), actualOutput.String())
} else {
// We didn't expect an error, and the output should match
require.NoError(t, err, actualError.String())
require.Equal(t,
standardizeSpacing(expectedOutput),
standardizeSpacing(actualOutput.String()), actualError.String())
}
return stdOut.String(), stdErr.String()
})
return true
}
func standardizeSpacing(s string) string {
// remove extra whitespace and convert Windows line endings
return strings.ReplaceAll(strings.TrimSpace(s), "\r\n", "\n")
}
// ProcessorResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
// ProcessorResultsChecker tests a processor function by running it with predefined inputs
// and comparing the outputs to expected results.
type ProcessorResultsChecker struct {
// TestDataDirectory is the directory containing the testdata subdirectories.
// CommandResultsChecker will recurse into each test directory and run the Processor
// if the directory contains both the InputFilename and at least one
// of ExpectedOutputFilename or ExpectedErrorFilename.
// ProcessorResultsChecker will recurse into each test directory and run the Command
// if the directory contains at least one of ExpectedOutputFilename or ExpectedErrorFilename.
// Defaults to "testdata"
TestDataDirectory string
// InputFilename is the name of the file containing the ResourceList input.
// Directories without this file will be skipped.
// Defaults to "input.yaml"
InputFilename string
// ExpectedOutputFilename is the file with the expected output of the function
// Defaults to "expected.yaml". Directories containing neither this file
// nor ExpectedErrorFilename will be skipped.
ExpectedOutputFilename string
// ExpectedErrorFilename is the file containing part of an expected error message
// Defaults to "error.yaml". Directories containing neither this file
// ExpectedErrorFilename is the file containing elements of an expected error message.
// Each line of the file will be treated as a regex that must match the actual error.
// Defaults to "errors.txt". Directories containing neither this file
// nor ExpectedOutputFilename will be skipped.
ExpectedErrorFilename string
// Processor returns a ResourceListProcessor to run.
Processor func() framework.ResourceListProcessor
// UpdateExpectedFromActual if set to true will write the actual results to the
// expected testdata files. This is useful for updating test data.
UpdateExpectedFromActual bool
testsCasesRun int
// InputFilename is the name of the file containing the ResourceList input.
// Directories without this file will be skipped.
// Defaults to "input.yaml"
InputFilename string
// OutputAssertionFunc allows you to swap out the logic used to compare the expected output
// from the fixture file to the actual output.
// By default, it performs a string comparison after normalizing whitespace.
OutputAssertionFunc AssertionFunc
// ErrorAssertionFunc allows you to swap out the logic used to compare the expected error
// message from the fixture file to the actual error message.
// By default, it interprets each line of the fixture as a regex that the actual error must match.
ErrorAssertionFunc AssertionFunc
// Processor returns a ResourceListProcessor to run.
Processor func() framework.ResourceListProcessor
}
// Assert asserts the results for functions
// Assert runs the processor with the input provided in each valid test directory
// and verifies that the actual output and error match the fixtures in the directory.
func (rc *ProcessorResultsChecker) Assert(t *testing.T) bool {
if rc.TestDataDirectory == "" {
rc.TestDataDirectory = "testdata"
}
if rc.InputFilename == "" {
rc.InputFilename = "input.yaml"
}
if rc.ExpectedOutputFilename == "" {
rc.ExpectedOutputFilename = "expected.yaml"
}
if rc.ExpectedErrorFilename == "" {
rc.ExpectedErrorFilename = "error.yaml"
rc.InputFilename = DefaultInputFilename
}
err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if !info.IsDir() {
// skip non-directories
return nil
checker := newResultsChecker(
rc.TestDataDirectory, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename,
rc.OutputAssertionFunc, rc.ErrorAssertionFunc,
rc.UpdateExpectedFromActual,
)
checker.assert(t, func() (string, string) {
_, err := os.Stat(rc.InputFilename)
if os.IsNotExist(err) {
t.Error("Test case is missing input file")
}
rc.compare(t, path)
return nil
})
require.NoError(t, err)
require.NoError(t, err)
require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)
return true
}
func (rc *ProcessorResultsChecker) compare(t *testing.T, path string) {
// cd into the directory so we can test functions that refer
// local files by relative paths
d, err := os.Getwd()
require.NoError(t, err)
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
// make sure this directory contains test data
_, err = os.Stat(rc.InputFilename)
if os.IsNotExist(err) {
// missing input
return
}
require.NoError(t, err)
expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
if expectedError == "" && expectedOutput == "" && !rc.UpdateExpectedFromActual {
// missing expected and UpdateExpectedFromActual == false, return and skip this test
return
}
// run the test
t.Run(path, func(t *testing.T) {
rc.testsCasesRun += 1
actualOutput := bytes.NewBuffer([]byte{})
rlBytes, err := ioutil.ReadFile(rc.InputFilename)
require.NoError(t, err)
@@ -267,63 +188,170 @@ func (rc *ProcessorResultsChecker) compare(t *testing.T, path string) {
Reader: bytes.NewBuffer(rlBytes),
Writer: actualOutput,
}
err = framework.Execute(rc.Processor(), &rw)
if err != nil {
require.NotEmptyf(t, err.Error(), "processor returned error with empty message")
return actualOutput.String(), err.Error()
}
return actualOutput.String(), ""
})
return true
}
// Update the fixtures if configured to
if rc.UpdateExpectedFromActual {
if err != nil {
require.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, []byte(err.Error()), 0600))
type AssertionFunc func(t *testing.T, expected string, actual string)
// RequireEachLineMatches is an AssertionFunc that treats each line of expected string
// as a regex that must match the actual string.
func RequireEachLineMatches(t *testing.T, expected, actual string) {
// Check that each expected line matches the output
actual = standardizeSpacing(actual)
for _, msg := range strings.Split(standardizeSpacing(expected), "\n") {
require.Regexp(t, regexp.MustCompile(msg), actual)
}
}
// RequireStrippedStringsEqual is an AssertionFunc that does a simple string comparison
// of expected and actual after normalizing whitespace.
func RequireStrippedStringsEqual(t *testing.T, expected, actual string) {
require.Equal(t,
standardizeSpacing(expected),
standardizeSpacing(actual))
}
func standardizeSpacing(s string) string {
// remove extra whitespace and convert Windows line endings
return strings.ReplaceAll(strings.TrimSpace(s), "\r\n", "\n")
}
// resultsChecker implements the core logic shared by all results checking types.
type resultsChecker struct {
testDataDirectory string
expectedOutputFilename string
expectedErrorFilename string
updateExpectedFromActual bool
outputAssertionFunc AssertionFunc
errorAssertionFunc AssertionFunc
testsCasesRun int
}
func newResultsChecker(testDataDir string, outputFilename string, errorFilename string,
outputAsserter AssertionFunc, errorAsserter AssertionFunc,
updateFixtures bool) *resultsChecker {
rc := resultsChecker{
testDataDirectory: testDataDir,
expectedOutputFilename: outputFilename,
expectedErrorFilename: errorFilename,
updateExpectedFromActual: updateFixtures,
outputAssertionFunc: outputAsserter,
errorAssertionFunc: errorAsserter,
}
if rc.testDataDirectory == "" {
rc.testDataDirectory = DefaultTestDataDirectory
}
if rc.expectedOutputFilename == "" {
rc.expectedOutputFilename = DefaultOutputFilename
}
if rc.expectedErrorFilename == "" {
rc.expectedErrorFilename = DefaultErrorFilename
}
if rc.outputAssertionFunc == nil {
rc.outputAssertionFunc = RequireStrippedStringsEqual
}
if rc.errorAssertionFunc == nil {
rc.errorAssertionFunc = RequireEachLineMatches
}
return &rc
}
// assert traverses TestDataDirectory to find test cases, calls getResult to invoke the function
// under test in each directory, and then runs assertions on the returned output and error results.
// It triggers a test failure if no valid test directories were found.
func (rc *resultsChecker) assert(t *testing.T, getResult func() (string, string)) {
err := filepath.Walk(rc.testDataDirectory,
func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if !info.IsDir() {
// skip non-directories
return nil
}
if len(actualOutput.String()) > 0 {
require.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
rc.runDirectoryTestCase(t, path, getResult)
return nil
})
require.NoError(t, err)
require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.testDataDirectory)
}
func (rc *resultsChecker) runDirectoryTestCase(t *testing.T, path string, getResult func() (string, string)) {
// cd into the directory so we can test functions that refer
// local files by relative paths
d, err := os.Getwd()
require.NoError(t, err)
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
expectedOutput, expectedError := rc.readAssertionFiles(t)
if expectedError == "" && expectedOutput == "" && !rc.updateExpectedFromActual {
// not a test directory: missing expectations and updateExpectedFromActual == false
return
}
// run the test
t.Run(path, func(t *testing.T) {
rc.testsCasesRun += 1
actualOutput, actualError := getResult()
// Configured to update the assertion files instead of comparing them
if rc.updateExpectedFromActual {
if actualError != "" {
require.NoError(t, ioutil.WriteFile(rc.expectedErrorFilename, []byte(actualError), 0600))
}
return
if len(actualOutput) > 0 {
require.NoError(t, ioutil.WriteFile(rc.expectedOutputFilename, []byte(actualOutput), 0600))
}
t.Skip("Updated fixtures for test case")
}
// Compare the results
// Compare the results to the assertion files
if expectedError != "" {
// We expected an error, so make sure there was one and it matches
require.Error(t, err, actualOutput.String())
require.Contains(t,
standardizeSpacing(err.Error()),
standardizeSpacing(expectedError), actualOutput.String())
// We expected an error, so make sure there was one
require.NotEmptyf(t, actualError, "test expected an error but message was empty, and output was:\n%s", actualOutput)
rc.errorAssertionFunc(t, expectedError, actualError)
} else {
// We didn't expect an error, and the output should match
require.NoError(t, err)
require.Equal(t,
standardizeSpacing(expectedOutput),
standardizeSpacing(actualOutput.String()))
require.Emptyf(t, actualError, "test expected no error but got an error message, and output was:\n%s", actualOutput)
rc.outputAssertionFunc(t, expectedOutput, actualOutput)
}
})
}
// getExpected reads the expected results and error files
func getExpected(t *testing.T, expectedOutFilename, expectedErrFilename string) (string, string) {
// readAssertionFiles reads the expected results and error files
func (rc *resultsChecker) readAssertionFiles(t *testing.T) (string, string) {
// read the expected results
var expectedOutput, expectedError string
if expectedOutFilename != "" {
_, err := os.Stat(expectedOutFilename)
if rc.expectedOutputFilename != "" {
_, err := os.Stat(rc.expectedOutputFilename)
if !os.IsNotExist(err) && err != nil {
t.FailNow()
}
if err == nil {
// only read the file if it exists
b, err := ioutil.ReadFile(expectedOutFilename)
b, err := ioutil.ReadFile(rc.expectedOutputFilename)
if !assert.NoError(t, err) {
t.FailNow()
}
expectedOutput = string(b)
}
}
if expectedErrFilename != "" {
_, err := os.Stat(expectedErrFilename)
if rc.expectedErrorFilename != "" {
_, err := os.Stat(rc.expectedErrorFilename)
if !os.IsNotExist(err) && err != nil {
t.FailNow()
}
if err == nil {
// only read the file if it exists
b, err := ioutil.ReadFile(expectedErrFilename)
b, err := ioutil.ReadFile(rc.expectedErrorFilename)
if !assert.NoError(t, err) {
t.FailNow()
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
@@ -589,3 +590,19 @@ items:
})
require.Nil(t, found, "openAPI schema was not reset")
}
func TestTemplateProcessor_Validator(t *testing.T) {
// This test proves the Validate method is called when implemented
// and demonstrates the use of ProcessorResultsChecker's error matching
p := func() framework.ResourceListProcessor {
return &framework.VersionedAPIProcessor{FilterProvider: framework.GVKFilterMap{
"JavaSpringBoot": {
"example.com/v1alpha1": &v1alpha1JavaSpringBoot{},
}}}
}
c := frameworktestutil.ProcessorResultsChecker{
TestDataDirectory: "testdata/validation",
Processor: p,
}
c.Assert(t)
}

View File

@@ -31,7 +31,7 @@ type ResultItem struct {
Severity Severity `yaml:"severity,omitempty"`
// ResourceRef is a reference to a resource
ResourceRef yaml.ResourceMeta `yaml:"resourceRef,omitempty"`
ResourceRef yaml.ResourceIdentifier `yaml:"resourceRef,omitempty"`
// Field is a reference to the field in a resource this result refers to
Field Field `yaml:"field,omitempty"`
@@ -42,9 +42,25 @@ type ResultItem struct {
// String provides a human-readable message for the result item
func (i ResultItem) String() string {
identifier := i.ResourceRef.GetIdentifier()
idString := strings.Join([]string{identifier.GetAPIVersion(), identifier.GetKind(), identifier.GetNamespace(), identifier.GetName()}, "/")
return fmt.Sprintf("[%s] %s %s: %s", i.Severity, idString, i.Field.Path, i.Message)
identifier := i.ResourceRef
var idStringList []string
if identifier.APIVersion != "" {
idStringList = append(idStringList, identifier.APIVersion)
}
if identifier.Kind != "" {
idStringList = append(idStringList, identifier.Kind)
}
if identifier.Namespace != "" {
idStringList = append(idStringList, identifier.Namespace)
}
if identifier.Name != "" {
idStringList = append(idStringList, identifier.Name)
}
if len(idStringList) > 0 {
idString := strings.Join(idStringList, "/")
return fmt.Sprintf("[%s] %s %s: %s", i.Severity, idString, i.Field.Path, i.Message)
}
return fmt.Sprintf("[%s] %s: %s", i.Severity, i.Field.Path, i.Message)
}
// File references a file containing a resource

View File

@@ -0,0 +1,5 @@
JavaSpringBoot had 4 errors:
\[\d\] replicas must be less than 10
\[\d\] name is required
\[\d\] image should not have latest tag
\[\d\] domain must be a subdomain of example.com

View File

@@ -0,0 +1,14 @@
# Copyright 2021 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
kind: ResourceList
apiVersion: config.kubernetes.io/v1alpha1
functionConfig:
apiVersion: example.com/v1alpha1
kind: JavaSpringBoot
metadata:
name: ""
spec:
replicas: 1000
image: foo:latest
domain: bad

View File

@@ -6,13 +6,16 @@ package kio
import (
"encoding/json"
"io"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Writer writes ResourceNodes to bytes.
// ByteWriter writes ResourceNodes to bytes. Generally YAML encoding will be used but in the special
// case of writing a single, bare yaml.RNode that has a kioutil.PathAnnotation indicating that the
// target is a JSON file JSON encoding is used. See shouldJSONEncodeSingleBareNode below for more
// information.
type ByteWriter struct {
// Writer is where ResourceNodes are encoded.
Writer io.Writer
@@ -47,7 +50,9 @@ type ByteWriter struct {
var _ Writer = ByteWriter{}
func (w ByteWriter) Write(nodes []*yaml.RNode) error {
func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
// Copy the nodes to prevent writer from mutating the original nodes.
nodes := copyRNodes(inputNodes)
yaml.DoSerializationHacksOnNodes(nodes)
if w.Sort {
if err := kioutil.SortNodes(nodes); err != nil {
@@ -55,8 +60,9 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
}
}
encoder := yaml.NewEncoder(w.Writer)
defer encoder.Close()
// Even though we use the this value further down we must check this before removing annotations
jsonEncodeSingleBareNode := w.shouldJSONEncodeSingleBareNode(nodes)
for i := range nodes {
// clean resources by removing annotations set by the Reader
if !w.KeepReaderAnnotations {
@@ -81,10 +87,18 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
}
}
if jsonEncodeSingleBareNode {
encoder := json.NewEncoder(w.Writer)
encoder.SetIndent("", " ")
return errors.Wrap(encoder.Encode(nodes[0]))
}
encoder := yaml.NewEncoder(w.Writer)
defer encoder.Close()
// don't wrap the elements
if w.WrappingKind == "" {
for i := range nodes {
if err := w.encode(encoder, nodes[i].Document()); err != nil {
if err := encoder.Encode(nodes[i].Document()); err != nil {
return err
}
}
@@ -118,23 +132,37 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
for i := range nodes {
items.Content = append(items.Content, nodes[i].YNode())
}
err := w.encode(encoder, doc)
yaml.UndoSerializationHacksOnNodes(nodes)
return err
}
// encode encodes the input document node to appropriate node format
func (w ByteWriter) encode(encoder *yaml.Encoder, doc *yaml.Node) error {
rNode := &yaml.RNode{}
rNode.SetYNode(doc)
str, err := rNode.String()
if err != nil {
return errors.Wrap(err)
}
if json.Valid([]byte(str)) {
je := json.NewEncoder(w.Writer)
je.SetIndent("", " ")
return errors.Wrap(je.Encode(rNode))
}
return encoder.Encode(doc)
}
func copyRNodes(in []*yaml.RNode) []*yaml.RNode {
out := make([]*yaml.RNode, len(in))
for i := range in {
out[i] = in[i].Copy()
}
return out
}
// shouldJSONEncodeSingleBareNode determines if nodes contain a single node that should not be
// wrapped and has a JSON file extension, which in turn means that the node should be JSON encoded.
// Note 1: this must be checked before any annotations to avoid losing information about the target
// filename extension.
// Note 2: JSON encoding should only be used for single, unwrapped nodes because multiple unwrapped
// nodes cannot be represented in JSON (no multi doc support). Furthermore, the typical use
// cases for wrapping nodes would likely not include later writing the whole wrapper to a
// .json file, i.e. there is no point risking any edge case information loss e.g. comments
// disappearing, that could come from JSON encoding the whole wrapper just to ensure that
// one (or all nodes) can be read as JSON.
func (w ByteWriter) shouldJSONEncodeSingleBareNode(nodes []*yaml.RNode) bool {
if w.WrappingKind == "" && len(nodes) == 1 {
if path, _, _ := kioutil.GetFileAnnotations(nodes[0]); path != "" {
filename := filepath.Base(path)
for _, glob := range JSONMatch {
if match, _ := filepath.Match(glob, filename); match {
return true
}
}
}
}
return false
}

View File

@@ -26,7 +26,7 @@ func TestByteWriter(t *testing.T) {
testCases := []testCase{
//
//
// Test Case
//
{
name: "wrap_resource_list",
@@ -60,7 +60,7 @@ functionConfig:
},
//
//
// Test Case
//
{
name: "multiple_items",
@@ -91,6 +91,127 @@ a: b #first
`,
},
//
// Test Case
//
{
name: "handle_comments",
items: []string{
`# comment 0
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
namespace: my-space
labels:
env: dev
foo: bar
spec:
# comment 1
replicas: 3
selector:
# comment 2
matchLabels: # comment 3
# comment 4
app: nginx # comment 5
template:
metadata:
labels:
app: nginx
spec:
# comment 6
containers:
# comment 7
- name: nginx
image: nginx:1.14.2 # comment 8
ports:
# comment 9
- containerPort: 80 # comment 10
`,
`apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
# comment 1
- name: etcd-server-ssl
port: 2380
# comment 2
- name: etcd-client-ssl
port: 2379
`,
`apiVersion: constraints.gatekeeper.sh/v1beta1
kind: EnforceFoo
metadata:
name: enforce-foo
spec:
parameters:
naming_rules:
- kind: Folder
patterns:
# comment 1
- ^(dev|prod|staging|qa|shared)$
`,
},
expectedOutput: `# comment 0
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
namespace: my-space
labels:
env: dev
foo: bar
spec:
# comment 1
replicas: 3
selector:
# comment 2
matchLabels: # comment 3
# comment 4
app: nginx # comment 5
template:
metadata:
labels:
app: nginx
spec:
# comment 6
containers:
# comment 7
- name: nginx
image: nginx:1.14.2 # comment 8
ports:
# comment 9
- containerPort: 80 # comment 10
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
# comment 1
- name: etcd-server-ssl
port: 2380
# comment 2
- name: etcd-client-ssl
port: 2379
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: EnforceFoo
metadata:
name: enforce-foo
spec:
parameters:
naming_rules:
- kind: Folder
patterns:
# comment 1
- ^(dev|prod|staging|qa|shared)$
`,
},
//
// Test Case
//
@@ -187,6 +308,110 @@ c: d # second
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
`,
},
//
// Test Case
//
{
name: "encode_valid_json",
items: []string{
`{
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
metadata: {
annotations: {
config.kubernetes.io/path: test.json
}
}
}`,
},
expectedOutput: `{
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
}
}
}`,
},
//
// Test Case
//
{
name: "encode_unformatted_valid_json",
items: []string{
`{ "a": "b", metadata: { annotations: { config.kubernetes.io/path: test.json } } }`,
},
expectedOutput: `{
"a": "b",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
}
}
}`,
},
//
// Test Case
//
{
name: "encode_wrapped_json_as_yaml",
instance: ByteWriter{
Sort: true,
WrappingKind: ResourceListKind,
WrappingAPIVersion: ResourceListAPIVersion,
},
items: []string{
`{
"a": "b",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
}
}
}`,
},
expectedOutput: `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- {"a": "b", "metadata": {"annotations": {"config.kubernetes.io/path": "test.json"}}}
`,
},
//
// Test Case
//
{
name: "encode_multi_doc_json_as_yaml",
items: []string{
`{
"a": "b",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test-1.json"
}
}
}`,
`{
"c": "d",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test-2.json"
}
}
}`,
},
expectedOutput: `
{"a": "b", "metadata": {"annotations": {"config.kubernetes.io/path": "test-1.json"}}}
---
{"c": "d", "metadata": {"annotations": {"config.kubernetes.io/path": "test-2.json"}}}
`,
},
}

View File

@@ -420,8 +420,8 @@ spec:
image: nginx:1.7.9
# this is a container
ports:
- # this is a port
containerPort: 80
# this is a port
- containerPort: 80
`
s, err := FormatInput(strings.NewReader(y))
assert.NoError(t, err)
@@ -743,8 +743,8 @@ func TestFormatFileOrDirectory_ymlExtFile(t *testing.T) {
assert.Equal(t, string(testyaml.FormattedYaml1), string(b))
}
// TestFormatFileOrDirectory_skipYamlExtFileWithJson verifies that the json content is formatted
// as yaml
// TestFormatFileOrDirectory_YamlExtFileWithJson verifies that the JSON compatible flow style
// YAML content is formatted as such.
func TestFormatFileOrDirectory_YamlExtFileWithJson(t *testing.T) {
// write the unformatted JSON file contents
f, err := ioutil.TempFile("", "yamlfmt*.yaml")
@@ -760,7 +760,27 @@ func TestFormatFileOrDirectory_YamlExtFileWithJson(t *testing.T) {
// check the result is formatted as yaml
b, err := ioutil.ReadFile(f.Name())
assert.NoError(t, err)
assert.Equal(t, string(testyaml.FormattedJSON1), string(b))
assert.Equal(t, string(testyaml.FormattedFlowYAML1), string(b))
}
// TestFormatFileOrDirectory_JsonExtFileWithNotModified verifies that a file with .json extensions
// and JSON contents won't be modified.
func TestFormatFileOrDirectory_JsonExtFileWithNotModified(t *testing.T) {
// write the unformatted JSON file contents
f, err := ioutil.TempFile("", "yamlfmt*.json")
assert.NoError(t, err)
defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), testyaml.UnformattedJSON1, 0600)
assert.NoError(t, err)
// format the file
err = FormatFileOrDirectory(f.Name())
assert.NoError(t, err)
// check the result is formatted as yaml
b, err := ioutil.ReadFile(f.Name())
assert.NoError(t, err)
assert.Equal(t, string(testyaml.UnformattedJSON1), string(b))
}
// TestFormatFileOrDirectory_partialKubernetesYamlFile verifies that if a yaml file contains both
@@ -854,6 +874,7 @@ func TestFormatFileOrDirectory_skipJsonExtFile(t *testing.T) {
func TestFormatFileOrDirectory_directory(t *testing.T) {
d, err := ioutil.TempDir("", "yamlfmt")
assert.NoError(t, err)
defer os.RemoveAll(d)
err = os.Mkdir(filepath.Join(d, "config"), 0700)
assert.NoError(t, err)

View File

@@ -97,18 +97,9 @@ status2:
- 2
`)
var FormattedJSON1 = []byte(`{
"apiVersion": "example.com/v1beta1",
"kind": "MyType",
"spec": "a",
"status": {
"conditions": [
3,
1,
2
]
}
}
var FormattedFlowYAML1 = []byte(
`{"apiVersion": "example.com/v1beta1", "kind": "MyType", "spec": "a", "status": {"conditions": [
3, 1, 2]}}
`)
var FormattedYaml3 = []byte(`apiVersion: v1

View File

@@ -1110,6 +1110,7 @@ func TestCmd_Execute_setInput(t *testing.T) {
}
outDir, err := ioutil.TempDir("", "kustomize-test")
defer os.RemoveAll(outDir)
if !assert.NoError(t, err) {
t.FailNow()
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
@@ -1671,7 +1672,8 @@ func TestSetName(t *testing.T) {
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
rn.SetName("marge")
err := rn.SetName("marge")
require.NoError(t, err)
if expected, actual := "marge", rn.GetName(); expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
@@ -1682,12 +1684,15 @@ func TestSetNamespace(t *testing.T) {
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
rn.SetNamespace("flanders")
meta, _ := rn.GetMeta()
err := rn.SetNamespace("flanders")
require.NoError(t, err)
meta, err := rn.GetMeta()
require.NoError(t, err)
if expected, actual := "flanders", meta.Namespace; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetLabels(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {

View File

@@ -34,43 +34,13 @@ func DoSerializationHacks(node *yaml.Node) {
// https://github.com/go-yaml/yaml/issues/587 in go-yaml.v3
// Remove this hack when the issue has been resolved
if len(node.Content) > 0 && node.Content[0].Kind == ScalarNode {
node.HeadComment = node.Content[0].HeadComment
// Don't clobber the head comment if it's not empty.
if node.HeadComment == "" && node.Content[0].HeadComment != "" {
node.HeadComment = node.Content[0].HeadComment
}
node.Content[0].HeadComment = ""
}
}
}
}
func UndoSerializationHacksOnNodes(nodes []*RNode) {
for _, node := range nodes {
UndoSerializationHacks(node.YNode())
}
}
// UndoSerializationHacks reverts the changes made by DoSerializationHacks
// Refer to https://github.com/go-yaml/yaml/issues/587 for more details
func UndoSerializationHacks(node *yaml.Node) {
switch node.Kind {
case DocumentNode:
for _, node := range node.Content {
DoSerializationHacks(node)
}
case MappingNode:
for _, node := range node.Content {
DoSerializationHacks(node)
}
case SequenceNode:
for _, node := range node.Content {
// revert the changes made in DoSerializationHacks
// This is necessary to address serialization issue
// https://github.com/go-yaml/yaml/issues/587 in go-yaml.v3
// Remove this hack when the issue has been resolved
if len(node.Content) > 0 && node.Content[0].Kind == ScalarNode {
node.Content[0].HeadComment = node.HeadComment
node.HeadComment = ""
}
}
}
}

View File

@@ -213,7 +213,10 @@ func String(node *yaml.Node, opts ...string) (string, error) {
b := &bytes.Buffer{}
e := NewEncoder(b)
err := e.Encode(node)
e.Close()
errClose := e.Close()
if err == nil {
err = errClose
}
val := b.String()
if optsSet.Has(Trim) {
val = strings.TrimSpace(val)

View File

@@ -285,6 +285,9 @@ func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string {
// I've tried placing the flag before and after the name argument.
args = append(args, "--generate-name")
}
if p.IncludeCRDs {
args = append(args, "--include-crds")
}
return args
}

View File

@@ -2,6 +2,7 @@ package main_test
import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"
@@ -458,3 +459,92 @@ valuesMerge: replace
111, // memory
))
}
func TestHelmChartInflationGeneratorWithIncludeCRDs(t *testing.T) {
th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t).
PrepBuiltin("HelmChartInflationGenerator")
defer th.Reset()
if err := th.ErrIfNoHelm(); err != nil {
t.Skip("skipping: " + err.Error())
}
// we store this data outside of the _test.go file as its sort of huge
// and has backticks, which makes string literals wonky
testData, err := ioutil.ReadFile("include_crds_testdata.txt")
if err != nil {
t.Errorf("unable to read test data for includeCRDs: %w", err)
}
rm := th.LoadAndRunGenerator(`
apiVersion: builtin
kind: HelmChartInflationGenerator
metadata:
name: terraform
name: terraform
version: 1.0.0
repo: https://helm.releases.hashicorp.com
releaseName: terraforming-mars
includeCRDs: true
valuesInline:
global:
enabled: false
tests:
enabled: false
`)
th.AssertActualEqualsExpected(rm, string(testData))
}
func TestHelmChartInflationGeneratorWithExcludeCRDs(t *testing.T) {
th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t).
PrepBuiltin("HelmChartInflationGenerator")
defer th.Reset()
if err := th.ErrIfNoHelm(); err != nil {
t.Skip("skipping: " + err.Error())
}
// we choose this helm chart as it has the ability to turn
// everything off, except CRDs.
rm := th.LoadAndRunGenerator(`
apiVersion: builtin
kind: HelmChartInflationGenerator
metadata:
name: terraform
name: terraform
version: 1.0.0
repo: https://helm.releases.hashicorp.com
releaseName: terraforming-mars
includeCRDs: false
valuesInline:
global:
enabled: false
tests:
enabled: false
`)
th.AssertActualEqualsExpected(rm, "")
}
func TestHelmChartInflationGeneratorWithIncludeCRDsNotSpecified(t *testing.T) {
th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t).
PrepBuiltin("HelmChartInflationGenerator")
defer th.Reset()
if err := th.ErrIfNoHelm(); err != nil {
t.Skip("skipping: " + err.Error())
}
rm := th.LoadAndRunGenerator(`
apiVersion: builtin
kind: HelmChartInflationGenerator
metadata:
name: terraform
name: terraform
version: 1.0.0
repo: https://helm.releases.hashicorp.com
releaseName: terraforming-mars
valuesInline:
global:
enabled: false
tests:
enabled: false
`)
th.AssertActualEqualsExpected(rm, "")
}

View File

@@ -0,0 +1,262 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: workspaces.app.terraform.io
spec:
group: app.terraform.io
names:
kind: Workspace
listKind: WorkspaceList
plural: workspaces
singular: workspace
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: Workspace is the Schema for the workspaces API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: WorkspaceSpec defines the desired state of Workspace
properties:
agentPoolID:
description: Specifies the agent pool ID we wish to use.
type: string
module:
description: Module source and version to use
nullable: true
properties:
source:
description: Any remote module source (version control, registry)
type: string
version:
description: Module version for registry modules
type: string
required:
- source
type: object
organization:
description: Terraform Cloud organization
type: string
outputs:
description: Outputs denote outputs wanted
items:
description: OutputSpec specifies which values need to be output
properties:
key:
description: Output name
type: string
moduleOutputName:
description: Attribute name in module
type: string
type: object
type: array
secretsMountPath:
description: File path within operator pod to load workspace secrets
type: string
sshKeyID:
description: SSH Key ID. This key must already exist in the TF Cloud
organization. This can either be the user assigned name of the SSH
Key, or the system assigned ID.
type: string
terraformVersion:
description: Terraform version used for this workspace. The default
is `latest`.
type: string
variables:
description: Variables as inputs to module
items:
description: Variable denotes an input to the module
properties:
environmentVariable:
description: EnvironmentVariable denotes if this variable should
be created as environment variable
type: boolean
hcl:
description: String input should be an HCL-formatted variable
type: boolean
key:
description: Variable name
type: string
sensitive:
description: Variable is a secret and should be retrieved from
file
type: boolean
value:
description: Variable value
type: string
valueFrom:
description: Source for the variable's value. Cannot be used if
value is not empty.
properties:
configMapKeyRef:
description: Selects a key of a ConfigMap.
properties:
key:
description: The key to select.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the ConfigMap or its key
must be defined
type: boolean
required:
- key
type: object
fieldRef:
description: 'Selects a field of the pod: supports metadata.name,
metadata.namespace, metadata.labels, metadata.annotations,
spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP,
status.podIPs.'
properties:
apiVersion:
description: Version of the schema the FieldPath is written
in terms of, defaults to "v1".
type: string
fieldPath:
description: Path of the field to select in the specified
API version.
type: string
required:
- fieldPath
type: object
resourceFieldRef:
description: 'Selects a resource of the container: only resources
limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage,
requests.cpu, requests.memory and requests.ephemeral-storage)
are currently supported.'
properties:
containerName:
description: 'Container name: required for volumes, optional
for env vars'
type: string
divisor:
anyOf:
- type: integer
- type: string
description: Specifies the output format of the exposed
resources, defaults to "1"
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
resource:
description: 'Required: resource to select'
type: string
required:
- resource
type: object
secretKeyRef:
description: Selects a key of a secret in the pod's namespace
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
type: object
required:
- environmentVariable
- key
- sensitive
type: object
type: array
vcs:
description: Details of the VCS repository we want to connect to the
workspace
nullable: true
properties:
branch:
description: The repository branch to use
type: string
ingress_submodules:
description: Whether submodules should be fetched when cloning the
VCS repository (Defaults to false)
type: boolean
repo_identifier:
description: A reference to your VCS repository in the format org/repo
type: string
token_id:
description: Token ID of the VCS Connection (OAuth Connection Token)
to use https://www.terraform.io/docs/cloud/vcs
type: string
required:
- repo_identifier
- token_id
type: object
required:
- organization
- secretsMountPath
type: object
status:
description: WorkspaceStatus defines the observed state of Workspace
properties:
configVersionID:
description: Configuration Version ID
type: string
outputs:
description: Outputs from state file
items:
description: OutputStatus outputs the values of Terraform output
properties:
key:
description: Attribute name in module
type: string
value:
description: Value
type: string
type: object
type: array
runID:
description: Run ID
type: string
runStatus:
description: Run Status gets the run status
type: string
workspaceID:
description: Workspace ID
type: string
required:
- configVersionID
- runID
- runStatus
- workspaceID
type: object
type: object
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,33 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//go:generate pluginator
package main
import (
"sigs.k8s.io/kustomize/api/filters/iampolicygenerator"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)
type plugin struct {
types.IAMPolicyGeneratorArgs
}
//noinspection GoUnusedGlobalVariable
var KustomizePlugin plugin
func (p *plugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
p.IAMPolicyGeneratorArgs = types.IAMPolicyGeneratorArgs{}
err = yaml.Unmarshal(config, p)
return
}
func (p *plugin) Generate() (resmap.ResMap, error) {
r := resmap.New()
err := r.ApplyFilter(iampolicygenerator.Filter{
IAMPolicyGenerator: p.IAMPolicyGeneratorArgs,
})
return r, err
}

View File

@@ -0,0 +1,12 @@
module sigs.k8s.io/kustomize/plugin/builtin/configmapgenerator
go 1.16
require (
sigs.k8s.io/kustomize/api v0.8.9
sigs.k8s.io/yaml v1.2.0
)
replace sigs.k8s.io/kustomize/api => ../../../api
replace sigs.k8s.io/kustomize/kyaml => ../../../kyaml

View File

@@ -0,0 +1,228 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

Some files were not shown because too many files have changed in this diff Show More