diff --git a/Makefile b/Makefile index 1d5bcf4cd..c6d4ced6e 100644 --- a/Makefile +++ b/Makefile @@ -35,12 +35,12 @@ verify-kustomize-e2e: test-examples-e2e-kustomize # since everything uses the same implicit GOPATH. # This installs in a temp dir to avoid overwriting someone else's # linter, then installs in MYGOBIN with a new name. -# Version pinned by api/go.mod +# Version pinned by hack/go.mod $(MYGOBIN)/golangci-lint-kustomize: ( \ set -e; \ - export GOBIN=$$(mktemp -d) \ - cd api; \ + export GOBIN=$$(mktemp -d); \ + cd hack; \ GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint; \ mv $$GOBIN/golangci-lint $(MYGOBIN)/golangci-lint-kustomize \ ) diff --git a/README.md b/README.md index 967bf3ad3..aedf5ba6a 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Take the work from step (1) above, move it into a `someApp` subdirectory called `base`, then place overlays in a sibling directory. -An overlay is just another kustomization, refering to +An overlay is just another kustomization, referring to the base, and referring to patches to apply to that base. diff --git a/api/builtins/NamespaceTransformer.go b/api/builtins/NamespaceTransformer.go index d4c701963..1e8804e66 100644 --- a/api/builtins/NamespaceTransformer.go +++ b/api/builtins/NamespaceTransformer.go @@ -52,7 +52,7 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error { matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals) if len(matches) != 1 { - return fmt.Errorf("namespace tranformation produces ID conflict: %+v", matches) + return fmt.Errorf("namespace transformation produces ID conflict: %+v", matches) } } return nil diff --git a/api/internal/crawl/README.md b/api/internal/crawl/README.md index 157995eb6..7a9190d92 100644 --- a/api/internal/crawl/README.md +++ b/api/internal/crawl/README.md @@ -209,7 +209,7 @@ but they are functional. ## Technical details -### Overall design and imlpementation +### Overall design and implementations There are a few components that are all running together in order to get the overall application to work smoothly. This section will provide a brief diff --git a/api/internal/crawl/config/elastic/esbackup.yaml b/api/internal/crawl/config/elastic/esbackup.yaml index fbdbee461..378ddc868 100644 --- a/api/internal/crawl/config/elastic/esbackup.yaml +++ b/api/internal/crawl/config/elastic/esbackup.yaml @@ -11,7 +11,7 @@ metadata: spec: storage: gcs: - # the bucket must exist for the snapshot respository to be created successfully. + # the bucket must exist for the snapshot repository to be created successfully. bucket: kustomize-backup # the path does not need to exist. # If the path does not exist, the controller will create the folder in the GCS bucket. diff --git a/api/internal/crawl/search_cmds/transformer.md b/api/internal/crawl/search_cmds/transformer.md index ca4f3ce4c..c358f7e5b 100644 --- a/api/internal/crawl/search_cmds/transformer.md +++ b/api/internal/crawl/search_cmds/transformer.md @@ -1,4 +1,4 @@ -Find all the trasnformer files whose `kinds` field includes `HelmValues`, and +Find all the transformer files whose `kinds` field includes `HelmValues`, and only output certain fields of each document: ``` curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' diff --git a/api/internal/k8sdeps/configmapandsecret/configmapfactory.go b/api/internal/k8sdeps/configmapandsecret/configmapfactory.go index bd11f30d5..74da4d381 100644 --- a/api/internal/k8sdeps/configmapandsecret/configmapfactory.go +++ b/api/internal/k8sdeps/configmapandsecret/configmapfactory.go @@ -38,10 +38,7 @@ func (f *Factory) MakeConfigMap( return nil, errors.Wrap(err, "trouble mapping") } } - if f.options != nil { - cm.SetLabels(f.options.Labels) - cm.SetAnnotations(f.options.Annotations) - } + f.setLabelsAndAnnnotations(cm, args.GeneratorOptions) return cm, nil } diff --git a/api/internal/k8sdeps/configmapandsecret/configmapfactory_test.go b/api/internal/k8sdeps/configmapandsecret/configmapfactory_test.go index fdf2b769d..023e00be2 100644 --- a/api/internal/k8sdeps/configmapandsecret/configmapfactory_test.go +++ b/api/internal/k8sdeps/configmapandsecret/configmapfactory_test.go @@ -53,7 +53,7 @@ BAR=baz } } -func makeLiteralConfigMap(name string) *corev1.ConfigMap { +func makeLiteralConfigMap(name string, labels, annotations map[string]string) *corev1.ConfigMap { cm := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -69,7 +69,12 @@ func makeLiteralConfigMap(name string) *corev1.ConfigMap { "d": "true", }, } - cm.SetLabels(map[string]string{"foo": "bar"}) + if labels != nil { + cm.SetLabels(labels) + } + if annotations != nil { + cm.SetAnnotations(annotations) + } return cm } @@ -128,7 +133,49 @@ func TestConstructConfigMap(t *testing.T) { "foo": "bar", }, }, - expected: makeLiteralConfigMap("literalConfigMap"), + expected: makeLiteralConfigMap("literalConfigMap", map[string]string{ + "foo": "bar", + }, nil), + }, + { + description: "construct config map from literal with GeneratorOptions in ConfigMapArgs", + input: types.ConfigMapArgs{ + GeneratorArgs: types.GeneratorArgs{ + Name: "literalConfigMap", + KvPairSources: types.KvPairSources{ + LiteralSources: []string{"a=x", "b=y", "c=\"Hello World\"", "d='true'"}, + }, + GeneratorOptions: &types.GeneratorOptions{ + Labels: map[string]string{ + "foo": "changed", + "cat": "dog", + }, + Annotations: map[string]string{ + "foo": "changed", + "cat": "dog", + }, + }, + }, + }, + options: &types.GeneratorOptions{ + Labels: map[string]string{ + "foo": "bar", + }, + Annotations: map[string]string{ + "foo": "bar", + }, + }, + // GeneratorOptions from the ConfigMapArgs take precedence over the + // factory level GeneratorOptions and should overwrite + // labels/annotations set in the factory level if there are common + // labels/annotations + expected: makeLiteralConfigMap("literalConfigMap", map[string]string{ + "foo": "changed", + "cat": "dog", + }, map[string]string{ + "foo": "changed", + "cat": "dog", + }), }, } diff --git a/api/internal/k8sdeps/configmapandsecret/factory.go b/api/internal/k8sdeps/configmapandsecret/factory.go index 3975e2ed6..2576b4449 100644 --- a/api/internal/k8sdeps/configmapandsecret/factory.go +++ b/api/internal/k8sdeps/configmapandsecret/factory.go @@ -4,6 +4,7 @@ package configmapandsecret import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/kustomize/api/ifc" "sigs.k8s.io/kustomize/api/types" ) @@ -20,4 +21,35 @@ func NewFactory( return &Factory{kvLdr: kvLdr, options: o} } +// setLabelsAndAnnnotations will take the labels and annotations from +// global GeneratorOptions and resource level GeneratorOptions and merge them +// with the resource level taking precedence, and then set them on the provided +// obj. +func (f *Factory) setLabelsAndAnnnotations(obj metav1.Object, opts *types.GeneratorOptions) { + labels := make(map[string]string) + annotations := make(map[string]string) + if f.options != nil { + for k, v := range f.options.Labels { + labels[k] = v + } + for k, v := range f.options.Annotations { + annotations[k] = v + } + } + if opts != nil { + for k, v := range opts.Labels { + labels[k] = v + } + for k, v := range opts.Annotations { + annotations[k] = v + } + } + if len(labels) != 0 { + obj.SetLabels(labels) + } + if len(annotations) != 0 { + obj.SetAnnotations(annotations) + } +} + const keyExistsErrorMsg = "cannot add key %s, another key by that name already exists: %v" diff --git a/api/internal/k8sdeps/configmapandsecret/secretfactory.go b/api/internal/k8sdeps/configmapandsecret/secretfactory.go index 6b48a0113..487273620 100644 --- a/api/internal/k8sdeps/configmapandsecret/secretfactory.go +++ b/api/internal/k8sdeps/configmapandsecret/secretfactory.go @@ -39,10 +39,7 @@ func (f *Factory) MakeSecret( return nil, err } } - if f.options != nil { - s.SetLabels(f.options.Labels) - s.SetAnnotations(f.options.Annotations) - } + f.setLabelsAndAnnnotations(s, args.GeneratorOptions) return s, nil } diff --git a/api/internal/k8sdeps/configmapandsecret/secretfactory_test.go b/api/internal/k8sdeps/configmapandsecret/secretfactory_test.go index 76416f886..beb51c7c9 100644 --- a/api/internal/k8sdeps/configmapandsecret/secretfactory_test.go +++ b/api/internal/k8sdeps/configmapandsecret/secretfactory_test.go @@ -51,7 +51,7 @@ BAR=baz } } -func makeLiteralSecret(name string) *corev1.Secret { +func makeLiteralSecret(name string, labels, annotations map[string]string) *corev1.Secret { s := &corev1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -66,7 +66,12 @@ func makeLiteralSecret(name string) *corev1.Secret { }, Type: "Opaque", } - s.SetLabels(map[string]string{"foo": "bar"}) + if labels != nil { + s.SetLabels(labels) + } + if annotations != nil { + s.SetAnnotations(annotations) + } return s } @@ -120,7 +125,49 @@ func TestConstructSecret(t *testing.T) { "foo": "bar", }, }, - expected: makeLiteralSecret("literalSecret"), + expected: makeLiteralSecret("literalSecret", map[string]string{ + "foo": "bar", + }, nil), + }, + { + description: "construct secret from literal with GeneratorOptions in SecretArgs", + input: types.SecretArgs{ + GeneratorArgs: types.GeneratorArgs{ + Name: "literalSecret", + KvPairSources: types.KvPairSources{ + LiteralSources: []string{"a=x", "b=y"}, + }, + GeneratorOptions: &types.GeneratorOptions{ + Labels: map[string]string{ + "foo": "changed", + "cat": "dog", + }, + Annotations: map[string]string{ + "foo": "changed", + "cat": "dog", + }, + }, + }, + }, + options: &types.GeneratorOptions{ + Labels: map[string]string{ + "foo": "bar", + }, + Annotations: map[string]string{ + "foo": "bar", + }, + }, + // GeneratorOptions from the SecretArgs take precedence over the + // factory level GeneratorOptions and should overwrite + // labels/annotations set in the factory level if there are common + // labels/annotations + expected: makeLiteralSecret("literalSecret", map[string]string{ + "foo": "changed", + "cat": "dog", + }, map[string]string{ + "foo": "changed", + "cat": "dog", + }), }, } diff --git a/api/internal/target/kusttarget.go b/api/internal/target/kusttarget.go index 35a2b281d..f5fb08fb9 100644 --- a/api/internal/target/kusttarget.go +++ b/api/internal/target/kusttarget.go @@ -7,7 +7,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "strings" "github.com/pkg/errors" @@ -302,18 +301,15 @@ func (kt *KustTarget) configureExternalTransformers() ([]resmap.Transformer, err func (kt *KustTarget) accumulateResources( ra *accumulator.ResAccumulator, paths []string) error { for _, path := range paths { - ldr, err := kt.ldr.New(path) - if err == nil { - err = kt.accumulateDirectory(ra, ldr) - if err != nil { - return err + // try loading resource as file then as base (directory or git repository) + if errF := kt.accumulateFile(ra, path); errF != nil { + ldr, errL := kt.ldr.New(path) + if errL != nil { + return fmt.Errorf("accumulateFile %q, loader.New %q", errF, errL) } - } else { - err2 := kt.accumulateFile(ra, path) - if err2 != nil { - // Log ldr.New() error to highlight git failures. - log.Print(err.Error()) - return err2 + errD := kt.accumulateDirectory(ra, ldr) + if errD != nil { + return fmt.Errorf("accumulateFile %q, accumulateDirector: %q", errF, errD) } } } diff --git a/api/konfig/builtinpluginconsts/varreference.go b/api/konfig/builtinpluginconsts/varreference.go index dbf2b45be..f2a418d45 100644 --- a/api/konfig/builtinpluginconsts/varreference.go +++ b/api/konfig/builtinpluginconsts/varreference.go @@ -30,6 +30,9 @@ varReference: - path: spec/jobTemplate/spec/template/spec/initContainers/volumeMounts/mountPath kind: CronJob +- path: spec/jobTemplate/spec/template/volumes/nfs/server + kind: CronJob + - path: spec/template/spec/containers/args kind: DaemonSet @@ -54,6 +57,9 @@ varReference: - path: spec/template/spec/initContainers/volumeMounts/mountPath kind: DaemonSet +- path: spec/template/spec/volumes/nfs/server + kind: DaemonSet + - path: spec/template/spec/containers/args kind: Deployment @@ -78,6 +84,9 @@ varReference: - path: spec/template/spec/initContainers/volumeMounts/mountPath kind: Deployment +- path: spec/template/spec/volumes/nfs/server + kind: Deployment + - path: spec/rules/host kind: Ingress @@ -111,6 +120,9 @@ varReference: - path: spec/template/spec/initContainers/volumeMounts/mountPath kind: Job +- path: spec/template/spec/volumes/nfs/server + kind: Job + - path: spec/containers/args kind: Pod @@ -135,6 +147,9 @@ varReference: - path: spec/initContainers/volumeMounts/mountPath kind: Pod +- path: spec/volumes/nfs/server + kind: Pod + - path: spec/template/spec/containers/args kind: ReplicaSet @@ -159,6 +174,9 @@ varReference: - path: spec/template/spec/initContainers/volumeMounts/mountPath kind: ReplicaSet +- path: spec/template/spec/volumes/nfs/server + kind: ReplicaSet + - path: spec/ports/port kind: Service @@ -189,6 +207,12 @@ varReference: - path: spec/template/spec/initContainers/volumeMounts/mountPath kind: StatefulSet +- path: spec/volumeClaimTemplates/spec/nfs/server + kind: StatefulSet + +- path: spec/nfs/server + kind: PersistentVolume + - path: metadata/labels - path: metadata/annotations diff --git a/api/krusty/accumulation_test.go b/api/krusty/accumulation_test.go index e0df84456..491e52013 100644 --- a/api/krusty/accumulation_test.go +++ b/api/krusty/accumulation_test.go @@ -74,7 +74,7 @@ spec: if err == nil { t.Fatalf("expected an error") } - if !IsMissingKustomizationFileError(err) { + if !strings.Contains(err.Error(), "accumulating resources") { t.Fatalf("unexpected error: %q", err) } } @@ -89,7 +89,7 @@ resources: if err == nil { t.Fatalf("expected an error") } - if !strings.Contains(err.Error(), "'/app/deployment.yaml' doesn't exist") { + if !strings.Contains(err.Error(), "accumulating resources") { t.Fatalf("unexpected error: %q", err) } } diff --git a/api/krusty/variableref_test.go b/api/krusty/variableref_test.go index 37b9bf082..13173c75f 100644 --- a/api/krusty/variableref_test.go +++ b/api/krusty/variableref_test.go @@ -1336,3 +1336,637 @@ spec: protocol: TCP `) } + +func TestVariableRefNFSServer(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK("/app/base", ` +resources: +- pv_pvc.yaml +- nfs_deployment.yaml +- nfs_service.yaml +- Deployment.yaml +- CronJob.yaml +- DaemonSet.yaml +- ReplicaSet.yaml +- StatefulSet.yaml +- Pod.yaml +- Job.yaml +- nfs_pv.yaml + +vars: +- name: NFS_SERVER_SERVICE_NAME + objref: + kind: Service + name: nfs-server-service + apiVersion: v1 + fieldref: + fieldpath: metadata.name +`) + th.WriteF("/app/base/pv_pvc.yaml", ` +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: shared-volume-claim +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +`) + th.WriteF("/app/base/nfs_deployment.yaml", ` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nfs-server +spec: + replicas: 1 + template: + spec: + metadata: + labels: + role: nfs-server + containers: + - name: nfs-server + image: gcr.io/google_containers/volume-nfs:0.8 + ports: + - name: nfs + containerPort: 2049 + - name: mountd + containerPort: 20048 + - name: rpcbind + containerPort: 111 + securityContext: + privileged: true + volumeMounts: + - mountPath: /exports + name: shared-files + volumes: + - name: shared-files + persistentVolumeClaim: + claimName: shared-volume-claim +`) + th.WriteF("/app/base/nfs_service.yaml", ` +apiVersion: v1 +kind: Service +metadata: + name: nfs-server-service +spec: + ports: + - name: nfs + port: 2049 + - name: mountd + port: 20048 + - name: rpcbind + port: 111 + selector: + role: nfs-server +`) + th.WriteF("/app/base/Deployment.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app.kubernetes.io/component: nginx +spec: + selector: + matchLabels: + app.kubernetes.io/component: nginx + template: + metadata: + labels: + app.kubernetes.io/component: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.7-alpine + ports: + - name: http + containerPort: 80 + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + th.WriteF("/app/base/CronJob.yaml", ` +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: hello +spec: + schedule: "*/1 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: hello + image: busybox + args: + - /bin/sh + - -c + - date; echo Hello from the Kubernetes cluster + restartPolicy: OnFailure + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + th.WriteF("/app/base/DaemonSet.yaml", ` +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: fluentd-elasticsearch + namespace: kube-system + labels: + k8s-app: fluentd-logging +spec: + selector: + matchLabels: + name: fluentd-elasticsearch + template: + metadata: + labels: + name: fluentd-elasticsearch + spec: + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + containers: + - name: fluentd-elasticsearch + image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2 + resources: + limits: + memory: 200Mi + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - name: varlog + mountPath: /var/log + - name: varlibdockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + - mountPath: /app/shared-files + name: nfs-files-vol + terminationGracePeriodSeconds: 30 + volumes: + - name: varlog + hostPath: + path: /var/log + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers + - name: nfs-files-vol + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + th.WriteF("/app/base/ReplicaSet.yaml", ` +apiVersion: apps/v1 +kind: ReplicaSet +metadata: + name: frontend + labels: + app: guestbook + tier: frontend +spec: + # modify replicas according to your case + replicas: 3 + selector: + matchLabels: + tier: frontend + template: + metadata: + labels: + tier: frontend + spec: + containers: + - name: php-redis + image: gcr.io/google_samples/gb-frontend:v3 + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + + th.WriteF("/app/base/Job.yaml", ` +apiVersion: batch/v1 +kind: Job +metadata: + name: pi +spec: + template: + spec: + containers: + - name: pi + image: perl + command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + restartPolicy: Never + volumes: + - name: nfs-files-vol + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false + backoffLimit: 4 +`) + th.WriteF("/app/base/StatefulSet.yaml", ` +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: web +spec: + selector: + matchLabels: + app: nginx # has to match .spec.template.metadata.labels + serviceName: "nginx" + replicas: 3 # by default is 1 + template: + metadata: + labels: + app: nginx # has to match .spec.selector.matchLabels + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: nginx + image: k8s.gcr.io/nginx-slim:0.8 + ports: + - containerPort: 80 + name: web + volumeMounts: + - name: www + mountPath: /usr/share/nginx/html + volumeClaimTemplates: + - metadata: + name: www + spec: + accessModes: [ "ReadWriteMany" ] + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + th.WriteF("/app/base/Pod.yaml", ` +apiVersion: v1 +kind: Pod +metadata: + name: myapp-pod + labels: + app: myapp +spec: + containers: + - name: nginx + image: nginx:1.15.7-alpine + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: nfs-files-vol + mountPath: /app/shared-files + volumes: + - name: nfs-files-vol + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + th.WriteF("/app/base/nfs_pv.yaml", ` +apiVersion: v1 +kind: PersistentVolume +metadata: + name: nfs-files-pv +spec: + capacity: + storage: 10Gi + accessModes: + - ReadWriteMany + nfs: + server: $(NFS_SERVER_SERVICE_NAME).default.srv.cluster.local + path: / + readOnly: false +`) + th.WriteK("/app/overlay", ` +nameprefix: kustomized- +resources: +- ../base +`) + m := th.Run("/app/overlay", th.MakeDefaultOptions()) + th.AssertActualEqualsExpected(m, ` +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: kustomized-shared-volume-claim +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: kustomized-nfs-server +spec: + replicas: 1 + template: + spec: + containers: + - image: gcr.io/google_containers/volume-nfs:0.8 + name: nfs-server + ports: + - containerPort: 2049 + name: nfs + - containerPort: 20048 + name: mountd + - containerPort: 111 + name: rpcbind + securityContext: + privileged: true + volumeMounts: + - mountPath: /exports + name: shared-files + metadata: + labels: + role: nfs-server + volumes: + - name: shared-files + persistentVolumeClaim: + claimName: kustomized-shared-volume-claim +--- +apiVersion: v1 +kind: Service +metadata: + name: kustomized-nfs-server-service +spec: + ports: + - name: nfs + port: 2049 + - name: mountd + port: 20048 + - name: rpcbind + port: 111 + selector: + role: nfs-server +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: nginx + name: kustomized-nginx +spec: + selector: + matchLabels: + app.kubernetes.io/component: nginx + template: + metadata: + labels: + app.kubernetes.io/component: nginx + spec: + containers: + - image: nginx:1.15.7-alpine + name: nginx + ports: + - containerPort: 80 + name: http + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: kustomized-hello +spec: + jobTemplate: + spec: + template: + spec: + containers: + - args: + - /bin/sh + - -c + - date; echo Hello from the Kubernetes cluster + image: busybox + name: hello + restartPolicy: OnFailure + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local + schedule: '*/1 * * * *' +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + k8s-app: fluentd-logging + name: kustomized-fluentd-elasticsearch + namespace: kube-system +spec: + selector: + matchLabels: + name: fluentd-elasticsearch + template: + metadata: + labels: + name: fluentd-elasticsearch + spec: + containers: + - image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2 + name: fluentd-elasticsearch + resources: + limits: + memory: 200Mi + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - mountPath: /var/log + name: varlog + - mountPath: /var/lib/docker/containers + name: varlibdockercontainers + readOnly: true + - mountPath: /app/shared-files + name: nfs-files-vol + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + volumes: + - hostPath: + path: /var/log + name: varlog + - hostPath: + path: /var/lib/docker/containers + name: varlibdockercontainers + - name: nfs-files-vol + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +--- +apiVersion: apps/v1 +kind: ReplicaSet +metadata: + labels: + app: guestbook + tier: frontend + name: kustomized-frontend +spec: + replicas: 3 + selector: + matchLabels: + tier: frontend + template: + metadata: + labels: + tier: frontend + spec: + containers: + - image: gcr.io/google_samples/gb-frontend:v3 + name: php-redis + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: kustomized-web +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + serviceName: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: k8s.gcr.io/nginx-slim:0.8 + name: nginx + ports: + - containerPort: 80 + name: web + volumeMounts: + - mountPath: /usr/share/nginx/html + name: www + terminationGracePeriodSeconds: 10 + volumeClaimTemplates: + - metadata: + name: www + spec: + accessModes: + - ReadWriteMany + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: myapp + name: kustomized-myapp-pod +spec: + containers: + - image: nginx:1.15.7-alpine + name: nginx + ports: + - containerPort: 80 + name: http + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + volumes: + - name: nfs-files-vol + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: kustomized-pi +spec: + backoffLimit: 4 + template: + spec: + containers: + - command: + - perl + - -Mbignum=bpi + - -wle + - print bpi(2000) + image: perl + name: pi + volumeMounts: + - mountPath: /app/shared-files + name: nfs-files-vol + restartPolicy: Never + volumes: + - name: nfs-files-vol + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: kustomized-nfs-files-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 10Gi + nfs: + path: / + readOnly: false + server: kustomized-nfs-server-service.default.srv.cluster.local +`) +} diff --git a/api/loader/fileloader.go b/api/loader/fileloader.go index 0822d0c84..930cea5b6 100644 --- a/api/loader/fileloader.go +++ b/api/loader/fileloader.go @@ -5,7 +5,10 @@ package loader import ( "fmt" + "io/ioutil" "log" + "net/http" + "net/url" "path/filepath" "strings" @@ -86,6 +89,9 @@ type fileLoader struct { // File system utilities. fSys filesys.FileSystem + // Used to load from HTTP + http *http.Client + // Used to clone repositories. cloner git.Cloner @@ -293,6 +299,25 @@ func (fl *fileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error { // else an error. Relative paths are taken relative // to the root. func (fl *fileLoader) Load(path string) ([]byte, error) { + if u, err := url.Parse(path); err == nil && (u.Scheme == "http" || u.Scheme == "https") { + var hc *http.Client + if fl.http != nil { + hc = fl.http + } else { + hc = &http.Client{} + } + resp, err := hc.Get(path) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil + } + if !filepath.IsAbs(path) { path = fl.root.Join(path) } diff --git a/api/loader/fileloader_test.go b/api/loader/fileloader_test.go index 41d01d711..983bb41b3 100644 --- a/api/loader/fileloader_test.go +++ b/api/loader/fileloader_test.go @@ -4,7 +4,9 @@ package loader import ( + "bytes" "io/ioutil" + "net/http" "os" "path" "path/filepath" @@ -54,6 +56,7 @@ func makeLoader() *fileLoader { return NewFileLoaderAtRoot(MakeFakeFs(testCases)) } + func TestLoaderLoad(t *testing.T) { l1 := makeLoader() if "/" != l1.Root() { @@ -579,3 +582,93 @@ func TestRepoIndirectCycleDetection(t *testing.T) { t.Fatalf("unexpected err: %v", err) } } + +// Inspired by https://hassansin.github.io/Unit-Testing-http-client-in-Go +type fakeRoundTripper func(req *http.Request) *http.Response + +func (f fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +func makeFakeHTTPClient(fn fakeRoundTripper) *http.Client { + return &http.Client{ + Transport: fn, + } +} + +// TestLoaderHTTP test http file loader +func TestLoaderHTTP(t *testing.T) { + var testCasesFile = []testData{ + { + path: "http/file.yaml", + expectedContent: "file content", + }, + } + + l1 := NewFileLoaderAtRoot(MakeFakeFs(testCasesFile)) + if "/" != l1.Root() { + t.Fatalf("incorrect root: '%s'\n", l1.Root()) + } + for _, x := range testCasesFile { + b, err := l1.Load(x.path) + if err != nil { + t.Fatalf("unexpected load error: %v", err) + } + if !reflect.DeepEqual([]byte(x.expectedContent), b) { + t.Fatalf("in load expected %s, but got %s", x.expectedContent, b) + } + } + + var testCasesHTTP = []testData{ + { + path: "http://example.com/resource.yaml", + expectedContent: "http content", + }, + { + path: "https://example.com/resource.yaml", + expectedContent: "https content", + }, + } + + for _, x := range testCasesHTTP { + hc := makeFakeHTTPClient(func(req *http.Request) *http.Response { + u := req.URL.String() + if x.path != u { + t.Fatalf("expected URL %s, but got %s", x.path, u) + } + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString(x.expectedContent)), + Header: make(http.Header), + } + }) + l2 := l1 + l2.http = hc + b, err := l2.Load(x.path) + if err != nil { + t.Fatalf("unexpected load error: %v", err) + } + if !reflect.DeepEqual([]byte(x.expectedContent), b) { + t.Fatalf("in load expected %s, but got %s", x.expectedContent, b) + } + } + + var testCaseUnsupported = []testData{ + { + path: "httpsnotreal://example.com/resource.yaml", + expectedContent: "invalid", + }, + } + for _, x := range testCaseUnsupported { + hc := makeFakeHTTPClient(func(req *http.Request) *http.Response { + t.Fatalf("unexpected request to URL %s", req.URL.String()) + return nil + }) + l2 := l1 + l2.http = hc + _, err := l2.Load(x.path) + if err == nil { + t.Fatalf("expect error but get %v", err) + } + } +} diff --git a/api/types/generatorargs.go b/api/types/generatorargs.go index 6b06ac9d5..22f64d505 100644 --- a/api/types/generatorargs.go +++ b/api/types/generatorargs.go @@ -21,4 +21,7 @@ type GeneratorArgs struct { // KvPairSources for the generator. KvPairSources `json:",inline,omitempty" yaml:",inline,omitempty"` + + // GeneratorOptions modify this generator + GeneratorOptions *GeneratorOptions `json:"generatorOptions,omitempty" yaml:"generatorOptions,omitempty"` } diff --git a/cmd/config/go.sum b/cmd/config/go.sum index d32361a45..cfbd6103a 100644 --- a/cmd/config/go.sum +++ b/cmd/config/go.sum @@ -1,12 +1,18 @@ +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= 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/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 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-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 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/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -16,6 +22,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -82,6 +89,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 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/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= @@ -91,6 +99,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -100,6 +109,7 @@ github.com/posener/complete/v2 v2.0.1-alpha.12 h1:0wvkuDfHb5vSZlNBYgpEH4XQHpF46M github.com/posener/complete/v2 v2.0.1-alpha.12/go.mod h1://JlL91cS2JV7rOl6LVHrRqBXoBUecJu3ILQPgbJiMQ= github.com/posener/script v1.0.4 h1:nSuXW5ZdmFnQIueLB2s0qvs4oNsUloM1Zydzh75v42w= github.com/posener/script v1.0.4/go.mod h1:Rg3ijooqulo05aGLyGsHoLmIOUzHUVK19WVgrYBPU/E= +github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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= @@ -116,6 +126,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -123,10 +134,15 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/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-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= @@ -137,6 +153,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/cmd/config/internal/commands/cmdlistsetters.go b/cmd/config/internal/commands/cmdlistsetters.go index e51f84fff..b43681fa4 100644 --- a/cmd/config/internal/commands/cmdlistsetters.go +++ b/cmd/config/internal/commands/cmdlistsetters.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" @@ -67,8 +68,15 @@ func (r *ListSettersRunner) runE(c *cobra.Command, args []string) error { table.SetHeader([]string{"NAME", "VALUE", "SET BY", "DESCRIPTION", "COUNT"}) for i := range r.List.Setters { s := r.List.Setters[i] + v := s.Value + + // if the setter is for a list, populate the values + if len(s.ListValues) > 0 { + v = strings.Join(s.ListValues, ",") + v = fmt.Sprintf("[%s]", v) + } table.Append([]string{ - s.Name, s.Value, s.SetBy, s.Description, fmt.Sprintf("%d", s.Count)}) + s.Name, v, s.SetBy, s.Description, fmt.Sprintf("%d", s.Count)}) } table.Render() diff --git a/cmd/config/internal/commands/cmdset.go b/cmd/config/internal/commands/cmdset.go index d549a616b..6886d16ed 100644 --- a/cmd/config/internal/commands/cmdset.go +++ b/cmd/config/internal/commands/cmdset.go @@ -21,7 +21,7 @@ func NewSetRunner(parent string) *SetRunner { r := &SetRunner{} c := &cobra.Command{ Use: "set DIR NAME [VALUE]", - Args: cobra.RangeArgs(1, 3), + Args: cobra.MinimumNArgs(3), Short: commands.SetShort, Long: commands.SetLong, Example: commands.SetExamples, @@ -94,6 +94,11 @@ func (r *SetRunner) preRunE(c *cobra.Command, args []string) error { var err error r.Set.Name = args[1] r.Set.Value = args[2] + + // set remaining values as list values + if len(args) > 3 { + r.Set.ListValues = args[3:] + } r.Set.Description = r.Perform.Description r.Set.SetBy = r.Perform.SetBy r.OpenAPIFile, err = ext.GetOpenAPIFile(args) diff --git a/cmd/config/internal/commands/run-fns.go b/cmd/config/internal/commands/run-fns.go index c33e2f59f..170a41d76 100644 --- a/cmd/config/internal/commands/run-fns.go +++ b/cmd/config/internal/commands/run-fns.go @@ -89,6 +89,14 @@ func (r *RunFnRunner) getFunctions(c *cobra.Command, args, dataItems []string) ( if err != nil { return nil, err } + if r.Network { + err = fn.PipeE( + yaml.LookupCreate(yaml.MappingNode, "container", "network"), + yaml.SetField("required", yaml.NewScalarRNode("true"))) + if err != nil { + return nil, err + } + } // create the function config rc, err := yaml.Parse(` diff --git a/cmd/config/internal/commands/run_test.go b/cmd/config/internal/commands/run_test.go index 12dcc5f8b..aef76dbee 100644 --- a/cmd/config/internal/commands/run_test.go +++ b/cmd/config/internal/commands/run_test.go @@ -101,7 +101,7 @@ metadata: name: function-input annotations: config.kubernetes.io/function: | - container: {image: 'foo:bar'} + container: {image: 'foo:bar', network: {required: true}} data: {} kind: ConfigMap apiVersion: v1 @@ -118,7 +118,7 @@ metadata: name: function-input annotations: config.kubernetes.io/function: | - container: {image: 'foo:bar'} + container: {image: 'foo:bar', network: {required: true}} data: {} kind: ConfigMap apiVersion: v1 diff --git a/docs/FAQ.md b/docs/FAQ.md index b7d01b8e1..25e52cb8e 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -18,7 +18,7 @@ cluster Resources (including configmap and secret generators) can _still be shared_ via the recommended best practice of placing them in a directory with their own -kustomization file, and refering to this directory as a +kustomization file, and referring to this directory as a [`base`](glossary.md#base) from any kustomization that wants to use it. This encourages modularity and relocatability. diff --git a/docs/eschewedFeatures.md b/docs/eschewedFeatures.md index e1a134b73..7ad3ef73f 100644 --- a/docs/eschewedFeatures.md +++ b/docs/eschewedFeatures.md @@ -48,7 +48,7 @@ like adding labels or annotatations, get dedicated transformer plugins - `LabelTransformer`, `AnnotationsTransformer`, etc. These accept relatively simple YAML configuration -allowing easy targetting of any number of resources. +allowing easy targeting of any number of resources. Another class of edits take data from one specific object's field and use it in another (e.g. a service diff --git a/docs/fields.md b/docs/fields.md index 30e52ce7a..10e191ac8 100644 --- a/docs/fields.md +++ b/docs/fields.md @@ -131,6 +131,10 @@ crds: Modifies behavior of all [ConfigMap](#configmapgenerator) and [Secret](#secretgenerator) generators. +Additionally, generatorOptions can be set on a per resource level within each +generator. For details on per-resource generatorOptions usage see +[field-name-configMapGenerator] and See [field-name-secretGenerator]. + ``` generatorOptions: # labels to add to all generated resources @@ -202,7 +206,7 @@ See [field-name-replicas]. ### resources Each entry in this list must be a path to a -_file_, or a path (or URL) refering to another +_file_, or a path (or URL) referring to another kustomization _directory_, e.g. ``` diff --git a/docs/glossary.md b/docs/glossary.md index f29a71a45..b584ff7ff 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -273,9 +273,9 @@ more than one version). _kustomize_ is a command line tool supporting template-free, structured customization of declarative -configuration targetted to k8s-style objects. +configuration targeted to k8s-style objects. -_Targetted to k8s means_ that kustomize has some +_Targeted to k8s means_ that kustomize has some understanding of API resources, k8s concepts like names, labels, namespaces, etc. and the semantics of resource patching. diff --git a/docs/plugins/README.md b/docs/plugins/README.md index 8add73c89..3c1e8c5de 100644 --- a/docs/plugins/README.md +++ b/docs/plugins/README.md @@ -209,7 +209,7 @@ a YAML file containing its configuration (the file name provided in the kustomization file). > TODO: restrictions on plugin to allow the _same exec -> plugin_ to be targetted by both the +> plugin_ to be targeted by both the > `generators` and `transformers` fields. > > - first arg could be the fixed string diff --git a/docs/plugins/builtins.md b/docs/plugins/builtins.md index 4acf9247e..48dc60d87 100644 --- a/docs/plugins/builtins.md +++ b/docs/plugins/builtins.md @@ -90,9 +90,9 @@ commonAnnotations: Each entry in this list results in the creation of one ConfigMap resource (it's a generator of n maps). -The example below creates two ConfigMaps. One with the -names and contents of the given files, the other with -key/value as data. +The example below creates three ConfigMaps. One with the names and contents of +the given files, one with key/value as data, and a third which sets an +annotation and label via generatorOptions for that single ConfigMap. Each configMapGenerator item accepts a parameter of `behavior: [create|replace|merge]`. @@ -109,6 +109,14 @@ configMapGenerator: literals: - JAVA_HOME=/opt/java/jdk - JAVA_TOOL_OPTIONS=-agentlib:hprof +- name: dashboards + files: + - mydashboard.json + generatorOptions: + annotations: + dashboard: "1" + labels: + app.kubernetes.io/name: "app1" ``` It is also possible to @@ -651,6 +659,15 @@ secretGenerator: envs: - env.txt type: Opaque +- name: secret-with-annotation + files: + - app-config.yaml + type: Opaque + generatorOptions: + annotations: + app_config: "true" + labels: + app.kubernetes.io/name: "app2" ``` ### Usage via plugin diff --git a/docs/v2.1.0_changelog.md b/docs/v2.1.0_changelog.md index 82c0564e7..55525d008 100644 --- a/docs/v2.1.0_changelog.md +++ b/docs/v2.1.0_changelog.md @@ -78,7 +78,7 @@ a7a2589e Fix yaml in generator examples. 529db049 Introduce envs field. 6d309b52 Introduce stacked transformers. abf538d8 Keep backward compatibility for image transformer -7e12918f Keep var refernce in resources +7e12918f Keep var references in resources 7130e3ff Leave defautconfig empty for images 3e85c458 Load default config for image transformer 4162dbc2 Maintain resources in order loaded. diff --git a/docs/versioningPolicy.md b/docs/versioningPolicy.md index 859f946bb..8336a4ad0 100644 --- a/docs/versioningPolicy.md +++ b/docs/versioningPolicy.md @@ -37,7 +37,7 @@ the _kustomize Go API_. [import path]: https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher -In `kustomize/v3` (and preceeding major versions), the +In `kustomize/v3` (and preceding major versions), the kustomize program and the API live the same Go module at `sigs.k8s.io/kustomize`, at [import path] `sigs.k8s.io/kustomize/v3`. diff --git a/examples/inlinePatch.md b/examples/inlinePatch.md index 6a32462aa..c3294d5c5 100644 --- a/examples/inlinePatch.md +++ b/examples/inlinePatch.md @@ -4,7 +4,7 @@ # Demo: Inline Patch A kustomization file supports patching in three ways: -- patchesStrategicMerge: A list of patch files where each file is parsed as a [Stragetic Merge Patch]. +- patchesStrategicMerge: A list of patch files where each file is parsed as a [Strategic Merge Patch]. - patchesJSON6902: A list of patches and associated targetes, where each file is parsed as a [JSON Patch] and can only be applied to one target resource. - patches: A list of patches and their associated targets. The patch can be applied to multiple objects. It auto detects whether the patch is a [Strategic Merge Patch] or [JSON Patch]. diff --git a/examples/loadHttp.md b/examples/loadHttp.md new file mode 100644 index 000000000..fb1e78386 --- /dev/null +++ b/examples/loadHttp.md @@ -0,0 +1,25 @@ +# load file from http + +Resource and patch files could be loaded from http + + +```sh +DEMO_HOME=$(mktemp -d) + +cat <$DEMO_HOME/kustomization.yaml +resources: +- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/helloWorld/configMap.yaml +EOF +``` + + +```sh +test 1 == \ + $(kustomize build $DEMO_HOME | grep "Good Morning!" | wc -l); \ + echo $? +``` + +Kustomize will try loading resource as a file either from local or http. If it +fails, try to load it as a directory or git repository. + +Http load applies to patches as well. See full example in [loadHttp](loadHttp/). diff --git a/examples/loadHttp/kustomization.yaml b/examples/loadHttp/kustomization.yaml new file mode 100644 index 000000000..28e1b6ad4 --- /dev/null +++ b/examples/loadHttp/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/wordpress/wordpress/deployment.yaml +- https://github.com/knative/serving/releases/download/v0.12.0/serving.yaml # redirects to s3 +patches: +- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/wordpress/patch.yaml +patchesStrategicMerge: +- |- + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: custom-metrics-auth-reader + namespace: kube-system + $patch: delete diff --git a/hack/go.mod b/hack/go.mod new file mode 100644 index 000000000..aed429f74 --- /dev/null +++ b/hack/go.mod @@ -0,0 +1,7 @@ +module sigs.k8s.io/kustomize/hack + +go 1.14 + +require github.com/golangci/golangci-lint v1.23.8 + +exclude github.com/golangci/golangci-lint v1.24.0 diff --git a/hack/go.sum b/hack/go.sum new file mode 100644 index 000000000..3cb808ebe --- /dev/null +++ b/hack/go.sum @@ -0,0 +1,351 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bombsimon/wsl/v2 v2.0.0 h1:+Vjcn+/T5lSrO8Bjzhk4v14Un/2UyCA1E3V5j9nwTkQ= +github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-critic/go-critic v0.4.1 h1:4DTQfT1wWwLg/hzxwD9bkdhDQrdJtxe6DUTadPlrIeE= +github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.23.8 h1:NlD+Ld2TKH8qVmADy4iEvPxVmXaqPIeQu3d1cGQP4jc= +github.com/golangci/golangci-lint v1.23.8/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 h1:AtnWoOvTioyDXFvu96MWEeE8qj4COSQnJogzLy/u41A= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.1.1 h1:4D0wuPKjOTiK2garzuPGGvm4zZ/wLYDOH8TJSABC7KU= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-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-20190522155817-f3200d17e092/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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3 h1:Ms82wn6YK4ZycO6Bxyh0kxX3gFFVGo79CCuc52xgcys= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +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= +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/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/kyaml/comments/comments.go b/kyaml/comments/comments.go new file mode 100644 index 000000000..e7547f15e --- /dev/null +++ b/kyaml/comments/comments.go @@ -0,0 +1,56 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package comments + +import ( + "sigs.k8s.io/kustomize/kyaml/openapi" + "sigs.k8s.io/kustomize/kyaml/yaml" + "sigs.k8s.io/kustomize/kyaml/yaml/walk" +) + +// CopyComments recursively copies the comments on fields in from to fields in to +func CopyComments(from, to *yaml.RNode) error { + // walk the fields copying comments + _, err := walk.Walker{ + Sources: []*yaml.RNode{from, to}, + Visitor: &copier{}, + VisitKeysAsScalars: true}.Walk() + return err +} + +// copier implements walk.Visitor, and copies comments to fields shared between 2 instances +// of a resource +type copier struct{} + +func (c *copier) VisitMap(s walk.Sources, _ *openapi.ResourceSchema) (*yaml.RNode, error) { + copy(s.Dest(), s.Origin()) + return s.Dest(), nil +} + +func (c *copier) VisitScalar(s walk.Sources, _ *openapi.ResourceSchema) (*yaml.RNode, error) { + copy(s.Dest(), s.Origin()) + return s.Dest(), nil +} + +func (c *copier) VisitList(s walk.Sources, _ *openapi.ResourceSchema, _ walk.ListKind) ( + *yaml.RNode, error) { + copy(s.Dest(), s.Origin()) + return s.Dest(), nil +} + +// copy copies the comment from one field to another +func copy(from, to *yaml.RNode) { + if from == nil || to == nil { + return + } + if from.YNode().LineComment != "" { + to.YNode().LineComment = from.YNode().LineComment + } + if from.YNode().HeadComment != "" { + to.YNode().HeadComment = from.YNode().HeadComment + } + if from.YNode().FootComment != "" { + to.YNode().FootComment = from.YNode().FootComment + } +} diff --git a/kyaml/fieldmeta/fieldmeta.go b/kyaml/fieldmeta/fieldmeta.go index 6006f4466..a6a34cccc 100644 --- a/kyaml/fieldmeta/fieldmeta.go +++ b/kyaml/fieldmeta/fieldmeta.go @@ -140,11 +140,11 @@ func (it FieldValueType) Validate(value string) error { func (it FieldValueType) Tag() string { switch it { case String: - return "!!str" + return yaml.StringTag case Bool: - return "!!bool" + return yaml.BoolTag case Int: - return "!!int" + return yaml.IntTag } return "" } @@ -152,17 +152,17 @@ func (it FieldValueType) Tag() string { func (it FieldValueType) TagForValue(value string) string { switch it { case String: - return "!!str" + return yaml.StringTag case Bool: if _, err := strconv.ParseBool(string(it)); err != nil { return "" } - return "!!bool" + return yaml.BoolTag case Int: if _, err := strconv.ParseInt(string(it), 0, 32); err != nil { return "" } - return "!!int" + return yaml.IntTag } return "" } diff --git a/kyaml/go.mod b/kyaml/go.mod index 33eefaad1..fa1886b41 100644 --- a/kyaml/go.mod +++ b/kyaml/go.mod @@ -6,9 +6,11 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/go-errors/errors v1.0.1 github.com/go-openapi/spec v0.19.5 + github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d github.com/sergi/go-diff v1.1.0 github.com/stretchr/testify v1.4.0 github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect gopkg.in/yaml.v2 v2.2.7 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 diff --git a/kyaml/go.sum b/kyaml/go.sum index 05b1b96aa..cdffe44f9 100644 --- a/kyaml/go.sum +++ b/kyaml/go.sum @@ -1,10 +1,21 @@ +github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= 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/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +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/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/dustmop/soup v1.1.2-0.20190516214245-38228baa104e h1:44fmjqDtdCiUNlSjJVp+w1AOs6na3Y6Ai0aIeseFjkI= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= @@ -25,23 +36,37 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/paulmach/orb v0.1.3 h1:Wa1nzU269Zv7V9paVEY1COWW8FCqv4PC/KJRbJSimpM= +github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= 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/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA= +github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +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/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/kyaml/openapi/openapi.go b/kyaml/openapi/openapi.go index 11c9d2422..5ff9507d3 100644 --- a/kyaml/openapi/openapi.go +++ b/kyaml/openapi/openapi.go @@ -210,6 +210,10 @@ func (rs *ResourceSchema) Elements() *ResourceSchema { // either not an array, or array has multiple types return nil } + if rs == nil || rs.Schema == nil || rs.Schema.Items == nil { + // no-scheme for the items + return nil + } s := *rs.Schema.Items.Schema for s.Ref.String() != "" { sc, e := Resolve(&s.Ref) diff --git a/kyaml/setters2/add.go b/kyaml/setters2/add.go index 83575c070..93bc47f5f 100644 --- a/kyaml/setters2/add.go +++ b/kyaml/setters2/add.go @@ -43,9 +43,14 @@ func (a *Add) Filter(object *yaml.RNode) (*yaml.RNode, error) { return object, accept(a, object) } +func (a *Add) visitSequence(_ *yaml.RNode, _ string, _ *openapi.ResourceSchema) error { + // no-op + return nil +} + // visitScalar implements visitor // visitScalar will set the field metadata on each scalar field whose name + value match -func (a *Add) visitScalar(object *yaml.RNode, p string) error { +func (a *Add) visitScalar(object *yaml.RNode, p string, _ *openapi.ResourceSchema) error { // check if the field matches if a.FieldName != "" && !strings.HasSuffix(p, a.FieldName) { return nil @@ -96,6 +101,9 @@ type SetterDefinition struct { // Value is the value of the setter. Value string `yaml:"value"` + // ListValues are the value of a list setter. + ListValues []string `yaml:"listValues,omitempty"` + // SetBy is the person or role that last set the value. SetBy string `yaml:"setBy,omitempty"` diff --git a/kyaml/setters2/set.go b/kyaml/setters2/set.go index b75f61d56..11b0f5714 100644 --- a/kyaml/setters2/set.go +++ b/kyaml/setters2/set.go @@ -8,7 +8,10 @@ import ( "github.com/go-openapi/spec" "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" "sigs.k8s.io/kustomize/kyaml/openapi" + "sigs.k8s.io/kustomize/kyaml/sets" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -27,10 +30,36 @@ func (s *Set) Filter(object *yaml.RNode) (*yaml.RNode, error) { return object, accept(s, object) } +// visitSequence will perform setters for sequences +func (s *Set) visitSequence(object *yaml.RNode, p string, schema *openapi.ResourceSchema) error { + ext, err := getExtFromComment(schema) + if err != nil { + return err + } + if ext == nil || ext.Setter == nil || ext.Setter.Name != s.Name || + len(ext.Setter.ListValues) == 0 { + // setter was not invoked for this sequence + return nil + } + s.Count++ + + // set the values on the sequences + var elements []*yaml.Node + for i := range ext.Setter.ListValues { + v := ext.Setter.ListValues[i] + n := yaml.NewScalarRNode(v).YNode() + n.Style = yaml.DoubleQuotedStyle + elements = append(elements, n) + } + object.YNode().Content = elements + object.YNode().Style = yaml.FoldedStyle + return nil +} + // visitScalar -func (s *Set) visitScalar(object *yaml.RNode, p string) error { +func (s *Set) visitScalar(object *yaml.RNode, p string, schema *openapi.ResourceSchema) error { // get the openAPI for this field describing how to apply the setter - ext, sch, err := getExtFromComment(object) + ext, err := getExtFromComment(schema) if err != nil { return err } @@ -39,13 +68,13 @@ func (s *Set) visitScalar(object *yaml.RNode, p string) error { } // perform a direct set of the field if it matches - if s.set(object, ext, sch) { + if s.set(object, ext, schema.Schema) { s.Count++ return nil } // perform a substitution of the field if it matches - sub, err := s.substitute(object, ext, sch) + sub, err := s.substitute(object, ext, schema.Schema) if err != nil { return err } @@ -111,7 +140,7 @@ func (s *Set) substitute(field *yaml.RNode, ext *cliExtension, _ *spec.Schema) ( field.YNode().Value = p // substitutions are always strings - field.YNode().Tag = "!!str" + field.YNode().Tag = yaml.StringTag return true, nil } @@ -147,6 +176,9 @@ type SetOpenAPI struct { // Value is the current value of the setter Value string `yaml:"value"` + // ListValue is the current value for a list of items + ListValues []string `yaml:"listValue"` + Description string `yaml:"description"` SetBy string `yaml:"setBy"` @@ -159,8 +191,14 @@ func (s SetOpenAPI) UpdateFile(path string) error { func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) { key := SetterDefinitionPrefix + s.Name - def, err := object.Pipe(yaml.Lookup( - "openAPI", "definitions", key, "x-k8s-cli", "setter")) + oa, err := object.Pipe(yaml.Lookup("openAPI", "definitions", key)) + if err != nil { + return nil, err + } + if oa == nil { + return nil, errors.Errorf("no setter %s found", s.Name) + } + def, err := oa.Pipe(yaml.Lookup("x-k8s-cli", "setter")) if err != nil { return nil, err } @@ -168,9 +206,16 @@ func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) { return nil, errors.Errorf("no setter %s found", s.Name) } + // record the OpenAPI type for the setter + var t string + if n := oa.Field("type"); n != nil { + t = n.Value.YNode().Value + } + // if the setter contains an enumValues map, then ensure the set value appears // as a key in the map - if values, err := def.Pipe(yaml.Lookup("enumValues")); err != nil { + if values, err := def.Pipe( + yaml.Lookup("enumValues")); err != nil { // error looking up the enumValues return nil, err } else if values != nil { @@ -202,11 +247,40 @@ func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) { // values are always represented as strings the OpenAPI // since the are unmarshalled into strings. Use double quote style to // ensure this consistently. - v.YNode().Tag = "!!str" + v.YNode().Tag = yaml.StringTag v.YNode().Style = yaml.DoubleQuotedStyle - if err := def.PipeE(&yaml.FieldSetter{Name: "value", Value: v}); err != nil { - return nil, err + if t != "array" { + // set a scalar value + if err := def.PipeE(&yaml.FieldSetter{Name: "value", Value: v}); err != nil { + return nil, err + } + } else { + // set a list value + if err := def.PipeE(&yaml.FieldClearer{Name: "value"}); err != nil { + return nil, err + } + // create the list values + var elements []*yaml.Node + n := yaml.NewScalarRNode(s.Value).YNode() + n.Tag = yaml.StringTag + n.Style = yaml.DoubleQuotedStyle + elements = append(elements, n) + for i := range s.ListValues { + v := s.ListValues[i] + n := yaml.NewScalarRNode(v).YNode() + n.Style = yaml.DoubleQuotedStyle + elements = append(elements, n) + } + l := yaml.NewRNode(&yaml.Node{ + Kind: yaml.SequenceNode, + Content: elements, + }) + + def.YNode().Style = yaml.FoldedStyle + if err := def.PipeE(&yaml.FieldSetter{Name: "listValues", Value: l}); err != nil { + return nil, err + } } if err := def.PipeE(&yaml.FieldSetter{Name: "setBy", StringValue: s.SetBy}); err != nil { @@ -226,3 +300,39 @@ func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) { return object, nil } + +// SetAll applies the set filter for all yaml nodes and only returns the nodes whose +// corresponding file has at least one node with input setter +func SetAll(s *Set) kio.Filter { + return kio.FilterFunc(func(nodes []*yaml.RNode) ([]*yaml.RNode, error) { + filesToUpdate := sets.String{} + // for each node record the set fields count before and after filter is applied and + // store the corresponding file paths if there is an increment in setters count + for i := range nodes { + preCount := s.Count + _, err := s.Filter(nodes[i]) + if err != nil { + return nil, errors.Wrap(err) + } + if s.Count > preCount { + path, _, err := kioutil.GetFileAnnotations(nodes[i]) + if err != nil { + return nil, errors.Wrap(err) + } + filesToUpdate.Insert(path) + } + } + var nodesInUpdatedFiles []*yaml.RNode + // return only the nodes whose corresponding file has at least one node with input setter + for i := range nodes { + path, _, err := kioutil.GetFileAnnotations(nodes[i]) + if err != nil { + return nil, errors.Wrap(err) + } + if filesToUpdate.Has(path) { + nodesInUpdatedFiles = append(nodesInUpdatedFiles, nodes[i]) + } + } + return nodesInUpdatedFiles, nil + }) +} diff --git a/kyaml/setters2/set_test.go b/kyaml/setters2/set_test.go index 3414cf10d..a3dece385 100644 --- a/kyaml/setters2/set_test.go +++ b/kyaml/setters2/set_test.go @@ -595,6 +595,76 @@ spec: containers: - name: nginx image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} + `, + }, + { + name: "set-args-list", + setter: "args", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.args: + x-k8s-cli: + type: array + setter: + name: args + listValues: ["1", "2", "3"] + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + # {"$ref": "#/definitions/io.k8s.cli.setters.args"} + replicas: [] + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + # {"$ref": "#/definitions/io.k8s.cli.setters.args"} + replicas: + - "1" + - "2" + - "3" + `, + }, + { + name: "set-args-list-replace", + setter: "args", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.args: + x-k8s-cli: + type: array + setter: + name: args + listValues: ["1", "2", "3"] + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + # {"$ref": "#/definitions/io.k8s.cli.setters.args"} + replicas: ["4", "5"] + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + # {"$ref": "#/definitions/io.k8s.cli.setters.args"} + replicas: + - "1" + - "2" + - "3" `, }, } @@ -632,6 +702,160 @@ spec: } } +func TestSet_SetAll(t *testing.T) { + var tests = []struct { + name string + description string + setter string + openapi string + input []string + expected []string + }{ + { + name: "set-replicas-same-file", + setter: "replicas", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "4" + `, + input: []string{` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'cluster.yaml' +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + `, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment2 + annotations: + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'cluster.yaml' +spec: + replicas: 10 + `}, + expected: []string{` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'cluster.yaml' +spec: + replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + `, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment2 + annotations: + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'cluster.yaml' +spec: + replicas: 10 + `}, + }, + { + name: "set-replicas-different-file", + setter: "replicas", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "4" + `, + input: []string{` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'cluster.yaml' +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + `, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment2 + annotations: + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'another_cluster.yaml' +spec: + replicas: 10 + `}, + expected: []string{` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'cluster.yaml' +spec: + replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + `}, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + // reset the openAPI afterward + defer openapi.ResetOpenAPI() + initSchema(t, test.openapi) + + // parse the input to be modified + var inputNodes []*yaml.RNode + for _, s := range test.input { + r, err := yaml.Parse(s) + if !assert.NoError(t, err) { + t.FailNow() + } + inputNodes = append(inputNodes, r) + } + + // invoke the setter + instance := &Set{Name: test.setter} + result, err := SetAll(instance).Filter(inputNodes) + if !assert.NoError(t, err) { + t.FailNow() + } + + // compare the actual and expected output + if !assert.NoError(t, err) { + t.FailNow() + } + if !assert.Equal(t, len(result), len(test.expected)) { + t.FailNow() + } + + for i := range result { + actual, _ := result[i].String() + actual = strings.TrimSpace(actual) + expected := strings.TrimSpace(test.expected[i]) + if !assert.Equal(t, expected, actual) { + t.FailNow() + } + } + }) + } +} + // initSchema initializes the openAPI with the definitions from s func initSchema(t *testing.T, s string) { // parse out the schema from the input openAPI @@ -678,6 +902,7 @@ func TestSetOpenAPI_Filter(t *testing.T) { name string setter string value string + values []string input string expected string description string @@ -1004,6 +1229,33 @@ openAPI: value: "2" `, }, + + { + name: "set-args-list", + setter: "args", + value: "2", + values: []string{"3", "4"}, + input: ` +openAPI: + definitions: + io.k8s.cli.setters.args: + type: array + x-k8s-cli: + setter: + name: args + listValues: ["1"] + `, + expected: ` +openAPI: + definitions: + io.k8s.cli.setters.args: + type: array + x-k8s-cli: + setter: + name: args + listValues: ["2", "3", "4"] +`, + }, } for i := range tests { test := tests[i] @@ -1015,7 +1267,7 @@ openAPI: // invoke the setter instance := &SetOpenAPI{ - Name: test.setter, Value: test.value, + Name: test.setter, Value: test.value, ListValues: test.values, SetBy: test.setBy, Description: test.description} result, err := instance.Filter(in) if test.err != "" { diff --git a/kyaml/setters2/settersutil/fieldsetter.go b/kyaml/setters2/settersutil/fieldsetter.go index bf2b44fa6..13361137f 100644 --- a/kyaml/setters2/settersutil/fieldsetter.go +++ b/kyaml/setters2/settersutil/fieldsetter.go @@ -7,6 +7,7 @@ import ( "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/setters2" + "sigs.k8s.io/kustomize/kyaml/yaml" ) // FieldSetter sets the value for a field setter. @@ -17,16 +18,35 @@ type FieldSetter struct { // Value is the value to set Value string + // ListValues contains a list of values to set on a Sequence + ListValues []string + Description string SetBy string + + Count int + + OpenAPIPath string + + ResourcesPath string +} + +func (fs *FieldSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { + fs.Count, _ = fs.Set(fs.OpenAPIPath, fs.ResourcesPath) + return nil, nil } // Set updates the OpenAPI definitions and resources with the new setter value func (fs FieldSetter) Set(openAPIPath, resourcesPath string) (int, error) { // Update the OpenAPI definitions soa := setters2.SetOpenAPI{ - Name: fs.Name, Value: fs.Value, Description: fs.Description, SetBy: fs.SetBy} + Name: fs.Name, + Value: fs.Value, + ListValues: fs.ListValues, + Description: fs.Description, + SetBy: fs.SetBy, + } if err := soa.UpdateFile(openAPIPath); err != nil { return 0, err } @@ -37,11 +57,13 @@ func (fs FieldSetter) Set(openAPIPath, resourcesPath string) (int, error) { } // Update the resources with the new value - inout := &kio.LocalPackageReadWriter{PackagePath: resourcesPath} + // Set NoDeleteFiles to true as SetAll will return only the nodes of files which should be updated and + // hence, rest of the files should not be deleted + inout := &kio.LocalPackageReadWriter{PackagePath: resourcesPath, NoDeleteFiles: true} s := &setters2.Set{Name: fs.Name} err := kio.Pipeline{ Inputs: []kio.Reader{inout}, - Filters: []kio.Filter{kio.FilterAll(s)}, + Filters: []kio.Filter{setters2.SetAll(s)}, Outputs: []kio.Writer{inout}, }.Execute() return s.Count, err diff --git a/kyaml/setters2/types.go b/kyaml/setters2/types.go index b896a2cd7..5b489bcb1 100644 --- a/kyaml/setters2/types.go +++ b/kyaml/setters2/types.go @@ -8,9 +8,7 @@ import ( "github.com/go-openapi/spec" "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/yaml" ) type cliExtension struct { @@ -21,6 +19,7 @@ type cliExtension struct { type setter struct { Name string `yaml:"name,omitempty" json:"name,omitempty"` Value string `yaml:"value,omitempty" json:"value,omitempty"` + ListValues []string `yaml:"listValues,omitempty" json:"listValues,omitempty"` EnumValues map[string]string `yaml:"enumValues,omitempty" json:"enumValues,omitempty"` } @@ -57,32 +56,17 @@ func getExtFromSchema(schema *spec.Schema) (*cliExtension, error) { // getExtFromComment returns the cliExtension openAPI extension if it is present as // a comment on the field. -func getExtFromComment(object *yaml.RNode) (*cliExtension, *spec.Schema, error) { - // TODO(pwittrock): also use path to the field to get openapi, not just comments - // parse comment containing the extended openapi for this field - fm := fieldmeta.FieldMeta{} - if err := fm.Read(object); err != nil { - return nil, nil, errors.Wrap(err) - } - if fm.Schema.Ref.String() == "" { - return nil, nil, nil - } - - // resolve the comment reference to the extended openapi definitions - r, err := openapi.Resolve(&fm.Schema.Ref) - if err != nil { - return nil, nil, errors.Wrap(err) - } - if r == nil { +func getExtFromComment(schema *openapi.ResourceSchema) (*cliExtension, error) { + if schema == nil { // no schema found // TODO(pwittrock): should this be an error if it doesn't resolve? - return nil, nil, nil + return nil, nil } // get the cli extension from the openapi (contains setter information) - ext, err := getExtFromSchema(r) + ext, err := getExtFromSchema(schema.Schema) if err != nil { - return nil, nil, errors.Wrap(err) + return nil, errors.Wrap(err) } - return ext, r, nil + return ext, nil } diff --git a/kyaml/setters2/walk.go b/kyaml/setters2/walk.go index d084842ce..786a5b815 100644 --- a/kyaml/setters2/walk.go +++ b/kyaml/setters2/walk.go @@ -4,6 +4,8 @@ package setters2 import ( + "sigs.k8s.io/kustomize/kyaml/fieldmeta" + "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -13,33 +15,100 @@ type visitor interface { // visitScalar is called for each scalar field value on a resource // node is the scalar field value // path is the path to the field; path elements are separated by '.' - visitScalar(node *yaml.RNode, path string) error + // oa is the OpenAPI schema for the field + visitScalar(node *yaml.RNode, path string, oa *openapi.ResourceSchema) error + + // visitSequence is called for each sequence field value on a resource + // node is the sequence field value + // path is the path to the field + // oa is the OpenAPI schema for the field + visitSequence(node *yaml.RNode, path string, oa *openapi.ResourceSchema) error } // accept invokes the appropriate function on v for each field in object func accept(v visitor, object *yaml.RNode) error { - return acceptImpl(v, object, "") + // get the OpenAPI for the type if it exists + oa := getSchema(object, nil, "") + return acceptImpl(v, object, "", oa) } // acceptImpl implements accept using recursion -func acceptImpl(v visitor, object *yaml.RNode, p string) error { +func acceptImpl(v visitor, object *yaml.RNode, p string, oa *openapi.ResourceSchema) error { switch object.YNode().Kind { case yaml.DocumentNode: // Traverse the child of the document return accept(v, yaml.NewRNode(object.YNode())) case yaml.MappingNode: return object.VisitFields(func(node *yaml.MapNode) error { + // get the schema for the field and propagate it + oa = getSchema(node.Key, oa, node.Key.YNode().Value) // Traverse each field value - return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value) + return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value, oa) }) case yaml.SequenceNode: + // get the schema for the sequence node, use the schema provided if not present + // on the field + if err := v.visitSequence(object, p, oa); err != nil { + return err + } + // get the schema for the elements + oa = getSchema(object, oa, "") return object.VisitElements(func(node *yaml.RNode) error { // Traverse each list element - return acceptImpl(v, node, p) + return acceptImpl(v, node, p, oa) }) case yaml.ScalarNode: // Visit the scalar field - return v.visitScalar(object, p) + oa = getSchema(object, oa, "") + return v.visitScalar(object, p, oa) } return nil } + +// getSchema returns OpenAPI schema for an RNode or field of the +// RNode. It will overriding the provide schema with field specific values +// if they are found +// r is the Node to get the Schema for +// s is the provided schema for the field if known +// field is the name of the field +func getSchema(r *yaml.RNode, s *openapi.ResourceSchema, field string) *openapi.ResourceSchema { + // get the override schema if it exists on the field + fm := fieldmeta.FieldMeta{} + if err := fm.Read(r); err == nil && !fm.IsEmpty() { + // per-field schema, this is fine + if fm.Schema.Ref.String() != "" { + // resolve the reference + s, err := openapi.Resolve(&fm.Schema.Ref) + if err == nil && s != nil { + fm.Schema = *s + } + } + return &openapi.ResourceSchema{Schema: &fm.Schema} + } + + // get the schema for a field of the node if the field is provided + if s != nil && field != "" { + return s.Field(field) + } + + // get the schema for the elements if this is a list + if s != nil && r.YNode().Kind == yaml.SequenceNode { + return s.Elements() + } + + // use the provided schema if present + if s != nil { + return s + } + + if yaml.IsEmpty(r) { + return nil + } + + // lookup the schema for the type + m, _ := r.GetMeta() + if m.Kind == "" || m.APIVersion == "" { + return nil + } + return openapi.SchemaForResourceType(yaml.TypeMeta{Kind: m.Kind, APIVersion: m.APIVersion}) +} diff --git a/kyaml/starlark/doc.go b/kyaml/starlark/doc.go new file mode 100644 index 000000000..70f0a2c7b --- /dev/null +++ b/kyaml/starlark/doc.go @@ -0,0 +1,36 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package starlark contains a kio.Filter which can be applied to resources to transform +// them through starlark program. +// +// Starlark has become a popular runtime embedding in go programs, especially for Kubernetes +// and data processing. +// Examples: https://github.com/cruise-automation/isopod, https://qri.io/docs/starlark/starlib, +// https://github.com/stripe/skycfg, https://github.com/k14s/ytt +// +// The resources are provided to the starlark program through the global variable "resourceList". +// "resourceList" is a dictionary containing an "items" field with a list of resources. +// The starlark modified "resourceList" is the Filter output. +// +// After being run through the starlark program, the filter will copy the comments from the input +// resources to restore them -- due to them being dropped as a result of serializing the resources +// as starlark values. +// +// "resourceList" may also contain a "functionConfig" entry to configure the starlark script itself. +// Changes made by the starlark program to the "functionConfig" will be reflected in the +// Filter.FunctionConfig value. +// +// The Filter will also format the output so that output has the preferred field ordering +// rather than an alphabetical field ordering. +// +// The resourceList variable adheres to the kustomize function spec as specified by: +// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md +// +// All items in the resourceList are resources represented as starlark dictionaries/ +// The items in the resourceList respect the io spec specified by: +// https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/config-io.md +// +// The starlark language spec can be found here: +// https://github.com/google/starlark-go/blob/master/doc/spec.md +package starlark diff --git a/kyaml/starlark/example_test.go b/kyaml/starlark/example_test.go new file mode 100644 index 000000000..c2ac8ef31 --- /dev/null +++ b/kyaml/starlark/example_test.go @@ -0,0 +1,290 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package starlark_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/starlark" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func ExampleFilter_Filter() { + // input contains the items that will provided to the starlark program + input := bytes.NewBufferString(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-1 +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-1"} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-2 +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-2"} +`) + + // fltr transforms the input using a starlark program + fltr := &starlark.Filter{ + Name: "annotate", + Program: ` +def run(items): + for item in items: + item["metadata"]["annotations"]["foo"] = "bar" + +run(resourceList["items"]) +`, + } + + // output contains the transformed resources + output := &bytes.Buffer{} + + // run the fltr against the inputs using a kio.Pipeline + err := kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: input}}, + Filters: []kio.Filter{fltr}, + Outputs: []kio.Writer{&kio.ByteWriter{Writer: output}}}.Execute() + if err != nil { + log.Fatal(err) + } + + fmt.Println(output.String()) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: deployment-1 + // annotations: + // foo: bar + // spec: + // template: + // spec: + // containers: + // - name: nginx + // image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-1"} + //--- + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: deployment-2 + // annotations: + // foo: bar + // spec: + // template: + // spec: + // containers: + // - name: nginx + // image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-2"} +} + +func ExampleFilter_Filter_functionConfig() { + // input contains the items that will provided to the starlark program + input := bytes.NewBufferString(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-1 +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-1"} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-2 +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-2"} +`) + + fc, err := yaml.Parse(` +kind: AnnotationSetter +spec: + value: "hello world" +`) + if err != nil { + log.Fatal(err) + } + + // fltr transforms the input using a starlark program + fltr := &starlark.Filter{ + Name: "annotate", + Program: ` +def run(items, value): + for item in items: + item["metadata"]["annotations"]["foo"] = value + +run(resourceList["items"], resourceList["functionConfig"]["spec"]["value"]) +`, + FunctionConfig: fc, + } + + // output contains the transformed resources + output := &bytes.Buffer{} + + // run the fltr against the inputs using a kio.Pipeline + err = kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: input}}, + Filters: []kio.Filter{fltr}, + Outputs: []kio.Writer{&kio.ByteWriter{Writer: output}}}.Execute() + if err != nil { + log.Fatal(err) + } + + fmt.Println(output.String()) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: deployment-1 + // annotations: + // foo: hello world + // spec: + // template: + // spec: + // containers: + // - name: nginx + // image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-1"} + //--- + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: deployment-2 + // annotations: + // foo: hello world + // spec: + // template: + // spec: + // containers: + // - name: nginx + // image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-2"} +} + +// ExampleFilter_Filter_file applies a starlark program in a local file to a collection of +// resource configuration read from a directory. +func ExampleFilter_Filter_file() { + // setup the configuration + d, err := ioutil.TempDir("", "") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(d) + + err = ioutil.WriteFile(filepath.Join(d, "deploy1.yaml"), []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-1 +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-1"} +`), 0600) + if err != nil { + log.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(d, "deploy2.yaml"), []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-2 +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-2"} +`), 0600) + if err != nil { + log.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(d, "annotate.star"), []byte(` +def run(items): + for item in items: + item["metadata"]["annotations"]["foo"] = "bar" + +run(resourceList["items"]) +`), 0600) + if err != nil { + log.Fatal(err) + } + + fltr := &starlark.Filter{ + Name: "annotate", + Path: filepath.Join(d, "annotate.star"), + } + + // output contains the transformed resources + output := &bytes.Buffer{} + + // run the fltr against the inputs using a kio.Pipeline + err = kio.Pipeline{ + Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: d}}, + Filters: []kio.Filter{fltr}, + Outputs: []kio.Writer{&kio.ByteWriter{ + Writer: output, + ClearAnnotations: []string{"config.kubernetes.io/path"}, + }}}.Execute() + if err != nil { + log.Fatal(err) + } + + fmt.Println(output.String()) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: deployment-1 + // annotations: + // foo: bar + // spec: + // template: + // spec: + // containers: + // - name: nginx + // image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-1"} + //--- + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: deployment-2 + // annotations: + // foo: bar + // spec: + // template: + // spec: + // containers: + // - name: nginx + // image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-2"} +} diff --git a/kyaml/starlark/starlark.go b/kyaml/starlark/starlark.go new file mode 100644 index 000000000..b855098dd --- /dev/null +++ b/kyaml/starlark/starlark.go @@ -0,0 +1,251 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package starlark + +import ( + "fmt" + "io/ioutil" + "net/http" + "strconv" + + "github.com/qri-io/starlib/util" + "go.starlark.net/starlark" + "sigs.k8s.io/kustomize/kyaml/comments" + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/kio/filters" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// Filter transforms a set of resources through the provided program +type Filter struct { + Name string + + // Program is a starlark script which will be run against the resources + Program string + + // URL is the url of a starlark program to fetch and run + URL string + + // Path is the path to a starlark program to read and run + Path string + + // FunctionConfig is the value to be provided for resourceList.functionConfig as specified by + // https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md. + FunctionConfig *yaml.RNode +} + +func (sf *Filter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { + if sf.URL != "" && sf.Path != "" || + sf.URL != "" && sf.Program != "" || + sf.Path != "" && sf.Program != "" { + return nil, errors.Errorf("Filter Path, Program and URL are mutually exclusive") + } + + // read the program from a file + if sf.Path != "" { + b, err := ioutil.ReadFile(sf.Path) + if err != nil { + return nil, err + } + sf.Program = string(b) + } + + // read the program from a URL + if sf.URL != "" { + err := func() error { + resp, err := http.Get(sf.URL) + if err != nil { + return err + } + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + sf.Program = string(b) + return nil + }() + if err != nil { + return nil, err + } + } + + // retain map of inputs to outputs by id so if the name is changed by the + // starlark program, we are able to match the same resources + value, ids, err := sf.inputToResourceList(input) + if err != nil { + return nil, errors.Wrap(err) + } + + // run the starlark as program as transformation function + thread := &starlark.Thread{Name: sf.Name} + predeclared := starlark.StringDict{"resourceList": value} + _, err = starlark.ExecFile(thread, sf.Name, sf.Program, predeclared) + if err != nil { + return nil, errors.Wrap(err) + } + + results, err := sf.resourceListToOutput(value, ids) + if err != nil { + return nil, errors.Wrap(err) + } + + // starlark will serialize the resources sorting the fields alphabetically, + // format them to have a better ordering + return filters.FormatFilter{}.Filter(results) +} + +// tuple maps an input resource to the output resource +type tuple struct { + // in is the RNode provided to the starlark program + in *yaml.RNode + // out is the RNode emitted by the starlark program with the id matching in + out *yaml.RNode +} + +// inputToResourceList transforms input into a starlark.Value +func (sf *Filter) inputToResourceList( + input []*yaml.RNode) (starlark.Value, map[int]*tuple, error) { + var id int + ids := map[int]*tuple{} + + // convert into a ResourceList which will be converted to a starlark dictionary + // create the ResourceList + resourceList, err := yaml.Parse(`kind: ResourceList`) + if err != nil { + return nil, nil, errors.Wrap(err) + } + + // set the functionConfig + if sf.FunctionConfig != nil { + if err := resourceList.PipeE( + yaml.FieldSetter{Name: "functionConfig", Value: sf.FunctionConfig}); err != nil { + return nil, nil, err + } + } + + // the inputs should be provided as the list "items" + items, err := resourceList.Pipe(yaml.LookupCreate(yaml.SequenceNode, "items")) + if err != nil { + return nil, nil, errors.Wrap(err) + } + // add the input as items, give each resource an id + for i := range input { + item := input[i] + + // create an id for tracking the resource through the program + err := item.PipeE(yaml.SetAnnotation("config.k8s.io/id", fmt.Sprintf("%d", id))) + if err != nil { + return nil, nil, errors.Wrap(err) + } + ids[id] = &tuple{in: item} + id++ + + items.YNode().Content = append(items.YNode().Content, item.YNode()) + } + + // convert the ResourceList into a starlark dictionary by + // first converting it into a map[string]interface{} + s, err := resourceList.String() + if err != nil { + return nil, nil, errors.Wrap(err) + } + var in map[string]interface{} + if err := yaml.Unmarshal([]byte(s), &in); err != nil { + return nil, nil, errors.Wrap(err) + } + value, err := util.Marshal(in) + if err != nil { + return nil, nil, errors.Wrap(err) + } + return value, ids, err +} + +// resourceListToOutput converts the output of the starlark program to the filter output +func (sf *Filter) resourceListToOutput( + value starlark.Value, ids map[int]*tuple) ([]*yaml.RNode, error) { + // convert the modified resourceList back into a slice of RNodes + // by first converting to a map[string]interface{} + out, err := util.Unmarshal(value) + if err != nil { + return nil, errors.Wrap(err) + } + o := out.(map[string]interface{}) + + // parse the function config + if _, found := o["functionConfig"]; found { + fc := (o["functionConfig"].(map[string]interface{})) + b, err := yaml.Marshal(fc) + if err != nil { + return nil, errors.Wrap(err) + } + sf.FunctionConfig, err = yaml.Parse(string(b)) + if err != nil { + return nil, errors.Wrap(err) + } + } + + // parse the items + // copy the items out of the ResourceList, and into the Filter output + var results []*yaml.RNode + it := (o["items"].([]interface{})) + for i := range it { + // convert the resource back to the native yaml form + b, err := yaml.Marshal(it[i]) + if err != nil { + return nil, errors.Wrap(err) + } + node, err := yaml.Parse(string(b)) + if err != nil { + return nil, errors.Wrap(err) + } + + // match it to an input + idS, err := node.Pipe(yaml.GetAnnotation("config.k8s.io/id")) + if err != nil { + return nil, errors.Wrap(err) + } + if idS == nil { + // no matching input -- new resource + results = append(results, node) + continue + } + + id, err := strconv.Atoi(idS.YNode().Value) + if err != nil { + return nil, errors.Wrap(err) + } + if match, found := ids[id]; found { + // matching resources + match.out = node + } else { + // no matching input with the same id -- new resource + // this may be an error case, the outputs probably shouldn't have ids + // assigned by the starlark program + results = append(results, node) + } + } + + // retain the comments instead of dropping them by copying them from the original inputs + for i := 0; i < len(ids); i++ { + v := ids[i] + if v.out == nil { + continue + } + if err := comments.CopyComments(v.in, v.out); err != nil { + return nil, errors.Wrap(err) + } + results = append(results, v.out) + } + + // delete the ids from resources, these were only to track through the starlark program + // and that is finished. + for i := range results { + err := results[i].PipeE(yaml.ClearAnnotation("config.k8s.io/id")) + if err != nil { + return nil, err + } + } + return results, nil +} diff --git a/kyaml/starlark/starlark_test.go b/kyaml/starlark/starlark_test.go new file mode 100644 index 000000000..310f1a44f --- /dev/null +++ b/kyaml/starlark/starlark_test.go @@ -0,0 +1,389 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package starlark + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" + + "github.com/go-errors/errors" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestFilter_Filter(t *testing.T) { + var tests = []struct { + name string + input string + functionConfig string + script string + expected string + expectedFunctionConfig string + }{ + { + name: "add_annotation", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + script: ` +# set the foo annotation on each resource +def run(r): + for resource in r: + resource["metadata"]["annotations"]["foo"] = "bar" + +run(resourceList["items"]) +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + foo: bar +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + }, + { + name: "update_annotation", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + foo: baz +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + script: ` +# set the foo annotation on each resource +def run(r): + for resource in r: + resource["metadata"]["annotations"]["foo"] = "bar" + +run(resourceList["items"]) +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + foo: bar +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + }, + { + name: "update_annotation_multiple", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 + annotations: + foo: baz +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + script: ` +# set the foo annotation on each resource +def run(r): + for resource in r: + resource["metadata"]["annotations"]["foo"] = "bar" + +run(resourceList["items"]) +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 + annotations: + foo: bar +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 + annotations: + foo: bar +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}`, + }, + { + name: "add_resource", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + script: ` +def run(r): + d = { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx-deployment-2", + }, +} + r.append(d) +run(resourceList["items"]) +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + }, + { + name: "remove_resource", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 +`, + script: ` +def run(r): + r.pop() +run(resourceList["items"]) +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 +`, + }, + { + name: "functionConfig", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + functionConfig: ` +kind: Script +spec: + value: "hello world" +`, + script: ` +# set the foo annotation on each resource +def run(r, an): + for resource in r: + resource["metadata"]["annotations"]["foo"] = an + +an = resourceList["functionConfig"]["spec"]["value"] +run(resourceList["items"], an) +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + foo: hello world +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + expectedFunctionConfig: ` +kind: Script +spec: + value: hello world +`, + }, + + { + name: "functionConfig_update_functionConfig", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + functionConfig: ` +kind: Script +spec: + value: "hello world" +`, + script: ` +# set the foo annotation on each resource +def run(r, an): + for resource in r: + resource["metadata"]["annotations"]["foo"] = an + +an = resourceList["functionConfig"]["spec"]["value"] +run(resourceList["items"], an) +resourceList["functionConfig"]["spec"]["value"] = "updated" +`, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + foo: hello world +spec: + template: + spec: + containers: + - name: nginx + # head comment + image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} +`, + expectedFunctionConfig: ` +kind: Script +spec: + value: updated +`, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + f := &Filter{Name: test.name, Program: test.script} + + if test.functionConfig != "" { + fc, err := yaml.Parse(test.functionConfig) + if !assert.NoError(t, err) { + t.FailNow() + } + f.FunctionConfig = fc + } + + r := &kio.ByteReader{Reader: bytes.NewBufferString(test.input)} + o := &bytes.Buffer{} + w := &kio.ByteWriter{Writer: o} + p := kio.Pipeline{ + Inputs: []kio.Reader{r}, + Filters: []kio.Filter{f}, + Outputs: []kio.Writer{w}, + } + err := p.Execute() + if !assert.NoError(t, err) { + if e, ok := err.(*errors.Error); ok { + fmt.Fprintf(os.Stderr, "%s\n", e.Stack()) + } + t.FailNow() + } + if !assert.Equal(t, strings.TrimSpace(test.expected), strings.TrimSpace(o.String())) { + t.FailNow() + } + + if test.expectedFunctionConfig != "" { + if !assert.Equal(t, + strings.TrimSpace(test.expectedFunctionConfig), + strings.TrimSpace(f.FunctionConfig.MustString())) { + t.FailNow() + } + } + }) + } +} diff --git a/kyaml/yaml/compatibility.go b/kyaml/yaml/compatibility.go index ff64f6f06..628feb0ad 100644 --- a/kyaml/yaml/compatibility.go +++ b/kyaml/yaml/compatibility.go @@ -14,9 +14,9 @@ import ( // typeToTag maps OpenAPI schema types to yaml 1.2 tags var typeToTag = map[string]string{ - "string": "!!str", - "integer": "!!int", - "boolean": "!!bool", + "string": StringTag, + "integer": IntTag, + "boolean": BoolTag, "number": "!!float", } diff --git a/kyaml/yaml/kfns.go b/kyaml/yaml/kfns.go index 84d3f2af7..229c9c4ca 100644 --- a/kyaml/yaml/kfns.go +++ b/kyaml/yaml/kfns.go @@ -3,7 +3,9 @@ package yaml -import "gopkg.in/yaml.v3" +import ( + "gopkg.in/yaml.v3" +) // AnnotationClearer removes an annotation at metadata.annotations. // Returns nil if the annotation or field does not exist. @@ -33,7 +35,7 @@ type AnnotationSetter struct { func (s AnnotationSetter) Filter(rn *RNode) (*RNode, error) { // some tools get confused about the type if annotations are not quoted v := NewScalarRNode(s.Value) - v.YNode().Tag = "!!str" + v.YNode().Tag = StringTag v.YNode().Style = yaml.SingleQuotedStyle return rn.Pipe( PathGetter{Path: []string{"metadata", "annotations"}, Create: yaml.MappingNode}, @@ -80,7 +82,7 @@ type LabelSetter struct { func (s LabelSetter) Filter(rn *RNode) (*RNode, error) { // some tools get confused about the type if labels are not quoted v := NewScalarRNode(s.Value) - v.YNode().Tag = "!!str" + v.YNode().Tag = StringTag v.YNode().Style = yaml.SingleQuotedStyle return rn.Pipe( PathGetter{Path: []string{"metadata", "labels"}, Create: yaml.MappingNode}, diff --git a/kyaml/yaml/types.go b/kyaml/yaml/types.go index cedf1f878..1a2af996f 100644 --- a/kyaml/yaml/types.go +++ b/kyaml/yaml/types.go @@ -588,6 +588,12 @@ func (rn *RNode) VisitFields(fn func(node *MapNode) error) error { return nil } +const ( + StringTag = "!!str" + BoolTag = "!!bool" + IntTag = "!!int" +) + // Elements returns the list of elements in the RNode. // Returns an error for non-SequenceNodes. func (rn *RNode) Elements() ([]*RNode, error) { diff --git a/kyaml/yaml/walk/associative_sequence.go b/kyaml/yaml/walk/associative_sequence.go index 7d4a219f1..72e8e02e7 100644 --- a/kyaml/yaml/walk/associative_sequence.go +++ b/kyaml/yaml/walk/associative_sequence.go @@ -40,6 +40,7 @@ func (l *Walker) walkAssociativeSequence() (*yaml.RNode, error) { } for _, value := range values { val, err := Walker{ + VisitKeysAsScalars: l.VisitKeysAsScalars, InferAssociativeLists: l.InferAssociativeLists, Visitor: l, Schema: s, diff --git a/kyaml/yaml/walk/map.go b/kyaml/yaml/walk/map.go index 188aed9fa..4300205ac 100644 --- a/kyaml/yaml/walk/map.go +++ b/kyaml/yaml/walk/map.go @@ -27,6 +27,32 @@ func (l Walker) walkMap() (*yaml.RNode, error) { // recursively set the field values on the map for _, key := range l.fieldNames() { + if l.VisitKeysAsScalars { + // visit the map keys as if they were scalars, + // this is necessary if doing things such as copying + // comments + var keys []*yaml.RNode + for i := range l.Sources { + // construct the sources from the keys + if l.Sources[i] == nil { + keys = append(keys, nil) + continue + } + field := l.Sources[i].Field(key) + if field == nil || yaml.IsEmpty(field.Key) { + keys = append(keys, nil) + continue + } + keys = append(keys, field.Key) + } + // visit the sources as a scalar + // keys don't have any schema --pass in nil + _, err := l.Visitor.VisitScalar(keys, nil) + if err != nil { + return nil, err + } + } + var s *openapi.ResourceSchema if l.Schema != nil { s = l.Schema.Field(key) @@ -36,6 +62,7 @@ func (l Walker) walkMap() (*yaml.RNode, error) { s = commentSch } val, err := Walker{ + VisitKeysAsScalars: l.VisitKeysAsScalars, InferAssociativeLists: l.InferAssociativeLists, Visitor: l, Schema: s, diff --git a/kyaml/yaml/walk/walk.go b/kyaml/yaml/walk/walk.go index 2040aabd3..c615bc382 100644 --- a/kyaml/yaml/walk/walk.go +++ b/kyaml/yaml/walk/walk.go @@ -32,6 +32,10 @@ type Walker struct { // fields which it doesn't have the schema based on the fields in the // list elements. InferAssociativeLists bool + + // VisitKeysAsScalars if true will call VisitScalar on map entry keys, + // providing nil as the OpenAPI schema. + VisitKeysAsScalars bool } func (l Walker) Kind() yaml.Kind { diff --git a/plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go b/plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go index afdbd257b..abdb3a656 100644 --- a/plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go +++ b/plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go @@ -388,3 +388,45 @@ spec: initContainers: null `) } + +func TestImageTagTransformerTagWithBraces(t *testing.T) { + th := kusttest_test.MakeEnhancedHarness(t). + PrepBuiltin("ImageTagTransformer") + defer th.Reset() + + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: ImageTagTransformer +metadata: + name: notImportantHere +imageTag: + name: some.registry.io/my-image + newTag: "my-fixed-tag" +`, ` +group: apps +apiVersion: v1 +kind: Deployment +metadata: + name: deploy1 +spec: + template: + spec: + containers: + - image: some.registry.io/my-image:{GENERATED_TAG} + name: my-image +`) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: v1 +group: apps +kind: Deployment +metadata: + name: deploy1 +spec: + template: + spec: + containers: + - image: some.registry.io/my-image:my-fixed-tag + name: my-image +`) +} \ No newline at end of file diff --git a/plugin/builtin/imagetagtransformer/go.mod b/plugin/builtin/imagetagtransformer/go.mod index b93a54097..3c5a2f481 100644 --- a/plugin/builtin/imagetagtransformer/go.mod +++ b/plugin/builtin/imagetagtransformer/go.mod @@ -6,3 +6,5 @@ require ( sigs.k8s.io/kustomize/api v0.3.1 sigs.k8s.io/yaml v1.1.0 ) + +replace sigs.k8s.io/kustomize/api => ../../../api diff --git a/plugin/builtin/namespacetransformer/NamespaceTransformer.go b/plugin/builtin/namespacetransformer/NamespaceTransformer.go index dd2540a60..3c5720cb3 100644 --- a/plugin/builtin/namespacetransformer/NamespaceTransformer.go +++ b/plugin/builtin/namespacetransformer/NamespaceTransformer.go @@ -56,7 +56,7 @@ func (p *plugin) Transform(m resmap.ResMap) error { matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals) if len(matches) != 1 { - return fmt.Errorf("namespace tranformation produces ID conflict: %+v", matches) + return fmt.Errorf("namespace transformation produces ID conflict: %+v", matches) } } return nil diff --git a/releasing/VERSIONS b/releasing/VERSIONS index d438b22e5..878bd9e39 100644 --- a/releasing/VERSIONS +++ b/releasing/VERSIONS @@ -6,7 +6,7 @@ # kyaml version export kyaml_major=0 export kyaml_minor=1 -export kyaml_patch=0 +export kyaml_patch=1 # kstatus version export kstatus_major=0 @@ -21,7 +21,7 @@ export api_patch=3 # cmd/config version export cmd_config_major=0 export cmd_config_minor=1 -export cmd_config_patch=0 +export cmd_config_patch=1 # cmd/kubectl version export cmd_kubectl_major=0 diff --git a/travis/check-go-mod.sh b/travis/check-go-mod.sh index b69a08024..1bfc9e91d 100755 --- a/travis/check-go-mod.sh +++ b/travis/check-go-mod.sh @@ -6,7 +6,7 @@ set -x set -e # verify all modules pass validation -for i in $(find . -name go.mod); do +for i in $(find . -name go.mod -not -path "./hack/*"); do pushd . cd $(dirname $i); go list -m -json all > /dev/null