From 624e7276090e15e71cb5eb0bc150634ac3615075 Mon Sep 17 00:00:00 2001 From: dong Date: Mon, 22 Aug 2022 22:39:04 +0800 Subject: [PATCH] feat: add resourcedistribution generator Signed-off-by: dong --- cmd/resourcedistributiongenerator/Makefile | 20 + cmd/resourcedistributiongenerator/README.md | 218 +++++ .../generator/alias.go | 51 + .../generator/resourcedistribution.go | 141 +++ .../generator/targets.go | 219 +++++ .../generator/utils.go | 272 ++++++ cmd/resourcedistributiongenerator/go.mod | 15 + cmd/resourcedistributiongenerator/go.sum | 294 ++++++ cmd/resourcedistributiongenerator/main.go | 29 + .../main_test.go | 917 ++++++++++++++++++ 10 files changed, 2176 insertions(+) create mode 100644 cmd/resourcedistributiongenerator/Makefile create mode 100644 cmd/resourcedistributiongenerator/README.md create mode 100644 cmd/resourcedistributiongenerator/generator/alias.go create mode 100644 cmd/resourcedistributiongenerator/generator/resourcedistribution.go create mode 100644 cmd/resourcedistributiongenerator/generator/targets.go create mode 100644 cmd/resourcedistributiongenerator/generator/utils.go create mode 100644 cmd/resourcedistributiongenerator/go.mod create mode 100644 cmd/resourcedistributiongenerator/go.sum create mode 100644 cmd/resourcedistributiongenerator/main.go create mode 100644 cmd/resourcedistributiongenerator/main_test.go diff --git a/cmd/resourcedistributiongenerator/Makefile b/cmd/resourcedistributiongenerator/Makefile new file mode 100644 index 0000000..408aa77 --- /dev/null +++ b/cmd/resourcedistributiongenerator/Makefile @@ -0,0 +1,20 @@ +BINARY="resourcedistributiongenerator" + +.PHONY: test fmt vet build clean + +default: build + +test: + go test -v ./... + +fmt: + go fmt ./... + +vet: + go vet ./... + +build: + go build -v -o ${BINARY} + +clean: + @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi \ No newline at end of file diff --git a/cmd/resourcedistributiongenerator/README.md b/cmd/resourcedistributiongenerator/README.md new file mode 100644 index 0000000..cc45b85 --- /dev/null +++ b/cmd/resourcedistributiongenerator/README.md @@ -0,0 +1,218 @@ +# ResourceDistribution Generator + +> To use this plug-in, you need to install Kustomize first. Please refer to the [Kustomize documentation](https://kubectl.docs.kubernetes.io/installation/kustomize/) for installation + +When using Kustomize to manage applications, the generator provided with Kustomize can directly read files as data content to create Configmap or Secret, avoiding various format errors that are easy to occur during manual replication. The ResourceDistribution Generator is a third-party plug-in for Kustomize that can be used to create a ResourceDistribution by reading files as data content. + +The application reads a Kubernetes object of type ResourceList, as shown below. functionConfig is a Kubernetes object used to pass build parameters to the application. Items is a list of Kubernetes objects, the content is the ResourceDistribution object generated by this application, and finally kustomize will fill this field into the output resource list. + +```yaml +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +items: + ... +functionConfig: + ... +``` + +## Download ResourceDistribution generaotor + +[This page](https://github.com/openkruise/kruise-tools/releases) provides the path to download binary files for common versions. Currently `Linux`, `Darwin` (OS X), `Windows` provide `X86_64` and `ARM64 `. If you use some other system or architecture, you must download the [source code](https://github.com/openkruise/kruise-tools/blob/master/cmd/resourcedistributiongenerator) and perform `Go Build` or `make` to build the binary + +```bash +go build -o resourcedistributiongenerator main.go +``` + +## API Description + +ResourceDistributionGenerator is the Exec KRM functions plugin of kusomize. It is mainly composed of `resource` and `targets` fields. After the build, it will generate `resource` and `targets` content corresponding to ResourceDistribution. The `name` in `metadata` is used to set the name of the generated resourceDistribution. The annotation `config.kubernetes.io/function` needs to write the path of this plugin in the file system. If a relative path is used, it needs to be relative to A kustomization file that references the configuration file. + +```yaml +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistributionGenerator +metadata: + name: rdname + annotations: + config.kubernetes.io/function: | + exec: + path: ./plugins/resourcedistributiongenerator +resource: + ... ... +targets: + ... ... +``` + +## Resource Field + +The contents of the `resource` field are used to generate the distributed resources. The `literals`, `files`, and `envs` fields are used in the same way as in [Configmap or Secret](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/configmapgenerator/) Generator. + +- `resourceKind`: Specify the resource kind to distribute, Secret or ConfigMap; +- `resourceName`: Set the name of the distribution resource, that is, the name of the Secret or ConfigMap; +- `literals`: create data content using key/value pairs in the given literals; +- `files`: create data content with the given file name and content; +- `envs`: create data content using key/value pairs in the file; + +A correctly configured resource field is as follows: + +```yaml +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistributionGenerator +metadata: + ... ... +resource: + resourceKind: ConfigMap + resourceName: cmname + files: + - file.properties + literals: + - JAVA_HOME=/opt/java/jdk +targets: + ... ... +``` + +## Targets Field + +The usage of the `targets` field is basically the same as that of the `targets` field in ResourceDistribution. Note that the contents of the `includedNamespaces` and `excludedNamespaces` fields are directly the names of the namespaces. + +A correctly configured targets field is as follows: + +```yaml +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistributionGenerator +metadata: + ... ... +resource: + ... ... +targets: + allNamespaces: true + excludedNamespaces: + - ns-2 + includedNamespaces: + - ns-1 + namespaceLabelSelector: + matchLabels: + group: "test" +``` + +## Options and ResourceOptions Field + +The `options` and `resourceOptions` fields are used to set annotations or labels for the generated ResourceDistribution and the Resource (ie ConfigMap or Secret) in it, respectively. + +A correctly configured `options` and `resourceOptions` fields is as follows: + +```yaml +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistributionGenerator +metadata: + ... ... +resource: + ... ... + resourceOptions: + annotations: + dashboard: "1" + labels: + environment: "dev" +targets: + ... ... +options: + annotations: + type: "slave" + labels: + version: "stable" +``` + +## A Complete Use Case + +1. Create an empty directory demo. Create a configuration file named rdGenerator.yaml in the demo directory with the following content. Put the downloaded ResourceDistributionGenerator plugin into the `demo/plugins/` path. + +```yaml +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistributionGenerator +metadata: + name: rdname + annotations: + config.kubernetes.io/function: | + exec: + path: ./plugins/resourcedistributiongenerator +resource: + resourceKind: ConfigMap + resourceName: cmname + files: + - application.properties + literals: + - JAVA_HOME=/opt/java/jdk + resourceOptions: + annotations: + dashboard: "1" +options: + labels: + app.kubernetes.io/name: "app1" +targets: + includedNamespaces: + - ns-1 + namespaceLabelSelector: + matchLabels: + group: "test" +``` + +2. Create the application.properties file in the demo directory with the following contents. + +```properties +FOO=Bar +``` + +3. Create a kustomization file in the demo directory that references the plugin configuration. + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + ... ... +generators: +- rdGenerator.yaml +``` + +4. Use the `kustomize build --enable-alpha-plugins --enable-exec demo` command to build your application, the effect is as follows + +```yaml +... +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +metadata: + labels: + app.kubernetes.io/name: app1 + name: rdname +spec: + resource: + apiVersion: v1 + data: + JAVA_HOME: /opt/java/jdk + application.properties: | + FOO=Bar + kind: ConfigMap + metadata: + annotations: + dashboard: "1" + name: cmname + targets: + includedNamespaces: + list: + - name: ns-1 + namespaceLabelSelector: + matchLabels: + group: test +``` + +## Use the ResourceDistribution Generator in ArgoCD + +To use the Kustomize plug-in in ArgoCD, you need to add build options for Kustomize that allow third-party plug-ins. Find the configMap named argocd-cm and add the following to the `data` field `kustomize.buildOptions : --enable-alpha-plugins --enable-exec` to add build options for third-party plugins to the default version of kustomize. See [ArgoCD](https://argo-cd.readthedocs.io/en/stable/user-guide/kustomize/#kustomize-build-optionsparameters) for more information. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + .... +data: + kustomize.buildOptions: --enable-alpha-plugins --enable-exec +``` + diff --git a/cmd/resourcedistributiongenerator/generator/alias.go b/cmd/resourcedistributiongenerator/generator/alias.go new file mode 100644 index 0000000..3f3734c --- /dev/null +++ b/cmd/resourcedistributiongenerator/generator/alias.go @@ -0,0 +1,51 @@ +/* +Copyright 2022 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generator + +const tmpl = ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 +` + +// Field names +const ( + kindField = "kind" + nameField = "name" + listField = "list" + allNamespacesField = "allNamespaces" + immutableField = "immutable" + typeField = "type" + matchExpressionsField = "matchExpressions" + keyField = "key" + operatorField = "operator" + valuesField = "values" +) + +var metadataLabelsPath = []string{"metadata", "labels"} +var metadataAnnotationsPath = []string{"metadata", "annotations"} +var resourcePath = []string{"spec", "resource"} +var metadataPath = []string{"spec", "resource", "metadata"} +var resourceLabelsPath = []string{"spec", "resource", "metadata", "labels"} +var resourceAnnotationsPath = []string{"spec", "resource", "metadata", "annotations"} +var targetsPath = []string{"spec", "targets"} +var includedNamespacesPath = []string{"spec", "targets", "includedNamespaces"} +var excludedNamespacesPath = []string{"spec", "targets", "excludedNamespaces"} +var NamespaceLabelSelectorPath = []string{"spec", "targets", "namespaceLabelSelector"} +var MatchLabelsPath = []string{"spec", "targets", "namespaceLabelSelector", "matchLabels"} diff --git a/cmd/resourcedistributiongenerator/generator/resourcedistribution.go b/cmd/resourcedistributiongenerator/generator/resourcedistribution.go new file mode 100644 index 0000000..c0a71bd --- /dev/null +++ b/cmd/resourcedistributiongenerator/generator/resourcedistribution.go @@ -0,0 +1,141 @@ +/* +Copyright 2022 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generator + +import ( + "github.com/go-errors/errors" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/fn/framework" + "sigs.k8s.io/kustomize/kyaml/fn/framework/command" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +type ResourceDistributionPlugin struct { + types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + ResourceArgs `json:"resource,omitempty" yaml:"resource,omitempty"` + TargetsArgs `json:"targets,omitempty" yaml:"targets,omitempty"` + + // Options for the resourcedistribution. + // GeneratorOptions same as configmap and secret generator Options + Options *types.GeneratorOptions `json:"options,omitempty" yaml:"options,omitempty"` + + // Behavior of generated resource, must be one of: + // 'create': create a new one + // 'replace': replace the existing one + // 'merge': merge with the existing one + Behavior string `json:"behavior,omitempty" yaml:"behavior,omitempty"` +} + +// ResourceArgs contain arguments for the resource to be distributed. +type ResourceArgs struct { + // Name of the resource to be distributed. + ResourceName string `json:"resourceName,omitempty" yaml:"resourceName,omitempty"` + // Only configmap and secret are available + ResourceKind string `json:"resourceKind,omitempty" yaml:"resourceKind,omitempty"` + + // KvPairSources defines places to obtain key value pairs. + // same as configmap and secret generator KvPairSources + types.KvPairSources `json:",inline,omitempty" yaml:",inline,omitempty"` + + // Options for the resource to be distributed. + // GeneratorOptions same as configmap and secret generator Options + ResourceOptions *types.GeneratorOptions `json:"resourceOptions,omitempty" yaml:"resourceOptions,omitempty"` + + // Type of the secret. It can be "Opaque" (default), or "kubernetes.io/tls". + // + // If type is "kubernetes.io/tls", then "literals" or "files" must have exactly two + // keys: "tls.key" and "tls.crt" + Type string `json:"type,omitempty" yaml:"type,omitempty"` +} + +// TargetsArgs defines places to obtain target namespace args. +type TargetsArgs struct { + // AllNamespaces if true distribute all namespaces + AllNamespaces bool `json:"allNamespaces,omitempty" yaml:"allNamespaces,omitempty"` + + // ExcludedNamespaces is a list of excluded namespaces name. + ExcludedNamespaces []string `json:"excludedNamespaces,omitempty" yaml:"excludedNamespaces,omitempty"` + + // IncludedNamespaces is a list of included namespaces name. + IncludedNamespaces []string `json:"includedNamespaces,omitempty" yaml:"includedNamespaces,omitempty"` + + // NamespaceLabelSelector for the generator. + NamespaceLabelSelector *metav1.LabelSelector `json:"namespaceLabelSelector,omitempty" yaml:"namespaceLabelSelector,omitempty"` +} + +func BuildCmd() *cobra.Command { + config := new(ResourceDistributionPlugin) + fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) { + rn, err := MakeResourceDistribution(config) + if err != nil { + return nil, err + } + + var itemsOutput []*yaml.RNode + itemsOutput = append(itemsOutput, rn) + return itemsOutput, nil + } + + p := framework.SimpleProcessor{Config: config, Filter: kio.FilterFunc(fn)} + cmd := command.Build(p, command.StandaloneDisabled, false) + return cmd +} + +// MakeResourceDistribution makes a ResourceDistribution. +// +// ResourceDistribution: https://openkruise.io/docs/user-manuals/resourcedistribution +func MakeResourceDistribution(config *ResourceDistributionPlugin) (*yaml.RNode, error) { + rn, err := makeBaseNode(&config.ObjectMeta) + if err != nil { + return nil, err + } + + // set Labels and Annotations for ResourceDistribution + if config.Options != nil { + err = setLabelsOrAnnotations(rn, config.Options.Annotations, metadataAnnotationsPath) + if err != nil { + return nil, err + } + err = setLabelsOrAnnotations(rn, config.Options.Labels, metadataLabelsPath) + if err != nil { + return nil, err + } + } + + if config.ObjectMeta.Name == "" { + return nil, errors.Errorf("a ResourceDistribution must have a name ") + } + err = rn.PipeE(yaml.SetK8sName(config.ObjectMeta.Name)) + if err != nil { + return nil, err + } + + err = setResource(rn, &config.ResourceArgs) + if err != nil { + return nil, err + } + + err = setTargets(rn, &config.TargetsArgs) + if err != nil { + return nil, err + } + + return rn, nil +} diff --git a/cmd/resourcedistributiongenerator/generator/targets.go b/cmd/resourcedistributiongenerator/generator/targets.go new file mode 100644 index 0000000..87cd995 --- /dev/null +++ b/cmd/resourcedistributiongenerator/generator/targets.go @@ -0,0 +1,219 @@ +/* +Copyright 2022 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generator + +import ( + "sort" + "strconv" + "strings" + + "github.com/go-errors/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func setTargets(rn *yaml.RNode, args *TargetsArgs) error { + if args.AllNamespaces == false && args.IncludedNamespaces == nil && args.ExcludedNamespaces == nil && + (args.NamespaceLabelSelector == nil || + args.NamespaceLabelSelector.MatchLabels == nil && args.NamespaceLabelSelector.MatchExpressions == nil) { + return errors.Errorf("The targets field of the ResourceDistribution cannot be empty") + } + + err := setAllNs(rn, args.AllNamespaces) + if err != nil { + return err + } + + err = setIncludedExcludedNs(rn, args.ExcludedNamespaces, excludedNamespacesPath) + if err != nil { + return err + } + + err = setIncludedExcludedNs(rn, args.IncludedNamespaces, includedNamespacesPath) + if err != nil { + return err + } + + err = setNsLabelSelector(rn, args.NamespaceLabelSelector) + if err != nil { + return err + } + + return nil +} + +// setIncludedExcludedNs set IncludedNamespaces Or ExcludedNamespaces for targets field +func setIncludedExcludedNs(rn *yaml.RNode, v []string, inExNsPath []string) error { + if v == nil { + return nil + } + if err := rn.SetMapField(newNameListRNode(v...), append(inExNsPath, listField)...); err != nil { + return err + } + return nil +} + +// newNameListRNode returns a new List *RNode +// containing the provided scalar values prefixed with a string of name. +func newNameListRNode(values ...string) *yaml.RNode { + matchSeq := &yaml.Node{Kind: yaml.SequenceNode} + for _, v := range values { + node := &yaml.Node{ + Kind: yaml.MappingNode, + } + node.Content = append(node.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: nameField, + }, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: v, + }) + matchSeq.Content = append(matchSeq.Content, node) + + } + return yaml.NewRNode(matchSeq) +} + +// setAllNs set AllNamespaces true for targets field +// The allNamespaces field is false by default and is not displayed +func setAllNs(rn *yaml.RNode, allNs bool) error { + if !allNs { + return nil + } + allNamespaces := strconv.FormatBool(allNs) + n := &yaml.Node{ + Kind: yaml.ScalarNode, + Value: allNamespaces, + Tag: yaml.NodeTagBool, + } + if err := rn.SetMapField(yaml.NewRNode(n), append(targetsPath, allNamespacesField)...); err != nil { + return err + } + return nil +} + +func setNsLabelSelector(rn *yaml.RNode, sel *metav1.LabelSelector) error { + if sel == nil { + return nil + } + + err := setMatchExpressions(rn, sel.MatchExpressions) + if err != nil { + return err + } + + err = setMatchLabels(rn, sel.MatchLabels) + if err != nil { + return err + } + + return nil +} + +func setMatchLabels(rn *yaml.RNode, matchLabels map[string]string) error { + if matchLabels == nil { + return nil + } + for _, k := range yaml.SortedMapKeys(matchLabels) { + v := matchLabels[k] + if err := rn.SetMapField(yaml.NewStringRNode(v), append(MatchLabelsPath, k)...); err != nil { + return err + } + } + return nil +} + +func setMatchExpressions(rn *yaml.RNode, args []metav1.LabelSelectorRequirement) error { + if args == nil { + return nil + } + + sort.Slice(args, func(i, j int) bool { + return strings.Compare(args[i].Key, args[j].Key) < 0 + }) + + // matchExpList will be seted to the matchExpressions field + matchExpList := &yaml.Node{Kind: yaml.SequenceNode} + for _, matchExpArgs := range args { + matchExpElement := &yaml.Node{ + Kind: yaml.MappingNode, + } + + // add key for matchExpression + if matchExpArgs.Key == "" { + return errors.Errorf("the field " + + "ResourceDistribution.targets.namespaceLabelSelector.matchExpressions.key cannot be empty") + } + matchExpElement.Content = append(matchExpElement.Content, newNode(keyField), newNode(matchExpArgs.Key)) + + // add operator for matchExpression + if matchExpArgs.Operator != metav1.LabelSelectorOpIn && + matchExpArgs.Operator != metav1.LabelSelectorOpNotIn && + matchExpArgs.Operator != metav1.LabelSelectorOpExists && + matchExpArgs.Operator != metav1.LabelSelectorOpDoesNotExist { + return errors.Errorf("the field " + + "ResourceDistribution.targets.namespaceLabelSelector.matchExpressions.operator is invalid") + } + operator := string(matchExpArgs.Operator) + matchExpElement.Content = append(matchExpElement.Content, newNode(operatorField), newNode(operator)) + + if matchExpArgs.Operator == metav1.LabelSelectorOpIn || + matchExpArgs.Operator == metav1.LabelSelectorOpNotIn { + // add values for matchExpression + if matchExpArgs.Values == nil { + return errors.Errorf("the field " + + "ResourceDistribution.targets.namespaceLabelSelector.matchExpressions.values for In and NotIn cannot be empty") + } + valSeq := &yaml.Node{Kind: yaml.SequenceNode} + knowValue := make(map[string]bool) + sort.Strings(matchExpArgs.Values) + for _, val := range matchExpArgs.Values { + if val == "" { + return errors.Errorf("the element of field " + + "ResourceDistribution.targets.namespaceLabelSelector.matchExpressions.values is invalid") + } + if knowValue[val] { + return errors.Errorf("the element of field " + + "ResourceDistribution.targets.namespaceLabelSelector.matchExpressions.values cannot be repeated") + } + valSeq.Content = append(valSeq.Content, newNode(val)) + knowValue[val] = true + } + matchExpElement.Content = append(matchExpElement.Content, newNode(valuesField), valSeq) + } else { + if matchExpArgs.Values != nil { + return errors.Errorf("the field " + + "ResourceDistribution.targets.namespaceLabelSelector.matchExpressions.values for Exist and DoesNotExist must be empty") + } + } + + // element is added to the list + matchExpList.Content = append(matchExpList.Content, matchExpElement) + } + + err := rn.SetMapField(yaml.NewRNode(matchExpList), append(NamespaceLabelSelectorPath, matchExpressionsField)...) + if err != nil { + return err + } + return nil +} + +func newNode(value string) *yaml.Node { + return &yaml.Node{ + Kind: yaml.ScalarNode, Value: value, + } +} diff --git a/cmd/resourcedistributiongenerator/generator/utils.go b/cmd/resourcedistributiongenerator/generator/utils.go new file mode 100644 index 0000000..7c03efa --- /dev/null +++ b/cmd/resourcedistributiongenerator/generator/utils.go @@ -0,0 +1,272 @@ +/* +Copyright 2022 The Kruise Authors. +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generator + +import ( + "encoding/base64" + "strings" + "unicode/utf8" + + "github.com/go-errors/errors" + "sigs.k8s.io/kustomize/api/ifc" + "sigs.k8s.io/kustomize/api/kv" + "sigs.k8s.io/kustomize/api/loader" + "sigs.k8s.io/kustomize/api/provider" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/filesys" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func makeBaseNode(meta *types.ObjectMeta) (*yaml.RNode, error) { + rn, err := yaml.Parse(tmpl) + if err != nil { + return nil, err + } + + return rn, nil +} + +func setResource(rn *yaml.RNode, args *ResourceArgs) error { + if err := setData(rn, args); err != nil { + return err + } + + if err := setImmutable(rn, args.ResourceOptions); err != nil { + return err + } + if err := setResourceKind(rn, args.ResourceKind); err != nil { + return err + } + // set Labels And Annotions for Secret or ConfigMap + if args.ResourceOptions != nil { + if err := setLabelsOrAnnotations(rn, args.ResourceOptions.Annotations, resourceAnnotationsPath); err != nil { + return err + } + if err := setLabelsOrAnnotations(rn, args.ResourceOptions.Labels, resourceLabelsPath); err != nil { + return err + } + } + + if err := setResourceName(rn, args.ResourceName); err != nil { + return err + } + if err := setResourceType(rn, args); err != nil { + return err + } + return nil +} + +func setResourceName(rn *yaml.RNode, name string) error { + if name == "" { + return errors.Errorf("a ResourceDistribution must have a resource name ") + } + if err := rn.SetMapField(yaml.NewStringRNode(name), append(metadataPath, nameField)...); err != nil { + return err + } + return nil +} + +func setResourceType(rn *yaml.RNode, args *ResourceArgs) error { + if args.ResourceKind == "Secret" { + t := "Opaque" + if args.Type != "" { + t = args.Type + } + if err := rn.SetMapField(yaml.NewStringRNode(t), append(resourcePath, typeField)...); err != nil { + return err + } + } + return nil +} + +func setResourceKind( + rn *yaml.RNode, kind string) error { + if kind == "" || kind != "Secret" && kind != "ConfigMap" { + return errors.Errorf("resourceKind must be ConfigMap or Secret ") + } + if err := rn.SetMapField(yaml.NewStringRNode(kind), append(resourcePath, kindField)...); err != nil { + return err + } + return nil +} + +func setLabelsOrAnnotations( + rn *yaml.RNode, labelsOrAnnotations map[string]string, labelsOrAnnotationsPath []string) error { + if labelsOrAnnotations == nil { + return nil + } + + for _, k := range yaml.SortedMapKeys(labelsOrAnnotations) { + v := labelsOrAnnotations[k] + if err := rn.SetMapField(yaml.NewStringRNode(v), append(labelsOrAnnotationsPath, k)...); err != nil { + return err + } + } + return nil +} + +func setData(rn *yaml.RNode, args *ResourceArgs) error { + ldr, err := loader.NewLoader(loader.RestrictionRootOnly, + "./", filesys.MakeFsOnDisk()) + if err != nil { + return err + } + kvLdr := kv.NewLoader(ldr, provider.NewDefaultDepProvider().GetFieldValidator()) + + m, err := makeValidatedDataMap(kvLdr, args.ResourceName, args.KvPairSources) + if err != nil { + return err + } + + if args.ResourceKind == "ConfigMap" { + if err = loadMapIntoConfigMapData(m, rn); err != nil { + return err + } + } else { + if err = loadMapIntoSecretData(m, rn); err != nil { + return err + } + } + return nil +} + +// copy from sigs.k8s.io/kustomize/api/internal/generators/utils.go +func makeValidatedDataMap( + ldr ifc.KvLoader, name string, sources types.KvPairSources) (map[string]string, error) { + pairs, err := ldr.Load(sources) + if err != nil { + return nil, errors.WrapPrefix(err, "loading KV pairs", 0) + } + knownKeys := make(map[string]string) + for _, p := range pairs { + // legal key: alphanumeric characters, '-', '_' or '.' + if err := ldr.Validator().ErrIfInvalidKey(p.Key); err != nil { + return nil, err + } + if _, ok := knownKeys[p.Key]; ok { + return nil, errors.Errorf( + "configmap %s illegally repeats the key `%s`", name, p.Key) + } + knownKeys[p.Key] = p.Value + } + return knownKeys, nil +} + +func setImmutable( + rn *yaml.RNode, opts *types.GeneratorOptions) error { + if opts == nil { + return nil + } + if opts.Immutable { + n := &yaml.Node{ + Kind: yaml.ScalarNode, + Value: "true", + Tag: yaml.NodeTagBool, + } + if err := rn.SetMapField(yaml.NewRNode(n), append(resourcePath, immutableField)...); err != nil { + return err + } + } + return nil +} + +// copy from sigs.k8s.io/kustomize/kyaml/yaml/datamap.go +// The resourcePath prefix is added to the fldName +func loadMapIntoConfigMapData(m map[string]string, rn *yaml.RNode) error { + for _, k := range yaml.SortedMapKeys(m) { + fldName, vrN := makeConfigMapValueRNode(m[k]) + if _, err := rn.Pipe( + yaml.LookupCreate(yaml.MappingNode, append(resourcePath, fldName)...), + yaml.SetField(k, vrN)); err != nil { + return err + } + } + return nil +} + +// copy from sigs.k8s.io/kustomize/kyaml/yaml/datamap.go +func makeConfigMapValueRNode(s string) (field string, rN *yaml.RNode) { + yN := &yaml.Node{Kind: yaml.ScalarNode} + yN.Tag = yaml.NodeTagString + if utf8.ValidString(s) { + field = yaml.DataField + yN.Value = s + } else { + field = yaml.BinaryDataField + yN.Value = encodeBase64(s) + } + if strings.Contains(yN.Value, "\n") { + yN.Style = yaml.LiteralStyle + } + return field, yaml.NewRNode(yN) +} + +// copy from sigs.k8s.io/kustomize/kyaml/yaml/datamap.go +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} + +// copy from sigs.k8s.io/kustomize/kyaml/yaml/datamap.go +// The resourcePath prefix is added to the yaml.DataField +func loadMapIntoSecretData(m map[string]string, rn *yaml.RNode) error { + mapNode, err := rn.Pipe(yaml.LookupCreate(yaml.MappingNode, append(resourcePath, yaml.DataField)...)) + if err != nil { + return err + } + for _, k := range yaml.SortedMapKeys(m) { + vrN := makeSecretValueRNode(m[k]) + if _, err := mapNode.Pipe(yaml.SetField(k, vrN)); err != nil { + return err + } + } + return nil +} + +// copy from sigs.k8s.io/kustomize/kyaml/yaml/datamap.go +func makeSecretValueRNode(s string) *yaml.RNode { + yN := &yaml.Node{Kind: yaml.ScalarNode} + // Purposely don't use YAML tags to identify the data as being plain text or + // binary. It kubernetes Secrets the values in the `data` map are expected + // to be base64 encoded, and in ConfigMaps that same can be said for the + // values in the `binaryData` field. + yN.Tag = yaml.NodeTagString + yN.Value = encodeBase64(s) + if strings.Contains(yN.Value, "\n") { + yN.Style = yaml.LiteralStyle + } + return yaml.NewRNode(yN) +} diff --git a/cmd/resourcedistributiongenerator/go.mod b/cmd/resourcedistributiongenerator/go.mod new file mode 100644 index 0000000..53a9eea --- /dev/null +++ b/cmd/resourcedistributiongenerator/go.mod @@ -0,0 +1,15 @@ +module github.com/openkruise/kruise-tools/resourcedistributiongenerator + +go 1.16 + +require ( + github.com/go-errors/errors v1.4.2 + k8s.io/apimachinery v0.24.4 + sigs.k8s.io/kustomize/api v0.12.1 + sigs.k8s.io/kustomize/kyaml v0.13.9 +) + +require ( + github.com/spf13/cobra v1.4.0 + github.com/stretchr/testify v1.7.0 +) diff --git a/cmd/resourcedistributiongenerator/go.sum b/cmd/resourcedistributiongenerator/go.sum new file mode 100644 index 0000000..c46836d --- /dev/null +++ b/cmd/resourcedistributiongenerator/go.sum @@ -0,0 +1,294 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +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/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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-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/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +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.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/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/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/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.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +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 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +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/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.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/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.27/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.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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.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-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-20190620200207-3b0461eec859/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-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-20201020160332-67f06af15bc9/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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/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-20210330210617-4fbd30eecc44/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +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.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= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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/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/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.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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/apimachinery v0.24.4 h1:S0Ur3J/PbivTcL43EdSdPhqCqKla2NIuneNwZcTDeGQ= +k8s.io/apimachinery v0.24.4/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +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.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 h1:nqYOUleKLC/0P1zbU29F5q6aoezM6MOAVz+iyfQbZ5M= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= +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 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/cmd/resourcedistributiongenerator/main.go b/cmd/resourcedistributiongenerator/main.go new file mode 100644 index 0000000..87e3dbd --- /dev/null +++ b/cmd/resourcedistributiongenerator/main.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The Kruise Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/openkruise/kruise-tools/resourcedistributiongenerator/generator" + "os" +) + +func main() { + cmd := generator.BuildCmd() + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/resourcedistributiongenerator/main_test.go b/cmd/resourcedistributiongenerator/main_test.go new file mode 100644 index 0000000..5e21258 --- /dev/null +++ b/cmd/resourcedistributiongenerator/main_test.go @@ -0,0 +1,917 @@ +/* +Copyright 2022 The Kruise Authors. +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/require" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/openkruise/kruise-tools/resourcedistributiongenerator/generator" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestResourceDistributionGenerator(t *testing.T) { + tests := []struct { + name string + in string + expect string + filename []string + envFilename []string + setupFile func(t *testing.T, FileSources []string) func() + setupEnvFile func(t *testing.T, EnvFileSources []string) func() + }{ + { + name: "configmap-literals-envs-files-resourceOptions-options-allTargets", + setupFile: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}), + filename: []string{"foo1", "foo2"}, + setupEnvFile: setupEnvFile([][]string{{"key1=value1", "#", "", "key2=value2"}, {"key3=value3"}}), + envFilename: []string{"file1.env", "file2.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + literals: + - JAVA_HOME=/opt/java/jdk + - A=b + envs: + - file1.env + - file2.env + files: + - foo1 + - foo2 + resourceOptions: + annotations: + dashboard: "1" + immutable: true + labels: + test: true + rsla: rs + options: + labels: + app.kubernetes.io/name: "app1" + annotations: + an: rdan + targets: + allNamespaces: true + includedNamespaces: + - ns-1 + excludedNamespaces: + - ns-2 + namespaceLabelSelector: + matchLabels: + group: "test" + matchExpressions: + - key: ffxc + operator: In + values: + - l + - a + - key: exc + operator: NotIn + values: + - albc + - a + - key: abc + operator: Exists +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + A: b + JAVA_HOME: /opt/java/jdk + foo1: hello world + foo2: hello world + key1: value1 + key2: value2 + key3: value3 + immutable: true + kind: ConfigMap + metadata: + annotations: + dashboard: "1" + labels: + rsla: rs + test: "true" + name: cmname + targets: + allNamespaces: true + excludedNamespaces: + list: + - name: ns-2 + includedNamespaces: + list: + - name: ns-1 + namespaceLabelSelector: + matchExpressions: + - key: abc + operator: Exists + - key: exc + operator: NotIn + values: + - a + - albc + - key: ffxc + operator: In + values: + - a + - l + matchLabels: + group: test +metadata: + annotations: + an: rdan + labels: + app.kubernetes.io/name: app1 + name: rdname +`, + }, + { + name: "configmap-literals-includedNamespaces", + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + literals: + - JAVA_HOME= /opt/java/jdk + - A=b + targets: + includedNamespaces: + - ns-1 +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + A: b + JAVA_HOME: ' /opt/java/jdk' + kind: ConfigMap + metadata: + name: cmname + targets: + includedNamespaces: + list: + - name: ns-1 +metadata: + name: rdname +`, + }, + { + name: "configmap-literals-includedNamespaces", + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + literals: + - JAVA_HOME=/opt/java/jdk + - A=b + targets: + includedNamespaces: + - ns-1 +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + A: b + JAVA_HOME: /opt/java/jdk + kind: ConfigMap + metadata: + name: cmname + targets: + includedNamespaces: + list: + - name: ns-1 +metadata: + name: rdname +`, + }, + { + name: "configmap-envs-resourceOptions-allNamespaces", + setupEnvFile: setupEnvFile([][]string{{"key1=value1"}}), + envFilename: []string{"file1.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + envs: + - file1.env + resourceOptions: + annotations: + dashboard: "1" + immutable: false + targets: + allNamespaces: true +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + key1: value1 + kind: ConfigMap + metadata: + annotations: + dashboard: "1" + name: cmname + targets: + allNamespaces: true +metadata: + name: rdname +`, + }, + { + name: "configmap-files-resourceOptions-matchLabels", + setupFile: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}), + filename: []string{"foo1", "foo2"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + files: + - foo1 + resourceOptions: + labels: + rsla: rs + immutable: true + targets: + namespaceLabelSelector: + matchLabels: + group: "test" + app: "dev" +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + foo1: hello world + immutable: true + kind: ConfigMap + metadata: + labels: + rsla: rs + name: cmname + targets: + namespaceLabelSelector: + matchLabels: + app: dev + group: test +metadata: + name: rdname +`, + }, + { + name: "configmap-literals-envs-options-matchExpressions", + setupFile: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}), + filename: []string{"foo1", "foo2"}, + setupEnvFile: setupEnvFile([][]string{{"key1=value1", "#", "", " key2=value2"}, {"key3=value3"}}), + envFilename: []string{"file1.env", "file2.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + literals: + - JAVA_HOME=/opt/java/jdk + - A=b + files: + - foo1 + options: + labels: + app.kubernetes.io/name: "app1" + targets: + namespaceLabelSelector: + matchExpressions: + - key: ffxc + operator: DoesNotExist + - key: exc + operator: Exists +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + A: b + JAVA_HOME: /opt/java/jdk + foo1: hello world + kind: ConfigMap + metadata: + name: cmname + targets: + namespaceLabelSelector: + matchExpressions: + - key: exc + operator: Exists + - key: ffxc + operator: DoesNotExist +metadata: + labels: + app.kubernetes.io/name: app1 + name: rdname +`, + }, + { + name: "configmap-envs-files-options-matchExpressions", + setupFile: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}), + filename: []string{"foo1"}, + setupEnvFile: setupEnvFile([][]string{{"key1=value1", "#", "", "key2=value2"}}), + envFilename: []string{"file1.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: ConfigMap + resourceName: cmname + envs: + - file1.env + files: + - foo1 + options: + annotations: + node: 123 + an: rdan + targets: + namespaceLabelSelector: + matchExpressions: + - key: exc + operator: NotIn + values: + - albc + - key: abc + operator: Exists +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + foo1: hello world + key1: value1 + key2: value2 + kind: ConfigMap + metadata: + name: cmname + targets: + namespaceLabelSelector: + matchExpressions: + - key: abc + operator: Exists + - key: exc + operator: NotIn + values: + - albc +metadata: + annotations: + an: rdan + node: "123" + name: rdname +`, + }, + { + name: "secret-literals-envs-files-resourceOptions-options-allTargets", + setupFile: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}), + filename: []string{"foo1", "foo2"}, + setupEnvFile: setupEnvFile([][]string{{"key1=value1", "#", "", "key2=value2"}, {"key3=value3"}}), + envFilename: []string{"file1.env", "file2.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: Secret + resourceName: cmname + literals: + - JAVA_HOME=/opt/java/jdk + - A=b + envs: + - file1.env + - file2.env + files: + - foo1 + - foo2 + resourceOptions: + annotations: + dashboard: "1" + immutable: true + labels: + test: true + rsla: rs + options: + labels: + app.kubernetes.io/name: "app1" + annotations: + an: rdan + targets: + allNamespaces: true + includedNamespaces: + - ns-1 + namespaceLabelSelector: + matchLabels: + group: "test" + matchExpressions: + - key: ffxc + operator: NotIn + values: + - l + - a + - key: exc + operator: NotIn + values: + - albc + - a +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + A: Yg== + JAVA_HOME: L29wdC9qYXZhL2pkaw== + foo1: aGVsbG8gd29ybGQ= + foo2: aGVsbG8gd29ybGQ= + key1: dmFsdWUx + key2: dmFsdWUy + key3: dmFsdWUz + immutable: true + kind: Secret + metadata: + annotations: + dashboard: "1" + labels: + rsla: rs + test: "true" + name: cmname + type: Opaque + targets: + allNamespaces: true + includedNamespaces: + list: + - name: ns-1 + namespaceLabelSelector: + matchExpressions: + - key: exc + operator: NotIn + values: + - a + - albc + - key: ffxc + operator: NotIn + values: + - a + - l + matchLabels: + group: test +metadata: + annotations: + an: rdan + labels: + app.kubernetes.io/name: app1 + name: rdname +`, + }, + { + name: "secret-envs-files-resourceOptions-allNamespaces-excludedNamespaces", + setupFile: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}), + filename: []string{"foo1"}, + setupEnvFile: setupEnvFile([][]string{{"key1=value1", "#", "", "key2=value2"}}), + envFilename: []string{"file1.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: Secret + resourceName: cmname + envs: + - file1.env + files: + - foo1 + resourceOptions: + annotations: + dashboard: "1" + node: 2 + immutable: false + labels: + test: true + rsla: rs + targets: + allNamespaces: true + excludedNamespaces: + - ns-2 + - ns-3 +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + foo1: aGVsbG8gd29ybGQ= + key1: dmFsdWUx + key2: dmFsdWUy + kind: Secret + metadata: + annotations: + dashboard: "1" + node: "2" + labels: + rsla: rs + test: "true" + name: cmname + type: Opaque + targets: + allNamespaces: true + excludedNamespaces: + list: + - name: ns-2 + - name: ns-3 +metadata: + name: rdname +`, + }, + { + name: "secret-literals-options-allNamespaces-includedNamespaces", + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: Secret + resourceName: cmname + literals: + - JAVA_HOME=/opt/java/jdk + type: Opaque + options: + labels: + app.kubernetes.io/name: app1 + annotations: + an: "rdan" + targets: + allNamespaces: true + includedNamespaces: + - ns-1 +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + JAVA_HOME: L29wdC9qYXZhL2pkaw== + kind: Secret + metadata: + name: cmname + type: Opaque + targets: + allNamespaces: true + includedNamespaces: + list: + - name: ns-1 +metadata: + annotations: + an: rdan + labels: + app.kubernetes.io/name: app1 + name: rdname +`, + }, + { + name: "secret-kubernetes.io/tls-literals-allNamespaces-matchExpressions", + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: Secret + resourceName: cmname + literals: + - tls.crt=LS0tLS1CRUd...tCg== + - tls.key=LS0tLS1CRUd...0tLQo= + type: kubernetes.io/tls + targets: + allNamespaces: true + namespaceLabelSelector: + matchLabels: + group: "test" +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + tls.crt: TFMwdExTMUNSVWQuLi50Q2c9PQ== + tls.key: TFMwdExTMUNSVWQuLi4wdExRbz0= + kind: Secret + metadata: + name: cmname + type: kubernetes.io/tls + targets: + allNamespaces: true + namespaceLabelSelector: + matchLabels: + group: test +metadata: + name: rdname +`, + }, + { + name: "secret-kubernetes.io/tls-envs-files-allNamespaces-matchExpressions", + setupEnvFile: setupEnvFile([][]string{{"tls.crt=LS0tLS1CRUd...tCg==", "tls.key=LS0tLS1CRUd...0tLQo="}}), + envFilename: []string{"file1.env"}, + in: ` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +functionConfig: + apiVersion: apps.kruise.io/v1alpha1 + kind: ResourceDistributionGenerator + metadata: + name: rdname + resource: + resourceKind: Secret + resourceName: cmname + envs: + - file1.env + type: kubernetes.io/tls + targets: + allNamespaces: true + namespaceLabelSelector: + matchExpressions: + - key: ffxc + operator: Exists + - key: exc + operator: NotIn + values: + - albc + - a + +`, + expect: ` +apiVersion: apps.kruise.io/v1alpha1 +kind: ResourceDistribution +spec: + resource: + apiVersion: v1 + data: + tls.crt: TFMwdExTMUNSVWQuLi50Q2c9PQ== + tls.key: TFMwdExTMUNSVWQuLi4wdExRbz0= + kind: Secret + metadata: + name: cmname + type: kubernetes.io/tls + targets: + allNamespaces: true + namespaceLabelSelector: + matchExpressions: + - key: exc + operator: NotIn + values: + - a + - albc + - key: ffxc + operator: Exists +metadata: + name: rdname +`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.setupFile != nil { + if teardown := test.setupFile(t, test.filename); teardown != nil { + defer teardown() + } + } + if test.setupEnvFile != nil { + if teardown := test.setupEnvFile(t, test.envFilename); teardown != nil { + defer teardown() + } + } + + cmd := generator.BuildCmd() + cmd.SetIn(bytes.NewBufferString(test.in)) + buf := &bytes.Buffer{} + cmd.SetOut(buf) + if err := cmd.Execute(); err != nil { + require.NoError(t, err) + } + + //Extract resourceDistribution from the output + out, _ := yaml.Parse(buf.String()) + items, _ := out.Pipe(yaml.Get("items")) + elements, _ := items.Elements() + rd, _ := elements[0].String() + + if !(strings.TrimSpace(rd) == strings.TrimSpace(test.expect)) { + reportDiffAndFail(t, []byte(strings.TrimSpace(rd)), strings.TrimSpace(test.expect)) + } + }) + } +} + +func setupEnvFile(lines [][]string) func(*testing.T, []string) func() { + return func(t *testing.T, EnvFileSources []string) func() { + filenames := EnvFileSources + for i, filename := range filenames { + data := []byte("") + for _, l := range lines[i] { + data = append(data, []byte(l)...) + data = append(data, []byte("\r\n")...) + } + ioutil.WriteFile(filename, data, 0644) + } + + return func() { + for _, file := range filenames { + os.Remove(file) + } + } + } +} + +func setupBinaryFile(data []byte) func(*testing.T, []string) func() { + return func(t *testing.T, FileSources []string) func() { + files := FileSources + for _, file := range files { + ioutil.WriteFile(file, data, 0644) + } + + return func() { + for _, file := range files { + os.Remove(file) + } + } + } +} + +// Pretty printing of file differences. +func reportDiffAndFail( + t *testing.T, actual []byte, expected string) { + t.Helper() + sE, maxLen := convertToArray(expected) + sA, _ := convertToArray(string(actual)) + fmt.Println("===== ACTUAL BEGIN ========================================") + fmt.Print(string(actual)) + fmt.Println("===== ACTUAL END ==========================================") + format := fmt.Sprintf("%%s %%-%ds %%s\n", maxLen+4) + var limit int + if len(sE) < len(sA) { + limit = len(sE) + } else { + limit = len(sA) + } + fmt.Printf(format, " ", "EXPECTED", "ACTUAL") + fmt.Printf(format, " ", "--------", "------") + for i := 0; i < limit; i++ { + fmt.Printf(format, hint(sE[i], sA[i]), sE[i], sA[i]) + } + if len(sE) < len(sA) { + for i := len(sE); i < len(sA); i++ { + fmt.Printf(format, "X", "", sA[i]) + } + } else { + for i := len(sA); i < len(sE); i++ { + fmt.Printf(format, "X", sE[i], "") + } + } + t.Fatalf("Expected not equal to actual") +} + +func hint(a, b string) string { + if a == b { + return " " + } + return "X" +} + +func convertToArray(x string) ([]string, int) { + a := strings.Split(strings.TrimSuffix(x, "\n"), "\n") + maxLen := 0 + for i, v := range a { + z := tabToSpace(v) + if len(z) > maxLen { + maxLen = len(z) + } + a[i] = z + } + return a, maxLen +} + +func tabToSpace(input string) string { + var result []string + for _, i := range input { + if i == 9 { + result = append(result, " ") + } else { + result = append(result, string(i)) + } + } + return strings.Join(result, "") +}