diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 9f195279..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/golang:1.17 - working_directory: /go/src/github.com/databus23/helm-diff - steps: - - checkout - - run: - name: Install dependencies - command: | - make bootstrap - - run: - name: Run unit tests - command: | - make test - - run: - name: Verify installation - command: | - mkdir -p helmhome - make install HELM_HOME=helmhome - helmhome/plugins/helm-diff/bin/diff version diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ea32980d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +# Ignore everything +* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..1624c811 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*.sh] +indent_style = space +indent_size = 2 +max_line_length = 120 +trim_trailing_whitespace = true +shell_variant = posix diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..40667dcd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,137 @@ +# Helm Diff Plugin + +Helm Diff is a Go-based Helm plugin that provides diff functionality for comparing Helm charts and releases. It shows what changes would occur during helm upgrade, rollback, or between different releases/revisions. + +Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here. + +## Working Effectively + +**Prerequisites:** +- Go >= 1.21 (currently uses Go 1.24.5) +- Helm v3 (tested with v3.17.4 and v3.18.6) +- Make sure `/home/runner/go/bin` is in your PATH for staticcheck: `export PATH=$PATH:/home/runner/go/bin` + +**Bootstrap and Build Process:** +- ALWAYS run: `make bootstrap` first - downloads dependencies and installs staticcheck. Takes <1 second (if already done) or ~50 seconds (first time). +- Build the plugin: `make build` - includes linting and compiles the binary. Takes ~9 seconds after bootstrap. +- NEVER CANCEL builds. Set timeout to 3+ minutes for bootstrap, 2+ minutes for build operations. + +**Testing:** +- Run unit tests: `make test` - includes coverage analysis. Takes ~12 seconds. NEVER CANCEL - set timeout to 3+ minutes. +- Tests include comprehensive coverage (38.7% overall) and use a fake helm binary for isolation. +- Test coverage is generated in `cover.out` with detailed function-level coverage reports. + +**Linting and Code Quality:** +- Local linting: `make lint` - runs gofmt, go vet, and staticcheck verification. Takes ~2 seconds. +- Code formatting: `make format` - applies gofmt formatting automatically. Takes <1 second. +- Full golangci-lint runs only in CI via GitHub Actions, not available locally. +- ALWAYS run `make format` and `make lint` before committing changes. + +**Plugin Installation:** +- Install as Helm plugin: `make install` or `make install/helm3` - builds and installs to Helm plugins directory. Takes ~3 seconds. +- The plugin installs via `install-binary.sh` script which handles cross-platform binary installation. + +## Validation Scenarios + +**ALWAYS test your changes with these scenarios:** + +1. **Basic Plugin Functionality:** + ```bash + # Test the binary directly + ./bin/diff version + ./bin/diff --help + ./bin/diff upgrade --help + ``` + +2. **Real Chart Diffing:** + ```bash + # Create a test chart and diff it + cd /tmp && helm create test-chart + cd /path/to/helm-diff + HELM_NAMESPACE=default HELM_BIN=helm ./bin/diff upgrade --install --dry-run test-release /tmp/test-chart + ``` + +3. **Plugin Installation Verification:** + ```bash + # Test plugin installation + export HELM_DATA_HOME=/tmp/helm-test + make install + /tmp/helm-test/plugins/helm-diff/bin/diff version + ``` + +## Build Times and Timeouts + +**CRITICAL: NEVER CANCEL long-running commands. Use these timeout values:** + +- `make bootstrap`: <1 second (if already done) or ~50 seconds (first time) (set timeout: 5+ minutes) +- `make build`: ~9 seconds after bootstrap (set timeout: 3+ minutes) +- `make test`: ~12 seconds (set timeout: 3+ minutes) +- `make lint`: ~2 seconds (set timeout: 1 minute) +- `make format`: <1 second (set timeout: 1 minute) +- `make install`: ~3 seconds (set timeout: 2 minutes) + +## Common Tasks + +**Repository Structure:** +- `main.go` - Entry point that delegates to cmd package +- `cmd/` - Command-line interface implementation (upgrade, release, revision, rollback, version) +- `diff/` - Core diffing logic and output formatting +- `manifest/` - Kubernetes manifest parsing and handling +- `scripts/` - Build and verification scripts (gofmt, govet, staticcheck) +- `testdata/`, `*/testdata/` - Test fixtures and mock data +- `plugin.yaml` - Helm plugin configuration +- `install-binary.sh` - Cross-platform installation script +- `Makefile` - Build system with all common targets + +**Key Files to Check After Changes:** +- Always run tests after modifying `cmd/` or `diff/` packages +- Check `plugin.yaml` version if making release changes +- Verify `Makefile` targets if changing build process +- Review `install-binary.sh` if modifying installation process + +**Environment Variables for Testing:** +- `HELM_NAMESPACE` - Kubernetes namespace for operations +- `HELM_BIN` - Path to helm binary (for direct testing) +- `HELM_DIFF_USE_UPGRADE_DRY_RUN` - Use helm upgrade --dry-run instead of template +- `HELM_DIFF_THREE_WAY_MERGE` - Enable three-way merge diffing +- `HELM_DIFF_NORMALIZE_MANIFESTS` - Normalize YAML before diffing +- `HELM_DIFF_OUTPUT_CONTEXT` - Configure output context lines + +**CI/CD Information:** +- GitHub Actions runs on push/PR to master branch +- Tests run on Ubuntu, macOS, Windows with multiple Helm versions +- Integration tests use Kind (Kubernetes in Docker) +- Linting uses golangci-lint via GitHub Actions (not available locally) +- Cross-platform plugin installation is tested via Docker + +**Direct Binary Usage (for development):** +```bash +# Build and test directly without Helm plugin installation +go build -o bin/diff -ldflags="-X github.com/databus23/helm-diff/v3/cmd.Version=dev" +HELM_NAMESPACE=default HELM_BIN=helm ./bin/diff upgrade --install --dry-run my-release ./chart-path +``` + +**Common Commands Reference:** +```bash +# Full development cycle +make bootstrap # Install dependencies (once) +make build # Build with linting +make test # Run all tests +make format # Format code +make install # Install as Helm plugin + +# Validation +./bin/diff version # Check version +./bin/diff upgrade --help # Check help +make test # Run test suite +``` + +## Important Notes + +- The plugin supports Helm v3 only (v2 support was removed) +- Uses Go modules for dependency management +- Cross-platform support: Linux, macOS, Windows, FreeBSD +- Multiple output formats: diff, simple, template, dyff +- Supports both client-side and server-side dry-run modes +- Includes three-way merge capabilities for advanced diffing +- Plugin binary is named `diff` and installed as `helm diff` command \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0fe61bb2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..a54a6ebf --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,139 @@ +--- +name: CI + +on: + pull_request: + push: + branches: + - master + +jobs: + build: + name: "Build & Test" + if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + + - name: Install dependencies + run: make bootstrap + + - name: Run unit tests + run: make test + + - name: Verify installation + run: | + mkdir -p helmhome + make install HELM_HOME=helmhome + helmhome/plugins/helm-diff/bin/diff version + + helm-install: + name: helm install + if: "!contains(github.event.head_commit.message, '[ci skip]')" + needs: [build] + runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + shell: [ default ] + experimental: [ false ] + helm-version: [ v3.18.6, v3.19.0 ] + include: + - os: windows-latest + shell: wsl + experimental: false + helm-version: v3.18.6 + - os: windows-latest + shell: cygwin + experimental: false + helm-version: v3.18.6 + - os: ubuntu-latest + container: alpine + shell: sh + experimental: false + helm-version: v3.18.6 + - os: windows-latest + shell: wsl + experimental: false + helm-version: v3.19.0 + - os: windows-latest + shell: cygwin + experimental: false + helm-version: v3.19.0 + - os: ubuntu-latest + container: alpine + shell: sh + experimental: false + helm-version: v3.19.0 + + steps: + - name: Disable autocrlf + if: "contains(matrix.os, 'windows-latest')" + run: |- + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v5 + + - name: Setup Helm + uses: azure/setup-helm@v4 + with: + version: ${{ matrix.helm-version }} + + - name: Setup WSL + if: "contains(matrix.shell, 'wsl')" + uses: Vampire/setup-wsl@v6 + + - name: Setup Cygwin + if: "contains(matrix.shell, 'cygwin')" + uses: egor-tensin/setup-cygwin@v4 + + - name: helm plugin install + run: helm plugin install . + + integration-tests: + name: Integration Tests + if: "!contains(github.event.head_commit.message, '[ci skip]')" + needs: [build] + runs-on: ubuntu-latest + strategy: + matrix: + include: + # Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions. + # That's why we cover only 2 Helm minor versions in this matrix. + # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context. + - helm-version: v3.18.6 + - helm-version: v3.19.0 + steps: + - uses: engineerd/setup-kind@v0.6.2 + with: + skipClusterLogsExport: true + + + - uses: actions/checkout@v5 + + - name: Setup Helm + uses: azure/setup-helm@v4 + with: + version: ${{ matrix.helm-version }} + + - name: helm plugin install + run: helm plugin install . + + - name: helm create helm-diff + run: helm create helm-diff + + - name: helm diff upgrade --install helm-diff ./helm-diff + run: helm diff upgrade --install helm-diff ./helm-diff + + - name: helm upgrade -i helm-diff ./helm-diff + run: helm upgrade -i helm-diff ./helm-diff + + - name: helm diff upgrade -C 3 --set replicaCount=2 --install helm-diff ./helm-diff + run: helm diff upgrade -C 3 --set replicaCount=2 --install helm-diff ./helm-diff diff --git a/.github/workflows/cleanup.yaml b/.github/workflows/cleanup.yaml new file mode 100644 index 00000000..4de2f098 --- /dev/null +++ b/.github/workflows/cleanup.yaml @@ -0,0 +1,33 @@ +--- +name: Cleanup + +on: + pull_request: + types: + - closed + +jobs: + cleanup-cache: + runs-on: ubuntu-latest + steps: + - name: 'Cleanup PR cache' + run: | + gh extension install actions/gh-actions-cache + + REPO="${{ github.repository }}" + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + shell: bash + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/lint-sh.yaml b/.github/workflows/lint-sh.yaml new file mode 100644 index 00000000..7fc80827 --- /dev/null +++ b/.github/workflows/lint-sh.yaml @@ -0,0 +1,22 @@ +name: Lint sh + +on: + push: + branches: [master] + paths: ['install-binary.sh'] + pull_request: + branches: [master] + paths: ['install-binary.sh'] + +jobs: + lint-sh: + name: Lint install-binary.sh + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + continue-on-error: true + steps: + - uses: actions/checkout@v5 + - uses: luizm/action-sh-checker@v0.9.0 + with: + sh_checker_exclude: 'scripts' + sh_checker_checkbashisms_enable: true diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..c3e7e912 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,24 @@ +--- +name: Lint + +on: + push: + branches: [ master ] + paths-ignore: [ '**.md' ] + pull_request: + branches: [ master ] + paths-ignore: [ '**.md' ] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + - uses: golangci/golangci-lint-action@v9 + with: + version: v2.1.6 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..adf0c1ee --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,43 @@ +name: Release + +on: + push: + tags: + - '*' + branches: + - 'main' + - 'master' + pull_request: + branches: + - 'main' + - 'master' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: echo "flags=--snapshot" >> $GITHUB_ENV + - + name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v1' + args: release --clean ${{ env.flags }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 6f6b5224..2612c609 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,9 @@ vendor/ bin/ build/ release/ +dist/ .envrc -.idea \ No newline at end of file +.idea +docker-run-release-cache/ +.vscode/ +/cover.out diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..13389079 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,182 @@ +version: "2" +run: + issues-exit-code: 1 + tests: true +output: + formats: + text: + path: stdout + print-linter-name: true + print-issued-lines: true + colors: false +linters: + default: none + enable: + - bodyclose + - copyloopvar + - depguard + - errcheck + - errorlint + - funlen + - gocognit + - goconst + - govet + - ineffassign + - misspell + - nakedret + - reassign + - revive + - staticcheck + - testifylint + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace + settings: + staticcheck: + checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1005", "-QF1001", "-QF1008"] + depguard: + rules: + main: + files: + - $all + allow: + - $gostd + - github.com/Masterminds/semver/v3 + - github.com/aryann/difflib + - github.com/databus23/helm-diff/v3 + - github.com/evanphx/json-patch/v5 + - github.com/gonvenience/bunt + - github.com/gonvenience/ytbx + - github.com/google/go-cmp/cmp + - github.com/homeport/dyff/pkg/dyff + - github.com/json-iterator/go + - github.com/mgutz/ansi + - github.com/spf13/cobra + - github.com/spf13/pflag + - golang.org/x/term + - gopkg.in/yaml.v2 + - github.com/stretchr/testify/require + - helm.sh/helm/v3 + - k8s.io/api/core/v1 + - k8s.io/apiextensions-apiserver + - k8s.io/apimachinery + - k8s.io/cli-runtime + - k8s.io/client-go + - sigs.k8s.io/yaml + deny: + - pkg: github.com/sirupsen/logrus + desc: not allowed + - pkg: github.com/pkg/errors + desc: Should be replaced by standard lib errors package + dogsled: + max-blank-identifiers: 2 + dupl: + threshold: 100 + errcheck: + check-type-assertions: false + check-blank: false + funlen: + lines: 280 + statements: 140 + gocognit: + min-complexity: 100 + goconst: + min-len: 3 + min-occurrences: 8 + gocritic: + settings: + captLocal: + paramsOnly: true + gocyclo: + min-complexity: 30 + godox: + keywords: + - TODO + - BUG + - FIXME + - NOTE + - OPTIMIZE + - HACK + gosec: + excludes: + - G104 + govet: + disable: + - shadow + settings: + printf: + funcs: + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + lll: + line-length: 120 + tab-width: 1 + misspell: + locale: US + ignore-rules: + - GitLab + nakedret: + max-func-lines: 30 + prealloc: + simple: true + range-loops: true + for-loops: false + revive: + confidence: 0.8 + severity: warning + unparam: + check-exported: false + whitespace: + multi-if: false + multi-func: false + wsl: + strict-append: true + allow-assign-and-call: true + allow-multiline-assign: true + force-case-trailing-whitespace: 0 + allow-trailing-comment: false + allow-cuddle-declarations: false + exclusions: + generated: lax + rules: + - linters: + - dupl + - errcheck + - funlen + - gocyclo + - gosec + path: _test\.go + - linters: + - lll + source: '^//go:generate ' + paths: + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + new: false +formatters: + enable: + - gci + - gofmt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(github.com/databus23/helm-diff/v3) + gofmt: + simplify: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 00000000..1c2d5f01 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,59 @@ +# To test this manually, run: +# go install github.com/goreleaser/goreleaser@latest +# goreleaser --snapshot --clean +# for f in dist/helm-diff*.tgz; do echo Testing $f...; tar tzvf $f; done +project_name: helm-diff +builds: + - id: default + main: . + binary: bin/diff + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X github.com/databus23/helm-diff/v3/cmd.Version={{ .Version }} + goos: + - freebsd + - darwin + - linux + - windows + goarch: + - amd64 + - arm64 + - id: linux-additional + main: . + binary: bin/diff + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X github.com/databus23/helm-diff/v3/cmd.Version={{ .Version }} + goos: + - linux + goarch: + - arm + - ppc64le + - s390x + goarm: + - "6" + - "7" + +archives: + - id: default + builds: + - default + - linux-additional + format: tgz + name_template: '{{ .ProjectName }}-{{ if eq .Os "darwin" }}macos{{ else }}{{ .Os }}{{ end }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' + wrap_in_directory: diff + files: + - README.md + - plugin.yaml + - LICENSE +changelog: + use: github-native + +release: + prerelease: auto diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..3627e5f1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +Before submitting a pull request, I'd encourage you to test it yourself. + +To do so, you need to run the plugin indirectly or directly. + +**Indirect** Install the plugin locally and run it via helm: + +``` +$ helm plugin uninstall diff + +$ helm plugin list +#=> Make sure that the previous installation of helm-diff has unisntalled + +$ make install + +$ helm plugin list +#=> Make sure that the version of helm-diff built from your branch has instaled + +$ helm diff upgrade ... (snip) +``` + +**Direct** Build the plugin binary and execute it with a few helm-specific environment variables: + +``` +$ go build . + +$ HELM_NAMESPACE=default \ +HELM_BIN=helm372 \ + ./helm-diff upgrade foo $CHART \ + --set argo-cd.nameOverride=testtest \ + --install +``` diff --git a/Dockerfile.release b/Dockerfile.release new file mode 100644 index 00000000..e0261ca8 --- /dev/null +++ b/Dockerfile.release @@ -0,0 +1,23 @@ +FROM golang:1.24 + +# See https://github.com/cli/cli/blob/trunk/docs/install_linux.md#debian-ubuntu-linux-raspberry-pi-os-apt +# for the latest gh install instructions when the below didn't work + +RUN type -p curl >/dev/null || (apt update && apt install curl -y) + +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt update \ + && apt install gh -y + +ARG HELM_DIFF_UID + +RUN adduser \ + --gecos "Helm Diff" \ + --disabled-password \ + -u "$HELM_DIFF_UID" \ + helm-diff-releaser \ + --shell /bin/sh + +USER helm-diff-releaser diff --git a/Makefile b/Makefile index d328b3d4..86ffbfb8 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,12 @@ -HELM_HOME ?= $(shell helm home) +HELM_HOME ?= $(shell helm env HELM_DATA_HOME) VERSION := $(shell sed -n -e 's/version:[ "]*\([^"]*\).*/\1/p' plugin.yaml) -HELM_3_PLUGINS := $(shell bash -c 'eval $$(helm env); echo $$HELM_PLUGINS') +HELM_3_PLUGINS := $(shell helm env HELM_PLUGINS) PKG:= github.com/databus23/helm-diff/v3 LDFLAGS := -X $(PKG)/cmd.Version=$(VERSION) -# Clear the "unreleased" string in BuildMetadata -LDFLAGS += -X k8s.io/helm/pkg/version.BuildMetadata= -LDFLAGS += -X k8s.io/helm/pkg/version.Version=$(shell ./scripts/dep-helm-version.sh) +GO ?= go .PHONY: format format: @@ -31,8 +29,8 @@ install/helm3: build lint: scripts/update-gofmt.sh scripts/verify-gofmt.sh - scripts/verify-golint.sh scripts/verify-govet.sh + scripts/verify-staticcheck.sh .PHONY: build build: lint @@ -41,19 +39,31 @@ build: lint .PHONY: test test: - go test -v ./... + go test -v ./... -coverprofile cover.out -race + go tool cover -func cover.out .PHONY: bootstrap bootstrap: go mod download - command -v golint || GO111MODULE=off go get -u golang.org/x/lint/golint + command -v staticcheck || go install honnef.co/go/tools/cmd/staticcheck@latest .PHONY: docker-run-release docker-run-release: export pkg=/go/src/github.com/databus23/helm-diff docker-run-release: git checkout master git push - docker run -it --rm -e GITHUB_TOKEN -v $(shell pwd):$(pkg) -w $(pkg) golang:1.17.5 make bootstrap release + # needed to avoid "failed to initialize build cache at /.cache/go-build: mkdir /.cache: permission denied" + mkdir -p docker-run-release-cache + # uid needs to be set to avoid "error obtaining VCS status: exit status 128" + # Also, there needs to be a valid Linux user with the uid in the container- + # otherwise git-push will fail. + docker build -t helm-diff-release -f Dockerfile.release \ + --build-arg HELM_DIFF_UID=$(shell id -u) --load . + docker run -it --rm -e GITHUB_TOKEN \ + -v ${SSH_AUTH_SOCK}:/tmp/ssh-agent.sock -e SSH_AUTH_SOCK=/tmp/ssh-agent.sock \ + -v $(shell pwd):$(pkg) \ + -v $(shell pwd)/docker-run-release-cache:/.cache \ + -w $(pkg) helm-diff-release make bootstrap release .PHONY: dist dist: export COPYFILE_DISABLE=1 #teach OSX tar to not put ._* files in tar archive @@ -62,26 +72,31 @@ dist: rm -rf build/diff/* release/* mkdir -p build/diff/bin release/ cp README.md LICENSE plugin.yaml build/diff - GOOS=linux GOARCH=amd64 go build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + GOOS=linux GOARCH=amd64 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-diff-linux-amd64.tgz diff/ - GOOS=linux GOARCH=arm64 go build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + GOOS=linux GOARCH=arm64 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-diff-linux-arm64.tgz diff/ - GOOS=freebsd GOARCH=amd64 go build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + GOOS=linux GOARCH=arm GOARM=6 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + tar -C build/ -zcvf $(CURDIR)/release/helm-diff-linux-armv6.tgz diff/ + GOOS=linux GOARCH=arm GOARM=7 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + tar -C build/ -zcvf $(CURDIR)/release/helm-diff-linux-armv7.tgz diff/ + GOOS=linux GOARCH=ppc64le $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + tar -C build/ -zcvf $(CURDIR)/release/helm-diff-linux-ppc64le.tgz diff/ + GOOS=linux GOARCH=s390x $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + tar -C build/ -zcvf $(CURDIR)/release/helm-diff-linux-s390x.tgz diff/ + GOOS=freebsd GOARCH=amd64 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-diff-freebsd-amd64.tgz diff/ - GOOS=darwin GOARCH=amd64 go build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + GOOS=darwin GOARCH=amd64 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-diff-macos-amd64.tgz diff/ - GOOS=darwin GOARCH=arm64 go build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" + GOOS=darwin GOARCH=arm64 $(GO) build -o build/diff/bin/diff -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-diff-macos-arm64.tgz diff/ rm build/diff/bin/diff - GOOS=windows GOARCH=amd64 go build -o build/diff/bin/diff.exe -trimpath -ldflags="$(LDFLAGS)" + GOOS=windows GOARCH=amd64 $(GO) build -o build/diff/bin/diff.exe -trimpath -ldflags="$(LDFLAGS)" tar -C build/ -zcvf $(CURDIR)/release/helm-diff-windows-amd64.tgz diff/ .PHONY: release release: lint dist -ifndef GITHUB_TOKEN - $(error GITHUB_TOKEN is undefined) -endif - scripts/release.sh v$(VERSION) master + scripts/release.sh v$(VERSION) # Test for the plugin installation with `helm plugin install -v THIS_BRANCH` works # Useful for verifying modified `install-binary.sh` still works against various environments diff --git a/README.md b/README.md index 8bf055af..5fcde05c 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/databus23/helm-diff)](https://goreportcard.com/report/github.com/databus23/helm-diff) [![GoDoc](https://godoc.org/github.com/databus23/helm-diff?status.svg)](https://godoc.org/github.com/databus23/helm-diff) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/databus23/helm-diff/blob/master/LICENSE) +[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/databus23/helm-diff) -This is a Helm plugin giving your a preview of what a `helm upgrade` would change. +This is a Helm plugin giving you a preview of what a `helm upgrade` would change. It basically generates a diff between the latest deployed version of a release -and a `helm upgrade --debug --dry-run`. This can also be used to compare two +and a `helm upgrade --debug --dry-run`. This can also be used to compare two revisions/versions of your helm release. @@ -14,6 +15,8 @@ revisions/versions of your helm release. ### Using Helm plugin manager (> 2.3.x) +*requires helm 3.18+* + ```shell helm plugin install https://github.com/databus23/helm-diff ``` @@ -30,21 +33,15 @@ curl -L $TARBALL_URL | tar -C $(helm home)/plugins -xzv ### From Source #### Prerequisites - - GoLang `>= 1.17` + - GoLang `>= 1.21` -Make sure you do not have a verison of `helm-diff` installed. You can remove it by running `helm plugin uninstall diff` +Make sure you do not have a version of `helm-diff` installed. You can remove it by running `helm plugin uninstall diff` #### Installation Steps -The first step is to download the repository and enter the directory. You can do this via `git clone` or downloaing and extracting the release. If you clone via git, remember to checkout the latest tag for the latest release. - -Next, depending on which helm version you have, install the plugin into helm. +The first step is to download the repository and enter the directory. You can do this via `git clone` or downloading and extracting the release. If you clone via git, remember to checkout the latest tag for the latest release. -##### Helm 2 -```bash -make install -``` +Next, install the plugin into helm. -##### Helm 3 ```bash make install/helm3 ``` @@ -57,18 +54,18 @@ The Helm Diff Plugin * Shows a diff explaining what a helm upgrade would change: This fetches the currently deployed version of a release - and compares it to a local chart plus values. This can be - used visualize what changes a helm upgrade will perform. + and compares it to a local chart plus values. This can be + used to visualize what changes a helm upgrade will perform. * Shows a diff explaining what had changed between two revisions: This fetches previously deployed versions of a release - and compares them. This can be used visualize what changes + and compares them. This can be used to visualize what changes were made during revision change. * Shows a diff explaining what a helm rollback would change: This fetches the currently deployed version of a release and compares it to the previously deployed version of the release, that you - want to rollback. This can be used visualize what changes a + want to rollback. This can be used to visualize what changes a helm rollback will perform. Usage: @@ -76,6 +73,7 @@ Usage: diff [command] Available Commands: + completion Generate the autocompletion script for the specified shell release Shows diff between release's manifests revision Shows diff between revision's manifests rollback Show a diff explaining what a helm rollback could perform @@ -83,40 +81,53 @@ Available Commands: version Show version of the helm diff plugin Flags: - --allow-unreleased enables diffing of releases that are not yet deployed via Helm - -a, --api-versions stringArray Kubernetes api versions used for Capabilities.APIVersions - --color color output. You can control the value for this flag via HELM_DIFF_COLOR=[true|false]. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" - -C, --context int output NUM lines of context around changes (default -1) - --detailed-exitcode return a non-zero exit code when there are changes - --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. - --disable-openapi-validation disables rendered templates validation against the Kubernetes OpenAPI Schema - --disable-validation disables rendered templates validation against the Kubernetes cluster you are currently pointing to. This is the same validation performed on an install - --dry-run disables cluster access and show diff as if it was install. Implies --install, --reset-values, and --disable-validation - -h, --help help for diff - --include-tests enable the diffing of the helm test hooks - --install enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match "helm upgrade --install" command - --kubeconfig string This flag is ignored, to allow passing of this top level flag to helm - --no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" - --no-hooks disable diffing of hooks - --normalize-manifests normalize manifests before running diff to exclude style differences from the output - --output string Possible values: diff, simple, json, template. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") - --post-renderer string the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path - --repo string specify the chart repository url to locate the requested chart - --reset-values reset the values to the ones built into the chart and merge in any new values - --reuse-values reuse the last release's values and merge in any new values. If '--reset-values' is specified, this is ignored - --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --set-file stringArray set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) - --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --show-secrets do not redact secret values in the output - --strip-trailing-cr strip trailing carriage return on input - --suppress stringArray allows suppression of the values listed in the diff output - -q, --suppress-secrets suppress secrets in the output - --three-way-merge use three-way-merge to compute patch and generate diff output - -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) - --version string specify the exact chart version to use. If this is not specified, the latest version is used - -Additional help topics: - diff + --allow-unreleased enables diffing of releases that are not yet deployed via Helm + -a, --api-versions stringArray Kubernetes api versions used for Capabilities.APIVersions + --color color output. You can control the value for this flag via HELM_DIFF_COLOR=[true|false]. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" + -C, --context int output NUM lines of context around changes (default -1) + --detailed-exitcode return a non-zero exit code when there are changes + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. + --disable-openapi-validation disables rendered templates validation against the Kubernetes OpenAPI Schema + --disable-validation disables rendered templates validation against the Kubernetes cluster you are currently pointing to. This is the same validation performed on an install + --dry-run string[="client"] --dry-run, --dry-run=client, or --dry-run=true disables cluster access and show diff as if it was install. Implies --install, --reset-values, and --disable-validation. --dry-run=server enables the cluster access with helm-get and the lookup template function. + --enable-dns enable DNS lookups when rendering templates + -D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched + -h, --help help for diff + --include-crds include CRDs in the diffing + --include-tests enable the diffing of the helm test hooks + --insecure-skip-tls-verify skip tls certificate checks for the chart download + --install enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match "helm upgrade --install" command + --kube-version string Kubernetes version used for Capabilities.KubeVersion + --kubeconfig string This flag is ignored, to allow passing of this top level flag to helm + --no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" + --no-hooks disable diffing of hooks + --normalize-manifests normalize manifests before running diff to exclude style differences from the output + --output string Possible values: diff, simple, template, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") + --post-renderer string the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path + --post-renderer-args stringArray an argument to the post-renderer (can specify multiple) + --repo string specify the chart repository url to locate the requested chart + --reset-then-reuse-values reset the values to the ones built into the chart, apply the last release's values and merge in any new values. If '--reset-values' or '--reuse-values' is specified, this is ignored + --reset-values reset the values to the ones built into the chart and merge in any new values + --reuse-values reuse the last release's values and merge in any new values. If '--reset-values' is specified, this is ignored + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-file stringArray set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --set-json stringArray set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2) + --set-literal stringArray set STRING literal values on the command line + --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --show-secrets do not redact secret values in the output + --show-secrets-decoded decode secret values in the output + --skip-schema-validation skip validation of the rendered manifests against the Kubernetes OpenAPI schema + --strip-trailing-cr strip trailing carriage return on input + --suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service') + --suppress-output-line-regex stringArray a regex to suppress diff output lines that match + -q, --suppress-secrets suppress secrets in the output + --take-ownership if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources + --three-way-merge use three-way-merge to compute patch and generate diff output + -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) + --version string specify the exact chart version to use. If this is not specified, the latest version is used + +Additional help topcis: + diff Use "diff [command] --help" for more information about a command. ``` @@ -131,7 +142,7 @@ Show a diff explaining what a helm upgrade would change. This fetches the currently deployed version of a release and compares it to a chart plus values. -This can be used visualize what changes a helm upgrade will +This can be used to visualize what changes a helm upgrade will perform. Usage: @@ -148,47 +159,72 @@ Examples: # Set HELM_DIFF_USE_UPGRADE_DRY_RUN=true to # use `helm upgrade --dry-run` instead of `helm template` to render manifests from the chart. # See https://github.com/databus23/helm-diff/issues/253 for more information. - HELM_DIFF_USE_UPGRADE_DRY_RUN=true helm diff upgarde my-release datadog/datadog + HELM_DIFF_USE_UPGRADE_DRY_RUN=true helm diff upgrade my-release datadog/datadog # Set HELM_DIFF_THREE_WAY_MERGE=true to # enable the three-way-merge on diff. # This is equivalent to specifying the --three-way-merge flag. # Read the flag usage below for more information on --three-way-merge. - HELM_DIFF_THREE_WAY_MERGE=true helm diff upgarde my-release datadog/datadog + HELM_DIFF_THREE_WAY_MERGE=true helm diff upgrade my-release datadog/datadog + + # Set HELM_DIFF_NORMALIZE_MANIFESTS=true to + # normalize the yaml file content when using helm diff. + # This is equivalent to specifying the --normalize-manifests flag. + # Read the flag usage below for more information on --normalize-manifests. + HELM_DIFF_NORMALIZE_MANIFESTS=true helm diff upgrade my-release datadog/datadog + +# Set HELM_DIFF_OUTPUT_CONTEXT=n to configure the output context to n lines. +# This is equivalent to specifying the --context flag. +# Read the flag usage below for more information on --context. +HELM_DIFF_OUTPUT_CONTEXT=5 helm diff upgrade my-release datadog/datadog Flags: - --allow-unreleased enables diffing of releases that are not yet deployed via Helm - -a, --api-versions stringArray Kubernetes api versions used for Capabilities.APIVersions - -C, --context int output NUM lines of context around changes (default -1) - --detailed-exitcode return a non-zero exit code when there are changes - --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. - --disable-openapi-validation disables rendered templates validation against the Kubernetes OpenAPI Schema - --disable-validation disables rendered templates validation against the Kubernetes cluster you are currently pointing to. This is the same validation performed on an install - --dry-run disables cluster access and show diff as if it was install. Implies --install, --reset-values, and --disable-validation - -h, --help help for upgrade - --include-tests enable the diffing of the helm test hooks - --install enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match "helm upgrade --install" command - --kubeconfig string This flag is ignored, to allow passing of this top level flag to helm - --no-hooks disable diffing of hooks - --normalize-manifests normalize manifests before running diff to exclude style differences from the output - --output string Possible values: diff, simple, json, template. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") - --post-renderer string the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path - --repo string specify the chart repository url to locate the requested chart - --reset-values reset the values to the ones built into the chart and merge in any new values - --reuse-values reuse the last release's values and merge in any new values. If '--reset-values' is specified, this is ignored - --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --set-file stringArray set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) - --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --show-secrets do not redact secret values in the output - --strip-trailing-cr strip trailing carriage return on input - --suppress stringArray allows suppression of the values listed in the diff output - -q, --suppress-secrets suppress secrets in the output - --three-way-merge use three-way-merge to compute patch and generate diff output - -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) - --version string specify the exact chart version to use. If this is not specified, the latest version is used + --allow-unreleased enables diffing of releases that are not yet deployed via Helm + -a, --api-versions stringArray Kubernetes api versions used for Capabilities.APIVersions + -C, --context int output NUM lines of context around changes (default -1) + --detailed-exitcode return a non-zero exit code when there are changes + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. + --disable-openapi-validation disables rendered templates validation against the Kubernetes OpenAPI Schema + --disable-validation disables rendered templates validation against the Kubernetes cluster you are currently pointing to. This is the same validation performed on an install + --dry-run string[="client"] --dry-run, --dry-run=client, or --dry-run=true disables cluster access and show diff as if it was install. Implies --install, --reset-values, and --disable-validation. --dry-run=server enables the cluster access with helm-get and the lookup template function. + --enable-dns enable DNS lookups when rendering templates + -D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched + -h, --help help for upgrade + --include-crds include CRDs in the diffing + --include-tests enable the diffing of the helm test hooks + --insecure-skip-tls-verify skip tls certificate checks for the chart download + --install enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match "helm upgrade --install" command + --kube-version string Kubernetes version used for Capabilities.KubeVersion + --kubeconfig string This flag is ignored, to allow passing of this top level flag to helm + --no-hooks disable diffing of hooks + --normalize-manifests normalize manifests before running diff to exclude style differences from the output + --output string Possible values: diff, simple, template, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") + --post-renderer string the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path + --post-renderer-args stringArray an argument to the post-renderer (can specify multiple) + --repo string specify the chart repository url to locate the requested chart + --reset-then-reuse-values reset the values to the ones built into the chart, apply the last release's values and merge in any new values. If '--reset-values' or '--reuse-values' is specified, this is ignored + --reset-values reset the values to the ones built into the chart and merge in any new values + --reuse-values reuse the last release's values and merge in any new values. If '--reset-values' is specified, this is ignored + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-file stringArray set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) + --set-json stringArray set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2) + --set-literal stringArray set STRING literal values on the command line + --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --show-secrets do not redact secret values in the output + --show-secrets-decoded decode secret values in the output + --skip-schema-validation skip validation of the rendered manifests against the Kubernetes OpenAPI schema + --strip-trailing-cr strip trailing carriage return on input + --suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service') + --suppress-output-line-regex stringArray a regex to suppress diff output lines that match + -q, --suppress-secrets suppress secrets in the output + --take-ownership if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources + --three-way-merge use three-way-merge to compute patch and generate diff output + -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) + --version string specify the exact chart version to use. If this is not specified, the latest version is used Global Flags: - --no-color remove colors from the output + --color color output. You can control the value for this flag via HELM_DIFF_COLOR=[true|false]. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" + --no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" ``` ### release: @@ -196,35 +232,37 @@ Global Flags: ``` $ helm diff release -h -This command compares the manifests details of a different releases created from the same chart +This command compares the manifests details of a different releases created from the same chart. +The release name may be specified using namespace/release syntax. It can be used to compare the manifests of - release1 with release2 - $ helm diff release [flags] release1 release2 + $ helm diff release [flags] release1 release2 Example: - $ helm diff release my-prod my-stage + $ helm diff release my-prod my-stage + $ helm diff release prod/my-prod stage/my-stage Usage: diff release [flags] RELEASE release1 [release2] Flags: - -C, --context int output NUM lines of context around changes (default -1) - -h, --help help for release - --home string location of your Helm config. Overrides $HELM_HOME (default "/home/aananth/.helm") - --include-tests enable the diffing of the helm test hooks - --strip-trailing-cr strip trailing carriage return on input - --suppress stringArray allows suppression of the values listed in the diff output - -q, --suppress-secrets suppress secrets in the output - --tls enable TLS for request - --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") - --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") - --tls-hostname string the server name used to verify the hostname on the returned certificates from the server - --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") - --tls-verify enable TLS for request and verify remote + -C, --context int output NUM lines of context around changes (default -1) + --detailed-exitcode return a non-zero exit code when there are changes + -D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched + -h, --help help for release + --include-tests enable the diffing of the helm test hooks + --normalize-manifests normalize manifests before running diff to exclude style differences from the output + --output string Possible values: diff, simple, template, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") + --show-secrets do not redact secret values in the output + --strip-trailing-cr strip trailing carriage return on input + --suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service') + --suppress-output-line-regex stringArray a regex to suppress diff output lines that match + -q, --suppress-secrets suppress secrets in the output Global Flags: - --no-color remove colors from the output + --color color output. You can control the value for this flag via HELM_DIFF_COLOR=[true|false]. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" + --no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" ``` ### revision: @@ -234,29 +272,40 @@ $ helm diff revision -h This command compares the manifests details of a named release. -It can be used to compare the manifests of - +It can be used to compare the manifests of + - latest REVISION with specified REVISION - $ helm diff revision [flags] RELEASE REVISION1 - Example: - $ helm diff revision my-release 2 + $ helm diff revision [flags] RELEASE REVISION1 + Example: + $ helm diff revision my-release 2 - REVISION1 with REVISION2 - $ helm diff revision [flags] RELEASE REVISION1 REVISION2 - Example: - $ helm diff revision my-release 2 3 + $ helm diff revision [flags] RELEASE REVISION1 REVISION2 + Example: + $ helm diff revision my-release 2 3 Usage: diff revision [flags] RELEASE REVISION1 [REVISION2] Flags: - -h, --help help for revision - --strip-trailing-cr strip trailing carriage return on input - --suppress stringArray allows suppression of the values listed in the diff output - -q, --suppress-secrets suppress secrets in the output + -C, --context int output NUM lines of context around changes (default -1) + --show-secrets-decoded decode secret values in the output + --detailed-exitcode return a non-zero exit code when there are changes + -D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched + -h, --help help for revision + --include-tests enable the diffing of the helm test hooks + --normalize-manifests normalize manifests before running diff to exclude style differences from the output + --output string Possible values: diff, simple, template, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") + --show-secrets do not redact secret values in the output + --show-secrets-decoded decode secret values in the output + --strip-trailing-cr strip trailing carriage return on input + --suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service') + --suppress-output-line-regex stringArray a regex to suppress diff output lines that match + -q, --suppress-secrets suppress secrets in the output Global Flags: - --no-color remove colors from the output + --color color output. You can control the value for this flag via HELM_DIFF_COLOR=[true|false]. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" + --no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" ``` ### rollback: @@ -264,7 +313,7 @@ Global Flags: ``` $ helm diff rollback -h -This command compares the latest manifest details of a named release +This command compares the latest manifest details of a named release with specific revision values to rollback. It forecasts/visualizes changes, that a helm rollback could perform. @@ -276,13 +325,23 @@ Examples: helm diff rollback my-release 2 Flags: - -h, --help help for rollback - --strip-trailing-cr strip trailing carriage return on input - --suppress stringArray allows suppression of the values listed in the diff output - -q, --suppress-secrets suppress secrets in the output + -C, --context int output NUM lines of context around changes (default -1) + --detailed-exitcode return a non-zero exit code when there are changes + -D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched + -h, --help help for rollback + --include-tests enable the diffing of the helm test hooks + --normalize-manifests normalize manifests before running diff to exclude style differences from the output + --output string Possible values: diff, simple, template, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff") + --show-secrets do not redact secret values in the output + --show-secrets-decoded decode secret values in the output + --strip-trailing-cr strip trailing carriage return on input + --suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service') + --suppress-output-line-regex stringArray a regex to suppress diff output lines that match + -q, --suppress-secrets suppress secrets in the output Global Flags: - --no-color remove colors from the output + --color color output. You can control the value for this flag via HELM_DIFF_COLOR=[true|false]. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" + --no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb" ``` ## Build @@ -313,6 +372,13 @@ go test -v ./... ## Release +Bump `version` in `plugin.yaml`: + +``` +$ code plugin.yaml +$ git commit -s -m 'Bump helm-diff version to 3.x.y' +``` + Set `GITHUB_TOKEN` and run: ``` diff --git a/cmd/helm.go b/cmd/helm.go index 91962441..8802ce50 100644 --- a/cmd/helm.go +++ b/cmd/helm.go @@ -6,18 +6,8 @@ package cmd import ( "errors" "fmt" - "io/ioutil" "os" - "path/filepath" "strings" - - "github.com/ghodss/yaml" - "google.golang.org/grpc" - "k8s.io/helm/pkg/downloader" - "k8s.io/helm/pkg/getter" - "k8s.io/helm/pkg/helm/environment" - "k8s.io/helm/pkg/helm/helmpath" - "k8s.io/helm/pkg/strvals" ) /////////////// Source: cmd/helm/install.go ///////////////////////// @@ -57,143 +47,6 @@ func (v *valueFiles) Set(value string) error { return nil } -func locateChartPath(name, version string, verify bool, keyring string) (string, error) { - name = strings.TrimSpace(name) - version = strings.TrimSpace(version) - if fi, err := os.Stat(name); err == nil { - abs, err := filepath.Abs(name) - if err != nil { - return abs, err - } - if verify { - if fi.IsDir() { - return "", errors.New("cannot verify a directory") - } - if _, err := downloader.VerifyChart(abs, keyring); err != nil { - return "", err - } - } - return abs, nil - } - if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { - return name, fmt.Errorf("path %q not found", name) - } - - crepo := filepath.Join(helmpath.Home(homePath()).Repository(), name) - if _, err := os.Stat(crepo); err == nil { - return filepath.Abs(crepo) - } - - dl := downloader.ChartDownloader{ - HelmHome: helmpath.Home(homePath()), - Out: os.Stdout, - Keyring: keyring, - Getters: getter.All(environment.EnvSettings{}), - } - if verify { - dl.Verify = downloader.VerifyAlways - } - - filename, _, err := dl.DownloadTo(name, version, helmpath.Home(homePath()).Archive()) - if err == nil { - lname, err := filepath.Abs(filename) - if err != nil { - return filename, err - } - return lname, nil - } - - return filename, err -} - -// Merges source and destination map, preferring values from the source map -func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { - for k, v := range src { - // If the key doesn't exist already, then just set the key to that value - if _, exists := dest[k]; !exists { - dest[k] = v - continue - } - nextMap, ok := v.(map[string]interface{}) - // If it isn't another map, overwrite the value - if !ok { - dest[k] = v - continue - } - // If the key doesn't exist already, then just set the key to that value - if _, exists := dest[k]; !exists { - dest[k] = nextMap - continue - } - // Edge case: If the key exists in the destination, but isn't a map - destMap, isMap := dest[k].(map[string]interface{}) - // If the source map has a map for this key, prefer it - if !isMap { - dest[k] = v - continue - } - // If we got to this point, it is a map in both, so merge them - dest[k] = mergeValues(destMap, nextMap) - } - return dest -} - -/////////////// Source: cmd/helm/upgrade.go ///////////////////////// - -func (d *diffCmd) vals() ([]byte, error) { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range d.valueFiles { - currentMap := map[string]interface{}{} - - var bytes []byte - var err error - if strings.TrimSpace(filePath) == "-" { - bytes, err = ioutil.ReadAll(os.Stdin) - } else { - bytes, err = ioutil.ReadFile(filePath) - } - if err != nil { - return []byte{}, err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) - } - // Merge with the previous map - base = mergeValues(base, currentMap) - } - - // User specified a value via --set - for _, value := range d.values { - if err := strvals.ParseInto(value, base); err != nil { - return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) - } - } - - // User specified a value via --set-string - for _, value := range d.stringValues { - if err := strvals.ParseIntoString(value, base); err != nil { - return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err) - } - } - - // User specified a value via --set-file - for _, value := range d.fileValues { - reader := func(rs []rune) (interface{}, error) { - bytes, err := ioutil.ReadFile(string(rs)) - return string(bytes), err - } - - if err := strvals.ParseIntoFile(value, base, reader); err != nil { - return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err) - } - } - - return yaml.Marshal(base) -} - /////////////// Source: cmd/helm/helm.go //////////////////////////// func checkArgsLength(argsReceived int, requiredArgs ...string) error { @@ -207,17 +60,3 @@ func checkArgsLength(argsReceived int, requiredArgs ...string) error { } return nil } - -func homePath() string { - return os.Getenv("HELM_HOME") -} - -func prettyError(err error) error { - if err == nil { - return nil - } - // This is ridiculous. Why is 'grpc.rpcError' not exported? The least they - // could do is throw an interface on the lib that would let us get back - // the desc. Instead, we have to pass ALL errors through this. - return errors.New(grpc.ErrorDesc(err)) -} diff --git a/cmd/helm3.go b/cmd/helm3.go index cd7424a0..f624656a 100644 --- a/cmd/helm3.go +++ b/cmd/helm3.go @@ -3,45 +3,68 @@ package cmd import ( "bytes" "fmt" - "io/ioutil" + "io" "os" "os/exec" "regexp" "strconv" "strings" - "github.com/Masterminds/semver" + "github.com/Masterminds/semver/v3" ) var ( helmVersionRE = regexp.MustCompile(`Version:\s*"([^"]+)"`) minHelmVersion = semver.MustParse("v3.1.0-rc.1") + // See https://github.com/helm/helm/pull/9426. + minHelmVersionWithDryRunLookupSupport = semver.MustParse("v3.13.0") + // The --reset-then-reuse-values flag for `helm upgrade` was added in + // https://github.com/helm/helm/pull/9653 and released as part of Helm v3.14.0. + minHelmVersionWithResetThenReuseValues = semver.MustParse("v3.14.0") ) -func compatibleHelm3Version() error { +func getHelmVersion() (*semver.Version, error) { cmd := exec.Command(os.Getenv("HELM_BIN"), "version") debugPrint("Executing %s", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("Failed to run `%s version`: %v", os.Getenv("HELM_BIN"), err) + return nil, fmt.Errorf("Failed to run `%s version`: %w", os.Getenv("HELM_BIN"), err) } versionOutput := string(output) matches := helmVersionRE.FindStringSubmatch(versionOutput) if matches == nil { - return fmt.Errorf("Failed to find version in output %#v", versionOutput) + return nil, fmt.Errorf("Failed to find version in output %#v", versionOutput) } helmVersion, err := semver.NewVersion(matches[1]) if err != nil { - return fmt.Errorf("Failed to parse version %#v: %v", matches[1], err) + return nil, fmt.Errorf("Failed to parse version %#v: %w", matches[1], err) } - if minHelmVersion.GreaterThan(helmVersion) { + return helmVersion, nil +} + +func isHelmVersionAtLeast(versionToCompareTo *semver.Version) (bool, error) { + helmVersion, err := getHelmVersion() + + if err != nil { + return false, err + } + if helmVersion.LessThan(versionToCompareTo) { + return false, nil + } + return true, nil +} + +func compatibleHelm3Version() error { + if isCompatible, err := isHelmVersionAtLeast(minHelmVersion); err != nil { + return err + } else if !isCompatible { return fmt.Errorf("helm diff upgrade requires at least helm version %s", minHelmVersion.String()) } return nil - } + func getRelease(release, namespace string) ([]byte, error) { args := []string{"get", "manifest", release} if namespace != "" { @@ -87,9 +110,12 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { if d.devel { flags = append(flags, "--devel") } - if d.noHooks { + if d.noHooks && !d.useUpgradeDryRun { flags = append(flags, "--no-hooks") } + if d.includeCRDs { + flags = append(flags, "--include-crds") + } if d.chartVersion != "" { flags = append(flags, "--version", d.chartVersion) } @@ -102,17 +128,39 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { if d.postRenderer != "" { flags = append(flags, "--post-renderer", d.postRenderer) } - // Helm automatically enable --reuse-values when there's no --set, --set-string, --set-values, --set-file present. + for _, arg := range d.postRendererArgs { + flags = append(flags, "--post-renderer-args", arg) + } + if d.insecureSkipTLSVerify { + flags = append(flags, "--insecure-skip-tls-verify") + } + // Helm automatically enable --reuse-values when there's no --set, --set-string, --set-json, --set-values, --set-file present. // Let's simulate that in helm-diff. // See https://medium.com/@kcatstack/understand-helm-upgrade-flags-reset-values-reuse-values-6e58ac8f127e - shouldDefaultReusingValues := isUpgrade && len(d.values) == 0 && len(d.stringValues) == 0 && len(d.valueFiles) == 0 && len(d.fileValues) == 0 - if (d.reuseValues || shouldDefaultReusingValues) && !d.resetValues && !d.dryRun { - tmpfile, err := ioutil.TempFile("", "existing-values") + shouldDefaultReusingValues := isUpgrade && len(d.values) == 0 && len(d.stringValues) == 0 && len(d.stringLiteralValues) == 0 && len(d.jsonValues) == 0 && len(d.valueFiles) == 0 && len(d.fileValues) == 0 + if (d.reuseValues || d.resetThenReuseValues || shouldDefaultReusingValues) && !d.resetValues && d.clusterAccessAllowed() { + tmpfile, err := os.CreateTemp("", "existing-values") if err != nil { return nil, err } - defer os.Remove(tmpfile.Name()) - if err := d.writeExistingValues(tmpfile); err != nil { + defer func() { + _ = os.Remove(tmpfile.Name()) + }() + // In the presence of --reuse-values (or --reset-values), --reset-then-reuse-values is ignored. + if d.resetThenReuseValues && !d.reuseValues { + var supported bool + supported, err = isHelmVersionAtLeast(minHelmVersionWithResetThenReuseValues) + if err != nil { + return nil, err + } + if !supported { + return nil, fmt.Errorf("Using --reset-then-reuse-values requires at least helm version %s", minHelmVersionWithResetThenReuseValues.String()) + } + err = d.writeExistingValues(tmpfile, false) + } else { + err = d.writeExistingValues(tmpfile, true) + } + if err != nil { return nil, err } flags = append(flags, "--values", tmpfile.Name()) @@ -123,8 +171,40 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { for _, stringValue := range d.stringValues { flags = append(flags, "--set-string", stringValue) } + for _, stringLiteralValue := range d.stringLiteralValues { + flags = append(flags, "--set-literal", stringLiteralValue) + } + for _, jsonValue := range d.jsonValues { + flags = append(flags, "--set-json", jsonValue) + } for _, valueFile := range d.valueFiles { - flags = append(flags, "--values", valueFile) + if strings.TrimSpace(valueFile) == "-" { + bytes, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + + tmpfile, err := os.CreateTemp("", "helm-diff-stdin-values") + if err != nil { + return nil, err + } + defer func() { + _ = os.Remove(tmpfile.Name()) + }() + + if _, err := tmpfile.Write(bytes); err != nil { + _ = tmpfile.Close() + return nil, err + } + + if err := tmpfile.Close(); err != nil { + return nil, err + } + + flags = append(flags, "--values", tmpfile.Name()) + } else { + flags = append(flags, "--values", valueFile) + } } for _, fileValue := range d.fileValues { flags = append(flags, "--set-file", fileValue) @@ -134,35 +214,56 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { flags = append(flags, "--disable-openapi-validation") } + if d.enableDNS { + flags = append(flags, "--enable-dns") + } + + if d.SkipSchemaValidation { + flags = append(flags, "--skip-schema-validation") + } + + if d.takeOwnership { + flags = append(flags, "--take-ownership") + } + var ( subcmd string filter func([]byte) []byte ) + // `--dry-run=client` or `--dry-run=server`? + // + // Or what's the relationoship between helm-diff's --dry-run flag, + // HELM_DIFF_UPGRADE_DRY_RUN env var and the helm upgrade --dry-run flag? + // + // Read on to find out. if d.useUpgradeDryRun { - if d.dryRun { - return nil, fmt.Errorf("`diff upgrade --dry-run` conflicts with HELM_DIFF_USE_UPGRADE_DRY_RUN_AS_TEMPLATE. Either remove --dry-run to enable cluster access, or unset HELM_DIFF_USE_UPGRADE_DRY_RUN_AS_TEMPLATE to make cluster access unnecessary") + if d.isAllowUnreleased() { + // Otherwise you get the following error when this is a diff for a new install + // Error: UPGRADE FAILED: "$RELEASE_NAME" has no deployed releases + flags = append(flags, "--install") } - flags = append(flags, "--dry-run") + // If the program reaches here, + // we are sure that the user wants to use the `helm upgrade --dry-run` command + // for generating the manifests to be diffed. + // + // So the question is only whether to use `--dry-run=client` or `--dry-run=server`. + // + // As HELM_DIFF_UPGRADE_DRY_RUN is there for producing more complete and correct diff results, + // we use --dry-run=server if the version of helm supports it. + // Otherwise, we use --dry-run=client, as that's the best we can do. + if useDryRunService, err := isHelmVersionAtLeast(minHelmVersionWithDryRunLookupSupport); err == nil && useDryRunService { + flags = append(flags, "--dry-run=server") + } else { + flags = append(flags, "--dry-run") + } subcmd = "upgrade" filter = func(s []byte) []byte { - if len(s) == 0 { - return s - } - - i := bytes.Index(s, []byte("MANIFEST:")) - s = s[i:] - i = bytes.Index(s, []byte("---")) - s = s[i:] - i = bytes.Index(s, []byte("\nNOTES:")) - if i != -1 { - s = s[:i+1] - } - return s + return extractManifestFromHelmUpgradeDryRunOutput(s, d.noHooks) } } else { - if !d.disableValidation && !d.dryRun { + if !d.disableValidation && d.clusterAccessAllowed() { flags = append(flags, "--validate") } @@ -174,6 +275,52 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { flags = append(flags, "--api-versions", a) } + if d.kubeVersion != "" { + flags = append(flags, "--kube-version", d.kubeVersion) + } + + // To keep the full compatibility with older helm-diff versions, + // we pass --dry-run to `helm template` only if Helm is greater than v3.13.0. + if useDryRunService, err := isHelmVersionAtLeast(minHelmVersionWithDryRunLookupSupport); err == nil && useDryRunService { + // However, which dry-run mode to use is still not clear. + // + // For compatibility with the old and new helm-diff options, + // old and new helm, we assume that the user wants to use the older `helm template --dry-run=client` mode + // if helm-diff has been invoked with any of the following flags: + // + // * no dry-run flags (to be consistent with helm-template) + // * --dry-run + // * --dry-run="" + // * --dry-run=client + // + // and the newer `helm template --dry-run=server` mode when invoked with: + // + // * --dry-run=server + // + // Any other values should result in errors. + // + // See the fllowing link for more details: + // - https://github.com/databus23/helm-diff/pull/458 + // - https://github.com/helm/helm/pull/9426#issuecomment-1501005666 + if d.dryRunMode == "server" { + // This is for security reasons! + // + // We give helm-template the additional cluster access for the helm `lookup` function + // only if the user has explicitly requested it by --dry-run=server, + // + // In other words, although helm-diff-upgrade implies limited cluster access by default, + // helm-diff-upgrade without a --dry-run flag does NOT imply + // full cluster-access via helm-template --dry-run=server! + flags = append(flags, "--dry-run=server") + } else { + // Since helm-diff 3.9.0 and helm 3.13.0, we pass --dry-run=client to `helm template` by default. + // This doesn't make any difference for helm-diff itself, + // because helm-template w/o flags is equivalent to helm-template --dry-run=client. + // See https://github.com/helm/helm/pull/9426#discussion_r1181397259 + flags = append(flags, "--dry-run=client") + } + } + subcmd = "template" filter = func(s []byte) []byte { @@ -189,10 +336,66 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { return filter(out), err } -func (d *diffCmd) writeExistingValues(f *os.File) error { - cmd := exec.Command(os.Getenv("HELM_BIN"), "get", "values", d.release, "--all", "--output", "yaml") +func (d *diffCmd) writeExistingValues(f *os.File, all bool) error { + args := []string{"get", "values", d.release, "--output", "yaml"} + if all { + args = append(args, "--all") + } + cmd := exec.Command(os.Getenv("HELM_BIN"), args...) debugPrint("Executing %s", strings.Join(cmd.Args, " ")) - defer f.Close() + defer func() { + _ = f.Close() + }() cmd.Stdout = f return cmd.Run() } + +func extractManifestFromHelmUpgradeDryRunOutput(s []byte, noHooks bool) []byte { + if len(s) == 0 { + return s + } + + var ( + hooks []byte + manifest []byte + ) + + i := bytes.Index(s, []byte("HOOKS:")) + if i > -1 { + hooks = s[i:] + } + + j := bytes.Index(hooks, []byte("MANIFEST:")) + if j > -1 { + manifest = hooks[j:] + hooks = hooks[:j] + } + + k := bytes.Index(manifest, []byte("\nNOTES:")) + + if k > -1 { + manifest = manifest[:k+1] + } + + if noHooks { + hooks = nil + } else { + a := bytes.Index(hooks, []byte("---")) + if a > -1 { + hooks = hooks[a:] + } else { + hooks = nil + } + } + + a := bytes.Index(manifest, []byte("---")) + if a > -1 { + manifest = manifest[a:] + } + + r := []byte{} + r = append(r, manifest...) + r = append(r, hooks...) + + return r +} diff --git a/cmd/helm3_test.go b/cmd/helm3_test.go new file mode 100644 index 00000000..bd11e086 --- /dev/null +++ b/cmd/helm3_test.go @@ -0,0 +1,137 @@ +package cmd + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestExtractManifestFromHelmUpgradeDryRunOutput(t *testing.T) { + type testdata struct { + description string + + s string + noHooks bool + + want string + } + + manifest := `--- +# Source: mysql/templates/secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: my1-mysql + namespace: default + labels: + app: my1-mysql + chart: "mysql-1.6.9" + release: "my1" + heritage: "Helm" +type: Opaque +data: + mysql-root-password: "ZlhEVGJseUhmeg==" + mysql-password: "YnRuU3pPOTJMVg==" +--- +# Source: mysql/templates/tests/test-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my1-mysql-test + namespace: default + labels: + app: my1-mysql + chart: "mysql-1.6.9" + heritage: "Helm" + release: "my1" +data: + run.sh: |- + +` + hooks := `--- +# Source: mysql/templates/tests/test.yaml +apiVersion: v1 +kind: Pod +metadata: + name: my1-mysql-test + namespace: default + labels: + app: my1-mysql + chart: "mysql-1.6.9" + heritage: "Helm" + release: "my1" + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: my1-test + image: "bats/bats:1.2.1" + imagePullPolicy: "IfNotPresent" + command: ["/opt/bats/bin/bats", "-t", "/tests/run.sh"] +` + + header := `Release "my1" has been upgraded. Happy Helming! +NAME: my1 +LAST DEPLOYED: Sun Feb 13 02:26:16 2022 +NAMESPACE: default +STATUS: pending-upgrade +REVISION: 2 +HOOKS: +` + + notes := `NOTES: +MySQL can be accessed via port 3306 on the following DNS name from within your cluster: +my1-mysql.default.svc.cluster.local + +*snip* + +To connect to your database directly from outside the K8s cluster: + MYSQL_HOST=127.0.0.1 + MYSQL_PORT=3306 + + # Execute the following command to route the connection: + kubectl port-forward svc/my1-mysql 3306 + + mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD} +` + + outputWithHooks := header + hooks + "MANIFEST:\n" + manifest + notes + outputWithNoHooks := header + "MANIFEST:\n" + manifest + notes + + testcases := []testdata{ + { + description: "should output manifest when noHooks specified", + s: outputWithHooks, + noHooks: true, + want: manifest, + }, + { + description: "should output manifest and hooks when noHooks unspecified", + s: outputWithHooks, + noHooks: false, + want: manifest + hooks, + }, + { + description: "should output manifest if noHooks specified but input did not contain hooks", + s: outputWithNoHooks, + noHooks: true, + want: manifest, + }, + { + description: "should output manifest if noHooks unspecified and input did not contain hooks", + s: outputWithNoHooks, + noHooks: false, + want: manifest, + }, + } + + for _, tc := range testcases { + t.Run(tc.description, func(t *testing.T) { + got := extractManifestFromHelmUpgradeDryRunOutput([]byte(tc.s), tc.noHooks) + + if d := cmp.Diff(tc.want, string(got)); d != "" { + t.Errorf("unexpected diff: %s", d) + } + }) + } +} diff --git a/cmd/helpers.go b/cmd/helpers.go index 07f472f7..6379a104 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -1,38 +1,21 @@ package cmd import ( + "errors" "fmt" "os" "os/exec" "path/filepath" "strings" - flag "github.com/spf13/pflag" "k8s.io/client-go/util/homedir" - "k8s.io/helm/pkg/helm" - helm_env "k8s.io/helm/pkg/helm/environment" - "k8s.io/helm/pkg/tlsutil" -) - -const ( - tlsCaCertDefault = "$HELM_HOME/ca.pem" - tlsCertDefault = "$HELM_HOME/cert.pem" - tlsKeyDefault = "$HELM_HOME/key.pem" - - helm2TestSuccessHook = "test-success" - helm3TestHook = "test" ) var ( - settings helm_env.EnvSettings // DefaultHelmHome to hold default home path of .helm dir DefaultHelmHome = filepath.Join(homedir.HomeDir(), ".helm") ) -func isHelm3() bool { - return os.Getenv("TILLER_HOST") == "" -} - func isDebug() bool { return os.Getenv("HELM_DEBUG") == "true" } @@ -42,51 +25,11 @@ func debugPrint(format string, a ...interface{}) { } } -func addCommonCmdOptions(f *flag.FlagSet) { - settings.AddFlagsTLS(f) - settings.InitTLS(f) - - f.StringVar((*string)(&settings.Home), "home", DefaultHelmHome, "location of your Helm config. Overrides $HELM_HOME") -} - -func createHelmClient() helm.Interface { - options := []helm.Option{helm.Host(os.Getenv("TILLER_HOST")), helm.ConnectTimeout(int64(30))} - - if settings.TLSVerify || settings.TLSEnable { - tlsopts := tlsutil.Options{ - ServerName: settings.TLSServerName, - KeyFile: settings.TLSKeyFile, - CertFile: settings.TLSCertFile, - InsecureSkipVerify: true, - } - - if settings.TLSVerify { - tlsopts.CaCertFile = settings.TLSCaCertFile - tlsopts.InsecureSkipVerify = false - } - - tlscfg, err := tlsutil.ClientConfig(tlsopts) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) - } - - options = append(options, helm.WithTLS(tlscfg)) - } - - return helm.NewClient(options...) -} - -func expandTLSPaths() { - settings.TLSCaCertFile = os.ExpandEnv(settings.TLSCaCertFile) - settings.TLSCertFile = os.ExpandEnv(settings.TLSCertFile) - settings.TLSKeyFile = os.ExpandEnv(settings.TLSKeyFile) -} - func outputWithRichError(cmd *exec.Cmd) ([]byte, error) { debugPrint("Executing %s", strings.Join(cmd.Args, " ")) output, err := cmd.Output() - if exitError, ok := err.(*exec.ExitError); ok { + var exitError *exec.ExitError + if errors.As(err, &exitError) { return output, fmt.Errorf("%s: %s", exitError.Error(), string(exitError.Stderr)) } return output, err diff --git a/cmd/helpers_test.go b/cmd/helpers_test.go new file mode 100644 index 00000000..a0eb1e72 --- /dev/null +++ b/cmd/helpers_test.go @@ -0,0 +1,142 @@ +package cmd + +import ( + "bytes" + "io" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/require" +) + +func captureStdout(f func()) (string, error) { + old := os.Stdout + r, w, err := os.Pipe() + if err != nil { + return "", err + } + os.Stdout = w + + defer func() { + os.Stdout = old + }() + + f() + + w.Close() + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + if err != nil { + return "", err + } + return buf.String(), nil +} + +func TestCaptureStdout(t *testing.T) { + output, err := captureStdout(func() { + _, _ = os.Stdout.Write([]byte("test")) + }) + require.NoError(t, err) + require.Equal(t, "test", output) +} + +func TestIsDebug(t *testing.T) { + tests := []struct { + name string + envValue string + expected bool + }{ + { + name: "HELM_DEBUG is true", + envValue: "true", + expected: true, + }, + { + name: "HELM_DEBUG is false", + envValue: "false", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("HELM_DEBUG", tt.envValue) + require.Equalf(t, tt.expected, isDebug(), "Expected %v but got %v", tt.expected, isDebug()) + }) + } +} + +func TestDebugPrint(t *testing.T) { + tests := []struct { + name string + envValue string + expected string + }{ + { + name: "non-empty when HELM_DEBUG is true", + envValue: "true", + expected: "test\n", + }, + { + name: "empty when HELM_DEBUG is false", + envValue: "false", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("HELM_DEBUG", tt.envValue) + output, err := captureStdout(func() { + debugPrint("test") + }) + require.NoError(t, err) + require.Equalf(t, tt.expected, output, "Expected %v but got %v", tt.expected, output) + }) + } +} + +func TestOutputWithRichError(t *testing.T) { + tests := []struct { + name string + envValue string + cmd *exec.Cmd + expected string + expectedStdout string + }{ + { + name: "debug output in stdout when HELM_DEBUG is true", + envValue: "true", + cmd: exec.Command("echo", "test1"), + expected: "test1\n", + expectedStdout: "Executing echo test1\n", + }, + { + name: "non-debug output in stdout when HELM_DEBUG is false", + envValue: "false", + cmd: exec.Command("echo", "test2"), + expected: "test2\n", + expectedStdout: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("HELM_DEBUG", tt.envValue) + var ( + stdoutString string + outBytes []byte + funcErr, captureErr error + ) + stdoutString, captureErr = captureStdout(func() { + outBytes, funcErr = outputWithRichError(tt.cmd) + }) + require.NoError(t, captureErr) + require.NoError(t, funcErr) + require.Equalf(t, tt.expected, string(outBytes), "Expected %v but got %v", tt.expected, string(outBytes)) + require.Equalf(t, tt.expectedStdout, stdoutString, "Expected %v but got %v", tt.expectedStdout, stdoutString) + }) + } +} diff --git a/cmd/options.go b/cmd/options.go new file mode 100644 index 00000000..502b73d7 --- /dev/null +++ b/cmd/options.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "github.com/spf13/pflag" + + "github.com/databus23/helm-diff/v3/diff" +) + +// AddDiffOptions adds flags for the various consolidated options to the functions in the diff package +func AddDiffOptions(f *pflag.FlagSet, o *diff.Options) { + f.BoolP("suppress-secrets", "q", false, "suppress secrets in the output") + f.BoolVar(&o.ShowSecrets, "show-secrets", false, "do not redact secret values in the output") + f.BoolVar(&o.ShowSecretsDecoded, "show-secrets-decoded", false, "decode secret values in the output") + f.StringArrayVar(&o.SuppressedKinds, "suppress", []string{}, "allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service')") + f.IntVarP(&o.OutputContext, "context", "C", -1, "output NUM lines of context around changes") + f.StringVar(&o.OutputFormat, "output", "diff", "Possible values: diff, simple, template, dyff. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.") + f.BoolVar(&o.StripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input") + f.Float32VarP(&o.FindRenames, "find-renames", "D", 0, "Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched") + f.StringArrayVar(&o.SuppressedOutputLineRegex, "suppress-output-line-regex", []string{}, "a regex to suppress diff output lines that match") +} + +// ProcessDiffOptions processes the set flags and handles possible interactions between them +func ProcessDiffOptions(f *pflag.FlagSet, o *diff.Options) { + if q, _ := f.GetBool("suppress-secrets"); q { + o.SuppressedKinds = append(o.SuppressedKinds, "Secret") + } +} diff --git a/cmd/release.go b/cmd/release.go index 1a4a6492..f0286e58 100644 --- a/cmd/release.go +++ b/cmd/release.go @@ -4,29 +4,25 @@ import ( "errors" "fmt" "os" + "strings" "github.com/spf13/cobra" - "k8s.io/helm/pkg/helm" "github.com/databus23/helm-diff/v3/diff" "github.com/databus23/helm-diff/v3/manifest" ) type release struct { - client helm.Interface detailedExitCode bool - suppressedKinds []string releases []string - outputContext int includeTests bool - showSecrets bool - output string - stripTrailingCR bool normalizeManifests bool + diff.Options } const releaseCmdLongUsage = ` -This command compares the manifests details of a different releases created from the same chart +This command compares the manifests details of a different releases created from the same chart. +The release name may be specified using namespace/release syntax. It can be used to compare the manifests of @@ -34,6 +30,7 @@ It can be used to compare the manifests of $ helm diff release [flags] release1 release2 Example: $ helm diff release my-prod my-stage + $ helm diff release prod/my-prod stage/my-stage ` func releaseCmd() *cobra.Command { @@ -42,9 +39,6 @@ func releaseCmd() *cobra.Command { Use: "release [flags] RELEASE release1 [release2]", Short: "Shows diff between release's manifests", Long: releaseCmdLongUsage, - PreRun: func(*cobra.Command, []string) { - expandTLSPaths() - }, RunE: func(cmd *cobra.Command, args []string) error { // Suppress the command usage on error. See #77 for more info cmd.SilenceUsage = true @@ -59,73 +53,64 @@ func releaseCmd() *cobra.Command { return errors.New("Too few arguments to Command \"release\".\nMinimum 2 arguments required: release name-1, release name-2") } - if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { - diff.suppressedKinds = append(diff.suppressedKinds, "Secret") - } + ProcessDiffOptions(cmd.Flags(), &diff.Options) diff.releases = args[0:] - if isHelm3() { - return diff.differentiateHelm3() - } - if diff.client == nil { - diff.client = createHelmClient() - } - return diff.differentiate() + return diff.differentiateHelm3() }, } - releaseCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") - releaseCmd.Flags().BoolVar(&diff.showSecrets, "show-secrets", false, "do not redact secret values in the output") releaseCmd.Flags().BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") - releaseCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") - releaseCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - releaseCmd.Flags().StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.") - releaseCmd.Flags().BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input") releaseCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") + AddDiffOptions(releaseCmd.Flags(), &diff.Options) releaseCmd.SuggestionsMinimumDistance = 1 - if !isHelm3() { - addCommonCmdOptions(releaseCmd.Flags()) - } - return releaseCmd } func (d *release) differentiateHelm3() error { - namespace := os.Getenv("HELM_NAMESPACE") - excludes := []string{helm3TestHook, helm2TestSuccessHook} + excludes := []string{manifest.Helm3TestHook, manifest.Helm2TestSuccessHook} if d.includeTests { excludes = []string{} } - releaseResponse1, err := getRelease(d.releases[0], namespace) + + namespace1 := os.Getenv("HELM_NAMESPACE") + release1 := d.releases[0] + if strings.Contains(release1, "/") { + namespace1 = strings.Split(release1, "/")[0] + release1 = strings.Split(release1, "/")[1] + } + releaseResponse1, err := getRelease(release1, namespace1) if err != nil { return err } - releaseChart1, err := getChart(d.releases[0], namespace) + releaseChart1, err := getChart(release1, namespace1) if err != nil { return err } - releaseResponse2, err := getRelease(d.releases[1], namespace) + namespace2 := os.Getenv("HELM_NAMESPACE") + release2 := d.releases[1] + if strings.Contains(release2, "/") { + namespace2 = strings.Split(release2, "/")[0] + release2 = strings.Split(release2, "/")[1] + } + releaseResponse2, err := getRelease(release2, namespace2) if err != nil { return err } - releaseChart2, err := getChart(d.releases[1], namespace) + releaseChart2, err := getChart(release2, namespace2) if err != nil { return err } if releaseChart1 == releaseChart2 { seenAnyChanges := diff.Releases( - manifest.Parse(string(releaseResponse1), namespace, d.normalizeManifests, excludes...), - manifest.Parse(string(releaseResponse2), namespace, d.normalizeManifests, excludes...), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, + manifest.Parse(string(releaseResponse1), namespace1, d.normalizeManifests, excludes...), + manifest.Parse(string(releaseResponse2), namespace2, d.normalizeManifests, excludes...), + &d.Options, os.Stdout) if d.detailedExitCode && seenAnyChanges { @@ -139,38 +124,3 @@ func (d *release) differentiateHelm3() error { } return nil } - -func (d *release) differentiate() error { - - releaseResponse1, err := d.client.ReleaseContent(d.releases[0]) - if err != nil { - return prettyError(err) - } - - releaseResponse2, err := d.client.ReleaseContent(d.releases[1]) - if err != nil { - return prettyError(err) - } - - if releaseResponse1.Release.Chart.Metadata.Name == releaseResponse2.Release.Chart.Metadata.Name { - seenAnyChanges := diff.Releases( - manifest.ParseRelease(releaseResponse1.Release, d.includeTests, d.normalizeManifests), - manifest.ParseRelease(releaseResponse2.Release, d.includeTests, d.normalizeManifests), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, - os.Stdout) - - if d.detailedExitCode && seenAnyChanges { - return Error{ - error: errors.New("identified at least one change, exiting with non-zero exit code (detailed-exitcode parameter enabled)"), - Code: 2, - } - } - } else { - fmt.Printf("Error : Incomparable Releases \n Unable to compare releases from two different charts \"%s\", \"%s\". \n try helm diff release --help to know more \n", releaseResponse1.Release.Chart.Metadata.Name, releaseResponse2.Release.Chart.Metadata.Name) - } - return nil -} diff --git a/cmd/revision.go b/cmd/revision.go index 59b29b7d..8599789b 100644 --- a/cmd/revision.go +++ b/cmd/revision.go @@ -7,7 +7,6 @@ import ( "strconv" "github.com/spf13/cobra" - "k8s.io/helm/pkg/helm" "github.com/databus23/helm-diff/v3/diff" "github.com/databus23/helm-diff/v3/manifest" @@ -15,16 +14,11 @@ import ( type revision struct { release string - client helm.Interface detailedExitCode bool - suppressedKinds []string revisions []string - outputContext int includeTests bool - showSecrets bool - output string - stripTrailingCR bool normalizeManifests bool + diff.Options } const revisionCmdLongUsage = ` @@ -32,7 +26,7 @@ This command compares the manifests details of a named release. It can be used to compare the manifests of - - lastest REVISION with specified REVISION + - latest REVISION with specified REVISION $ helm diff revision [flags] RELEASE REVISION1 Example: $ helm diff revision my-release 2 @@ -49,9 +43,6 @@ func revisionCmd() *cobra.Command { Use: "revision [flags] RELEASE REVISION1 [REVISION2]", Short: "Shows diff between revision's manifests", Long: revisionCmdLongUsage, - PreRun: func(*cobra.Command, []string) { - expandTLSPaths() - }, RunE: func(cmd *cobra.Command, args []string) error { // Suppress the command usage on error. See #77 for more info cmd.SilenceUsage = true @@ -68,44 +59,27 @@ func revisionCmd() *cobra.Command { return errors.New("Too many arguments to Command \"revision\".\nMaximum 3 arguments allowed: release name, revision1, revision2") } - if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { - diff.suppressedKinds = append(diff.suppressedKinds, "Secret") - } + ProcessDiffOptions(cmd.Flags(), &diff.Options) diff.release = args[0] diff.revisions = args[1:] - if isHelm3() { - return diff.differentiateHelm3() - } - if diff.client == nil { - diff.client = createHelmClient() - } - return diff.differentiate() + return diff.differentiateHelm3() }, } - revisionCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") - revisionCmd.Flags().BoolVar(&diff.showSecrets, "show-secrets", false, "do not redact secret values in the output") revisionCmd.Flags().BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") - revisionCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") - revisionCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - revisionCmd.Flags().StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.") - revisionCmd.Flags().BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input") revisionCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") + AddDiffOptions(revisionCmd.Flags(), &diff.Options) revisionCmd.SuggestionsMinimumDistance = 1 - if !isHelm3() { - addCommonCmdOptions(revisionCmd.Flags()) - } - return revisionCmd } func (d *revision) differentiateHelm3() error { namespace := os.Getenv("HELM_NAMESPACE") - excludes := []string{helm3TestHook, helm2TestSuccessHook} + excludes := []string{manifest.Helm3TestHook, manifest.Helm2TestSuccessHook} if d.includeTests { excludes = []string{} } @@ -126,11 +100,7 @@ func (d *revision) differentiateHelm3() error { diff.Manifests( manifest.Parse(string(revisionResponse), namespace, d.normalizeManifests, excludes...), manifest.Parse(string(releaseResponse), namespace, d.normalizeManifests, excludes...), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, + &d.Options, os.Stdout) case 2: @@ -142,89 +112,18 @@ func (d *revision) differentiateHelm3() error { revisionResponse1, err := getRevision(d.release, revision1, namespace) if err != nil { - return prettyError(err) + return err } revisionResponse2, err := getRevision(d.release, revision2, namespace) if err != nil { - return prettyError(err) + return err } seenAnyChanges := diff.Manifests( manifest.Parse(string(revisionResponse1), namespace, d.normalizeManifests, excludes...), manifest.Parse(string(revisionResponse2), namespace, d.normalizeManifests, excludes...), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, - os.Stdout) - - if d.detailedExitCode && seenAnyChanges { - return Error{ - error: errors.New("identified at least one change, exiting with non-zero exit code (detailed-exitcode parameter enabled)"), - Code: 2, - } - } - - default: - return errors.New("Invalid Arguments") - } - - return nil -} - -func (d *revision) differentiate() error { - - switch len(d.revisions) { - case 1: - releaseResponse, err := d.client.ReleaseContent(d.release) - - if err != nil { - return prettyError(err) - } - - revision, _ := strconv.Atoi(d.revisions[0]) - revisionResponse, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision))) - if err != nil { - return prettyError(err) - } - - diff.Manifests( - manifest.ParseRelease(revisionResponse.Release, d.includeTests, d.normalizeManifests), - manifest.ParseRelease(releaseResponse.Release, d.includeTests, d.normalizeManifests), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, - os.Stdout) - - case 2: - revision1, _ := strconv.Atoi(d.revisions[0]) - revision2, _ := strconv.Atoi(d.revisions[1]) - if revision1 > revision2 { - revision1, revision2 = revision2, revision1 - } - - revisionResponse1, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision1))) - if err != nil { - return prettyError(err) - } - - revisionResponse2, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision2))) - if err != nil { - return prettyError(err) - } - - seenAnyChanges := diff.Manifests( - manifest.ParseRelease(revisionResponse1.Release, d.includeTests, d.normalizeManifests), - manifest.ParseRelease(revisionResponse2.Release, d.includeTests, d.normalizeManifests), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, + &d.Options, os.Stdout) if d.detailedExitCode && seenAnyChanges { diff --git a/cmd/rollback.go b/cmd/rollback.go index 9d46050c..6f8d6d17 100644 --- a/cmd/rollback.go +++ b/cmd/rollback.go @@ -7,7 +7,6 @@ import ( "strconv" "github.com/spf13/cobra" - "k8s.io/helm/pkg/helm" "github.com/databus23/helm-diff/v3/diff" "github.com/databus23/helm-diff/v3/manifest" @@ -15,16 +14,11 @@ import ( type rollback struct { release string - client helm.Interface detailedExitCode bool - suppressedKinds []string revisions []string - outputContext int includeTests bool - showSecrets bool - output string - stripTrailingCR bool normalizeManifests bool + diff.Options } const rollbackCmdLongUsage = ` @@ -41,9 +35,6 @@ func rollbackCmd() *cobra.Command { Short: "Show a diff explaining what a helm rollback could perform", Long: rollbackCmdLongUsage, Example: " helm diff rollback my-release 2", - PreRun: func(*cobra.Command, []string) { - expandTLSPaths() - }, RunE: func(cmd *cobra.Command, args []string) error { // Suppress the command usage on error. See #77 for more info cmd.SilenceUsage = true @@ -57,47 +48,28 @@ func rollbackCmd() *cobra.Command { return err } - if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { - diff.suppressedKinds = append(diff.suppressedKinds, "Secret") - } + ProcessDiffOptions(cmd.Flags(), &diff.Options) diff.release = args[0] diff.revisions = args[1:] - if isHelm3() { - return diff.backcastHelm3() - } - - if diff.client == nil { - diff.client = createHelmClient() - } - - return diff.backcast() + return diff.backcastHelm3() }, } - rollbackCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") - rollbackCmd.Flags().BoolVar(&diff.showSecrets, "show-secrets", false, "do not redact secret values in the output") rollbackCmd.Flags().BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") - rollbackCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") - rollbackCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - rollbackCmd.Flags().StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.") - rollbackCmd.Flags().BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input") rollbackCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") + AddDiffOptions(rollbackCmd.Flags(), &diff.Options) rollbackCmd.SuggestionsMinimumDistance = 1 - if !isHelm3() { - addCommonCmdOptions(rollbackCmd.Flags()) - } - return rollbackCmd } func (d *rollback) backcastHelm3() error { namespace := os.Getenv("HELM_NAMESPACE") - excludes := []string{helm3TestHook, helm2TestSuccessHook} + excludes := []string{manifest.Helm3TestHook, manifest.Helm2TestSuccessHook} if d.includeTests { excludes = []string{} } @@ -119,48 +91,7 @@ func (d *rollback) backcastHelm3() error { seenAnyChanges := diff.Manifests( manifest.Parse(string(releaseResponse), namespace, d.normalizeManifests, excludes...), manifest.Parse(string(revisionResponse), namespace, d.normalizeManifests, excludes...), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, - os.Stdout) - - if d.detailedExitCode && seenAnyChanges { - return Error{ - error: errors.New("identified at least one change, exiting with non-zero exit code (detailed-exitcode parameter enabled)"), - Code: 2, - } - } - - return nil -} - -func (d *rollback) backcast() error { - - // get manifest of the latest release - releaseResponse, err := d.client.ReleaseContent(d.release) - - if err != nil { - return prettyError(err) - } - - // get manifest of the release to rollback - revision, _ := strconv.Atoi(d.revisions[0]) - revisionResponse, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision))) - if err != nil { - return prettyError(err) - } - - // create a diff between the current manifest and the version of the manifest that a user is intended to rollback - seenAnyChanges := diff.Manifests( - manifest.ParseRelease(releaseResponse.Release, d.includeTests, d.normalizeManifests), - manifest.ParseRelease(revisionResponse.Release, d.includeTests, d.normalizeManifests), - d.suppressedKinds, - d.showSecrets, - d.outputContext, - d.output, - d.stripTrailingCR, + &d.Options, os.Stdout) if d.detailedExitCode && seenAnyChanges { diff --git a/cmd/root.go b/cmd/root.go index 0c71f2db..b688f7b0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,10 +3,12 @@ package cmd import ( "os" "strconv" + "strings" + "github.com/gonvenience/bunt" "github.com/mgutz/ansi" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) const rootCmdLongUsage = ` @@ -14,24 +16,23 @@ The Helm Diff Plugin * Shows a diff explaining what a helm upgrade would change: This fetches the currently deployed version of a release - and compares it to a local chart plus values. This can be - used visualize what changes a helm upgrade will perform. + and compares it to a local chart plus values. This can be + used to visualize what changes a helm upgrade will perform. * Shows a diff explaining what had changed between two revisions: This fetches previously deployed versions of a release - and compares them. This can be used visualize what changes + and compares them. This can be used to visualize what changes were made during revision change. * Shows a diff explaining what a helm rollback would change: This fetches the currently deployed version of a release - and compares it to the previously deployed version of the release, that you - want to rollback. This can be used visualize what changes a + and compares it to the previously deployed version of the release, that you + want to rollback. This can be used to visualize what changes a helm rollback will perform. ` // New creates a new cobra client func New() *cobra.Command { - chartCommand := newChartCommand() cmd := &cobra.Command{ @@ -54,15 +55,26 @@ func New() *cobra.Command { } } + if !cmd.Flags().Changed("output") { + v, set := os.LookupEnv("HELM_DIFF_OUTPUT") + if set && strings.TrimSpace(v) != "" { + _ = cmd.Flags().Set("output", v) + } + } + + // Dyff relies on bunt, default to color=on + bunt.SetColorSettings(bunt.ON, bunt.ON) nc, _ := cmd.Flags().GetBool("no-color") if nc || (fc != nil && !*fc) { ansi.DisableColors(true) + bunt.SetColorSettings(bunt.OFF, bunt.OFF) } else if !cmd.Flags().Changed("no-color") && fc == nil { - term := terminal.IsTerminal(int(os.Stdout.Fd())) + term := term.IsTerminal(int(os.Stdout.Fd())) // https://github.com/databus23/helm-diff/issues/281 dumb := os.Getenv("TERM") == "dumb" ansi.DisableColors(!term || dumb) + bunt.SetColorSettings(bunt.OFF, bunt.OFF) } }, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/upgrade.go b/cmd/upgrade.go index ebb6aba1..48945cf8 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -2,66 +2,77 @@ package cmd import ( "bytes" - "encoding/json" + "errors" "fmt" "log" "os" + "slices" + "strconv" "strings" - jsoniterator "github.com/json-iterator/go" + "github.com/spf13/cobra" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli" - - jsonpatch "github.com/evanphx/json-patch" - "github.com/pkg/errors" "helm.sh/helm/v3/pkg/kube" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/cli-runtime/pkg/resource" - "sigs.k8s.io/yaml" - - "github.com/spf13/cobra" - "k8s.io/helm/pkg/helm" "github.com/databus23/helm-diff/v3/diff" "github.com/databus23/helm-diff/v3/manifest" ) +var ( + validDryRunValues = []string{"server", "client", "true", "false"} +) + +const ( + dryRunNoOptDefVal = "client" +) + type diffCmd struct { release string chart string chartVersion string chartRepo string - client helm.Interface detailedExitCode bool devel bool disableValidation bool disableOpenAPIValidation bool - dryRun bool + enableDNS bool + SkipSchemaValidation bool namespace string // namespace to assume the release to be installed into. Defaults to the current kube config namespace. valueFiles valueFiles values []string stringValues []string + stringLiteralValues []string + jsonValues []string fileValues []string reuseValues bool resetValues bool + resetThenReuseValues bool allowUnreleased bool noHooks bool includeTests bool - suppressedKinds []string - outputContext int - showSecrets bool + includeCRDs bool postRenderer string - output string + postRendererArgs []string + insecureSkipTLSVerify bool install bool - stripTrailingCR bool normalizeManifests bool + takeOwnership bool threeWayMerge bool extraAPIs []string + kubeVersion string useUpgradeDryRun bool + diff.Options + + // dryRunMode can take the following values: + // - "none": no dry run is performed + // - "client": dry run is performed without remote cluster access + // - "server": dry run is performed with remote cluster access + // - "true": same as "client" + // - "false": same as "none" + dryRunMode string } func (d *diffCmd) isAllowUnreleased() bool { @@ -71,21 +82,41 @@ func (d *diffCmd) isAllowUnreleased() bool { return d.allowUnreleased || d.install } +// clusterAccessAllowed returns true if the diff command is allowed to access the cluster at some degree. +// +// helm-diff basically have 2 modes of operation: +// 1. without cluster access at all when --dry-run=true or --dry-run=client is specified. +// 2. with cluster access when --dry-run is unspecified, false, or server. +// +// clusterAccessAllowed returns true when the mode is either 2 or 3. +// +// If false, helm-diff should not access the cluster at all. +// More concretely: +// - It shouldn't pass --validate to helm-template because it requires cluster access. +// - It shouldn't get the current release manifest using helm-get-manifest because it requires cluster access. +// - It shouldn't get the current release hooks using helm-get-hooks because it requires cluster access. +// - It shouldn't get the current release values using helm-get-values because it requires cluster access. +// +// See also https://github.com/helm/helm/pull/9426#discussion_r1181397259 +func (d *diffCmd) clusterAccessAllowed() bool { + return d.dryRunMode == "none" || d.dryRunMode == "false" || d.dryRunMode == "server" +} + const globalUsage = `Show a diff explaining what a helm upgrade would change. This fetches the currently deployed version of a release and compares it to a chart plus values. -This can be used visualize what changes a helm upgrade will +This can be used to visualize what changes a helm upgrade will perform. ` var envSettings = cli.New() -var yamlSeperator = []byte("\n---\n") func newChartCommand() *cobra.Command { diff := diffCmd{ namespace: os.Getenv("HELM_NAMESPACE"), } + unknownFlags := os.Getenv("HELM_DIFF_IGNORE_UNKNOWN_FLAGS") == "true" cmd := &cobra.Command{ Use: "upgrade [flags] [RELEASE] [CHART]", @@ -102,21 +133,35 @@ func newChartCommand() *cobra.Command { " # Set HELM_DIFF_USE_UPGRADE_DRY_RUN=true to", " # use `helm upgrade --dry-run` instead of `helm template` to render manifests from the chart.", " # See https://github.com/databus23/helm-diff/issues/253 for more information.", - " HELM_DIFF_USE_UPGRADE_DRY_RUN=true helm diff upgarde my-release datadog/datadog", + " HELM_DIFF_USE_UPGRADE_DRY_RUN=true helm diff upgrade my-release datadog/datadog", "", " # Set HELM_DIFF_THREE_WAY_MERGE=true to", " # enable the three-way-merge on diff.", " # This is equivalent to specifying the --three-way-merge flag.", " # Read the flag usage below for more information on --three-way-merge.", - " HELM_DIFF_THREE_WAY_MERGE=true helm diff upgarde my-release datadog/datadog", + " HELM_DIFF_THREE_WAY_MERGE=true helm diff upgrade my-release datadog/datadog", + "", + " # Set HELM_DIFF_NORMALIZE_MANIFESTS=true to", + " # normalize the yaml file content when using helm diff.", + " # This is equivalent to specifying the --normalize-manifests flag.", + " # Read the flag usage below for more information on --normalize-manifests.", + " HELM_DIFF_NORMALIZE_MANIFESTS=true helm diff upgrade my-release datadog/datadog", + "", + "# Set HELM_DIFF_OUTPUT_CONTEXT=n to configure the output context to n lines.", + "# This is equivalent to specifying the --context flag.", + "# Read the flag usage below for more information on --context.", + "HELM_DIFF_OUTPUT_CONTEXT=5 helm diff upgrade my-release datadog/datadog", }, "\n"), Args: func(cmd *cobra.Command, args []string) error { return checkArgsLength(len(args), "release name", "chart path") }, - PreRun: func(*cobra.Command, []string) { - expandTLSPaths() - }, RunE: func(cmd *cobra.Command, args []string) error { + if diff.dryRunMode == "" { + diff.dryRunMode = "none" + } else if !slices.Contains(validDryRunValues, diff.dryRunMode) { + return fmt.Errorf("flag %q must take a bool value or either %q or %q, but got %q", "dry-run", "client", "server", diff.dryRunMode) + } + // Suppress the command usage on error. See #77 for more info cmd.SilenceUsage = true @@ -128,26 +173,37 @@ func newChartCommand() *cobra.Command { diff.threeWayMerge = enabled if enabled { - fmt.Println("Enabled three way merge via the envvar") + fmt.Fprintf(os.Stderr, "Enabled three way merge via the envvar\n") } } - if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { - diff.suppressedKinds = append(diff.suppressedKinds, "Secret") + if !diff.normalizeManifests && !cmd.Flags().Changed("normalize-manifests") { + enabled := os.Getenv("HELM_DIFF_NORMALIZE_MANIFESTS") == "true" + diff.normalizeManifests = enabled + + if enabled { + fmt.Fprintf(os.Stderr, "Enabled normalize manifests via the envvar\n") + } } + if diff.OutputContext == -1 && !cmd.Flags().Changed("context") { + contextEnvVar := os.Getenv("HELM_DIFF_OUTPUT_CONTEXT") + if contextEnvVar != "" { + context, err := strconv.Atoi(contextEnvVar) + if err == nil { + diff.OutputContext = context + } + } + } + + ProcessDiffOptions(cmd.Flags(), &diff.Options) + diff.release = args[0] diff.chart = args[1] - if isHelm3() { - return diff.runHelm3() - } - if diff.client == nil { - diff.client = createHelmClient() - } - return diff.run() + return diff.runHelm3() }, FParseErrWhitelist: cobra.FParseErrWhitelist{ - UnknownFlags: os.Getenv("HELM_DIFF_IGNORE_UNKNOWN_FLAGS") == "true", + UnknownFlags: unknownFlags, }, } @@ -163,42 +219,42 @@ func newChartCommand() *cobra.Command { // - https://github.com/helm/helm/blob/d9ffe37d371c9d06448c55c852c800051830e49a/cmd/helm/template.go#L184 // - https://github.com/databus23/helm-diff/issues/318 f.StringArrayVarP(&diff.extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") - f.BoolP("suppress-secrets", "q", false, "suppress secrets in the output") - f.BoolVar(&diff.showSecrets, "show-secrets", false, "do not redact secret values in the output") + // Support for kube-version was re-enabled and ported from helm2 to helm3 on https://github.com/helm/helm/pull/9040 + f.StringVar(&diff.kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion") f.VarP(&diff.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.StringArrayVar(&diff.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&diff.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringArrayVar(&diff.stringLiteralValues, "set-literal", []string{}, "set STRING literal values on the command line") + f.StringArrayVar(&diff.jsonValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)") f.StringArrayVar(&diff.fileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") f.BoolVar(&diff.reuseValues, "reuse-values", false, "reuse the last release's values and merge in any new values. If '--reset-values' is specified, this is ignored") f.BoolVar(&diff.resetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values") + f.BoolVar(&diff.resetThenReuseValues, "reset-then-reuse-values", false, "reset the values to the ones built into the chart, apply the last release's values and merge in any new values. If '--reset-values' or '--reuse-values' is specified, this is ignored") f.BoolVar(&diff.allowUnreleased, "allow-unreleased", false, "enables diffing of releases that are not yet deployed via Helm") f.BoolVar(&diff.install, "install", false, "enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match \"helm upgrade --install\" command") f.BoolVar(&diff.noHooks, "no-hooks", false, "disable diffing of hooks") f.BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + f.BoolVar(&diff.includeCRDs, "include-crds", false, "include CRDs in the diffing") f.BoolVar(&diff.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") - f.StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") - f.IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") f.BoolVar(&diff.disableValidation, "disable-validation", false, "disables rendered templates validation against the Kubernetes cluster you are currently pointing to. This is the same validation performed on an install") f.BoolVar(&diff.disableOpenAPIValidation, "disable-openapi-validation", false, "disables rendered templates validation against the Kubernetes OpenAPI Schema") - f.BoolVar(&diff.dryRun, "dry-run", false, "disables cluster access and show diff as if it was install. Implies --install, --reset-values, and --disable-validation") + f.StringVar(&diff.dryRunMode, "dry-run", "", "--dry-run, --dry-run=client, or --dry-run=true disables cluster access and show diff as if it was install. Implies --install, --reset-values, and --disable-validation."+ + " --dry-run=server enables the cluster access with helm-get and the lookup template function.") + f.Lookup("dry-run").NoOptDefVal = dryRunNoOptDefVal + f.BoolVar(&diff.enableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") + f.BoolVar(&diff.SkipSchemaValidation, "skip-schema-validation", false, "skip validation of the rendered manifests against the Kubernetes OpenAPI schema") f.StringVar(&diff.postRenderer, "post-renderer", "", "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path") - f.StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, json, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.") - f.BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input") + f.StringArrayVar(&diff.postRendererArgs, "post-renderer-args", []string{}, "an argument to the post-renderer (can specify multiple)") + f.BoolVar(&diff.insecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") f.BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") - if !isHelm3() { - f.StringVar(&diff.namespace, "namespace", "default", "namespace to assume the release to be installed into") - } + f.BoolVar(&diff.takeOwnership, "take-ownership", false, "if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources") - if !isHelm3() { - addCommonCmdOptions(f) - } + AddDiffOptions(f, &diff.Options) return cmd - } func (d *diffCmd) runHelm3() error { - if err := compatibleHelm3Version(); err != nil { return err } @@ -207,52 +263,56 @@ func (d *diffCmd) runHelm3() error { var err error - if !d.dryRun { + if d.takeOwnership { + // We need to do a three way merge between the manifests of the new + // release, the manifests of the old release and what is currently deployed + d.threeWayMerge = true + } + + if d.clusterAccessAllowed() { releaseManifest, err = getRelease(d.release, d.namespace) } var newInstall bool if err != nil && strings.Contains(err.Error(), "release: not found") { if d.isAllowUnreleased() { - fmt.Printf("********************\n\n\tRelease was not present in Helm. Diff will show entire contents as new.\n\n********************\n") newInstall = true err = nil } else { - fmt.Printf("********************\n\n\tRelease was not present in Helm. Include the `--allow-unreleased` to perform diff without exiting in error.\n\n********************\n") + fmt.Fprintf(os.Stderr, "********************\n\n\tRelease was not present in Helm. Include the `--allow-unreleased` to perform diff without exiting in error.\n\n********************\n") return err } } if err != nil { - return fmt.Errorf("Failed to get release %s in namespace %s: %s", d.release, d.namespace, err) + return fmt.Errorf("Failed to get release %s in namespace %s: %w", d.release, d.namespace, err) } installManifest, err := d.template(!newInstall) if err != nil { - return fmt.Errorf("Failed to render chart: %s", err) + return fmt.Errorf("Failed to render chart: %w", err) } - if d.threeWayMerge { - actionConfig := new(action.Configuration) + var actionConfig *action.Configuration + if d.threeWayMerge || d.takeOwnership { + actionConfig = new(action.Configuration) if err := actionConfig.Init(envSettings.RESTClientGetter(), envSettings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil { log.Fatalf("%+v", err) } if err := actionConfig.KubeClient.IsReachable(); err != nil { return err } - original, err := actionConfig.KubeClient.Build(bytes.NewBuffer(releaseManifest), false) - if err != nil { - return errors.Wrap(err, "unable to build kubernetes objects from original release manifest") - } - target, err := actionConfig.KubeClient.Build(bytes.NewBuffer(installManifest), false) + } + + if d.threeWayMerge { + releaseManifest, installManifest, err = manifest.Generate(actionConfig, releaseManifest, installManifest) if err != nil { - return errors.Wrap(err, "unable to build kubernetes objects from new release manifest") + return fmt.Errorf("unable to generate manifests: %w", err) } - releaseManifest, installManifest, err = genManifest(original, target) } currentSpecs := make(map[string]*manifest.MappingResult) - if !newInstall && !d.dryRun { - if !d.noHooks { + if !newInstall && d.clusterAccessAllowed() { + if !d.noHooks && !d.threeWayMerge { hooks, err := getHooks(d.release, d.namespace) if err != nil { return err @@ -262,16 +322,30 @@ func (d *diffCmd) runHelm3() error { if d.includeTests { currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests) } else { - currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests, helm3TestHook, helm2TestSuccessHook) + currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) } } + + var newOwnedReleases map[string]diff.OwnershipDiff + if d.takeOwnership { + resources, err := actionConfig.KubeClient.Build(bytes.NewBuffer(installManifest), false) + if err != nil { + return err + } + newOwnedReleases, err = checkOwnership(d, resources, currentSpecs) + if err != nil { + return err + } + } + var newSpecs map[string]*manifest.MappingResult if d.includeTests { newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests) } else { - newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests, helm3TestHook, helm2TestSuccessHook) + newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) } - seenAnyChanges := diff.Manifests(currentSpecs, newSpecs, d.suppressedKinds, d.showSecrets, d.outputContext, d.output, d.stripTrailingCR, os.Stdout) + + seenAnyChanges := diff.ManifestsOwnership(currentSpecs, newSpecs, newOwnedReleases, &d.Options, os.Stdout) if d.detailedExitCode && seenAnyChanges { return Error{ @@ -283,299 +357,46 @@ func (d *diffCmd) runHelm3() error { return nil } -func genManifest(original, target kube.ResourceList) ([]byte, []byte, error) { - var err error - releaseManifest, installManifest := make([]byte, 0), make([]byte, 0) - - // to be deleted - targetResources := make(map[string]bool) - for _, r := range target { - targetResources[objectKey(r)] = true - } - for _, r := range original { - if !targetResources[objectKey(r)] { - out, _ := yaml.Marshal(r.Object) - releaseManifest = append(releaseManifest, yamlSeperator...) - releaseManifest = append(releaseManifest, out...) - } - } - - existingResources := make(map[string]bool) - for _, r := range original { - existingResources[objectKey(r)] = true - } - - var toBeCreated kube.ResourceList - for _, r := range target { - if !existingResources[objectKey(r)] { - toBeCreated = append(toBeCreated, r) - } - } - - toBeUpdated, err := existingResourceConflict(toBeCreated) - if err != nil { - return nil, nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update") - } - - _ = toBeUpdated.Visit(func(r *resource.Info, err error) error { - if err != nil { - return err - } - original.Append(r) - return nil - }) - - err = target.Visit(func(info *resource.Info, err error) error { +func checkOwnership(d *diffCmd, resources kube.ResourceList, currentSpecs map[string]*manifest.MappingResult) (map[string]diff.OwnershipDiff, error) { + newOwnedReleases := make(map[string]diff.OwnershipDiff) + err := resources.Visit(func(info *resource.Info, err error) error { if err != nil { return err } - kind := info.Mapping.GroupVersionKind.Kind - // Fetch the current object for the three way merge helper := resource.NewHelper(info.Client, info.Mapping) currentObj, err := helper.Get(info.Namespace, info.Name) if err != nil { if !apierrors.IsNotFound(err) { - return errors.Wrap(err, "could not get information about the resource") + return err } - // to be created - out, _ := yaml.Marshal(info.Object) - installManifest = append(installManifest, yamlSeperator...) - installManifest = append(installManifest, out...) return nil } - // to be updated - out, _ := jsoniterator.ConfigCompatibleWithStandardLibrary.Marshal(currentObj) - pruneObj, err := deleteStatusAndTidyMetadata(out) - if err != nil { - return errors.Wrapf(err, "prune current obj %q with kind %s", info.Name, kind) - } - pruneOut, err := yaml.Marshal(pruneObj) - if err != nil { - return errors.Wrapf(err, "prune current out %q with kind %s", info.Name, kind) - } - releaseManifest = append(releaseManifest, yamlSeperator...) - releaseManifest = append(releaseManifest, pruneOut...) - - originalInfo := original.Get(info) - if originalInfo == nil { - return fmt.Errorf("could not find %q", info.Name) - } - - patch, patchType, err := createPatch(originalInfo.Object, currentObj, info) - if err != nil { - return err - } - - helper.ServerDryRun = true - targetObj, err := helper.Patch(info.Namespace, info.Name, patchType, patch, nil) - if err != nil { - return errors.Wrapf(err, "cannot patch %q with kind %s", info.Name, kind) - } - out, _ = jsoniterator.ConfigCompatibleWithStandardLibrary.Marshal(targetObj) - pruneObj, err = deleteStatusAndTidyMetadata(out) - if err != nil { - return errors.Wrapf(err, "prune current obj %q with kind %s", info.Name, kind) - } - pruneOut, err = yaml.Marshal(pruneObj) - if err != nil { - return errors.Wrapf(err, "prune current out %q with kind %s", info.Name, kind) - } - installManifest = append(installManifest, yamlSeperator...) - installManifest = append(installManifest, pruneOut...) - return nil - }) - - return releaseManifest, installManifest, err -} - -func (d *diffCmd) run() error { - if d.chartVersion == "" && d.devel { - d.chartVersion = ">0.0.0-0" - } - - chartPath, err := locateChartPath(d.chart, d.chartVersion, false, "") - if err != nil { - return err - } - - if err := d.valueFiles.Valid(); err != nil { - return err - } - - rawVals, err := d.vals() - if err != nil { - return err - } - releaseResponse, err := d.client.ReleaseContent(d.release) - - var newInstall bool - if err != nil && strings.Contains(err.Error(), fmt.Sprintf("release: %q not found", d.release)) { - if d.isAllowUnreleased() { - fmt.Printf("********************\n\n\tRelease was not present in Helm. Diff will show entire contents as new.\n\n********************\n") - newInstall = true - err = nil + var result *manifest.MappingResult + var oldRelease string + if d.includeTests { + result, oldRelease, err = manifest.ParseObject(currentObj, d.namespace) } else { - fmt.Printf("********************\n\n\tRelease was not present in Helm. Include the `--allow-unreleased` to perform diff without exiting in error.\n\n********************\n") + result, oldRelease, err = manifest.ParseObject(currentObj, d.namespace, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) } - } - - if err != nil { - return prettyError(err) - } - - var currentSpecs, newSpecs map[string]*manifest.MappingResult - if newInstall { - installResponse, err := d.client.InstallRelease( - chartPath, - d.namespace, - helm.ReleaseName(d.release), - helm.ValueOverrides(rawVals), - helm.InstallDryRun(true), - ) if err != nil { - return prettyError(err) - } - - currentSpecs = make(map[string]*manifest.MappingResult) - newSpecs = manifest.Parse(installResponse.Release.Manifest, installResponse.Release.Namespace, d.normalizeManifests) - } else { - upgradeResponse, err := d.client.UpdateRelease( - d.release, - chartPath, - helm.UpdateValueOverrides(rawVals), - helm.ReuseValues(d.reuseValues), - helm.ResetValues(d.resetValues), - helm.UpgradeDryRun(true), - ) - if err != nil { - return prettyError(err) - } - - if d.noHooks { - currentSpecs = manifest.Parse(releaseResponse.Release.Manifest, releaseResponse.Release.Namespace, d.normalizeManifests) - newSpecs = manifest.Parse(upgradeResponse.Release.Manifest, upgradeResponse.Release.Namespace, d.normalizeManifests) - } else { - currentSpecs = manifest.ParseRelease(releaseResponse.Release, d.includeTests, d.normalizeManifests) - newSpecs = manifest.ParseRelease(upgradeResponse.Release, d.includeTests, d.normalizeManifests) - } - } - - seenAnyChanges := diff.Manifests(currentSpecs, newSpecs, d.suppressedKinds, d.showSecrets, d.outputContext, d.output, d.stripTrailingCR, os.Stdout) - - if d.detailedExitCode && seenAnyChanges { - return Error{ - error: errors.New("identified at least one change, exiting with non-zero exit code (detailed-exitcode parameter enabled)"), - Code: 2, + return err } - } - - return nil -} - -func createPatch(originalObj, currentObj runtime.Object, target *resource.Info) ([]byte, types.PatchType, error) { - oldData, err := json.Marshal(originalObj) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing current configuration") - } - newData, err := json.Marshal(target.Object) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing target configuration") - } - - // Even if currentObj is nil (because it was not found), it will marshal just fine - currentData, err := json.Marshal(currentObj) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing live configuration") - } - // kind := target.Mapping.GroupVersionKind.Kind - // if kind == "Deployment" { - // curr, _ := yaml.Marshal(currentObj) - // fmt.Println(string(curr)) - // } - - // Get a versioned object - versionedObject := kube.AsVersioned(target) - - // Unstructured objects, such as CRDs, may not have an not registered error - // returned from ConvertToVersion. Anything that's unstructured should - // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported - // on objects like CRDs. - _, isUnstructured := versionedObject.(runtime.Unstructured) - - // On newer K8s versions, CRDs aren't unstructured but has this dedicated type - _, isCRD := versionedObject.(*apiextv1.CustomResourceDefinition) - - if isUnstructured || isCRD { - // fall back to generic JSON merge patch - patch, err := jsonpatch.CreateMergePatch(oldData, newData) - return patch, types.MergePatchType, err - } - - patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "unable to create patch metadata from object") - } - - patch, err := strategicpatch.CreateThreeWayMergePatch(oldData, newData, currentData, patchMeta, true) - return patch, types.StrategicMergePatchType, err -} - -func objectKey(r *resource.Info) string { - gvk := r.Object.GetObjectKind().GroupVersionKind() - return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) -} -func existingResourceConflict(resources kube.ResourceList) (kube.ResourceList, error) { - var requireUpdate kube.ResourceList - - err := resources.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err + newRelease := d.namespace + "/" + d.release + if oldRelease == newRelease { + return nil } - helper := resource.NewHelper(info.Client, info.Mapping) - _, err = helper.Get(info.Namespace, info.Name) - if err != nil { - if apierrors.IsNotFound(err) { - return nil - } - return errors.Wrap(err, "could not get information about the resource") + newOwnedReleases[result.Name] = diff.OwnershipDiff{ + OldRelease: oldRelease, + NewRelease: newRelease, } + currentSpecs[result.Name] = result - requireUpdate.Append(info) return nil }) - - return requireUpdate, err -} - -func deleteStatusAndTidyMetadata(obj []byte) (map[string]interface{}, error) { - var objectMap map[string]interface{} - err := jsoniterator.Unmarshal(obj, &objectMap) - if err != nil { - return nil, errors.Wrap(err, "could not unmarshal byte sequence") - } - - delete(objectMap, "status") - - metadata := objectMap["metadata"].(map[string]interface{}) - - delete(metadata, "managedFields") - - // See the below for the goal of this metadata tidy logic. - // https://github.com/databus23/helm-diff/issues/326#issuecomment-1008253274 - if a := metadata["annotations"]; a != nil { - annotations := a.(map[string]interface{}) - delete(annotations, "meta.helm.sh/release-name") - delete(annotations, "meta.helm.sh/release-namespace") - - if len(annotations) == 0 { - delete(metadata, "annotations") - } - } - - return objectMap, nil + return newOwnedReleases, err } diff --git a/cmd/upgrade_test.go b/cmd/upgrade_test.go new file mode 100644 index 00000000..6f6752c5 --- /dev/null +++ b/cmd/upgrade_test.go @@ -0,0 +1,63 @@ +package cmd + +import "testing" + +func TestIsRemoteAccessAllowed(t *testing.T) { + cases := []struct { + name string + cmd diffCmd + expected bool + }{ + { + name: "no flags", + cmd: diffCmd{ + dryRunMode: "none", + }, + expected: true, + }, + { + name: "legacy explicit dry-run=true flag", + cmd: diffCmd{ + dryRunMode: "true", + }, + expected: false, + }, + { + name: "legacy explicit dry-run=false flag", + cmd: diffCmd{ + dryRunMode: "false", + }, + expected: true, + }, + { + name: "legacy empty dry-run flag", + cmd: diffCmd{ + dryRunMode: dryRunNoOptDefVal, + }, + expected: false, + }, + { + name: "server-side dry-run flag", + cmd: diffCmd{ + dryRunMode: "server", + }, + expected: true, + }, + { + name: "client-side dry-run flag", + cmd: diffCmd{ + dryRunMode: "client", + }, + expected: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + actual := tc.cmd.clusterAccessAllowed() + if actual != tc.expected { + t.Errorf("Expected %v, got %v", tc.expected, actual) + } + }) + } +} diff --git a/diff/diff.go b/diff/diff.go index c4634c84..f2b63589 100644 --- a/diff/diff.go +++ b/diff/diff.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "math" + "regexp" "sort" "strings" @@ -18,81 +19,284 @@ import ( "github.com/databus23/helm-diff/v3/manifest" ) +// Options are all the options to be passed to generate a diff +type Options struct { + OutputFormat string + OutputContext int + StripTrailingCR bool + ShowSecrets bool + ShowSecretsDecoded bool + SuppressedKinds []string + FindRenames float32 + SuppressedOutputLineRegex []string +} + +type OwnershipDiff struct { + OldRelease string + NewRelease string +} + // Manifests diff on manifests -func Manifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, showSecrets bool, context int, output string, stripTrailingCR bool, to io.Writer) bool { +func Manifests(oldIndex, newIndex map[string]*manifest.MappingResult, options *Options, to io.Writer) bool { + return ManifestsOwnership(oldIndex, newIndex, nil, options, to) +} + +func ManifestsOwnership(oldIndex, newIndex map[string]*manifest.MappingResult, newOwnedReleases map[string]OwnershipDiff, options *Options, to io.Writer) bool { + seenAnyChanges, report, err := generateReport(oldIndex, newIndex, newOwnedReleases, options) + if err != nil { + panic(err) + } + + report.print(to) + report.clean() + return seenAnyChanges +} + +func ManifestReport(oldIndex, newIndex map[string]*manifest.MappingResult, options *Options) (*Report, error) { + _, report, err := generateReport(oldIndex, newIndex, nil, options) + + return report, err +} + +func generateReport(oldIndex, newIndex map[string]*manifest.MappingResult, newOwnedReleases map[string]OwnershipDiff, options *Options) (bool, *Report, error) { report := Report{} - report.setupReportFormat(output) - seenAnyChanges := false - emptyMapping := &manifest.MappingResult{} + report.setupReportFormat(options.OutputFormat) + var possiblyRemoved []string + + for name, diff := range newOwnedReleases { + diff := diffStrings(diff.OldRelease, diff.NewRelease, true) + report.addEntry(name, options.SuppressedKinds, "", 0, diff, "OWNERSHIP") + } + for _, key := range sortedKeys(oldIndex) { oldContent := oldIndex[key] if newContent, ok := newIndex[key]; ok { - if oldContent.Content != newContent.Content { - // modified - if !showSecrets { - redactSecrets(oldContent, newContent) - } + // modified? + doDiff(&report, key, oldContent, newContent, options) + } else { + possiblyRemoved = append(possiblyRemoved, key) + } + } - diffs := diffMappingResults(oldContent, newContent, stripTrailingCR) - if len(diffs) > 0 { - seenAnyChanges = true + var possiblyAdded []string + for _, key := range sortedKeys(newIndex) { + if _, ok := oldIndex[key]; !ok { + possiblyAdded = append(possiblyAdded, key) + } + } + + removed, added := contentSearch(&report, possiblyRemoved, oldIndex, possiblyAdded, newIndex, options) + + for _, key := range removed { + oldContent := oldIndex[key] + if oldContent.ResourcePolicy != "keep" { + doDiff(&report, key, oldContent, nil, options) + } + } + + for _, key := range added { + newContent := newIndex[key] + doDiff(&report, key, nil, newContent, options) + } + + seenAnyChanges := len(report.Entries) > 0 + + report, err := doSuppress(report, options.SuppressedOutputLineRegex) + + return seenAnyChanges, &report, err +} + +func doSuppress(report Report, suppressedOutputLineRegex []string) (Report, error) { + if len(report.Entries) == 0 || len(suppressedOutputLineRegex) == 0 { + return report, nil + } + + filteredReport := Report{} + filteredReport.format = report.format + filteredReport.Entries = []ReportEntry{} + + var suppressOutputRegexes []*regexp.Regexp + + for _, suppressOutputRegex := range suppressedOutputLineRegex { + regex, err := regexp.Compile(suppressOutputRegex) + if err != nil { + return Report{}, err + } + + suppressOutputRegexes = append(suppressOutputRegexes, regex) + } + + for _, entry := range report.Entries { + var diffs []difflib.DiffRecord + + DIFFS: + for _, diff := range entry.Diffs { + for _, suppressOutputRegex := range suppressOutputRegexes { + if suppressOutputRegex.MatchString(diff.Payload) { + continue DIFFS } - report.addEntry(key, suppressedKinds, oldContent.Kind, context, diffs, "MODIFY") } - } else { - // removed - if !showSecrets { - redactSecrets(oldContent, nil) + diffs = append(diffs, diff) + } + + containsDiff := false + + // Add entry to the report, if diffs are present. + for _, diff := range diffs { + if diff.Delta.String() != " " { + containsDiff = true + break } - diffs := diffMappingResults(oldContent, emptyMapping, stripTrailingCR) - if len(diffs) > 0 { - seenAnyChanges = true - } - report.addEntry(key, suppressedKinds, oldContent.Kind, context, diffs, "REMOVE") } + + diffRecords := []difflib.DiffRecord{} + switch { + case containsDiff: + diffRecords = diffs + case entry.ChangeType == "MODIFY": + entry.ChangeType = "MODIFY_SUPPRESSED" + } + + filteredReport.addEntry(entry.Key, entry.SuppressedKinds, entry.Kind, entry.Context, diffRecords, entry.ChangeType) } - for _, key := range sortedKeys(newIndex) { - newContent := newIndex[key] + return filteredReport, nil +} - if _, ok := oldIndex[key]; !ok { - // added - if !showSecrets { - redactSecrets(nil, newContent) +func actualChanges(diff []difflib.DiffRecord) int { + changes := 0 + for _, record := range diff { + if record.Delta != difflib.Common { + changes++ + } + } + return changes +} + +func contentSearch(report *Report, possiblyRemoved []string, oldIndex map[string]*manifest.MappingResult, possiblyAdded []string, newIndex map[string]*manifest.MappingResult, options *Options) ([]string, []string) { + if options.FindRenames <= 0 { + return possiblyRemoved, possiblyAdded + } + + var removed []string + + for _, removedKey := range possiblyRemoved { + oldContent := oldIndex[removedKey] + var smallestKey string + var smallestFraction float32 = math.MaxFloat32 + for _, addedKey := range possiblyAdded { + newContent := newIndex[addedKey] + if oldContent.Kind != newContent.Kind { + continue } - diffs := diffMappingResults(emptyMapping, newContent, stripTrailingCR) - if len(diffs) > 0 { - seenAnyChanges = true + + switch { + case options.ShowSecretsDecoded: + decodeSecrets(oldContent, newContent) + case !options.ShowSecrets: + redactSecrets(oldContent, newContent) + } + + diff := diffMappingResults(oldContent, newContent, options.StripTrailingCR) + delta := actualChanges(diff) + if delta == 0 || len(diff) == 0 { + continue // Should never happen, but better safe than sorry + } + fraction := float32(delta) / float32(len(diff)) + if fraction > 0 && fraction < smallestFraction { + smallestKey = addedKey + smallestFraction = fraction } - report.addEntry(key, suppressedKinds, newContent.Kind, context, diffs, "ADD") + } + + if smallestFraction < options.FindRenames { + index := sort.SearchStrings(possiblyAdded, smallestKey) + possiblyAdded = append(possiblyAdded[:index], possiblyAdded[index+1:]...) + newContent := newIndex[smallestKey] + doDiff(report, removedKey, oldContent, newContent, options) + } else { + removed = append(removed, removedKey) } } - report.print(to) - report.clean() - return seenAnyChanges + + return removed, possiblyAdded } -func redactSecrets(old, new *manifest.MappingResult) { - if (old != nil && old.Kind != "Secret") || (new != nil && new.Kind != "Secret") { +func doDiff(report *Report, key string, oldContent *manifest.MappingResult, newContent *manifest.MappingResult, options *Options) { + if oldContent != nil && newContent != nil && oldContent.Content == newContent.Content { return } - serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, - scheme.Scheme) - var oldSecret, newSecret v1.Secret + switch { + case options.ShowSecretsDecoded: + decodeSecrets(oldContent, newContent) + case !options.ShowSecrets: + redactSecrets(oldContent, newContent) + } + if oldContent == nil { + emptyMapping := &manifest.MappingResult{} + diffs := diffMappingResults(emptyMapping, newContent, options.StripTrailingCR) + report.addEntry(key, options.SuppressedKinds, newContent.Kind, options.OutputContext, diffs, "ADD") + } else if newContent == nil { + emptyMapping := &manifest.MappingResult{} + diffs := diffMappingResults(oldContent, emptyMapping, options.StripTrailingCR) + report.addEntry(key, options.SuppressedKinds, oldContent.Kind, options.OutputContext, diffs, "REMOVE") + } else { + diffs := diffMappingResults(oldContent, newContent, options.StripTrailingCR) + if actualChanges(diffs) > 0 { + report.addEntry(key, options.SuppressedKinds, oldContent.Kind, options.OutputContext, diffs, "MODIFY") + } + } +} + +func preHandleSecrets(old, new *manifest.MappingResult) (v1.Secret, v1.Secret, error, error) { + var oldSecretDecodeErr, newSecretDecodeErr error + var oldSecret, newSecret v1.Secret if old != nil { - if err := yaml.NewYAMLToJSONDecoder(bytes.NewBufferString(old.Content)).Decode(&oldSecret); err != nil { - old.Content = fmt.Sprintf("Error parsing old secret: %s", err) + oldSecretDecodeErr = yaml.NewYAMLToJSONDecoder(bytes.NewBufferString(old.Content)).Decode(&oldSecret) + if oldSecretDecodeErr != nil { + old.Content = fmt.Sprintf("Error parsing old secret: %s", oldSecretDecodeErr) + } else { + // if we have a Secret containing `stringData`, apply the same + // transformation that the apiserver would do with it (this protects + // stringData keys from being overwritten down below) + if len(oldSecret.StringData) > 0 && oldSecret.Data == nil { + oldSecret.Data = make(map[string][]byte, len(oldSecret.StringData)) + } + for k, v := range oldSecret.StringData { + oldSecret.Data[k] = []byte(v) + } } } if new != nil { - if err := yaml.NewYAMLToJSONDecoder(bytes.NewBufferString(new.Content)).Decode(&newSecret); err != nil { - new.Content = fmt.Sprintf("Error parsing new secret: %s", err) + newSecretDecodeErr = yaml.NewYAMLToJSONDecoder(bytes.NewBufferString(new.Content)).Decode(&newSecret) + if newSecretDecodeErr != nil { + new.Content = fmt.Sprintf("Error parsing new secret: %s", newSecretDecodeErr) + } else { + // same as above + if len(newSecret.StringData) > 0 && newSecret.Data == nil { + newSecret.Data = make(map[string][]byte, len(newSecret.StringData)) + } + for k, v := range newSecret.StringData { + newSecret.Data[k] = []byte(v) + } } } - if old != nil { + return oldSecret, newSecret, oldSecretDecodeErr, newSecretDecodeErr +} + +// redactSecrets redacts secrets from the diff output. +func redactSecrets(old, new *manifest.MappingResult) { + if (old != nil && old.Kind != "Secret") || (new != nil && new.Kind != "Secret") { + return + } + serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme) + + oldSecret, newSecret, oldSecretDecodeErr, newSecretDecodeErr := preHandleSecrets(old, new) + + if old != nil && oldSecretDecodeErr == nil { oldSecret.StringData = make(map[string]string, len(oldSecret.Data)) for k, v := range oldSecret.Data { if new != nil && bytes.Equal(v, newSecret.Data[k]) { @@ -102,7 +306,7 @@ func redactSecrets(old, new *manifest.MappingResult) { } } } - if new != nil { + if new != nil && newSecretDecodeErr == nil { newSecret.StringData = make(map[string]string, len(newSecret.Data)) for k, v := range newSecret.Data { if old != nil && bytes.Equal(v, oldSecret.Data[k]) { @@ -112,22 +316,68 @@ func redactSecrets(old, new *manifest.MappingResult) { } } } + // remove Data field now that we are using StringData for serialization - var buf bytes.Buffer - if old != nil { + if old != nil && oldSecretDecodeErr == nil { + oldSecretBuf := bytes.NewBuffer(nil) oldSecret.Data = nil - if err := serializer.Encode(&oldSecret, &buf); err != nil { - + if err := serializer.Encode(&oldSecret, oldSecretBuf); err != nil { + new.Content = fmt.Sprintf("Error encoding new secret: %s", err) } - old.Content = getComment(old.Content) + strings.Replace(strings.Replace(buf.String(), "stringData", "data", 1), " creationTimestamp: null\n", "", 1) - buf.Reset() //reuse buffer for new secret + old.Content = getComment(old.Content) + strings.Replace(strings.Replace(oldSecretBuf.String(), "stringData", "data", 1), " creationTimestamp: null\n", "", 1) + oldSecretBuf.Reset() } - if new != nil { + if new != nil && newSecretDecodeErr == nil { + newSecretBuf := bytes.NewBuffer(nil) newSecret.Data = nil - if err := serializer.Encode(&newSecret, &buf); err != nil { + if err := serializer.Encode(&newSecret, newSecretBuf); err != nil { + new.Content = fmt.Sprintf("Error encoding new secret: %s", err) + } + new.Content = getComment(new.Content) + strings.Replace(strings.Replace(newSecretBuf.String(), "stringData", "data", 1), " creationTimestamp: null\n", "", 1) + newSecretBuf.Reset() + } +} + +// decodeSecrets decodes secrets from the diff output. +func decodeSecrets(old, new *manifest.MappingResult) { + if (old != nil && old.Kind != "Secret") || (new != nil && new.Kind != "Secret") { + return + } + serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme) + + oldSecret, newSecret, oldSecretDecodeErr, newSecretDecodeErr := preHandleSecrets(old, new) + + if old != nil && oldSecretDecodeErr == nil { + oldSecret.StringData = make(map[string]string, len(oldSecret.Data)) + for k, v := range oldSecret.Data { + oldSecret.StringData[k] = string(v) + } + } + if new != nil && newSecretDecodeErr == nil { + newSecret.StringData = make(map[string]string, len(newSecret.Data)) + for k, v := range newSecret.Data { + newSecret.StringData[k] = string(v) + } + } + // remove Data field now that we are using StringData for serialization + if old != nil && oldSecretDecodeErr == nil { + oldSecretBuf := bytes.NewBuffer(nil) + oldSecret.Data = nil + if err := serializer.Encode(&oldSecret, oldSecretBuf); err != nil { + new.Content = fmt.Sprintf("Error encoding new secret: %s", err) } - new.Content = getComment(new.Content) + strings.Replace(strings.Replace(buf.String(), "stringData", "data", 1), " creationTimestamp: null\n", "", 1) + old.Content = getComment(old.Content) + strings.Replace(oldSecretBuf.String(), " creationTimestamp: null\n", "", 1) + oldSecretBuf.Reset() + } + if new != nil && newSecretDecodeErr == nil { + newSecretBuf := bytes.NewBuffer(nil) + newSecret.Data = nil + if err := serializer.Encode(&newSecret, newSecretBuf); err != nil { + new.Content = fmt.Sprintf("Error encoding new secret: %s", err) + } + new.Content = getComment(new.Content) + strings.Replace(newSecretBuf.String(), " creationTimestamp: null\n", "", 1) + newSecretBuf.Reset() } } @@ -139,14 +389,13 @@ func getComment(s string) string { return "" } return s[:i+1] - } // Releases reindex the content based on the template names and pass it to Manifests -func Releases(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, showSecrets bool, context int, output string, stripTrailingCR bool, to io.Writer) bool { +func Releases(oldIndex, newIndex map[string]*manifest.MappingResult, options *Options, to io.Writer) bool { oldIndex = reIndexForRelease(oldIndex) newIndex = reIndexForRelease(newIndex) - return Manifests(oldIndex, newIndex, suppressedKinds, showSecrets, context, output, stripTrailingCR, to) + return Manifests(oldIndex, newIndex, options, to) } func diffMappingResults(oldContent *manifest.MappingResult, newContent *manifest.MappingResult, stripTrailingCR bool) []difflib.DiffRecord { @@ -172,10 +421,9 @@ func split(value string, stripTrailingCR bool) []string { func printDiffRecords(suppressedKinds []string, kind string, context int, diffs []difflib.DiffRecord, to io.Writer) { for _, ckind := range suppressedKinds { - if ckind == kind { str := fmt.Sprintf("+ Changes suppressed on sensitive content of type %s\n", kind) - fmt.Fprintf(to, ansi.Color(str, "yellow")) + _, _ = fmt.Fprint(to, ansi.Color(str, "yellow")) return } } @@ -186,7 +434,7 @@ func printDiffRecords(suppressedKinds []string, kind string, context int, diffs for i, diff := range diffs { if distances[i] > context { if !omitting { - fmt.Fprintln(to, "...") + _, _ = fmt.Fprintln(to, "...") omitting = true } } else { @@ -206,11 +454,15 @@ func printDiffRecord(diff difflib.DiffRecord, to io.Writer) { switch diff.Delta { case difflib.RightOnly: - fmt.Fprintf(to, "%s\n", ansi.Color("+ "+text, "green")) + _, _ = fmt.Fprintf(to, "%s\n", ansi.Color("+ "+text, "green")) case difflib.LeftOnly: - fmt.Fprintf(to, "%s\n", ansi.Color("- "+text, "red")) + _, _ = fmt.Fprintf(to, "%s\n", ansi.Color("- "+text, "red")) case difflib.Common: - fmt.Fprintf(to, "%s\n", " "+text) + if text == "" { + _, _ = fmt.Fprintln(to) + } else { + _, _ = fmt.Fprintf(to, "%s\n", " "+text) + } } } @@ -251,7 +503,6 @@ func calculateDistances(diffs []difflib.DiffRecord) map[int]int { // reIndexForRelease based on template names func reIndexForRelease(index map[string]*manifest.MappingResult) map[string]*manifest.MappingResult { - // sort the index to iterate map in the same order var keys []string for key := range index { @@ -265,7 +516,6 @@ func reIndexForRelease(index map[string]*manifest.MappingResult) map[string]*man newIndex := make(map[string]*manifest.MappingResult) for key := range keys { - str := strings.Replace(strings.Split(index[keys[key]].Content, "\n")[0], "# Source: ", "", 1) if _, ok := newIndex[str]; ok { diff --git a/diff/diff_test.go b/diff/diff_test.go index 0cab7cc8..56b844f7 100644 --- a/diff/diff_test.go +++ b/diff/diff_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/aryann/difflib" "github.com/mgutz/ansi" "github.com/stretchr/testify/require" @@ -48,7 +49,6 @@ var text3 = "" + "line10" func TestPrintDiffWithContext(t *testing.T) { - t.Run("context-disabled", func(t *testing.T) { assertDiff(t, text1, text2, -1, false, ""+ "- line1\n"+ @@ -154,7 +154,6 @@ func TestPrintDiffWithContext(t *testing.T) { " line9\n"+ " line10\n") }) - } func assertDiff(t *testing.T, before, after string, context int, stripTrailingCR bool, expected string) { @@ -169,9 +168,10 @@ func assertDiff(t *testing.T, before, after string, context int, stripTrailingCR } func TestManifests(t *testing.T) { + ansi.DisableColors(true) + specBeta := map[string]*manifest.MappingResult{ "default, nginx, Deployment (apps)": { - Name: "default, nginx, Deployment (apps)", Kind: "Deployment", Content: ` @@ -180,11 +180,24 @@ kind: Deployment metadata: name: nginx `, - }} + }, + } specRelease := map[string]*manifest.MappingResult{ "default, nginx, Deployment (apps)": { + Name: "default, nginx, Deployment (apps)", + Kind: "Deployment", + Content: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +`, + }, + } + specReleaseSpec := map[string]*manifest.MappingResult{ + "default, nginx, Deployment (apps)": { Name: "default, nginx, Deployment (apps)", Kind: "Deployment", Content: ` @@ -192,70 +205,335 @@ apiVersion: apps/v1 kind: Deployment metadata: name: nginx +spec: + replicas: 3 `, - }} + }, + } - t.Run("OnChange", func(t *testing.T) { + specReleaseRenamed := map[string]*manifest.MappingResult{ + "default, nginx-renamed, Deployment (apps)": { + Name: "default, nginx-renamed, Deployment (apps)", + Kind: "Deployment", + Content: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-renamed +spec: + replicas: 3 +`, + }, + } + + specReleaseRenamedAndUpdated := map[string]*manifest.MappingResult{ + "default, nginx-renamed, Deployment (apps)": { + Name: "default, nginx-renamed, Deployment (apps)", + Kind: "Deployment", + Content: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-renamed +spec: + replicas: 1 +`, + }, + } + specReleaseRenamedAndAdded := map[string]*manifest.MappingResult{ + "default, nginx-renamed, Deployment (apps)": { + Name: "default, nginx-renamed, Deployment (apps)", + Kind: "Deployment", + Content: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-renamed +spec: + replicas: 3 + selector: + matchLabels: + app: nginx-renamed +`, + }, + } + + specReleaseKeep := map[string]*manifest.MappingResult{ + "default, nginx, Deployment (apps)": { + Name: "default, nginx, Deployment (apps)", + Kind: "Deployment", + Content: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +annotations: + helm.sh/resource-policy: keep +`, + ResourcePolicy: "keep", + }, + } + + t.Run("OnChange", func(t *testing.T) { var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{}} - if changesSeen := Manifests(specBeta, specRelease, []string{}, true, 10, "diff", false, &buf1); !changesSeen { + if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") } require.Equal(t, `default, nginx, Deployment (apps) has changed: - + - apiVersion: apps/v1beta1 + apiVersion: apps/v1 kind: Deployment metadata: name: nginx - + +`, buf1.String()) + }) + + t.Run("OnChangeWithSuppress", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{"apiVersion"}} + + if changesSeen := Manifests(specBeta, specReleaseSpec, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has changed: + + kind: Deployment + metadata: + name: nginx ++ spec: ++ replicas: 3 + +`, buf1.String()) + }) + + t.Run("OnChangeWithSuppressAll", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{"apiVersion"}} + + if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has changed, but diff is empty after suppression. +`, buf1.String()) + }) + + t.Run("OnChangeRename", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} + + if changesSeen := Manifests(specReleaseSpec, specReleaseRenamed, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has changed: + + apiVersion: apps/v1 + kind: Deployment + metadata: +- name: nginx ++ name: nginx-renamed + spec: + replicas: 3 + +`, buf1.String()) + }) + + t.Run("OnChangeRenameAndUpdate", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} + + if changesSeen := Manifests(specReleaseSpec, specReleaseRenamedAndUpdated, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has changed: + + apiVersion: apps/v1 + kind: Deployment + metadata: +- name: nginx ++ name: nginx-renamed + spec: +- replicas: 3 ++ replicas: 1 + +`, buf1.String()) + }) + + t.Run("OnChangeRenameAndAdded", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} + + if changesSeen := Manifests(specReleaseSpec, specReleaseRenamedAndAdded, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has changed: + + apiVersion: apps/v1 + kind: Deployment + metadata: +- name: nginx ++ name: nginx-renamed + spec: + replicas: 3 ++ selector: ++ matchLabels: ++ app: nginx-renamed + +`, buf1.String()) + }) + + t.Run("OnChangeRenameAndAddedWithPartialSuppress", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{"app: "}} + + if changesSeen := Manifests(specReleaseSpec, specReleaseRenamedAndAdded, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has changed: + + apiVersion: apps/v1 + kind: Deployment + metadata: +- name: nginx ++ name: nginx-renamed + spec: + replicas: 3 ++ selector: ++ matchLabels: + +`, buf1.String()) + }) + + t.Run("OnChangeRenameAndRemoved", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} + + if changesSeen := Manifests(specReleaseRenamedAndAdded, specReleaseSpec, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx-renamed, Deployment (apps) has changed: + + apiVersion: apps/v1 + kind: Deployment + metadata: +- name: nginx-renamed ++ name: nginx + spec: + replicas: 3 +- selector: +- matchLabels: +- app: nginx-renamed + +`, buf1.String()) + }) + + t.Run("OnChangeRenameAndRemovedWithPartialSuppress", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{"app: "}} + + if changesSeen := Manifests(specReleaseRenamedAndAdded, specReleaseSpec, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx-renamed, Deployment (apps) has changed: + + apiVersion: apps/v1 + kind: Deployment + metadata: +- name: nginx-renamed ++ name: nginx + spec: + replicas: 3 +- selector: +- matchLabels: + `, buf1.String()) }) t.Run("OnNoChange", func(t *testing.T) { var buf2 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{}} - if changesSeen := Manifests(specRelease, specRelease, []string{}, true, 10, "diff", false, &buf2); changesSeen { + if changesSeen := Manifests(specRelease, specRelease, &diffOptions, &buf2); changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") } - require.Equal(t, ``, buf2.String()) + require.Empty(t, buf2.String()) }) - t.Run("OnChangeSimple", func(t *testing.T) { + t.Run("OnChangeRemoved", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} + + if changesSeen := Manifests(specRelease, nil, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, nginx, Deployment (apps) has been removed: + +- apiVersion: apps/v1 +- kind: Deployment +- metadata: +- name: nginx +- `+` +`, buf1.String()) + }) + + t.Run("OnChangeRemovedWithResourcePolicyKeep", func(t *testing.T) { + var buf2 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.0, []string{}} + if changesSeen := Manifests(specReleaseKeep, nil, &diffOptions, &buf2); changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") + } + + require.Empty(t, buf2.String()) + }) + + t.Run("OnChangeSimple", func(t *testing.T) { var buf1 bytes.Buffer + diffOptions := Options{"simple", 10, false, true, false, []string{}, 0.0, []string{}} - if changesSeen := Manifests(specBeta, specRelease, []string{}, true, 10, "simple", false, &buf1); !changesSeen { + if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") } require.Equal(t, `default, nginx, Deployment (apps) to be changed. -Plan: 0 to add, 1 to change, 0 to destroy. +Plan: 0 to add, 1 to change, 0 to destroy, 0 to change ownership. `, buf1.String()) }) t.Run("OnNoChangeSimple", func(t *testing.T) { var buf2 bytes.Buffer - - if changesSeen := Manifests(specRelease, specRelease, []string{}, true, 10, "simple", false, &buf2); changesSeen { + diffOptions := Options{"simple", 10, false, true, false, []string{}, 0.0, []string{}} + if changesSeen := Manifests(specRelease, specRelease, &diffOptions, &buf2); changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") } - require.Equal(t, "Plan: 0 to add, 0 to change, 0 to destroy.\n", buf2.String()) + require.Equal(t, "Plan: 0 to add, 0 to change, 0 to destroy, 0 to change ownership.\n", buf2.String()) }) t.Run("OnChangeTemplate", func(t *testing.T) { - var buf1 bytes.Buffer + diffOptions := Options{"template", 10, false, true, false, []string{}, 0.0, []string{}} - if changesSeen := Manifests(specBeta, specRelease, []string{}, true, 10, "template", false, &buf1); !changesSeen { + if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") } - require.Equal(t, `[{ + require.JSONEq(t, `[{ "api": "apps", "kind": "Deployment", "namespace": "default", @@ -266,14 +544,14 @@ Plan: 0 to add, 1 to change, 0 to destroy. }) t.Run("OnChangeJSON", func(t *testing.T) { - var buf1 bytes.Buffer + diffOptions := Options{"json", 10, false, true, false, []string{}, 0.0, []string{}} - if changesSeen := Manifests(specBeta, specRelease, []string{}, true, 10, "json", false, &buf1); !changesSeen { + if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") } - require.Equal(t, `[{ + require.JSONEq(t, `[{ "api": "apps", "kind": "Deployment", "namespace": "default", @@ -285,8 +563,9 @@ Plan: 0 to add, 1 to change, 0 to destroy. t.Run("OnNoChangeTemplate", func(t *testing.T) { var buf2 bytes.Buffer + diffOptions := Options{"template", 10, false, true, false, []string{}, 0.0, []string{}} - if changesSeen := Manifests(specRelease, specRelease, []string{}, true, 10, "template", false, &buf2); changesSeen { + if changesSeen := Manifests(specRelease, specRelease, &diffOptions, &buf2); changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") } @@ -296,10 +575,627 @@ Plan: 0 to add, 1 to change, 0 to destroy. t.Run("OnChangeCustomTemplate", func(t *testing.T) { var buf1 bytes.Buffer os.Setenv("HELM_DIFF_TPL", "testdata/customTemplate.tpl") - if changesSeen := Manifests(specBeta, specRelease, []string{}, true, 10, "template", false, &buf1); !changesSeen { + diffOptions := Options{"template", 10, false, true, false, []string{}, 0.0, []string{}} + + if changesSeen := Manifests(specBeta, specRelease, &diffOptions, &buf1); !changesSeen { t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") } require.Equal(t, "Resource name: nginx\n", buf1.String()) }) } + +func TestManifestsWithRedactedSecrets(t *testing.T) { + ansi.DisableColors(true) + + specSecretWithByteData := map[string]*manifest.MappingResult{ + "default, foobar, Secret (v1)": { + Name: "default, foobar, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foobar +type: Opaque +data: + key1: dmFsdWUx + key2: dmFsdWUy + key3: dmFsdWUz +`, + }, + } + + specSecretWithByteDataChanged := map[string]*manifest.MappingResult{ + "default, foobar, Secret (v1)": { + Name: "default, foobar, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foobar +type: Opaque +data: + key1: dmFsdWUxY2hhbmdlZA== + key2: dmFsdWUy + key4: dmFsdWU0 +`, + }, + } + + specSecretWithStringData := map[string]*manifest.MappingResult{ + "default, foobar, Secret (v1)": { + Name: "default, foobar, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foobar +type: Opaque +stringData: + key1: value1 + key2: value2 + key3: value3 +`, + }, + } + + specSecretWithStringDataChanged := map[string]*manifest.MappingResult{ + "default, foobar, Secret (v1)": { + Name: "default, foobar, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foobar +type: Opaque +stringData: + key1: value1changed + key2: value2 + key4: value4 +`, + }, + } + + t.Run("OnChangeSecretWithByteData", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, false, false, []string{}, 0.5, []string{}} // NOTE: ShowSecrets = false + + if changesSeen := Manifests(specSecretWithByteData, specSecretWithByteDataChanged, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + // TODO: Why is there no empty line between the header and the start of the diff, like in the other diffs? + require.Equal(t, `default, foobar, Secret (v1) has changed: + apiVersion: v1 + kind: Secret + metadata: + name: foobar + data: +- key1: '-------- # (6 bytes)' ++ key1: '++++++++ # (13 bytes)' + key2: 'REDACTED # (6 bytes)' +- key3: '-------- # (6 bytes)' ++ key4: '++++++++ # (6 bytes)' + type: Opaque + +`, buf1.String()) + }) + + t.Run("OnChangeSecretWithStringData", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, false, false, []string{}, 0.5, []string{}} // NOTE: ShowSecrets = false + + if changesSeen := Manifests(specSecretWithStringData, specSecretWithStringDataChanged, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, foobar, Secret (v1) has changed: + apiVersion: v1 + kind: Secret + metadata: + name: foobar + data: +- key1: '-------- # (6 bytes)' ++ key1: '++++++++ # (13 bytes)' + key2: 'REDACTED # (6 bytes)' +- key3: '-------- # (6 bytes)' ++ key4: '++++++++ # (6 bytes)' + type: Opaque + +`, buf1.String()) + }) +} + +func TestDoSuppress(t *testing.T) { + for _, tt := range []struct { + name string + input Report + supressRegex []string + expected Report + }{ + { + name: "noop", + input: Report{}, + supressRegex: []string{}, + expected: Report{}, + }, + { + name: "simple", + input: Report{ + Entries: []ReportEntry{ + { + Diffs: diffStrings("hello: world", "hello: world2", false), + }, + }, + }, + supressRegex: []string{}, + expected: Report{ + Entries: []ReportEntry{ + { + Diffs: diffStrings("hello: world", "hello: world2", false), + }, + }, + }, + }, + { + name: "ignore all", + input: Report{ + Entries: []ReportEntry{ + { + Diffs: diffStrings("hello: world", "hello: world2", false), + }, + }, + }, + supressRegex: []string{".*world2?"}, + expected: Report{ + Entries: []ReportEntry{ + { + Diffs: []difflib.DiffRecord{}, + }, + }, + }, + }, + { + name: "ignore partial", + input: Report{ + Entries: []ReportEntry{ + { + Diffs: diffStrings("hello: world", "hello: world2", false), + }, + }, + }, + supressRegex: []string{".*world2"}, + expected: Report{ + Entries: []ReportEntry{ + { + Diffs: []difflib.DiffRecord{ + { + Payload: "hello: world", + Delta: difflib.LeftOnly, + }, + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + report, err := doSuppress(tt.input, tt.supressRegex) + require.NoError(t, err) + + require.Equal(t, tt.expected, report) + }) + } +} + +func TestChangeOwnership(t *testing.T) { + ansi.DisableColors(true) + + specOriginal := map[string]*manifest.MappingResult{ + "default, foobar, ConfigMap (v1)": { + Name: "default, foobar, ConfigMap (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar +data: + key1: value1 +`, + }, + } + + t.Run("OnChangeOwnershipWithoutSpecChange", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} // NOTE: ShowSecrets = false + + newOwnedReleases := map[string]OwnershipDiff{ + "default, foobar, ConfigMap (v1)": { + OldRelease: "default/oldfoobar", + NewRelease: "default/foobar", + }, + } + if changesSeen := ManifestsOwnership(specOriginal, specOriginal, newOwnedReleases, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, foobar, ConfigMap (v1) changed ownership: +- default/oldfoobar ++ default/foobar +`, buf1.String()) + }) + + t.Run("OnChangeOwnershipWithSpecChange", func(t *testing.T) { + var buf1 bytes.Buffer + diffOptions := Options{"diff", 10, false, true, false, []string{}, 0.5, []string{}} // NOTE: ShowSecrets = false + + specNew := map[string]*manifest.MappingResult{ + "default, foobar, ConfigMap (v1)": { + Name: "default, foobar, ConfigMap (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar +data: + key1: newValue1 +`, + }, + } + + newOwnedReleases := map[string]OwnershipDiff{ + "default, foobar, ConfigMap (v1)": { + OldRelease: "default/oldfoobar", + NewRelease: "default/foobar", + }, + } + if changesSeen := ManifestsOwnership(specOriginal, specNew, newOwnedReleases, &diffOptions, &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `default, foobar, ConfigMap (v1) changed ownership: +- default/oldfoobar ++ default/foobar +default, foobar, ConfigMap (v1) has changed: + + apiVersion: v1 + kind: ConfigMap + metadata: + name: foobar + data: +- key1: value1 ++ key1: newValue1 + +`, buf1.String()) + }) +} + +func TestDecodeSecrets(t *testing.T) { + ansi.DisableColors(true) + + t.Run("decodeSecrets with valid base64 data", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: dmFsdWUx + key2: dmFsdWUy +`, + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: bmV3dmFsdWUx + key2: dmFsdWUy +`, + } + decodeSecrets(old, new) + require.Contains(t, old.Content, "key1: value1") + require.Contains(t, old.Content, "key2: value2") + require.Contains(t, new.Content, "key1: newvalue1") + require.Contains(t, new.Content, "key2: value2") + }) + + t.Run("decodeSecrets with stringData", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +stringData: + key1: value1 + key2: value2 +`, + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +stringData: + key1: value1changed + key2: value2 +`, + } + decodeSecrets(old, new) + require.Contains(t, old.Content, "key1: value1") + require.Contains(t, old.Content, "key2: value2") + require.Contains(t, new.Content, "key1: value1changed") + require.Contains(t, new.Content, "key2: value2") + }) + t.Run("decodeSecrets with stringData and data ensuring that stringData always precedes/overrides data on Secrets", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +stringData: + key1: value1.stringdata + key2: value2.stringdata +data: + key2: dmFsdWUyLmRhdGE= + key3: dmFsdWUzLmRhdGE= +`, + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +stringData: + key1: value1changed.stringdata + key2: value2.stringdata +data: + key3: dmFsdWUzLmRhdGE= +`, + } + decodeSecrets(old, new) + require.Contains(t, old.Content, "key1: value1.stringdata") + require.Contains(t, old.Content, "key2: value2.stringdata") + require.Contains(t, old.Content, "key3: value3.data") + require.Contains(t, new.Content, "key1: value1changed.stringdata") + require.Contains(t, new.Content, "key2: value2.stringdata") + require.Contains(t, new.Content, "key3: value3.data") + }) + + t.Run("decodeSecrets with invalid base64", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: invalidbase64 +`, + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: dmFsdWUx +`, + } + decodeSecrets(old, new) + require.Contains(t, old.Content, "Error parsing old secret") + require.Contains(t, new.Content, "key1: value1") + }) + + t.Run("decodeSecrets with non-Secret kind", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, ConfigMap (v1)", + Kind: "ConfigMap", + Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n", + } + new := &manifest.MappingResult{ + Name: "default, foo, ConfigMap (v1)", + Kind: "ConfigMap", + Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n", + } + origOld := old.Content + origNew := new.Content + decodeSecrets(old, new) + require.Equal(t, origOld, old.Content) + require.Equal(t, origNew, new.Content) + }) + + t.Run("decodeSecrets with nil arguments", func(t *testing.T) { + // Should not panic or change anything + decodeSecrets(nil, nil) + }) +} + +func TestRedactSecrets(t *testing.T) { + ansi.DisableColors(true) + + t.Run("redactSecrets with valid base64 data", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: dmFsdWUx + key2: dmFsdWUy +`, + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: bmV3dmFsdWUx + key2: dmFsdWUy +`, + } + redactSecrets(old, new) + require.Contains(t, old.Content, "key1: '-------- # (6 bytes)'") + require.Contains(t, old.Content, "key2: 'REDACTED # (6 bytes)'") + require.Contains(t, new.Content, "key1: '++++++++ # (9 bytes)'") + require.Contains(t, new.Content, "key2: 'REDACTED # (6 bytes)'") + }) + + t.Run("redactSecrets with stringData", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +stringData: + key1: value1 + key2: value2 +`, + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +stringData: + key1: value1changed + key2: value2 +`, + } + redactSecrets(old, new) + require.Contains(t, old.Content, "key1: '-------- # (6 bytes)'") + require.Contains(t, old.Content, "key2: 'REDACTED # (6 bytes)'") + require.Contains(t, new.Content, "key1: '++++++++ # (13 bytes)'") + require.Contains(t, new.Content, "key2: 'REDACTED # (6 bytes)'") + }) + + t.Run("redactSecrets with nil arguments", func(t *testing.T) { + // Should not panic or change anything + redactSecrets(nil, nil) + }) + + t.Run("redactSecrets with non-Secret kind", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, ConfigMap (v1)", + Kind: "ConfigMap", + Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n", + } + new := &manifest.MappingResult{ + Name: "default, foo, ConfigMap (v1)", + Kind: "ConfigMap", + Content: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: foo\n", + } + origOld := old.Content + origNew := new.Content + redactSecrets(old, new) + require.Equal(t, origOld, old.Content) + require.Equal(t, origNew, new.Content) + }) + + t.Run("redactSecrets with invalid YAML", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: "invalid: yaml: :::", + } + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: "invalid: yaml: :::", + } + redactSecrets(old, new) + require.Contains(t, old.Content, "Error parsing old secret") + require.Contains(t, new.Content, "Error parsing new secret") + }) + + t.Run("redactSecrets with only old secret", func(t *testing.T) { + old := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: dmFsdWUx +`, + } + redactSecrets(old, nil) + require.Contains(t, old.Content, "key1: '-------- # (6 bytes)'") + }) + + t.Run("redactSecrets with only new secret", func(t *testing.T) { + new := &manifest.MappingResult{ + Name: "default, foo, Secret (v1)", + Kind: "Secret", + Content: ` +apiVersion: v1 +kind: Secret +metadata: + name: foo +type: Opaque +data: + key1: dmFsdWUx +`, + } + redactSecrets(nil, new) + require.Contains(t, new.Content, "key1: '++++++++ # (6 bytes)'") + }) +} diff --git a/diff/report.go b/diff/report.go index 22347166..ac456365 100644 --- a/diff/report.go +++ b/diff/report.go @@ -12,23 +12,25 @@ import ( "text/template" "github.com/aryann/difflib" + "github.com/gonvenience/ytbx" + "github.com/homeport/dyff/pkg/dyff" "github.com/mgutz/ansi" ) // Report to store report data and format type Report struct { format ReportFormat - entries []ReportEntry + Entries []ReportEntry } // ReportEntry to store changes between releases type ReportEntry struct { - key string - suppressedKinds []string - kind string - context int - diffs []difflib.DiffRecord - changeType string + Key string + SuppressedKinds []string + Kind string + Context int + Diffs []difflib.DiffRecord + ChangeType string } // ReportFormat to the context to make a changes report @@ -61,11 +63,56 @@ func (r *Report) setupReportFormat(format string) { setupTemplateReport(r) case "json": setupJSONReport(r) + case "dyff": + setupDyffReport(r) default: setupDiffReport(r) } } +func setupDyffReport(r *Report) { + r.format.output = printDyffReport +} + +func printDyffReport(r *Report, to io.Writer) { + currentFile, _ := os.CreateTemp("", "existing-values") + defer func() { + _ = os.Remove(currentFile.Name()) + }() + newFile, _ := os.CreateTemp("", "new-values") + defer func() { + _ = os.Remove(newFile.Name()) + }() + + for _, entry := range r.Entries { + _, _ = currentFile.WriteString("---\n") + _, _ = newFile.WriteString("---\n") + for _, record := range entry.Diffs { + switch record.Delta { + case difflib.Common: + _, _ = currentFile.WriteString(record.Payload + "\n") + _, _ = newFile.WriteString(record.Payload + "\n") + case difflib.LeftOnly: + _, _ = currentFile.WriteString(record.Payload + "\n") + case difflib.RightOnly: + _, _ = newFile.WriteString(record.Payload + "\n") + } + } + } + _ = currentFile.Close() + _ = newFile.Close() + + currentInputFile, newInputFile, _ := ytbx.LoadFiles(currentFile.Name(), newFile.Name()) + + report, _ := dyff.CompareInputFiles(currentInputFile, newInputFile) + reportWriter := &dyff.HumanReport{ + Report: report, + OmitHeader: true, + MinorChangeThreshold: 0.1, + } + _ = reportWriter.WriteReport(to) +} + // addEntry: stores diff changes. func (r *Report) addEntry(key string, suppressedKinds []string, kind string, context int, diffs []difflib.DiffRecord, changeType string) { entry := ReportEntry{ @@ -76,7 +123,7 @@ func (r *Report) addEntry(key string, suppressedKinds []string, kind string, con diffs, changeType, } - r.entries = append(r.entries, entry) + r.Entries = append(r.Entries, entry) } // print: prints entries added to the report. @@ -86,7 +133,7 @@ func (r *Report) print(to io.Writer) { // clean: needed for testing func (r *Report) clean() { - r.entries = nil + r.Entries = nil } // setup report for default output: diff @@ -96,15 +143,21 @@ func setupDiffReport(r *Report) { r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: "has been added:"} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: "has been removed:"} r.format.changestyles["MODIFY"] = ChangeStyle{color: "yellow", message: "has changed:"} + r.format.changestyles["OWNERSHIP"] = ChangeStyle{color: "magenta", message: "changed ownership:"} + r.format.changestyles["MODIFY_SUPPRESSED"] = ChangeStyle{color: "blue+h", message: "has changed, but diff is empty after suppression."} } // print report for default output: diff func printDiffReport(r *Report, to io.Writer) { - for _, entry := range r.entries { - fmt.Fprintf(to, ansi.Color("%s %s", "yellow")+"\n", entry.key, r.format.changestyles[entry.changeType].message) - printDiffRecords(entry.suppressedKinds, entry.kind, entry.context, entry.diffs, to) + for _, entry := range r.Entries { + _, _ = fmt.Fprintf( + to, + ansi.Color("%s %s", r.format.changestyles[entry.ChangeType].color)+"\n", + entry.Key, + r.format.changestyles[entry.ChangeType].message, + ) + printDiffRecords(entry.SuppressedKinds, entry.Kind, entry.Context, entry.Diffs, to) } - } // setup report for simple output. @@ -114,28 +167,32 @@ func setupSimpleReport(r *Report) { r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: "to be added."} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: "to be removed."} r.format.changestyles["MODIFY"] = ChangeStyle{color: "yellow", message: "to be changed."} + r.format.changestyles["OWNERSHIP"] = ChangeStyle{color: "magenta", message: "to change ownership."} + r.format.changestyles["MODIFY_SUPPRESSED"] = ChangeStyle{color: "blue+h", message: "has changed, but diff is empty after suppression."} } // print report for simple output func printSimpleReport(r *Report, to io.Writer) { - var summary = map[string]int{ - "ADD": 0, - "REMOVE": 0, - "MODIFY": 0, + summary := map[string]int{ + "ADD": 0, + "REMOVE": 0, + "MODIFY": 0, + "OWNERSHIP": 0, + "MODIFY_SUPPRESSED": 0, } - for _, entry := range r.entries { - fmt.Fprintf(to, ansi.Color("%s %s", r.format.changestyles[entry.changeType].color)+"\n", - entry.key, - r.format.changestyles[entry.changeType].message, + for _, entry := range r.Entries { + _, _ = fmt.Fprintf(to, ansi.Color("%s %s", r.format.changestyles[entry.ChangeType].color)+"\n", + entry.Key, + r.format.changestyles[entry.ChangeType].message, ) - summary[entry.changeType]++ + summary[entry.ChangeType]++ } - fmt.Fprintf(to, "Plan: %d to add, %d to change, %d to destroy.\n", summary["ADD"], summary["MODIFY"], summary["REMOVE"]) + _, _ = fmt.Fprintf(to, "Plan: %d to add, %d to change, %d to destroy, %d to change ownership.\n", summary["ADD"], summary["MODIFY"], summary["REMOVE"], summary["OWNERSHIP"]) } func newTemplate(name string) *template.Template { // Prepare template functions - var funcsMap = template.FuncMap{ + funcsMap := template.FuncMap{ "last": func(x int, a interface{}) bool { return x == reflect.ValueOf(a).Len()-1 }, @@ -156,6 +213,8 @@ func setupJSONReport(r *Report) { r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: ""} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: ""} r.format.changestyles["MODIFY"] = ChangeStyle{color: "yellow", message: ""} + r.format.changestyles["OWNERSHIP"] = ChangeStyle{color: "magenta", message: ""} + r.format.changestyles["MODIFY_SUPPRESSED"] = ChangeStyle{color: "blue+h", message: ""} } // setup report for template output @@ -186,12 +245,14 @@ func setupTemplateReport(r *Report) { r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: ""} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: ""} r.format.changestyles["MODIFY"] = ChangeStyle{color: "yellow", message: ""} + r.format.changestyles["OWNERSHIP"] = ChangeStyle{color: "magenta", message: ""} + r.format.changestyles["MODIFY_SUPPRESSED"] = ChangeStyle{color: "blue+h", message: ""} } // report with template output will only have access to ReportTemplateSpec. // This function reverts parsedMetadata.String() func (t *ReportTemplateSpec) loadFromKey(key string) error { - pattern := regexp.MustCompile(`(?P[a-z0-9-]+), (?P[a-z0-9-]+), (?P\w+) \((?P[a-z0-9.]+)\)`) + pattern := regexp.MustCompile(`(?P[a-z0-9-]+), (?P[a-z0-9.-]+), (?P\w+) \((?P[^)]+)\)`) matches := pattern.FindStringSubmatch(key) if len(matches) > 1 { t.Namespace = matches[1] @@ -208,18 +269,18 @@ func templateReportPrinter(t *template.Template) func(r *Report, to io.Writer) { return func(r *Report, to io.Writer) { var templateDataArray []ReportTemplateSpec - for _, entry := range r.entries { + for _, entry := range r.Entries { templateData := ReportTemplateSpec{} - err := templateData.loadFromKey(entry.key) + err := templateData.loadFromKey(entry.Key) if err != nil { log.Println("error processing report entry") } else { - templateData.Change = entry.changeType + templateData.Change = entry.ChangeType templateDataArray = append(templateDataArray, templateData) } } - t.Execute(to, templateDataArray) + _ = t.Execute(to, templateDataArray) _, _ = to.Write([]byte("\n")) } } diff --git a/diff/report_test.go b/diff/report_test.go new file mode 100644 index 00000000..1a2aff60 --- /dev/null +++ b/diff/report_test.go @@ -0,0 +1,36 @@ +package diff + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoadFromKey(t *testing.T) { + keyToReportTemplateSpec := map[string]ReportTemplateSpec{ + "default, nginx, Deployment (apps)": { + Namespace: "default", + Name: "nginx", + Kind: "Deployment", + API: "apps", + }, + "default, probes.monitoring.coreos.com, CustomResourceDefinition (apiextensions.k8s.io)": { + Namespace: "default", + Name: "probes.monitoring.coreos.com", + Kind: "CustomResourceDefinition", + API: "apiextensions.k8s.io", + }, + "default, my-cert, Certificate (cert-manager.io/v1)": { + Namespace: "default", + Name: "my-cert", + Kind: "Certificate", + API: "cert-manager.io/v1", + }, + } + + for key, expectedTemplateSpec := range keyToReportTemplateSpec { + templateSpec := &ReportTemplateSpec{} + require.NoError(t, templateSpec.loadFromKey(key)) + require.Equal(t, expectedTemplateSpec, *templateSpec) + } +} diff --git a/go.mod b/go.mod index 35dd7919..9e1a1160 100644 --- a/go.mod +++ b/go.mod @@ -1,145 +1,141 @@ module github.com/databus23/helm-diff/v3 -go 1.17 +go 1.24.6 require ( - github.com/Masterminds/semver v1.5.0 - github.com/Masterminds/sprig v2.22.0+incompatible // indirect - github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a - github.com/evanphx/json-patch v4.9.0+incompatible - github.com/ghodss/yaml v1.0.0 - github.com/huandu/xstrings v1.3.2 // indirect - github.com/json-iterator/go v1.1.10 - github.com/mattn/go-colorable v0.1.7 // indirect + github.com/Masterminds/semver/v3 v3.4.0 + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b + github.com/evanphx/json-patch/v5 v5.9.11 + github.com/gonvenience/bunt v1.4.2 + github.com/gonvenience/ytbx v1.4.7 + github.com/google/go-cmp v0.7.0 + github.com/homeport/dyff v1.10.2 + github.com/json-iterator/go v1.1.12 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d - github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.1.3 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e - google.golang.org/grpc v1.30.0 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 + golang.org/x/term v0.36.0 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.6.1 - k8s.io/api v0.21.0 - k8s.io/apiextensions-apiserver v0.21.0 - k8s.io/apimachinery v0.21.0 - k8s.io/cli-runtime v0.21.0 - k8s.io/client-go v0.21.0 - k8s.io/helm v2.16.12+incompatible - rsc.io/letsencrypt v0.0.3 // indirect - sigs.k8s.io/yaml v1.2.0 + helm.sh/helm/v3 v3.19.0 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/cli-runtime v0.34.1 + k8s.io/client-go v0.34.1 + sigs.k8s.io/yaml v1.6.0 ) require ( - github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // indirect - github.com/Masterminds/squirrel v1.5.0 // indirect - github.com/Microsoft/go-winio v0.4.16 // indirect - github.com/Microsoft/hcsshim v0.8.14 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect - github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 // indirect - github.com/containerd/containerd v1.4.4 // indirect - github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 // indirect - github.com/cyphar/filepath-securejoin v0.2.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deislabs/oras v0.11.1 // indirect - github.com/docker/cli v20.10.5+incompatible // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.3 // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/color v1.7.0 // indirect - github.com/go-errors/errors v1.0.1 // indirect - github.com/go-logr/logr v0.4.0 // indirect - github.com/go-openapi/jsonpointer v0.19.3 // indirect - github.com/go-openapi/jsonreference v0.19.3 // indirect - github.com/go-openapi/spec v0.19.5 // indirect - github.com/go-openapi/swag v0.19.5 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/containerd/containerd v1.7.29 // indirect + github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.4.3 // indirect - github.com/google/btree v1.0.0 // indirect - github.com/google/go-cmp v0.5.2 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gnostic v0.4.1 // indirect - github.com/gorilla/mux v1.7.3 // indirect + github.com/gonvenience/idem v0.0.2 // indirect + github.com/gonvenience/neat v1.3.16 // indirect + github.com/gonvenience/term v1.0.4 // indirect + github.com/gonvenience/text v1.0.9 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect - github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/hashicorp/golang-lru v0.5.1 // indirect - github.com/imdario/mergo v0.3.11 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/jmoiron/sqlx v1.3.1 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.0 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - github.com/mattn/go-runewidth v0.0.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/mitchellh/copystructure v1.1.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.1 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/hashstructure v1.1.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.1 // indirect - github.com/opencontainers/runc v0.1.1 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.7.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.10.0 // indirect - github.com/prometheus/procfs v0.2.0 // indirect - github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 // indirect - github.com/russross/blackfriday v1.5.2 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect - go.opencensus.io v0.22.3 // indirect - go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect - golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect - golang.org/x/text v0.3.4 // indirect - golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect - google.golang.org/appengine v1.6.5 // indirect - google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect - google.golang.org/protobuf v1.25.0 // indirect - gopkg.in/gorp.v1 v1.7.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rubenv/sql-migrate v1.8.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect - k8s.io/apiserver v0.21.0 // indirect - k8s.io/component-base v0.21.0 // indirect - k8s.io/klog/v2 v2.8.0 // indirect - k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect - k8s.io/kubectl v0.21.0 // indirect - k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect - sigs.k8s.io/kustomize/api v0.8.5 // indirect - sigs.k8s.io/kustomize/kyaml v0.10.15 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/component-base v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/kubectl v0.34.0 // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + oras.land/oras-go/v2 v2.6.0 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) diff --git a/go.sum b/go.sum index 2c9ca8f6..ca2e1cd4 100644 --- a/go.sum +++ b/go.sum @@ -1,469 +1,186 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go/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= -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/Azure/azure-sdk-for-go v16.2.1+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 v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -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/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -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/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/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -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/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= -github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -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/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-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/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -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/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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -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 h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw= -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/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -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/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -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/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 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/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -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/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -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.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -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/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= -github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8dskcM7wQ28gONkukbQkM4qpj4RnYbpFzrI= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -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.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= -github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= -github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -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/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.5+incompatible h1:bjflayQbWg+xOkF2WPEAOi4Y7zWhR7ptoPhV/VqLVDE= -github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -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/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -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/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -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/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= -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 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/form3tech-oss/jwt-go v3.2.2+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/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -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-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-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-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -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-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/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -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-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= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -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.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.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/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= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -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.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.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/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-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= -github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= -github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= -github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= -github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= -github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= -github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= +github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -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.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -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 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/gonvenience/bunt v1.4.2 h1:nTgkFZsw38SIJKABhLj8aXj2rqion9Zo1so/EBkbFBY= +github.com/gonvenience/bunt v1.4.2/go.mod h1:WjyEO2rSYR+OLZg67Ucl+gjdXPs8GpFl63SCA02XDyI= +github.com/gonvenience/idem v0.0.2 h1:jWHknjPfSbiWgYKre9wB2FhMgVLd1RWXCXzVq+7VIWg= +github.com/gonvenience/idem v0.0.2/go.mod h1:0Xv1MpnNL40+dsyOxaJFa7L8ekeTRr63WaWXpiWLFFM= +github.com/gonvenience/neat v1.3.16 h1:Vb0iCkSHGWaA+ry69RY3HpQ6Ooo6o/g2wjI80db8DjI= +github.com/gonvenience/neat v1.3.16/go.mod h1:sLxdQNNluxbpROxTTHs3XBSJX8fwFX5toEULUy74ODA= +github.com/gonvenience/term v1.0.4 h1:qkCGfmUtpzs9W4jWgNijaGF6dg3oSIh+kZCzT5cPNZY= +github.com/gonvenience/term v1.0.4/go.mod h1:OzNdQC5NVBou9AifaHd1QG6EP8iDdpaT7GFm1bVgslg= +github.com/gonvenience/text v1.0.9 h1:U29BxT3NZnNPcfiEnAwt6yHXe38fQs2Q+WTqs1X+atI= +github.com/gonvenience/text v1.0.9/go.mod h1:JQF1ifXNRaa66jnPLqoITA+y8WATlG0eJzFC9ElJS3s= +github.com/gonvenience/ytbx v1.4.7 h1:3wJ7EOfdv3Lg+h0mzKo7f8d1zMY1EJtVzzYrA3UhjHQ= +github.com/gonvenience/ytbx v1.4.7/go.mod h1:ZmAU727eOTYeC4aUJuqyb9vogNAN7NiSKfw6Aoxbqys= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/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-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 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -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/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -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-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -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/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-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/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -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.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= -github.com/imdario/mergo v0.3.11/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/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -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/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= +github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/homeport/dyff v1.10.2 h1:XyB+D0KVwjbUFTZYIkvPtsImwkfh+ObH2CEdEHTqdr4= +github.com/homeport/dyff v1.10.2/go.mod h1:0kIjL/JOGaXigzrLY6kcl5esSStbAa99r6GzEvr7lrs= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/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.2/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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -471,697 +188,301 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 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/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/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/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/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -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/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -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/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= -github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/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/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/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -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/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -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/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.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -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.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.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/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= +github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= +github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -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.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -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/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/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -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/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -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 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -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-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -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 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -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 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -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/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= -github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -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 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/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -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/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -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 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -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-20190330032615-68dc04aab96a/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/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/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -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-0.20171106142849-4c012f6dcd95/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/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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= +github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -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/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/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/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -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/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.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.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/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -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= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-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-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 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/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-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= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/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-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-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-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 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/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/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= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20190916202348-b4ddaad3f8a3/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-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= -golang.org/x/text v0.3.4/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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/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-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-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-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/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-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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -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/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.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/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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-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-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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -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.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.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/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/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/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 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.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.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/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.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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.6.1 h1:TQ6q4pAatXr7qh2fbLcb0oNd0I3J7kv26oo5cExKTtc= -helm.sh/helm/v3 v3.6.1/go.mod h1:mIIus8EOqj+obtycw3sidsR4ORr2aFDmXMSI3k+oeVY= -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= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= -k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= -k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= -k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= -k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= -k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= -k8s.io/apiserver v0.21.0 h1:1hWMfsz+cXxB77k6/y0XxWxwl6l9OF26PC9QneUVn1Q= -k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= -k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= -k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= -k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= -k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= -k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= -k8s.io/component-base v0.21.0 h1:tLLGp4BBjQaCpS/KiuWh7m2xqvAdsxLm4ATxHSe5Zpg= -k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= -k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/helm v2.16.12+incompatible h1:K2zhF8+B85Ya1n7n3eH34xwwp5qNUM42TBFENDZJT7w= -k8s.io/helm v2.16.12+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= -k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= -k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= -rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= -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/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ= -sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= -sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= -sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= -sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI= -sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= -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 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -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/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= +helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= +k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= +k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/install-binary.ps1 b/install-binary.ps1 new file mode 100644 index 00000000..e182b4c3 --- /dev/null +++ b/install-binary.ps1 @@ -0,0 +1,77 @@ +param ( + [switch] $Update = $false +) + +function Get-Architecture { + $architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture + + $arch = switch ($architecture) { + "X64" { "amd64" } + "Arm64" { "arm64" } + Default { "" } + } + + if ($arch -eq "") { + throw "Unsupported architecture: ${architecture}" + } + + return $arch +} + +function Get-Version { + param ([Parameter(Mandatory=$true)][bool] $Update) + + if ($Update) { + return "latest" + } + + return git describe --tags --exact-match 2>$null || "latest" +} + +function New-TemporaryDirectory { + $tmp = [System.IO.Path]::GetTempPath() + $name = (New-Guid).ToString("N") + $dir = New-Item -ItemType Directory -Path (Join-Path $tmp $name) + return $dir.FullName +} + +function Get-Url { + param ([Parameter(Mandatory=$true)][string] $Version, [Parameter(Mandatory=$true)][string] $Architecture) + + if ($Version -eq "latest") { + return "https://github.com/databus23/helm-diff/releases/latest/download/helm-diff-windows-${Architecture}.tgz" + } + return "https://github.com/databus23/helm-diff/releases/download/${Version}/helm-diff-windows-${Architecture}.tgz" +} + +function Download-Plugin { + param ([Parameter(Mandatory=$true)][string] $Url, [Parameter(Mandatory=$true)][string] $Output) + + Invoke-WebRequest -OutFile $Output $Url +} + +function Install-Plugin { + param ([Parameter(Mandatory=$true)][string] $ArchiveDirectory, [Parameter(Mandatory=$true)][string] $ArchiveName, [Parameter(Mandatory=$true)][string] $Destination) + + Push-Location $ArchiveDirectory + tar -xzf $ArchiveName -C . + Pop-Location + + New-Item -ItemType Directory -Path $Destination -Force + Copy-Item -Path (Join-Path $ArchiveDirectory "diff" "bin" "diff.exe") -Destination $Destination -Force +} + +$ErrorActionPreference = "Stop" + +$archiveName = "helm-diff.tgz" +$arch = Get-Architecture +$version = Get-Version -Update $Update +$tmpDir = New-TemporaryDirectory + +trap { Remove-Item -path $tmpDir -Recurse -Force } + +$url = Get-Url -Version $version -Architecture $arch +$output = Join-Path $tmpDir $archiveName + +Download-Plugin -Url $url -Output $output +Install-Plugin -ArchiveDirectory $tmpDir -ArchiveName $archiveName -Destination (Join-Path $env:HELM_PLUGIN_DIR "bin") \ No newline at end of file diff --git a/install-binary.sh b/install-binary.sh index 3a9e5f98..da8782e9 100755 --- a/install-binary.sh +++ b/install-binary.sh @@ -11,19 +11,18 @@ export GREP_COLOR="never" # on Windows where helm returns a Windows path but we # need a Unix path -if type cygpath >/dev/null 2>&1; then +if command -v cygpath >/dev/null 2>&1; then HELM_BIN="$(cygpath -u "${HELM_BIN}")" HELM_PLUGIN_DIR="$(cygpath -u "${HELM_PLUGIN_DIR}")" fi [ -z "$HELM_BIN" ] && HELM_BIN=$(command -v helm) -HELM_MAJOR_VERSION=$("${HELM_BIN}" version --client --short | awk -F '.' '{print $1}') [ -z "$HELM_HOME" ] && HELM_HOME=$(helm env | grep 'HELM_DATA_HOME' | cut -d '=' -f2 | tr -d '"') mkdir -p "$HELM_HOME" -: ${HELM_PLUGIN_DIR:="$HELM_HOME/plugins/helm-diff"} +: "${HELM_PLUGIN_DIR:="$HELM_HOME/plugins/helm-diff"}" if [ "$SKIP_BIN_INSTALL" = "1" ]; then echo "Skipping binary install" @@ -48,32 +47,39 @@ initArch() { x86_64) ARCH="amd64" ;; i686) ARCH="386" ;; i386) ARCH="386" ;; + ppc64le) ARCH="ppc64le" ;; + s390x) ARCH="s390x" ;; esac } # initOS discovers the operating system for this system. initOS() { - OS=$(uname | tr '[:upper:]' '[:lower:]') + OS=$(uname -s) case "$OS" in + Windows_NT) OS='windows' ;; # Msys support - msys*) OS='windows' ;; + MSYS*) OS='windows' ;; # Minimalist GNU for Windows - mingw*) OS='windows' ;; - darwin) OS='macos' ;; + MINGW*) OS='windows' ;; + CYGWIN*) OS='windows' ;; + Darwin) OS='macos' ;; + Linux) OS='linux' ;; esac } # verifySupported checks that the os/arch combination is supported for # binary builds. verifySupported() { - supported="linux-amd64\nlinux-arm64\nfreebsd-amd64\nmacos-amd64\nmacos-arm64\nwindows-amd64" + supported="linux-amd64\nlinux-arm64\nlinux-armv6\nlinux-armv7\nlinux-ppc64le\nlinux-s390x\nfreebsd-amd64\nfreebsd-arm64\nmacos-amd64\nmacos-arm64\nwindows-amd64\nwindows-arm64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then echo "No prebuild binary for ${OS}-${ARCH}." exit 1 fi - if ! type "curl" >/dev/null && ! type "wget" >/dev/null; then + if + ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1 + then echo "Either curl or wget is required" exit 1 fi @@ -82,7 +88,7 @@ verifySupported() { # getDownloadURL checks the latest available version. getDownloadURL() { version=$(git -C "$HELM_PLUGIN_DIR" describe --tags --exact-match 2>/dev/null || :) - if [ "$SCRIPT_MODE" = "install" -a -n "$version" ]; then + if [ "$SCRIPT_MODE" = "install" ] && [ -n "$version" ]; then DOWNLOAD_URL="https://github.com/$PROJECT_GH/releases/download/$version/helm-diff-$OS-$ARCH.tgz" else DOWNLOAD_URL="https://github.com/$PROJECT_GH/releases/latest/download/helm-diff-$OS-$ARCH.tgz" @@ -104,18 +110,25 @@ rmTempDir() { downloadFile() { PLUGIN_TMP_FILE="${HELM_TMP}/${PROJECT_NAME}.tgz" echo "Downloading $DOWNLOAD_URL" - if type "curl" >/dev/null; then - curl -L "$DOWNLOAD_URL" -o "$PLUGIN_TMP_FILE" - elif type "wget" >/dev/null; then - wget -q -O "$PLUGIN_TMP_FILE" "$DOWNLOAD_URL" + if + command -v curl >/dev/null 2>&1 + then + curl -sSf -L "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE" + elif + command -v wget >/dev/null 2>&1 + then + wget -q -O - "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE" fi } # installFile verifies the SHA256 for the file, then unpacks and # installs it. installFile() { - tar xvzf "$PLUGIN_TMP_FILE" -C "$HELM_TMP" + tar xzf "$PLUGIN_TMP_FILE" -C "$HELM_TMP" HELM_TMP_BIN="$HELM_TMP/diff/bin/diff" + if [ "${OS}" = "windows" ]; then + HELM_TMP_BIN="$HELM_TMP_BIN.exe" + fi echo "Preparing to install into ${HELM_PLUGIN_DIR}" mkdir -p "$HELM_PLUGIN_DIR/bin" cp "$HELM_TMP_BIN" "$HELM_PLUGIN_DIR/bin" @@ -132,14 +145,6 @@ exit_trap() { exit $result } -# testVersion tests the installed client to make sure it is working. -testVersion() { - set +e - echo "$PROJECT_NAME installed into $HELM_PLUGIN_DIR/$PROJECT_NAME" - "${HELM_PLUGIN_DIR}/bin/diff" -h - set -e -} - # Execution #Stop execution on any error @@ -152,4 +157,3 @@ getDownloadURL mkTempDir downloadFile installFile -testVersion diff --git a/main.go b/main.go index fe18f4be..8c321d1a 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,23 @@ package main import ( + "errors" "os" + _ "k8s.io/client-go/plugin/pkg/client/auth/azure" + _ "k8s.io/client-go/plugin/pkg/client/auth/exec" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + "github.com/databus23/helm-diff/v3/cmd" ) func main() { if err := cmd.New().Execute(); err != nil { - switch e := err.(type) { - case cmd.Error: - os.Exit(e.Code) + var cmdErr cmd.Error + switch { + case errors.As(err, &cmdErr): + os.Exit(cmdErr.Code) default: os.Exit(1) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000..89445916 --- /dev/null +++ b/main_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/databus23/helm-diff/v3/cmd" +) + +func TestMain(m *testing.M) { + if os.Getenv(env) == envValue { + os.Exit(runFakeHelm()) + } + + os.Exit(m.Run()) +} + +func TestHelmDiff(t *testing.T) { + os.Setenv(env, envValue) + defer os.Unsetenv(env) + + helmBin, helmBinSet := os.LookupEnv("HELM_BIN") + os.Setenv("HELM_BIN", os.Args[0]) + defer func() { + if helmBinSet { + os.Setenv("HELM_BIN", helmBin) + } else { + os.Unsetenv("HELM_BIN") + } + }() + + os.Args = []string{"helm-diff", "upgrade", "-f", "test/testdata/test-values.yaml", "test-release", "test/testdata/test-chart"} + require.NoError(t, cmd.New().Execute()) +} + +const ( + env = "BECOME_FAKE_HELM" + envValue = "1" +) + +type fakeHelmSubcmd struct { + cmd []string + args []string + stdout string + stderr string + exitCode int +} + +var helmSubcmdStubs = []fakeHelmSubcmd{ + { + cmd: []string{"version"}, + stdout: `version.BuildInfo{Version:"v3.1.0-rc.1", GitCommit:"12345", GitTreeState:"clean", GoVersion:"go1.20.12"}`, + }, + { + cmd: []string{"get", "manifest"}, + args: []string{"test-release"}, + stdout: `--- +# Source: test-chart/templates/cm.yaml +`, + }, + { + cmd: []string{"template"}, + args: []string{"test-release", "test/testdata/test-chart", "--values", "test/testdata/test-values.yaml", "--validate", "--is-upgrade"}, + }, + { + cmd: []string{"get", "hooks"}, + args: []string{"test-release"}, + }, +} + +func runFakeHelm() int { + var stub *fakeHelmSubcmd + + if len(os.Args) < 2 { + _, _ = fmt.Fprintln(os.Stderr, "fake helm does not support invocations without subcommands") + return 1 + } + + cmdAndArgs := os.Args[1:] + for i := range helmSubcmdStubs { + s := helmSubcmdStubs[i] + if reflect.DeepEqual(s.cmd, cmdAndArgs[:len(s.cmd)]) { + stub = &s + break + } + } + + if stub == nil { + _, _ = fmt.Fprintf(os.Stderr, "no stub for %s\n", cmdAndArgs) + return 1 + } + + want := stub.args + if want == nil { + want = []string{} + } + got := cmdAndArgs[len(stub.cmd):] + if !reflect.DeepEqual(want, got) { + _, _ = fmt.Fprintf(os.Stderr, "want: %v\n", want) + _, _ = fmt.Fprintf(os.Stderr, "got : %v\n", got) + _, _ = fmt.Fprintf(os.Stderr, "args : %v\n", os.Args) + _, _ = fmt.Fprintf(os.Stderr, "env : %v\n", os.Environ()) + return 1 + } + _, _ = fmt.Fprintf(os.Stdout, "%s", stub.stdout) + _, _ = fmt.Fprintf(os.Stderr, "%s", stub.stderr) + return stub.exitCode +} diff --git a/manifest/generate.go b/manifest/generate.go new file mode 100644 index 00000000..f59bf10a --- /dev/null +++ b/manifest/generate.go @@ -0,0 +1,214 @@ +package manifest + +import ( + "bytes" + "encoding/json" + "fmt" + + jsonpatch "github.com/evanphx/json-patch/v5" + jsoniter "github.com/json-iterator/go" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/kube" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/yaml" +) + +const ( + Helm2TestSuccessHook = "test-success" + Helm3TestHook = "test" +) + +func Generate(actionConfig *action.Configuration, originalManifest, targetManifest []byte) ([]byte, []byte, error) { + var err error + original, err := actionConfig.KubeClient.Build(bytes.NewBuffer(originalManifest), false) + if err != nil { + return nil, nil, fmt.Errorf("unable to build kubernetes objects from original release manifest: %w", err) + } + target, err := actionConfig.KubeClient.Build(bytes.NewBuffer(targetManifest), false) + if err != nil { + return nil, nil, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err) + } + releaseManifest, installManifest := make([]byte, 0), make([]byte, 0) + // to be deleted + targetResources := make(map[string]bool) + for _, r := range target { + targetResources[objectKey(r)] = true + } + for _, r := range original { + if !targetResources[objectKey(r)] { + out, _ := yaml.Marshal(r.Object) + releaseManifest = append(releaseManifest, yamlSeparator...) + releaseManifest = append(releaseManifest, out...) + } + } + + existingResources := make(map[string]bool) + for _, r := range original { + existingResources[objectKey(r)] = true + } + + var toBeCreated kube.ResourceList + for _, r := range target { + if !existingResources[objectKey(r)] { + toBeCreated = append(toBeCreated, r) + } + } + + toBeUpdated, err := existingResourceConflict(toBeCreated) + if err != nil { + return nil, nil, fmt.Errorf("rendered manifests contain a resource that already exists. Unable to continue with update: %w", err) + } + + _ = toBeUpdated.Visit(func(r *resource.Info, err error) error { + if err != nil { + return err + } + original.Append(r) + return nil + }) + + err = target.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + kind := info.Mapping.GroupVersionKind.Kind + + // Fetch the current object for the three-way merge + helper := resource.NewHelper(info.Client, info.Mapping) + currentObj, err := helper.Get(info.Namespace, info.Name) + if err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("could not get information about the resource: %w", err) + } + // to be created + out, _ := yaml.Marshal(info.Object) + installManifest = append(installManifest, yamlSeparator...) + installManifest = append(installManifest, out...) + return nil + } + // to be updated + out, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(currentObj) + pruneObj, err := deleteStatusAndTidyMetadata(out) + if err != nil { + return fmt.Errorf("prune current obj %q with kind %s: %w", info.Name, kind, err) + } + pruneOut, err := yaml.Marshal(pruneObj) + if err != nil { + return fmt.Errorf("prune current out %q with kind %s: %w", info.Name, kind, err) + } + releaseManifest = append(releaseManifest, yamlSeparator...) + releaseManifest = append(releaseManifest, pruneOut...) + + originalInfo := original.Get(info) + if originalInfo == nil { + return fmt.Errorf("could not find %q", info.Name) + } + + patch, patchType, err := createPatch(originalInfo.Object, currentObj, info) + if err != nil { + return err + } + + helper.ServerDryRun = true + targetObj, err := helper.Patch(info.Namespace, info.Name, patchType, patch, nil) + if err != nil { + return fmt.Errorf("cannot patch %q with kind %s: %w", info.Name, kind, err) + } + out, _ = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(targetObj) + pruneObj, err = deleteStatusAndTidyMetadata(out) + if err != nil { + return fmt.Errorf("prune current obj %q with kind %s: %w", info.Name, kind, err) + } + pruneOut, err = yaml.Marshal(pruneObj) + if err != nil { + return fmt.Errorf("prune current out %q with kind %s: %w", info.Name, kind, err) + } + installManifest = append(installManifest, yamlSeparator...) + installManifest = append(installManifest, pruneOut...) + return nil + }) + + return releaseManifest, installManifest, err +} + +func createPatch(originalObj, currentObj runtime.Object, target *resource.Info) ([]byte, types.PatchType, error) { + oldData, err := json.Marshal(originalObj) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %w", err) + } + newData, err := json.Marshal(target.Object) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %w", err) + } + + // Even if currentObj is nil (because it was not found), it will marshal just fine + currentData, err := json.Marshal(currentObj) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing live configuration: %w", err) + } + // kind := target.Mapping.GroupVersionKind.Kind + // if kind == "Deployment" { + // curr, _ := yaml.Marshal(currentObj) + // fmt.Println(string(curr)) + // } + + // Get a versioned object + versionedObject := kube.AsVersioned(target) + + // Unstructured objects, such as CRDs, may not have an not registered error + // returned from ConvertToVersion. Anything that's unstructured should + // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported + // on objects like CRDs. + _, isUnstructured := versionedObject.(runtime.Unstructured) + + // On newer K8s versions, CRDs aren't unstructured but has this dedicated type + _, isCRD := versionedObject.(*apiextv1.CustomResourceDefinition) + + if isUnstructured || isCRD { + // fall back to generic JSON merge patch + patch, err := jsonpatch.CreateMergePatch(oldData, newData) + return patch, types.MergePatchType, err + } + + patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("unable to create patch metadata from object: %w", err) + } + + patch, err := strategicpatch.CreateThreeWayMergePatch(oldData, newData, currentData, patchMeta, true) + return patch, types.StrategicMergePatchType, err +} + +func objectKey(r *resource.Info) string { + gvk := r.Object.GetObjectKind().GroupVersionKind() + return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) +} + +func existingResourceConflict(resources kube.ResourceList) (kube.ResourceList, error) { + var requireUpdate kube.ResourceList + + err := resources.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + helper := resource.NewHelper(info.Client, info.Mapping) + _, err = helper.Get(info.Namespace, info.Name) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("could not get information about the resource: %w", err) + } + + requireUpdate.Append(info) + return nil + }) + + return requireUpdate, err +} diff --git a/manifest/parse.go b/manifest/parse.go index 68d08e03..aae6c71d 100644 --- a/manifest/parse.go +++ b/manifest/parse.go @@ -7,21 +7,24 @@ import ( "log" "strings" - yaml "gopkg.in/yaml.v2" - "k8s.io/helm/pkg/proto/hapi/release" + jsoniter "github.com/json-iterator/go" + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/runtime" ) const ( - hookAnnotation = "helm.sh/hook" + hookAnnotation = "helm.sh/hook" + resourcePolicyAnnotation = "helm.sh/resource-policy" ) var yamlSeparator = []byte("\n---\n") // MappingResult to store result of diff type MappingResult struct { - Name string - Kind string - Content string + Name string + Kind string + Content string + ResourcePolicy string } type metadata struct { @@ -40,8 +43,13 @@ func (m metadata) String() string { if len(sp) > 1 { apiBase = strings.Join(sp[:len(sp)-1], "/") } - - return fmt.Sprintf("%s, %s, %s (%s)", m.Metadata.Namespace, m.Metadata.Name, m.Kind, apiBase) + name := m.Metadata.Name + if a := m.Metadata.Annotations; a != nil { + if baseName, ok := a["helm-diff/base-name"]; ok { + name = baseName + } + } + return fmt.Sprintf("%s, %s, %s (%s)", m.Metadata.Namespace, name, m.Kind, apiBase) } func scanYamlSpecs(data []byte, atEOF bool) (advance int, token []byte, err error) { @@ -60,28 +68,6 @@ func scanYamlSpecs(data []byte, atEOF bool) (advance int, token []byte, err erro return 0, nil, nil } -func splitSpec(token string) (string, string) { - if i := strings.Index(token, "\n"); i >= 0 { - return token[0:i], token[i+1:] - } - return "", "" -} - -// ParseRelease parses release objects into MappingResult -func ParseRelease(release *release.Release, includeTests bool, normalizeManifests bool) map[string]*MappingResult { - manifest := release.Manifest - for _, hook := range release.Hooks { - if !includeTests && isTestHook(hook.Events) { - continue - } - - manifest += "\n---\n" - manifest += fmt.Sprintf("# Source: %s\n", hook.Path) - manifest += hook.Manifest - } - return Parse(manifest, release.Namespace, normalizeManifests) -} - // Parse parses manifest strings into MappingResult func Parse(manifest string, defaultNamespace string, normalizeManifests bool, excludedHooks ...string) map[string]*MappingResult { // Ensure we have a newline in front of the yaml separator @@ -89,8 +75,6 @@ func Parse(manifest string, defaultNamespace string, normalizeManifests bool, ex scanner.Split(scanYamlSpecs) // Allow for tokens (specs) up to 10MiB in size scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), 10485760) - // Discard the first result, we only care about everything after the first separator - scanner.Scan() result := make(map[string]*MappingResult) @@ -121,6 +105,48 @@ func Parse(manifest string, defaultNamespace string, normalizeManifests bool, ex return result } +func ParseObject(object runtime.Object, defaultNamespace string, excludedHooks ...string) (*MappingResult, string, error) { + json, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(object) + var objectMap map[string]interface{} + err := jsoniter.Unmarshal(json, &objectMap) + if err != nil { + return nil, "", fmt.Errorf("could not unmarshal byte sequence: %w", err) + } + + metadata := objectMap["metadata"].(map[string]interface{}) + var oldRelease string + if a := metadata["annotations"]; a != nil { + annotations := a.(map[string]interface{}) + if releaseNs, ok := annotations["meta.helm.sh/release-namespace"].(string); ok { + oldRelease += releaseNs + "/" + } + if releaseName, ok := annotations["meta.helm.sh/release-name"].(string); ok { + oldRelease += releaseName + } + } + + // Clean namespace metadata as it exists in Kubernetes but not in Helm manifest + purgedObj, _ := deleteStatusAndTidyMetadata(json) + + content, err := yaml.Marshal(purgedObj) + if err != nil { + return nil, "", err + } + + result, err := parseContent(string(content), defaultNamespace, true, excludedHooks...) + if err != nil { + return nil, "", err + } + + if len(result) != 1 { + return nil, "", fmt.Errorf("failed to parse content of Kubernetes resource %s", metadata["name"]) + } + + result[0].Content = strings.TrimSuffix(result[0].Content, "\n") + + return result[0], oldRelease, nil +} + func parseContent(content string, defaultNamespace string, normalizeManifests bool, excludedHooks ...string) ([]*MappingResult, error) { var parsedMetadata metadata if err := yaml.Unmarshal([]byte(content), &parsedMetadata); err != nil { @@ -133,7 +159,7 @@ func parseContent(content string, defaultNamespace string, normalizeManifests bo return nil, nil } - if parsedMetadata.Kind == "List" { + if strings.HasSuffix(parsedMetadata.Kind, "List") { type ListV1 struct { Items []yaml.MapSlice `yaml:"items"` } @@ -154,7 +180,7 @@ func parseContent(content string, defaultNamespace string, normalizeManifests bo subs, err := parseContent(string(subcontent), defaultNamespace, normalizeManifests, excludedHooks...) if err != nil { - return nil, fmt.Errorf("Parsing YAML list item: %v", err) + return nil, fmt.Errorf("Parsing YAML list item: %w", err) } result = append(result, subs...) @@ -189,9 +215,10 @@ func parseContent(content string, defaultNamespace string, normalizeManifests bo name := parsedMetadata.String() return []*MappingResult{ { - Name: name, - Kind: parsedMetadata.Kind, - Content: content, + Name: name, + Kind: parsedMetadata.Kind, + Content: content, + ResourcePolicy: parsedMetadata.Metadata.Annotations[resourcePolicyAnnotation], }, }, nil } @@ -204,13 +231,3 @@ func isHook(metadata metadata, hooks ...string) bool { } return false } - -func isTestHook(hookEvents []release.Hook_Event) bool { - for _, event := range hookEvents { - if event == release.Hook_RELEASE_TEST_FAILURE || event == release.Hook_RELEASE_TEST_SUCCESS { - return true - } - } - - return false -} diff --git a/manifest/parse_test.go b/manifest/parse_test.go index 14c6c2cc..eef3ea5f 100644 --- a/manifest/parse_test.go +++ b/manifest/parse_test.go @@ -1,11 +1,13 @@ package manifest_test import ( - "io/ioutil" + "os" "sort" "testing" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/serializer/yaml" . "github.com/databus23/helm-diff/v3/manifest" ) @@ -20,7 +22,7 @@ func foundObjects(result map[string]*MappingResult) []string { } func TestPod(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/pod.yaml") + spec, err := os.ReadFile("testdata/pod.yaml") require.NoError(t, err) require.Equal(t, @@ -30,7 +32,7 @@ func TestPod(t *testing.T) { } func TestPodNamespace(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/pod_namespace.yaml") + spec, err := os.ReadFile("testdata/pod_namespace.yaml") require.NoError(t, err) require.Equal(t, @@ -40,7 +42,7 @@ func TestPodNamespace(t *testing.T) { } func TestPodHook(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/pod_hook.yaml") + spec, err := os.ReadFile("testdata/pod_hook.yaml") require.NoError(t, err) require.Equal(t, @@ -60,7 +62,7 @@ func TestPodHook(t *testing.T) { } func TestDeployV1(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/deploy_v1.yaml") + spec, err := os.ReadFile("testdata/deploy_v1.yaml") require.NoError(t, err) require.Equal(t, @@ -70,7 +72,7 @@ func TestDeployV1(t *testing.T) { } func TestDeployV1Beta1(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/deploy_v1beta1.yaml") + spec, err := os.ReadFile("testdata/deploy_v1beta1.yaml") require.NoError(t, err) require.Equal(t, @@ -80,7 +82,7 @@ func TestDeployV1Beta1(t *testing.T) { } func TestList(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/list.yaml") + spec, err := os.ReadFile("testdata/list.yaml") require.NoError(t, err) require.Equal(t, @@ -92,8 +94,34 @@ func TestList(t *testing.T) { ) } +func TestConfigMapList(t *testing.T) { + spec, err := os.ReadFile("testdata/configmaplist_v1.yaml") + require.NoError(t, err) + + require.Equal(t, + []string{ + "default, configmap-2-1, ConfigMap (v1)", + "default, configmap-2-2, ConfigMap (v1)", + }, + foundObjects(Parse(string(spec), "default", false)), + ) +} + +func TestSecretList(t *testing.T) { + spec, err := os.ReadFile("testdata/secretlist_v1.yaml") + require.NoError(t, err) + + require.Equal(t, + []string{ + "default, my-secret-1, Secret (v1)", + "default, my-secret-2, Secret (v1)", + }, + foundObjects(Parse(string(spec), "default", false)), + ) +} + func TestEmpty(t *testing.T) { - spec, err := ioutil.ReadFile("testdata/empty.yaml") + spec, err := os.ReadFile("testdata/empty.yaml") require.NoError(t, err) require.Equal(t, @@ -101,3 +129,53 @@ func TestEmpty(t *testing.T) { foundObjects(Parse(string(spec), "default", false)), ) } + +func TestBaseNameAnnotation(t *testing.T) { + spec, err := os.ReadFile("testdata/secret_immutable.yaml") + require.NoError(t, err) + + require.Equal(t, + []string{"default, bat-secret, Secret (v1)"}, + foundObjects(Parse(string(spec), "default", false)), + ) +} + +func TestParseObject(t *testing.T) { + for _, tt := range []struct { + name string + filename string + releaseName string + kind string + oldRelease string + }{ + { + name: "no release info", + filename: "testdata/pod_no_release_annotations.yaml", + releaseName: "testNS, nginx, Pod (v1)", + kind: "Pod", + oldRelease: "", + }, + { + name: "get old release info", + filename: "testdata/pod_release_annotations.yaml", + releaseName: "testNS, nginx, Pod (v1)", + kind: "Pod", + oldRelease: "oldNS/oldReleaseName", + }, + } { + t.Run(tt.name, func(t *testing.T) { + spec, err := os.ReadFile(tt.filename) + require.NoError(t, err) + + obj, _, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(spec, nil, nil) + require.NoError(t, err) + + release, oldRelease, err := ParseObject(obj, "testNS") + require.NoError(t, err) + + require.Equal(t, tt.releaseName, release.Name) + require.Equal(t, tt.kind, release.Kind) + require.Equal(t, tt.oldRelease, oldRelease) + }) + } +} diff --git a/manifest/testdata/configmaplist_v1.yaml b/manifest/testdata/configmaplist_v1.yaml new file mode 100644 index 00000000..d12959fc --- /dev/null +++ b/manifest/testdata/configmaplist_v1.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMapList +items: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: configmap-2-1 + data: + key1: data1 +- apiVersion: v1 + kind: ConfigMap + metadata: + name: configmap-2-2 + data: + key2: data2 \ No newline at end of file diff --git a/manifest/testdata/pod_no_release_annotations.yaml b/manifest/testdata/pod_no_release_annotations.yaml new file mode 100644 index 00000000..a2fc1082 --- /dev/null +++ b/manifest/testdata/pod_no_release_annotations.yaml @@ -0,0 +1,15 @@ + +--- +# Source: nginx/pod.yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx + annotations: + some: "annotation" +spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 diff --git a/manifest/testdata/pod_release_annotations.yaml b/manifest/testdata/pod_release_annotations.yaml new file mode 100644 index 00000000..3354a97c --- /dev/null +++ b/manifest/testdata/pod_release_annotations.yaml @@ -0,0 +1,16 @@ + +--- +# Source: nginx/pod.yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx + annotations: + meta.helm.sh/release-namespace: "oldNS" + meta.helm.sh/release-name: "oldReleaseName" +spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 diff --git a/manifest/testdata/secret_immutable.yaml b/manifest/testdata/secret_immutable.yaml new file mode 100644 index 00000000..b3dc160c --- /dev/null +++ b/manifest/testdata/secret_immutable.yaml @@ -0,0 +1,14 @@ + +--- +# Source: nginx/some-secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: bat-secret-ab1234cd + annotations: + helm-diff/base-name: bat-secret +immutable: true +type: Opaque +stringData: + secret1: Very secretive secret + secret2: One more diff --git a/manifest/testdata/secretlist_v1.yaml b/manifest/testdata/secretlist_v1.yaml new file mode 100644 index 00000000..5b586498 --- /dev/null +++ b/manifest/testdata/secretlist_v1.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: SecretList +items: +- apiVersion: v1 + kind: Secret + metadata: + name: my-secret-1 + type: Opaque + data: + username: YWRtaW4= + password: MWYyZDFlMmU2N2Rm +- apiVersion: v1 + kind: Secret + metadata: + name: my-secret-2 + type: Opaque + data: + token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SjFhV1FpT2pFc0luUnBiV1VpT2pFMU56RXlPRGd3TmpFeE1qQXdNVGN5TWpFeE1qQXdNVEE0TWpVMUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SjFhV1FpT2pFc0luUnBiV1VpT2pFMU56RXlPRGd3TmpFeE1qQXdNVGN5TWpFeE1qQXdNVEE0TWpVMUxDSmhiR2NpT2lKSVV6STFOaUo= diff --git a/manifest/util.go b/manifest/util.go new file mode 100644 index 00000000..88b818af --- /dev/null +++ b/manifest/util.go @@ -0,0 +1,40 @@ +package manifest + +import ( + "fmt" + + jsoniter "github.com/json-iterator/go" +) + +func deleteStatusAndTidyMetadata(obj []byte) (map[string]interface{}, error) { + var objectMap map[string]interface{} + err := jsoniter.Unmarshal(obj, &objectMap) + if err != nil { + return nil, fmt.Errorf("could not unmarshal byte sequence: %w", err) + } + + delete(objectMap, "status") + + metadata := objectMap["metadata"].(map[string]interface{}) + + delete(metadata, "managedFields") + delete(metadata, "generation") + delete(metadata, "creationTimestamp") + delete(metadata, "resourceVersion") + delete(metadata, "uid") + + // See the below for the goal of this metadata tidy logic. + // https://github.com/databus23/helm-diff/issues/326#issuecomment-1008253274 + if a := metadata["annotations"]; a != nil { + annotations := a.(map[string]interface{}) + delete(annotations, "meta.helm.sh/release-name") + delete(annotations, "meta.helm.sh/release-namespace") + delete(annotations, "deployment.kubernetes.io/revision") + + if len(annotations) == 0 { + delete(metadata, "annotations") + } + } + + return objectMap, nil +} diff --git a/manifest/util_test.go b/manifest/util_test.go new file mode 100644 index 00000000..0f86223a --- /dev/null +++ b/manifest/util_test.go @@ -0,0 +1,97 @@ +package manifest + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_deleteStatusAndTidyMetadata(t *testing.T) { + tests := []struct { + name string + obj []byte + want map[string]interface{} + wantErr bool + }{ + { + name: "not valid json", + obj: []byte("notvalid"), + want: nil, + wantErr: true, + }, + { + name: "valid json", + obj: []byte(` +{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "1", + "meta.helm.sh/release-name": "test-release", + "meta.helm.sh/release-namespace": "test-ns", + "other-annot": "value" + }, + "creationTimestamp": "2025-03-03T10:07:50Z", + "generation": 1, + "name": "nginx-deployment", + "namespace": "test-ns", + "resourceVersion": "33648", + "uid": "7a8d3b74-6452-46f4-a31f-4fdacbe828ac" + }, + "spec": { + "template": { + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx" + } + ] + } + } + }, + "status": { + "availableReplicas": 2 + } +} +`), + want: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "other-annot": "value", + }, + "name": "nginx-deployment", + "namespace": "test-ns", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := deleteStatusAndTidyMetadata(tt.obj) + if (err != nil) != tt.wantErr { + t.Errorf("deleteStatusAndTidyMetadata() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.Equalf(t, tt.want, got, "deleteStatusAndTidyMetadata() = %v, want %v", got, tt.want) + }) + } +} diff --git a/plugin.yaml b/plugin.yaml index d55d8836..451a4687 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,11 +1,30 @@ -name: "diff" +name: diff # Version is the version of Helm plus the number of official builds for this # plugin -version: "3.3.2" +version: "3.13.2" usage: "Preview helm upgrade changes as a diff" description: "Preview helm upgrade changes as a diff" useTunnel: true -command: "$HELM_PLUGIN_DIR/bin/diff" -hooks: - install: "$HELM_PLUGIN_DIR/install-binary.sh" - update: "$HELM_PLUGIN_DIR/install-binary.sh -u" +platformCommand: + - command: ${HELM_PLUGIN_DIR}/bin/diff + - os: windows + command: ${HELM_PLUGIN_DIR}\bin\diff.exe + +platformHooks: + install: + - command: ${HELM_PLUGIN_DIR}/install-binary.sh + - os: windows + command: pwsh + args: + - -c + - ${HELM_PLUGIN_DIR}/install-binary.ps1 + update: + - command: ${HELM_PLUGIN_DIR}/install-binary.sh + args: + - -u + - os: windows + command: pwsh + args: + - -c + - ${HELM_PLUGIN_DIR}/install-binary.ps1 + - -Update \ No newline at end of file diff --git a/scripts/dep-helm-version.sh b/scripts/dep-helm-version.sh deleted file mode 100755 index 7d4608ed..00000000 --- a/scripts/dep-helm-version.sh +++ /dev/null @@ -1,3 +0,0 @@ -#/bin/sh - -grep "k8s.io/helm" go.mod | sed -n -e "s/.*k8s.io\/helm \(v[.0-9]*\).*/\1/p" diff --git a/scripts/release.sh b/scripts/release.sh index 8eff3d64..9ca0716b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,18 +1,11 @@ -#!/usr/bin/env bash -set -x +#!/usr/bin/env sh +set -e -if [ ! -f bin/github-release ]; then - OS=$(uname) - curl -L https://github.com/aktau/github-release/releases/download/v0.7.2/$OS-amd64-github-release.tar.bz2 | tar -C bin/ -jvx --strip-components=3 +# == would end up with: scripts/release.sh: 5: [: v3.8.1: unexpected operator +if [ "$1" = "" ]; then + echo usage: "$0 VERSION" fi -user=databus23 -repo=helm-diff -tag=$1 -commit=$2 - -bin/github-release release -u $user -r $repo -t $tag -c $commit -n $tag - -for f in $(ls release); do - bin/github-release upload -u $user -r $repo -t $tag -n $f -f release/$f -done +git tag $1 +git push origin $1 +gh release create $1 --draft --generate-notes --title "$1" release/*.tgz diff --git a/scripts/verify-golint.sh b/scripts/verify-staticcheck.sh similarity index 52% rename from scripts/verify-golint.sh rename to scripts/verify-staticcheck.sh index 6d00a1ed..b061a3c6 100755 --- a/scripts/verify-golint.sh +++ b/scripts/verify-staticcheck.sh @@ -1,20 +1,19 @@ #!/bin/bash # script credits : https://github.com/infracloudio/botkube -set -o errexit set -o nounset set -o pipefail -find_files() { +find_packages() { find . -not \( \ \( \ -wholename '*/vendor/*' \ \) -prune \ - \) -name '*.go' + \) -name '*.go' -exec dirname '{}' ';' | sort -u } -bad_files=$(find_files | xargs -I@ bash -c "golint @") -if [[ -n "${bad_files}" ]]; then - echo "${bad_files}" +errors="$(find_packages | xargs -I@ bash -c "staticcheck @")" +if [[ -n "${errors}" ]]; then + echo "${errors}" exit 1 fi diff --git a/staticcheck.conf b/staticcheck.conf new file mode 100644 index 00000000..5393d168 --- /dev/null +++ b/staticcheck.conf @@ -0,0 +1 @@ +checks = ["all", "-ST1000", "-ST1005"]