diff --git a/.github/actions/kustomize/Dockerfile b/.github/actions/kustomize/Dockerfile deleted file mode 100644 index 2ebd633..0000000 --- a/.github/actions/kustomize/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM giantswarm/tiny-tools - -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/kustomize/action.yml b/.github/actions/kustomize/action.yml deleted file mode 100644 index bd53d1d..0000000 --- a/.github/actions/kustomize/action.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: 'kustomize' -description: 'A GitHub Action to run kustomize commands' -author: 'Stefan Prodan' -branding: - icon: 'command' - color: 'blue' -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/kustomize/entrypoint.sh b/.github/actions/kustomize/entrypoint.sh deleted file mode 100644 index db8be7e..0000000 --- a/.github/actions/kustomize/entrypoint.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -l - -VERSION=3.5.4 -curl -sL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${VERSION}/kustomize_v${VERSION}_linux_amd64.tar.gz | tar xz - -mkdir -p $GITHUB_WORKSPACE/bin -cp ./kustomize $GITHUB_WORKSPACE/bin -chmod +x $GITHUB_WORKSPACE/bin/kustomize -ls -lh $GITHUB_WORKSPACE/bin - -echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH -echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH diff --git a/.github/workflows/action-test.yaml b/.github/workflows/action-test.yaml deleted file mode 100644 index 28ec395..0000000 --- a/.github/workflows/action-test.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: action-test - -on: - push: - branches: - - '*' - -jobs: - action: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install Kustomizer - uses: ./action - - name: Run Kustomizer - run: | - kustomizer -v diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 0c17536..c9caaae 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -2,9 +2,9 @@ name: e2e on: pull_request: + branches: [main] push: - branches: - - master + branches: [main] jobs: kind: @@ -22,13 +22,14 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: 1.14.x + go-version: 1.16.x - name: Setup Kubernetes uses: engineerd/setup-kind@v0.5.0 - - name: Setup Kustomize - uses: ./.github/actions/kustomize + with: + version: v0.11.1 + image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6 - name: Run test - run: make test + run: make test-race - name: Check if working tree is dirty run: | if [[ $(git diff --stat) != '' ]]; then @@ -36,18 +37,45 @@ jobs: exit 1 fi - name: Build - run: sudo go build -o ./bin/kustomizer ./cmd/kustomizer - - name: Smoke tests + run: make build + - name: Test apply --kustomize + run: | + ./bin/kustomizer apply -i kustomizer-test -k testdata/kustomize/ --wait --prune + kubectl -n kustomizer-test get tests 2>&1 | grep custom-resource-test1 + - name: Test diff --kustomize + run: | + echo "diff=test" >> testdata/kustomize/test.conf + ./bin/kustomizer diff -i kustomizer-test -k testdata/kustomize/ --prune 2>&1 | grep drifted + - name: Test diff secret marks + run: | + echo "diff=test" >> testdata/kustomize/secret.conf + ./bin/kustomizer diff -i kustomizer-test -k testdata/kustomize/ 2>&1 | grep -F "secret.conf: '******'" + - name: Test apply --filename + run: | + ./bin/kustomizer apply -i kustomizer-demo -f testdata/plain/ --wait --prune + kubectl -n kustomizer-test get svc frontend 2>&1 | grep frontend + - name: Test apply --prune run: | - ./bin/kustomizer apply testdata/plain/ --name=test --revision=1.0.0 - kubectl -n kustomizer-demo get svc frontend 2>&1 | grep frontend rm -rf testdata/plain/frontend - ./bin/kustomizer apply testdata/plain/ --name=test --revision=1.1.0 + ./bin/kustomizer apply -i kustomizer-demo -f testdata/plain/ --wait --prune kubectl -n kustomizer-demo get svc frontend 2>&1 | grep NotFound - ./bin/kustomizer delete --name=test + - name: Test delete --wait + run: | + ./bin/kustomizer delete -i kustomizer-demo --wait + kubectl get ns kustomizer-demo 2>&1 | grep NotFound + ./bin/kustomizer delete -i kustomizer-test --wait + kubectl get crd tests.testing.kustomizer.dev 2>&1 | grep NotFound + - name: Load test apply (110 objects) + run: | + ./bin/kustomizer apply -i load-test -k ./testdata/loadtest/ + - name: Load test delete (110 objects) + run: | + ./bin/kustomizer delete -i load-test + - name: Test staged apply + run: | + ./bin/kustomizer apply -i cert-test -k ./testdata/certs/ --prune --wait + kubectl -n kustomizer-cert-test wait issuers/my-ca-issuer --for=condition=ready --timeout=1m - name: Debug failure if: failure() run: | - kubectl version --client --short kubectl -n default get configmaps -oyaml - kubectl -n kustomizer-demo get all diff --git a/.gitignore b/.gitignore index b9d8154..d21d574 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ -bin/ \ No newline at end of file +bin/ +testbin/ diff --git a/.goreleaser.yml b/.goreleaser.yml index 1dc42c5..440e56e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,18 +16,3 @@ archives: - name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" files: - LICENSE -brews: - - tap: - owner: stefanprodan - name: kustomizer - - folder: Formula - homepage: "https://kustomizer.dev/" - description: "Kustomize build, apply, prune command-line utility." - - dependencies: - - name: kubectl - type: optional - - test: | - system "#{bin}/kustomizer --version" \ No newline at end of file diff --git a/Formula/kustomizer.rb b/Formula/kustomizer.rb deleted file mode 100644 index f1698f0..0000000 --- a/Formula/kustomizer.rb +++ /dev/null @@ -1,34 +0,0 @@ -# This file was generated by GoReleaser. DO NOT EDIT. -class Kustomizer < Formula - desc "Kustomize build, apply, prune command-line utility." - homepage "https://kustomizer.dev/" - version "0.2.1" - bottle :unneeded - - if OS.mac? - url "https://github.com/stefanprodan/kustomizer/releases/download/v0.2.1/kustomizer_0.2.1_darwin_amd64.tar.gz" - sha256 "afb30c0b4caae50d7e8c671990fd7150a0fb4a160f90d16c760a268b092fe300" - elsif OS.linux? - if Hardware::CPU.intel? - url "https://github.com/stefanprodan/kustomizer/releases/download/v0.2.1/kustomizer_0.2.1_linux_amd64.tar.gz" - sha256 "0dcf3a2976fadf5961fc2ca4df914a207d4a483def1ee892d03d41450e1ea345" - end - if Hardware::CPU.arm? - if Hardware::CPU.is_64_bit? - url "https://github.com/stefanprodan/kustomizer/releases/download/v0.2.1/kustomizer_0.2.1_linux_arm64.tar.gz" - sha256 "ce0e546e0f431425587b9630d31fb2735e953981e468b204d6e685b28b93a518" - else - end - end - end - - depends_on "kubectl" => :optional - - def install - bin.install "kustomizer" - end - - test do - system "#{bin}/kustomizer --version" - end -end diff --git a/Makefile b/Makefile index bbb52bd..9442ab6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION?=$(shell grep 'VERSION' cmd/kustomizer/main.go | awk '{ print $$4 }' | tr -d '"') +# Kustomizer test, build, install makefile all: test build @@ -11,14 +11,11 @@ fmt: vet: go vet ./... -test: tidy fmt vet - go test ./... -coverprofile cover.out - build: CGO_ENABLED=0 go build -o ./bin/kustomizer ./cmd/kustomizer install: - go install cmd/kustomizer + go install ./cmd/kustomizer install-dev: CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/kustomizer @@ -26,18 +23,49 @@ install-dev: install-plugin: CGO_ENABLED=0 go build -o /usr/local/bin/kubectl-kustomizer ./cmd/kustomizer -release: - git tag "v$(VERSION)" - git push origin "v$(VERSION)" +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin +ENVTEST_AKUBERNETES_VERSION=latest +install-envtest: setup-envtest + $(SETUP_ENVTEST) use $(ENVTEST_AKUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) + +KUBEBUILDER_ASSETS?="$(shell $(SETUP_ENVTEST) use -i $(ENVTEST_AKUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)" +test: tidy fmt vet install-envtest + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -v -parallel 4 -coverprofile cover.out + +test-race: tidy fmt vet install-envtest + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -v -race -parallel 4 -coverprofile cover.out + +test-bench: + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -v -bench=. -run=none + +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +setup-envtest: +ifeq (, $(shell which setup-envtest)) + @{ \ + set -e ;\ + SETUP_ENVTEST_TMP_DIR=$$(mktemp -d) ;\ + cd $$SETUP_ENVTEST_TMP_DIR ;\ + go mod init tmp ;\ + go get sigs.k8s.io/controller-runtime/tools/setup-envtest@latest ;\ + rm -rf $$SETUP_ENVTEST_TMP_DIR ;\ + } +SETUP_ENVTEST=$(GOBIN)/setup-envtest +else +SETUP_ENVTEST=$(shell which setup-envtest) +endif .PHONY: release-docs release-docs: - git checkout master && git pull; \ + git checkout main && git pull; \ README=$$(cat README.md); \ git checkout gh-pages && git pull; \ echo "$$README" > README.md; \ - git add -A; \ + git add README.md; \ git commit -m "update docs"; \ git push origin gh-pages; \ - git checkout master - + git checkout main diff --git a/README.md b/README.md index a2f7ca6..43c1e20 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,193 @@ # kustomizer [![e2e](https://github.com/stefanprodan/kustomizer/workflows/e2e/badge.svg)](https://github.com/stefanprodan/kustomizer/actions) -[![license](https://img.shields.io/github/license/stefanprodan/kustomizer.svg)](https://github.com/stefanprodan/kustomizer/blob/master/LICENSE) +[![license](https://img.shields.io/github/license/stefanprodan/kustomizer.svg)](https://github.com/stefanprodan/kustomizer/blob/main/LICENSE) [![release](https://img.shields.io/github/release/stefanprodan/kustomizer/all.svg)](https://github.com/stefanprodan/kustomizer/releases) -Kustomizer is a command-line utility for applying kustomizations on Kubernetes clusters. +Kustomizer is a command-line utility for reconciling Kubernetes manifests and Kustomize overlays onto clusters. Kustomizer garbage collector keeps track of the applied resources and prunes the Kubernetes -objects that were previously applied on the cluster but are missing from the current revision. +objects that were previously applied but are missing from the current revision. + +Compared to `kubectl apply`, Kustomizer does things a little different: + +- Applies first custom resource definitions (CRDs) and namespaces, waits for them to register and only then applies the custom resources. +- Skips to apply resources that haven't changed. +- Waits for the applied resources to be fully reconciled (waits for replicasets rollout, ingress and other custom resources to become ready). +- Deletes stale objects like ConfigMap and Secrets generated with Kustomize or other tools. + +Kustomizer relies on Kubernetes API [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) +and [kstatus](https://pkg.go.dev/sigs.k8s.io/cli-utils/pkg/kstatus). ## Install -Download the Kustomizer binary from the -[release page](https://github.com/stefanprodan/kustomizer/releases) -or run [this script](install/README.md): +The Kustomizer CLI is available as a binary executable for all major platforms, +the binaries can be downloaded form GitHub [release page](https://github.com/stefanprodan/kustomizer/releases). + +Install the latest release on macOS or Linux with [this script](install/README.md): ```bash -curl -s https://kustomizer.dev/install/kustomizer.sh | sudo bash +curl -s https://kustomizer.dev/install.sh | bash ``` -Windows users can download the binary from the [release page](https://github.com/stefanprodan/kustomizer/releases). +Kustomizer needs a Kubernetes cluster version **1.18** or later and a valid `kubeconfig` file. -If you want to use kustomizer as a kubectl plugin, rename the binary to `kubectl-kustomizer`: +## Available Commands -```bash -mv /usr/local/bin/kustomizer /usr/local/bin/kubectl-kustomizer -``` +The Kustomize CLI comes with the following commands: + +* `build` Build scans the given path for Kubernetes manifests or Kustomize overlays and prints the YAML multi-doc to stdout. +* `apply` Apply validates the given Kubernetes manifests or Kustomize overlays and reconciles them using server-side apply. +* `get` Prints the content of inventories and their source revision. +* `diff` Diff compares the local Kubernetes manifests with the in-cluster objects and prints the YAML diff to stdout. +* `delete` Delete removes the Kubernetes objects in the inventory from the cluster and waits for termination. -## Usage +## Get Started -Apply a kustomization by pointing Kustomizer to a local dir that contains Kubernetes manifests: +Clone the Kustomizer Git repository locally: ```bash git clone https://github.com/stefanprodan/kustomizer cd kustomizer +``` + +Apply a local directory that contains Kubernetes manifests: + +```console +$ kustomizer apply -f ./testdata/plain --prune --wait \ +--source="$(git ls-remote --get-url)" \ +--revision="$(git describe --always)" \ +--inventory-name=demo \ +--inventory-namespace=default + +building inventory... +applying 10 manifest(s)... +Namespace/kustomizer-demo created +ServiceAccount/kustomizer-demo/demo created +ClusterRole/kustomizer-demo-read-only created +ClusterRoleBinding/kustomizer-demo-read-only created +Service/kustomizer-demo/backend created +Service/kustomizer-demo/frontend created +Deployment/kustomizer-demo/backend created +Deployment/kustomizer-demo/frontend created +HorizontalPodAutoscaler/kustomizer-demo/backend created +HorizontalPodAutoscaler/kustomizer-demo/frontend created +waiting for resources to become ready... +all resources are ready +``` + +Kustomizer scans the given path recursively for Kubernetes manifests in YAML format, +validates them against the cluster, applies them with server-side apply, and finally +waits for the workloads to be rollout. + +To apply Kustomize overlays, you can use `kustomizer apply -k path/to/overlay`, for more details see `--help`. + +After applying the resources, Kustomizer creates an inventory. +You cal list all inventories in a specific namespace with: + +```console +$ kustomizer get inventories --namespace default -kustomizer apply testdata/plain --name=demo --revision=1.0.0 +NAME ENTRIES SOURCE REVISION LAST APPLIED +demo 10 https://github.com/stefanprodan/kustomizer.git e44c210 2021-09-06T16:33:08Z ``` -Kustomizer generates a `kustomization.yaml` if one doesn't exist, builds it and applies the -resulting manifests on the cluster. - -```text -$ kustomizer apply testdata/plain/ --name=demo --revision=1.0.0 - -namespace/kustomizer-demo created -serviceaccount/demo created -clusterrole.rbac.authorization.k8s.io/demo-read-only created -clusterrolebinding.rbac.authorization.k8s.io/demo-read-only created -service/backend created -service/frontend created -deployment.apps/backend created -deployment.apps/frontend created -horizontalpodautoscaler.autoscaling/backend created -horizontalpodautoscaler.autoscaling/frontend created -configmap/demo-snapshot created +You can list the Kubernetes objects in an inventory with: + +```console +$ kustomizer get inventory demo + +Inventory: default/demo +LastApplied: 2021-09-06T16:33:08Z +Source: https://github.com/stefanprodan/kustomizer.git +Revision: e44c210 +Entries: +- Namespace/kustomizer-demo +- ServiceAccount/kustomizer-demo/demo +- ClusterRole/kustomizer-demo-read-only +- ClusterRoleBinding/kustomizer-demo-read-only +- Service/kustomizer-demo/backend +- Service/kustomizer-demo/frontend +- Deployment/kustomizer-demo/backend +- Deployment/kustomizer-demo/frontend +- HorizontalPodAutoscaler/kustomizer-demo/backend +- HorizontalPodAutoscaler/kustomizer-demo/frontend ``` -After applying the resources, Kustomizer creates a ConfigMap in the format `-snapshot` -used for garbage collection. You can change the ConfigMap namespace with `--gc-namespace` arg. +The inventory records are used to track which objects are subject to garbage collection. +The inventory is persistent on the cluster as a ConfigMap. -Remove the `frontend` and `rbac` manifests from the local dir: +Change the min replicas of the `backend` HPA and remove the `frontend` and the `rbac` manifests from the local dir: ```bash rm -rf testdata/plain/frontend rm -rf testdata/plain/common/rbac.yaml ``` -Rerun the apply by changing the revision: - -```text -$ kustomizer apply testdata/plain/ --name=demo --revision=2.0.0 - -namespace/kustomizer-demo configured -serviceaccount/demo configured -service/backend configured -deployment.apps/backend configured -horizontalpodautoscaler.autoscaling/backend configured -deployment.apps "frontend" deleted -horizontalpodautoscaler.autoscaling "frontend" deleted -service "frontend" deleted -clusterrole.rbac.authorization.k8s.io "demo-read-only" deleted -clusterrolebinding.rbac.authorization.k8s.io "demo-read-only" deleted -configmap/demo-snapshot configured +Preview the changes using diff: + +```console +$ kustomizer diff -i demo -f ./testdata/plain/ --prune + +► HorizontalPodAutoscaler/kustomizer-demo/backend drifted +  ( +   """ +   ... // 18 identical lines +   type: Utilization +   type: Resource +-  minReplicas: 2 ++  minReplicas: 1 +   scaleTargetRef: +   apiVersion: apps/v1 +   ... // 32 identical lines +   """ +  ) + +► ClusterRole/kustomizer-demo-read-only deleted +► ClusterRoleBinding/kustomizer-demo-read-only deleted +► Service/kustomizer-demo/frontend deleted +► Deployment/kustomizer-demo/frontend deleted +► HorizontalPodAutoscaler/kustomizer-demo/frontend deleted ``` -After applying the resources, Kustomizer removes the Kubernetes objects that are not present in the current revision. -Kustomizer garbage collector deletes the namespaced objects first then it removes the non-namspaced ones. -After the garbage collection finishes, Kustomizer update the ConfigMap snapshot with the new revision number. - -Delete all the Kubernetes objects belonging to a kustomization including the ConfigMap snapshot: +Rerun the apply command: + +```console +$ kustomizer apply -i demo -f testdata/plain/ --prune --wait + +building inventory... +applying 5 manifest(s)... +Namespace/kustomizer-demo unchanged +ServiceAccount/kustomizer-demo/demo unchanged +Service/kustomizer-demo/backend unchanged +Deployment/kustomizer-demo/backend unchanged +HorizontalPodAutoscaler/kustomizer-demo/backend configured +HorizontalPodAutoscaler/kustomizer-demo/frontend deleted +Deployment/kustomizer-demo/frontend deleted +Service/kustomizer-demo/frontend deleted +ClusterRoleBinding/kustomizer-demo-read-only deleted +ClusterRole/kustomizer-demo-read-only deleted +waiting for resources to become ready... +all resources are ready +``` -```text -$ kustomizer delete --name=demo +After applying the resources, Kustomizer removes the Kubernetes objects that are not present in the current inventory. +Kustomizer garbage collector deletes the namespaced objects first then it removes the non-namspaced ones. -deployment.apps "backend" deleted -horizontalpodautoscaler.autoscaling "backend" deleted -service "backend" deleted -serviceaccount "demo" deleted -namespace "kustomizer-demo" deleted -configmap "demo-snapshot" deleted +Delete all the Kubernetes objects belonging to an inventory including the inventory ConfigMap: + +```console +$ kustomizer delete -i demo --wait + +retrieving inventory... +deleting 5 manifest(s)... +HorizontalPodAutoscaler/kustomizer-demo/backend deleted +Deployment/kustomizer-demo/backend deleted +Service/kustomizer-demo/backend deleted +ServiceAccount/kustomizer-demo/demo deleted +Namespace/kustomizer-demo deleted +ConfigMap/default/demo deleted +waiting for resources to be terminated... +all resources have been deleted ``` ## CIOps @@ -112,7 +201,7 @@ name: deploy on: push: branches: - - 'master' + - 'main' jobs: kustomizer: @@ -124,19 +213,20 @@ jobs: with: kubeconfig: ${{ secrets.KUBE_CONFIG }} - name: Install Kustomizer - uses: stefanprodan/kustomizer/action@master - - name: Apply changes - run: kustomizer apply testdata/plain/ --name=demo --revision=${GITHUB_SHA} + uses: stefanprodan/kustomizer/action@main + - name: Deploy + run: kustomizer apply -f testdata/plain/ -i my-app --wait --prune ``` -For running kustomizations in a **GitOps** manner, take a look at [kustomize-controller](https://github.com/fluxcd/kustomize-controller). +For deploying to Kubernetes in a **GitOps** manner, +take a look at [Flux](https://github.com/fluxcd/flux2). ## Motivation If you got so far you may wander how is Kustomizer different to running: ```bash -kustomize build . | kubectl apply -f- --prune -l app=my-app +kubectl apply -k ./my-app --prune -l app=my-app ``` The pruning feature in kubectl while still experimental has many downsides, most notable is that pruning @@ -146,11 +236,8 @@ Another downside is the fact that pruning can delete non-namespaced objects outs If you want to prune custom resources, then you need to pass the group/version/kind to prune-whitelist and maintain a list per kustomization. -Kustomizer takes the supplied name and revision, and using Kustomize transformers, it labels all -the Kubernetes objects before applying them on the cluster. -The name, revision and objects metadata are persisted on the cluster in a ConfigMap. -When the revision changes, Kustomizer can reliably detect the objects that were previously applied but -are missing from the current revision. For namespaced objects, Kustomizer runs the delete commands +Kustomizer can reliably detect the objects that were previously applied but +are missing from the current inventory. For namespaced objects, Kustomizer runs the delete commands scoped to a namespace, this way an account that doesn't have a cluster role binding can prune objects in the namespaces it owns. diff --git a/action/Dockerfile b/action/Dockerfile deleted file mode 100644 index 97429a0..0000000 --- a/action/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM stefanprodan/alpine-base:latest - -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/action/action.yml b/action/action.yml index 982b9fc..613abd8 100644 --- a/action/action.yml +++ b/action/action.yml @@ -1,9 +1,43 @@ -name: 'kustomizer' -description: 'A GitHub Action to run kustomizer commands' -author: 'Stefan Prodan' +name: Setup Kustomizer CLI +description: A GitHub Action for running Kustomizer commands +author: Stefan Prodan branding: - icon: 'command' - color: 'blue' + color: blue + icon: command +inputs: + version: + description: "Kustomizer version e.g. 1.0.0 (defaults to latest stable release)" + required: false + arch: + description: "arch can be amd64, arm64 or arm" + required: true + default: "amd64" runs: - using: 'docker' - image: 'Dockerfile' + using: composite + steps: + - name: "Download kustomizer binary to tmp" + shell: bash + run: | + ARCH=${{ inputs.arch }} + VERSION=${{ inputs.version }} + + if [ -z $VERSION ]; then + VERSION=$(curl https://api.github.com/repos/stefanprodan/kustomizer/releases/latest -sL | grep tag_name | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-) + fi + + BIN_URL="https://github.com/stefanprodan/kustomizer/releases/download/v${VERSION}/flux_${VERSION}_linux_${ARCH}.tar.gz" + curl -sL ${BIN_URL} -o /tmp/kustomizer.tar.gz + mkdir -p /tmp/kustomizer + tar -C /tmp/kustomizer/ -zxvf /tmp/kustomizer.tar.gz + - name: "Add kustomizer binary to /usr/local/bin" + shell: bash + run: | + sudo cp /tmp/kustomizer/kustomizer /usr/local/bin + - name: "Cleanup tmp" + shell: bash + run: | + rm -rf /tmp/kustomizer/ /tmp/kustomizer.tar.gz + - name: "Verify correct installation of binary" + shell: bash + run: | + kustomizer -v diff --git a/action/entrypoint.sh b/action/entrypoint.sh deleted file mode 100644 index 3cb8f4c..0000000 --- a/action/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -e - -curl -s https://api.github.com/repos/stefanprodan/kustomizer/releases/latest |\ - grep browser_download |\ - grep linux |\ - cut -d '"' -f 4 |\ - xargs curl -sL -o kustomizer.tar.gz - -tar xzf ./kustomizer.tar.gz -rm ./kustomizer.tar.gz - -mkdir -p $GITHUB_WORKSPACE/bin -mv ./kustomizer $GITHUB_WORKSPACE/bin - -echo "::add-path::$GITHUB_WORKSPACE/bin" -echo "::add-path::$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" diff --git a/cmd/kustomizer/apply.go b/cmd/kustomizer/apply.go index db0585e..a650162 100644 --- a/cmd/kustomizer/apply.go +++ b/cmd/kustomizer/apply.go @@ -1,140 +1,206 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package main import ( + "context" "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" + "sort" "time" - "github.com/spf13/cobra" - "sigs.k8s.io/kustomize/api/filesys" + "github.com/stefanprodan/kustomizer/pkg/inventory" + "github.com/stefanprodan/kustomizer/pkg/manager" + "github.com/stefanprodan/kustomizer/pkg/objectutil" - "github.com/stefanprodan/kustomizer/pkg/engine" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var applyCmd = &cobra.Command{ - Use: "apply [path]", - Short: "Apply kustomization and prune previous applied Kubernetes objects", - RunE: applyCmdRun, + Use: "apply", + Short: "Apply Kubernetes manifests and Kustomize overlays using server-side apply.", + RunE: runApplyCmd, } -var ( - group string - name string +type applyFlags struct { + filename []string + kustomize string + inventoryName string + inventoryNamespace string + wait bool + force bool + prune bool + source string revision string - timeout time.Duration - cfgNamespace string - buildWithKustomize bool - dryRun bool -) +} + +var applyArgs applyFlags func init() { - applyCmd.Flags().StringVar(&group, "group", "kustomizer", "group") - applyCmd.Flags().StringVarP(&name, "name", "", "", "name of this kustomization") - applyCmd.Flags().StringVarP(&revision, "revision", "r", "", "revision of this kustomization") - applyCmd.Flags().StringVarP(&cfgNamespace, "gc-namespace", "", "default", "namespace to store the GC snapshot ConfigMap") - applyCmd.Flags().DurationVar(&timeout, "timeout", 5*time.Minute, "timeout for this operation") - applyCmd.Flags().BoolVar(&buildWithKustomize, "use-kustomize", false, "use Kustomize binary for build operations") - applyCmd.Flags().BoolVar(&dryRun, "dry-run", false, "dry-run apply") + applyCmd.Flags().StringSliceVarP(&applyArgs.filename, "filename", "f", nil, + "Path to Kubernetes manifest(s). If a directory is specified, then all manifests in the directory tree will be processed recursively.") + applyCmd.Flags().StringVarP(&applyArgs.kustomize, "kustomize", "k", "", + "Path to a directory that contains a kustomization.yaml.") + applyCmd.Flags().BoolVar(&applyArgs.wait, "wait", false, "Wait for the applied Kubernetes objects to become ready.") + applyCmd.Flags().BoolVar(&applyArgs.force, "force", false, "Recreate objects that contain immutable fields changes.") + applyCmd.Flags().BoolVar(&applyArgs.prune, "prune", false, "Delete stale objects from the cluster.") + applyCmd.Flags().StringVarP(&applyArgs.inventoryName, "inventory-name", "i", "", "The name of the inventory configmap.") + applyCmd.Flags().StringVar(&applyArgs.inventoryNamespace, "inventory-namespace", "default", + "The namespace of the inventory configmap. The namespace must exist on the target cluster.") + applyCmd.Flags().StringVar(&applyArgs.source, "source", "", "The URL to the source code.") + applyCmd.Flags().StringVar(&applyArgs.revision, "revision", "", "The revision identifier.") rootCmd.AddCommand(applyCmd) } -func applyCmdRun(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("path is required") - } - base := args[0] - fs := filesys.MakeFsOnDisk() - - tmpDir, err := ioutil.TempDir("", name) - if err != nil { - return fmt.Errorf("tmp dir error: %w", err) +func runApplyCmd(cmd *cobra.Command, args []string) error { + if applyArgs.kustomize == "" && len(applyArgs.filename) == 0 { + return fmt.Errorf("-f or -k is required") } - defer os.RemoveAll(tmpDir) - - if !strings.HasSuffix(base, "/") { - base += "/" + if applyArgs.inventoryName == "" { + return fmt.Errorf("--inventory-name is required") } - - c := fmt.Sprintf("cp -r %s* %s", base, tmpDir) - command := exec.Command("/bin/sh", "-c", c) - if err := command.Run(); err != nil { - return fmt.Errorf("%s command failed", c) + if applyArgs.inventoryNamespace == "" { + return fmt.Errorf("--inventory-namespace is required") } - base = tmpDir - - revisor, err := engine.NewRevisior(group, name, revision) + logger.Println("building inventory...") + objects, err := buildManifests(applyArgs.kustomize, applyArgs.filename) if err != nil { return err } - transformer, err := engine.NewTransformer(fs, revisor) - if err != nil { - return err + newInventory := inventory.NewInventory(applyArgs.inventoryName, applyArgs.inventoryNamespace) + newInventory.SetSource(applyArgs.source, applyArgs.revision) + if err := newInventory.AddObjects(objects); err != nil { + return fmt.Errorf("creating inventory failed, error: %w", err) } + logger.Println(fmt.Sprintf("applying %v manifest(s)...", len(objects))) - err = transformer.Generate(base) - if err != nil { - return err + for _, object := range objects { + fixReplicasConflict(object, objects) } - generator, err := engine.NewGenerator(fs, revisor) + kubeClient, err := newKubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) if err != nil { - return err + return fmt.Errorf("client init failed: %w", err) } - err = generator.Generate(base) + statusPoller, err := newKubeStatusPoller(rootArgs.kubeconfig, rootArgs.kubecontext) if err != nil { - return err + return fmt.Errorf("status poller init failed: %w", err) } - builder, err := engine.NewBuilder(fs) - if err != nil { - return err - } + resMgr := manager.NewResourceManager(kubeClient, statusPoller, inventoryOwner) + + resMgr.SetOwnerLabels(objects, applyArgs.inventoryName, applyArgs.inventoryNamespace) + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() - manifest := filepath.Join(base, revisor.ManifestFile()) + // contains only CRDs and Namespaces + var stageOne []*unstructured.Unstructured - if buildWithKustomize { - if err = builder.Build(base, manifest); err != nil { + // contains all objects except for CRDs and Namespaces + var stageTwo []*unstructured.Unstructured + + for _, u := range objects { + if resMgr.IsClusterDefinition(u.GetKind()) { + stageOne = append(stageOne, u) + } else { + stageTwo = append(stageTwo, u) + } + } + + if len(stageOne) > 0 { + changeSet, err := resMgr.ApplyAll(ctx, stageOne, applyArgs.force) + if err != nil { return err } - } else { - if err = builder.Generate(base, manifest); err != nil { + for _, change := range changeSet.Entries { + logger.Println(change.String()) + } + + if err := resMgr.Wait(stageOne, 2*time.Second, 30*time.Second); err != nil { return err } } - applier, err := engine.NewApplier(fs, timeout, engine.NewKubectlExecutor(kubectl, nil)) - if err != nil { - return err + sort.Sort(objectutil.SortableUnstructureds(stageTwo)) + for _, object := range stageTwo { + change, err := resMgr.Apply(ctx, object, applyArgs.force) + if err != nil { + return err + } + logger.Println(change.String()) } - err = applier.Run(manifest, dryRun) + staleObjects, err := resMgr.GetInventoryStaleObjects(ctx, newInventory) if err != nil { - return err + return fmt.Errorf("inventory query failed, error: %w", err) } - gc, err := engine.NewGarbageCollector(revisor, timeout, engine.NewKubectlExecutor(kubectl, nil)) + err = resMgr.ApplyInventory(ctx, newInventory) if err != nil { - return err + return fmt.Errorf("inventory apply failed, error: %w", err) } - write := func(obj string) { - if !strings.Contains(obj, "No resources found") { - fmt.Println(obj) + if applyArgs.prune && len(staleObjects) > 0 { + changeSet, err := resMgr.DeleteAll(ctx, staleObjects) + if err != nil { + return fmt.Errorf("prune failed, error: %w", err) + } + for _, change := range changeSet.Entries { + logger.Println(change.String()) } } - err = gc.Run(manifest, cfgNamespace, write) - if err != nil { - return err + if applyArgs.wait { + logger.Println("waiting for resources to become ready...") + + err = resMgr.Wait(objects, 2*time.Second, rootArgs.timeout) + if err != nil { + return err + } + + if applyArgs.prune && len(staleObjects) > 0 { + err = resMgr.WaitForTermination(staleObjects, 2*time.Second, rootArgs.timeout) + if err != nil { + return fmt.Errorf("wating for termination failed, error: %w", err) + } + } + + logger.Println("all resources are ready") } return nil } + +// fixReplicasConflict removes the replicas field from the given workload if it's managed by an HPA +func fixReplicasConflict(object *unstructured.Unstructured, objects []*unstructured.Unstructured) { + for _, hpa := range objects { + if hpa.GetKind() == "HorizontalPodAutoscaler" && object.GetNamespace() == hpa.GetNamespace() { + targetKind, found, err := unstructured.NestedFieldCopy(hpa.Object, "spec", "scaleTargetRef", "kind") + if err == nil && found && fmt.Sprintf("%v", targetKind) == object.GetKind() { + targetName, found, err := unstructured.NestedFieldCopy(hpa.Object, "spec", "scaleTargetRef", "name") + if err == nil && found && fmt.Sprintf("%v", targetName) == object.GetName() { + unstructured.RemoveNestedField(object.Object, "spec", "replicas") + } + } + } + } +} diff --git a/cmd/kustomizer/build.go b/cmd/kustomizer/build.go new file mode 100644 index 0000000..d2e5ec4 --- /dev/null +++ b/cmd/kustomizer/build.go @@ -0,0 +1,230 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bufio" + "bytes" + "fmt" + "os" + "path" + "path/filepath" + "sort" + "sync" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/kustomize/api/krusty" + kustypes "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +var buildCmd = &cobra.Command{ + Use: "build", + Short: "Build a set of Kubernetes manifests or Kustomize overlays.", + RunE: runBuildCmd, +} + +type buildFlags struct { + filename []string + kustomize string + output string +} + +var buildArgs buildFlags + +func init() { + buildCmd.Flags().StringSliceVarP(&buildArgs.filename, "filename", "f", nil, + "Path to Kubernetes manifest(s). If a directory is specified, then all manifests in the directory tree will be processed recursively.") + buildCmd.Flags().StringVarP(&buildArgs.kustomize, "kustomize", "k", "", + "Path to a directory that contains a kustomization.yaml.") + buildCmd.Flags().StringVarP(&buildArgs.output, "output", "o", "yaml", + "Write manifests to stdout in YAML or JSON format.") + + rootCmd.AddCommand(buildCmd) +} + +func runBuildCmd(cmd *cobra.Command, args []string) error { + if buildArgs.kustomize == "" && len(buildArgs.filename) == 0 { + return fmt.Errorf("-f or -k is required") + } + + objects, err := buildManifests(buildArgs.kustomize, buildArgs.filename) + if err != nil { + return err + } + + switch buildArgs.output { + case "yaml": + yml, err := objectutil.ObjectsToYAML(objects) + if err != nil { + return err + } + fmt.Println(yml) + case "json": + json, err := objectutil.ObjectsToJSON(objects) + if err != nil { + return err + } + fmt.Println(json) + default: + return fmt.Errorf("unsupported output, can be yaml or json") + } + + return nil +} + +func buildManifests(kustomizePath string, filePaths []string) ([]*unstructured.Unstructured, error) { + objects := make([]*unstructured.Unstructured, 0) + if kustomizePath != "" { + data, err := buildKustomization(kustomizePath) + if err != nil { + return nil, err + } + + objs, err := objectutil.ReadObjects(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("%s: %w", kustomizePath, err) + } + objects = append(objects, objs...) + } + + if len(filePaths) > 0 { + manifests, err := scanForManifests(filePaths) + if err != nil { + return nil, err + } + for _, manifest := range manifests { + ms, err := os.Open(manifest) + if err != nil { + return nil, err + } + + objs, err := objectutil.ReadObjects(bufio.NewReader(ms)) + ms.Close() + if err != nil { + return nil, fmt.Errorf("%s: %w", manifest, err) + } + + for _, obj := range objs { + if objectutil.IsKubernetesObject(obj) && !objectutil.IsKustomization(obj) { + objects = append(objects, obj) + } + } + } + } + + sort.Sort(objectutil.SortableUnstructureds(objects)) + return objects, nil +} + +func scanForManifests(paths []string) ([]string, error) { + var manifests []string + + for _, in := range paths { + fi, err := os.Stat(in) + if err != nil { + return nil, err + } + + switch mode := fi.Mode(); { + case mode.IsDir(): + m, err := scanRec(in) + if err != nil { + return nil, err + } + manifests = append(manifests, m...) + case mode.IsRegular(): + if matchExt(fi.Name()) { + manifests = append(manifests, in) + } + } + } + + return manifests, nil +} + +func scanRec(dir string) ([]string, error) { + var manifests []string + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + for _, file := range files { + if file.IsDir() { + m, err := scanRec(path.Join(dir, file.Name())) + if err != nil { + return nil, err + } + manifests = append(manifests, m...) + } + if matchExt(file.Name()) { + manifests = append(manifests, path.Join(dir, file.Name())) + } + } + return manifests, err +} + +func matchExt(f string) bool { + ext := path.Ext(f) + return ext == ".yaml" || ext == ".yml" +} + +var kustomizeBuildMutex sync.Mutex + +func buildKustomization(base string) ([]byte, error) { + kustomizeBuildMutex.Lock() + defer kustomizeBuildMutex.Unlock() + + kfile := path.Join(base, "kustomization.yaml") + + fs := filesys.MakeFsOnDisk() + if !fs.Exists(kfile) { + return nil, fmt.Errorf("%s not found", kfile) + } + + if path.IsAbs(base) { + wd, err := os.Getwd() + if err != nil { + return nil, err + } + base, err = filepath.Rel(wd, base) + if err != nil { + return nil, err + } + } + + buildOptions := &krusty.Options{ + LoadRestrictions: kustypes.LoadRestrictionsNone, + PluginConfig: kustypes.DisabledPluginConfig(), + } + + k := krusty.MakeKustomizer(buildOptions) + m, err := k.Run(fs, base) + if err != nil { + return nil, err + } + + resources, err := m.AsYaml() + if err != nil { + return nil, err + } + + return resources, nil +} diff --git a/cmd/kustomizer/client.go b/cmd/kustomizer/client.go new file mode 100644 index 0000000..37ed33e --- /dev/null +++ b/cmd/kustomizer/client.go @@ -0,0 +1,108 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "runtime" + "strings" + + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +func newScheme() *apiruntime.Scheme { + scheme := apiruntime.NewScheme() + _ = apiextensionsv1.AddToScheme(scheme) + _ = corev1.AddToScheme(scheme) + return scheme +} + +func newKubeClient(kubeConfigPath string, kubeContext string) (client.WithWatch, error) { + cfg, err := newKubeConfig(kubeConfigPath, kubeContext) + if err != nil { + return nil, fmt.Errorf("kubernetes client initialization failed: %w", err) + } + + kubeClient, err := client.NewWithWatch(cfg, client.Options{ + Scheme: newScheme(), + }) + if err != nil { + return nil, fmt.Errorf("kubernetes client initialization failed: %w", err) + } + + return kubeClient, nil +} + +func newKubeStatusPoller(kubeConfigPath string, kubeContext string) (*polling.StatusPoller, error) { + kubeConfig, err := newKubeConfig(kubeConfigPath, kubeContext) + if err != nil { + return nil, err + } + + restMapper, err := apiutil.NewDynamicRESTMapper(kubeConfig) + if err != nil { + return nil, err + } + c, err := client.New(kubeConfig, client.Options{Mapper: restMapper}) + if err != nil { + return nil, err + } + + return polling.NewStatusPoller(c, restMapper), nil +} + +func newKubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error) { + configFiles := splitKubeConfigPath(kubeConfigPath) + configOverrides := clientcmd.ConfigOverrides{} + + if len(kubeContext) > 0 { + configOverrides.CurrentContext = kubeContext + } + + cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{Precedence: configFiles}, + &configOverrides, + ).ClientConfig() + + if err != nil { + return nil, fmt.Errorf("kubeconfig load failed: %w", err) + } + + cfg.QPS = 50 + cfg.Burst = 100 + + return cfg, nil +} + +func splitKubeConfigPath(path string) []string { + var sep string + switch runtime.GOOS { + case "windows": + sep = ";" + default: + sep = ":" + } + return strings.Split(path, sep) +} diff --git a/cmd/kustomizer/delete.go b/cmd/kustomizer/delete.go index f15c4f0..e423068 100644 --- a/cmd/kustomizer/delete.go +++ b/cmd/kustomizer/delete.go @@ -1,55 +1,123 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package main import ( + "context" "fmt" - "strings" + "os" + "sort" "time" + "github.com/stefanprodan/kustomizer/pkg/inventory" + "github.com/stefanprodan/kustomizer/pkg/manager" + "github.com/stefanprodan/kustomizer/pkg/objectutil" + "github.com/spf13/cobra" - "github.com/stefanprodan/kustomizer/pkg/engine" ) var deleteCmd = &cobra.Command{ Use: "delete", - Short: "Delete Kubernetes objects previous applied", + Short: "Delete the Kubernetes objects in the inventory including the inventory configmap.", RunE: deleteCmdRun, } -var ( - deleteName string - deleteTimeout time.Duration - deleteCfgNamespace string -) +type deleteFlags struct { + inventoryName string + inventoryNamespace string + wait bool +} + +var deleteArgs deleteFlags func init() { - deleteCmd.Flags().StringVarP(&deleteName, "name", "", "", "name of the kustomization to be deleted") - deleteCmd.Flags().StringVarP(&deleteCfgNamespace, "gc-namespace", "", "default", "namespace where the GC snapshot ConfigMap is") - deleteCmd.Flags().DurationVar(&deleteTimeout, "timeout", 5*time.Minute, "timeout for this operation") + deleteCmd.Flags().StringVarP(&deleteArgs.inventoryName, "inventory-name", "i", "", "The name of the inventory configmap.") + deleteCmd.Flags().StringVar(&deleteArgs.inventoryNamespace, "inventory-namespace", "default", "The namespace of the inventory configmap.") + deleteCmd.Flags().BoolVar(&deleteArgs.wait, "wait", true, "Wait for the deleted Kubernetes objects to be terminated.") rootCmd.AddCommand(deleteCmd) } func deleteCmdRun(cmd *cobra.Command, args []string) error { - revisor, err := engine.NewRevisior(group, deleteName, "none") + if deleteArgs.inventoryName == "" { + return fmt.Errorf("--inventory-name is required") + } + if deleteArgs.inventoryNamespace == "" { + return fmt.Errorf("--inventory-namespace is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + logger.Println("retrieving inventory...") + + kubeClient, err := newKubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) if err != nil { + return fmt.Errorf("client init failed: %w", err) + } + + statusPoller, err := newKubeStatusPoller(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("status poller init failed: %w", err) + } + + resMgr := manager.NewResourceManager(kubeClient, statusPoller, inventoryOwner) + + inv := inventory.NewInventory(deleteArgs.inventoryName, deleteArgs.inventoryNamespace) + if err := resMgr.GetInventory(ctx, inv); err != nil { return err } - gc, err := engine.NewGarbageCollector(revisor, deleteTimeout, engine.NewKubectlExecutor(kubectl, nil)) + objects, err := inv.ListObjects() if err != nil { return err } - write := func(obj string) { - if !strings.Contains(obj, "No resources found") { - fmt.Println(obj) + logger.Println(fmt.Sprintf("deleting %v manifest(s)...", len(objects))) + hasErrors := false + sort.Sort(sort.Reverse(objectutil.SortableUnstructureds(objects))) + for _, object := range objects { + change, err := resMgr.Delete(ctx, object) + if err != nil { + logger.Println(`✗`, err) + hasErrors = true + continue } + logger.Println(change.String()) } - err = gc.Cleanup(deleteCfgNamespace, write) - if err != nil { + if hasErrors { + os.Exit(1) + } + + if err := resMgr.DeleteInventory(ctx, inv); err != nil { return err } + logger.Println(fmt.Sprintf("ConfigMap/%s/%s deleted", deleteArgs.inventoryNamespace, deleteArgs.inventoryName)) + + if deleteArgs.wait { + logger.Println("waiting for resources to be terminated...") + err = resMgr.WaitForTermination(objects, 2*time.Second, rootArgs.timeout) + if err != nil { + return err + } + logger.Println("all resources have been deleted") + } + return nil } diff --git a/cmd/kustomizer/diff.go b/cmd/kustomizer/diff.go new file mode 100644 index 0000000..81a509d --- /dev/null +++ b/cmd/kustomizer/diff.go @@ -0,0 +1,128 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/stefanprodan/kustomizer/pkg/inventory" + "github.com/stefanprodan/kustomizer/pkg/manager" + "github.com/stefanprodan/kustomizer/pkg/objectutil" +) + +var diffCmd = &cobra.Command{ + Use: "diff", + Short: "Diff compares the local Kubernetes manifests with the in-cluster objects and prints the YAML diff to stdout.", + RunE: runDiffCmd, +} + +type diffFlags struct { + filename []string + kustomize string + inventoryName string + inventoryNamespace string + prune bool +} + +var diffArgs diffFlags + +func init() { + diffCmd.Flags().StringSliceVarP(&diffArgs.filename, "filename", "f", nil, + "Path to Kubernetes manifest(s). If a directory is specified, then all manifests in the directory tree will be processed recursively.") + diffCmd.Flags().StringVarP(&diffArgs.kustomize, "kustomize", "k", "", + "Path to a directory that contains a kustomization.yaml.") + diffCmd.Flags().BoolVar(&diffArgs.prune, "prune", false, "Delete stale objects from the cluster.") + diffCmd.Flags().StringVarP(&diffArgs.inventoryName, "inventory-name", "i", "", "The name of the inventory configmap.") + diffCmd.Flags().StringVar(&diffArgs.inventoryNamespace, "inventory-namespace", "default", + "The namespace of the inventory configmap. The namespace must exist on the target cluster.") + rootCmd.AddCommand(diffCmd) +} + +func runDiffCmd(cmd *cobra.Command, args []string) error { + if diffArgs.kustomize == "" && len(diffArgs.filename) == 0 { + return fmt.Errorf("-f or -k is required") + } + + objects, err := buildManifests(diffArgs.kustomize, diffArgs.filename) + if err != nil { + return err + } + + newInventory := inventory.NewInventory(diffArgs.inventoryName, diffArgs.inventoryNamespace) + if err := newInventory.AddObjects(objects); err != nil { + return fmt.Errorf("creating inventory failed, error: %w", err) + } + + kubeClient, err := newKubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("client init failed: %w", err) + } + + statusPoller, err := newKubeStatusPoller(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("status poller init failed: %w", err) + } + + resMgr := manager.NewResourceManager(kubeClient, statusPoller, inventoryOwner) + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + if diffArgs.inventoryName != "" { + resMgr.SetOwnerLabels(objects, diffArgs.inventoryName, diffArgs.inventoryNamespace) + } + + invalid := false + for _, object := range objects { + change, err := resMgr.Diff(ctx, object) + if err != nil { + logger.Println(`✗`, err) + invalid = true + continue + } + + if change.Action == string(manager.CreatedAction) { + fmt.Println(`►`, change.Subject, "created") + } + + if change.Action == string(manager.ConfiguredAction) { + fmt.Println(`►`, change.Subject, "drifted") + fmt.Println(change.Diff) + } + + } + + if diffArgs.inventoryName != "" { + staleObjects, err := resMgr.GetInventoryStaleObjects(ctx, newInventory) + if err != nil { + return fmt.Errorf("inventory query failed, error: %w", err) + } + + for _, object := range staleObjects { + fmt.Println(`►`, fmt.Sprintf("%s deleted", objectutil.FmtUnstructured(object))) + } + } + + if invalid { + os.Exit(1) + } + return nil +} diff --git a/cmd/kustomizer/get.go b/cmd/kustomizer/get.go new file mode 100644 index 0000000..b46a843 --- /dev/null +++ b/cmd/kustomizer/get.go @@ -0,0 +1,37 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" +) + +var getCmd = &cobra.Command{ + Use: "get", + Short: "Get prints the content of inventories.", +} + +type getFlags struct { + namespace string +} + +var getArgs getFlags + +func init() { + getCmd.Flags().StringVar(&getArgs.namespace, "namespace", "default", "The namespace of the inventory.") + rootCmd.AddCommand(getCmd) +} diff --git a/cmd/kustomizer/get_inventories.go b/cmd/kustomizer/get_inventories.go new file mode 100644 index 0000000..30dffaf --- /dev/null +++ b/cmd/kustomizer/get_inventories.go @@ -0,0 +1,117 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/stefanprodan/kustomizer/pkg/inventory" + "github.com/stefanprodan/kustomizer/pkg/manager" + + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var getInventories = &cobra.Command{ + Use: "inventories", + Short: "Get prints the content of all inventories in the given namespace.", + RunE: runGetInventoriesCmd, +} + +func init() { + getCmd.AddCommand(getInventories) +} + +func runGetInventoriesCmd(cmd *cobra.Command, args []string) error { + + if getArgs.namespace == "" { + return fmt.Errorf("you must specify an intentory namespace") + } + + kubeClient, err := newKubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("client init failed: %w", err) + } + + statusPoller, err := newKubeStatusPoller(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("status poller init failed: %w", err) + } + + resMgr := manager.NewResourceManager(kubeClient, statusPoller, inventoryOwner) + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + list := &corev1.ConfigMapList{} + err = resMgr.Client().List(ctx, list, client.InNamespace(getArgs.namespace), client.MatchingLabels{ + "app.kubernetes.io/component": "inventory", + "app.kubernetes.io/created-by": "kustomizer", + }) + if err != nil { + return err + } + + var rows [][]string + for _, cm := range list.Items { + var ts string + var source string + var rev string + if s, ok := cm.GetAnnotations()["inventory.kustomizer.dev/last-applied-time"]; ok { + ts = s + } + if s, ok := cm.GetAnnotations()["inventory.kustomizer.dev/source"]; ok { + source = s + } + if s, ok := cm.GetAnnotations()["inventory.kustomizer.dev/revision"]; ok { + rev = s + } + i := inventory.NewInventory(cm.GetName(), cm.GetNamespace()) + if err := resMgr.GetInventory(ctx, i); err != nil { + return err + } + row := []string{cm.GetName(), fmt.Sprintf("%v", len(i.Entries)), source, rev, ts} + rows = append(rows, row) + } + + printTable(os.Stdout, []string{"name", "entries", "source", "revision", "last applied"}, rows) + + return nil +} + +func printTable(writer io.Writer, header []string, rows [][]string) { + table := tablewriter.NewWriter(writer) + table.SetHeader(header) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetTablePadding("\t") + table.SetNoWhiteSpace(true) + table.AppendBulk(rows) + table.Render() +} diff --git a/cmd/kustomizer/get_inventory.go b/cmd/kustomizer/get_inventory.go new file mode 100644 index 0000000..fc0dbf6 --- /dev/null +++ b/cmd/kustomizer/get_inventory.go @@ -0,0 +1,82 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/stefanprodan/kustomizer/pkg/inventory" + "github.com/stefanprodan/kustomizer/pkg/manager" + "github.com/stefanprodan/kustomizer/pkg/objectutil" +) + +var getInventoryCmd = &cobra.Command{ + Use: "inventory [name]", + Short: "Get inventory prints the content of the given inventory.", + RunE: runGetInventoryCmd, +} + +func init() { + getCmd.AddCommand(getInventoryCmd) +} + +func runGetInventoryCmd(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("you must specify an intentory name") + } + name := args[0] + if getArgs.namespace == "" { + return fmt.Errorf("you must specify a namespace") + } + + i := inventory.NewInventory(name, getArgs.namespace) + + kubeClient, err := newKubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("client init failed: %w", err) + } + + statusPoller, err := newKubeStatusPoller(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return fmt.Errorf("status poller init failed: %w", err) + } + + resMgr := manager.NewResourceManager(kubeClient, statusPoller, inventoryOwner) + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + if err := resMgr.GetInventory(ctx, i); err != nil { + return err + } + + fmt.Println(fmt.Sprintf("Inventory: %s/%s", i.Namespace, i.Name)) + fmt.Println(fmt.Sprintf("Source: %s", i.Source)) + fmt.Println(fmt.Sprintf("Revision: %s", i.Revision)) + fmt.Println("Entries:") + entries, err := i.ListMeta() + if err != nil { + return err + } + for _, entry := range entries { + fmt.Println("-", objectutil.FmtObjMetadata(entry)) + } + + return nil +} diff --git a/cmd/kustomizer/log.go b/cmd/kustomizer/log.go new file mode 100644 index 0000000..f608c01 --- /dev/null +++ b/cmd/kustomizer/log.go @@ -0,0 +1,30 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io" +) + +type stderrLogger struct { + stderr io.Writer +} + +func (l stderrLogger) Println(a ...interface{}) { + fmt.Fprintln(l.stderr, a...) +} diff --git a/cmd/kustomizer/main.go b/cmd/kustomizer/main.go index d69bdf0..1558fc9 100644 --- a/cmd/kustomizer/main.go +++ b/cmd/kustomizer/main.go @@ -1,34 +1,93 @@ +/* +Copyright 2021 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package main import ( - "log" + "github.com/stefanprodan/kustomizer/pkg/manager" "os" + "path" + "time" "github.com/spf13/cobra" + _ "k8s.io/client-go/plugin/pkg/client/auth" ) -var VERSION = "0.0.0-dev.0" +var VERSION = "1.0.0-dev.0" + +const PROJECT = "kustomizer" var rootCmd = &cobra.Command{ - Use: "kustomizer", + Use: PROJECT, Version: VERSION, SilenceUsage: true, SilenceErrors: true, - Short: "A command line utility for generating, building and applying kustomizations", + Short: "A command line utility for reconciling Kubernetes manifests and Kustomize overlays.", +} + +type rootFlags struct { + kubeconfig string + kubecontext string + timeout time.Duration } var ( - kubectl string + rootArgs = rootFlags{} + logger = stderrLogger{stderr: os.Stderr} + inventoryOwner = manager.Owner{ + Field: "kustomizer", + Group: "inventory.kustomizer.dev", + } ) func init() { - rootCmd.PersistentFlags().StringVar(&kubectl, "kubectl", "kubectl", "Command to run kubectl") + rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "", + "Absolute path to the kubeconfig file.") + rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", + "The Kubernetes context to use.") + rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", time.Minute, + "The length of time to wait before giving up on the current operation.") + + rootCmd.DisableAutoGenTag = true } func main() { - log.SetFlags(0) + configureKubeconfig() + if err := rootCmd.Execute(); err != nil { - log.Println(err) + logger.Println(`✗`, err) os.Exit(1) } } + +func configureKubeconfig() { + switch { + case len(rootArgs.kubeconfig) > 0: + case len(os.Getenv("KUBECONFIG")) > 0: + rootArgs.kubeconfig = os.Getenv("KUBECONFIG") + default: + if home := homeDir(); len(home) > 0 { + rootArgs.kubeconfig = path.Join(home, ".kube", "config") + } + } +} + +func homeDir() string { + if h := os.Getenv("HOME"); h != "" { + return h + } + return os.Getenv("USERPROFILE") // windows +} diff --git a/go.mod b/go.mod index 8340a54..d9c87a3 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,18 @@ module github.com/stefanprodan/kustomizer -go 1.14 +go 1.16 require ( - github.com/spf13/cobra v1.0.0 - go.mozilla.org/sops/v3 v3.6.0 - k8s.io/api v0.19.0 - k8s.io/apimachinery v0.19.0 - k8s.io/client-go v0.19.0 // indirect - sigs.k8s.io/kustomize/api v0.6.0 + github.com/google/go-cmp v0.5.5 + github.com/olekukonko/tablewriter v0.0.4 + github.com/spf13/cobra v1.1.3 + k8s.io/api v0.22.1 + k8s.io/apiextensions-apiserver v0.22.1 + k8s.io/apimachinery v0.22.1 + k8s.io/client-go v0.22.1 + sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a + sigs.k8s.io/controller-runtime v0.10.0 + sigs.k8s.io/kustomize/api v0.9.0 + sigs.k8s.io/kustomize/kyaml v0.11.1 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 0a6e309..34de6b2 100644 --- a/go.sum +++ b/go.sum @@ -1,171 +1,198 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= -github.com/Azure/azure-sdk-for-go v31.2.0+incompatible h1:kZFnTLmdQYNGfakatSivKHUfUnDZhqNdchHD4oIhp5k= -github.com/Azure/azure-sdk-for-go v31.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= -github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0= -github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= -github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= -github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/azure/auth v0.1.0 h1:YgO/vSnJEc76NLw2ecIXvXa8bDWiqf1pOJzARAoZsYU= -github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM= -github.com/Azure/go-autorest/autorest/azure/cli v0.1.0 h1:YTtBrcb6mhA+PoSW8WxFDoIIyjp13XqJeX80ssQtri4= -github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U= -github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= -github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= -github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= -github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= -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.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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.23.13 h1:l/NG+mgQFRGG3dsFzEj0jw9JIs/zYdtU6MXhY1WIDmM= -github.com/aws/aws-sdk-go v1.23.13/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= 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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 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/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= 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/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= -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-errors/errors v1.4.0 h1:2OA7MFw38+e9na72T1xgkomPb6GzZzzxvJ5U630FoRM= +github.com/go-errors/errors v1.4.0/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -174,18 +201,18 @@ github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -194,99 +221,81 @@ github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCs github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +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-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -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/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/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/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/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -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/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= -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/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= -github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk= -github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= -github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= -github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -294,324 +303,399 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= -github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= -github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= -github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= -github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0= -github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -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.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -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/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -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/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 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/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c h1:yE1NxRAZA3wF0laDWECtOe2J0tFjSHUI6MXXbMif+QY= -github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c/go.mod h1:Is/Ucts/yU/mWyGR8yELRoO46mejouKsJfQLAIfTR18= -github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8= -github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -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/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 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/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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -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 h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +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/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 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 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= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 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/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= -github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/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/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM= -github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= -go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= -go.mozilla.org/sops/v3 v3.6.0 h1:V+RjhX96enZY9a5iVP/r60lLABq8/8Pv2Fybh10Np3g= -go.mozilla.org/sops/v3 v3.6.0/go.mod h1:X8YOCEZzMFL0p28vkqtn3gW2eFt+dDUt7HwKXGvcXvA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 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-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -620,20 +704,28 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/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/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -648,41 +740,52 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-20181026203630-95b1ffbd15a5/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-20181122145206-62eef0e2fa9b/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-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -690,107 +793,183 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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-20180828015842-6cd1fcedba52/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -799,82 +978,118 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0= -gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo= -gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= -k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= -k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= -k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= -k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= -k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= -k8s.io/client-go v0.19.0 h1:1+0E0zfWFIWeyRhQYWzimJOyAk2UT7TiARaLNwJCf7k= -k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s= +k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= +k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= +k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA= +k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE= +k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c= +k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY= +k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= +k8s.io/cli-runtime v0.21.1 h1:Oj/iZxa7LLXrhzShaLNF4rFJEIEBTDHj0dJw4ra2vX4= +k8s.io/cli-runtime v0.21.1/go.mod h1:TI9Bvl8lQWZB2KqE91QLCp9AZE4l29zNFnj/x4IX4Fw= +k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs= +k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= +k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= +k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= +k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA= +k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= +k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= +k8s.io/component-helpers v0.21.1/go.mod h1:FtC1flbiQlosHQrLrRUulnKxE4ajgWCGy/67fT2GRlQ= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kubectl v0.21.1/go.mod h1:PMYR88MqESuysBM/MX+Vu4JbX/50nY4d4kny+SPEI2U= +k8s.io/metrics v0.21.1/go.mod h1:pyDVLsLe++FIGDBFU80NcW4xMFsuiVTWL8Zfi7+PpNo= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210517184530-5a248b5acedc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -sigs.k8s.io/kustomize/api v0.6.0 h1:Gj+MH9uEPh7tBHKCGGwA+fHgg9th55StaU+ZT05+8bY= -sigs.k8s.io/kustomize/api v0.6.0/go.mod h1:M7410E0ULUFQlxRskB//n5G0MPwGvs9HG6K8Sf8gw+M= -sigs.k8s.io/kustomize/kyaml v0.7.1 h1:Ih6SJPvfKYfZaIFWUa2YAyg/0ZSTpA3LFjR/hv7+8ao= -sigs.k8s.io/kustomize/kyaml v0.7.1/go.mod h1:ne3F9JPhW2wrVaLslxBsEe6MQJQ9YK5rUutrdhBWXwI= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a h1:S17+FPWGsOonXim+GcadLcSblEwL131Y9fKLfifSYkY= +sigs.k8s.io/cli-utils v0.25.1-0.20210608181808-f3974341173a/go.mod h1:I4jgHr6uRfX0CkOMECwSgg2J48rNzZE1+kDXj9SnJBc= +sigs.k8s.io/controller-runtime v0.9.0-beta.5.0.20210524185538-7181f1162e79/go.mod h1:rgf+cBz72pYlKXDRNhI1WFQv/S86EMUV4/ySmsEYgHk= +sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= +sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= +sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY= +sigs.k8s.io/kustomize/api v0.9.0 h1:yGQnm/2GBbHBLSVbM0CsqgPmqK/NSDbhSGbXuhuVN7s= +sigs.k8s.io/kustomize/api v0.9.0/go.mod h1:bOF7z4DcRIXcOCeSbVq5o9JhMRnNzWqrRSSBFtz05A4= +sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0= +sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo= +sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/kustomize/kyaml v0.11.1 h1:MWihd9syKG7VQnAzr/OpKb94FvH+cw96nEV1u3it7pI= +sigs.k8s.io/kustomize/kyaml v0.11.1/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/install/README.md b/install/README.md index 903877e..275228b 100644 --- a/install/README.md +++ b/install/README.md @@ -1,17 +1,15 @@ # Kustomizer Installation -Binaries for macOS, Linux and Windows AMD64 are available for download on the -[release page](https://github.com/stefanprodan/kustomizer/releases). - -To install the latest release run: +To install the latest release on macOS or Linux: ```bash -curl -s https://raw.githubusercontent.com/stefanprodan/kustomizer/master/install/kustomizer.sh | sudo bash +curl -s https://raw.githubusercontent.com/stefanprodan/kustomizer/main/install/kustomizer.sh | sudo bash ``` The install script does the following: * attempts to detect your OS -* downloads and unpacks the release tar file in a temporary directory +* downloads and unpacks the [release tar file](https://github.com/stefanprodan/kustomizer/releases) in a temporary directory +* verifies the binary checksum * copies the kustomizer binary to `/usr/local/bin` * removes the temporary directory @@ -24,7 +22,7 @@ git clone https://github.com/stefanprodan/kustomizer cd kustomizer ``` -Build the kustomizer binary (requires go >= 1.14): +Build the kustomizer binary (requires go >= 1.16): ```bash make build diff --git a/install/kustomizer.sh b/install/kustomizer.sh index 8574b5d..14133b2 100755 --- a/install/kustomizer.sh +++ b/install/kustomizer.sh @@ -1,51 +1,204 @@ #!/usr/bin/env bash +# Copyright 2021 Stefan Prodan +# Copyright 2020 The Flux authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -e DEFAULT_BIN_DIR="/usr/local/bin" -BIN_DIR=${1:-"$DEFAULT_BIN_DIR"} - -opsys="" -if [[ "$OSTYPE" == linux* ]]; then - opsys=linux -elif [[ "$OSTYPE" == darwin* ]]; then - opsys=darwin -fi - -if [[ "$opsys" == "" ]]; then - echo "OS $OSTYPE not supported" - exit 1 -fi - -if [[ ! -x "$(command -v curl)" ]]; then - echo "curl not found" +BIN_DIR=${1:-"${DEFAULT_BIN_DIR}"} +GITHUB_REPO="stefanprodan/kustomizer" + +# Helper functions for logs +info() { + echo '[INFO] ' "$@" +} + +warn() { + echo '[WARN] ' "$@" >&2 +} + +fatal() { + echo '[ERROR] ' "$@" >&2 exit 1 -fi +} + +# Set os, fatal if operating system not supported +setup_verify_os() { + if [[ -z "${OS}" ]]; then + OS=$(uname) + fi + case ${OS} in + Darwin) + OS=darwin + ;; + Linux) + OS=linux + ;; + *) + fatal "Unsupported operating system ${OS}" + esac +} + +# Set arch, fatal if architecture not supported +setup_verify_arch() { + if [[ -z "${ARCH}" ]]; then + ARCH=$(uname -m) + fi + case ${ARCH} in + arm64|aarch64|armv8l) + ARCH=arm64 + ;; + amd64) + ARCH=amd64 + ;; + x86_64) + ARCH=amd64 + ;; + *) + fatal "Unsupported architecture ${ARCH}" + esac +} + +# Verify existence of downloader executable +verify_downloader() { + # Return failure if it doesn't exist or is no executable + [[ -x "$(which "$1")" ]] || return 1 + + # Set verified executable as our downloader program and return success + DOWNLOADER=$1 + return 0 +} -tmpDir=`mktemp -d` -if [[ ! "$tmpDir" || ! -d "$tmpDir" ]]; then - echo "could not create temp dir" - exit 1 -fi +# Create tempory directory and cleanup when done +setup_tmp() { + TMP_DIR=$(mktemp -d -t kustomizer-install.XXXXXXXXXX) + TMP_METADATA="${TMP_DIR}/kustomizer.json" + TMP_HASH="${TMP_DIR}/kustomizer.hash" + TMP_BIN="${TMP_DIR}/kustomizer.tar.gz" + cleanup() { + local code=$? + set +e + trap - EXIT + rm -rf "${TMP_DIR}" + exit ${code} + } + trap cleanup INT EXIT +} + +# Find version from Github metadata +get_release_version() { + METADATA_URL="https://api.github.com/repos/${GITHUB_REPO}/releases/latest" + + info "Downloading metadata ${METADATA_URL}" + download "${TMP_METADATA}" "${METADATA_URL}" + + VERSION_KUSTOMIZER=$(grep '"tag_name":' "${TMP_METADATA}" | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-) + if [[ -n "${VERSION_KUSTOMIZER}" ]]; then + info "Using ${VERSION_KUSTOMIZER} as release" + else + fatal "Unable to determine release version" + fi +} + +# Download from file from URL +download() { + [[ $# -eq 2 ]] || fatal 'download needs exactly 2 arguments' + + case $DOWNLOADER in + curl) + curl -o "$1" -sfL "$2" + ;; + wget) + wget -qO "$1" "$2" + ;; + *) + fatal "Incorrect executable '${DOWNLOADER}'" + ;; + esac -function cleanup { - rm -rf "$tmpDir" + # Abort if download command failed + [[ $? -eq 0 ]] || fatal 'Download failed' } -trap cleanup EXIT +# Download hash from Github URL +download_hash() { + HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_KUSTOMIZER}/kustomizer_${VERSION_KUSTOMIZER}_checksums.txt" + set -e -pushd $tmpDir >& /dev/null + info "Downloading hash ${HASH_URL}" + download "${TMP_HASH}" "${HASH_URL}" + HASH_EXPECTED=$(grep " kustomizer_${VERSION_KUSTOMIZER}_${OS}_${ARCH}.tar.gz$" "${TMP_HASH}") + HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*} +} -curl -s https://api.github.com/repos/stefanprodan/kustomizer/releases/latest |\ - grep browser_download |\ - grep $opsys |\ - cut -d '"' -f 4 |\ - xargs curl -sL -o kustomizer.tar.gz +# Download binary from Github URL +download_binary() { + BIN_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_KUSTOMIZER}/kustomizer_${VERSION_KUSTOMIZER}_${OS}_${ARCH}.tar.gz" + info "Downloading binary ${BIN_URL}" + download "${TMP_BIN}" "${BIN_URL}" +} -tar xzf ./kustomizer.tar.gz +compute_sha256sum() { + cmd=$(which sha256sum shasum | head -n 1) + case $(basename "$cmd") in + sha256sum) + sha256sum "$1" | cut -f 1 -d ' ' + ;; + shasum) + shasum -a 256 "$1" | cut -f 1 -d ' ' + ;; + *) + fatal "Can not find sha256sum or shasum to compute checksum" + ;; + esac +} -mv ./kustomizer $BIN_DIR +# Verify downloaded binary hash +verify_binary() { + info "Verifying binary download" + HASH_BIN=$(compute_sha256sum "${TMP_BIN}") + HASH_BIN=${HASH_BIN%%[[:blank:]]*} + if [[ "${HASH_EXPECTED}" != "${HASH_BIN}" ]]; then + fatal "Download sha256 does not match ${HASH_EXPECTED}, got ${HASH_BIN}" + fi +} -popd >& /dev/null +# Setup permissions and move binary +setup_binary() { + chmod 755 "${TMP_BIN}" + info "Installing kustomizer to ${BIN_DIR}/kustomizer" + tar -xzof "${TMP_BIN}" -C "${TMP_DIR}" -echo "$(kustomizer --version) installed" + local CMD_MOVE="mv -f \"${TMP_DIR}/kustomizer\" \"${BIN_DIR}\"" + if [[ -w "${BIN_DIR}" ]]; then + eval "${CMD_MOVE}" + else + eval "sudo ${CMD_MOVE}" + fi +} + +# Run the install process +{ + setup_verify_os + setup_verify_arch + verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files' + setup_tmp + get_release_version + download_hash + download_binary + verify_binary + setup_binary +} diff --git a/pkg/engine/applier.go b/pkg/engine/applier.go deleted file mode 100644 index bae6324..0000000 --- a/pkg/engine/applier.go +++ /dev/null @@ -1,117 +0,0 @@ -package engine - -import ( - "bytes" - "context" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "time" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/yaml" - "sigs.k8s.io/kustomize/api/filesys" - yaml2 "sigs.k8s.io/yaml" -) - -type Applier struct { - fs filesys.FileSystem - timeout time.Duration - kubectl KubectlExecutor -} - -func NewApplier(fs filesys.FileSystem, timeout time.Duration, ke KubectlExecutor) (*Applier, error) { - return &Applier{ - fs: fs, - timeout: timeout, - kubectl: ke, - }, nil -} - -func (a *Applier) Run(manifestPath string, dryRun bool) error { - if !a.fs.Exists(manifestPath) { - return fmt.Errorf("%s not found", manifestPath) - } - - crds, err := a.ExtractCRDs(manifestPath) - if err != nil { - return err - } - - ctx, cancel := context.WithTimeout(context.Background(), a.timeout+time.Second) - defer cancel() - - if crds != "" { - args := []string{"apply", "-f", crds, "--timeout", a.timeout.String()} - if dryRun { - args = append(args, "--dry-run", "client") - } - - if err := a.kubectl.Exec(ctx, args...); err != nil { - return err - } - } - - args := []string{"apply", "-f", manifestPath, "--timeout", a.timeout.String()} - if dryRun { - args = append(args, "--dry-run", "client") - } - - return a.kubectl.Exec(ctx, args...) -} - -func (a *Applier) ExtractCRDs(manifestPath string) (string, error) { - manifests, err := ioutil.ReadFile(manifestPath) - if err != nil { - return "", err - } - - crds := "" - reader := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifests), 2048) - for { - var obj unstructured.Unstructured - err := reader.Decode(&obj) - if err == io.EOF { - break - } else if err != nil { - return "", err - } - if obj.IsList() { - err := obj.EachListItem(func(item runtime.Object) error { - return nil - }) - if err != nil { - return "", err - } - } else { - if obj.GetKind() == "CustomResourceDefinition" { - b, err := obj.MarshalJSON() - if err != nil { - return "", err - } - - y, err := yaml2.JSONToYAML(b) - if err != nil { - return "", err - } - crds += "---\n" + string(y) - } - } - } - - if crds == "" { - return "", nil - } - - base := filepath.Dir(manifestPath) - crdsFile := filepath.Join(base, "extracted-crds.yaml") - - if err := ioutil.WriteFile(crdsFile, []byte(crds), os.ModePerm); err != nil { - return "", err - } - - return crdsFile, nil -} diff --git a/pkg/engine/builder.go b/pkg/engine/builder.go deleted file mode 100644 index c5605f6..0000000 --- a/pkg/engine/builder.go +++ /dev/null @@ -1,126 +0,0 @@ -package engine - -import ( - "bytes" - "fmt" - "os/exec" - "path/filepath" - "runtime" - - "go.mozilla.org/sops/v3/aes" - "go.mozilla.org/sops/v3/cmd/sops/common" - "go.mozilla.org/sops/v3/cmd/sops/formats" - "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/krusty" - "sigs.k8s.io/kustomize/api/resource" - "sigs.k8s.io/yaml" -) - -type Builder struct { - fs filesys.FileSystem -} - -func NewBuilder(fs filesys.FileSystem) (*Builder, error) { - return &Builder{fs: fs}, nil -} - -// Generate Kubernetes manifests from a kustomization -func (b *Builder) Generate(base string, filePath string) error { - kfile := filepath.Join(base, "kustomization.yaml") - - if !b.fs.Exists(kfile) { - return fmt.Errorf("%s not found", kfile) - } - - opt := krusty.MakeDefaultOptions() - opt.DoLegacyResourceSort = true - k := krusty.MakeKustomizer(b.fs, opt) - m, err := k.Run(base) - if err != nil { - return err - } - - // check if resources are SOPS encrypted and decrypt them before - // generating the final YAML - for _, res := range m.Resources() { - outRes, err := b.decryptSOPS(res) - if err != nil { - return err - } - - if outRes != nil { - _, err = m.Replace(res) - if err != nil { - return err - } - } - } - - resources, err := m.AsYaml() - if err != nil { - return err - } - - if err := b.fs.WriteFile(filePath, resources); err != nil { - return err - } - - return nil -} - -func (b *Builder) decryptSOPS(res *resource.Resource) (*resource.Resource, error) { - out, err := res.AsYAML() - if err != nil { - return nil, err - } - - if bytes.Contains(out, []byte("sops:")) && bytes.Contains(out, []byte("mac: ENC[")) { - store := common.StoreForFormat(formats.Yaml) - - // Load SOPS file and access the data key - tree, err := store.LoadEncryptedFile(out) - if err != nil { - return nil, fmt.Errorf("LoadEncryptedFile: %w", err) - } - key, err := tree.Metadata.GetDataKey() - if err != nil { - return nil, fmt.Errorf("GetDataKey: %w", err) - } - - // Decrypt the tree - cipher := aes.NewCipher() - if _, err := tree.Decrypt(key, cipher); err != nil { - return nil, fmt.Errorf("Decrypt: %w", err) - } - - data, err := store.EmitPlainFile(tree.Branches) - if err != nil { - return nil, fmt.Errorf("EmitPlainFile: %w", err) - } - - jsonData, err := yaml.YAMLToJSON(data) - if err != nil { - return nil, fmt.Errorf("YAMLToJSON: %w", err) - } - - err = res.UnmarshalJSON(jsonData) - if err != nil { - return nil, fmt.Errorf("UnmarshalJSON: %w", err) - } - return res, nil - } - return nil, nil -} - -func (b *Builder) Build(base string, filePath string) error { - if _, err := exec.LookPath("kustomize"); err != nil { - return fmt.Errorf("kustomize not found") - } - - command := fmt.Sprintf("kustomize build %s > %s", base, filePath) - c := exec.Command("/bin/sh", "-c", command) - if runtime.GOOS == "windows" { - c = exec.Command("cmd", "/c", command) - } - return c.Run() -} diff --git a/pkg/engine/collector.go b/pkg/engine/collector.go deleted file mode 100644 index 8b89f2d..0000000 --- a/pkg/engine/collector.go +++ /dev/null @@ -1,161 +0,0 @@ -package engine - -import ( - "context" - "fmt" - "io/ioutil" - "os/exec" - "strings" - "time" -) - -type GarbageCollector struct { - rv *Revisor - timeout time.Duration - kubectl KubectlExecutor -} - -func NewGarbageCollector(revisor *Revisor, timeout time.Duration, ke KubectlExecutor) (*GarbageCollector, error) { - if revisor == nil { - return nil, fmt.Errorf("revisor is nil") - } - - if _, err := exec.LookPath("kubectl"); err != nil { - return nil, fmt.Errorf("kubectl not found") - } - - return &GarbageCollector{ - rv: revisor, - timeout: timeout, - kubectl: ke, - }, nil -} - -func (gc *GarbageCollector) Run(manifestsFile string, cfgNamespace string, write func(string)) error { - data, err := ioutil.ReadFile(manifestsFile) - if err != nil { - return err - } - - newSnapshot, err := NewSnapshot(data, gc.rv.revision) - if err != nil { - return err - } - - firstTime := false - cfg, err := gc.getSnapshot(cfgNamespace) - if err != nil { - if strings.Contains(err.Error(), "NotFound") { - firstTime = true - } else { - return err - } - } - - if !firstTime { - oldSnapshot, err := NewSnapshotFromConfigMap(cfg) - if err != nil { - return err - } - - if newSnapshot.Revision != oldSnapshot.Revision { - err := gc.prune(*oldSnapshot, false, write) - if err != nil { - return err - } - } - } - - newCfg, err := newSnapshot.ToConfigMap(gc.rv.SnapshotName(), cfgNamespace) - if err != nil { - return err - } - - msg, err := gc.applySnapshot(newCfg) - if err != nil { - return err - } - - write(msg) - return nil -} - -func (gc *GarbageCollector) Cleanup(cfgNamespace string, write func(string)) error { - cfg, err := gc.getSnapshot(cfgNamespace) - if err != nil { - return err - } - - snapshot, err := NewSnapshotFromConfigMap(cfg) - if err != nil { - return err - } - - err = gc.prune(*snapshot, false, write) - if err != nil { - return err - } - - msg, err := gc.deleteSnapshot(cfgNamespace) - if err != nil { - return err - } - - write(msg) - - return nil -} - -func (gc *GarbageCollector) prune(snapshot Snapshot, dryRun bool, write func(string)) error { - selector := gc.rv.PrevSelectors(snapshot.Revision) - for ns, kinds := range snapshot.NamespacedKinds() { - for _, kind := range kinds { - if output, err := gc.deleteByKind(kind, ns, selector, dryRun, gc.timeout); err != nil { - write(err.Error()) - } else { - write(output) - } - } - } - - for _, kind := range snapshot.NonNamespacedKinds() { - if output, err := gc.deleteByKind(kind, "", selector, dryRun, gc.timeout); err != nil { - write(err.Error()) - } else { - write(output) - } - } - - return nil -} - -func (gc *GarbageCollector) deleteByKind(kind string, namespace string, selector string, dryRun bool, timeout time.Duration) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeout+time.Second) - defer cancel() - - args := []string{"delete", kind, "-l", selector} - - if namespace != "" { - args = append(args, "-n", namespace) - } - if dryRun { - args = append(args, "--dry-run", "server") - } - - return gc.kubectl.Get(ctx, args...) -} - -func (gc *GarbageCollector) getSnapshot(cfgNamespace string) (string, error) { - args := []string{"-n", cfgNamespace, "get", "configmap", gc.rv.SnapshotName(), "-o", "yaml"} - return gc.kubectl.Get(context.TODO(), args...) -} - -func (gc *GarbageCollector) applySnapshot(cfg string) (string, error) { - args := []string{"apply", "-f", "-"} - return gc.kubectl.Pipe(context.TODO(), cfg, args...) -} - -func (gc *GarbageCollector) deleteSnapshot(cfgNamespace string) (string, error) { - args := []string{"-n", cfgNamespace, "delete", "configmap", gc.rv.SnapshotName()} - return gc.kubectl.Get(context.TODO(), args...) -} diff --git a/pkg/engine/generator.go b/pkg/engine/generator.go deleted file mode 100644 index a5d2e0e..0000000 --- a/pkg/engine/generator.go +++ /dev/null @@ -1,177 +0,0 @@ -package engine - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" - "sigs.k8s.io/kustomize/api/konfig" - "sigs.k8s.io/kustomize/api/types" - "sigs.k8s.io/yaml" -) - -type Generator struct { - rv *Revisor - fs filesys.FileSystem -} - -func NewGenerator(fs filesys.FileSystem, revisor *Revisor) (*Generator, error) { - if revisor == nil { - return nil, fmt.Errorf("revisor is nil") - } - - return &Generator{ - rv: revisor, - fs: fs, - }, nil -} - -// Generate kustomization file or append label transformer to an existing one -func (g *Generator) Generate(base string) error { - kfile := filepath.Join(base, "kustomization.yaml") - if g.fs.Exists(kfile) { - if err := g.edit(base); err != nil { - return err - } - } else { - if err := g.create(base); err != nil { - return err - } - } - return nil -} - -func (g *Generator) create(base string) error { - kfile := filepath.Join(base, "kustomization.yaml") - - path, err := filepath.Abs(base) - if err != nil { - return err - } - - files, err := g.scan(path, true) - if err != nil { - return err - } - - f, err := g.fs.Create(kfile) - if err != nil { - return err - } - f.Close() - - kustomization := types.Kustomization{ - TypeMeta: types.TypeMeta{ - APIVersion: types.KustomizationVersion, - Kind: types.KustomizationKind, - }, - } - - var resources []string - - for _, file := range files { - if _, name := filepath.Split(file); name == g.rv.LabelsFile() { - continue - } - resources = append(resources, strings.Replace(file, path, ".", 1)) - } - - kustomization.Resources = resources - kustomization.Transformers = []string{g.rv.LabelsFile()} - - data, err := yaml.Marshal(kustomization) - if err != nil { - return err - } - - if err := g.fs.WriteFile(kfile, data); err != nil { - return err - } - return nil -} - -func (g *Generator) scan(base string, recursive bool) ([]string, error) { - var paths []string - uf := kunstruct.NewKunstructuredFactoryImpl() - err := g.fs.Walk(base, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if path == base { - return nil - } - if info.IsDir() { - if !recursive { - return filepath.SkipDir - } - // If a sub-directory contains an existing kustomization file add the - // directory as a resource and do not decend into it. - for _, kfilename := range konfig.RecognizedKustomizationFileNames() { - if g.fs.Exists(filepath.Join(path, kfilename)) { - paths = append(paths, path) - return filepath.SkipDir - } - } - return nil - } - fContents, err := g.fs.ReadFile(path) - if err != nil { - return err - } - if _, err := uf.SliceFromBytes(fContents); err != nil { - return nil - } - paths = append(paths, path) - return nil - }) - return paths, err -} - -func (g *Generator) edit(base string) error { - kfile := filepath.Join(base, "kustomization.yaml") - - f, err := g.fs.ReadFile(kfile) - if err != nil { - return err - } - - kustomization := types.Kustomization{ - TypeMeta: types.TypeMeta{ - APIVersion: types.KustomizationVersion, - Kind: types.KustomizationKind, - }, - } - - if err := yaml.Unmarshal(f, &kustomization); err != nil { - return err - } - - if len(kustomization.Transformers) == 0 { - kustomization.Transformers = []string{g.rv.LabelsFile()} - } else { - var exists bool - for _, transformer := range kustomization.Transformers { - if transformer == g.rv.LabelsFile() { - exists = true - break - } - } - if !exists { - kustomization.Transformers = append(kustomization.Transformers, g.rv.LabelsFile()) - } - } - - data, err := yaml.Marshal(kustomization) - if err != nil { - return err - } - - if err := g.fs.WriteFile(kfile, data); err != nil { - return err - } - - return nil -} diff --git a/pkg/engine/kubectl.go b/pkg/engine/kubectl.go deleted file mode 100644 index bd2283e..0000000 --- a/pkg/engine/kubectl.go +++ /dev/null @@ -1,66 +0,0 @@ -package engine - -import ( - "context" - "fmt" - "os" - "os/exec" - "strings" -) - -// KubectlExecutor an executor that shells out to run commands -type KubectlExecutor struct { - kubectl string - envVars []string -} - -// NewKubectlExecutor creates a new executor that runs kubectl commands -func NewKubectlExecutor(kubectl string, envVars []string) KubectlExecutor { - return KubectlExecutor{ - envVars: envVars, - kubectl: kubectl, - } -} - -// Exec execute the kubectl command with the specified args -func (e KubectlExecutor) Exec(ctx context.Context, args ...string) error { - cmd := e.buildCmd(ctx, args) - if len(e.envVars) > 0 { - cmd.Env = e.envVars - } - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} - -// Get execute the kubectl command with the specified args and returns the output as string -func (e KubectlExecutor) Get(ctx context.Context, args ...string) (string, error) { - cmd := e.buildCmd(ctx, args) - if len(e.envVars) > 0 { - cmd.Env = e.envVars - } - if output, err := cmd.CombinedOutput(); err != nil { - return "", fmt.Errorf("%s", string(output)) - } else { - return strings.TrimSuffix(string(output), "\n"), nil - } -} - -// Pipe execute the kubectl command by piping the yaml arg -func (e KubectlExecutor) Pipe(ctx context.Context, yaml string, args ...string) (string, error) { - cmd := e.buildCmd(ctx, args) - if len(e.envVars) > 0 { - cmd.Env = e.envVars - } - cmd.Stdin = strings.NewReader(yaml) - if output, err := cmd.CombinedOutput(); err != nil { - return "", fmt.Errorf("%s", string(output)) - } else { - return strings.TrimSuffix(string(output), "\n"), nil - } -} - -func (e KubectlExecutor) buildCmd(ctx context.Context, args []string) *exec.Cmd { - s := append(strings.Fields(e.kubectl), args...) - return exec.CommandContext(ctx, s[0], s[1:]...) -} diff --git a/pkg/engine/revisor.go b/pkg/engine/revisor.go deleted file mode 100644 index 3ad042b..0000000 --- a/pkg/engine/revisor.go +++ /dev/null @@ -1,62 +0,0 @@ -package engine - -import ( - "crypto/sha1" - "fmt" -) - -type Revisor struct { - group string - name string - revision string -} - -func NewRevisior(group, name, revision string) (*Revisor, error) { - if group == "" { - return nil, fmt.Errorf("group not specified") - } - if name == "" { - return nil, fmt.Errorf("name not specified") - } - if revision == "" { - return nil, fmt.Errorf("revision not specified") - } - - return &Revisor{ - group: group, - name: name, - revision: revision, - }, nil -} - -func (r *Revisor) Hash() string { - gv := fmt.Sprintf("%s-%s", r.group, r.name) - return fmt.Sprintf("%x", sha1.Sum([]byte(gv))) -} - -func (r *Revisor) Labels() map[string]string { - return map[string]string{ - fmt.Sprintf("%s/name", r.group): r.name, - fmt.Sprintf("%s/revision", r.group): r.revision, - } -} - -func (r *Revisor) LabelsFile() string { - return fmt.Sprintf("%s-labels.yaml", r.Hash()) -} - -func (r *Revisor) NextSelectors() string { - return fmt.Sprintf("%s/name=%s,%s/revision=%s", r.group, r.name, r.group, r.revision) -} - -func (r *Revisor) PrevSelectors(revision string) string { - return fmt.Sprintf("%s/name=%s,%s/revision=%s", r.group, r.name, r.group, revision) -} - -func (r *Revisor) ManifestFile() string { - return fmt.Sprintf("%s-manifest.yaml", r.Hash()) -} - -func (r *Revisor) SnapshotName() string { - return fmt.Sprintf("%s-snapshot", r.name) -} diff --git a/pkg/engine/snapshot.go b/pkg/engine/snapshot.go deleted file mode 100644 index a62bf2c..0000000 --- a/pkg/engine/snapshot.go +++ /dev/null @@ -1,159 +0,0 @@ -package engine - -import ( - "bytes" - js "encoding/json" - "fmt" - "io" - - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer/json" - "k8s.io/apimachinery/pkg/util/yaml" -) - -type Snapshot struct { - Revision string `json:"revision"` - Entries []SnapshotEntry `json:"entries"` -} - -type SnapshotEntry struct { - Namespace string `json:"namespace"` - Kinds map[string]string `json:"kinds"` -} - -func NewSnapshot(manifests []byte, revision string) (*Snapshot, error) { - snapshot := Snapshot{ - Revision: revision, - Entries: []SnapshotEntry{}, - } - - reader := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifests), 2048) - for { - var obj unstructured.Unstructured - err := reader.Decode(&obj) - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - if obj.IsList() { - err := obj.EachListItem(func(item runtime.Object) error { - snapshot.addEntry(item.(*unstructured.Unstructured)) - return nil - }) - if err != nil { - return nil, err - } - } else { - snapshot.addEntry(&obj) - } - } - - return &snapshot, nil -} - -func NewSnapshotFromConfigMap(manifest string) (*Snapshot, error) { - reader := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(manifest), 2048) - var cm corev1.ConfigMap - err := reader.Decode(&cm) - if err != nil { - return nil, err - } - - if _, ok := cm.Data["snapshot"]; !ok { - return nil, fmt.Errorf("snapshot data not found") - } - - data := []byte(cm.Data["snapshot"]) - - var snapshot Snapshot - err = js.Unmarshal(data, &snapshot) - if err != nil { - return nil, err - } - - return &snapshot, err -} - -func (s *Snapshot) ToConfigMap(name, namespace string) (string, error) { - data, err := js.Marshal(s) - if err != nil { - return "", err - } - cm := &corev1.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: v1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Data: map[string]string{ - "snapshot": string(data), - }, - } - - scheme := runtime.NewScheme() - serializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{ - Pretty: false, - Yaml: false, - Strict: true, - }) - - buf := bytes.NewBufferString("") - err = serializer.Encode(cm, buf) - if err != nil { - return "", err - } - - return buf.String(), nil -} - -func (s *Snapshot) addEntry(item *unstructured.Unstructured) { - found := false - for _, tracker := range s.Entries { - if tracker.Namespace == item.GetNamespace() { - tracker.Kinds[item.GetKind()] = item.GetAPIVersion() - found = true - break - } - } - if !found { - s.Entries = append(s.Entries, SnapshotEntry{ - Namespace: item.GetNamespace(), - Kinds: map[string]string{ - item.GetKind(): item.GetAPIVersion(), - }, - }) - } -} - -func (s *Snapshot) NonNamespacedKinds() []string { - kinds := make([]string, 0) - for _, tracker := range s.Entries { - if tracker.Namespace == "" { - for k, _ := range tracker.Kinds { - kinds = append(kinds, k) - } - } - } - return kinds -} - -func (s *Snapshot) NamespacedKinds() map[string][]string { - nsk := make(map[string][]string) - for _, tracker := range s.Entries { - if tracker.Namespace != "" { - var kinds []string - for k, _ := range tracker.Kinds { - kinds = append(kinds, k) - } - nsk[tracker.Namespace] = kinds - } - } - return nsk -} diff --git a/pkg/engine/transfomer.go b/pkg/engine/transfomer.go deleted file mode 100644 index f8e0faf..0000000 --- a/pkg/engine/transfomer.go +++ /dev/null @@ -1,63 +0,0 @@ -package engine - -import ( - "fmt" - "path/filepath" - - "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/types" - "sigs.k8s.io/yaml" -) - -type Transformer struct { - rv *Revisor - fs filesys.FileSystem -} - -func NewTransformer(fs filesys.FileSystem, revisor *Revisor) (*Transformer, error) { - if revisor == nil { - return nil, fmt.Errorf("revisor is nil") - } - - return &Transformer{ - rv: revisor, - fs: fs, - }, nil -} - -// Generate label transformer file in the base dir -func (t *Transformer) Generate(base string) error { - var lt = struct { - ApiVersion string `json:"apiVersion" yaml:"apiVersion"` - Kind string `json:"kind" yaml:"kind"` - Metadata struct { - Name string `json:"name" yaml:"name"` - } `json:"metadata" yaml:"metadata"` - Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` - FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` - }{ - ApiVersion: "builtin", - Kind: "LabelTransformer", - Metadata: struct { - Name string `json:"name" yaml:"name"` - }{ - Name: t.rv.name, - }, - Labels: t.rv.Labels(), - FieldSpecs: []types.FieldSpec{ - {Path: "metadata/labels", CreateIfNotPresent: true}, - }, - } - - data, err := yaml.Marshal(lt) - if err != nil { - return err - } - - path := filepath.Join(base, t.rv.LabelsFile()) - if err := t.fs.WriteFile(path, data); err != nil { - return err - } - - return nil -} diff --git a/pkg/inventory/doc.go b/pkg/inventory/doc.go new file mode 100644 index 0000000..fc01cfd --- /dev/null +++ b/pkg/inventory/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package inventory contains utilities for keeping a record of Kubernetes objects applied on a cluster. +package inventory diff --git a/pkg/inventory/inventory.go b/pkg/inventory/inventory.go new file mode 100644 index 0000000..cecb7ac --- /dev/null +++ b/pkg/inventory/inventory.go @@ -0,0 +1,173 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package inventory + +import ( + "sort" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/object" +) + +// Inventory is a record of objects that are applied on a cluster stored as a configmap. +type Inventory struct { + // Name of the inventory configmap. + Name string `json:"name"` + + // Namespace of the inventory configmap. + Namespace string `json:"namespace"` + + // Source is the URL of the source code. + Source string `json:"source,omitempty"` + + // Revision is the Source control revision identifier. + Revision string `json:"revision,omitempty"` + + // Entries of Kubernetes objects metadata. + Entries []Entry `json:"entries"` +} + +// Entry contains the information necessary to locate the +// resource within a cluster. +type Entry struct { + // ObjectID is the string representation of object.ObjMetadata, + // in the format '___'. + ObjectID string `json:"id"` + + // ObjectVersion is the API version of this entry kind. + ObjectVersion string `json:"ver"` +} + +func NewInventory(name, namespace string) *Inventory { + return &Inventory{ + Name: name, + Namespace: namespace, + Entries: []Entry{}, + } +} + +// SetSource sets the source url and revision for this inventory. +func (inv *Inventory) SetSource(url, revision string) { + inv.Source = url + inv.Revision = revision +} + +// AddObjects extracts the metadata from the given objects and adds it to the inventory. +func (inv *Inventory) AddObjects(objects []*unstructured.Unstructured) error { + sort.Sort(objectutil.SortableUnstructureds(objects)) + for _, om := range objects { + objMetadata := object.UnstructuredToObjMeta(om) + gv, err := schema.ParseGroupVersion(om.GetAPIVersion()) + if err != nil { + return err + } + + inv.Entries = append(inv.Entries, Entry{ + ObjectID: objMetadata.String(), + ObjectVersion: gv.Version, + }) + } + + return nil +} + +// VersionOf returns the API version of the given object if found in this inventory. +func (inv *Inventory) VersionOf(objMetadata object.ObjMetadata) string { + for _, entry := range inv.Entries { + if entry.ObjectID == objMetadata.String() { + return entry.ObjectVersion + } + } + return "" +} + +// ListObjects returns the inventory entries as unstructured.Unstructured objects. +func (inv *Inventory) ListObjects() ([]*unstructured.Unstructured, error) { + objects := make([]*unstructured.Unstructured, 0) + + for _, entry := range inv.Entries { + objMetadata, err := object.ParseObjMetadata(entry.ObjectID) + if err != nil { + return nil, err + } + + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{ + Group: objMetadata.GroupKind.Group, + Kind: objMetadata.GroupKind.Kind, + Version: entry.ObjectVersion, + }) + u.SetName(objMetadata.Name) + u.SetNamespace(objMetadata.Namespace) + objects = append(objects, u) + } + + sort.Sort(objectutil.SortableUnstructureds(objects)) + return objects, nil +} + +// ListMeta returns the inventory entries as object.ObjMetadata objects. +func (inv *Inventory) ListMeta() ([]object.ObjMetadata, error) { + var metas []object.ObjMetadata + for _, e := range inv.Entries { + m, err := object.ParseObjMetadata(e.ObjectID) + if err != nil { + return metas, err + } + metas = append(metas, m) + } + + return metas, nil +} + +// Diff returns the slice of objects that do not exist in the target inventory. +func (inv *Inventory) Diff(target *Inventory) ([]*unstructured.Unstructured, error) { + objects := make([]*unstructured.Unstructured, 0) + aList, err := inv.ListMeta() + if err != nil { + return nil, err + } + + bList, err := target.ListMeta() + if err != nil { + return nil, err + } + + list := object.SetDiff(aList, bList) + if len(list) == 0 { + return objects, nil + } + + for _, metadata := range list { + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.GroupKind.Group, + Kind: metadata.GroupKind.Kind, + Version: inv.VersionOf(metadata), + }) + u.SetName(metadata.Name) + u.SetNamespace(metadata.Namespace) + objects = append(objects, u) + } + + sort.Sort(objectutil.SortableUnstructureds(objects)) + return objects, nil +} diff --git a/pkg/manager/changeset.go b/pkg/manager/changeset.go new file mode 100644 index 0000000..e13b9ed --- /dev/null +++ b/pkg/manager/changeset.go @@ -0,0 +1,64 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import "fmt" + +// Action resents the action type performed by the reconciliation process. +type Action string + +const ( + CreatedAction Action = "created" + ConfiguredAction Action = "configured" + UnchangedAction Action = "unchanged" + DeletedAction Action = "deleted" +) + +// ChangeSet holds the result of the reconciliation of an object collection. +type ChangeSet struct { + Entries []ChangeSetEntry +} + +// NewChangeSet returns a ChangeSet will an empty slice of entries. +func NewChangeSet() *ChangeSet { + return &ChangeSet{Entries: []ChangeSetEntry{}} +} + +// Add appends the given entry to the end of the slice. +func (c *ChangeSet) Add(e ChangeSetEntry) { + c.Entries = append(c.Entries, e) +} + +// Append adds the given ChangeSet entries to end of the slice. +func (c *ChangeSet) Append(e []ChangeSetEntry) { + c.Entries = append(c.Entries, e...) +} + +// ChangeSetEntry defines the result of an action performed on an object. +type ChangeSetEntry struct { + // Subject represents the Object ID in the format 'kind/namespace/name'. + Subject string + // Action represents the action type taken by the reconciler for this object. + Action string + // Diff contains the YAML diff resulting from server-side apply dry-run. + Diff string +} + +func (e ChangeSetEntry) String() string { + return fmt.Sprintf("%s %s", e.Subject, e.Action) +} diff --git a/pkg/manager/doc.go b/pkg/manager/doc.go new file mode 100644 index 0000000..8a1810d --- /dev/null +++ b/pkg/manager/doc.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package manager contains utilities for managing Kubernetes resources. +// +// The ResourceManager can be used to write a GitOps reconciler that: +// - maintains an inventory of objects applied on the cluster +// - orders the Kubernetes objects for apply (CRDs, Namespaces first, Webhooks last) +// - validates the objects with server-side dry-run apply +// - determines if the in-cluster objects are in drift based on the dry-run result +// - applies the objects on the cluster with server-side apply +// - waits for the objects to be fully reconciled by looking up their readiness status +// - deletes objects that are subject to garbage collection +// - waits for the deleted objects to be terminated +package manager diff --git a/pkg/manager/main_test.go b/pkg/manager/main_test.go new file mode 100644 index 0000000..180f715 --- /dev/null +++ b/pkg/manager/main_test.go @@ -0,0 +1,107 @@ +package manager + +import ( + "fmt" + "os" + "strings" + "sync/atomic" + "testing" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +var manager *ResourceManager + +func TestMain(m *testing.M) { + testEnv := &envtest.Environment{} + + cfg, err := testEnv.Start() + if err != nil { + panic(err) + } + + restMapper, err := apiutil.NewDynamicRESTMapper(cfg) + if err != nil { + panic(err) + } + + kubeClient, err := client.New(cfg, client.Options{ + Mapper: restMapper, + }) + if err != nil { + panic(err) + } + + poller := polling.NewStatusPoller(kubeClient, restMapper) + + manager = &ResourceManager{ + client: kubeClient, + poller: poller, + owner: Owner{ + Field: "resource-manager", + Group: "resource-manager.io", + }, + } + + code := m.Run() + + testEnv.Stop() + + os.Exit(code) +} + +func readManifest(manifest, namespace string) ([]*unstructured.Unstructured, error) { + data, err := os.ReadFile(manifest) + if err != nil { + return nil, err + } + yml := fmt.Sprintf(string(data), namespace) + + objects, err := objectutil.ReadObjects(strings.NewReader(yml)) + if err != nil { + return nil, err + } + + return objects, nil +} + +func setNamespace(objects []*unstructured.Unstructured, namespace string) { + for _, object := range objects { + object.SetNamespace(namespace) + } + + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "", + Kind: "Namespace", + Version: "v1", + }) + u.SetName(namespace) + objects = append(objects, u) +} + +var nextNameId int64 + +func generateName(prefix string) string { + id := atomic.AddInt64(&nextNameId, 1) + return fmt.Sprintf("%s-%d", prefix, id) +} + +func getFirstObject(objects []*unstructured.Unstructured, kind, name string) (string, *unstructured.Unstructured) { + for _, object := range objects { + if object.GetKind() == kind && object.GetName() == name { + return objectutil.FmtUnstructured(object), object + } + } + return "", nil +} + +func removeObject(s []*unstructured.Unstructured, index int) []*unstructured.Unstructured { + return append(s[:index], s[index+1:]...) +} diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go new file mode 100644 index 0000000..9c50dd3 --- /dev/null +++ b/pkg/manager/manager.go @@ -0,0 +1,64 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" +) + +// ResourceManager reconciles Kubernetes resources onto the target cluster. +type ResourceManager struct { + client client.Client + poller *polling.StatusPoller + owner Owner +} + +// NewResourceManager creates a ResourceManager for the given Kubernetes client. +func NewResourceManager(client client.Client, poller *polling.StatusPoller, owner Owner) *ResourceManager { + return &ResourceManager{ + client: client, + poller: poller, + owner: owner, + } +} + +// Client returns the underlying controller-runtime client. +func (m *ResourceManager) Client() client.Client { + return m.client +} + +// SetOwnerLabels adds the ownership labels to the given objects. +// The ownership labels are in the format: +// /name: +// /namespace: +func (m *ResourceManager) SetOwnerLabels(objects []*unstructured.Unstructured, name, namespace string) { + for _, object := range objects { + object.SetLabels(map[string]string{ + m.owner.Group + "/name": name, + m.owner.Group + "/namespace": namespace, + }) + } +} + +func (m *ResourceManager) changeSetEntry(object *unstructured.Unstructured, action Action) *ChangeSetEntry { + return &ChangeSetEntry{Subject: objectutil.FmtUnstructured(object), Action: string(action)} +} diff --git a/pkg/manager/manager_apply.go b/pkg/manager/manager_apply.go new file mode 100644 index 0000000..a269e94 --- /dev/null +++ b/pkg/manager/manager_apply.go @@ -0,0 +1,181 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" +) + +// Apply performs a server-side apply of the given object if the matching in-cluster object is different or if it doesn't exist. +// Drift detection is performed by comparing the server-side dry-run result with the existing object. +// When immutable field changes are detected, the object is recreated if 'force' is set to 'true'. +func (m *ResourceManager) Apply(ctx context.Context, object *unstructured.Unstructured, force bool) (*ChangeSetEntry, error) { + existingObject := object.DeepCopy() + _ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject) + + dryRunObject := object.DeepCopy() + if err := m.dryRunApply(ctx, dryRunObject); err != nil { + if force && strings.Contains(err.Error(), "immutable") { + if err := m.client.Delete(ctx, existingObject); err != nil { + return nil, fmt.Errorf("%s immutable field detected, failed to delete object, error: %w", + objectutil.FmtUnstructured(dryRunObject), err) + } + return m.Apply(ctx, object, force) + } + + return nil, m.validationError(dryRunObject, err) + } + + // do not apply objects that have not drifted to avoid bumping the resource version + if !m.hasDrifted(existingObject, dryRunObject) { + return m.changeSetEntry(object, UnchangedAction), nil + } + + appliedObject := object.DeepCopy() + if err := m.apply(ctx, appliedObject); err != nil { + return nil, fmt.Errorf("%s apply failed, error: %w", objectutil.FmtUnstructured(appliedObject), err) + } + + if dryRunObject.GetResourceVersion() == "" { + return m.changeSetEntry(appliedObject, CreatedAction), nil + } + + return m.changeSetEntry(appliedObject, ConfiguredAction), nil +} + +// ApplyAll performs a server-side dry-run of the given objects, and based on the diff result, +// it applies the objects that are new or modified. +func (m *ResourceManager) ApplyAll(ctx context.Context, objects []*unstructured.Unstructured, force bool) (*ChangeSet, error) { + sort.Sort(objectutil.SortableUnstructureds(objects)) + changeSet := NewChangeSet() + var toApply []*unstructured.Unstructured + for _, object := range objects { + existingObject := object.DeepCopy() + _ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject) + + dryRunObject := object.DeepCopy() + if err := m.dryRunApply(ctx, dryRunObject); err != nil { + if force && strings.Contains(err.Error(), "immutable") { + if err := m.client.Delete(ctx, existingObject); err != nil { + return nil, fmt.Errorf("%s immutable field detected, failed to delete object, error: %w", + objectutil.FmtUnstructured(dryRunObject), err) + } + return m.ApplyAll(ctx, objects, force) + } + + return nil, m.validationError(dryRunObject, err) + } + + if m.hasDrifted(existingObject, dryRunObject) { + toApply = append(toApply, object) + if dryRunObject.GetResourceVersion() == "" { + changeSet.Add(*m.changeSetEntry(dryRunObject, CreatedAction)) + } else { + changeSet.Add(*m.changeSetEntry(dryRunObject, ConfiguredAction)) + } + } else { + changeSet.Add(*m.changeSetEntry(dryRunObject, UnchangedAction)) + } + } + + for _, object := range toApply { + appliedObject := object.DeepCopy() + if err := m.apply(ctx, appliedObject); err != nil { + return nil, fmt.Errorf("%s apply failed, error: %w", objectutil.FmtUnstructured(appliedObject), err) + } + } + + return changeSet, nil +} + +// ApplyAllStaged extracts the CRDs and Namespaces, applies them with ApplyAll, +// waits for CRDs and Namespaces to become ready, then is applies all the other objects. +// This function should be used when the given objects have a mix of custom resource definition and custom resources, +// or a mix of namespace definitions with namespaced objects. +func (m *ResourceManager) ApplyAllStaged(ctx context.Context, objects []*unstructured.Unstructured, force bool, wait time.Duration) (*ChangeSet, error) { + changeSet := NewChangeSet() + + // contains only CRDs and Namespaces + var stageOne []*unstructured.Unstructured + + // contains all objects except for CRDs and Namespaces + var stageTwo []*unstructured.Unstructured + + for _, u := range objects { + if m.IsClusterDefinition(u.GetKind()) { + stageOne = append(stageOne, u) + } else { + stageTwo = append(stageTwo, u) + } + } + + if len(stageOne) > 0 { + cs, err := m.ApplyAll(ctx, stageOne, force) + if err != nil { + return nil, err + } + changeSet.Append(cs.Entries) + + if err := m.Wait(stageOne, 2*time.Second, wait); err != nil { + return nil, err + } + } + + cs, err := m.ApplyAll(ctx, stageTwo, force) + if err != nil { + return nil, err + } + changeSet.Append(cs.Entries) + + return changeSet, nil +} + +func (m *ResourceManager) dryRunApply(ctx context.Context, object *unstructured.Unstructured) error { + opts := []client.PatchOption{ + client.DryRunAll, + client.ForceOwnership, + client.FieldOwner(m.owner.Field), + } + return m.client.Patch(ctx, object, client.Apply, opts...) +} + +func (m *ResourceManager) apply(ctx context.Context, object *unstructured.Unstructured) error { + opts := []client.PatchOption{ + client.ForceOwnership, + client.FieldOwner(m.owner.Field), + } + return m.client.Patch(ctx, object, client.Apply, opts...) +} + +func (m *ResourceManager) IsClusterDefinition(kind string) bool { + switch strings.ToLower(kind) { + case "customresourcedefinition": + case "namespace": + return true + } + return false +} diff --git a/pkg/manager/manager_apply_bench_test.go b/pkg/manager/manager_apply_bench_test.go new file mode 100644 index 0000000..3784812 --- /dev/null +++ b/pkg/manager/manager_apply_bench_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "context" + "testing" + "time" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func BenchmarkTestApply10(b *testing.B) { + for i := 0; i < b.N; i++ { + timeout := 10 * time.Second + + id := generateName("bench") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + panic(err) + } + + _, err = manager.ApplyAllStaged(context.Background(), objects, false, timeout) + if err != nil { + panic(err) + } + } +} + +func BenchmarkTestApplyWait10(b *testing.B) { + for i := 0; i < b.N; i++ { + timeout := 10 * time.Second + + id := generateName("bench") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + b.Fatal(err) + } + + if _, err = manager.ApplyAllStaged(context.Background(), objects, false, timeout); err != nil { + b.Fatal(err) + } + + if err := manager.Wait(objects, time.Second, 5*time.Second); err != nil { + b.Error(err) + } + } +} + +func BenchmarkTestApplyDelete10(b *testing.B) { + for i := 0; i < b.N; i++ { + timeout := 10 * time.Second + id := generateName("bench") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + b.Fatal(err) + } + + if _, err = manager.ApplyAllStaged(context.Background(), objects, false, timeout); err != nil { + b.Fatal(err) + } + + if _, err := manager.DeleteAll(context.Background(), objects); err != nil { + b.Error(err) + } + } +} + +func BenchmarkTestApplyDeleteWait10(b *testing.B) { + for i := 0; i < b.N; i++ { + timeout := 10 * time.Second + id := generateName("bench") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + b.Fatal(err) + } + + if _, err = manager.ApplyAllStaged(context.Background(), objects, false, timeout); err != nil { + b.Fatal(err) + } + + if _, err := manager.DeleteAll(context.Background(), objects); err != nil { + b.Error(err) + } + + _, configmap := getFirstObject(objects, "ConfigMap", id) + _, role := getFirstObject(objects, "ClusterRole", id) + if err := manager.WaitForTermination([]*unstructured.Unstructured{role, configmap}, time.Second, timeout); err != nil { + b.Error(err) + } + } +} + +func BenchmarkTestApplyDryRun10(b *testing.B) { + for i := 0; i < b.N; i++ { + id := generateName("bench") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + panic(err) + } + + _, role := getFirstObject(objects, "ClusterRole", id) + if _, err := manager.Diff(context.Background(), role); err != nil { + b.Error(err) + } + + _, roleb := getFirstObject(objects, "ClusterRoleBinding", id) + if _, err := manager.Diff(context.Background(), roleb); err != nil { + b.Error(err) + } + + _, ns := getFirstObject(objects, "Namespace", id) + if _, err := manager.Diff(context.Background(), ns); err != nil { + b.Error(err) + } + } +} diff --git a/pkg/manager/manager_apply_test.go b/pkg/manager/manager_apply_test.go new file mode 100644 index 0000000..fbf434b --- /dev/null +++ b/pkg/manager/manager_apply_test.go @@ -0,0 +1,183 @@ +package manager + +import ( + "context" + "encoding/base64" + "fmt" + "sort" + "testing" + "time" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestApply(t *testing.T) { + timeout := 10 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + id := generateName("apply") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + t.Fatal(err) + } + + manager.SetOwnerLabels(objects, "app1", "default") + + configMapName, configMap := getFirstObject(objects, "ConfigMap", id) + secretName, secret := getFirstObject(objects, "Secret", id) + + t.Run("creates objects in order", func(t *testing.T) { + // create objects + changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) + if err != nil { + t.Fatal(err) + } + + // expected created order + sort.Sort(objectutil.SortableUnstructureds(objects)) + var expected []string + for _, object := range objects { + expected = append(expected, objectutil.FmtUnstructured(object)) + } + + // verify the change set contains only created actions + var output []string + for _, entry := range changeSet.Entries { + if diff := cmp.Diff(entry.Action, string(CreatedAction)); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + output = append(output, entry.Subject) + } + + // verify the change set contains all objects in the right order + if diff := cmp.Diff(expected, output); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + }) + + t.Run("does not apply unchanged objects", func(t *testing.T) { + // no-op apply + changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) + if err != nil { + t.Fatal(err) + } + + // verify the change set contains only unchanged actions + var output []string + for _, entry := range changeSet.Entries { + if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s\n%v", diff, changeSet) + } + output = append(output, entry.Subject) + } + }) + + t.Run("applies only changed objects", func(t *testing.T) { + // update a value in the configmap + err = unstructured.SetNestedField(configMap.Object, "val", "data", "key") + if err != nil { + t.Fatal(err) + } + + // apply changes + changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) + if err != nil { + t.Fatal(err) + } + + // verify the change set contains the configured action only for the configmap + for _, entry := range changeSet.Entries { + if entry.Subject == configMapName { + if diff := cmp.Diff(string(ConfiguredAction), entry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + } else { + if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + } + } + + // get the configmap from cluster + configMapClone := configMap.DeepCopy() + err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone) + if err != nil { + t.Fatal(err) + } + + // get data value from the in-cluster configmap + val, _, err := unstructured.NestedFieldCopy(configMapClone.Object, "data", "key") + if err != nil { + t.Fatal(err) + } + + // verify the configmap was updated in cluster with the right data value + if diff := cmp.Diff(val, "val"); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + }) + + t.Run("fails to apply immutable secret", func(t *testing.T) { + // update a value in the secret + err = unstructured.SetNestedField(secret.Object, "val-secret", "stringData", "key") + if err != nil { + t.Fatal(err) + } + + // apply and expect to fail + _, err := manager.ApplyAllStaged(ctx, objects, false, timeout) + if err == nil { + t.Fatal("Expected error got none") + } + + // verify that the error message does not contain sensitive information + expectedErr := fmt.Sprintf("%s invalid, error: secret is immutable", objectutil.FmtUnstructured(secret)) + if diff := cmp.Diff(expectedErr, err.Error()); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + }) + + t.Run("force applies immutable secret", func(t *testing.T) { + // force apply + changeSet, err := manager.ApplyAllStaged(ctx, objects, true, timeout) + if err != nil { + t.Fatal(err) + } + + // verify the secret was recreated + for _, entry := range changeSet.Entries { + if entry.Subject == secretName { + if diff := cmp.Diff(string(CreatedAction), entry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + } else { + if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + } + } + + // get the secret from cluster + secretClone := secret.DeepCopy() + err = manager.client.Get(ctx, client.ObjectKeyFromObject(secretClone), secretClone) + if err != nil { + t.Fatal(err) + } + + // get data value from the in-cluster secret + val, _, err := unstructured.NestedFieldCopy(secretClone.Object, "data", "key") + if err != nil { + t.Fatal(err) + } + + // verify the secret was updated in cluster with the right data value + if diff := cmp.Diff(val, base64.StdEncoding.EncodeToString([]byte("val-secret"))); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + }) +} diff --git a/pkg/manager/manager_delete.go b/pkg/manager/manager_delete.go new file mode 100644 index 0000000..82f4785 --- /dev/null +++ b/pkg/manager/manager_delete.go @@ -0,0 +1,63 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "context" + "fmt" + "sort" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Delete deletes the given object (not found errors are ignored). +func (m *ResourceManager) Delete(ctx context.Context, object *unstructured.Unstructured) (*ChangeSetEntry, error) { + existingObject := object.DeepCopy() + err := m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject) + if err != nil { + if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("%s query failed, error: %w", objectutil.FmtUnstructured(object), err) + } + } else { + if err := m.client.Delete(ctx, existingObject); err != nil { + return nil, fmt.Errorf("%s delete failed, error: %w", objectutil.FmtUnstructured(object), err) + } + } + + return m.changeSetEntry(object, DeletedAction), nil +} + +// DeleteAll deletes the given set of objects (not found errors are ignored). +func (m *ResourceManager) DeleteAll(ctx context.Context, objects []*unstructured.Unstructured) (*ChangeSet, error) { + sort.Sort(sort.Reverse(objectutil.SortableUnstructureds(objects))) + changeSet := NewChangeSet() + + for _, object := range objects { + cse, err := m.Delete(ctx, object) + if err != nil { + return nil, err + } + changeSet.Add(*cse) + } + + return changeSet, nil +} diff --git a/pkg/manager/manager_delete_test.go b/pkg/manager/manager_delete_test.go new file mode 100644 index 0000000..4560160 --- /dev/null +++ b/pkg/manager/manager_delete_test.go @@ -0,0 +1,85 @@ +package manager + +import ( + "context" + "github.com/stefanprodan/kustomizer/pkg/objectutil" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestDelete(t *testing.T) { + timeout := 10 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + id := generateName("delete") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + t.Fatal(err) + } + + _, configMap := getFirstObject(objects, "ConfigMap", id) + _, role := getFirstObject(objects, "ClusterRole", id) + + if _, err = manager.ApplyAllStaged(ctx, objects, false, timeout); err != nil { + t.Fatal(err) + } + + t.Run("deletes objects in order", func(t *testing.T) { + changeSet, err := manager.DeleteAll(ctx, objects) + if err != nil { + t.Fatal(err) + } + + // expected deleted order + var expected []string + for _, object := range objects { + expected = append(expected, objectutil.FmtUnstructured(object)) + } + + // verify the change set contains only created actions + var output []string + for _, entry := range changeSet.Entries { + if diff := cmp.Diff(entry.Action, string(DeletedAction)); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + output = append(output, entry.Subject) + } + + // verify the change set contains all objects in the right order + if diff := cmp.Diff(expected, output); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + + configMapClone := configMap.DeepCopy() + err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone) + if !apierrors.IsNotFound(err) { + t.Fatal(err) + } + + roleClone := role.DeepCopy() + err = manager.client.Get(ctx, client.ObjectKeyFromObject(roleClone), roleClone) + if !apierrors.IsNotFound(err) { + t.Fatal(err) + } + }) + + t.Run("waits for objects termination", func(t *testing.T) { + _, err := manager.DeleteAll(ctx, objects) + if err != nil { + t.Fatal(err) + } + + if err := manager.WaitForTermination(objects, time.Second, 5*time.Second); err != nil { + // workaround for https://github.com/kubernetes-sigs/controller-runtime/issues/880 + if !strings.Contains(err.Error(), "Namespace/") { + t.Fatal(err) + } + } + }) +} diff --git a/pkg/manager/manager_diff.go b/pkg/manager/manager_diff.go new file mode 100644 index 0000000..43c6408 --- /dev/null +++ b/pkg/manager/manager_diff.go @@ -0,0 +1,136 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "context" + "fmt" + "strings" + + "github.com/google/go-cmp/cmp" + "github.com/stefanprodan/kustomizer/pkg/objectutil" + apiequality "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +// Diff performs a server-side apply dry-un and returns the fields that changed in YAML format. +// If the diff contains Kubernetes Secrets, the data values are masked. +func (m *ResourceManager) Diff(ctx context.Context, object *unstructured.Unstructured) (*ChangeSetEntry, error) { + existingObject := object.DeepCopy() + _ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject) + + dryRunObject := object.DeepCopy() + if err := m.dryRunApply(ctx, dryRunObject); err != nil { + return nil, m.validationError(dryRunObject, err) + } + + if dryRunObject.GetResourceVersion() == "" { + return m.changeSetEntry(dryRunObject, CreatedAction), nil + } + + if m.hasDrifted(existingObject, dryRunObject) { + cse := m.changeSetEntry(object, ConfiguredAction) + + unstructured.RemoveNestedField(dryRunObject.Object, "metadata", "managedFields") + unstructured.RemoveNestedField(existingObject.Object, "metadata", "managedFields") + + if dryRunObject.GetKind() == "Secret" { + d, err := objectutil.MaskSecret(dryRunObject, "******") + if err != nil { + return nil, fmt.Errorf("masking secret data failed, error: %w", err) + } + dryRunObject = d + ex, err := objectutil.MaskSecret(existingObject, "*****") + if err != nil { + return nil, fmt.Errorf("masking secret data failed, error: %w", err) + } + existingObject = ex + } + + d, _ := yaml.Marshal(dryRunObject) + e, _ := yaml.Marshal(existingObject) + cse.Diff = cmp.Diff(string(e), string(d)) + + return cse, nil + } + + return m.changeSetEntry(dryRunObject, UnchangedAction), nil +} + +// hasDrifted detects changes to metadata labels, metadata annotations, spec and webhooks. +func (m *ResourceManager) hasDrifted(existingObject, dryRunObject *unstructured.Unstructured) bool { + if dryRunObject.GetResourceVersion() == "" { + return true + } + + if !apiequality.Semantic.DeepDerivative(dryRunObject.GetLabels(), existingObject.GetLabels()) { + return true + + } + + if !apiequality.Semantic.DeepDerivative(dryRunObject.GetAnnotations(), existingObject.GetAnnotations()) { + return true + } + + if _, ok := existingObject.Object["spec"]; ok { + if !apiequality.Semantic.DeepDerivative(dryRunObject.Object["spec"], existingObject.Object["spec"]) { + return true + } + } else if _, ok := existingObject.Object["webhooks"]; ok { + if !apiequality.Semantic.DeepDerivative(dryRunObject.Object["webhooks"], existingObject.Object["webhooks"]) { + return true + } + } else { + if !apiequality.Semantic.DeepDerivative(dryRunObject.Object, existingObject.Object) { + return true + } + } + + return false +} + +// validationError formats the given error and hides sensitive data +// if the error was caused by an invalid Kubernetes secrets. +func (m *ResourceManager) validationError(object *unstructured.Unstructured, err error) error { + if apierrors.IsNotFound(err) { + return fmt.Errorf("%s namespace not specified, error: %w", objectutil.FmtUnstructured(object), err) + } + + reason := fmt.Sprintf("%v", apierrors.ReasonForError(err)) + + if object.GetKind() == "Secret" { + msg := "data values must be of type string" + if strings.Contains(err.Error(), "immutable") { + msg = "secret is immutable" + } + return fmt.Errorf("%s %s, error: %s", objectutil.FmtUnstructured(object), strings.ToLower(reason), msg) + } + + // detect managed field conflict + if status, ok := apierrors.StatusCause(err, metav1.CauseTypeFieldManagerConflict); ok { + reason = fmt.Sprintf("%v", status.Type) + } + + return fmt.Errorf("%s dry-run falied, reason: %s, error: %w", + objectutil.FmtUnstructured(object), reason, err) + +} diff --git a/pkg/manager/manager_diff_test.go b/pkg/manager/manager_diff_test.go new file mode 100644 index 0000000..25a3280 --- /dev/null +++ b/pkg/manager/manager_diff_test.go @@ -0,0 +1,100 @@ +package manager + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestDiff(t *testing.T) { + timeout := 10 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + id := generateName("diff") + objects, err := readManifest("testdata/test1.yaml", id) + if err != nil { + t.Fatal(err) + } + + configMapName, configMap := getFirstObject(objects, "ConfigMap", id) + secretName, secret := getFirstObject(objects, "Secret", id) + + if err := unstructured.SetNestedField(secret.Object, false, "immutable"); err != nil { + t.Fatal(err) + } + if _, err = manager.ApplyAllStaged(ctx, objects, false, timeout); err != nil { + t.Fatal(err) + } + + t.Run("generates empty diff for unchanged object", func(t *testing.T) { + changeSetEntry, err := manager.Diff(ctx, configMap) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(configMapName, changeSetEntry.Subject); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + + if diff := cmp.Diff(string(UnchangedAction), changeSetEntry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + }) + + t.Run("generates diff for changed object", func(t *testing.T) { + newVal := "diff-test" + err = unstructured.SetNestedField(configMap.Object, newVal, "data", "key") + if err != nil { + t.Fatal(err) + } + + changeSetEntry, err := manager.Diff(ctx, configMap) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(string(ConfiguredAction), changeSetEntry.Action); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + + if !strings.Contains(changeSetEntry.Diff, newVal) { + t.Errorf("Mismatch from expected value, want %s", newVal) + } + }) + + t.Run("masks secret values", func(t *testing.T) { + newVal := "diff-test" + err = unstructured.SetNestedField(secret.Object, newVal, "stringData", "key") + if err != nil { + t.Fatal(err) + } + + newKey := "key.new" + err = unstructured.SetNestedField(secret.Object, newVal, "stringData", newKey) + if err != nil { + t.Fatal(err) + } + + changeSetEntry, err := manager.Diff(ctx, secret) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(secretName, changeSetEntry.Subject); diff != "" { + t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) + } + + if !strings.Contains(changeSetEntry.Diff, newKey) { + t.Errorf("Mismatch from expected value, got %s", changeSetEntry.Diff) + } + + if strings.Contains(changeSetEntry.Diff, newVal) { + t.Errorf("Mismatch from expected value, got %s", changeSetEntry.Diff) + } + }) +} diff --git a/pkg/manager/manager_inventory.go b/pkg/manager/manager_inventory.go new file mode 100644 index 0000000..0c09164 --- /dev/null +++ b/pkg/manager/manager_inventory.go @@ -0,0 +1,145 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "context" + "encoding/json" + "fmt" + "github.com/stefanprodan/kustomizer/pkg/inventory" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" +) + +const inventoryKindName = "inventory" + +// ApplyInventory creates or updates the ConfigMap object for the given inventory. +func (m *ResourceManager) ApplyInventory(ctx context.Context, i *inventory.Inventory) error { + data, err := json.Marshal(i.Entries) + if err != nil { + return err + } + + cm := m.newConfigMap(i.Name, i.Namespace) + cm.Annotations = map[string]string{ + m.owner.Group + "/last-applied-time": time.Now().UTC().Format(time.RFC3339), + } + if i.Source != "" { + cm.Annotations[m.owner.Group+"/source"] = i.Source + } + if i.Revision != "" { + cm.Annotations[m.owner.Group+"/revision"] = i.Revision + } + + cm.Data = map[string]string{ + inventoryKindName: string(data), + } + + opts := []client.PatchOption{ + client.ForceOwnership, + client.FieldOwner(m.owner.Field), + } + return m.client.Patch(ctx, cm, client.Apply, opts...) +} + +// GetInventory retrieves the entries from the ConfigMap for the given inventory name and namespace. +func (m *ResourceManager) GetInventory(ctx context.Context, i *inventory.Inventory) error { + cm := m.newConfigMap(i.Name, i.Namespace) + + cmKey := client.ObjectKeyFromObject(cm) + err := m.client.Get(ctx, cmKey, cm) + if err != nil { + return err + } + + if _, ok := cm.Data[inventoryKindName]; !ok { + return fmt.Errorf("inventory data not found in ConfigMap/%s", cmKey) + } + + var entries []inventory.Entry + err = json.Unmarshal([]byte(cm.Data[inventoryKindName]), &entries) + if err != nil { + return err + } + + i.Entries = entries + + for k, v := range cm.GetAnnotations() { + switch k { + case m.owner.Group + "/source": + i.Source = v + case m.owner.Group + "/revision": + i.Revision = v + } + } + + return nil +} + +// DeleteInventory removes the ConfigMap for the given inventory name and namespace. +func (m *ResourceManager) DeleteInventory(ctx context.Context, i *inventory.Inventory) error { + cm := m.newConfigMap(i.Name, i.Namespace) + + cmKey := client.ObjectKeyFromObject(cm) + err := m.client.Delete(ctx, cm) + if err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to delete ConfigMap/%s, error: %w", cmKey, err) + } + return nil +} + +// GetInventoryStaleObjects returns the list of objects metadata subject to pruning. +func (m *ResourceManager) GetInventoryStaleObjects(ctx context.Context, i *inventory.Inventory) ([]*unstructured.Unstructured, error) { + objects := make([]*unstructured.Unstructured, 0) + existingInventory := inventory.NewInventory(i.Name, i.Namespace) + if err := m.GetInventory(ctx, existingInventory); err != nil { + if apierrors.IsNotFound(err) { + return objects, nil + } + return nil, err + } + + objects, err := existingInventory.Diff(i) + if err != nil { + return nil, err + } + + return objects, nil +} + +func (m *ResourceManager) newConfigMap(name, namespace string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + "app.kubernetes.io/name": name, + "app.kubernetes.io/component": inventoryKindName, + "app.kubernetes.io/created-by": m.owner.Field, + }, + }, + } +} diff --git a/pkg/manager/manager_wait.go b/pkg/manager/manager_wait.go new file mode 100644 index 0000000..324eb0c --- /dev/null +++ b/pkg/manager/manager_wait.go @@ -0,0 +1,128 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/stefanprodan/kustomizer/pkg/objectutil" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" + "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Wait checks if the given set of objects has been fully reconciled. +func (m *ResourceManager) Wait(objects []*unstructured.Unstructured, interval, timeout time.Duration) error { + objectsMeta := object.UnstructuredsToObjMetas(objects) + statusCollector := collector.NewResourceStatusCollector(objectsMeta) + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + opts := polling.Options{ + PollInterval: interval, + UseCache: true, + } + eventsChan := m.poller.Poll(ctx, objectsMeta, opts) + + lastStatus := make(map[object.ObjMetadata]*event.ResourceStatus) + + done := statusCollector.ListenWithObserver(eventsChan, collector.ObserverFunc( + func(statusCollector *collector.ResourceStatusCollector, e event.Event) { + var rss []*event.ResourceStatus + for _, rs := range statusCollector.ResourceStatuses { + if rs == nil { + continue + } + if rs.Error == nil { + lastStatus[rs.Identifier] = rs + } + rss = append(rss, rs) + } + desired := status.CurrentStatus + aggStatus := aggregator.AggregateStatus(rss, desired) + if aggStatus == desired { + cancel() + return + } + }), + ) + + <-done + + if statusCollector.Error != nil { + return statusCollector.Error + } + + if ctx.Err() == context.DeadlineExceeded { + var errors = []string{} + for id, rs := range statusCollector.ResourceStatuses { + if rs == nil { + errors = append(errors, fmt.Sprintf("can't determine status for %s", objectutil.FmtObjMetadata(id))) + continue + } + if lastStatus[id].Status != status.CurrentStatus { + var builder strings.Builder + builder.WriteString(fmt.Sprintf("%s status: '%s'", + objectutil.FmtObjMetadata(rs.Identifier), lastStatus[id].Status)) + if rs.Error != nil { + builder.WriteString(fmt.Sprintf(": %s", rs.Error)) + } + errors = append(errors, builder.String()) + } + } + return fmt.Errorf("timeout waiting for: [%s]", strings.Join(errors, ", ")) + } + + return nil +} + +// WaitForTermination waits for the given objects to be deleted from the cluster. +func (m *ResourceManager) WaitForTermination(objects []*unstructured.Unstructured, interval, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + for _, object := range objects { + if err := wait.PollImmediate(interval, timeout, m.isDeleted(ctx, object)); err != nil { + return fmt.Errorf("%s termination timeout, error: %w", objectutil.FmtUnstructured(object), err) + } + } + return nil +} + +func (m *ResourceManager) isDeleted(ctx context.Context, object *unstructured.Unstructured) wait.ConditionFunc { + return func() (bool, error) { + obj := object.DeepCopy() + err := m.client.Get(ctx, client.ObjectKeyFromObject(obj), obj) + if apierrors.IsNotFound(err) { + return true, nil + } + return false, err + } +} diff --git a/pkg/manager/owner.go b/pkg/manager/owner.go new file mode 100644 index 0000000..450b700 --- /dev/null +++ b/pkg/manager/owner.go @@ -0,0 +1,27 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +// Owner contains options for setting the field manager and ownership labels group. +type Owner struct { + // Field sets the field manager name for the given server-side apply patch. + Field string + + // Group sets the owner label key prefix. + Group string +} diff --git a/pkg/manager/testdata/test1.yaml b/pkg/manager/testdata/test1.yaml new file mode 100644 index 0000000..f9c60b2 --- /dev/null +++ b/pkg/manager/testdata/test1.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "%[1]s" + namespace: "%[1]s" +--- +apiVersion: v1 +kind: Secret +metadata: + name: "%[1]s" + namespace: "%[1]s" +immutable: true +stringData: + key: "private-key" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "%[1]s" + namespace: "%[1]s" +data: + key: "public-key" +--- +apiVersion: v1 +kind: Namespace +metadata: + name: "%[1]s" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: "%[1]s" +rules: + - apiGroups: + - apps + resources: ["*"] + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "%[1]s" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: "%[1]s" +subjects: + - kind: ServiceAccount + name: "%[1]s" + namespace: "%[1]s" diff --git a/pkg/objectutil/doc.go b/pkg/objectutil/doc.go new file mode 100644 index 0000000..643341e --- /dev/null +++ b/pkg/objectutil/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package objectutil contains utilities for manipulating Kubernetes objects. +package objectutil diff --git a/pkg/objectutil/fmt.go b/pkg/objectutil/fmt.go new file mode 100644 index 0000000..b2d0eaa --- /dev/null +++ b/pkg/objectutil/fmt.go @@ -0,0 +1,64 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package objectutil + +import ( + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/cli-utils/pkg/object" +) + +const fmtSeparator = "/" + +// FmtObjMetadata returns the object ID in the format //. +func FmtObjMetadata(obj object.ObjMetadata) string { + var builder strings.Builder + builder.WriteString(obj.GroupKind.Kind + fmtSeparator) + if obj.Namespace != "" { + builder.WriteString(obj.Namespace + fmtSeparator) + } + builder.WriteString(obj.Name) + return builder.String() +} + +// FmtUnstructured returns the object ID in the format //. +func FmtUnstructured(obj *unstructured.Unstructured) string { + return FmtObjMetadata(object.UnstructuredToObjMeta(obj)) +} + +// MaskSecret replaces the data key values with the given mask. +func MaskSecret(object *unstructured.Unstructured, mask string) (*unstructured.Unstructured, error) { + data, found, err := unstructured.NestedMap(object.Object, "data") + if err != nil { + return nil, err + } + + if found { + for k, _ := range data { + data[k] = mask + } + + err = unstructured.SetNestedMap(object.Object, data, "data") + if err != nil { + return nil, err + } + } + + return object, err +} diff --git a/pkg/objectutil/io.go b/pkg/objectutil/io.go new file mode 100644 index 0000000..a9e19b2 --- /dev/null +++ b/pkg/objectutil/io.go @@ -0,0 +1,138 @@ +/* +Copyright 2021 Stefan Prodan +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package objectutil + +import ( + "encoding/json" + "io" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + apiruntime "k8s.io/apimachinery/pkg/runtime" + yamlutil "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/yaml" +) + +// ReadObject decodes a YAML or JSON document from the given reader into an unstructured Kubernetes API object. +func ReadObject(r io.Reader) (*unstructured.Unstructured, error) { + reader := yamlutil.NewYAMLOrJSONDecoder(r, 2048) + obj := &unstructured.Unstructured{} + err := reader.Decode(obj) + if err != nil { + return nil, err + } + + return obj, nil +} + +// ReadObjects decodes the YAML or JSON documents from the given reader into unstructured Kubernetes API objects. +func ReadObjects(r io.Reader) ([]*unstructured.Unstructured, error) { + reader := yamlutil.NewYAMLOrJSONDecoder(r, 2048) + objects := make([]*unstructured.Unstructured, 0) + + for { + obj := &unstructured.Unstructured{} + err := reader.Decode(obj) + if err != nil { + if err == io.EOF { + err = nil + break + } + return objects, err + } + + if obj.IsList() { + err = obj.EachListItem(func(item apiruntime.Object) error { + obj := item.(*unstructured.Unstructured) + objects = append(objects, obj) + return nil + }) + if err != nil { + return objects, err + } + continue + } + + if IsKubernetesObject(obj) && !IsKustomization(obj) { + objects = append(objects, obj) + } + } + + return objects, nil +} + +func IsKubernetesObject(object *unstructured.Unstructured) bool { + if object.GetName() == "" || object.GetKind() == "" || object.GetAPIVersion() == "" { + return false + } + return true +} + +func IsKustomization(object *unstructured.Unstructured) bool { + if object.GetKind() == "Kustomization" && object.GroupVersionKind().GroupKind().Group == "kustomize.config.k8s.io" { + return true + } + return false +} + +// ObjectToYAML encodes the given Kubernetes API object to YAML. +func ObjectToYAML(object *unstructured.Unstructured) string { + var builder strings.Builder + data, err := yaml.Marshal(object) + if err != nil { + return "" + } + builder.Write(data) + builder.WriteString("---\n") + + return builder.String() +} + +// ObjectsToYAML encodes the given Kubernetes API objects to a YAML multi-doc. +func ObjectsToYAML(objects []*unstructured.Unstructured) (string, error) { + var builder strings.Builder + for _, obj := range objects { + data, err := yaml.Marshal(obj) + if err != nil { + return "", err + } + builder.Write(data) + builder.WriteString("---\n") + } + return builder.String(), nil +} + +// ObjectsToJSON encodes the given Kubernetes API objects to a YAML multi-doc. +func ObjectsToJSON(objects []*unstructured.Unstructured) (string, error) { + list := struct { + ApiVersion string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + Items []*unstructured.Unstructured `json:"items,omitempty"` + }{ + ApiVersion: "v1", + Kind: "ListMeta", + Items: objects, + } + + data, err := json.MarshalIndent(list, "", " ") + if err != nil { + return "", err + } + + return string(data), nil +} diff --git a/pkg/objectutil/sort.go b/pkg/objectutil/sort.go new file mode 100644 index 0000000..da33d2f --- /dev/null +++ b/pkg/objectutil/sort.go @@ -0,0 +1,123 @@ +/* +Copyright 2021 Stefan Prodan. +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package objectutil + +import ( + "sort" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/object" +) + +type SortableUnstructureds []*unstructured.Unstructured + +var _ sort.Interface = SortableUnstructureds{} + +func (a SortableUnstructureds) Len() int { return len(a) } +func (a SortableUnstructureds) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortableUnstructureds) Less(i, j int) bool { + first := object.UnstructuredToObjMeta(a[i]) + second := object.UnstructuredToObjMeta(a[j]) + return less(first, second) +} + +type SortableMetas []object.ObjMetadata + +var _ sort.Interface = SortableMetas{} + +func (a SortableMetas) Len() int { return len(a) } +func (a SortableMetas) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortableMetas) Less(i, j int) bool { + return less(a[i], a[j]) +} + +func less(i, j object.ObjMetadata) bool { + if !Equals(i.GroupKind, j.GroupKind) { + return IsLessThan(i.GroupKind, j.GroupKind) + } + // In case of tie, compare the namespace and name combination so that the output + // order is consistent irrespective of input order + if i.Namespace != j.Namespace { + return i.Namespace < j.Namespace + } + return i.Name < j.Name +} + +var kind2index = computeKind2index() + +func computeKind2index() map[string]int { + // An attempt to order things to help k8s, e.g. + // a Service should come before things that refer to it. + // Namespace should be first. + // In some cases order just specified to provide determinism. + orderFirst := []string{ + "CustomResourceDefinition", + "Namespace", + "ResourceQuota", + "StorageClass", + "ServiceAccount", + "PodSecurityPolicy", + "Role", + "ClusterRole", + "RoleBinding", + "ClusterRoleBinding", + "ConfigMap", + "Secret", + "Service", + "LimitRange", + "PriorityClass", + "Deployment", + "StatefulSet", + "CronJob", + "PodDisruptionBudget", + } + orderLast := []string{ + "MutatingWebhookConfiguration", + "ValidatingWebhookConfiguration", + } + kind2indexResult := make(map[string]int, len(orderFirst)+len(orderLast)) + for i, n := range orderFirst { + kind2indexResult[n] = -len(orderFirst) + i + } + for i, n := range orderLast { + kind2indexResult[n] = 1 + i + } + return kind2indexResult +} + +// getIndexByKind returns the index of the kind respecting the order +func getIndexByKind(kind string) int { + return kind2index[kind] +} + +func Equals(i, j schema.GroupKind) bool { + return i.Group == j.Group && i.Kind == j.Kind +} + +func IsLessThan(i, j schema.GroupKind) bool { + indexI := getIndexByKind(i.Kind) + indexJ := getIndexByKind(j.Kind) + if indexI != indexJ { + return indexI < indexJ + } + if i.Group != j.Group { + return i.Group < j.Group + } + return i.Kind < j.Kind +} diff --git a/testdata/certs/cert.yaml b/testdata/certs/cert.yaml new file mode 100644 index 0000000..b311011 --- /dev/null +++ b/testdata/certs/cert.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kustomizer-cert-test +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: my-selfsigned-ca + namespace: kustomizer-cert-test +spec: + isCA: true + commonName: my-selfsigned-ca + secretName: root-secret + privateKey: + algorithm: ECDSA + size: 384 + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: my-ca-issuer + namespace: kustomizer-cert-test +spec: + ca: + secretName: root-secret diff --git a/testdata/certs/kustomization.yaml b/testdata/certs/kustomization.yaml new file mode 100644 index 0000000..f72285a --- /dev/null +++ b/testdata/certs/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- https://github.com/jetstack/cert-manager/releases/download/v1.5.3/cert-manager.yaml +- cert.yaml diff --git a/testdata/crds/resources/cr.yaml b/testdata/crds/resources/cr.yaml deleted file mode 100644 index 82370e2..0000000 --- a/testdata/crds/resources/cr.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: testing.k8s.io/v1 -kind: Test -metadata: - name: some-test - namespace: default -spec: - type: integration diff --git a/testdata/invalid/test.yaml b/testdata/invalid/test.yaml new file mode 100644 index 0000000..ac49ab5 --- /dev/null +++ b/testdata/invalid/test.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Secret +metadata: + name: invalid1 +stringData: + token: 0 +--- +apiVersion: v1 +kind: Secret +metadata: + name: invalid2 + namespace: default +stringData: + token: 0 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: invalid3 + namespace: default +data: + token: 0 diff --git a/testdata/kustomize/cr.yaml b/testdata/kustomize/cr.yaml new file mode 100644 index 0000000..c90cead --- /dev/null +++ b/testdata/kustomize/cr.yaml @@ -0,0 +1,16 @@ +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test1 +spec: + type: integration + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test2 +spec: + type: unit + valuesFrom: test-config +--- diff --git a/testdata/crds/definitions/crd.yaml b/testdata/kustomize/crd.yaml similarity index 78% rename from testdata/crds/definitions/crd.yaml rename to testdata/kustomize/crd.yaml index fd92a83..d12e0da 100644 --- a/testdata/crds/definitions/crd.yaml +++ b/testdata/kustomize/crd.yaml @@ -1,9 +1,9 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: tests.testing.k8s.io + name: tests.testing.kustomizer.dev spec: - group: testing.k8s.io + group: testing.kustomizer.dev version: v1 versions: - name: v1 @@ -31,3 +31,6 @@ spec: enum: - unit - integration + valuesFrom: + description: ConfigMap reference + type: string diff --git a/testdata/kustomize/kustomization.yaml b/testdata/kustomize/kustomization.yaml new file mode 100644 index 0000000..1b414fb --- /dev/null +++ b/testdata/kustomize/kustomization.yaml @@ -0,0 +1,19 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kustomizer-test +configurations: + - kustomizeconfig.yaml +resources: +- namespace.yaml +- crd.yaml +- cr.yaml +configMapGenerator: + - name: test-config + files: + - test.conf +secretGenerator: + - name: test-secret + options: + disableNameSuffixHash: true + files: + - secret.conf diff --git a/testdata/kustomize/kustomizeconfig.yaml b/testdata/kustomize/kustomizeconfig.yaml new file mode 100644 index 0000000..a5b0487 --- /dev/null +++ b/testdata/kustomize/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: + - kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom + kind: Test diff --git a/testdata/sops/namespace.yaml b/testdata/kustomize/namespace.yaml similarity index 63% rename from testdata/sops/namespace.yaml rename to testdata/kustomize/namespace.yaml index de51780..e5f0515 100644 --- a/testdata/sops/namespace.yaml +++ b/testdata/kustomize/namespace.yaml @@ -1,4 +1,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test2 + name: kustomizer-test diff --git a/testdata/kustomize/secret.conf b/testdata/kustomize/secret.conf new file mode 100644 index 0000000..82496c9 --- /dev/null +++ b/testdata/kustomize/secret.conf @@ -0,0 +1 @@ +token=demo2 \ No newline at end of file diff --git a/testdata/kustomize/test.conf b/testdata/kustomize/test.conf new file mode 100644 index 0000000..8462a7a --- /dev/null +++ b/testdata/kustomize/test.conf @@ -0,0 +1 @@ +test=test2 \ No newline at end of file diff --git a/testdata/loadtest/README.md b/testdata/loadtest/README.md new file mode 100644 index 0000000..3bac85b --- /dev/null +++ b/testdata/loadtest/README.md @@ -0,0 +1,46 @@ +# Load test + +Commands: + +```shell +# count objects +kustomize build ./testdata/loadtest/ --load-restrictor=LoadRestrictionsNone | grep apiVersion | wc -l + +kubectl apply -f apply -f ./testdata/loadtest/crd.yaml + +# kubectl dry-run +time kustomize build ./testdata/loadtest/ --load-restrictor=LoadRestrictionsNone | kubectl apply -f- --dry-run + +# kubectl apply +time kustomize build ./testdata/loadtest/ --load-restrictor=LoadRestrictionsNone | kubectl apply -f- + +# kubectl delete +time kustomize build ./testdata/loadtest/ --load-restrictor=LoadRestrictionsNone --reorder=none | kubectl delete -f- + +# kustomizer dry-run + apply +time ./bin/kustomizer apply -k ./testdata/loadtest/ --inventory-name load-test + +# kustomizer dry-run +time ./bin/kustomizer apply -k ./testdata/loadtest/ --inventory-name load-test + +# kustomizer delete +time ./bin/kustomizer delete --inventory-name load-test +``` + +Env: + +- Kubernetes v1.20.8-gke.2100 +- kubectl v1.21.3 + +Results: + +| Operation | Time | Objects | +| ------------------------------------------ | ------------------ | ------------------ | +| kubectl dry-run | 16.024s | 110 | +| kustomizer dry-run | 15.258s | 110 | +| kubectl dry-run + apply | 35.770s | 110 | +| kustomizer dry-run + apply | 25.575s | 110 | +| kubectl delete | 28.486s | 110 | +| kustomizer delete | 21.507s | 110 | + + diff --git a/testdata/loadtest/base/custom-resources.yaml b/testdata/loadtest/base/custom-resources.yaml new file mode 100644 index 0000000..fa19b80 --- /dev/null +++ b/testdata/loadtest/base/custom-resources.yaml @@ -0,0 +1,81 @@ +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test1 +spec: + type: integration + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test2 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test3 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test4 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test5 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test6 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test7 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test8 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test9 +spec: + type: unit + valuesFrom: test-config +--- +apiVersion: testing.kustomizer.dev/v1 +kind: Test +metadata: + name: custom-resource-test10 +spec: + type: unit + valuesFrom: test-config +--- + diff --git a/testdata/loadtest/base/deployments.yaml b/testdata/loadtest/base/deployments.yaml new file mode 100644 index 0000000..4302d7f --- /dev/null +++ b/testdata/loadtest/base/deployments.yaml @@ -0,0 +1,739 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend1 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend1 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend1 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend2 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend2 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend2 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend3 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend3 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend3 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend4 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend4 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend4 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend5 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend5 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend5 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend6 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend6 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend6 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend7 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend7 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend7 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend8 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend8 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend8 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend9 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend9 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend9 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend10 +spec: + replicas: 0 + minReadySeconds: 3 + revisionHistoryLimit: 5 + progressDeadlineSeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: frontend10 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9797" + labels: + app: frontend10 + spec: + serviceAccountName: demo + containers: + - name: frontend + image: stefanprodan/podinfo:3.2.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9898 + protocol: TCP + - name: http-metrics + containerPort: 9797 + protocol: TCP + - name: grpc + containerPort: 9999 + protocol: TCP + command: + - ./podinfo + - --port=9898 + - --port-metrics=9797 + - --level=info + - --backend-url=http://backend:9898/echo + env: + - name: PODINFO_UI_COLOR + value: "#34577c" + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + cpu: 1000m + memory: 128Mi + requests: + cpu: 100m + memory: 32Mi \ No newline at end of file diff --git a/testdata/loadtest/base/kustomization.yaml b/testdata/loadtest/base/kustomization.yaml new file mode 100644 index 0000000..c67afdd --- /dev/null +++ b/testdata/loadtest/base/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +configurations: + - kustomizeconfig.yaml +resources: +- custom-resources.yaml +- deployments.yaml +configMapGenerator: + - name: test-config + files: + - test.conf diff --git a/testdata/loadtest/base/kustomizeconfig.yaml b/testdata/loadtest/base/kustomizeconfig.yaml new file mode 100644 index 0000000..a5b0487 --- /dev/null +++ b/testdata/loadtest/base/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: + - kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom + kind: Test diff --git a/testdata/loadtest/base/test.conf b/testdata/loadtest/base/test.conf new file mode 100644 index 0000000..8462a7a --- /dev/null +++ b/testdata/loadtest/base/test.conf @@ -0,0 +1 @@ +test=test2 \ No newline at end of file diff --git a/testdata/loadtest/crd.yaml b/testdata/loadtest/crd.yaml new file mode 100644 index 0000000..81dcede --- /dev/null +++ b/testdata/loadtest/crd.yaml @@ -0,0 +1,65 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.5.0 + creationTimestamp: null + name: tests.testing.kustomizer.dev +spec: + group: testing.kustomizer.dev + names: + kind: Test + listKind: TestList + plural: tests + singular: test + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: TYPE + type: string + name: v1 + schema: + openAPIV3Schema: + description: Test is the Schema for the testing API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TestSpec defines the desired state of a test run + properties: + type: + description: Type of test + type: string + enum: + - unit + - integration + valuesFrom: + description: config reference + type: string + type: object + status: + description: BucketStatus defines the observed state of a bucket + properties: + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/testdata/loadtest/kustomization.yaml b/testdata/loadtest/kustomization.yaml new file mode 100644 index 0000000..cf5c6b2 --- /dev/null +++ b/testdata/loadtest/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- crd.yaml +- overlay1 +- overlay2 +- overlay3 +- overlay4 +- overlay5 diff --git a/testdata/loadtest/overlay1/kustomization.yaml b/testdata/loadtest/overlay1/kustomization.yaml new file mode 100644 index 0000000..adb4c9a --- /dev/null +++ b/testdata/loadtest/overlay1/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kustomizer-test1 +resources: +- ../base +- namespace.yaml diff --git a/testdata/loadtest/overlay1/namespace.yaml b/testdata/loadtest/overlay1/namespace.yaml new file mode 100644 index 0000000..142c239 --- /dev/null +++ b/testdata/loadtest/overlay1/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kustomizer-test1 diff --git a/testdata/sops/kustomization.yaml b/testdata/loadtest/overlay2/kustomization.yaml similarity index 70% rename from testdata/sops/kustomization.yaml rename to testdata/loadtest/overlay2/kustomization.yaml index b19345e..98f57d8 100644 --- a/testdata/sops/kustomization.yaml +++ b/testdata/loadtest/overlay2/kustomization.yaml @@ -1,6 +1,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -namespace: test2 +namespace: kustomizer-test2 resources: - - secret.yaml + - ../base - namespace.yaml diff --git a/testdata/loadtest/overlay2/namespace.yaml b/testdata/loadtest/overlay2/namespace.yaml new file mode 100644 index 0000000..4396c9e --- /dev/null +++ b/testdata/loadtest/overlay2/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kustomizer-test2 diff --git a/testdata/loadtest/overlay3/kustomization.yaml b/testdata/loadtest/overlay3/kustomization.yaml new file mode 100644 index 0000000..cf4354a --- /dev/null +++ b/testdata/loadtest/overlay3/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kustomizer-test3 +resources: + - ../base + - namespace.yaml diff --git a/testdata/loadtest/overlay3/namespace.yaml b/testdata/loadtest/overlay3/namespace.yaml new file mode 100644 index 0000000..7b6724b --- /dev/null +++ b/testdata/loadtest/overlay3/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kustomizer-test3 diff --git a/testdata/loadtest/overlay4/kustomization.yaml b/testdata/loadtest/overlay4/kustomization.yaml new file mode 100644 index 0000000..e2d2664 --- /dev/null +++ b/testdata/loadtest/overlay4/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kustomizer-test4 +resources: + - ../base + - namespace.yaml diff --git a/testdata/loadtest/overlay4/namespace.yaml b/testdata/loadtest/overlay4/namespace.yaml new file mode 100644 index 0000000..a9a9dd6 --- /dev/null +++ b/testdata/loadtest/overlay4/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kustomizer-test4 diff --git a/testdata/loadtest/overlay5/kustomization.yaml b/testdata/loadtest/overlay5/kustomization.yaml new file mode 100644 index 0000000..1576ba4 --- /dev/null +++ b/testdata/loadtest/overlay5/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kustomizer-test5 +resources: + - ../base + - namespace.yaml diff --git a/testdata/loadtest/overlay5/namespace.yaml b/testdata/loadtest/overlay5/namespace.yaml new file mode 100644 index 0000000..ad65430 --- /dev/null +++ b/testdata/loadtest/overlay5/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kustomizer-test5 diff --git a/testdata/plain/backend/hpa.yaml b/testdata/plain/backend/hpa.yaml index 7359fde..edd00aa 100644 --- a/testdata/plain/backend/hpa.yaml +++ b/testdata/plain/backend/hpa.yaml @@ -8,8 +8,8 @@ spec: apiVersion: apps/v1 kind: Deployment name: backend - minReplicas: 1 - maxReplicas: 2 + minReplicas: 2 + maxReplicas: 4 metrics: - type: Resource resource: diff --git a/testdata/plain/common/rbac.yaml b/testdata/plain/common/rbac.yaml index 1df6ff9..9c64104 100644 --- a/testdata/plain/common/rbac.yaml +++ b/testdata/plain/common/rbac.yaml @@ -1,7 +1,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: demo-read-only + name: kustomizer-demo-read-only rules: - apiGroups: - apps @@ -14,11 +14,11 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: demo-read-only + name: kustomizer-demo-read-only roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: demo-read-only + name: kustomizer-demo-read-only subjects: - kind: ServiceAccount name: demo diff --git a/testdata/sops/secret.yaml b/testdata/sops/secret.yaml deleted file mode 100644 index 58ff518..0000000 --- a/testdata/sops/secret.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: v1 -data: - password: ENC[AES256_GCM,data:kxrD5xoC2t0=,iv:w2AtSHOgqEeKICYG70LwNwsy0HraAujhfBeabqp0O4c=,tag:PkO08GLLebzYAxF4eJUiqA==,type:str] -kind: Secret -metadata: - name: test - annotations: - test: "2" -sops: - kms: [] - gcp_kms: [] - azure_kv: [] - hc_vault: [] - lastmodified: '2020-08-22T08:00:58Z' - mac: ENC[AES256_GCM,data:8PhS4AALpJKxnFQn6eGRl8um8KN7qh/mem6jRpkkEuXB4wwYrUyLV9ClYoSdmTULrSHsW8m3A1EpKby67bmDuUcMTzX2TDbWb50d6ivBbBmTsQvW22hI1/UPstiM3r3Or8gGIkaj8NrjLjHwLQV4cPcsk0HZ4vFn8b6+un1EAj0=,iv:ToObhfB5r0Llr1XhVLtasNJ+xIdNYTcLnnTd7Zg72ts=,tag:Lr/A05EjvDT3qVwo3k/RoQ==,type:str] - pgp: - - created_at: '2020-08-22T08:00:57Z' - enc: | - -----BEGIN PGP MESSAGE----- - - hQEMAyUpShfNkFB/AQf+OXrPlyj098j3iAjG+jPTzZnUhtRjSermp02sOubRj2/8 - KABcmcWmQwoDDkIs2snnioHZhfpmzWCfA91Rhz/72KA0UKtGT149JEiAQ5R3I0y7 - zR4+cn7kqeTpZk7WLZJWO8NQZxzm8bjZjY6vTfbZk7JlJWfwv/IIFM8KTarWZbgm - RcgY/iF/ei1lPQR8ioiC8UJE9XjuR8SEi1oyt5Rc30DvloYK03monAJExqleN3RB - UBohdzvbTpYPwH+fPFj7L+tb8n11L4pzGBjoVSBkGHDnDqKYs9YLo6Zh5YoN8Fxh - 3nYV5rXrNZ5jcskyPbUwgx3Gkx+MUqUPStQ7jo0X1NJeAYb9QISU+w3kb3DcY2X2 - mDTj0JeX7ulyMOx/4WdvTtaWeQbh9ERtuuWbd3i6yBBU+rxGSnhgXbo2wHisAN4S - aynag7ZrmNz2X+zE+g0pGsqevPSK+XUXJcg3YE0JHg== - =HCsv - -----END PGP MESSAGE----- - fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 - encrypted_regex: ^(data|stringData)$ - version: 3.6.0