diff --git a/go/.golangci.yml b/go/.golangci.yml new file mode 100644 index 000000000..3d6c5ff24 --- /dev/null +++ b/go/.golangci.yml @@ -0,0 +1,50 @@ +run: + deadline: 5m + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + # - funlen + - gochecknoinits + - goconst + # - gocritic + # - gocyclo + - gofmt + - goimports + - golint + - gosec + - gosimple + - govet + - ineffassign + - interfacer + - lll + - misspell + - nakedret + - scopelint + - staticcheck + - structcheck + # stylecheck demands that acronyms not be treated as words + # in camelCase, so JsonOp become JSONOp, etc. Yuck. + # - stylecheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace + +linters-settings: + dupl: + threshold: 400 + lll: + line-length: 170 + gocyclo: + min-complexity: 15 + golint: + min-confidence: 0.85 diff --git a/go/Makefile b/go/Makefile index 4d4a90ce4..6b99e6363 100644 --- a/go/Makefile +++ b/go/Makefile @@ -17,7 +17,7 @@ fmt: lint: (which $(GOPATH)/bin/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint) - $(GOPATH)/bin/golangci-lint run ./... + $(GOPATH)/bin/golangci-lint -c ./.golangci.yml run ./... test: go test -cover ./... diff --git a/go/doc.go b/go/doc.go new file mode 100644 index 000000000..e9b28613b --- /dev/null +++ b/go/doc.go @@ -0,0 +1,50 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +/* +Package krmfn.provides an SDK for writing KRM functions in Go. The function +specification is defined at: +https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md + +Note: this package is an krmfn.package. + +A KRM functions can generate, mutate or validate Kubernetes resources in a +ResourceList. + +The ResourceList type and the KubeObject type are the core part of this package. +The ResourceList type maps to the ResourceList in the function spec. The +KubeObject represent a kubernetes resource in a ResourceList, and it's the basic +unit to perform most CRUD operations. + +A KRM function does the following things: + + 1. read yaml bytes from stdin and convert it to a ResourceList + 2. perform mutation and validation on the resources in the ResourceList + 3. write the updated ResourceList out to stdout in yaml format + 4. Any diagnostic messages should be written to stderr + +ResourceListProcessor + +In most cases, you only need to do #2 which is implementing a +ResourceListProcessor and then pass it to AsMain. In the following example, we +use ResourceListProcessorFunc that implements the ResourceListProcessor +interface. + + func main() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(myfn)); err != nil { + os.Exit(1) + } + } + + func myfn(rl *krmfn.ResourceList) error { + krmfn.Log("log something") + // mutate or validate the ResourceList + } + +KubeObject + +KubeObject hides all the details about yaml.Node and yaml.RNode. It is always +recommended converting a KubeObject to a strong typed object or getting a field +as a strong typed object. Then do the CRUD operation on the strong typed objects. +*/ +package krmfn diff --git a/go/document.go b/go/document.go new file mode 100644 index 000000000..303087d99 --- /dev/null +++ b/go/document.go @@ -0,0 +1,57 @@ +package krmfn + +import ( + "bytes" + "io" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +type doc struct { + nodes []*yaml.Node +} + +func newDoc(nodes ...*yaml.Node) *doc { + return &doc{nodes: nodes} +} + +func parseDoc(b []byte) (*doc, error) { + br := bytes.NewReader(b) + + var nodes []*yaml.Node + decoder := yaml.NewDecoder(br) + for { + node := &yaml.Node{} + if err := decoder.Decode(node); err != nil { + if err == io.EOF { + break + } + return nil, err + } + nodes = append(nodes, node) + } + + return &doc{nodes: nodes}, nil +} + +func (d *doc) ToYAML() ([]byte, error) { + var w bytes.Buffer + encoder := yaml.NewEncoder(&w) + for _, node := range d.nodes { + if node.Kind == yaml.DocumentNode { + if len(node.Content) == 0 { + // These cause errors when we try to write them + continue + } + } + if err := encoder.Encode(node); err != nil { + return nil, err + } + } + + return w.Bytes(), nil +} + +func (d *doc) Objects() ([]*mapVariant, error) { + return extractObjects(d.nodes...) +} diff --git a/go/examples/example_filter_GVK_test.go b/go/examples/example_filter_GVK_test.go new file mode 100644 index 000000000..ad97baa62 --- /dev/null +++ b/go/examples/example_filter_GVK_test.go @@ -0,0 +1,30 @@ +package example_test + +import ( + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +// This example implements a function that updates the replicas field for all deployments. + +func Example_filterGVK() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(updateReplicas)); err != nil { + os.Exit(1) + } +} + +// updateReplicas sets a field in resources selecting by GVK. +func updateReplicas(rl *krmfn.ResourceList) error { + if rl.FunctionConfig == nil { + return krmfn.ErrMissingFnConfig{} + } + var replicas int + rl.FunctionConfig.GetOrDie(&replicas, "replicas") + for i, obj := range rl.Items { + if obj.APIVersion() == "apps/v1" && obj.Kind() == "Deployment" { + rl.Items[i].SetOrDie(replicas, "spec", "replicas") + } + } + return nil +} diff --git a/go/examples/example_generator_test.go b/go/examples/example_generator_test.go new file mode 100644 index 000000000..acdebf577 --- /dev/null +++ b/go/examples/example_generator_test.go @@ -0,0 +1,68 @@ +package example + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// This function generates Graphana configuration in the form of ConfigMap. It +// accepts Revision and ID as input. + +func Example_generator() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(generate)); err != nil { + os.Exit(1) + } +} + +// generate generates a ConfigMap. +func generate(rl *krmfn.ResourceList) error { + if rl.FunctionConfig == nil { + return krmfn.ErrMissingFnConfig{} + } + + revision := rl.FunctionConfig.GetStringOrDie("data", "revision") + id := rl.FunctionConfig.GetStringOrDie("data", "id") + js, err := fetchDashboard(revision, id) + if err != nil { + return fmt.Errorf("fetch dashboard: %v", err) + } + + cm := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%v-gen", rl.FunctionConfig.Name()), + Namespace: rl.FunctionConfig.Namespace(), + Labels: map[string]string{ + "grafana_dashboard": "true", + }, + }, + Data: map[string]string{ + fmt.Sprintf("%v.json", rl.FunctionConfig.Name()): fmt.Sprintf("%q", js), + }, + } + return rl.UpsertObjectToItems(cm, nil, false) +} + +func fetchDashboard(revision, id string) (string, error) { + url := fmt.Sprintf("https://grafana.com/api/dashboards/%s/revisions/%s/download", id, revision) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/go/examples/example_logger_injector_test.go b/go/examples/example_logger_injector_test.go new file mode 100644 index 000000000..401741663 --- /dev/null +++ b/go/examples/example_logger_injector_test.go @@ -0,0 +1,59 @@ +package example + +import ( + "os" + + corev1 "k8s.io/api/core/v1" + yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +// In this example, we implement a function that injects a logger as a sidecar +// container in workload APIs. + +func Example_loggeInjector() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(injectLogger)); err != nil { + os.Exit(1) + } +} + +// injectLogger injects a logger container into the workload API resources. +// generate implements the gokrmfn.KRMFunction interface. +func injectLogger(rl *krmfn.ResourceList) error { + var li LoggerInjection + if err := rl.FunctionConfig.As(&li); err != nil { + return err + } + for i, obj := range rl.Items { + if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") { + var containers []corev1.Container + obj.GetOrDie(&containers, "spec", "template", "spec", "containers") + foundTargetContainer := false + for j, container := range containers { + if container.Name == li.ContainerName { + containers[j].Image = li.ImageName + foundTargetContainer = true + break + } + } + if !foundTargetContainer { + c := corev1.Container{ + Name: li.ContainerName, + Image: li.ImageName, + } + containers = append(containers, c) + } + rl.Items[i].SetOrDie(containers, "spec", "template", "spec", "containers") + } + } + return nil +} + +// LoggerInjection is type definition of the functionConfig. +type LoggerInjection struct { + yaml2.ResourceMeta `json:",inline" yaml:",inline"` + + ContainerName string `json:"containerName" yaml:"containerName"` + ImageName string `json:"imageName" yaml:"imageName"` +} diff --git a/go/examples/example_logger_injector_test.go b/go/examples/example_logger_injector_test.go new file mode 100644 index 000000000..1932e9184 --- /dev/null +++ b/go/examples/example_logger_injector_test.go @@ -0,0 +1,59 @@ +package example + +import ( + "os" + + corev1 "k8s.io/api/core/v1" + yaml2 "github.com/GoogleContainerTools/kpt-functions-sdk/internal/forked/kyaml/yaml" + + "github.com/GoogleContainerTools/kpt-functions-sdk/alpha" +) + +// In this example, we implement a function that injects a logger as a sidecar +// container in workload APIs. + +func Example_loggeInjector() { + if err := alpha.AsMain(alpha.ResourceListProcessorFunc(injectLogger)); err != nil { + os.Exit(1) + } +} + +// injectLogger injects a logger container into the workload API resources. +// generate implements the goalpha.KRMFunction interface. +func injectLogger(rl *alpha.ResourceList) error { + var li LoggerInjection + if err := rl.FunctionConfig.As(&li); err != nil { + return err + } + for i, obj := range rl.Items { + if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") { + var containers []corev1.Container + obj.GetOrDie(&containers, "spec", "template", "spec", "containers") + foundTargetContainer := false + for j, container := range containers { + if container.Name == li.ContainerName { + containers[j].Image = li.ImageName + foundTargetContainer = true + break + } + } + if !foundTargetContainer { + c := corev1.Container{ + Name: li.ContainerName, + Image: li.ImageName, + } + containers = append(containers, c) + } + rl.Items[i].SetOrDie(containers, "spec", "template", "spec", "containers") + } + } + return nil +} + +// LoggerInjection is type definition of the functionConfig. +type LoggerInjection struct { + yaml2.ResourceMeta `json:",inline" yaml:",inline"` + + ContainerName string `json:"containerName" yaml:"containerName"` + ImageName string `json:"imageName" yaml:"imageName"` +} diff --git a/go/examples/example_mutate_comments_test.go b/go/examples/example_mutate_comments_test.go new file mode 100644 index 000000000..6afd83739 --- /dev/null +++ b/go/examples/example_mutate_comments_test.go @@ -0,0 +1,40 @@ +package example + +import ( + "os" + "strings" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +// In this example, we mutate line comments for field metadata.name. +// Some function may want to store some information in the comments (e.g. +// apply-setters function: https://catalog.kpt.dev/apply-setters/v0.2/) + +func Example_dMutateComments() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(mutateComments)); err != nil { + os.Exit(1) + } +} + +func mutateComments(rl *krmfn.ResourceList) error { + for i := range rl.Items { + lineComment, found, err := rl.Items[i].LineComment("metadata", "name") + if err != nil { + return err + } + if !found { + return nil + } + + if strings.TrimSpace(lineComment) == "" { + lineComment = "bar-system" + } else { + lineComment = strings.Replace(lineComment, "foo", "bar", -1) + } + if err = rl.Items[i].SetLineComment(lineComment, "metadata", "name"); err != nil { + return err + } + } + return nil +} diff --git a/go/examples/example_read_field_test.go b/go/examples/example_read_field_test.go new file mode 100644 index 000000000..298fbd882 --- /dev/null +++ b/go/examples/example_read_field_test.go @@ -0,0 +1,26 @@ +package example + +import ( + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +// In this example, we read a field from the input object and print it to the log. + +func Example_aReadField() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(readField)); err != nil { + os.Exit(1) + } +} + +func readField(rl *krmfn.ResourceList) error { + for _, obj := range rl.Items { + if obj.APIVersion() == "apps/v1" && obj.Kind() == "Deployment" { + var replicas int + obj.GetOrDie(&replicas, "spec", "replicas") + krmfn.Logf("replicas is %v\n", replicas) + } + } + return nil +} diff --git a/go/examples/example_read_functionConfig_test.go b/go/examples/example_read_functionConfig_test.go new file mode 100644 index 000000000..63bb54458 --- /dev/null +++ b/go/examples/example_read_functionConfig_test.go @@ -0,0 +1,30 @@ +package example + +import ( + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" + yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// In this example, we convert the functionConfig as strong typed object and then +// read a field from the functionConfig object. + +func Example_bReadFunctionConfig() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(readFunctionConfig)); err != nil { + os.Exit(1) + } +} + +func readFunctionConfig(rl *krmfn.ResourceList) error { + var sr SetReplicas + rl.FunctionConfig.AsOrDie(&sr) + krmfn.Logf("desired replicas is %v\n", sr.DesiredReplicas) + return nil +} + +// SetReplicas is the type definition of the functionConfig +type SetReplicas struct { + yaml2.ResourceIdentifier `json:",inline" yaml:",inline"` + DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"` +} diff --git a/go/examples/example_read_functionConfig_test.go b/go/examples/example_read_functionConfig_test.go new file mode 100644 index 000000000..901ee645a --- /dev/null +++ b/go/examples/example_read_functionConfig_test.go @@ -0,0 +1,30 @@ +package example + +import ( + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/alpha" + yaml2 "github.com/GoogleContainerTools/kpt-functions-sdk/internal/forked/kyaml/yaml" +) + +// In this example, we convert the functionConfig as strong typed object and then +// read a field from the functionConfig object. + +func Example_bReadFunctionConfig() { + if err := alpha.AsMain(alpha.ResourceListProcessorFunc(readFunctionConfig)); err != nil { + os.Exit(1) + } +} + +func readFunctionConfig(rl *alpha.ResourceList) error { + var sr SetReplicas + rl.FunctionConfig.AsOrDie(&sr) + alpha.Logf("desired replicas is %v\n", sr.DesiredReplicas) + return nil +} + +// SetReplicas is the type definition of the functionConfig +type SetReplicas struct { + yaml2.ResourceIdentifier `json:",inline" yaml:",inline"` + DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"` +} diff --git a/go/examples/example_set_field_test.go b/go/examples/example_set_field_test.go new file mode 100644 index 000000000..fa1b54a0a --- /dev/null +++ b/go/examples/example_set_field_test.go @@ -0,0 +1,25 @@ +package example + +import ( + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +// In this example, we read a field from the input object and print it to the log. + +func Example_cSetField() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(setField)); err != nil { + os.Exit(1) + } +} + +func setField(rl *krmfn.ResourceList) error { + for _, obj := range rl.Items { + if obj.APIVersion() == "apps/v1" && obj.Kind() == "Deployment" { + replicas := 10 + obj.SetOrDie(&replicas, "spec", "replicas") + } + } + return nil +} diff --git a/go/examples/example_test.go b/go/examples/example_test.go new file mode 100644 index 000000000..39faea479 --- /dev/null +++ b/go/examples/example_test.go @@ -0,0 +1,104 @@ +package example + +import ( + corev1 "k8s.io/api/core/v1" + yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +var ( + deployment krmfn.KubeObject + configMap krmfn.KubeObject +) + +func ExampleKubeObject_mutatePrimitiveField() { + replicas, found, err := deployment.GetInt("spec", "replicas") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the replicas variable + + err = deployment.Set(&replicas, "spec", "replicas") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutatePrimitiveSlice() { + var finalizers []string + found, err := deployment.Get(&finalizers, "metadata", "finalizers") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the finalizers slice + + err = deployment.Set(finalizers, "metadata", "finalizers") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutatePrimitiveMap() { + var data map[string]string + found, err := configMap.Get(&data, "data") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the data map + + err = deployment.Set(data, "data") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutateStrongTypedField() { + var podTemplate corev1.PodTemplate + found, err := configMap.Get(&podTemplate, "spec", "template") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the podTemplate object + + err = deployment.Set(podTemplate, "spec", "template") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutateStrongTypedSlice() { + var containers []corev1.Container + found, err := deployment.Get(&containers, "spec", "template", "spec", "containers") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the podTemplate object + + err = deployment.Set(containers, "spec", "template", "spec", "containers") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutateRNode() { + var rnode yaml2.RNode + // Get a field as RNode. This may be useful if you want to deal with low-level + // yaml manipulation (e.g. dealing with comments). + found, err := deployment.Get(&rnode, "metadata", "namespace") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // Any modification done on the rnode will be reflected on the original object. + // No need to invoke Set method when using RNode + ynode := rnode.YNode() + ynode.HeadComment = ynode.LineComment + ynode.LineComment = "" +} diff --git a/go/examples/example_test.go b/go/examples/example_test.go new file mode 100644 index 000000000..a6d3bdaf6 --- /dev/null +++ b/go/examples/example_test.go @@ -0,0 +1,104 @@ +package example + +import ( + corev1 "k8s.io/api/core/v1" + yaml2 "github.com/GoogleContainerTools/kpt-functions-sdk/internal/forked/kyaml/yaml" + + "github.com/GoogleContainerTools/kpt-functions-sdk/alpha" +) + +var ( + deployment alpha.KubeObject + configMap alpha.KubeObject +) + +func ExampleKubeObject_mutatePrimitiveField() { + replicas, found, err := deployment.GetInt("spec", "replicas") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the replicas variable + + err = deployment.Set(&replicas, "spec", "replicas") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutatePrimitiveSlice() { + var finalizers []string + found, err := deployment.Get(&finalizers, "metadata", "finalizers") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the finalizers slice + + err = deployment.Set(finalizers, "metadata", "finalizers") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutatePrimitiveMap() { + var data map[string]string + found, err := configMap.Get(&data, "data") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the data map + + err = deployment.Set(data, "data") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutateStrongTypedField() { + var podTemplate corev1.PodTemplate + found, err := configMap.Get(&podTemplate, "spec", "template") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the podTemplate object + + err = deployment.Set(podTemplate, "spec", "template") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutateStrongTypedSlice() { + var containers []corev1.Container + found, err := deployment.Get(&containers, "spec", "template", "spec", "containers") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // mutate the podTemplate object + + err = deployment.Set(containers, "spec", "template", "spec", "containers") + if err != nil { /* do something */ + } +} + +func ExampleKubeObject_mutateRNode() { + var rnode yaml2.RNode + // Get a field as RNode. This may be useful if you want to deal with low-level + // yaml manipulation (e.g. dealing with comments). + found, err := deployment.Get(&rnode, "metadata", "namespace") + if err != nil { /* do something */ + } + if !found { /* do something */ + } + + // Any modification done on the rnode will be reflected on the original object. + // No need to invoke Set method when using RNode + ynode := rnode.YNode() + ynode.HeadComment = ynode.LineComment + ynode.LineComment = "" +} diff --git a/go/examples/example_validator_test.go b/go/examples/example_validator_test.go new file mode 100644 index 000000000..e2ed61e99 --- /dev/null +++ b/go/examples/example_validator_test.go @@ -0,0 +1,30 @@ +package example + +import ( + "os" + + "github.com/GoogleContainerTools/kpt-functions-sdk/krmfn" +) + +// This example implements a function that validate resources to ensure +// spec.template.spec.securityContext.runAsNonRoot is set in workload APIs. + +func Example_validator() { + if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(validator)); err != nil { + os.Exit(1) + } +} + +func validator(rl *krmfn.ResourceList) error { + var results krmfn.Results + for _, obj := range rl.Items { + if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") { + var runAsNonRoot bool + obj.GetOrDie(&runAsNonRoot, "spec", "template", "spec", "securityContext", "runAsNonRoot") + if !runAsNonRoot { + results = append(results, krmfn.ConfigObjectResult("`spec.template.spec.securityContext.runAsNonRoot` must be set to true", obj, krmfn.Error)) + } + } + } + return results +} diff --git a/go/go.mod b/go/go.mod index b075d2243..6becabb26 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,5 +1,44 @@ -module github.com/GoogleContainerTools/kpt-functions-sdk/go +module github.com/GoogleContainerTools/kpt-functions-sdk/krmfn go 1.17 -require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +require ( + github.com/GoogleContainerTools/kpt-functions-sdk/go v0.0.0-20220301220754-6964a09d6cd2 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + k8s.io/api v0.23.4 + k8s.io/apimachinery v0.23.4 + sigs.k8s.io/kustomize/kyaml v0.13.3 +) + +require ( + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.3 // indirect + github.com/go-openapi/swag v0.19.5 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect + k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/go/go.sum b/go/go.sum index e387ff0b1..94625d006 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,4 +1,708 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleContainerTools/kpt-functions-sdk/go v0.0.0-20220301220754-6964a09d6cd2 h1:xAvEbj/KELmHntrHXkQVtYitM604wMGHXvI5+zQw22k= +github.com/GoogleContainerTools/kpt-functions-sdk/go v0.0.0-20220301220754-6964a09d6cd2/go.mod h1:lJYiqfBOl6AOiefK9kmkhinbffIysu+nnclOBwKEPlQ= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.1/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/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +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.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/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +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.3/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.3.0/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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E= +k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= +k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM= +k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf h1:M9XBsiMslw2lb2ZzglC0TOkBPK5NQi0/noUrdnoFwUg= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/kustomize/kyaml v0.13.3 h1:tNNQIC+8cc+aXFTVg+RtQAOsjwUdYBZRAgYOVI3RBc4= +sigs.k8s.io/kustomize/kyaml v0.13.3/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+JnwQ9Vhrc= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/go/log.go b/go/log.go new file mode 100644 index 000000000..664a36c07 --- /dev/null +++ b/go/log.go @@ -0,0 +1,14 @@ +package krmfn + +import ( + "fmt" + "os" +) + +func Log(in ...interface{}) { + fmt.Fprintln(os.Stderr, in...) +} + +func Logf(format string, in ...interface{}) { + fmt.Fprintf(os.Stderr, format, in...) +} diff --git a/go/map.go b/go/map.go new file mode 100644 index 000000000..54f0fe333 --- /dev/null +++ b/go/map.go @@ -0,0 +1,243 @@ +package krmfn + +import ( + "fmt" + "log" + "sort" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func newMap() *mapVariant { + var node = &yaml.Node{ + Kind: yaml.MappingNode, + } + return &mapVariant{node: node} +} + +func newStringMapVariant(m map[string]string) *mapVariant { + node := &yaml.Node{ + Kind: yaml.MappingNode, + } + for k, v := range m { + node.Content = append(node.Content, buildStringNode(k), buildStringNode(v)) + } + return &mapVariant{node: node} +} + +type mapVariant struct { + node *yaml.Node +} + +func (o *mapVariant) Kind() variantKind { + return variantKindMap +} + +func (o *mapVariant) Node() *yaml.Node { + return o.node +} + +func (o *mapVariant) Entries() (map[string]variant, error) { + entries := make(map[string]variant) + + ynode := o.node + children := ynode.Content + if len(children)%2 != 0 { + return nil, fmt.Errorf("unexpected number of children for map %d", len(children)) + } + + for i := 0; i < len(children); i += 2 { + keyNode := children[i] + valueNode := children[i+1] + + keyVariant := toVariant(keyNode) + valueVariant := toVariant(valueNode) + + switch keyVariant := keyVariant.(type) { + case *scalarVariant: + sv, isString := keyVariant.StringValue() + if isString { + entries[sv] = valueVariant + } else { + return nil, fmt.Errorf("key was not a string %v", keyVariant) + } + default: + return nil, fmt.Errorf("unexpected variant kind %T", keyVariant) + } + } + return entries, nil +} + +func asString(node *yaml.Node) (string, bool) { + if node.Kind == yaml.ScalarNode && (node.Tag == "!!str" || node.Tag == "") { + return node.Value, true + } + return "", false +} + +func (o *mapVariant) getVariant(key string) (variant, bool) { + valueNode, found := getValueNode(o.node, key) + if !found { + return nil, found + } + + v := toVariant(valueNode) + return v, true +} + +func getValueNode(m *yaml.Node, key string) (*yaml.Node, bool) { + children := m.Content + if len(children)%2 != 0 { + log.Fatalf("unexpected number of children for map %d", len(children)) + } + + for i := 0; i < len(children); i += 2 { + keyNode := children[i] + + k, ok := asString(keyNode) + if ok && k == key { + valueNode := children[i+1] + return valueNode, true + } + } + return nil, false +} + +func (o *mapVariant) set(key string, val variant) { + o.setYAMLNode(key, val.Node()) +} + +func (o *mapVariant) setYAMLNode(key string, node *yaml.Node) { + children := o.node.Content + if len(children)%2 != 0 { + log.Fatalf("unexpected number of children for map %d", len(children)) + } + + for i := 0; i < len(children); i += 2 { + keyNode := children[i] + + k, ok := asString(keyNode) + if ok && k == key { + // TODO: Copy comments? + oldNode := children[i+1] + children[i+1] = node + children[i+1].FootComment = oldNode.FootComment + children[i+1].HeadComment = oldNode.HeadComment + children[i+1].LineComment = oldNode.LineComment + return + } + } + + o.node.Content = append(o.node.Content, buildStringNode(key), node) +} + +func (o *mapVariant) remove(key string) (bool, error) { + removed := false + + children := o.node.Content + if len(children)%2 != 0 { + return false, fmt.Errorf("unexpected number of children for map %d", len(children)) + } + + var keep []*yaml.Node + for i := 0; i < len(children); i += 2 { + keyNode := children[i] + + k, ok := asString(keyNode) + if ok && k == key { + removed = true + continue + } + + keep = append(keep, children[i], children[i+1]) + } + + o.node.Content = keep + + return removed, nil +} + +// remove field metadata.creationTimestamp when it's null. +func (o *mapVariant) cleanupCreationTimestamp() { + if o.node.Kind != yaml.MappingNode { + return + } + scalar, found, err := o.GetNestedScalar("metadata", "creationTimestamp") + if err != nil || !found { + return + } + if scalar.IsNull() { + _, _ = o.RemoveNestedField("metadata", "creationTimestamp") + } +} + +// sortFields tried to sort fields that it understands. e.g. data should come +// after apiVersion, kind and metadata in corev1.ConfigMap. +func (o *mapVariant) sortFields() error { + return sortFields(o.node) +} + +func sortFields(ynode *yaml.Node) error { + pairs, err := ynodeToYamlKeyValuePairs(ynode) + if err != nil { + return fmt.Errorf("unable to sort fields in yaml: %w", err) + } + for _, pair := range pairs { + if err = sortFields(pair.value); err != nil { + return err + } + } + sort.Sort(pairs) + ynode.Content = yamlKeyValuePairsToYnode(pairs) + return nil +} + +func ynodeToYamlKeyValuePairs(ynode *yaml.Node) (yamlKeyValuePairs, error) { + if len(ynode.Content)%2 != 0 { + return nil, fmt.Errorf("invalid number of nodes: %d", len(ynode.Content)) + } + + var pairs yamlKeyValuePairs + for i := 0; i < len(ynode.Content); i += 2 { + pairs = append(pairs, &yamlKeyValuePair{name: ynode.Content[i], value: ynode.Content[i+1]}) + } + return pairs, nil +} + +func yamlKeyValuePairsToYnode(pairs yamlKeyValuePairs) []*yaml.Node { + var nodes []*yaml.Node + for _, pair := range pairs { + nodes = append(nodes, pair.name, pair.value) + } + return nodes +} + +type yamlKeyValuePair struct { + name *yaml.Node + value *yaml.Node +} + +type yamlKeyValuePairs []*yamlKeyValuePair + +func (nodes yamlKeyValuePairs) Len() int { return len(nodes) } + +func (nodes yamlKeyValuePairs) Less(i, j int) bool { + iIndex, iFound := yaml.FieldOrder[nodes[i].name.Value] + jIndex, jFound := yaml.FieldOrder[nodes[j].name.Value] + if iFound && jFound { + return iIndex < jIndex + } + if iFound { + return true + } + if jFound { + return false + } + + if nodes[i].name != nodes[j].name { + return nodes[i].name.Value < nodes[j].name.Value + } + return false +} + +func (nodes yamlKeyValuePairs) Swap(i, j int) { nodes[i], nodes[j] = nodes[j], nodes[i] } diff --git a/go/maphelper_test.go b/go/maphelper_test.go new file mode 100644 index 000000000..913c926e6 --- /dev/null +++ b/go/maphelper_test.go @@ -0,0 +1,177 @@ +package krmfn + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" +) + +const deploymentYaml = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx + env: prod + finalizers: + - foo + - bar +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +` + +func parseRaw(in []byte) ([]*mapVariant, error) { + d := yaml2.NewDecoder(bytes.NewBuffer(in)) + node := &yaml2.Node{} + err := d.Decode(node) + if err != nil { + return nil, err + } + return extractObjects(node) +} + +func TestHelpers(t *testing.T) { + rawmvs, _ := parseRaw([]byte(deploymentYaml)) + assert.Len(t, rawmvs, 1, "expect 1 object after parsing") + mv := rawmvs[0] + + name, found, err := mv.GetNestedString("metadata", "name") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, "nginx-deployment", name) + + ns, found, err := mv.GetNestedString("metadata", "namespace") + assert.NoError(t, err) + assert.False(t, found) + assert.Equal(t, "", ns) + err = mv.SetNestedString("test-ns", "metadata", "namespace") + assert.NoError(t, err) + ns, found, err = mv.GetNestedString("metadata", "namespace") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, "test-ns", ns) + + replicas, found, err := mv.GetNestedInt("spec", "replicas") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, 3, replicas) + err = mv.SetNestedInt(10, "spec", "replicas") + assert.NoError(t, err) + replicas, found, err = mv.GetNestedInt("spec", "replicas") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, 10, replicas) + + notExistInt, found, err := mv.GetNestedInt("spec", "foo") + assert.NoError(t, err) + assert.False(t, found) + assert.Equal(t, 0, notExistInt) + + labels, found, err := mv.GetNestedStringMap("metadata", "labels") + assert.NoError(t, err) + assert.True(t, found) + assert.Len(t, labels, 2) + assert.Equal(t, map[string]string{"app": "nginx", "env": "prod"}, labels) + + notExistStringMap, found, err := mv.GetNestedStringMap("metadata", "something") + assert.NoError(t, err) + assert.False(t, found) + assert.Len(t, notExistStringMap, 0) + var emptyMap map[string]string + assert.Equal(t, emptyMap, notExistStringMap) + + annotations, found, err := mv.GetNestedStringMap("metadata", "annotations") + assert.NoError(t, err) + assert.False(t, found) + assert.Len(t, annotations, 0) + assert.Equal(t, emptyMap, annotations) + err = mv.SetNestedStringMap(map[string]string{"hello": "world"}, "metadata", "annotations") + assert.NoError(t, err) + annotation, found, err := mv.GetNestedString("metadata", "annotations", "hello") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, "world", annotation) + + metadata, found, err := mv.GetNestedMap("metadata") + assert.NoError(t, err) + assert.True(t, found) + err = mv.SetNestedMap(metadata, "spec", "template", "metadata") + assert.NoError(t, err) + label, found, err := mv.GetNestedString("spec", "template", "metadata", "labels", "env") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, "prod", label) + + container := map[string]string{ + "name": "logger", + "image": "my-logger", + } + containers, found, err := mv.GetNestedSlice("spec", "template", "spec", "containers") + assert.NoError(t, err) + assert.True(t, found) + mvs, err := containers.Objects() + assert.NoError(t, err) + assert.Len(t, mvs, 1) + containers.Add(newStringMapVariant(container)) + containers, found, err = mv.GetNestedSlice("spec", "template", "spec", "containers") + assert.NoError(t, err) + assert.True(t, found) + mvs, err = containers.Objects() + assert.NoError(t, err) + assert.Len(t, mvs, 2) + + containers, found, err = mv.GetNestedSlice("spec", "template", "spec", "containers") + assert.NoError(t, err) + assert.True(t, found) + conts, err := containers.Objects() + assert.NoError(t, err) + for i, cont := range conts { + name, found, err = cont.GetNestedString("name") + if err == nil && found && name == "nginx" { + err = conts[i].SetNestedString("nginx:1.21.3", "image") + assert.NoError(t, err) + } + } + containers, found, err = mv.GetNestedSlice("spec", "template", "spec", "containers") + assert.NoError(t, err) + assert.True(t, found) + mvs, err = containers.Objects() + assert.NoError(t, err) + assert.Len(t, mvs, 2) + img, found, err := mvs[0].GetNestedString("image") + assert.NoError(t, err) + assert.True(t, found) + assert.Equal(t, "nginx:1.21.3", img) + + trueVar := true + falseVar := false + sc := corev1.SecurityContext{ + RunAsNonRoot: &trueVar, + AllowPrivilegeEscalation: &falseVar, + } + scmv, err := typedObjectToMapVariant(sc) + assert.NoError(t, err) + err = mv.SetNestedMap(scmv, "spec", "template", "spec", "securityContext") + assert.NoError(t, err) + + runAsNonRoot, found, err := mv.GetNestedBool("spec", "template", "spec", "securityContext", "runAsNonRoot") + assert.NoError(t, err) + assert.True(t, found) + assert.True(t, runAsNonRoot) +} diff --git a/go/maphelpers.go b/go/maphelpers.go new file mode 100644 index 000000000..b44d4af7e --- /dev/null +++ b/go/maphelpers.go @@ -0,0 +1,213 @@ +package krmfn + +import ( + "fmt" +) + +func (o *mapVariant) GetNestedValue(fields ...string) (variant, bool, error) { + current := o + n := len(fields) + for i := 0; i < n; i++ { + entry, found := current.getVariant(fields[i]) + if !found { + return nil, found, nil + } + + if i == n-1 { + return entry, true, nil + } + entryM, ok := entry.(*mapVariant) + if !ok { + return nil, found, fmt.Errorf("wrong type, got: %T", entry) + } + current = entryM + } + return nil, false, fmt.Errorf("unexpected code reached") +} + +func (o *mapVariant) SetNestedValue(val variant, fields ...string) error { + current := o + n := len(fields) + var err error + for i := 0; i < n; i++ { + if i == n-1 { + current.set(fields[i], val) + } else { + current, _, err = current.getMap(fields[i], true) + if err != nil { + return err + } + } + } + return nil +} + +func (o *mapVariant) GetNestedMap(fields ...string) (*mapVariant, bool, error) { + v, found, err := o.GetNestedValue(fields...) + if err != nil || !found { + return nil, found, err + } + mv, ok := v.(*mapVariant) + if !ok { + return nil, found, fmt.Errorf("wrong type, got: %T", v) + } + return mv, found, err +} + +func (o *mapVariant) SetNestedMap(m *mapVariant, fields ...string) error { + return o.SetNestedValue(m, fields...) +} + +func (o *mapVariant) GetNestedStringMap(fields ...string) (map[string]string, bool, error) { + v, found, err := o.GetNestedValue(fields...) + if err != nil || !found { + return nil, found, err + } + children := v.Node().Content + if len(children)%2 != 0 { + return nil, found, fmt.Errorf("invalid yaml map node") + } + m := make(map[string]string, len(children)/2) + for i := 0; i < len(children); i = i + 2 { + m[children[i].Value] = children[i+1].Value + } + return m, found, nil +} + +func (o *mapVariant) SetNestedStringMap(m map[string]string, fields ...string) error { + return o.SetNestedMap(newStringMapVariant(m), fields...) +} + +func (o *mapVariant) GetNestedScalar(fields ...string) (*scalarVariant, bool, error) { + node, found, err := o.GetNestedValue(fields...) + if err != nil || !found { + return nil, found, err + } + nodeS, ok := node.(*scalarVariant) + if !ok { + return nil, found, fmt.Errorf("incorrect type, was %T", node) + } + return nodeS, found, nil +} + +func (o *mapVariant) GetNestedString(fields ...string) (string, bool, error) { + scalar, found, err := o.GetNestedScalar(fields...) + if err != nil || !found { + return "", found, err + } + sv, isString := scalar.StringValue() + if isString { + return sv, found, nil + } + return "", found, fmt.Errorf("node was not a string, was %v", scalar.node.Tag) +} + +func (o *mapVariant) SetNestedString(s string, fields ...string) error { + return o.SetNestedValue(newStringScalarVariant(s), fields...) +} + +func (o *mapVariant) GetNestedBool(fields ...string) (bool, bool, error) { + scalar, found, err := o.GetNestedScalar(fields...) + if err != nil || !found { + return false, found, err + } + bv, isBool := scalar.BoolValue() + if isBool { + return bv, found, nil + } + return false, found, fmt.Errorf("node was not a bool, was %v", scalar.Node().Tag) +} + +func (o *mapVariant) SetNestedBool(b bool, fields ...string) error { + return o.SetNestedValue(newBoolScalarVariant(b), fields...) +} + +func (o *mapVariant) GetNestedInt(fields ...string) (int, bool, error) { + scalar, found, err := o.GetNestedScalar(fields...) + if err != nil || !found { + return 0, found, err + } + iv, isInt := scalar.IntValue() + if isInt { + return iv, found, nil + } + return 0, found, fmt.Errorf("node was not a int, was %v", scalar.node.Tag) +} + +func (o *mapVariant) SetNestedInt(i int, fields ...string) error { + return o.SetNestedValue(newIntScalarVariant(i), fields...) +} + +func (o *mapVariant) GetNestedFloat(fields ...string) (float64, bool, error) { + scalar, found, err := o.GetNestedScalar(fields...) + if err != nil || !found { + return 0, found, err + } + fv, isFloat := scalar.FloatValue() + if isFloat { + return fv, found, nil + } + return 0, found, fmt.Errorf("node was not a float, was %v", scalar.node.Tag) +} + +func (o *mapVariant) SetNestedFloat(f float64, fields ...string) error { + return o.SetNestedValue(newFloatScalarVariant(f), fields...) +} + +func (o *mapVariant) GetNestedSlice(fields ...string) (*sliceVariant, bool, error) { + node, found, err := o.GetNestedValue(fields...) + if err != nil || !found { + return nil, found, err + } + nodeS, ok := node.(*sliceVariant) + if !ok { + return nil, found, fmt.Errorf("incorrect type, was %T", node) + } + return nodeS, found, err +} + +func (o *mapVariant) SetNestedSlice(s *sliceVariant, fields ...string) error { + return o.SetNestedValue(s, fields...) +} + +func (o *mapVariant) RemoveNestedField(fields ...string) (bool, error) { + current := o + n := len(fields) + for i := 0; i < n; i++ { + entry, found := current.getVariant(fields[i]) + if !found { + return false, nil + } + + if i == n-1 { + return current.remove(fields[i]) + } + switch entry := entry.(type) { + case *mapVariant: + current = entry + default: + return false, fmt.Errorf("value is of unexpected type %T", entry) + } + } + return false, fmt.Errorf("unexpected code reached") +} + +func (o *mapVariant) getMap(field string, create bool) (*mapVariant, bool, error) { + node, found := o.getVariant(field) + + if !found { + if !create { + return nil, found, nil + } + keyNode := buildStringNode(field) + valueNode := buildMappingNode() + o.node.Content = append(o.node.Content, keyNode, valueNode) + valueVariant := &mapVariant{node: valueNode} + return valueVariant, found, nil + } + + if node, ok := node.(*mapVariant); ok { + return node, found, nil + } + return nil, found, fmt.Errorf("incorrect type, was %T", node) +} diff --git a/go/object.go b/go/object.go new file mode 100644 index 000000000..d237e9bdc --- /dev/null +++ b/go/object.go @@ -0,0 +1,528 @@ +package krmfn + +import ( + "fmt" + "reflect" + "strconv" + + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// KubeObject presents a k8s object. +type KubeObject struct { + obj *mapVariant +} + +func NewFromRNode(rn *yaml.RNode) *KubeObject { + return &KubeObject{obj: &mapVariant{rn.YNode()}} +} + +func (o *KubeObject) ToRNode() *yaml.RNode { + return yaml.NewRNode(o.obj.node) +} + +func ParseKubeObject(in []byte) (*KubeObject, error) { + doc, err := parseDoc(in) + if err != nil { + return nil, fmt.Errorf("failed to parse input bytes: %w", err) + } + objects, err := doc.Objects() + if err != nil { + return nil, fmt.Errorf("failed to extract objects: %w", err) + } + if len(objects) != 1 { + return nil, fmt.Errorf("expected exactly one object, got %d", len(objects)) + } + rlMap := objects[0] + return asKubeObject(rlMap), nil +} + +func asKubeObject(obj *mapVariant) *KubeObject { + return &KubeObject{obj} +} + +func (o *KubeObject) node() *mapVariant { + return o.obj +} + +// GetOrDie gets the value for a nested field located by fields. A pointer must +// be passed in, and the value will be stored in ptr. If the field doesn't +// exist, the ptr will be set to nil. It will panic if it encounters any error. +func (o *KubeObject) GetOrDie(ptr interface{}, fields ...string) { + _, err := o.Get(ptr, fields...) + if err != nil { + panic(err) + } +} + +// GetString returns the string value, if the field exist and a potential error. +func (o *KubeObject) GetString(fields ...string) (string, bool, error) { + var val string + found, err := o.Get(&val, fields...) + return val, found, err +} + +// GetStringOrDie returns the string value at fields. An empty string will be +// returned if the field is not found. It will panic if encountering any errors. +func (o *KubeObject) GetStringOrDie(fields ...string) string { + val, _, err := o.GetString(fields...) + if err != nil { + panic(err) + } + return val +} + +// GetInt returns the string value, if the field exist and a potential error. +func (o *KubeObject) GetInt(fields ...string) (int, bool, error) { + var val int + found, err := o.Get(&val, fields...) + return val, found, err +} + +// GetIntOrDie returns the string value at fields. An empty string will be +// returned if the field is not found. It will panic if encountering any errors. +func (o *KubeObject) GetIntOrDie(fields ...string) int { + val, _, err := o.GetInt(fields...) + if err != nil { + panic(err) + } + return val +} + +// Get gets the value for a nested field located by fields. A pointer must be +// passed in, and the value will be stored in ptr. ptr can be a concrete type +// (e.g. string, []corev1.Container, []string, corev1.Pod, map[string]string) or +// a yaml.RNode. yaml.RNode should be used if you are dealing with comments that +// is more than what LineComment, HeadComment, SetLineComment and +// SetHeadComment can handle. It returns if the field is found and a +// potential error. +func (o *KubeObject) Get(ptr interface{}, fields ...string) (bool, error) { + found, err := func() (bool, error) { + if o == nil { + return false, fmt.Errorf("the object doesn't exist") + } + if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr { + return false, fmt.Errorf("ptr must be a pointer to an object") + } + + if rn, ok := ptr.(*yaml.RNode); ok { + val, found, err := o.obj.GetNestedValue(fields...) + if err != nil || !found { + return found, err + } + rn.SetYNode(val.Node()) + return found, err + } + + switch k := reflect.TypeOf(ptr).Elem().Kind(); k { + case reflect.Struct, reflect.Map: + m, found, err := o.obj.GetNestedMap(fields...) + if err != nil || !found { + return found, err + } + err = m.Node().Decode(ptr) + return found, err + case reflect.Slice: + s, found, err := o.obj.GetNestedSlice(fields...) + if err != nil || !found { + return found, err + } + err = s.Node().Decode(ptr) + return found, err + case reflect.String: + s, found, err := o.obj.GetNestedString(fields...) + if err != nil || !found { + return found, err + } + *(ptr.(*string)) = s + return found, nil + case reflect.Int, reflect.Int64: + i, found, err := o.obj.GetNestedInt(fields...) + if err != nil || !found { + return found, err + } + if k == reflect.Int { + *(ptr.(*int)) = i + } else if k == reflect.Int64 { + *(ptr.(*int64)) = int64(i) + } + return found, nil + case reflect.Float64: + f, found, err := o.obj.GetNestedFloat(fields...) + if err != nil || !found { + return found, err + } + *(ptr.(*float64)) = f + return found, nil + case reflect.Bool: + b, found, err := o.obj.GetNestedBool(fields...) + if err != nil || !found { + return found, err + } + *(ptr.(*bool)) = b + return found, nil + default: + return false, fmt.Errorf("unhandled kind %s", k) + } + }() + if err != nil { + return found, fmt.Errorf("unable to get fields %v as %T with error: %w", fields, ptr, err) + } + return found, nil +} + +// LineComment returns the line comment, if the target field exist and a +// potential error. +func (o *KubeObject) LineComment(fields ...string) (string, bool, error) { + rn := &yaml.RNode{} + found, err := o.Get(rn, fields...) + if !found || err != nil { + return "", found, err + } + return rn.YNode().LineComment, true, nil +} + +// HeadComment returns the head comment, if the target field exist and a +// potential error. +func (o *KubeObject) HeadComment(fields ...string) (string, bool, error) { + rn := &yaml.RNode{} + found, err := o.Get(rn, fields...) + if !found || err != nil { + return "", found, err + } + return rn.YNode().HeadComment, true, nil +} + +// SetOrDie sets a nested field located by fields to the value provided as val. +// It will panic if it encounters any error. +func (o *KubeObject) SetOrDie(val interface{}, fields ...string) { + if err := o.Set(val, fields...); err != nil { + panic(err) + } +} + +// Set sets a nested field located by fields to the value provided as val. val +// should not be a yaml.RNode. If you want to deal with yaml.RNode, you should +// use Get method and modify the underlying yaml.Node. +func (o *KubeObject) Set(val interface{}, fields ...string) error { + err := func() error { + if o == nil { + return fmt.Errorf("the object doesn't exist") + } + if val == nil { + return fmt.Errorf("the passed-in object must not be nil") + } + kind := reflect.ValueOf(val).Kind() + if kind == reflect.Ptr { + kind = reflect.TypeOf(val).Elem().Kind() + } + + switch kind { + case reflect.Struct, reflect.Map: + m, err := typedObjectToMapVariant(val) + if err != nil { + return err + } + return o.obj.SetNestedMap(m, fields...) + case reflect.Slice: + s, err := typedObjectToSliceVariant(val) + if err != nil { + return err + } + return o.obj.SetNestedSlice(s, fields...) + case reflect.String: + var s string + switch val := val.(type) { + case string: + s = val + case *string: + s = *val + } + return o.obj.SetNestedString(s, fields...) + case reflect.Int, reflect.Int64: + var i int + switch val := val.(type) { + case int: + i = val + case *int: + i = *val + case int64: + i = int(val) + case *int64: + i = int(*val) + } + return o.obj.SetNestedInt(i, fields...) + case reflect.Float64: + var f float64 + switch val := val.(type) { + case float64: + f = val + case *float64: + f = *val + } + return o.obj.SetNestedFloat(f, fields...) + case reflect.Bool: + var b bool + switch val := val.(type) { + case bool: + b = val + case *bool: + b = *val + } + return o.obj.SetNestedBool(b, fields...) + default: + return fmt.Errorf("unhandled kind %s", kind) + } + }() + if err != nil { + return fmt.Errorf("unable to set %v at fields %v with error: %w", val, fields, err) + } + return nil +} + +func (o *KubeObject) SetLineComment(comment string, fields ...string) error { + rn := &yaml.RNode{} + found, err := o.Get(rn, fields...) + if err != nil { + return err + } + if !found { + return fmt.Errorf("can't set line comment because the field doesn't exist") + } + rn.YNode().LineComment = comment + return nil +} + +func (o *KubeObject) SetHeadComment(comment string, fields ...string) error { + rn := &yaml.RNode{} + found, err := o.Get(rn, fields...) + if err != nil { + return err + } + if !found { + return fmt.Errorf("can't set head comment because the field doesn't exist") + } + rn.YNode().HeadComment = comment + return nil +} + +// RemoveOrDie removes the field located by fields if found. It will panic if it +// encounters any error. +func (o *KubeObject) RemoveOrDie(fields ...string) { + if _, err := o.Remove(fields...); err != nil { + panic(err) + } +} + +// Remove removes the field located by fields if found. It returns if the field +// is found and a potential error. +func (o *KubeObject) Remove(fields ...string) (bool, error) { + found, err := func() (bool, error) { + if o == nil { + return false, fmt.Errorf("the object doesn't exist") + } + return o.obj.RemoveNestedField(fields...) + }() + if err != nil { + return found, fmt.Errorf("unable to remove fields %v with error: %w", fields, err) + } + return found, nil +} + +// AsOrDie converts a KubeObject to the desired typed object. ptr must +// be a pointer to a typed object. It will panic if it encounters an error. +func (o *KubeObject) AsOrDie(ptr interface{}) { + if err := o.As(ptr); err != nil { + panic(err) + } +} + +// As converts a KubeObject to the desired typed object. ptr must be +// a pointer to a typed object. +func (o *KubeObject) As(ptr interface{}) error { + err := func() error { + if o == nil { + return fmt.Errorf("the object doesn't exist") + } + if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr { + return fmt.Errorf("ptr must be a pointer to an object") + } + return mapVariantToTypedObject(o.obj, ptr) + }() + if err != nil { + return fmt.Errorf("unable to convert object to %T with error: %w", ptr, err) + } + return nil +} + +// NewFromTypedObject construct a KubeObject from a typed object (e.g. corev1.Pod) +func NewFromTypedObject(v interface{}) (*KubeObject, error) { + m, err := typedObjectToMapVariant(v) + if err != nil { + return nil, err + } + return asKubeObject(m), nil +} + +// String serializes the object in yaml format. +func (o *KubeObject) String() string { + doc := newDoc([]*yaml.Node{o.obj.Node()}...) + s, _ := doc.ToYAML() + return string(s) +} + +// ResourceIdentifier returns the resource identifier including apiVersion, kind, +// namespace and name. +func (o *KubeObject) ResourceIdentifier() *yaml.ResourceIdentifier { + apiVersion := o.APIVersion() + kind := o.Kind() + name := o.Name() + ns := o.Namespace() + return &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: apiVersion, + Kind: kind, + }, + NameMeta: yaml.NameMeta{ + Name: name, + Namespace: ns, + }, + } +} + +func (o *KubeObject) APIVersion() string { + apiVersion, _, _ := o.obj.GetNestedString("apiVersion") + return apiVersion +} + +func (o *KubeObject) SetAPIVersion(apiVersion string) { + if err := o.obj.SetNestedString(apiVersion, "apiVersion"); err != nil { + panic(fmt.Errorf("cannot set apiVersion '%v': %v", apiVersion, err)) + } +} + +func (o *KubeObject) Kind() string { + kind, _, _ := o.obj.GetNestedString("kind") + return kind +} + +func (o *KubeObject) SetKind(kind string) { + if err := o.obj.SetNestedString(kind, "kind"); err != nil { + panic(fmt.Errorf("cannot set kind '%v': %v", kind, err)) + } +} + +func (o *KubeObject) Name() string { + s, _, _ := o.obj.GetNestedString("metadata", "name") + return s +} + +func (o *KubeObject) SetName(name string) { + if err := o.obj.SetNestedString("metadata", "name", name); err != nil { + panic(fmt.Errorf("cannot set metadata name '%v': %v", name, err)) + } +} + +func (o *KubeObject) Namespace() string { + s, _, _ := o.obj.GetNestedString("metadata", "namespace") + return s +} + +func (o *KubeObject) HasNamespace() bool { + _, found, _ := o.obj.GetNestedString("metadata", "namespace") + return found +} + +func (o *KubeObject) SetNamespace(name string) { + if err := o.obj.SetNestedString("namespace", name); err != nil { + panic(fmt.Errorf("cannot set namespace '%v': %v", name, err)) + } +} + +func (o *KubeObject) SetAnnotation(k, v string) { + if err := o.obj.SetNestedString(v, "metadata", "annotations", k); err != nil { + panic(fmt.Errorf("cannot set metadata annotations '%v': %v", k, err)) + } +} + +// Annotations returns all annotations. +func (o *KubeObject) Annotations() map[string]string { + v, _, _ := o.obj.GetNestedStringMap("metadata", "annotations") + return v +} + +// Annotation returns one annotation with key k. +func (o *KubeObject) Annotation(k string) string { + v, _, _ := o.obj.GetNestedString("metadata", "annotations", k) + return v +} + +// RemoveAnnotationsIfEmpty removes the annotations field when it has zero annotations. +func (o *KubeObject) RemoveAnnotationsIfEmpty() error { + annotations, found, err := o.obj.GetNestedStringMap("metadata", "annotations") + if err != nil { + return err + } + if found && len(annotations) == 0 { + _, err = o.obj.RemoveNestedField("metadata", "annotations") + return err + } + return nil +} + +func (o *KubeObject) SetLabel(k, v string) { + if err := o.obj.SetNestedString(v, "metadata", "labels", k); err != nil { + panic(fmt.Errorf("cannot set metadata labels '%v': %v", k, err)) + } +} + +// Label returns one label with key k. +func (o *KubeObject) Label(k string) string { + v, _, _ := o.obj.GetNestedString("metadata", "labels", k) + return v +} + +// Labels returns all labels. +func (o *KubeObject) Labels() map[string]string { + v, _, _ := o.obj.GetNestedStringMap("metadata", "labels") + return v +} + +func (o *KubeObject) PathAnnotation() string { + anno := o.Annotation(kioutil.PathAnnotation) + return anno +} + +// IndexAnnotation return -1 if not found. +func (o *KubeObject) IndexAnnotation() int { + anno := o.Annotation(kioutil.IndexAnnotation) + if anno == "" { + return -1 + } + i, _ := strconv.Atoi(anno) + return i +} + +// IdAnnotation return -1 if not found. +func (o *KubeObject) IdAnnotation() int { + anno := o.Annotation(kioutil.IdAnnotation) + + if anno == "" { + return -1 + } + i, _ := strconv.Atoi(anno) + return i +} + +type kubeObjects []*KubeObject + +func (o kubeObjects) Len() int { return len(o) } +func (o kubeObjects) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o kubeObjects) Less(i, j int) bool { + idi := o[i].ResourceIdentifier() + idj := o[j].ResourceIdentifier() + idStrI := fmt.Sprintf("%s %s %s %s", idi.GetAPIVersion(), idi.GetKind(), idi.GetNamespace(), idi.GetName()) + idStrJ := fmt.Sprintf("%s %s %s %s", idj.GetAPIVersion(), idj.GetKind(), idj.GetNamespace(), idj.GetName()) + return idStrI < idStrJ +} diff --git a/go/resourcelist.go b/go/resourcelist.go new file mode 100644 index 000000000..842298ae8 --- /dev/null +++ b/go/resourcelist.go @@ -0,0 +1,273 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package krmfn + +import ( + "fmt" + "reflect" + "sort" + + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// TODO(mengqiy): we want eventually reconcile the following with the ResourceList +// definition in kyaml/fn/fnsdk. + +// ResourceList is a Kubernetes list type used as the primary data interchange format +// in the Configuration Functions Specification: +// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md +// This framework facilitates building functions that receive and emit ResourceLists, +// as required by the specification. +type ResourceList struct { + // Items is the ResourceList.items input and output value. + // + // e.g. given the function input: + // + // kind: ResourceList + // items: + // - kind: Deployment + // ... + // - kind: Service + // ... + // + // Items will be a slice containing the Deployment and Service resources + // Mutating functions will alter this field during processing. + // This field is required. + Items []*KubeObject `yaml:"items" json:"items"` + + // FunctionConfig is the ResourceList.functionConfig input value. + // + // e.g. given the input: + // + // kind: ResourceList + // functionConfig: + // kind: Example + // spec: + // foo: var + // + // FunctionConfig will contain the RNodes for the Example: + // kind: Example + // spec: + // foo: var + FunctionConfig *KubeObject `yaml:"functionConfig,omitempty" json:"functionConfig,omitempty"` + + // Results is ResourceList.results output value. + // Validating functions can optionally use this field to communicate structured + // validation error data to downstream functions. + Results Results `yaml:"results,omitempty" json:"results,omitempty"` +} + +// ParseResourceList parses a ResourceList from the input byte array. +func ParseResourceList(in []byte) (*ResourceList, error) { + rl := &ResourceList{} + rlObj, err := ParseKubeObject(in) + if err != nil { + return nil, fmt.Errorf("failed to parse input bytes: %w", err) + } + if rlObj.Kind() != kio.ResourceListKind { + return nil, fmt.Errorf("input was of unexpected kind %q; expected ResourceList", rlObj.Kind()) + } + fc, found, err := rlObj.obj.GetNestedMap("functionConfig") + if err != nil { + return nil, fmt.Errorf("failed when tried to get functionConfig: %w", err) + } + if found { + rl.FunctionConfig = asKubeObject(fc) + } + + items, _, err := rlObj.obj.GetNestedSlice("items") + if err != nil { + return nil, fmt.Errorf("failed when tried to get items: %w", err) + } + objectItems, err := items.Objects() + if err != nil { + return nil, fmt.Errorf("failed extract objects from items: %w", err) + } + for i := range objectItems { + rl.Items = append(rl.Items, asKubeObject(objectItems[i])) + } + + return rl, nil +} + +// toYNode converts the ResourceList to the yaml.Node representation. +func (rl *ResourceList) toYNode() (*yaml.Node, error) { + reMap := newMap() + reObj := asKubeObject(reMap) + reObj.SetAPIVersion(kio.ResourceListAPIVersion) + reObj.SetKind(kio.ResourceListKind) + + if rl.Items != nil && len(rl.Items) > 0 { + itemsSlice := newSliceVariant() + for i := range rl.Items { + itemsSlice.Add(rl.Items[i].node()) + } + if err := reMap.SetNestedSlice(itemsSlice, "items"); err != nil { + return nil, err + } + } + + if rl.FunctionConfig != nil { + if err := reMap.SetNestedMap(rl.FunctionConfig.node(), "functionConfig"); err != nil { + return nil, err + } + } + + if rl.Results != nil && len(rl.Results) > 0 { + resultsSlice := newSliceVariant() + for _, result := range rl.Results { + mv, err := typedObjectToMapVariant(result) + if err != nil { + return nil, err + } + resultsSlice.Add(mv) + } + if err := reMap.SetNestedSlice(resultsSlice, "results"); err != nil { + return nil, err + } + } + + return reMap.Node(), nil +} + +// ToYAML converts the ResourceList to yaml. +func (rl *ResourceList) ToYAML() ([]byte, error) { + // Sort the resources first. + rl.Sort() + ynode, err := rl.toYNode() + if err != nil { + return nil, err + } + doc := newDoc([]*yaml.Node{ynode}...) + return doc.ToYAML() +} + +// Sort sorts the ResourceList.items by apiVersion, kind, namespace and name. +func (rl *ResourceList) Sort() { + sort.Sort(kubeObjects(rl.Items)) +} + +// UpsertObjectToItems adds an object to ResourceList.items. The input object can +// be a KubeObject or any typed object (e.g. corev1.Pod). +func (rl *ResourceList) UpsertObjectToItems(obj interface{}, checkExistence func(obj, another *KubeObject) bool, replaceIfAlreadyExist bool) error { + if checkExistence == nil { + checkExistence = func(obj, another *KubeObject) bool { + ri1 := obj.ResourceIdentifier() + ri2 := another.ResourceIdentifier() + return reflect.DeepEqual(ri1, ri2) + } + } + + var ko *KubeObject + switch obj := obj.(type) { + case KubeObject: + ko = &obj + case *KubeObject: + ko = obj + case yaml.RNode: + ko = NewFromRNode(&obj) + case *yaml.RNode: + ko = NewFromRNode(obj) + case yaml.Node: + ko = NewFromRNode(yaml.NewRNode(&obj)) + case *yaml.Node: + ko = NewFromRNode(yaml.NewRNode(obj)) + default: + var err error + ko, err = NewFromTypedObject(obj) + if err != nil { + return err + } + } + + idx := -1 + for i, item := range rl.Items { + if checkExistence(ko, item) { + idx = i + break + } + } + if idx == -1 { + rl.Items = append(rl.Items, ko) + } else if replaceIfAlreadyExist { + rl.Items[idx] = ko + } + return nil +} + +// ResourceListProcessor is implemented by configuration functions built with this framework +// to conform to the Configuration Functions Specification: +// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md +type ResourceListProcessor interface { + Process(rl *ResourceList) error +} + +// ResourceListProcessorFunc converts a compatible function to a ResourceListProcessor. +type ResourceListProcessorFunc func(rl *ResourceList) error + +func (p ResourceListProcessorFunc) Process(rl *ResourceList) error { + return p(rl) +} + +// Chain chains a list of ResourceListProcessor as a single ResourceListProcessor. +func Chain(processors ...ResourceListProcessor) ResourceListProcessor { + return ResourceListProcessorFunc(func(rl *ResourceList) error { + for _, processor := range processors { + if err := processor.Process(rl); err != nil { + return err + } + } + return nil + }) +} + +// ChainFunctions chains a list of ResourceListProcessorFunc as a single +// ResourceListProcessorFunc. +func ChainFunctions(functions ...ResourceListProcessorFunc) ResourceListProcessorFunc { + return func(rl *ResourceList) error { + for _, fn := range functions { + if err := fn(rl); err != nil { + return err + } + } + return nil + } +} + +// ApplyFnBySelector iterates through every object in ResourceList.items, and if +// it satisfies the selector, fn will be applied on it. +func ApplyFnBySelector(rl *ResourceList, selector func(obj *KubeObject) bool, fn func(obj *KubeObject) error) error { + var results Results + for i, obj := range rl.Items { + if !selector(obj) { + continue + } + + err := fn(rl.Items[i]) + if err == nil { + continue + } + + switch te := err.(type) { + case Results: + results = append(results, te...) + case *Result: + results = append(results, te) + default: + results = append(results, ErrorResult(err)) + } + } + if len(results) > 0 { + rl.Results = results + return results + } + return nil +} + +type ErrMissingFnConfig struct{} + +func (ErrMissingFnConfig) Error() string { + return "unable to find the functionConfig in the resourceList" +} diff --git a/go/result.go b/go/result.go new file mode 100644 index 000000000..5d8880b92 --- /dev/null +++ b/go/result.go @@ -0,0 +1,240 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package krmfn + +import ( + "fmt" + "sort" + "strings" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// Severity indicates the severity of the Result +type Severity string + +const ( + // Error indicates the result is an error. Will cause the function to exit non-0. + Error Severity = "error" + // Warning indicates the result is a warning + Warning Severity = "warning" + // Info indicates the result is an informative message + Info Severity = "info" +) + +// Result defines a validation result +type Result struct { + // Message is a human readable message. This field is required. + Message string `yaml:"message,omitempty" json:"message,omitempty"` + + // Severity is the severity of this result + Severity Severity `yaml:"severity,omitempty" json:"severity,omitempty"` + + // ResourceRef is a reference to a resource. + // Required fields: apiVersion, kind, name. + ResourceRef *yaml.ResourceIdentifier `yaml:"resourceRef,omitempty" json:"resourceRef,omitempty"` + + // Field is a reference to the field in a resource this result refers to + Field *Field `yaml:"field,omitempty" json:"field,omitempty"` + + // File references a file containing the resource this result refers to + File *File `yaml:"file,omitempty" json:"file,omitempty"` + + // Tags is an unstructured key value map stored with a result that may be set + // by external tools to store and retrieve arbitrary metadata + Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"` +} + +func (i Result) Error() string { + return (i).String() +} + +// String provides a human-readable message for the result item +func (i Result) String() string { + identifier := i.ResourceRef + var idStringList []string + if identifier != nil { + 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) + } + } + formatString := "[%s]" + severity := i.Severity + // We default Severity to Info when converting a result to a message. + if i.Severity == "" { + severity = Info + } + list := []interface{}{severity} + if len(idStringList) > 0 { + formatString += " %s" + list = append(list, strings.Join(idStringList, "/")) + } + if i.Field != nil { + formatString += " %s" + list = append(list, i.Field.Path) + } + formatString += ": %s" + list = append(list, i.Message) + return fmt.Sprintf(formatString, list...) +} + +// File references a file containing a resource +type File struct { + // Path is relative path to the file containing the resource. + // This field is required. + Path string `yaml:"path,omitempty" json:"path,omitempty"` + + // Index is the index into the file containing the resource + // (i.e. if there are multiple resources in a single file) + Index int `yaml:"index,omitempty" json:"index,omitempty"` +} + +// Field references a field in a resource +type Field struct { + // Path is the field path. This field is required. + Path string `yaml:"path,omitempty" json:"path,omitempty"` + + // CurrentValue is the current field value + CurrentValue interface{} `yaml:"currentValue,omitempty" json:"currentValue,omitempty"` + + // ProposedValue is the proposed value of the field to fix an issue. + ProposedValue interface{} `yaml:"proposedValue,omitempty" json:"proposedValue,omitempty"` +} + +type Results []*Result + +// Error enables Results to be returned as an error +func (e Results) Error() string { + var msgs []string + for _, i := range e { + msgs = append(msgs, i.String()) + } + return strings.Join(msgs, "\n\n") +} + +// ExitCode provides the exit code based on the result's severity +func (e Results) ExitCode() int { + for _, i := range e { + if i.Severity == Error { + return 1 + } + } + return 0 +} + +// Sort performs an in place stable sort of Results +func (e Results) Sort() { + sort.SliceStable(e, func(i, j int) bool { + if fileLess(e, i, j) != 0 { + return fileLess(e, i, j) < 0 + } + if severityLess(e, i, j) != 0 { + return severityLess(e, i, j) < 0 + } + return resultToString(*e[i]) < resultToString(*e[j]) + }) +} + +func severityLess(items Results, i, j int) int { + severityToNumber := map[Severity]int{ + Error: 0, + Warning: 1, + Info: 2, + } + + severityLevelI, found := severityToNumber[items[i].Severity] + if !found { + severityLevelI = 3 + } + severityLevelJ, found := severityToNumber[items[j].Severity] + if !found { + severityLevelJ = 3 + } + return severityLevelI - severityLevelJ +} + +func fileLess(items Results, i, j int) int { + var fileI, fileJ File + if items[i].File == nil { + fileI = File{} + } else { + fileI = *items[i].File + } + if items[j].File == nil { + fileJ = File{} + } else { + fileJ = *items[j].File + } + if fileI.Path != fileJ.Path { + if fileI.Path < fileJ.Path { + return -1 + } + return 1 + } + return fileI.Index - fileJ.Index +} + +func resultToString(item Result) string { + return fmt.Sprintf("resource-ref:%s,field:%s,message:%s", + item.ResourceRef, item.Field, item.Message) +} + +func ErrorConfigFileResult(err error, path string) *Result { + return ConfigFileResult(err.Error(), path, Error) +} + +func ConfigFileResult(msg, path string, severity Severity) *Result { + return &Result{ + Message: msg, + Severity: severity, + File: &File{ + Path: path, + }, + } +} + +func ErrorResult(err error) *Result { + return GeneralResult(err.Error(), Error) +} + +func GeneralResult(msg string, severity Severity) *Result { + return &Result{ + Message: msg, + Severity: severity, + } +} + +func ErrorConfigObjectResult(err error, obj *KubeObject) *Result { + return ConfigObjectResult(err.Error(), obj, Error) +} + +func ConfigObjectResult(msg string, obj *KubeObject, severity Severity) *Result { + return &Result{ + Message: msg, + Severity: severity, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: obj.APIVersion(), + Kind: obj.Kind(), + }, + NameMeta: yaml.NameMeta{ + Name: obj.Name(), + Namespace: obj.Namespace(), + }, + }, + File: &File{ + Path: obj.PathAnnotation(), + Index: obj.IndexAnnotation(), + }, + } +} diff --git a/go/result_test.go b/go/result_test.go new file mode 100644 index 000000000..a1971de14 --- /dev/null +++ b/go/result_test.go @@ -0,0 +1,273 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package krmfn + +import ( + "reflect" + "testing" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestResults_Sort(t *testing.T) { + testcases := []struct { + name string + input Results + output Results + }{ + { + name: "sort based on severity", + input: Results{ + { + Message: "Error message 1", + Severity: Info, + }, + { + Message: "Error message 2", + Severity: Error, + }, + }, + output: Results{ + { + Message: "Error message 2", + Severity: Error, + }, + { + Message: "Error message 1", + Severity: Info, + }, + }, + }, + { + name: "sort based on file", + input: Results{ + { + Message: "Error message", + Severity: Error, + File: &File{ + Path: "resource.yaml", + Index: 1, + }, + }, + { + Message: "Error message", + Severity: Info, + File: &File{ + Path: "resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: Info, + File: &File{ + Path: "other-resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: Warning, + File: &File{ + Path: "resource.yaml", + Index: 2, + }, + }, + { + Message: "Error message", + Severity: Warning, + }, + }, + output: Results{ + { + Message: "Error message", + Severity: Warning, + }, + { + Message: "Error message", + Severity: Info, + File: &File{ + Path: "other-resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: Info, + File: &File{ + Path: "resource.yaml", + Index: 0, + }, + }, + { + Message: "Error message", + Severity: Error, + File: &File{ + Path: "resource.yaml", + Index: 1, + }, + }, + { + Message: "Error message", + Severity: Warning, + File: &File{ + Path: "resource.yaml", + Index: 2, + }, + }, + }, + }, + + { + name: "sort based on other fields", + input: Results{ + { + Message: "Error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "spec", + }, + }, + { + Message: "Error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "metadata.name", + }, + }, + { + Message: "Another error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "metadata.name", + }, + }, + { + Message: "Another error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "metadata.name", + }, + }, + }, + output: Results{ + { + Message: "Another error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "metadata.name", + }, + }, + { + Message: "Another error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "metadata.name", + }, + }, + { + Message: "Error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "metadata.name", + }, + }, + { + Message: "Error message", + Severity: Error, + ResourceRef: &yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + NameMeta: yaml.NameMeta{ + Namespace: "foo-ns", + Name: "bar", + }, + }, + Field: &Field{ + Path: "spec", + }, + }, + }, + }, + } + + for _, tc := range testcases { + tc.input.Sort() + if !reflect.DeepEqual(tc.input, tc.output) { + t.Errorf("in testcase %q, expect: %#v, but got: %#v", tc.name, tc.output, tc.input) + } + } +} diff --git a/go/run.go b/go/run.go new file mode 100644 index 000000000..5462324d6 --- /dev/null +++ b/go/run.go @@ -0,0 +1,46 @@ +package krmfn + +import ( + "fmt" + "io/ioutil" + "os" +) + +// AsMain reads the resourceList in yaml format from stdin, evaluates the +// function and write the updated resourceList in yaml to stdout. Errors if any +// will be printed to stderr. +func AsMain(p ResourceListProcessor) error { + in, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("unable to read from stdin: %v", err) + } + out, err := Run(p, in) + if err != nil { + return err + } + _, err = os.Stdout.Write(out) + return err +} + +// Run evaluates the function. input must be a resourceList in yaml format. An +// updated resourceList will be returned. +func Run(p ResourceListProcessor, input []byte) ([]byte, error) { + rl, err := ParseResourceList(input) + if err != nil { + return nil, err + } + err = p.Process(rl) + if err != nil { + // If the error is not a Results type, we wrap the error as a Result. + if results, ok := err.(Results); ok { + rl.Results = append(rl.Results, results...) + } else if result, ok := err.(Result); ok { + rl.Results = append(rl.Results, &result) + } else if result, ok := err.(*Result); ok { + rl.Results = append(rl.Results, result) + } else { + rl.Results = append(rl.Results, ErrorResult(err)) + } + } + return rl.ToYAML() +} diff --git a/go/scalar.go b/go/scalar.go new file mode 100644 index 000000000..2a2202d3e --- /dev/null +++ b/go/scalar.go @@ -0,0 +1,103 @@ +package krmfn + +import ( + "strconv" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +const ( + tagString = "!!str" + tagBool = "!!bool" + tagInt = "!!int" + tagFloat = "!!float" + tagNull = "!!null" +) + +type scalarVariant struct { + node *yaml.Node +} + +func (v *scalarVariant) Kind() variantKind { + return variantKindScalar +} + +func newStringScalarVariant(s string) *scalarVariant { + return &scalarVariant{ + node: buildStringNode(s), + } +} + +func newBoolScalarVariant(b bool) *scalarVariant { + return &scalarVariant{ + node: buildBoolNode(b), + } +} + +func newIntScalarVariant(i int) *scalarVariant { + return &scalarVariant{ + node: buildIntNode(i), + } +} + +func newFloatScalarVariant(f float64) *scalarVariant { + return &scalarVariant{ + node: buildFloatNode(f), + } +} + +func (v *scalarVariant) IsNull() bool { + return v.node.Tag == tagNull +} + +func (v *scalarVariant) StringValue() (string, bool) { + switch v.node.Tag { + case tagString: + return v.node.Value, true + default: + return "", false + } +} + +func (v *scalarVariant) BoolValue() (bool, bool) { + switch v.node.Tag { + case tagBool: + b, err := strconv.ParseBool(v.node.Value) + if err != nil { + return b, false + } + return b, true + default: + return false, false + } +} + +func (v *scalarVariant) IntValue() (int, bool) { + switch v.node.Tag { + case tagInt: + i, err := strconv.Atoi(v.node.Value) + if err != nil { + return i, false + } + return i, true + default: + return 0, false + } +} + +func (v *scalarVariant) FloatValue() (float64, bool) { + switch v.node.Tag { + case tagFloat: + f, err := strconv.ParseFloat(v.node.Value, 64) + if err != nil { + return f, false + } + return f, true + default: + return 0, false + } +} + +func (v *scalarVariant) Node() *yaml.Node { + return v.node +} diff --git a/go/slice.go b/go/slice.go new file mode 100644 index 000000000..a8b927835 --- /dev/null +++ b/go/slice.go @@ -0,0 +1,37 @@ +package krmfn + +import ( + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +type sliceVariant struct { + node *yaml.Node +} + +func newSliceVariant(s ...variant) *sliceVariant { + node := buildSequenceNode() + for _, v := range s { + node.Content = append(node.Content, v.Node()) + } + return &sliceVariant{node: node} +} + +func (v *sliceVariant) Kind() variantKind { + return variantKindSlice +} + +func (v *sliceVariant) Node() *yaml.Node { + return v.node +} + +func (v *sliceVariant) Clear() { + v.node.Content = nil +} + +func (v *sliceVariant) Objects() ([]*mapVariant, error) { + return extractObjects(v.node.Content...) +} + +func (v *sliceVariant) Add(node variant) { + v.node.Content = append(v.node.Content, node.Node()) +} diff --git a/go/variant.go b/go/variant.go new file mode 100644 index 000000000..f2eb27dba --- /dev/null +++ b/go/variant.go @@ -0,0 +1,186 @@ +package krmfn + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +type variantKind string + +const ( + variantKindMap variantKind = "Map" + variantKindSlice variantKind = "Slice" + variantKindScalar variantKind = "Scalar" +) + +type variant interface { + Kind() variantKind + Node() *yaml.Node +} + +// nodes are expected to be key1,value1,key2,value2,... +func buildMappingNode(nodes ...*yaml.Node) *yaml.Node { + return &yaml.Node{ + Kind: yaml.MappingNode, + Content: nodes, + } +} + +func buildSequenceNode(nodes ...*yaml.Node) *yaml.Node { + return &yaml.Node{ + Kind: yaml.SequenceNode, + Content: nodes, + } +} + +func buildStringNode(s string) *yaml.Node { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: s, + } +} + +func buildIntNode(i int) *yaml.Node { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!int", + Value: strconv.Itoa(i), + } +} + +func buildFloatNode(f float64) *yaml.Node { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!float", + Value: strconv.FormatFloat(f, 'f', -1, 64), + } +} + +func buildBoolNode(b bool) *yaml.Node { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!bool", + Value: strconv.FormatBool(b), + } +} + +func toVariant(n *yaml.Node) variant { + switch n.Kind { + case yaml.ScalarNode: + return &scalarVariant{node: n} + case yaml.MappingNode: + return &mapVariant{node: n} + case yaml.SequenceNode: + return &sliceVariant{node: n} + + default: + panic("unhandled yaml node kind") + } +} + +func extractObjects(nodes ...*yaml.Node) ([]*mapVariant, error) { + var objects []*mapVariant + + for _, node := range nodes { + switch node.Kind { + case yaml.DocumentNode: + children, err := extractObjects(node.Content...) + if err != nil { + return nil, err + } + objects = append(objects, children...) + case yaml.MappingNode: + objects = append(objects, &mapVariant{node: node}) + default: + return nil, fmt.Errorf("unhandled node kind %v", node.Kind) + } + } + return objects, nil +} + +func typedObjectToMapVariant(v interface{}) (*mapVariant, error) { + // The built-in types only have json tags. We can't simply do ynode.Encode(v), + // since it use the lowercased field name by default if no yaml tag is specified. + // This affects both k8s built-in types (e.g. appsv1.Deployment) and any types + // that depends on built-in types (e.g. metav1.ObjectMeta, corev1.PodTemplate). + // To work around it, we rely on the json tags. We first convert v to + // map[string]interface{} through json and then convert it to ynode. + node, err := func() (*yaml.Node, error) { + j, err := json.Marshal(v) + if err != nil { + return nil, err + } + var m map[string]interface{} + if err = json.Unmarshal(j, &m); err != nil { + return nil, err + } + + node := &yaml.Node{} + if err = node.Encode(m); err != nil { + return nil, err + } + return node, nil + }() + if err != nil { + return nil, fmt.Errorf("unable to convert strong typed object to yaml node: %w", err) + } + + mv := &mapVariant{node: node} + mv.cleanupCreationTimestamp() + err = mv.sortFields() + return mv, err +} + +func typedObjectToSliceVariant(v interface{}) (*sliceVariant, error) { + // The built-in types only have json tags. We can't simply do ynode.Encode(v), + // since it use the lowercased field name by default if no yaml tag is specified. + // This affects both k8s built-in types (e.g. appsv1.Deployment) and any types + // that depends on built-in types (e.g. metav1.ObjectMeta, corev1.PodTemplate). + // To work around it, we rely on the json tags. We first convert v to + // map[string]interface{} through json and then convert it to ynode. + node, err := func() (*yaml.Node, error) { + j, err := json.Marshal(v) + if err != nil { + return nil, err + } + var l []interface{} + if err = json.Unmarshal(j, &l); err != nil { + return nil, err + } + + node := &yaml.Node{} + if err = node.Encode(l); err != nil { + return nil, err + } + return node, nil + }() + if err != nil { + return nil, fmt.Errorf("unable to convert strong typed object to yaml node: %w", err) + } + + return &sliceVariant{node: node}, nil +} + +func mapVariantToTypedObject(mv *mapVariant, ptr interface{}) error { + if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr { + return fmt.Errorf("ptr must be a pointer to an object") + } + + // The built-in types only have json tags. We can't simply do mv.Node().Decode(ptr), + // since it use the lowercased field name by default if no yaml tag is specified. + // This affects both k8s built-in types (e.g. appsv1.Deployment) and any types + // that depends on built-in types (e.g. metav1.ObjectMeta, corev1.PodTemplate). + // To work around it, we rely on the json tags. We first convert mv to json + // and then unmarshal it to ptr. + j, err := yaml.NewRNode(mv.Node()).MarshalJSON() + if err != nil { + return err + } + err = json.Unmarshal(j, ptr) + return err +} diff --git a/go/variant_test.go b/go/variant_test.go new file mode 100644 index 000000000..c53a9be52 --- /dev/null +++ b/go/variant_test.go @@ -0,0 +1,193 @@ +package krmfn + +import ( + "testing" + + "sigs.k8s.io/kustomize/kyaml/yaml" + + "github.com/stretchr/testify/assert" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMapVariantToTypedObject(t *testing.T) { + testcases := []struct { + name string + src string + dst interface{} + expected interface{} + }{ + { + name: "k8s built-in types", + src: `apiVersion: v1 +kind: ConfigMap +metadata: + name: my-cm + namespace: my-ns +data: + foo: bar +`, + dst: &corev1.ConfigMap{}, + expected: &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cm", + Namespace: "my-ns", + }, + Data: map[string]string{ + "foo": "bar", + }, + }, + }, + { + name: "crd type with metav1.ObjectMeta", + src: `apiVersion: example.co/v1 +kind: Foo +metadata: + name: my-foo +desiredReplicas: 1 +`, + dst: &Foo{}, + expected: &Foo{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "example.co/v1", + Kind: "Foo", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-foo", + }, + DesiredReplicas: 1, + }, + }, + { + name: "crd type with yaml.ResourceIdentifier", + src: `apiVersion: example.co/v1 +kind: Bar +metadata: + name: my-bar +desiredReplicas: 1 +`, + dst: &Bar{}, + expected: &Bar{ + ResourceMeta: yaml.ResourceMeta{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "example.co/v1", + Kind: "Bar", + }, + ObjectMeta: yaml.ObjectMeta{ + NameMeta: yaml.NameMeta{ + Name: "my-bar", + }, + }, + }, + DesiredReplicas: 1, + }, + }, + } + + for _, tc := range testcases { + rn := yaml.MustParse(tc.src) + mv := &mapVariant{node: rn.YNode()} + err := mapVariantToTypedObject(mv, tc.dst) + assert.NoError(t, err) + assert.Equal(t, tc.expected, tc.dst) + } +} + +func TestTypedObjectToMapVariant(t *testing.T) { + testcases := []struct { + name string + input interface{} + expected string + }{ + { + name: "k8s built-in types", + input: &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cm", + Namespace: "my-ns", + }, + Data: map[string]string{ + "foo": "bar", + }, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: my-cm + namespace: my-ns +data: + foo: bar +`, + }, + { + name: "crd type with metav1.ObjectMeta", + input: &Foo{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "example.co/v1", + Kind: "Foo", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-foo", + }, + DesiredReplicas: 1, + }, + expected: `apiVersion: example.co/v1 +kind: Foo +metadata: + name: my-foo +desiredReplicas: 1 +`, + }, + { + name: "crd type with yaml.ResourceIdentifier", + input: &Bar{ + ResourceMeta: yaml.ResourceMeta{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "example.co/v1", + Kind: "Bar", + }, + ObjectMeta: yaml.ObjectMeta{ + NameMeta: yaml.NameMeta{ + Name: "my-bar", + }, + }, + }, + DesiredReplicas: 1, + }, + expected: `apiVersion: example.co/v1 +kind: Bar +metadata: + name: my-bar +desiredReplicas: 1 +`, + }, + } + + for _, tc := range testcases { + mv, err := typedObjectToMapVariant(tc.input) + assert.NoError(t, err) + s, err := yaml.NewRNode(mv.node).String() + assert.NoError(t, err) + assert.Equal(t, tc.expected, s) + } +} + +type Foo struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"` +} + +type Bar struct { + yaml.ResourceMeta `json:",inline" yaml:",inline"` + DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"` +}