[
  {
    "path": ".circleci/config.yml",
    "content": "---\n  version: 2.0\n\n  jobs:\n    unit_tests:\n      docker:\n      - image: circleci/golang:1.16\n      environment:\n        KUBEBUILDER_CONTROLPLANE_START_TIMEOUT: \"60s\"\n      steps:\n      - checkout\n      - setup_remote_docker\n      - run: make test\n      - run:\n          command: bash <(curl -s https://codecov.io/bash)\n          when: always\n\n    docker_hub_master:\n      docker:\n      - image: circleci/golang:1.16\n      environment:\n        KUBEBUILDER_CONTROLPLANE_START_TIMEOUT: \"60s\"\n      steps:\n      - checkout\n      - setup_remote_docker\n      - run: make test\n\n    docker_hub_release_tags:\n      docker:\n      - image: circleci/golang:1.16\n      environment:\n        KUBEBUILDER_CONTROLPLANE_START_TIMEOUT: \"60s\"\n      steps:\n      - checkout\n      - setup_remote_docker\n      - run: make test\n\n  workflows:\n    version: 2\n    secrets-manager:\n      jobs:\n      - unit_tests\n      - docker_hub_master:\n          requires:\n          - unit_tests\n          filters:\n            branches:\n              only: master\n      - docker_hub_release_tags:\n          requires:\n          - unit_tests\n          filters:\n            tags:\n              only: /^v[0-9]+(\\.[0-9]+){2}(-.+|[^-.]*)$/\n            branches:\n              ignore: /.*/\n"
  },
  {
    "path": ".dockerignore",
    "content": "vendor/\nbuild/\nDockerfile\n.git\n.gitignore"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# Status\n\nREADY/IN DEVELOPMENT/HOLD\n\n# Migrations\n\nYES (describe migration) | NO\n\n# Description\n\nA few sentences describing the overall goals of the pull request's commits.\n\n# List of fixes # (issue)\n  - fix #X\n  - fix #N\n\n# Type of change\n\nPlease delete options that are not relevant.\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n- [ ] This change requires a documentation update\n\n# How Has This Been Tested?\n\nPlease describe the tests that you ran to verify your changes.\n\nProvide instructions so we can reproduce.\n\nPlease also list any relevant details for your test configuration\n\n# Checklist:\n\n- [ ] My code follows the style guidelines of this project\n- [ ] I have performed a self-review of my own code\n- [ ] I have commented my code, particularly in hard-to-understand areas\n- [ ] I have made corresponding changes to the documentation\n- [ ] My changes generate no new warnings\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] New and existing unit tests pass locally with my changes\n- [ ] Any dependent changes have been merged and published in downstream modules\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin\ntestbin\n\n# Tarballs\n*.tar*\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Skip vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.vscode\n.idea\n*.swp\n*.swo\n*~\n\nsecrets-manager\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## Unreleased\n\n- [FEATURE] Add support for Azure KeyVault backend\n\n## v2.0.1 2022-04-04\n\n- [BUG] Fix nil pointer dereference bug in controller's regular kubernetes client\n\n## v2.0.0 2022-02-21\n\n- [FEATURE] Populating Labels and Annotations from the SecretDefinition to the generated Secret.\n- [ENHANCEMENT] Updates the `managed-by` and `updatedAt` labels to more closely match k8s recommended values (using annotations and recommended labels), as seen below:\n```yaml\nannotations:\n    secrets-manager.tuenti.io/lastUpdateTime: 2020-04-22T14.34.17Z\nlabels:\n   app.kubernetes.io/managed-by: secrets-manager\n```\n- [ENHANCEMENT] Update to kubebuilder 3.1.0\n\n## v1.1.0 2021-01-05\n\n- [BEHAVIOUR] Using flags watch-namespaces / exclude-namespaces. They interact differently.\n  - All namespaces are watched. A namespace is excluded if it is specified within the *exclude-namespaces* flag.\n- [FEATURE] Adding **auth-method** param to specify Vault authentication method.\n  - Adding vault authentication method from kubernetes. With **auth-method** param set to **kubernetes**.\n- [BUG] set the controller name to something unique avoid 'duplicate metrics collector registration attempted' errors.\n\n## no code related changes 2020-04-28\n\n- No logic changes in secrets-manager. But we are going to stablish some changes in the project management:\n  - Now versions are going to follow [semantic versioning](https://semver.org/) where version tags are going to have the 'v' preffix, they are going to be just:\n    - v{major}.{minor}.{patch}, where major, minor and path are integers\n\n  - From now on we are going to push release candidates to the [docker registry](https://hub.docker.com/repository/docker/tuentitech/secrets-manager)\n\n## v1.0.2 2019-11-17\n\nStable release. Adds watching specific namespaces (see v1.0.2-rc.1) and some minor fixes.\n\n### Fixes\n- [#47 missing return provokes wrong metrics delivery](https://github.com/tuenti/secrets-manager/issues/47)\n- [#37 Unable to build 1.0.1](https://github.com/tuenti/secrets-manager/issues/37)\n\n## v1.0.2-rc.1 2019-09-30\n\n### Fixes\n- [#38 add the ability to watch secretDefinitions scoped to a particular namespace](https://github.com/tuenti/secrets-manager/issues/38)\n\n## v1.0.1 2019-08-14\n### Fixes\n- Deleting a `SecretDefinition` hangs if the corresponding secret does not exist.\n- Invalid metric names in README\n\n### Deprecates\n- Unused prometheus metrics `secrets_manager_controller_update_secret_errors_total` and `secrets_manager_controller_last_updated`\n\n## v1.0.0 2019-07-29\nStable release\n\n## v1.0.0-rc.1 2019-07-12\nRelease Candidate 1\n## v1.0.0-snapshot-1 2019-07-09\n\n### Added\n- `SecretDefinitions` created via `CustomResourceDefinitions`\n- If the `SecretDefinion` gets deleted, the corresponding secret will be removed too.\n- New zap logger based on [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) project. Use `-enable-debug-log` to get a more verbose output.\n### Fixes\n- [#2 Switch to custom resource definitions instead of a single configmap](https://github.com/tuenti/secrets-manager/issues/2)\n- [#8 Secrets deletion proposal](https://github.com/tuenti/secrets-manager/issues/8)\n\n### Breaking changes\n- congimaps won't be supported to define secrets, and so that won't work all the relevant configmap flags.\n- log.format and log.level flags won't work anymore, as we have changed the logger to addapt to the [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) project. Use `-enable-debug-log` to get a more verbose output.\n- `config.backend-scrape-interval` no longer works as we check the backend state on every reconcile event. Use `reconcile-period` instead\n- `listen-address` removed in favor of `metrics-addr`\n\n## v1.0.0-snapshot 2019-05-22\n\n### Added\n- Enable Vault AppRole auth method and `secrets-manager` will try to re-login every time it fails to fetch the token. This will make `secrets-manager` more resilient to issues connecting to Vault that potentially caused the token to expire.\n- New `secrets_manager_login_errors_total` Prometheus metric.\n\n### Fixes\n- [#27-Implement AppRole auth](https://github.com/tuenti/secrets-manager/issues/27)\n\n### Breaking changes\n- Token based login won't be supported, as re-login with and invalid token won't make `secrets-manager` to self-heal.\n- This makes this new version not backward compatible with previous v0.2.0\n\n## v0.2.0 - 2019-03-29\n\nStable\n## v0.2.0-rc.2 - 2019-01-29\n\n### Added\n- New `secrets_manager_vault_max_token_ttl` metric, so a user could alert based on this and `secrets_manager_token_ttl`\n- New `secrets_manager_secret_last_sync_status` metric, that shows wether the secret succeeded or not in last synchronization iteration\n\n### Fixed\n- Backend timeout not properly set through flags\n- Deprecates `secrets_manager_vault_token_expired` metric as it was quite confusing since it's not really possible for `secrets-manager` to know when the token it's expired, just when it's \"close to expire\".\n- Renames counter metrics to follow the Prometheus naming standard with the `_total` suffix instead of `_count`.\n- Simplifies prometheus token renewal metrics by merging `secrets_manager_vault_token_lookup_errors_count` and `secrets_manager_vault_token_renew_errors_count` into one single metric `secrets_manager_vault_token_renewal_errors_total` with one more dimension called `vault_operation` which will be one of `lookup-self, renew-self, is-renewable`.\n\n## v0.2.0-rc.1 - 2019-01-21\n\n### Added\n- Enable prometheus metrics\n- `cfg.backend-timeout` flag to specify a connection timeout to the secrets backend.\n- `listen-address` flag to specify the listen address of the HTTP API\n\n### Fixed\n- Bad return condition in startTokenRenewer, so token lookup won't\n  happen in case of a token revoked.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to secrets-manager\n\nIf you find something missing or not working as expected, we are happy to receive your pull requests! These are the guidelines to follow to make your awesome code part of _secrets-manager_\n\n## Steps to Contribute\n\n* Create an Issue\n* Fork _secrets-manager_ and work on the new feature/bugfix\n* Open a Pull Request to propose your changes:\n    * New features: into master\n    * Bugfixes: into latest release branch\n\n## Git Branches Model\n\nWe have 4 kind of branches in _secrets-manager_ development:\n\n* **`master`**: is the integration branch, new features are merged into this. Developers create their feature branch and open a Pull Request to master to propose the changes.\n* **`release-*` branches**: are used to prepare every new _secrets-manager_ minor release (`<major>.<minor>`, ie: 0.1, 1.2, etc). In these branches we don't merge new features, only bugfixes. Once a new bug is fixed in the latest release branch, it has to be merged into `master` too.\n* **feature branches**: branches created by developers to implement new functinalities in _secrets-manager_. They can only be merged into master.\n* **bugfix branches**: branches with fixes for bugs, they can only be merged into release branches.\n\nGiven this, we use `master` as our integration branch and maintain separated branches for each minor release. New features should be merged into `master` branch, and bugfixes should be merged into `release-*` branches. For a patch release, work on the corresponding minor release branch.\n\n### Publishing the release\n\nOnce the work in the release branch is stabilized, create a tag in the branch.\n\n### Release Candidates\n\nRelease candidates are treated as normal releases, but they must append `-rc[0-9]*` to the branch name."
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "\nDOCKER_REGISTRY = \"registry.hub.docker.com\"\nORGANIZATION = \"tuentitech\"\nBINARY_NAME=secrets-manager\nVERSION=$(shell deploy/version/get.sh)\nBUILD_FLAGS=-ldflags \"-X main.version=${VERSION}\"\n\n# Image URL to use all building/pushing image targets\nIMG = ${DOCKER_REGISTRY}/${ORGANIZATION}/${BINARY_NAME}:${VERSION}\n\n# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)\nCRD_OPTIONS ?= \"crd:trivialVersions=true,preserveUnknownFields=false\"\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# This is a requirement for 'setup-envtest.sh' in the test target.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk commands is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t$(CONTROLLER_GEN) object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\nvet: ## Run go vet against code.\n\tgo vet ./...\n\nENVTEST_ASSETS_DIR=$(shell pwd)/testbin\ntest: manifests generate fmt vet ## Run tests.\n\tmkdir -p ${ENVTEST_ASSETS_DIR}\n\ttest -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.8.3/hack/setup-envtest.sh\n\tsource ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out\n\n##@ Build\n\nbuild: generate fmt vet ## Build manager binary.\n\tgo build ${BUILD_FLAGS} -o bin/${BINARY_NAME} main.go\n\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./main.go\n\n# Run tests in docker\ndocker-test:\n\tdocker-compose run tests\n\n# Build release docker image\ndocker-build: docker-test\n\tdocker build . \\\n\t\t--file ./deploy/Dockerfile \\\n\t\t--target release \\\n\t\t--build-arg SECRETS_MANAGER_VERSION=${VERSION} \\\n\t\t--tag ${IMG}\n\t@echo \"updating kustomize image patch file for manager resource\"\n\tsed -i'' -e 's@image: .*@image: '\"${IMG}\"'@' ./config/default/manager_image_patch.yaml\n\n# Push the docker image\ndocker-push:\n\tdocker push ${IMG}\n\nupdate-major-version:\n\tdeploy/version/update.sh --major\n\nupdate-minor-version:\n\tdeploy/version/update.sh --minor\n\nupdate-patch-version:\n\tdeploy/version/update.sh --patch\n\n##@ Deployment\n\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t$(KUSTOMIZE) build config/crd | kubectl apply -f -\n\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.\n\t$(KUSTOMIZE) build config/crd | kubectl delete -f -\n\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}\n\t$(KUSTOMIZE) build config/default | kubectl apply -f -\n\nundeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.\n\t$(KUSTOMIZE) build config/default | kubectl delete -f -\n\n\nCONTROLLER_GEN = $(shell pwd)/bin/controller-gen\ncontroller-gen: ## Download controller-gen locally if necessary.\n\t$(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1)\n\nKUSTOMIZE = $(shell pwd)/bin/kustomize\nkustomize: ## Download kustomize locally if necessary.\n\t$(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)\n\n# go-get-tool will 'go get' any package $2 and install it to $1.\nPROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))\ndefine go-get-tool\n@[ -f $(1) ] || { \\\nset -e ;\\\nTMP_DIR=$$(mktemp -d) ;\\\ncd $$TMP_DIR ;\\\ngo mod init tmp ;\\\necho \"Downloading $(2)\" ;\\\nGOBIN=$(PROJECT_DIR)/bin go get $(2) ;\\\nrm -rf $$TMP_DIR ;\\\n}\nendef\n"
  },
  {
    "path": "PROJECT",
    "content": "domain: secrets-manager.tuenti.io\nlayout:\n- go.kubebuilder.io/v3\nprojectName: secrets-manager\nrepo: github.com/tuenti/secrets-manager\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: secrets-manager.tuenti.io\n  group: secretsmanager\n  kind: SecretDefinition\n  path: github.com/tuenti/secrets-manager/api/v1alpha1\n  version: v1alpha1\nversion: \"3\"\n"
  },
  {
    "path": "README.md",
    "content": "# secrets-manager\n[![CircleCI](https://circleci.com/gh/tuenti/secrets-manager/tree/master.svg?style=svg)](https://circleci.com/gh/tuenti/secrets-manager/tree/master)\n[![Go Report Card](https://goreportcard.com/badge/github.com/tuenti/secrets-manager)](https://goreportcard.com/report/github.com/tuenti/secrets-manager)\n[![codecov](https://codecov.io/gh/tuenti/secrets-manager/branch/master/graph/badge.svg)](https://codecov.io/gh/tuenti/secrets-manager)\n\nA tool to keep your Kubernetes secrets in sync with Vault\n\n# Rationale\n\nLots of companies use [Vault](https://www.vaultproject.io) as their secrets store backend for multiple kind of secrets and different purposes. Kubernetes brings a nice secrets API, but it means that you have two different sources of truth for your secrets.\n\n*secrets-manager* tries to solve this, by reading secrets from Vault and comparing them to Kubernetes secrets, creating and updating them as you do it in Vault.\n\n# How does it compare to other tools?\n\n- [cert-manager](https://github.com/jetstack/cert-manager). *cert-manager* solves a different issue, automation around issuing and renewing certificates. It integrates with Let's Encrypt and Vault (using the pki backend) being those the certificates issuer. While this is really powerful and really a tool which is fully compatible with *secrets-manager*, it does not really sync a secret from a secret backend. *secrets-manager* is a more generic tool where you can sync certificates or any kind of secret from the source of truth of your secrets to Kubernetes secrets.\n\n- [vault-operator](https://github.com/coreos/vault-operator). This manages vault clusters in Kubernetes, so it is a completely different tool.\n\n- [vault-crd](https://github.com/DaspawnW/vault-crd). This is the tool that really inspired *secrets-manager*. We opened this [issue](https://github.com/DaspawnW/vault-crd/issues/4) asking for token renewal or other login mechanism. While the author is very responsive answering, we could not wait for an implementation and since we were are more familiar with Go than Java we decided to write *secrets-manager*. We are very thankful to the author of *vault-crd*, since it has been really inspiring. Some differences:\n  - *vault-crd* only supports Hashicorp Vault as its secrets manager, while *secrets-manager* has been designed to support other backends (we only support Vault for now, though).\n  - *vault-crd* supports KV1, KV2 and PKI secret engines. *secrets-manager* supports KV1 and KV2. It is on our roadmap to support more secret engines.\n\n# How it works\n\n*secrets-manager* will login to Vault using AppRole credentials and it will start a reconciliation loop watching for changes in `SecretsDefinition` objects. In background it will run two main operations:\n\n- If Vault token is close to expire and if that's the case, renewing it. If it can't renew, it will try to re-login.\n- It will re-queue `SecretsDefinition` events and in every event loop it will verify if the current Kubernetes secret it is in the desired state by comparing it with the data in Vault and creating/updating them accordingly\n\n## Custom Resource Definition (CRD)\n\n*secrets-manager* now uses [Custom Resource Definitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) to extend Kubernetes APIs with a new `SecretDefinition` object that it will watch.\n\nTo install the CRD in your cluster: `kubectl apply -f crd.yaml`\n\n\n### Secrets Definition\n\n- `name`: This will be the name of the secret created in Kubernetes.\n- `type`: Kubernetes secret type. One of `kubernetes.io/tls`, `Opaque`.\n- `keysMap`: This will contain the Kubernetes secret data keys as a map of datasources. Each datasource will contain the way to access the secret in the secret backend source of truth, via a `path` and  a `key`. And optional `encoding` key can be provided if your secrets are codified in `base64`. The absence of `encoding` or `encoding: text` means no encoding.\n\n**NOTE**: We let the user all the responsibility to set the whole Vault path. So it is important to know which path a secret engine needs to be set. For instance, with the KV version 1 all secrets are stored in `secret/` whereas with the KV version 2, all secrets go under `secret/data/`\n\nAn example of a `secretdefinition` object\n\n```\n$ cat > secretdefinition-sample.yaml <<EOF\n---\napiVersion: secrets-manager.tuenti.io/v1alpha1\nkind: SecretDefinition\nmetadata:\n  name: secretdefinition-sample\nspec:\n  # Add fields here\n  name: supersecretnew\n  keysMap:\n    decoded:\n      path: secret/data/pathtosecret1\n      encoding: base64\n      key: value\n    raw:\n      path: secret/data/pathtosecret1\n      key: value\n\nEOF\n```\n\nTo deploy it just run `kubectl apply -f secretdefinition-sample.yaml`\n## Flags\n\n| Flag | Default | Description |\n| ------ | ------- | ------ |\n| `backend`| vault | Selected backend. One of vault or azure-kv |\n| `enable-debug-log` | `false` | Enable this to get more logs verbosity and debug messages.|\n| `enable-leader-election` | `false` | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.|\n| `reconcile-period`| 5s | How often the controller will re-queue secretdefinition events |\n| `config.backend-timeout`| 5s | Backend connection timeout |\n| `azure-kv.name` | `\"\"` | Azure KeyVault name. `AZURE_KV_NAME` environment would take precedence |\n| `azure-kv.tenant-id` | `\"\"` | Azure KeyVault Tenant ID. `AZURE_TENANT_ID` environment would take precedence |\n| `azure-kv.client-id` | `\"\"` | Azure KeyVault Cliend ID used to authenticate. `AZURE_CLIENT_ID` environment would take precedence |\n| `azure-kv.client-secret` | `\"\"` | Azure KeyVault Client Secret used to authenticate. `AZURE_CLIENT_SECRET` environment would take precedence |\n| `azure-kv.managed-client-id` | `\"\"` | Azure Managed Identity Client ID used to authenticate. `AZURE_MANAGED_CLIENT_ID` environment would take precedence |\n| `azure-kv.managed-resource-id` | `\"\"` | Azure Managed Identity Resource ID used to authenticate. `AZURE_MANAGED_RESOURCE_ID` environment would take precedence |\n| `vault.url` | https://127.0.0.1:8200 | Vault address. `VAULT_ADDR` environment would take precedence. |\n| `vault.role-id` | `\"\"` | Vault appRole `role_id`. `VAULT_ROLE_ID` environment would take precedence. |\n| `vault.secret-id` | `\"\"` | Vault appRole `secret_id`. `VAULT_SECRET_ID` environment would take precedence. |\n| `vault.engine` | kv2 | Vault secrets engine to use. Only key/value engines supported. Default is kv version 2 |\n| `vault.auth-method` | approle | Vault authentication method. Supported: approle, kubernetes. |\n| `vault.approle-path` | approle | Vault approle login path |\n| `vault.kubernetes-path` | kubernetes | Vault kubernetes login path |\n| `vault.kubernetes-role` | `\"\"` | Vault kubernetes role name |\n| `vault.max-token-ttl` | 300 |Max seconds to consider a token expired. |\n| `vault.token-polling-period` | 15s | Polling interval to check token expiration time. |\n| `vault.renew-ttl-increment` | 600 | TTL time for renewed token. |\n| `metrics-addr` | `:8080` | The address to listen on for HTTP requests. |\n| `controller-name` | SecretDefinition | If running secrets manager in multiple namespaces, set the controller name to something unique avoid 'duplicate metrics collector registration attempted' errors. |\n| `watch-namespaces` | `\"\"` | Comma separated list of namespaces that secrets-manager will watch for `SecretDefinitions`. By default all namespaces are watched. |\n| `exclude-namespaces` | `\"\"` | Comma separated list of namespaces that secrets-manager will not watch for `SecretDefinitions`. By default all namespaces are watched. Note that if you exclude and watch the same namespace, excluding it will be prioritized. |\n\n## RBAC\n\nSecrets Manager can be run in one of 2 ways:\n\n* Global secrets management in all namespaces for the whole of a Kuberentes cluster\n* Manage specific namespaces\n\nIn order for Secrets Manager to act as a manager for all Namespaces it requires a ClusterRole that enables it to manage all secrets and secretdefinitions in the entire Kubernetes cluster as in the [config/rbac/role.yaml](config/rbac/role.yaml) and [config/rbac/rolebinding.yaml](config/rbac/rolebinding.yaml) examples.\n\nAlternatively if you use the `watch-namespaces` argument to limit secretdefinition monitoring to sepcific namespaces then you can just give the `serviceAccount` that `secrets-manager` is running as a standard role and a rolebinding in each of the namespaces that you want it to manage as shown in the [config/rbac/secrets_manager_role.yaml](config/rbac/secrets_manager_role.yaml) and [config/rbac/secrets_manager_role_binding.yaml](config/rbac/secrets_manager_role_binding.yaml) examples. Alternatively you can still use a cluster role if you so wish.\n\nTo be able to interact with `secretdefinition` resources using the standard `admin`, `edit` and `view` Kubernetes native roles, you need to create the following `ClusterRole` aggregations:\n\n- [config/rbac/secretdefinitions_admin_clusterrole_aggregation.yaml](config/rbac/secretdefinitions_admin_clusterrole_aggregation.yaml)\n- [config/rbac/secretdefinitions_view_clusterrole_aggregation.yaml](config/rbac/secretdefinitions_view_clusterrole_aggregation.yaml)\n\nMore information about aggregated `ClusterRoles` can be found at [kubernetes.io > Using RBAC Authorization > Aggregated ClusterRoles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles).\n\n## Prometheus Metrics\n\n`secrets-manager` exposes the following [Prometheus](https://prometheus.io) metrics at `http://$cfg.listen-addr/metrics`:\n\n| Metric| Type| Description| Labels|\n| ------| ----|------------| ------|\n|`secrets_manager_vault_max_token_ttl` | Gauge | `secrets-manager` max Vault token TTL | `\"vault_address\", \"vault_engine\", \"vault_version\", \"vault_cluster_id\", \"vault_cluster_name\"` |\n|`secrets_manager_vault_token_ttl` | Gauge | Vault token TTL | `\"vault_address\", \"vault_engine\", \"vault_version\", \"vault_cluster_id\", \"vault_cluster_name\"` |\n|`secrets_manager_vault_token_renewal_errors_total`| Counter | Vault token renewal errors counter | `\"vault_address\", \"vault_engine\", \"vault_version\", \"vault_cluster_id\", \"vault_cluster_name\", \"vault_operation\", \"error\"` |\n|`secrets_manager_controller_secret_read_errors_total`| Counter | Errors total count when reading a secret from Kubernetes | `\"name\", \"namespace\"` |\n| `secrets_manager_controller_sync_errors_total`| Counter |Secrets synchronization total errors.|`\"name\", \"namespace\"`|\n|`secrets_manager_controller_last_sync_status`| Gauge |The result of the last sync of a secret. 1 = OK, 0 = Error|`\"name\", \"namespace\"`|\n\n## Getting Started with Vault\n\n### Vault Policies\n\nWe do recommend you use policies to make sure you grant `secrets-manager` only to those secrets you need available in your Kubernetes cluster. An example of simple policy could be:\n\n```\npath \"secret/data/my-k8s-cluster/*\" {\n  capabilities = [\"read\"]\n}\n```\n\nTo create this policy:\n\n```\n$ cat > my-policy.hcl <<EOF\npath \"secret/data/my-k8s-cluster/*\" {\n  capabilities = [\"read\"]\n}\nEOF\n\n$ cat my-policy.hcl | vault policy write my-policy -\n```\n\n### Vault Tokens\n\nVault tokens will be renewed by `secrets-manager` if the `ttl` is lower than `vault.max-token-ttl` and the token is renewable. But as per Vault's [documentation](https://www.vaultproject.io/docs/concepts/tokens.html#the-general-case), regular tokens will have their own max TTL that it's calculated on every renewal, so that a token will eventually expire. This can be ok for your use case, but for others a [periodic token](https://www.vaultproject.io/docs/concepts/tokens.html#periodic-tokens) could be much more convinient. In the case of a periodic token, the `period` will invalidate the `vault.renew-ttl-increment` option.\n\n\n### Vault AppRole\nVault token as a login mechanism has been deprecated in favor of the [AppRole](https://www.vaultproject.io/docs/auth/approle.html) authentication method for `secrets-manager`.\n`secrets-manager` will still renew the token obtained after login in, but will make `secrets-manager` more resilient in case of a token has expired due to network issues, Vault sealed, etc.\n\nSo instead of expecting a token, `secrets-manager` expects a `role_id` and a `secret_id` to connect to Vault.\n\nTo create a role with a permanent `secret_id` attached to a policy:\n\n`$ vault write auth/approle/role/secrets-manager policies=my-policy secret_id_num_uses=0 secret_id_ttl=0`\n\nTo get a `secret_id`:\n\n`$ vault write -force auth/approle/role/secrets-manager/secret-id`\n\nTo get the `role_id`:\n\n`$ vault read auth/approle/role/secrets-manager/role-id`\n\n### Vault Kubernetes Authentication\nIn addition to `appRole`, `secrets-manager` can authenticate to Vault using its own Kubernetes `serviceAccount`. Follow the [Vault Kubernetes auth guide](https://www.vaultproject.io/docs/auth/kubernetes) to enable it and configure it.\n\nExample:\n\n```sh\n$ cat > secrets-manager-role.json <<EOF\n{\n  \"bound_service_account_names\": [ \"secrets-manager\" ],\n  \"bound_service_account_namespaces\": [ \"my-namesapace\" ],\n  \"policies\": [ \"my-policy\" ],\n  \"max_ttl\": 3600\n}\nEOF\n\n$ vault write auth/kubernetes/role/secrets-manager @secrets-manager-role.json\n```\n\n## Getting Started with Azure KeyVault\n\n### Deploy Azure KeyVault\n\nIf you haven't still deployed an Azure KeyVault server, you can do it with Azure CLI:\n\n```\n$ az keyvault create --location <location> --name <keyvault_name> --resource-group <resource_group>\n```\n\n### Azure authentication methods\n\nSecrets manager currently supports the following authentication methods for Azure:\n- [*Azure Managed Identity*](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview).\n This is the preferred method if secrets manager is running in an Azure VM or Azure Kubernetes Service (AKS) cluster.\n For further information about how to use managed identities in Azure Kubernetes Service (AKS) see [documentation](https://docs.microsoft.com/en-us/azure/aks/use-managed-identity).\n- [*Azure Service Principal*](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals).\n This method can be used if secrets manager runs outside of Azure service, although it requires more configuration steps.\n\n### Create a Service Principal to access secrets\n\n`secrets-manager` uses Azure Service Principal to authenticate against Azure KeyVault API. It's recommended\nto use an isolated Service Principal to limit its access to the least required resources:\n\n```\n$ az ad sp create-for-rbac --name \"<service_principal_name>\" --role Contributor --scopes /subscriptions/{SubID}/resourceGroups/{ResourceGroup}\n```\n\nThis command will output a JSON object like the following:\n\n```\n{\n  \"appId\": \"<AppID>\", // ClientId\n  \"displayName\": \"<ServicePrincipleName>\",\n  \"name\": \"http://<ServicePrincipleName>\",\n  \"password\": \"<Password>\", // ClientSecret\n  \"tenant\": \"<TenantId>\"\n}\n```\n\nThe fields needed by `secrets-manager` to authenticate are `appId` (`azure-kv.client-id`),\n`password` (`azure-kv.client-secret`) and `tenant` (`azure-kv.tenant-id`).\n\nOnce the Service Principal is created, add permission to access Azure KeyVault's secrets with:\n\n```\n$ az keyvault set-policy --name <keyvault_name> --spn <appId> --secret-permissions get list set delete\n```\n\n## Versioning\n\nRight now versioning it's a manually task.\nDepending on the kind of the update we would apply a major, minor or patch update given that we follow [semantic versioning](https://semver.org/).\n\nBefore building release images, we should run one of the following commands:\n- make update-major-version\n- make update-minor-version\n- make update-patch-version\n\n## Deployment\n*secrets-manager* has been designed to be deployed in Kubernetes, you will find a full deployment example in the [config/samples](config/samples) folder.\n\n## Credits & Contact\n\n*secrets-manager* is developed and maintained by [Tuenti Technologies S.L.](http://github.com/tuenti)\n\nYou can follow Tuenti engineering team on Twitter [@tuentieng](http://twitter.com/tuentieng).\n\n## License\n\n*secrets-manager* is available under the Apache License, Version 2.0. See LICENSE file\nfor more info.\n"
  },
  {
    "path": "api/v1alpha1/groupversion_info.go",
    "content": "/*\nCopyright 2021.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1alpha1 contains API Schema definitions for the secretsmanager v1alpha1 API group\n//+kubebuilder:object:generate=true\n//+groupName=secrets-manager.tuenti.io\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nconst (\n\tGroup   = \"secrets-manager.tuenti.io\"\n\tVersion = \"v1alpha1\"\n)\n\nvar (\n\t// GroupVersion is group version used to register these objects\n\tGroupVersion = schema.GroupVersion{Group: Group, Version: Version}\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme\n\tSchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "api/v1alpha1/secretdefinition_types.go",
    "content": "/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// DataSource represents the actual source of truth path for a secret\ntype DataSource struct {\n\t// Path to the actual secret\n\tPath string `json:\"path\"`\n\t// Key where the actual secret is stored\n\tKey string `json:\"key\"`\n\t// Encoding type for the secret. Only base64 supported. Optional\n\tEncoding string `json:\"encoding,omitempty\"`\n}\n\n// SecretDefinitionSpec defines the desired state of SecretDefinition\ntype SecretDefinitionSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\tName    string                `json:\"name\"`\n\tType    string                `json:\"type,omitempty\"`\n\tKeysMap map[string]DataSource `json:\"keysMap\"`\n}\n\n// SecretDefinitionStatus defines the observed state of SecretDefinition\ntype SecretDefinitionStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n}\n\n// +kubebuilder:object:root=true\n\n// SecretDefinition is the Schema for the secretdefinitions API\ntype SecretDefinition struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   SecretDefinitionSpec   `json:\"spec,omitempty\"`\n\tStatus SecretDefinitionStatus `json:\"status,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n\n// SecretDefinitionList contains a list of SecretDefinition\ntype SecretDefinitionList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []SecretDefinition `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&SecretDefinition{}, &SecretDefinitionList{})\n}\n"
  },
  {
    "path": "api/v1alpha1/secretdefinition_types_test.go",
    "content": "package v1alpha1\n\n/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport (\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"golang.org/x/net/context\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n)\n\n// These tests are written in BDD-style using Ginkgo framework. Refer to\n// http://onsi.github.io/ginkgo to learn more.\n\nvar _ = Describe(\"SecretDefinition\", func() {\n\tvar (\n\t\tkey              types.NamespacedName\n\t\tcreated, fetched *SecretDefinition\n\t)\n\n\tBeforeEach(func() {\n\t\t// Add any setup steps that needs to be executed before each test\n\t})\n\n\tAfterEach(func() {\n\t\t// Add any teardown steps that needs to be executed after each test\n\t})\n\n\t// Add Tests for OpenAPI validation (or additional CRD features) specified in\n\t// your API definition.\n\t// Avoid adding tests for vanilla CRUD operations because they would\n\t// test Kubernetes API server, which isn't the goal here.\n\tContext(\"Create API\", func() {\n\n\t\tIt(\"should create an object successfully\", func() {\n\n\t\t\tkey = types.NamespacedName{\n\t\t\t\tName:      \"foo\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t}\n\t\t\tcreated = &SecretDefinition{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: SecretDefinitionSpec{\n\t\t\t\t\tName: \"foo\",\n\t\t\t\t\tType: \"Opaque\",\n\t\t\t\t\tKeysMap: map[string]DataSource{\n\t\t\t\t\t\t\"foo\": {\n\t\t\t\t\t\t\tPath:     \"secret/supersecret1\",\n\t\t\t\t\t\t\tKey:      \"foo\",\n\t\t\t\t\t\t\tEncoding: \"text\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tBy(\"creating an API obj\")\n\t\t\tExpect(k8sClient.Create(context.TODO(), created)).To(Succeed())\n\n\t\t\tfetched = &SecretDefinition{}\n\t\t\tExpect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())\n\t\t\tExpect(fetched).To(Equal(created))\n\n\t\t\tBy(\"deleting the created object\")\n\t\t\tExpect(k8sClient.Delete(context.TODO(), created)).To(Succeed())\n\t\t\tExpect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed())\n\t\t})\n\n\t})\n\n})\n"
  },
  {
    "path": "api/v1alpha1/suite_test.go",
    "content": "/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest/printer\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar cfg *rest.Config\nvar k8sClient client.Client\nvar testEnv *envtest.Environment\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecsWithDefaultAndCustomReporters(t,\n\t\t\"v1alpha1 Suite\",\n\t\t[]Reporter{printer.NewlineReporter{}})\n}\n\nvar _ = BeforeSuite(func(done Done) {\n\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths: []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t}\n\n\terr := SchemeBuilder.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\tcfg, err = testEnv.Start()\n\tExpect(err).ToNot(HaveOccurred())\n\tExpect(cfg).ToNot(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).ToNot(HaveOccurred())\n\tExpect(k8sClient).ToNot(BeNil())\n\n\tclose(done)\n}, 60)\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\terr := testEnv.Stop()\n\tExpect(err).ToNot(HaveOccurred())\n})\n"
  },
  {
    "path": "api/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright 2021.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *DataSource) DeepCopyInto(out *DataSource) {\n\t*out = *in\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataSource.\nfunc (in *DataSource) DeepCopy() *DataSource {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(DataSource)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SecretDefinition) DeepCopyInto(out *SecretDefinition) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tout.Status = in.Status\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretDefinition.\nfunc (in *SecretDefinition) DeepCopy() *SecretDefinition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SecretDefinition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *SecretDefinition) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SecretDefinitionList) DeepCopyInto(out *SecretDefinitionList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]SecretDefinition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretDefinitionList.\nfunc (in *SecretDefinitionList) DeepCopy() *SecretDefinitionList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SecretDefinitionList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *SecretDefinitionList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SecretDefinitionSpec) DeepCopyInto(out *SecretDefinitionSpec) {\n\t*out = *in\n\tif in.KeysMap != nil {\n\t\tin, out := &in.KeysMap, &out.KeysMap\n\t\t*out = make(map[string]DataSource, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretDefinitionSpec.\nfunc (in *SecretDefinitionSpec) DeepCopy() *SecretDefinitionSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SecretDefinitionSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SecretDefinitionStatus) DeepCopyInto(out *SecretDefinitionStatus) {\n\t*out = *in\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretDefinitionStatus.\nfunc (in *SecretDefinitionStatus) DeepCopy() *SecretDefinitionStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SecretDefinitionStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "backend/azure_kv.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"fmt\"\n\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azcore\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azidentity\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nvar akvMetrics *azureKVMetrics\n\nconst (\n\tazureKVEndpoint = \"vault.azure.net\"\n)\n\ntype azureKVClient struct {\n\tclient       *azsecrets.Client\n\tkeyvaultName string\n\tcontext      context.Context\n\tlogger       logr.Logger\n}\n\n// getAzureCredential finds the better way to authenticate to Azure\nfunc getAzureCredential(ctx context.Context, logger logr.Logger, cfg Config) (azcore.TokenCredential, error) {\n\tif cfg.AzureKVManagedClientID != \"\" || cfg.AzureKVManagedResourceID != \"\" {\n\t\topts := azidentity.ManagedIdentityCredentialOptions{}\n\t\tif cfg.AzureKVManagedClientID != \"\" {\n\t\t\topts.ID = azidentity.ClientID(cfg.AzureKVManagedClientID)\n\t\t} else if cfg.AzureKVManagedResourceID != \"\" {\n\t\t\topts.ID = azidentity.ResourceID(cfg.AzureKVManagedResourceID)\n\t\t}\n\n\t\tmanaged, err := azidentity.NewManagedIdentityCredential(&opts)\n\t\tif err == nil {\n\t\t\tlogger.Info(\"Azure Managed Identity will be used as authentication method\")\n\t\t\treturn managed, err\n\t\t}\n\t}\n\n\tspSecret, err := azidentity.NewClientSecretCredential(cfg.AzureKVTenantID, cfg.AzureKVClientID, cfg.AzureKVClientSecret, nil)\n\tif err == nil {\n\t\tlogger.Info(\"Azure Service Principal will be used as authentication method\")\n\t\treturn spSecret, err\n\t}\n\n\treturn nil, goerrors.New(\"Unable to authenticate to Azure API using any method\")\n}\n\nfunc azureKeyVaultClient(ctx context.Context, l logr.Logger, cfg Config) (*azureKVClient, error) {\n\tlogger := l.WithName(\"azure-kv\").WithValues(\n\t\t\"azure_kv_name\", cfg.AzureKVName,\n\t\t\"azure_kv_tenant\", cfg.AzureKVTenantID)\n\n\tcred, err := getAzureCredential(ctx, logger, cfg)\n\tif err != nil {\n\t\tlogger.Error(err, \"Error while authenticating to Azure\")\n\t\treturn nil, err\n\t}\n\takvMetrics = newAzureKVMetrics(cfg.AzureKVName, cfg.AzureKVTenantID)\n\tvaultEndpoint := fmt.Sprintf(\"https://%s.%s\", cfg.AzureKVName, azureKVEndpoint)\n\takvClient, err := azsecrets.NewClient(vaultEndpoint, cred, nil)\n\n\tif err != nil {\n\t\tlogger.Error(err, \"Error while creating Azure KV client\")\n\t\takvMetrics.updateLoginErrorsTotalMetric()\n\t\treturn nil, err\n\t}\n\n\tlogger.Info(\"Successfully logged into Azure KeyVault\")\n\n\tclient := azureKVClient{\n\t\tclient:       akvClient,\n\t\tkeyvaultName: cfg.AzureKVName,\n\t\tcontext:      ctx,\n\t\tlogger:       logger,\n\t}\n\n\treturn &client, err\n}\n\nfunc (c *azureKVClient) ReadSecret(path string, key string) (string, error) {\n\tdata := \"\"\n\n\t// TODO: Add support for secret version?\n\tresult, err := c.client.GetSecret(c.context, path, nil)\n\n\tif err != nil {\n\t\terrorType := errors.UnknownErrorType\n\t\tvar responseError *azcore.ResponseError\n\t\tif goerrors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode == 404 {\n\t\t\t\terrorType = errors.BackendSecretNotFoundErrorType\n\t\t\t}\n\t\t\tif responseError.StatusCode == 403 {\n\t\t\t\terrorType = errors.BackendSecretForbiddenErrorType\n\t\t\t}\n\t\t}\n\t\takvMetrics.updateSecretReadErrorsTotalMetric(path, errorType)\n\t\treturn data, err\n\t}\n\n\tdata = *result.Value\n\treturn data, err\n}\n"
  },
  {
    "path": "backend/azure_kv_metrics.go",
    "content": "package backend\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\tazureKVLabelNames            = []string{\"azure_kv_name\", \"azure_kv_tenant\"}\n\tazureKVSecretReadErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"azure_kv\",\n\t\tName:      \"read_secret_errors_total\",\n\t\tHelp:      \"AzureKV read operations counter\",\n\t}, append(azureKVLabelNames, secretLabelNames...))\n\tazureKVLoginErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"azure_kv\",\n\t\tName:      \"login_errors_total\",\n\t\tHelp:      \"AzureKV login errors counter\",\n\t}, azureKVLabelNames)\n)\n\ntype azureKVMetrics struct {\n\tlabels map[string]string\n}\n\nfunc newAzureKVMetrics(keyvaultName string, tenantID string) *azureKVMetrics {\n\tlabels := make(map[string]string, len(azureKVLabelNames))\n\tlabels[\"azure_kv_name\"] = keyvaultName\n\tlabels[\"azure_kv_tenant\"] = tenantID\n\n\treturn &azureKVMetrics{labels: labels}\n}\n\nfunc (vm *azureKVMetrics) updateSecretReadErrorsTotalMetric(path string, errorType string) {\n\tazureKVSecretReadErrorsTotal.WithLabelValues(\n\t\tvm.labels[\"azure_kv_name\"],\n\t\tvm.labels[\"azure_kv_tenant\"],\n\t\tpath,\n\t\t\"\",\n\t\terrorType,\n\t).Inc()\n}\n\nfunc (vm *azureKVMetrics) updateLoginErrorsTotalMetric() {\n\tazureKVLoginErrorsTotal.WithLabelValues(\n\t\tvm.labels[\"azure_kv_name\"],\n\t\tvm.labels[\"azure_kv_tenant\"],\n\t).Inc()\n}\n"
  },
  {
    "path": "backend/azure_kv_metrics_test.go",
    "content": "package backend\n\nimport (\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nfunc TestAzureKVUpdateLoginErrorsTotal(t *testing.T) {\n\tmetrics := newAzureKVMetrics(fakeKeyVaultName, fakeKeyVaultTenant)\n\tazureKVLoginErrorsTotal.Reset()\n\tmetrics.updateLoginErrorsTotalMetric()\n\tmetricLoginErrors, _ := azureKVLoginErrorsTotal.GetMetricWithLabelValues(fakeKeyVaultName, fakeKeyVaultTenant)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricLoginErrors))\n}\n\nfunc TestAzureKVUpdateReadSecretErrorsTotal(t *testing.T) {\n\tpath := \"/path/to/secret\"\n\tkey := \"\"\n\n\tmetrics := newAzureKVMetrics(fakeKeyVaultName, fakeKeyVaultTenant)\n\tazureKVSecretReadErrorsTotal.Reset()\n\tmetrics.updateSecretReadErrorsTotalMetric(path, errors.UnknownErrorType)\n\tmetricSecretReadErrorsTotal, _ := azureKVSecretReadErrorsTotal.GetMetricWithLabelValues(fakeKeyVaultName, fakeKeyVaultTenant, path, key, errors.UnknownErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal))\n\n\tazureKVSecretReadErrorsTotal.Reset()\n\tmetrics.updateSecretReadErrorsTotalMetric(path, errors.BackendSecretNotFoundErrorType)\n\tmetricSecretReadErrorsTotal, _ = azureKVSecretReadErrorsTotal.GetMetricWithLabelValues(fakeKeyVaultName, fakeKeyVaultTenant, path, key, errors.BackendSecretNotFoundErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal))\n\n\tazureKVSecretReadErrorsTotal.Reset()\n\tmetrics.updateSecretReadErrorsTotalMetric(path, errors.BackendSecretForbiddenErrorType)\n\tmetricSecretReadErrorsTotal, _ = azureKVSecretReadErrorsTotal.GetMetricWithLabelValues(fakeKeyVaultName, fakeKeyVaultTenant, path, key, errors.BackendSecretForbiddenErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal))\n}\n"
  },
  {
    "path": "backend/azure_kv_test.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azcore\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azidentity\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets\"\n\t\"github.com/gorilla/mux\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst (\n\tfakeKeyVaultName   = \"azure-keyvault-fake-name\"\n\tfakeKeyVaultTenant = \"01234567-0123-0123-0123-0123456789ab\"\n\tfakeKeyVaultSecret = \"fake-secret\"\n)\n\nvar akvSecrets = map[string]struct {\n\tvalue  string\n\taccess bool\n}{\n\tfakeKeyVaultSecret: {value: \"some-fake-value\", access: true},\n\t\"exists\":           {value: \"yes\", access: true},\n\t\"internal-error\":   {value: \"\\\"bad-scaped\", access: true},\n\t\"forbidden\":        {value: \"yes\", access: false},\n}\n\nfunc akvGetSecret(w http.ResponseWriter, r *http.Request) {\n\t// Info about what keyvault responses should look like extracted from\n\t// https://github.com/Azure/azure-sdk-for-go/blob/c73b114ded83c0a9c2685336b8b90836c1530cb3/sdk/keyvault/azsecrets/testdata/recordings/TestSetGetSecret.json\n\tvars := mux.Vars(r)\n\tjsonData := \"{}\"\n\tif v, ok := akvSecrets[vars[\"secretName\"]]; ok {\n\t\tif v.access {\n\t\t\tjsonData = fmt.Sprintf(`\n\t\t\t{\n\t\t\t\t\"value\": \"%s\",\n\t\t\t\t\"id\": \"https://%s.vault.azure.net/secrets/%s/3f3b11064811494a8a8b27edf4f0985b\",\n        \"attributes\": {\n          \"enabled\": true,\n          \"created\": 1643130727,\n          \"updated\": 1643130727,\n          \"recoveryLevel\": \"CustomizedRecoverable\\u002BPurgeable\",\n          \"recoverableDays\": 7\n        }\n\t\t\t}`, v.value, fakeKeyVaultName, vars[\"secretName\"])\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusForbidden)\n\t\t}\n\t} else {\n\t\tjsonData = fmt.Sprintf(`\n\t\t\"error\": {\n          \"code\": \"SecretNotFound\",\n          \"message\": \"Secret not found: %s\"\n        }\n\t\t`, vars[\"secretName\"])\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}\n\tvar response interface{}\n\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(\"x-ms-keyvault-network-info\", \"conn_type=Ipv4;addr=72.49.29.93;act_addr_fam=InterNetwork;\")\n\tw.Header().Set(\"x-ms-keyvault-region\", \"westus2\")\n\tw.Header().Set(\"x-ms-keyvault-service-version\", \"1.9.264.2\")\n\tw.Header().Set(\"x-ms-request-id\", \"868ba1d2-efe7-4930-b3ad-d010cf499778\")\n\tw.Header().Set(\"X-Powered-By\", \"ASP.NET\")\n\t// Trick Azure client to make it think everything is legit\n\tw.Header().Set(\n\t\t\"WWW-Authenticate\",\n\t\t\"Bearer authorization=\\u0022https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47\\u0022, resource=\\u0022https://vault.azure.net\\u0022\",\n\t)\n\tjson.NewEncoder(w).Encode(response)\n}\n\n// Copied from https://github.com/Azure/azure-sdk-for-go/blob/35fb64f82ef3b3308f55b1da37c1fec36bdd4166/sdk/keyvault/azsecrets/utils_test.go\ntype FakeCredential struct {\n\taccountName string\n\taccountKey  string\n}\n\nfunc NewFakeCredential(accountName, accountKey string) *FakeCredential {\n\treturn &FakeCredential{\n\t\taccountName: accountName,\n\t\taccountKey:  accountKey,\n\t}\n}\n\nfunc (f *FakeCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (*azcore.AccessToken, error) {\n\treturn &azcore.AccessToken{\n\t\tToken:     \"faketoken\",\n\t\tExpiresOn: time.Date(2040, time.January, 1, 1, 1, 1, 1, time.UTC),\n\t}, nil\n}\n\nfunc TestGetAzureCredential(t *testing.T) {\n\tcases := []struct {\n\t\tcfg Config\n\t\terr bool\n\t\ttyp azcore.TokenCredential\n\t\tmsg string\n\t}{\n\t\t{\n\t\t\tConfig{},\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"Empty config should not be able to generate any client\",\n\t\t},\n\t\t{\n\t\t\tConfig{AzureKVManagedClientID: \"fake-client-id\"},\n\t\t\tfalse,\n\t\t\tnew(azidentity.ManagedIdentityCredential),\n\t\t\t\"Managed identity client should be generated using managed client ID\",\n\t\t},\n\t\t{\n\t\t\tConfig{AzureKVManagedResourceID: \"fake-resource-id\"},\n\t\t\tfalse,\n\t\t\tnew(azidentity.ManagedIdentityCredential),\n\t\t\t\"Managed identity client should be generated using managed resource ID\",\n\t\t},\n\t\t{\n\t\t\tConfig{\n\t\t\t\tAzureKVTenantID: \"fake-tenant-id\",\n\t\t\t},\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"Incomplete config should not generate any client (tenant)\",\n\t\t},\n\t\t{\n\t\t\tConfig{\n\t\t\t\tAzureKVClientID: \"fake-client-id\",\n\t\t\t},\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"Incomplete config should not generate any client (clientID)\",\n\t\t},\n\t\t{\n\t\t\tConfig{\n\t\t\t\tAzureKVClientSecret: \"fake-client-secret\",\n\t\t\t},\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"Incomplete config should not generate any client (clientID)\",\n\t\t},\n\t\t{\n\t\t\tConfig{\n\t\t\t\tAzureKVTenantID: \"fake-tenant-id\",\n\t\t\t\tAzureKVClientID: \"fake-client-id\",\n\t\t\t},\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"Incomplete config should not generate any client (tenant, clientID)\",\n\t\t},\n\t\t{\n\t\t\tConfig{\n\t\t\t\tAzureKVClientID:     \"fake-client-id\",\n\t\t\t\tAzureKVClientSecret: \"fake-client-secret\",\n\t\t\t},\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"Incomplete config should not generate any client (clientID, clientSecret)\",\n\t\t},\n\t\t{\n\t\t\tConfig{\n\t\t\t\tAzureKVTenantID:     \"fake-tenant-id\",\n\t\t\t\tAzureKVClientID:     \"fake-client-id\",\n\t\t\t\tAzureKVClientSecret: \"fake-client-secret\",\n\t\t\t},\n\t\t\tfalse,\n\t\t\tnew(azidentity.ClientSecretCredential),\n\t\t\t\"ClientSecretCredential should be generated with TenantID, ClientID and ClientSecret\",\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tclient, err := getAzureCredential(context.TODO(), logger, c.cfg)\n\t\tif c.err {\n\t\t\tassert.NotNilf(t, err, c.msg)\n\t\t} else {\n\t\t\tassert.Nilf(t, err, c.msg)\n\t\t}\n\t\tif c.typ == nil {\n\t\t\tassert.Nilf(t, client, c.msg)\n\t\t} else {\n\t\t\tassert.IsTypef(t, c.typ, client, c.msg)\n\t\t}\n\t}\n}\n\nfunc TestAzureKeyVaultClient(t *testing.T) {\n\tcfg := Config{}\n\tclient, err := azureKeyVaultClient(context.TODO(), logger, cfg)\n\tassert.NotNilf(t, err, \"Empty config should generate an error\")\n\tassert.Nilf(t, client, \"Empty config should not generate any client\")\n\n\t// Managed Identity auth is performed at client call, so the client generated is \"valid\"\n\tcfg = Config{\n\t\tAzureKVTenantID: fakeKeyVaultTenant,\n\t\tAzureKVClientID: \"fake-client-id\",\n\t}\n\tclient, err = azureKeyVaultClient(context.TODO(), logger, cfg)\n\tassert.NotNilf(t, err, \"Invalid Service Principal Authentication should generate an error\")\n\tassert.Nilf(t, client, \"Invalid Service Principal Authentication should not generate any client\")\n\n\t// Authentication is performed at client call, so the client generated is \"valid\"\n\t// This happens for both service principal and managed identity\n\tcfg = Config{\n\t\tAzureKVTenantID:     fakeKeyVaultTenant,\n\t\tAzureKVClientID:     \"fake-client-id\",\n\t\tAzureKVClientSecret: \"fake-client-secret\",\n\t}\n\tclient, err = azureKeyVaultClient(context.TODO(), logger, cfg)\n\tassert.Nilf(t, err, \"Service Principal Authentication should not generate error\")\n\tassert.NotNilf(t, client, \"Service Principal Authentication should generate a client\")\n\n\tcfg = Config{AzureKVManagedClientID: \"fake-client-id\"}\n\tclient, err = azureKeyVaultClient(context.TODO(), logger, cfg)\n\tassert.Nilf(t, err, \"Managed Identity Authentication should not generate error\")\n\tassert.NotNilf(t, client, \"Managed Identity Authentication should generate a client\")\n}\n\nfunc TestAzureKVClientReadSecret(t *testing.T) {\n\takvMetrics = newAzureKVMetrics(fakeKeyVaultName, fakeKeyVaultTenant)\n\tazClient, _ := azsecrets.NewClient(\n\t\ttestingCfg.VaultURL, // Is a mock server, valid for both cases\n\t\tNewFakeCredential(\"fake\", \"fake\"),\n\t\tnil,\n\t)\n\tclient := azureKVClient{\n\t\tclient:       azClient,\n\t\tkeyvaultName: \"fakekvurl\",\n\t\tcontext:      context.TODO(),\n\t\tlogger:       logger,\n\t}\n\n\tvalue, err := client.ReadSecret(\"exists\", \"\")\n\tassert.Nil(t, err)\n\tassert.Equal(t, akvSecrets[\"exists\"].value, value)\n\n\tvalue, err = client.ReadSecret(fakeKeyVaultSecret, \"\")\n\tassert.Nil(t, err)\n\tassert.Equal(t, akvSecrets[fakeKeyVaultSecret].value, value)\n\n\tvalue, err = client.ReadSecret(\"forbidden\", \"\")\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"\", value)\n\tassert.IsType(t, new(azcore.ResponseError), err)\n\n\tvalue, err = client.ReadSecret(\"not-found\", \"\")\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"\", value)\n\tassert.IsType(t, new(azcore.ResponseError), err)\n\n\tvalue, err = client.ReadSecret(\"internal-error\", \"\")\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"\", value)\n}\n"
  },
  {
    "path": "backend/backend.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nconst (\n\tvaultBackendName   = \"vault\"\n\tazureKVBackendName = \"azure-kv\"\n)\n\nvar supportedBackends map[string]bool\n\nfunc init() {\n\tsupportedBackends = map[string]bool{\n\t\tvaultBackendName:   true,\n\t\tazureKVBackendName: true,\n\t}\n}\n\n// Config type represent backend config, and should include all backends config\ntype Config struct {\n\tBackendTimeout           time.Duration\n\tVaultURL                 string\n\tVaultAuthMethod          string\n\tVaultRoleID              string\n\tVaultSecretID            string\n\tVaultKubernetesRole      string\n\tVaultMaxTokenTTL         int64\n\tVaultTokenPollingPeriod  time.Duration\n\tVaultRenewTTLIncrement   int\n\tVaultEngine              string\n\tVaultApprolePath         string\n\tVaultKubernetesPath      string\n\tAzureKVName              string\n\tAzureKVTenantID          string\n\tAzureKVClientID          string\n\tAzureKVClientSecret      string\n\tAzureKVManagedClientID   string\n\tAzureKVManagedResourceID string\n}\n\n// Client interface represent a backend client interface that should be implemented\ntype Client interface {\n\tReadSecret(path string, key string) (string, error)\n}\n\n// NewBackendClient returns and implementation of Client interface, given the selected backend\nfunc NewBackendClient(ctx context.Context, backend string, logger logr.Logger, cfg Config) (*Client, error) {\n\tvar err error\n\tvar client Client\n\n\tif !supportedBackends[backend] {\n\t\terr = &errors.BackendNotImplementedError{ErrType: errors.BackendNotImplementedErrorType, Backend: backend}\n\t\treturn nil, err\n\t}\n\tswitch backend {\n\tcase vaultBackendName:\n\t\tvclient, verr := vaultClient(logger, cfg)\n\t\tif verr != nil {\n\t\t\treturn nil, verr\n\t\t}\n\t\tvclient.startTokenRenewer(ctx)\n\t\tclient = vclient\n\t\terr = verr\n\tcase azureKVBackendName:\n\t\takvclient, akverr := azureKeyVaultClient(ctx, logger, cfg)\n\t\tif akverr != nil {\n\t\t\treturn nil, akverr\n\t\t}\n\t\tclient = akvclient\n\t\terr = akverr\n\t}\n\treturn &client, err\n}\n"
  },
  {
    "path": "backend/backend_test.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n)\n\nvar (\n\ttestingCfg Config\n\tserver     *httptest.Server\n\tmutex      sync.Mutex\n\tlogger     logr.Logger\n)\n\nfunc TestNotImplementedBackend(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tcfg := Config{}\n\tbackend := \"foo\"\n\t_, err := NewBackendClient(ctx, backend, nil, cfg)\n\tassert.EqualError(t, err, fmt.Sprintf(\"[%s] backend %s not supported\", errors.BackendNotImplementedErrorType, backend))\n}\n\nfunc TestMain(m *testing.M) {\n\tr := mux.NewRouter()\n\tv1SysHandler := r.PathPrefix(fmt.Sprintf(\"/%s/sys\", vaultAPIVersion)).Subrouter()\n\tv1AuthHandler := r.PathPrefix(fmt.Sprintf(\"/%s/auth\", vaultAPIVersion)).Subrouter()\n\tv1SecretHandler := r.PathPrefix(fmt.Sprintf(\"/%s/secret\", vaultAPIVersion)).Subrouter()\n\takvSecretsHandler := r.PathPrefix(\"/secrets\").Subrouter()\n\n\tv1SysHandler.HandleFunc(\"/health\", v1SysHealth).Methods(\"GET\")\n\tv1AuthHandler.HandleFunc(\"/token/lookup-self\", v1AuthTokenLookupSelf).Methods(\"GET\")\n\tv1AuthHandler.HandleFunc(\"/token/renew-self\", v1AuthTokenRenewSelf).Methods(\"PUT\")\n\tv1AuthHandler.HandleFunc(\"/approle/login\", v1AuthAppRoleLogin).Methods(\"PUT\")\n\tv1AuthHandler.HandleFunc(\"/kubernetes/login\", v1AuthKubernetesLogin).Methods(\"PUT\")\n\tv1SecretHandler.HandleFunc(\"/data/test\", v1SecretTestKv2).Methods(\"GET\")\n\tv1SecretHandler.HandleFunc(\"/test\", v1SecretTestKv1).Methods(\"GET\")\n\n\takvSecretsHandler.PathPrefix(\"/{secretName}\").HandlerFunc(akvGetSecret).Methods(\"GET\")\n\n\tserver = httptest.NewServer(r)\n\tdefer server.Close()\n\n\ttestingCfg = Config{\n\t\tVaultURL:                string(server.URL),\n\t\tVaultRoleID:             vaultFakeRoleID,\n\t\tVaultSecretID:           vaultFakeSecretID,\n\t\tVaultTokenPollingPeriod: 1,\n\t\tVaultEngine:             \"kv2\",\n\t\tVaultApprolePath:        vaultAppRolePath,\n\t}\n\n\tvaultTestCfg = &testConfig{\n\t\ttokenRenewable:  defaultTokenRenewable,\n\t\ttokenTTL:        defaultTokenTTL,\n\t\ttokenRevoked:    defaultRevokedToken,\n\t\tinvalidRoleID:   defaultInvalidAppRole,\n\t\tinvalidSecretID: defaultInvalidAppRole,\n\t}\n\n\tlogger = zap.New(zap.UseDevMode(true))\n\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "backend/decoder.go",
    "content": "package backend\n\nimport (\n\t\"encoding/base64\"\n\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nconst (\n\t// Base64EncodingType is the internal code to represent a base64 encoding\n\tBase64EncodingType = \"base64\"\n\n\t// TextEncodingType is the internal code to represent a basic text encoding\n\tTextEncodingType = \"text\"\n\n\t// DefaultEncodingType is the default encoding to use.\n\tDefaultEncodingType = \"text\"\n)\n\n// Decoder interface represents anything that can implement DecodeString: get some bytes from input string\ntype Decoder interface {\n\tDecodeString(input string) ([]byte, error)\n}\n\n// Base64Decoder represents a Decoder for base64 text\ntype Base64Decoder struct {\n\tEncoding string\n}\n\n// TextDecoder represents a Decoder for plain text\ntype TextDecoder struct {\n\tEncoding string\n}\n\n// DecodeString for Base64Decoder will get the text version (in bytes) of the input base64 text\nfunc (d Base64Decoder) DecodeString(input string) ([]byte, error) {\n\tdata, err := base64.StdEncoding.DecodeString(input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn data, err\n}\n\n// DecodeString for TextDecoder, will simply cast to []bytes the input text\nfunc (d TextDecoder) DecodeString(input string) ([]byte, error) {\n\treturn []byte(input), nil\n}\n\n// NewDecoder returns a new Decoder implementation or an error if the provided encoding is not implemented\nfunc NewDecoder(encoding string) (Decoder, error) {\n\tif encoding == \"\" {\n\t\tencoding = DefaultEncodingType\n\t}\n\tswitch encoding {\n\tcase Base64EncodingType:\n\t\treturn Base64Decoder{Encoding: encoding}, nil\n\tcase TextEncodingType:\n\t\treturn TextDecoder{Encoding: encoding}, nil\n\tdefault:\n\t\treturn nil, &errors.EncodingNotImplementedError{ErrType: errors.EncodingNotImplementedErrorType, Encoding: encoding}\n\t}\n}\n"
  },
  {
    "path": "backend/decoder_test.go",
    "content": "package backend\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nfunc TestNotImplementedDecoder(t *testing.T) {\n\tencoding := \"foo\"\n\t_, err := NewDecoder(encoding)\n\tassert.EqualError(t, err, fmt.Sprintf(\"[%s] encoding %s not supported\", errors.EncodingNotImplementedErrorType, encoding))\n}\n\nfunc TestGetB64Decoder(t *testing.T) {\n\tencoding := \"base64\"\n\tdecoder, err := NewDecoder(encoding)\n\tb64Decoder := decoder.(Base64Decoder)\n\tassert.Nil(t, err)\n\tassert.Equal(t, encoding, b64Decoder.Encoding)\n}\n\nfunc TestGetTextDecoder(t *testing.T) {\n\tencoding := \"text\"\n\tdecoder, err := NewDecoder(encoding)\n\ttextDecoder := decoder.(TextDecoder)\n\tassert.Nil(t, err)\n\tassert.Equal(t, encoding, textDecoder.Encoding)\n}\n\nfunc TestGetTextDecoderFromEmptyString(t *testing.T) {\n\tencoding := \"\"\n\tdecoder, err := NewDecoder(encoding)\n\ttextDecoder := decoder.(TextDecoder)\n\tassert.Nil(t, err)\n\tassert.Equal(t, \"text\", textDecoder.Encoding)\n}\n\nfunc TestDecodeB64String(t *testing.T) {\n\tb64data := \"dGVzdGluZyBiYXNlNjQgZGVjb2Rpbmc=\"\n\tdecoder, _ := NewDecoder(\"base64\")\n\tdata, err := decoder.DecodeString(b64data)\n\tassert.Nil(t, err)\n\tassert.Equal(t, \"testing base64 decoding\", fmt.Sprintf(\"%s\", data))\n}\n\nfunc TestDecodeInvalidB64String(t *testing.T) {\n\tb64data := \"Invalid b64 data\"\n\tdecoder, _ := NewDecoder(\"base64\")\n\tdata, err := decoder.DecodeString(b64data)\n\tassert.NotNil(t, err)\n\tassert.Nil(t, data)\n}\n\nfunc TestDecodeText(t *testing.T) {\n\ttext := \"secret text\"\n\tdecoder, _ := NewDecoder(\"text\")\n\tdata, err := decoder.DecodeString(text)\n\tassert.Nil(t, err)\n\tassert.Equal(t, text, fmt.Sprintf(\"%s\", data))\n}\n"
  },
  {
    "path": "backend/vault.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/hashicorp/vault/api\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nvar vMetrics *vaultMetrics\n\nconst (\n\tdefaultSecretKey       = \"data\"\n\tkubernetesJwtTokenPath = \"/var/run/secrets/kubernetes.io/serviceaccount/token\"\n\tkubernetesAuthMethod   = \"kubernetes\"\n\tappRoleAuthMethod      = \"approle\"\n)\n\ntype client struct {\n\tvclient            *api.Client\n\tlogical            *api.Logical\n\troleID             string\n\tauthMethod         string\n\tsecretID           string\n\tkubernetesRole     string\n\tmaxTokenTTL        int64\n\ttokenPollingPeriod time.Duration\n\trenewTTLIncrement  int\n\tengine             engine\n\tapprolePath        string\n\tkubernetesPath     string\n\tlogger             logr.Logger\n}\n\nfunc (c *client) vaultLogin() error {\n\tswitch c.authMethod {\n\tcase kubernetesAuthMethod:\n\t\tfd, err := os.Open(kubernetesJwtTokenPath)\n\t\tdefer fd.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.vaultKubernetesLogin(fd)\n\tcase appRoleAuthMethod:\n\t\tfallthrough\n\tdefault:\n\t\treturn c.vaultAppRoleLogin()\n\t}\n}\n\nfunc (c *client) vaultAppRoleLogin() error {\n\tappRole := map[string]interface{}{\n\t\t\"role_id\":   c.roleID,\n\t\t\"secret_id\": c.secretID,\n\t}\n\tresp, err := c.logical.Write(fmt.Sprintf(\"auth/%s/login\", c.approlePath), appRole)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.vclient.SetToken(resp.Auth.ClientToken)\n\treturn nil\n}\n\nfunc (c *client) vaultKubernetesLogin(podSATokenReader io.Reader) error {\n\tjwt, err := ioutil.ReadAll(podSATokenReader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tkubernetes := map[string]interface{}{\n\t\t\"jwt\":  string(jwt),\n\t\t\"role\": c.kubernetesRole,\n\t}\n\tresp, err := c.logical.Write(fmt.Sprintf(\"auth/%s/login\", c.kubernetesPath), kubernetes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.vclient.SetToken(resp.Auth.ClientToken)\n\treturn nil\n}\n\nfunc vaultClient(l logr.Logger, cfg Config) (*client, error) {\n\tlogger := l.WithName(\"vault\").WithValues(\n\t\t\"vault_url\", cfg.VaultURL,\n\t\t\"vault_engine\", cfg.VaultEngine)\n\n\thttpClient := new(http.Client)\n\thttpClient.Timeout = cfg.BackendTimeout\n\n\tvclient, err := api.NewClient(&api.Config{Address: cfg.VaultURL, HttpClient: httpClient})\n\n\tif err != nil {\n\t\tlogger.Error(err, \"unable to create vault api client\")\n\t\treturn nil, err\n\t}\n\n\tlogical := vclient.Logical()\n\n\tengine, err := newEngine(cfg.VaultEngine)\n\tif err != nil {\n\t\tlogger.Error(err, \"unable to setup vault engine\")\n\t\treturn nil, err\n\t}\n\n\tclient := client{\n\t\tvclient:            vclient,\n\t\tlogical:            logical,\n\t\tauthMethod:         cfg.VaultAuthMethod,\n\t\troleID:             cfg.VaultRoleID,\n\t\tsecretID:           cfg.VaultSecretID,\n\t\tkubernetesRole:     cfg.VaultKubernetesRole,\n\t\tmaxTokenTTL:        cfg.VaultMaxTokenTTL,\n\t\ttokenPollingPeriod: cfg.VaultTokenPollingPeriod,\n\t\trenewTTLIncrement:  cfg.VaultRenewTTLIncrement,\n\t\tengine:             engine,\n\t\tapprolePath:        cfg.VaultApprolePath,\n\t\tkubernetesPath:     cfg.VaultKubernetesPath,\n\t}\n\n\terr = client.vaultLogin()\n\tif err != nil {\n\t\tlogger.Error(err, \"unable to login to vault with provided credentials\")\n\t\treturn nil, err\n\t}\n\n\tsys := vclient.Sys()\n\thealth, err := sys.Health()\n\n\tif err != nil {\n\t\tlogger.Error(err, \"could not get health information about vault cluster\")\n\t\treturn nil, err\n\t}\n\n\tlogger = logger.WithValues(\n\t\t\"vault_cluster_name\", health.ClusterName,\n\t\t\"vault_cluster_id\", health.ClusterID,\n\t\t\"vault_version\", health.Version,\n\t\t\"vault_sealed\", strconv.FormatBool(health.Sealed),\n\t\t\"vault_server_time_utc\", health.ServerTimeUTC,\n\t)\n\n\tlogger.Info(\"successfully logged into vault cluster\")\n\n\tclient.logger = logger\n\n\tvMetrics = newVaultMetrics(cfg.VaultURL, health.Version, cfg.VaultEngine, health.ClusterID, health.ClusterName)\n\n\tvMetrics.updateVaultMaxTokenTTLMetric(cfg.VaultMaxTokenTTL)\n\n\treturn &client, err\n}\n\nfunc (c *client) getToken() (*api.Secret, error) {\n\tauth := c.vclient.Auth()\n\tlookup, err := auth.Token().LookupSelf()\n\tif err != nil {\n\t\tvMetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultLookupSelfOperationName, errors.UnknownErrorType)\n\t\treturn nil, err\n\t}\n\treturn lookup, nil\n}\n\nfunc (c *client) getTokenTTL(token *api.Secret) (int64, error) {\n\tvar ttl int64\n\tttl, err := token.Data[\"ttl\"].(json.Number).Int64()\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tvMetrics.updateVaultTokenTTLMetric(ttl)\n\treturn ttl, nil\n}\n\nfunc (c *client) renewToken(token *api.Secret) error {\n\tisRenewable, err := token.TokenIsRenewable()\n\tif err != nil {\n\t\tvMetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultIsRenewableOperationName, errors.UnknownErrorType)\n\t\treturn err\n\t}\n\tif !isRenewable {\n\t\tvMetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType)\n\t\terr = &errors.VaultTokenNotRenewableError{ErrType: errors.VaultTokenNotRenewableErrorType}\n\t\treturn err\n\t}\n\tauth := c.vclient.Auth()\n\tif _, err = auth.Token().RenewSelf(c.renewTTLIncrement); err != nil {\n\t\tvMetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultRenewSelfOperationName, errors.UnknownErrorType)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *client) renewalLoop() {\n\ttoken, err := c.getToken()\n\tif err != nil {\n\t\tc.logger.Error(err, \"unable to get vault token\")\n\t\tc.logger.Info(\"trying to login to vault again\")\n\t\tif err = c.vaultLogin(); err != nil {\n\t\t\tvMetrics.updateVaultLoginErrorsTotalMetric()\n\t\t\tc.logger.Error(err, \"login error, vault token not obtained\")\n\t\t} else {\n\t\t\tc.logger.Info(\"login successful, got a new vault token\")\n\t\t}\n\t\treturn\n\t}\n\n\tttl, err := c.getTokenTTL(token)\n\tif err != nil {\n\t\tc.logger.Error(err, \"failed to read vault token TTL\")\n\t} else if ttl < c.maxTokenTTL {\n\t\tc.logger.Info(\"vault token is really close to expire\", \"vault_token_ttl\", ttl)\n\t\terr := c.renewToken(token)\n\t\tif err != nil {\n\t\t\tc.logger.Error(err, \"failed to renew vault token\")\n\t\t} else {\n\t\t\tc.logger.Info(\"vault token renewed successfully!\")\n\t\t}\n\t}\n\treturn\n}\n\nfunc (c *client) startTokenRenewer(ctx context.Context) {\n\tgo func(ctx context.Context) {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-time.After(c.tokenPollingPeriod):\n\t\t\t\tc.renewalLoop()\n\t\t\t\tbreak\n\t\t\tcase <-ctx.Done():\n\t\t\t\tc.logger.Info(\"gracefully shutting down token renewal go routine\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}(ctx)\n}\n\nfunc (c *client) ReadSecret(path string, key string) (string, error) {\n\tdata := \"\"\n\tif key == \"\" {\n\t\tkey = defaultSecretKey\n\t}\n\n\tlogical := c.logical\n\tsecret, err := logical.Read(path)\n\tif err != nil {\n\t\tvMetrics.updateVaultSecretReadErrorsTotalMetric(path, key, errors.UnknownErrorType)\n\t\treturn data, err\n\t}\n\n\tif secret != nil {\n\t\tsecretData := c.engine.getData(secret)\n\t\twarnings := secret.Warnings\n\t\tif secretData != nil {\n\t\t\tif secretData[key] != nil {\n\t\t\t\tdata = secretData[key].(string)\n\t\t\t} else {\n\t\t\t\tvMetrics.updateVaultSecretReadErrorsTotalMetric(path, key, errors.BackendSecretNotFoundErrorType)\n\t\t\t\terr = &errors.BackendSecretNotFoundError{ErrType: errors.BackendSecretNotFoundErrorType, Path: path, Key: key}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, w := range warnings {\n\t\t\t\tc.logger.Info(\"secret contains warnings\", \"vault_secret_warning\", w)\n\t\t\t}\n\t\t\tvMetrics.updateVaultSecretReadErrorsTotalMetric(path, key, errors.BackendSecretNotFoundErrorType)\n\t\t\terr = &errors.BackendSecretNotFoundError{ErrType: errors.BackendSecretNotFoundErrorType, Path: path, Key: key}\n\t\t}\n\t} else {\n\t\tvMetrics.updateVaultSecretReadErrorsTotalMetric(path, key, errors.BackendSecretNotFoundErrorType)\n\t\terr = &errors.BackendSecretNotFoundError{ErrType: errors.BackendSecretNotFoundErrorType, Path: path, Key: key}\n\t}\n\treturn data, err\n}\n"
  },
  {
    "path": "backend/vault_engine.go",
    "content": "package backend\n\nimport (\n\t\"github.com/hashicorp/vault/api\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nconst (\n\tkvEngineV1Name = \"kv1\"\n\tkvEngineV2Name = \"kv2\"\n)\n\ntype engine interface {\n\tgetData(s *api.Secret) map[string]interface{}\n}\n\ntype kvEngineV1 struct {\n\tname string\n}\n\ntype kvEngineV2 struct {\n\tname string\n}\n\nfunc (e kvEngineV1) getData(s *api.Secret) map[string]interface{} {\n\treturn s.Data\n}\n\nfunc (e kvEngineV2) getData(s *api.Secret) map[string]interface{} {\n\tif s.Data[\"data\"] == nil {\n\t\treturn nil\n\t}\n\treturn s.Data[\"data\"].(map[string]interface{})\n}\n\nfunc newEngine(eng string) (engine, error) {\n\tif eng == \"\" {\n\t\teng = kvEngineV2Name\n\t}\n\tswitch eng {\n\tcase kvEngineV1Name:\n\t\treturn kvEngineV1{name: kvEngineV1Name}, nil\n\tcase kvEngineV2Name:\n\t\treturn kvEngineV2{name: kvEngineV2Name}, nil\n\tdefault:\n\t\treturn nil, &errors.VaultEngineNotImplementedError{ErrType: errors.VaultEngineNotImplementedErrorType, Engine: eng}\n\t}\n}\n"
  },
  {
    "path": "backend/vault_engine_test.go",
    "content": "package backend\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/vault/api\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nfunc TestNewEngineKV1(t *testing.T) {\n\teng := \"kv1\"\n\tengine, err := newEngine(eng)\n\tassert.Nil(t, err)\n\tassert.Equal(t, eng, engine.(kvEngineV1).name)\n}\n\nfunc TestNewEngineKV2(t *testing.T) {\n\teng := \"kv2\"\n\tengine, err := newEngine(eng)\n\tassert.Nil(t, err)\n\tassert.Equal(t, eng, engine.(kvEngineV2).name)\n}\n\nfunc TestNotImplementedEngine(t *testing.T) {\n\teng := \"kv3\"\n\t_, err := newEngine(eng)\n\tassert.NotNil(t, err)\n\tassert.EqualError(t, err, fmt.Sprintf(\"[%s] vault engine %s not supported\", errors.VaultEngineNotImplementedErrorType, eng))\n}\n\nfunc TestGetDataKv1(t *testing.T) {\n\tdata := make(map[string]interface{})\n\tdata[\"foo\"] = \"bar\"\n\ts := &api.Secret{Data: data}\n\tengine, _ := newEngine(\"kv1\")\n\td := engine.getData(s)\n\tassert.NotNil(t, d)\n\tassert.Equal(t, data, d)\n}\n\nfunc TestGetDataKv2(t *testing.T) {\n\tdata := make(map[string]interface{})\n\tnested := make(map[string]interface{})\n\tnested[\"foo\"] = \"bar\"\n\tdata[\"data\"] = nested\n\ts := &api.Secret{Data: data}\n\tengine, _ := newEngine(\"kv2\")\n\td := engine.getData(s)\n\tassert.NotNil(t, d)\n\tassert.Equal(t, nested, d)\n}\n\nfunc TestGetDataKv2WithKv1Engine(t *testing.T) {\n\tdata := make(map[string]interface{})\n\tnested := make(map[string]interface{})\n\tnested[\"foo\"] = \"bar\"\n\tdata[\"data\"] = nested\n\ts := &api.Secret{Data: data}\n\tengine, _ := newEngine(\"kv1\")\n\td := engine.getData(s)\n\tassert.NotNil(t, d)\n\tassert.Equal(t, data, d)\n}\n"
  },
  {
    "path": "backend/vault_metrics.go",
    "content": "package backend\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics\"\n)\n\nconst (\n\tvaultLookupSelfOperationName  = \"lookup-self\"\n\tvaultRenewSelfOperationName   = \"renew-self\"\n\tvaultIsRenewableOperationName = \"is-renewable\"\n)\n\nvar (\n\tvaultLabelNames      = []string{\"vault_address\", \"vault_engine\", \"vault_version\", \"vault_cluster_id\", \"vault_cluster_name\"}\n\tsecretLabelNames     = []string{\"path\", \"key\", \"error\"}\n\tvaultErrorLabelNames = []string{\"vault_operation\", \"error\"}\n\n\t// Prometeheus metrics: https://prometheus.io\n\ttokenTTL = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"vault\",\n\t\tName:      \"token_ttl\",\n\t\tHelp:      \"Vault token TTL\",\n\t}, vaultLabelNames)\n\tmaxTokenTTL = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"vault\",\n\t\tName:      \"max_token_ttl\",\n\t\tHelp:      \"secrets-manager max Vault token TTL\",\n\t}, vaultLabelNames)\n\ttokenRenewalErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"vault\",\n\t\tName:      \"token_renewal_errors_total\",\n\t\tHelp:      \"Vault token renewal errors counter\",\n\t}, append(vaultLabelNames, vaultErrorLabelNames...))\n\tsecretReadErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"vault\",\n\t\tName:      \"read_secret_errors_total\",\n\t\tHelp:      \"Vault read operations counter\",\n\t}, append(vaultLabelNames, secretLabelNames...))\n\tloginErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"vault\",\n\t\tName:      \"login_errors_total\",\n\t\tHelp:      \"Vault login errors counter\",\n\t}, vaultLabelNames)\n)\n\ntype vaultMetrics struct {\n\tvaultLabels map[string]string\n}\n\nfunc init() {\n\tr := metrics.Registry\n\tr.MustRegister(tokenTTL)\n\tr.MustRegister(maxTokenTTL)\n\tr.MustRegister(tokenRenewalErrorsTotal)\n\tr.MustRegister(secretReadErrorsTotal)\n\tr.MustRegister(loginErrorsTotal)\n}\n\nfunc newVaultMetrics(vaultAddr string, vaultVersion string, vaultEngine string, vaultClusterID string, vaultClusterName string) *vaultMetrics {\n\tlabels := make(map[string]string, len(vaultLabelNames))\n\tlabels[\"vault_addr\"] = vaultAddr\n\tlabels[\"vault_engine\"] = vaultEngine\n\tlabels[\"vault_version\"] = vaultVersion\n\tlabels[\"vault_cluster_id\"] = vaultClusterID\n\tlabels[\"vault_cluster_name\"] = vaultClusterName\n\n\treturn &vaultMetrics{vaultLabels: labels}\n}\n\nfunc (vm *vaultMetrics) updateVaultMaxTokenTTLMetric(value int64) {\n\tmaxTokenTTL.WithLabelValues(\n\t\tvm.vaultLabels[\"vault_addr\"],\n\t\tvm.vaultLabels[\"vault_engine\"],\n\t\tvm.vaultLabels[\"vault_version\"],\n\t\tvm.vaultLabels[\"vault_cluster_id\"],\n\t\tvm.vaultLabels[\"vault_cluster_name\"]).Set(float64(value))\n}\n\nfunc (vm *vaultMetrics) updateVaultTokenTTLMetric(value int64) {\n\ttokenTTL.WithLabelValues(\n\t\tvm.vaultLabels[\"vault_addr\"],\n\t\tvm.vaultLabels[\"vault_engine\"],\n\t\tvm.vaultLabels[\"vault_version\"],\n\t\tvm.vaultLabels[\"vault_cluster_id\"],\n\t\tvm.vaultLabels[\"vault_cluster_name\"]).Set(float64(value))\n}\n\nfunc (vm *vaultMetrics) updateVaultSecretReadErrorsTotalMetric(path string, key string, errorType string) {\n\tsecretReadErrorsTotal.WithLabelValues(\n\t\tvm.vaultLabels[\"vault_addr\"],\n\t\tvm.vaultLabels[\"vault_engine\"],\n\t\tvm.vaultLabels[\"vault_version\"],\n\t\tvm.vaultLabels[\"vault_cluster_id\"],\n\t\tvm.vaultLabels[\"vault_cluster_name\"],\n\t\tpath,\n\t\tkey,\n\t\terrorType).Inc()\n}\n\nfunc (vm *vaultMetrics) updateVaultTokenRenewalErrorsTotalMetric(vaultOperation string, errorType string) {\n\ttokenRenewalErrorsTotal.WithLabelValues(\n\t\tvm.vaultLabels[\"vault_addr\"],\n\t\tvm.vaultLabels[\"vault_engine\"],\n\t\tvm.vaultLabels[\"vault_version\"],\n\t\tvm.vaultLabels[\"vault_cluster_id\"],\n\t\tvm.vaultLabels[\"vault_cluster_name\"],\n\t\tvaultOperation,\n\t\terrorType).Inc()\n}\n\nfunc (vm *vaultMetrics) updateVaultLoginErrorsTotalMetric() {\n\tloginErrorsTotal.WithLabelValues(\n\t\tvm.vaultLabels[\"vault_addr\"],\n\t\tvm.vaultLabels[\"vault_engine\"],\n\t\tvm.vaultLabels[\"vault_version\"],\n\t\tvm.vaultLabels[\"vault_cluster_id\"],\n\t\tvm.vaultLabels[\"vault_cluster_name\"]).Inc()\n}\n"
  },
  {
    "path": "backend/vault_metrics_test.go",
    "content": "package backend\n\nimport (\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nconst (\n\tfakeVaultAddress     = \"https://vault.example.com:8200\"\n\tfakeVaultVersion     = \"0.11.1\"\n\tfakeVaultEngine      = \"kv2\"\n\tfakeVaultClusterID   = \"vault-fake-1\"\n\tfakeVaultClusterName = \"vault-fake\"\n)\n\nfunc TestUpdateMaxTokenTTL(t *testing.T) {\n\tmetrics := newVaultMetrics(fakeVaultAddress, fakeVaultVersion, fakeVaultEngine, fakeVaultClusterID, fakeVaultClusterName)\n\tmaxTokenTTL.Reset()\n\tmetrics.updateVaultMaxTokenTTLMetric(600)\n\tmetricMaxTokenTTL, _ := maxTokenTTL.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName)\n\n\tassert.Equal(t, 600.0, testutil.ToFloat64(metricMaxTokenTTL))\n}\n\nfunc TestUpdateTokenTTL(t *testing.T) {\n\tmetrics := newVaultMetrics(fakeVaultAddress, fakeVaultVersion, fakeVaultEngine, fakeVaultClusterID, fakeVaultClusterName)\n\ttokenTTL.Reset()\n\tmetrics.updateVaultTokenTTLMetric(300)\n\tmetricTokenTTL, _ := tokenTTL.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName)\n\n\tassert.Equal(t, 300.0, testutil.ToFloat64(metricTokenTTL))\n}\n\nfunc TestUpdateTokenLookupErrorsTotal(t *testing.T) {\n\tmetrics := newVaultMetrics(fakeVaultAddress, fakeVaultVersion, fakeVaultEngine, fakeVaultClusterID, fakeVaultClusterName)\n\ttokenRenewalErrorsTotal.Reset()\n\tmetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultLookupSelfOperationName, errors.UnknownErrorType)\n\tmetricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName, vaultLookupSelfOperationName, errors.UnknownErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n}\n\nfunc TestUpdateTokenRenewErrorsTotal(t *testing.T) {\n\tmetrics := newVaultMetrics(fakeVaultAddress, fakeVaultVersion, fakeVaultEngine, fakeVaultClusterID, fakeVaultClusterName)\n\ttokenRenewalErrorsTotal.Reset()\n\tmetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultRenewSelfOperationName, errors.UnknownErrorType)\n\tmetricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName, vaultRenewSelfOperationName, errors.UnknownErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n\n\ttokenRenewalErrorsTotal.Reset()\n\tmetrics.updateVaultTokenRenewalErrorsTotalMetric(vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType)\n\tmetricTokenRenewalErrorsTotal, _ = tokenRenewalErrorsTotal.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n}\n\nfunc TestUpdateReadSecretErrorsTotal(t *testing.T) {\n\tpath := \"/path/to/secret\"\n\tkey := \"key\"\n\n\tmetrics := newVaultMetrics(fakeVaultAddress, fakeVaultVersion, fakeVaultEngine, fakeVaultClusterID, fakeVaultClusterName)\n\tsecretReadErrorsTotal.Reset()\n\tmetrics.updateVaultSecretReadErrorsTotalMetric(path, key, errors.UnknownErrorType)\n\tmetricSecretReadErrorsTotal, _ := secretReadErrorsTotal.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName, path, key, errors.UnknownErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal))\n\n\tsecretReadErrorsTotal.Reset()\n\tmetrics.updateVaultSecretReadErrorsTotalMetric(path, key, errors.BackendSecretNotFoundErrorType)\n\tmetricSecretReadErrorsTotal, _ = secretReadErrorsTotal.GetMetricWithLabelValues(fakeVaultAddress, fakeVaultEngine, fakeVaultVersion, fakeVaultClusterID, fakeVaultClusterName, path, key, errors.BackendSecretNotFoundErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal))\n}\n"
  },
  {
    "path": "backend/vault_test.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/hashicorp/vault/api\"\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n)\n\nconst (\n\tvaultAPIVersion       = \"v1\"\n\tvaultFakeClusterName  = \"vault-mock-cluster\"\n\tvaultFakeClusterID    = \"vault-mock-cluster-1\"\n\tvaultFakeVersion      = \"0.11.1\"\n\tselectedBackend       = \"vault\"\n\tfakeToken             = \"fake-token\"\n\tvaultFakeRoleID       = \"12345678-9aaa-bbbb-cccc-dddddddddddd\"\n\tvaultFakeSecretID     = \"eeeeeeee-ffff-0000-1111-123456789aaa\"\n\tvaultAppRolePath      = \"approle\"\n\tdefaultTokenTTL       = 40\n\tdefaultTokenRenewable = true\n\tdefaultRevokedToken   = false\n\tdefaultInvalidAppRole = false\n\tdefaultKubernetesRole = false\n\tfakeKubernetesSAToken = `eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.bQTnz6AuMJvmXXQsVPrxeQNvzDkimo7VNXxHeSBfClLufmCVZRUuyTwJF311JHuh`\n)\n\ntype testConfig struct {\n\ttokenTTL              int\n\ttokenRenewable        bool\n\ttokenRevoked          bool\n\tinvalidRoleID         bool\n\tinvalidSecretID       bool\n\tinvalidKubernetesRole bool\n}\n\nvar (\n\tvaultTestCfg *testConfig\n)\n\nfunc v1SysHealth(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := fmt.Sprintf(`\n\t{\n\t\t\"initialized\": true,\n\t\t\"sealed\": false,\n\t\t\"standby\": false,\n\t\t\"performance_standby\": false,\n\t\t\"replication_performance_mode\": \"disabled\",\n\t\t\"replication_dr_mode\": \"disabled\",\n\t\t\"server_time_utc\": 1537804485,\n\t\t\"version\": \"%s\",\n\t\t\"cluster_name\": \"%s\",\n\t\t\"cluster_id\": \"%s\"\n\t}`, vaultFakeVersion, vaultFakeClusterName, vaultFakeClusterID)\n\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc v1AuthTokenLookupSelf(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := \"\"\n\tif !vaultTestCfg.tokenRevoked {\n\t\tjsonData = fmt.Sprintf(`\n\t\t{\n\t\t\t\"request_id\": \"8d70f864-5f77-44fe-0940-df085376101f\",\n\t\t\t\"lease_id\": \"\",\n\t\t\t\"renewable\": false,\n\t\t\t\"lease_duration\": 0,\n\t\t\t\"data\": {\n\t\t\t\t\"accessor\": \"d2d7308c-b9f2-3399-4202-11d670b8c053\",\n\t\t\t\t\"creation_time\": 1537810558,\n\t\t\t\t\"creation_ttl\": 60,\n\t\t\t\t\"display_name\": \"token\",\n\t\t\t\t\"entity_id\": \"\",\n\t\t\t\t\"expire_time\": \"2018-09-24T17:36:58.797772932Z\",\n\t\t\t\t\"explicit_max_ttl\": 0,\n\t\t\t\t\"id\": \"31a5ea4e-907d-c1b9-1dfc-6b88526be248\",\n\t\t\t\t\"issue_time\": \"2018-09-24T17:35:58.79776585Z\",\n\t\t\t\t\"meta\": null,\n\t\t\t\t\"num_uses\": 0,\n\t\t\t\t\"orphan\": false,\n\t\t\t\t\"path\": \"auth/token/create\",\n\t\t\t\t\"policies\": [\n\t\t\t\t\t\"fake-policy\"\n\t\t\t\t],\n\t\t\t\t\"renewable\": %t,\n\t\t\t\t\"ttl\": %d\n\t\t\t},\n\t\t\t\"wrap_info\": null,\n\t\t\t\"warnings\": null,\n\t\t\t\"auth\": null\n\t\t}`, vaultTestCfg.tokenRenewable, vaultTestCfg.tokenTTL)\n\t} else {\n\t\tjsonData = `{\"errors\":[\"permission denied\"]}`\n\t\tw.WriteHeader(http.StatusForbidden)\n\t}\n\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc v1AuthTokenRenewSelf(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := \"\"\n\tif !vaultTestCfg.tokenRevoked {\n\t\tjsonData = fmt.Sprintf(`\n\t\t{\n\t\t\t\"request_id\": \"d8ae3e67-91a0-2f7a-528b-522048f9dad3\",\n\t\t\t\"lease_id\": \"\",\n\t\t\t\"renewable\": false,\n\t\t\t\"lease_duration\": 0,\n\t\t\t\"data\": null,\n\t\t\t\"wrap_info\": null,\n\t\t\t\"warnings\": null,\n\t\t\t\"auth\": {\n\t\t\t\t\"client_token\": \"%s\",\n\t\t\t\t\"accessor\": \"dc6aa861-3020-322c-8df5-4b08afa43a34\",\n\t\t\t\t\"policies\": [\n\t\t\t\t\t\"fake-policy\"\n\t\t\t\t],\n\t\t\t\t\"token_policies\": [\n\t\t\t\t\t\"fake-policy\"\n\t\t\t\t],\n\t\t\t\t\"metadata\": null,\n\t\t\t\t\"lease_duration\": 1000,\n\t\t\t\t\"renewable\": true,\n\t\t\t\t\"entity_id\": \"\"\n\t\t\t}\n\t\t}`, fakeToken)\n\t} else {\n\t\tjsonData = `{\"errors\":[\"permission denied\"]}`\n\t\tw.WriteHeader(http.StatusForbidden)\n\t}\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc v1AuthKubernetesLogin(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := \"\"\n\tif !vaultTestCfg.invalidKubernetesRole {\n\t\tjsonData = fmt.Sprintf(`\n\t\t{\n  \"auth\": {\n    \"client_token\": \"%s\",\n    \"accessor\": \"78e87a38-84ed-2692-538f-ca8b9f400ab3\",\n    \"policies\": [\"secrets-manager\"],\n    \"metadata\": {\n      \"role\": \"secrets-manager\",\n      \"service_account_name\": \"secrets-manager\",\n      \"service_account_namespace\": \"default\",\n      \"service_account_secret_name\": \"secrets-manager-token-pd21c\",\n      \"service_account_uid\": \"aa9aa8ff-98d0-11e7-9bb7-0800276d99bf\"\n    },\n    \"lease_duration\": 2764800,\n    \"renewable\": true\n  }\n}`, fakeToken)\n\t} else {\n\t\tjsonData = `{\"errors\":[\"forbidden\"]}`\n\t\tw.WriteHeader(http.StatusForbidden)\n\t}\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc v1AuthAppRoleLogin(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := \"\"\n\tif !vaultTestCfg.invalidRoleID && !vaultTestCfg.invalidSecretID {\n\t\tjsonData = fmt.Sprintf(`\n\t\t{\n  \t\t\t\"request_id\": \"ecc0025f-040a-3c28-164e-0651abd7f6ac\",\n  \t\t\t\"lease_id\": \"\",\n  \t\t\t\"renewable\": false,\n\t\t\t\"lease_duration\": 0,\n\t\t\t\"data\": null,\n\t\t\t\"wrap_info\": null,\n\t\t\t\"warnings\": null,\n\t\t\t\"auth\": {\n\t\t\t\t\"client_token\": \"%s\",\n\t\t\t\t\"accessor\": \"AEuaibYaTmrB44ZG6QjRpv0o\",\n\t\t\t\t\"policies\": [\n\t\t\t\t\"default\",\n\t\t\t\t\"secrets-manager\"\n\t\t\t\t],\n\t\t\t\t\"token_policies\": [\n\t\t\t\t\"default\",\n\t\t\t\t\"secrets-manager\"\n\t\t\t\t],\n\t\t\t\t\"metadata\": {\n\t\t\t\t\"role_name\": \"secrets-manager\"\n\t\t\t\t},\n\t\t\t\t\"lease_duration\": 1200,\n\t\t\t\t\"renewable\": true,\n\t\t\t\t\"entity_id\": \"79619c25-955d-2888-7abf-52bf4b87ae94\",\n\t\t\t\t\"token_type\": \"service\",\n\t\t\t\t\"orphan\": true\n\t\t\t}\n\n\t\t}`, fakeToken)\n\t} else if vaultTestCfg.invalidRoleID {\n\t\tjsonData = `{\"errors\":[\"invalid role ID\"]}`\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t} else {\n\t\tjsonData = `{\"errors\":[\"invalid secret ID\"]}`\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t}\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc v1SecretTestKv2(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := `\n\t{\n\t\t\"request_id\": \"a21f835e-7e72-dd43-d5a1-80fea23c0649\",\n\t\t\"lease_id\": \"\",\n\t\t\"renewable\": false,\n\t\t\"lease_duration\": 0,\n\t\t\"data\": {\n\t\t\t\"data\": {\n\t\t\t\t\"foo\": \"bar\"\n\t\t\t},\n\t\t\t\"metadata\": {\n\t\t\t\t\"created_time\": \"2018-09-25T08:35:15.504392904Z\",\n\t\t\t\t\"deletion_time\": \"\",\n\t\t\t\t\"destroyed\": false,\n\t\t\t\t\"version\": 1\n\t\t\t}\n\t\t},\n\t\t\"wrap_info\": null,\n\t\t\"warnings\": null,\n\t\t\"auth\": null\n\t}`\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc v1SecretTestKv1(w http.ResponseWriter, r *http.Request) {\n\tvar response interface{}\n\tjsonData := `\n\t{\n\t\t\"request_id\": \"a21f835e-7e72-dd43-d5a1-80fea23c0649\",\n\t\t\"lease_id\": \"\",\n\t\t\"renewable\": false,\n\t\t\"lease_duration\": 0,\n\t\t\"data\": {\n\t\t\t\"foo\": \"bar\"\n\t\t},\n\t\t\"wrap_info\": null,\n\t\t\"warnings\": null,\n\t\t\"auth\": null\n\t}`\n\tif err := json.Unmarshal([]byte(jsonData), &response); err != nil {\n\t\tfmt.Printf(\"unable to unmarshal json %v\", err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(response)\n}\n\nfunc TestVaultLoginKubernetes(t *testing.T) {\n\thttpClient := new(http.Client)\n\tvclient, _ := api.NewClient(&api.Config{Address: testingCfg.VaultURL, HttpClient: httpClient})\n\tc := &client{\n\t\tvclient:        vclient,\n\t\tlogical:        vclient.Logical(),\n\t\tauthMethod:     \"kubernetes\",\n\t\tkubernetesRole: \"secrets-manager\",\n\t\tkubernetesPath: \"kubernetes\",\n\t}\n\terr := c.vaultKubernetesLogin(strings.NewReader(fakeKubernetesSAToken))\n\tassert.Nil(t, err)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.invalidKubernetesRole = true\n\terr2 := c.vaultKubernetesLogin(strings.NewReader(fakeKubernetesSAToken))\n\tassert.NotNil(t, err2)\n}\n\nfunc TestVaultBackendInvalidCfg(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tcfg := Config{VaultURL: \"http://1.1.1.1:8300\", VaultEngine: \"kv3\", BackendTimeout: 1}\n\tbackend := \"vault\"\n\tclient, err := NewBackendClient(ctx, backend, logger, cfg)\n\tassert.NotNil(t, err)\n\tassert.Nil(t, client)\n}\n\nfunc TestVaultBackend(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tclient, err := NewBackendClient(ctx, \"vault\", logger, testingCfg)\n\tassert.Nil(t, err)\n\tassert.NotNil(t, client)\n}\n\nfunc TestVaultLoginInvalidRoleId(t *testing.T) {\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.invalidRoleID = true\n\tclient, err := vaultClient(logger, testingCfg)\n\tassert.Nil(t, client)\n\tassert.NotNil(t, err)\n\tvaultTestCfg.invalidRoleID = defaultInvalidAppRole\n}\n\nfunc TestVaultLoginInvalidSecretId(t *testing.T) {\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.invalidSecretID = true\n\tclient, err := vaultClient(logger, testingCfg)\n\tassert.Nil(t, client)\n\tassert.NotNil(t, err)\n\tvaultTestCfg.invalidSecretID = defaultInvalidAppRole\n}\n\nfunc TestVaultClient(t *testing.T) {\n\tmaxTokenTTL.Reset()\n\tclient, err := vaultClient(logger, testingCfg)\n\tmetricMaxTokenTTL, _ := maxTokenTTL.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName)\n\tassert.Nil(t, err)\n\tassert.NotNil(t, client)\n\tassert.Equal(t, float64(client.maxTokenTTL), testutil.ToFloat64(metricMaxTokenTTL))\n}\n\nfunc TestVaultClientInvalidCfg(t *testing.T) {\n\tinvalidCfg := Config{VaultURL: \"http://1.1.1.1:8300\", VaultRoleID: vaultFakeRoleID, VaultSecretID: vaultFakeSecretID, BackendTimeout: 1 * time.Second}\n\tclient, err := vaultClient(logger, invalidCfg)\n\tassert.NotNil(t, err)\n\tassert.Nil(t, client)\n}\n\nfunc TestGetToken(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\ttoken, err := client.getToken()\n\tassert.NotNil(t, token)\n\tassert.Nil(t, err)\n}\n\nfunc TestGetTokenTTL(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\ttokenTTL.Reset()\n\n\ttoken, _ := client.getToken()\n\tttl, err := client.getTokenTTL(token)\n\tmetricTokenTTL, _ := tokenTTL.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName)\n\n\tassert.Equal(t, float64(vaultTestCfg.tokenTTL), testutil.ToFloat64(metricTokenTTL))\n\tassert.Equal(t, int64(vaultTestCfg.tokenTTL), ttl)\n\tassert.Nil(t, err)\n}\n\nfunc TestRenewToken(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.tokenRenewable = true\n\tvaultTestCfg.tokenTTL = 600\n\tclient.maxTokenTTL = 6000\n\n\ttoken, _ := client.getToken()\n\terr := client.renewToken(token)\n\n\tassert.Nil(t, err)\n}\n\nfunc TestRenewTokenRevokedToken(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.tokenRenewable = true\n\tvaultTestCfg.tokenTTL = 600\n\tclient.maxTokenTTL = 6000\n\n\ttoken, _ := client.getToken()\n\tvaultTestCfg.tokenRevoked = true\n\ttokenRenewalErrorsTotal.Reset()\n\terr := client.renewToken(token)\n\tmetricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultRenewSelfOperationName, errors.UnknownErrorType)\n\tassert.NotNil(t, err)\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n}\n\nfunc TestTokenNotRenewableError(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.tokenRenewable = false\n\tvaultTestCfg.tokenTTL = 600\n\tclient.maxTokenTTL = 6000\n\n\ttoken, _ := client.getToken()\n\n\ttokenRenewalErrorsTotal.Reset()\n\terr := client.renewToken(token)\n\n\tmetricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n\tassert.EqualError(t, err, fmt.Sprintf(\"[%s] vault token not renewable\", errors.VaultTokenNotRenewableErrorType))\n}\n\nfunc TestRenewalLoopRevokedToken(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.tokenRevoked = true\n\ttokenRenewalErrorsTotal.Reset()\n\tclient.renewalLoop()\n\tmetricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultLookupSelfOperationName, errors.UnknownErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n}\n\nfunc TestRenewalLoopNotRenewableToken(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.tokenRenewable = false\n\tvaultTestCfg.tokenRevoked = false\n\tvaultTestCfg.tokenTTL = 600\n\tclient.maxTokenTTL = 6000\n\n\ttokenRenewalErrorsTotal.Reset()\n\tclient.renewalLoop()\n\tmetricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal))\n}\n\nfunc TestRenewalLoopInvalidRoleId(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.invalidRoleID = true\n\tvaultTestCfg.tokenRevoked = true\n\n\ttokenRenewalErrorsTotal.Reset()\n\tloginErrorsTotal.Reset()\n\tclient.renewalLoop()\n\tloginErrorsTotal, _ := loginErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(loginErrorsTotal))\n\tvaultTestCfg.invalidRoleID = defaultInvalidAppRole\n\tvaultTestCfg.tokenRevoked = defaultRevokedToken\n}\n\nfunc TestRenewalLoopInvalidSecretId(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\tvaultTestCfg.invalidSecretID = true\n\tvaultTestCfg.tokenRevoked = true\n\n\ttokenRenewalErrorsTotal.Reset()\n\tloginErrorsTotal.Reset()\n\tclient.renewalLoop()\n\tloginErrorsTotal, _ := loginErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName)\n\n\tassert.Equal(t, 1.0, testutil.ToFloat64(loginErrorsTotal))\n\tvaultTestCfg.invalidSecretID = defaultInvalidAppRole\n\tvaultTestCfg.tokenRevoked = defaultRevokedToken\n}\n\nfunc TestReadSecretKv2(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tsecretValue, err := client.ReadSecret(\"/secret/data/test\", \"foo\")\n\tassert.Nil(t, err)\n\tassert.Equal(t, \"bar\", secretValue)\n}\n\nfunc TestReadSecretKv1(t *testing.T) {\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\ttestingCfg.VaultEngine = \"kv1\"\n\tclient, _ := vaultClient(logger, testingCfg)\n\tsecretValue, err := client.ReadSecret(\"/secret/test\", \"foo\")\n\tassert.Nil(t, err)\n\tassert.Equal(t, \"bar\", secretValue)\n}\n\nfunc TestSecretNotFound(t *testing.T) {\n\tclient, _ := vaultClient(logger, testingCfg)\n\tpath := \"/secret/data/test\"\n\tkey := \"foo2\"\n\tsecretReadErrorsTotal.Reset()\n\tsecretValue, err := client.ReadSecret(path, key)\n\tmetricSecretReadErrorsTotal, _ := secretReadErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, path, key, errors.BackendSecretNotFoundErrorType)\n\n\tassert.Empty(t, secretValue)\n\tassert.EqualError(t, err, fmt.Sprintf(\"[%s] secret key %s not found at %s\", errors.BackendSecretNotFoundErrorType, key, path))\n\tassert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal))\n}\n"
  },
  {
    "path": "config/crd/bases/secrets-manager.tuenti.io_secretdefinitions.yaml",
    "content": "\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.4.1\n  creationTimestamp: null\n  name: secretdefinitions.secrets-manager.tuenti.io\nspec:\n  group: secrets-manager.tuenti.io\n  names:\n    kind: SecretDefinition\n    listKind: SecretDefinitionList\n    plural: secretdefinitions\n    singular: secretdefinition\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: SecretDefinition is the Schema for the secretdefinitions API\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: SecretDefinitionSpec defines the desired state of SecretDefinition\n            properties:\n              keysMap:\n                additionalProperties:\n                  description: DataSource represents the actual source of truth path\n                    for a secret\n                  properties:\n                    encoding:\n                      description: Encoding type for the secret. Only base64 supported.\n                        Optional\n                      type: string\n                    key:\n                      description: Key where the actual secret is stored\n                      type: string\n                    path:\n                      description: Path to the actual secret\n                      type: string\n                  required:\n                  - key\n                  - path\n                  type: object\n                type: object\n              name:\n                description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                  Important: Run \"make\" to regenerate code after modifying this file'\n                type: string\n              type:\n                type: string\n            required:\n            - keysMap\n            - name\n            type: object\n          status:\n            description: SecretDefinitionStatus defines the observed state of SecretDefinition\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n"
  },
  {
    "path": "config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/secrets-manager.tuenti.io_secretdefinitions.yaml\n#+kubebuilder:scaffold:crdkustomizeresource\n\npatchesStrategicMerge:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n#- patches/webhook_in_secretdefinitions.yaml\n#+kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.\n# patches here are for enabling the CA injection for each CRD\n#- patches/cainjection_in_secretdefinitions.yaml\n#+kubebuilder:scaffold:crdkustomizecainjectionpatch\n\n# the following config is for teaching kustomize how to do kustomization for CRDs.\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nnamespace:\n- kind: CustomResourceDefinition\n  version: v1\n  group: apiextensions.k8s.io\n  path: spec/conversion/webhook/clientConfig/service/namespace\n  create: false\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "config/crd/patches/cainjection_in_secretdefinitions.yaml",
    "content": "# The following patch adds a directive for certmanager to inject CA into the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n  name: secretdefinitions.secretsmanager.secrets-manager.tuenti.io\n"
  },
  {
    "path": "config/crd/patches/webhook_in_secretdefinitions.yaml",
    "content": "# The following patch enables a conversion webhook for the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: secretsmanager.secrets-manager.tuenti.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          namespace: system\n          name: webhook-service\n          path: /convert\n      conversionReviewVersions:\n      - v1\n"
  },
  {
    "path": "config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: secrets-manager-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: secrets-manager-\n\n# Labels to add to all resources and selectors.\n#commonLabels:\n#  someName: someValue\n\nbases:\n- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n#- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n#- ../prometheus\n\npatchesStrategicMerge:\n- manager_image_patch.yaml\n# Protect the /metrics endpoint by putting it behind auth.\n# If you want your controller-manager to expose the /metrics\n# endpoint w/o any authn/z, please comment the following line.\n#- manager_auth_proxy_patch.yaml\n\n# Mount the controller config file for loading manager configurations\n# through a ComponentConfig type\n#- manager_config_patch.yaml\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- manager_webhook_patch.yaml\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.\n# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.\n# 'CERTMANAGER' needs to be enabled to use ca injection\n#- webhookcainjection_patch.yaml\n\n# the following config is for teaching kustomize how to do var substitution\nvars:\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR\n#  objref:\n#    kind: Certificate\n#    group: cert-manager.io\n#    version: v1\n#    name: serving-cert # this name should match the one in certificate.yaml\n#  fieldref:\n#    fieldpath: metadata.namespace\n#- name: CERTIFICATE_NAME\n#  objref:\n#    kind: Certificate\n#    group: cert-manager.io\n#    version: v1\n#    name: serving-cert # this name should match the one in certificate.yaml\n#- name: SERVICE_NAMESPACE # namespace of the service\n#  objref:\n#    kind: Service\n#    version: v1\n#    name: webhook-service\n#  fieldref:\n#    fieldpath: metadata.namespace\n#- name: SERVICE_NAME\n#  objref:\n#    kind: Service\n#    version: v1\n#    name: webhook-service\n"
  },
  {
    "path": "config/default/manager_auth_proxy_patch.yaml",
    "content": "# This patch inject a sidecar container which is a HTTP proxy for the\n# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\nspec:\n  template:\n    spec:\n      containers:\n      - name: kube-rbac-proxy\n        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0\n        args:\n        - \"--secure-listen-address=0.0.0.0:8443\"\n        - \"--upstream=http://127.0.0.1:8080/\"\n        - \"--logtostderr=true\"\n        - \"--v=10\"\n        ports:\n        - containerPort: 8443\n          name: https\n      - name: manager\n        args:\n        - \"--health-probe-bind-address=:8081\"\n        - \"--metrics-bind-address=127.0.0.1:8080\"\n        - \"--leader-elect\"\n"
  },
  {
    "path": "config/default/manager_config_patch.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        args:\n        - \"--config=controller_manager_config.yaml\"\n        volumeMounts:\n        - name: manager-config\n          mountPath: /controller_manager_config.yaml\n          subPath: controller_manager_config.yaml\n      volumes:\n      - name: manager-config\n        configMap:\n          name: manager-config\n"
  },
  {
    "path": "config/default/manager_image_patch.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\nspec:\n  template:\n    spec:\n      containers:\n      # Change the value of image field below to your controller image URL\n      - image: registry.hub.docker.com/tuentitech/secrets-manager:v2.1.0\n        name: manager\n"
  },
  {
    "path": "config/manager/controller_manager_config.yaml",
    "content": "apiVersion: controller-runtime.sigs.k8s.io/v1alpha1\nkind: ControllerManagerConfig\nhealth:\n  healthProbeBindAddress: :8081\nmetrics:\n  bindAddress: 127.0.0.1:8080\nwebhook:\n  port: 9443\nleaderElection:\n  leaderElect: true\n  resourceName: 5ac9a181.secrets-manager.tuenti.io\n"
  },
  {
    "path": "config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\n\ngeneratorOptions:\n  disableNameSuffixHash: true\n\nconfigMapGenerator:\n- name: manager-config\n  files:\n  - controller_manager_config.yaml\n"
  },
  {
    "path": "config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      securityContext:\n        runAsNonRoot: true\n      containers:\n      - command:\n        - /manager\n        args:\n        - --leader-elect\n        image: controller:latest\n        name: manager\n        securityContext:\n          allowPrivilegeEscalation: false\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 100m\n            memory: 30Mi\n          requests:\n            cpu: 100m\n            memory: 20Mi\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n"
  },
  {
    "path": "config/prometheus/monitor.yaml",
    "content": "\n# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n"
  },
  {
    "path": "config/rbac/auth_proxy_client_clusterrole.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/auth_proxy_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: proxy-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "config/rbac/auth_proxy_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: proxy-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: proxy-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/rbac/auth_proxy_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    targetPort: https\n  selector:\n    control-plane: controller-manager\n"
  },
  {
    "path": "config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# Comment the following 4 lines if you want to disable\n# the auth proxy (https://github.com/brancz/kube-rbac-proxy)\n# which protects your /metrics endpoint.\n- auth_proxy_service.yaml\n- auth_proxy_role.yaml\n- auth_proxy_role_binding.yaml\n- auth_proxy_client_clusterrole.yaml\n"
  },
  {
    "path": "config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/rbac/role.yaml",
    "content": "\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  creationTimestamp: null\n  name: manager-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/rbac/secretdefinition_editor_role.yaml",
    "content": "# permissions for end users to edit secretdefinitions.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: secretdefinition-editor-role\nrules:\n- apiGroups:\n  - secretsmanager.secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - secretsmanager.secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/secretdefinition_viewer_role.yaml",
    "content": "# permissions for end users to view secretdefinitions.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: secretdefinition-viewer-role\nrules:\n- apiGroups:\n  - secretsmanager.secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - secretsmanager.secrets-manager.tuenti.io\n  resources:\n  - secretdefinitions/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/samples/README.md",
    "content": "### Deployment sample\n\nThis examples allows you to deploy vault and secrets-manager in your own cluster, using microk8s.\n\n1.- Deploy vault\n\n`kubectl apply -f vault.yaml`\n\n2.- Expose Vault port locally\n\n`kubectl port-forward $(kubectl get po -l app=vault| awk '{print $1}' | grep -v NAME) 8200:8200`\n\n3.- Get Vault token\n\n`kubectl logs -l app=vault --tail=500 | grep Root`\n\n4.- Vault setup\n\nThis will create the policy, the role and a kubernetes secret containing role_id and secret_id.\n\n`VAULT_TOKEN=<TOKEN_FROM_STEP_3> ./vault-setup.sh`\n\n5.- Install crd\n\n`kubectl apply -f crd.yaml`\n\n6.- Deploy secrets-manager\n\n`kubectl apply -f secrets-manager.yaml`\n\n*NOTE*: You have a `SecretDefinition` example there too to play with it: `secretsmanager_v1alpha1_secretdefinition.yaml`\n"
  },
  {
    "path": "config/samples/crd.yaml",
    "content": "\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.4.1\n  creationTimestamp: null\n  name: secretdefinitions.secrets-manager.tuenti.io\nspec:\n  group: secrets-manager.tuenti.io\n  names:\n    kind: SecretDefinition\n    listKind: SecretDefinitionList\n    plural: secretdefinitions\n    singular: secretdefinition\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: SecretDefinition is the Schema for the secretdefinitions API\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: SecretDefinitionSpec defines the desired state of SecretDefinition\n            properties:\n              keysMap:\n                additionalProperties:\n                  description: DataSource represents the actual source of truth path\n                    for a secret\n                  properties:\n                    encoding:\n                      description: Encoding type for the secret. Only base64 supported.\n                        Optional\n                      type: string\n                    key:\n                      description: Key where the actual secret is stored\n                      type: string\n                    path:\n                      description: Path to the actual secret\n                      type: string\n                  required:\n                  - key\n                  - path\n                  type: object\n                type: object\n              name:\n                description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                  Important: Run \"make\" to regenerate code after modifying this file'\n                type: string\n              type:\n                type: string\n            required:\n            - keysMap\n            - name\n            type: object\n          status:\n            description: SecretDefinitionStatus defines the observed state of SecretDefinition\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n"
  },
  {
    "path": "config/samples/secrets-manager.yaml",
    "content": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app: secrets-manager\n  name: secrets-manager\n  namespace: default\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: secrets-manager\n  labels:\n    app: secrets-manager\nrules:\n- apiGroups:\n  - \"\"\n  - \"secrets-manager.tuenti.io\"\n  resources:\n  - \"secrets\"\n  - \"secretdefinitions\"\n  verbs:\n  - \"get\"\n  - \"list\"\n  - \"watch\"\n  - \"update\"\n  - \"delete\"\n  - \"create\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secrets-manager\n  namespace: default\n  labels:\n    app: secrets-manager\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - \"configmaps\"\n  verbs:\n  - \"get\"\n  - \"list\"\n  - \"watch\"\n  - \"create\"\n  - \"update\"\n- apiGroups:\n  - \"coordination.k8s.io\"\n  resources:\n  - leases\n  verbs:\n  - get\n  - create\n  - update\n  - delete\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: secrets-manager\n  labels:\n    app: secrets-manager\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: secrets-manager\nsubjects:\n  - kind: ServiceAccount\n    name: secrets-manager\n    namespace: default\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: secrets-manager\n  namespace: default\n  labels:\n    app: secrets-manager\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: secrets-manager\nsubjects:\n  - kind: ServiceAccount\n    name: secrets-manager\n    namespace: default\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: secrets-manager-config\n  namespace: default\ndata:\n  secretDefinitions: |-\n    - name: supersecret1\n      type: kubernetes.io/tls\n      namespaces:\n      - default\n      data:\n        tls.crt:\n          encoding: base64\n          path: secret/data/pathtosecret1\n          key: value\n        tls.key:\n          encoding: base64\n          path: secret/data/pathtosecret3\n          key: value\n\n    - name: supersecret2\n      type: Opaque\n      namespaces:\n      - default\n      data:\n        value1:\n          path: secret/data/pathtosecret1\n          key: value\n        value2:\n          path: secret/data/pathtosecret2\n          key: value\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    deployment.kubernetes.io/revision: \"1\"\n  labels:\n    app: secrets-manager\n  name: secrets-manager\n  namespace: default\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: secrets-manager\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: secrets-manager\n    spec:\n      serviceAccountName: secrets-manager\n      containers:\n      - image: secrets-manager:v1.1.0\n        imagePullPolicy: IfNotPresent\n        name: secrets-manager\n        args:\n        - -vault.url=http://vault:8200\n        - -zap-log-level=debug\n        env:\n        - name: VAULT_ROLE_ID\n          valueFrom:\n            secretKeyRef:\n              name: vault-approle-secret\n              key: role_id\n        - name: VAULT_SECRET_ID\n          valueFrom:\n            secretKeyRef:\n              name: vault-approle-secret\n              key: secret_id\n      dnsPolicy: ClusterFirst\n      restartPolicy: Always\n"
  },
  {
    "path": "config/samples/secretsmanager_v1alpha1_secretdefinition.yaml",
    "content": "---\napiVersion: secrets-manager.tuenti.io/v1alpha1\nkind: SecretDefinition\nmetadata:\n  name: secretdefinition-sample\nspec:\n  # Add fields here\n  name: supersecretnew\n  keysMap:\n    decoded:\n      path: secret/data/pathtosecret1\n      encoding: base64\n      key: value\n    raw:\n      path: secret/data/pathtosecret1\n      key: value\n"
  },
  {
    "path": "config/samples/vault-setup.sh",
    "content": "#!/bin/sh\nexport VAULT_ADDR=http://localhost:8200\necho \"Waiting vault to launch on 8200...\"\n\nwhile ! nc -z localhost 8200; do   \n  sleep 0.1 # wait for 1/10 of the second before check again\ndone\n\necho \"Vault launched\"\n\necho \"Enabling approle\"\nvault auth enable approle\n\necho \"Creating vault policy\"\ncat > secrets-manager.hcl  <<EOF\npath \"secret/data/*\" {\n  capabilities = [\"read\"]\n}\nEOF\n\ncat secrets-manager.hcl | vault policy write secrets-manager -\n\necho \"creating role\"\n\nvault write auth/approle/role/secrets-manager policies=secrets-manager secret_id_num_uses=0 secret_id_ttl=0\n\necho \"creating some secrets\"\n\nvault kv put secret/pathtosecret1 \"value=dmFsdWUzCg==\"\nvault kv put secret/pathtosecret2 \"value=value2\"\nvault kv put secret/pathtosecret3 \"value=value3\"\n\n\necho \"creating approle secret\"\nkubectl delete secret vault-approle-secret 2>/dev/null || true\nkubectl create secret generic vault-approle-secret --from-literal role_id=$(vault read --field role_id auth/approle/role/secrets-manager/role-id) --from-literal secret_id=$(vault write --field secret_id -force auth/approle/role/secrets-manager/secret-id)\n"
  },
  {
    "path": "config/samples/vault.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: vault\n  labels:\n    app: vault\nspec:\n  ports:\n    - name: vault\n      port: 8200\n      targetPort: 8200\n      protocol: TCP\n  selector:\n    app: vault\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: vault\n  name: vault\nspec:\n  selector:\n    matchLabels:\n      app: vault\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: vault\n    spec:\n      containers:\n      - image: vault\n        name: vault\n        command:\n        - vault\n        args:\n        - server\n        - -dev\n        - -dev-listen-address=0.0.0.0:8200\n        ports:\n        - containerPort: 8200\n          name: vaultport\n          protocol: TCP\n        volumeMounts:\n        - name: root-home\n          mountPath: /root\n        env:\n        - name: VAULT_ADDR\n          value: http://localhost:8200\n      volumes:\n      - name: root-home\n        emptyDir: {}\n"
  },
  {
    "path": "controllers/metrics.go",
    "content": "package controllers\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics\"\n)\n\nvar (\n\tregistry prometheus.Registry\n\t// Prometeheus metrics: https://prometheus.io\n\tsecretReadErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"controller\",\n\t\tName:      \"secret_read_errors_total\",\n\t\tHelp:      \"Errors total count when reading a secret from Kubernetes\",\n\t}, []string{\"namespace\", \"name\"})\n\n\tsecretSyncErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"controller\",\n\t\tName:      \"sync_errors_total\",\n\t\tHelp:      \"Secrets synchronization total errors.\",\n\t}, []string{\"namespace\", \"name\"})\n\n\tsecretLastSyncStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: \"secrets_manager\",\n\t\tSubsystem: \"controller\",\n\t\tName:      \"last_sync_status\",\n\t\tHelp:      \"The result of the last sync of a secret. 1 = OK, 0 = Error\",\n\t}, []string{\"namespace\", \"name\"})\n)\n\nfunc init() {\n\tr := metrics.Registry\n\tr.MustRegister(secretReadErrorsTotal)\n\tr.MustRegister(secretSyncErrorsTotal)\n\tr.MustRegister(secretLastSyncStatus)\n}\n"
  },
  {
    "path": "controllers/secretdefinition_controller.go",
    "content": "/*\nCopyright 2021.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\tsmv1alpha1 \"github.com/tuenti/secrets-manager/api/v1alpha1\"\n\t\"github.com/tuenti/secrets-manager/backend\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\nconst (\n\t// https://golang.org/pkg/time/#pkg-constants\n\ttimestampFormat = \"2006-01-02T15.04.05Z\"\n\tfinalizerName   = \"secret.finalizer.\" + smv1alpha1.Group\n\tmanagedByLabel  = \"app.kubernetes.io/managed-by\"\n\tlastUpdateLabel = smv1alpha1.Group + \"/lastUpdateTime\"\n)\n\n// SecretDefinitionReconciler reconciles a SecretDefinition object\ntype SecretDefinitionReconciler struct {\n\tclient.Client\n\tBackend              backend.Client\n\tLog                  logr.Logger\n\tAPIReader            client.Reader\n\tReconciliationPeriod time.Duration\n\tExcludeNamespaces    map[string]bool\n\tScheme               *runtime.Scheme\n}\n\n// Annotations to skip when copying from a SecretDef to a Secret\nvar annotationsToSkip = make(map[string]bool)\n\n// Helper functions to merge labels and annotations\ntype skipfn func(string) bool\n\nfunc noSkip(_ string) bool {\n\treturn false\n}\n\nfunc skipAnnotation(key string) bool {\n\treturn annotationsToSkip[key]\n}\n\nfunc mergeMap(dst map[string]string, srcMap map[string]string, skipKey skipfn) {\n\tfor k, v := range srcMap {\n\t\tif skipKey(k) {\n\t\t\tcontinue\n\t\t}\n\t\tdst[k] = v\n\t}\n}\n\nfunc getSecretFromSecretDefinition(sDef *smv1alpha1.SecretDefinition, data map[string][]byte) *corev1.Secret {\n\tobjectMeta := getObjectMetaFromSecretDefinition(sDef)\n\treturn &corev1.Secret{\n\t\tType:       corev1.SecretType(sDef.Spec.Type),\n\t\tObjectMeta: objectMeta,\n\t\tData:       data,\n\t}\n}\n\n// Helper functions to check and remove string from a slice of strings.\nfunc containsString(slice []string, s string) bool {\n\tfor _, item := range slice {\n\t\tif item == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc removeString(slice []string, s string) (result []string) {\n\tfor _, item := range slice {\n\t\tif item == s {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, item)\n\t}\n\treturn\n}\n\n// Ignore not found errors\nfunc ignoreNotFoundError(err error) error {\n\tif errors.IsNotFound(err) {\n\t\treturn nil\n\t}\n\treturn err\n}\n\n// isNotMarkedForRemoval will determine if the SecretDefinition object has been marked to be deleted\nfunc isNotMarkedForRemoval(sDef smv1alpha1.SecretDefinition) bool {\n\treturn sDef.ObjectMeta.DeletionTimestamp.IsZero()\n}\n\n// getDesiredState reads the content from the Datasource for later comparison\nfunc (r *SecretDefinitionReconciler) getDesiredState(keysMap map[string]smv1alpha1.DataSource) (map[string][]byte, error) {\n\n\tdesiredState := make(map[string][]byte)\n\tvar err error\n\tfor k, v := range keysMap {\n\t\tbSecret, err := r.Backend.ReadSecret(v.Path, v.Key)\n\t\tif err != nil {\n\t\t\tr.Log.Error(err, \"unable to read secret from backend\", \"path\", v.Path, \"key\", v.Key)\n\t\t\treturn nil, err\n\t\t}\n\t\tdecoder, err := backend.NewDecoder(v.Encoding)\n\t\tif err != nil {\n\t\t\tr.Log.Error(err, \"refusing to use encoding\", \"encoding\", v.Encoding)\n\t\t\treturn nil, err\n\t\t}\n\t\tdesiredState[k], err = decoder.DecodeString(bSecret)\n\t\tif err != nil {\n\t\t\tr.Log.Error(err, \"unable to decode data for secret\", \"encoding\", v.Encoding, \"path\", v.Path, \"key\", v.Key)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn desiredState, err\n}\n\n// getCurrentState reads the content from the Kubernetes Secret API object for later comparison\nfunc (r *SecretDefinitionReconciler) getCurrentState(ctx context.Context, namespace string, name string) (map[string][]byte, error) {\n\t// We don't read secrets from cache, as it's not the object we reconcile\n\treader := r.APIReader\n\tdata := make(map[string][]byte)\n\tsecret := &corev1.Secret{}\n\terr := reader.Get(ctx, client.ObjectKey{\n\t\tNamespace: namespace,\n\t\tName:      name,\n\t}, secret)\n\tif err != nil {\n\t\tsecretReadErrorsTotal.WithLabelValues(name, namespace).Inc()\n\t\treturn data, err\n\t}\n\tdata = secret.Data\n\treturn data, err\n}\n\n// upsertSecret will create or update a secret\nfunc (r *SecretDefinitionReconciler) upsertSecret(ctx context.Context, sDef *smv1alpha1.SecretDefinition, data map[string][]byte) error {\n\tsecret := getSecretFromSecretDefinition(sDef, data)\n\terr := r.Create(ctx, secret)\n\tif errors.IsAlreadyExists(err) {\n\t\terr = r.Update(ctx, secret)\n\t}\n\treturn err\n}\n\n// deleteSecret will delete a secret given its namespace and name\nfunc (r *SecretDefinitionReconciler) deleteSecret(ctx context.Context, namespace string, name string) error {\n\tsecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tName:      name,\n\t\t},\n\t}\n\treturn r.Delete(ctx, secret)\n}\n\n// shouldExclude will return true if the secretDefinition is in an excluded namespace\nfunc (r *SecretDefinitionReconciler) shouldExclude(sDefNamespace string) bool {\n\tif len(r.ExcludeNamespaces) > 0 {\n\t\treturn r.ExcludeNamespaces[sDefNamespace]\n\t}\n\treturn false\n}\n\n// AddFinalizerIfNotPresent will check if finalizerName is the finalizers slice\nfunc (r *SecretDefinitionReconciler) AddFinalizerIfNotPresent(ctx context.Context, sDef *smv1alpha1.SecretDefinition, finalizerName string) error {\n\tif !containsString(sDef.ObjectMeta.Finalizers, finalizerName) {\n\t\tsDef.ObjectMeta.Finalizers = append(sDef.ObjectMeta.Finalizers, finalizerName)\n\t\treturn r.Update(ctx, sDef)\n\t}\n\treturn nil\n}\n\n// Helper functions to manage corev1.Secret and smv1alpha1.SecretDefinition\nfunc getObjectMetaFromSecretDefinition(sDef *smv1alpha1.SecretDefinition) metav1.ObjectMeta {\n\tlabels := map[string]string{\n\t\tmanagedByLabel: \"secrets-manager\",\n\t}\n\tannotations := map[string]string{\n\t\tlastUpdateLabel: time.Now().Format(timestampFormat),\n\t}\n\n\tmergeMap(labels, sDef.Labels, noSkip)\n\tmergeMap(annotations, sDef.Annotations, skipAnnotation)\n\n\treturn metav1.ObjectMeta{\n\t\tNamespace:   sDef.Namespace,\n\t\tName:        sDef.Spec.Name,\n\t\tLabels:      labels,\n\t\tAnnotations: annotations,\n\t}\n}\n\n//+kubebuilder:rbac:groups=secrets-manager.tuenti.io,resources=secretdefinitions,verbs=get;list;watch;create;update;patch;delete\n//+kubebuilder:rbac:groups=secrets-manager.tuenti.io,resources=secretdefinitions/status,verbs=get;update;patch\n//+kubebuilder:rbac:groups=secrets-manager.tuenti.io,resources=secretdefinitions/finalizers,verbs=update\n//+kubebuilder:rbac:groups=\"\",resources=secrets,verbs=get;list;watch;create;update;patch;delete\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the SecretDefinition object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile\nfunc (r *SecretDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\n\tlog := r.Log.WithValues(\"secretdefinition\", req.NamespacedName)\n\tsDef := &smv1alpha1.SecretDefinition{}\n\n\terr := r.Get(ctx, req.NamespacedName, sDef)\n\tif err != nil {\n\t\tlog.Error(err, fmt.Sprintf(\"could not get SecretDefinition '%s'\", req.NamespacedName))\n\t\treturn ctrl.Result{}, ignoreNotFoundError(err)\n\t}\n\n\tsecretName := sDef.Spec.Name\n\tsecretNamespace := sDef.Namespace\n\n\tlog = log.WithValues(\"secret\", fmt.Sprintf(\"%s/%s\", secretNamespace, secretName))\n\n\tif isNotMarkedForRemoval(*sDef) {\n\n\t\terr = r.AddFinalizerIfNotPresent(ctx, sDef, finalizerName)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update SecretDefinition finalizers\", \"finalizer\", finalizerName)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tif r.shouldExclude(sDef.Namespace) {\n\t\t\tlog.Info(\"Secret definition in excluded namespace, ignoring\", \"excluded_namespaces\", r.ExcludeNamespaces)\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Get data from the secret source of truth\n\t\tdesiredState, err := r.getDesiredState(sDef.Spec.KeysMap)\n\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to get desired state for secret\")\n\t\t\tsecretSyncErrorsTotal.WithLabelValues(secretNamespace, secretName).Inc()\n\t\t\tsecretLastSyncStatus.WithLabelValues(secretNamespace, secretName).Set(0.0)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Get the actual secret from Kubernetes\n\t\tcurrentState, err := r.getCurrentState(ctx, secretNamespace, secretName)\n\n\t\tif err != nil && !errors.IsNotFound(err) {\n\t\t\tlog.Error(err, \"unable to get current state of secret\")\n\t\t\tsecretSyncErrorsTotal.WithLabelValues(secretNamespace, secretName).Inc()\n\t\t\tsecretLastSyncStatus.WithLabelValues(secretNamespace, secretName).Set(0.0)\n\t\t\treturn ctrl.Result{}, ignoreNotFoundError(err)\n\t\t}\n\n\t\teq := reflect.DeepEqual(desiredState, currentState)\n\t\tif !eq {\n\t\t\tlog.Info(\"secret must be updated\")\n\t\t\tif err := r.upsertSecret(ctx, sDef, desiredState); err != nil {\n\t\t\t\tlog.Error(err, \"unable to upsert secret\")\n\t\t\t\tsecretSyncErrorsTotal.WithLabelValues(secretNamespace, secretName).Inc()\n\t\t\t\tsecretLastSyncStatus.WithLabelValues(secretNamespace, secretName).Set(0.0)\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t\tlog.Info(\"secret updated\")\n\t\t}\n\t\tsecretLastSyncStatus.WithLabelValues(secretNamespace, secretName).Set(1.0)\n\t\treturn ctrl.Result{RequeueAfter: r.ReconciliationPeriod}, nil\n\n\t} else {\n\t\t// SecretDefinition has been marked for deletion and contains finalizer\n\t\tif containsString(sDef.ObjectMeta.Finalizers, finalizerName) {\n\t\t\tif err = r.deleteSecret(ctx, secretNamespace, secretName); err != nil && !errors.IsNotFound(err) {\n\t\t\t\tlog.Error(err, \"unable to delete secret\")\n\t\t\t\treturn ctrl.Result{}, ignoreNotFoundError(err)\n\t\t\t}\n\t\t\tlog.Info(\"secret deleted successfully\")\n\t\t\t// If success remove finalizer\n\t\t\tsDef.ObjectMeta.Finalizers = removeString(sDef.ObjectMeta.Finalizers, finalizerName)\n\t\t\tif err = r.Update(ctx, sDef); err != nil {\n\t\t\t\tlog.Error(err, \"unable to remove finalizer from SecretDefinition\", \"finalizer\", finalizerName)\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *SecretDefinitionReconciler) SetupWithManager(mgr ctrl.Manager, name string) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&smv1alpha1.SecretDefinition{}).\n\t\tNamed(name).\n\t\tComplete(r)\n}\n\nfunc init() {\n\t// last-applied-configuration should not be copied from the SecretDef to the Secret\n\tannotationsToSkip[corev1.LastAppliedConfigAnnotation] = true\n}\n"
  },
  {
    "path": "controllers/secretdefinition_controller_test.go",
    "content": "package controllers\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\tsmv1alpha1 \"github.com/tuenti/secrets-manager/api/v1alpha1\"\n\t\"github.com/tuenti/secrets-manager/errors\"\n\n\t\"reflect\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\nconst (\n\tencodedValue = \"bG9yZW0gaXBzdW0gZG9ybWEK\"\n\tdecodedValue = \"lorem ipsum dorma\"\n)\n\nvar _ = Describe(\"SecretsManager\", func() {\n\tvar (\n\t\t//cfg *rest.Config\n\t\tr *SecretDefinitionReconciler\n\n\t\tdecodedBytes, _ = base64.StdEncoding.DecodeString(encodedValue)\n\t\tanyData         = map[string][]byte{\"foo\": decodedBytes}\n\n\t\tsd = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      \"secret-test\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"test.example.com/name\": \"test\",\n\t\t\t\t\t\"name\":                  \"secret_labels\",\n\t\t\t\t},\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\"ann1\": \"another_value\",\n\t\t\t\t\t\"ann2\": \"just_a_value\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-test\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"foo\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdWithSkipAnnotations = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      \"secret-test\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"test.example.com/name\": \"test\",\n\t\t\t\t\t\"name\":                  \"secret_labels\",\n\t\t\t\t},\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\"ann1\":                             \"another_value\",\n\t\t\t\t\t\"ann2\":                             \"just_a_value\",\n\t\t\t\t\tcorev1.LastAppliedConfigAnnotation: \"to_be_skipped\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-test\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"foo\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsd2 = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      \"secret-test2\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-test2\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"foo2\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdNotWatched = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"notwatched\",\n\t\t\t\tName:      \"secret-notwatched\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-notwatched\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"notwatched\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdWatched = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"watched\",\n\t\t\t\tName:      \"secret-watched\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-watched\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"watched\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdMultiWatched1 = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"watched1\",\n\t\t\t\tName:      \"secret-multi1\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-multi1\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"multival1\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdMultiWatched2 = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"watched2\",\n\t\t\t\tName:      \"secret-multi2\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-multi2\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"multival2\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdBackendSecretNotFound = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      \"secret-beckend-secret-not-found\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-backend-secret-not-found\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"foo3\": {\n\t\t\t\t\t\tPath:     \"secret/data/notfound\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdWrongEncoding = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      \"secret-wrong-encoding\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-wrong-encoding\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"foo4\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base65\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tsdExcludedNs = &smv1alpha1.SecretDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      \"secret-excluded-ns\",\n\t\t\t},\n\t\t\tSpec: smv1alpha1.SecretDefinitionSpec{\n\t\t\t\tName: \"secret-excluded-ns\",\n\t\t\t\tType: \"Opaque\",\n\t\t\t\tKeysMap: map[string]smv1alpha1.DataSource{\n\t\t\t\t\t\"fooExcludedNs\": {\n\t\t\t\t\t\tPath:     \"secret/data/pathtosecret1\",\n\t\t\t\t\t\tKey:      \"value\",\n\t\t\t\t\t\tEncoding: \"base64\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t)\n\n\tBeforeEach(func() {\n\t\tr = getReconciler()\n\t\tcfg = getConfig()\n\t})\n\n\tAfterEach(func() {\n\t})\n\n\tContext(\"SecretDefinitionReconciler.Reconcile\", func() {\n\n\t\tIt(\"Create a secretdefinition and read the secret\", func() {\n\t\t\t// setup:\n\t\t\tsecretdefinition := sd\n\t\t\tctx := context.Background()\n\n\t\t\t// when:\n\t\t\terr := r.Create(ctx, secretdefinition)\n\t\t\tres, err2 := r.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: secretdefinition.Namespace,\n\t\t\t\t\tName:      secretdefinition.Name,\n\t\t\t\t},\n\t\t\t})\n\t\t\tdata, err3 := r.getCurrentState(ctx, \"default\", secretdefinition.ObjectMeta.Name)\n\t\t\tprint(data)\n\t\t\t// then:\n\t\t\tExpect(err).To(BeNil())\n\n\t\t\tExpect(res).ToNot(BeNil())\n\t\t\tExpect(err2).To(BeNil())\n\n\t\t\tExpect(err3).To(BeNil())\n\t\t\tExpect(data).To(Equal(anyData))\n\n\t\t\t//Expect(data).To(HaveKey(\"finalizers\"))\n\n\t\t\t//(\"finalizers\", \"secret.finalizer.secrets-manager.tuenti.io\"))\n\t\t})\n\n\t\tIt(\"Delete a secretdefinition should delete a secret\", func() {\n\t\t\t// setup:\n\t\t\tsecretdefinition := sd2\n\t\t\tctx := context.Background()\n\n\t\t\t// when:\n\t\t\terr := r.Create(ctx, secretdefinition)\n\t\t\tres, err2 := r.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: secretdefinition.Namespace,\n\t\t\t\t\tName:      secretdefinition.Name,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\t// then:\n\t\t\tExpect(err).To(BeNil())\n\t\t\tExpect(res).ToNot(BeNil())\n\t\t\tExpect(err2).To(BeNil())\n\t\t\t//Expect(secretdefinition.ObjectMeta.Finalizers).To(BeEmpty())\n\n\t\t\t// when:\n\t\t\tdata, err3 := r.getCurrentState(ctx, \"default\", secretdefinition.ObjectMeta.Name)\n\n\t\t\t// then:\n\t\t\tExpect(err3).To(BeNil())\n\t\t\tExpect(data).To(Equal(map[string][]byte{\"foo2\": decodedBytes}))\n\n\t\t\t// when:\n\t\t\terr4 := r.Delete(ctx, secretdefinition)\n\t\t\t_, err5 := r.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: secretdefinition.Namespace,\n\t\t\t\t\tName:      secretdefinition.Name,\n\t\t\t\t},\n\t\t\t})\n\t\t\tdata2, err6 := r.getCurrentState(ctx, \"default\", secretdefinition.ObjectMeta.Name)\n\n\t\t\t// then:\n\t\t\tExpect(err4).To(BeNil())\n\t\t\tExpect(err5).To(BeNil())\n\t\t\tExpect(err6).ToNot(BeNil())\n\t\t\tExpect(data2).To(BeEmpty())\n\n\t\t})\n\t\tIt(\"Create a secretdefinition with a secret not deployed in the backend\", func() {\n\t\t\tctx := context.Background()\n\t\t\terr := r.Create(ctx, sdBackendSecretNotFound)\n\t\t\tExpect(err).To(BeNil())\n\t\t\tres, err2 := r.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: sdBackendSecretNotFound.Namespace,\n\t\t\t\t\tName:      sdBackendSecretNotFound.Name,\n\t\t\t\t},\n\t\t\t})\n\t\t\tExpect(err2).ToNot(BeNil())\n\t\t\tExpect(res).To(Equal(reconcile.Result{}))\n\t\t})\n\t\tIt(\"Create a secretdefinition with a wrong encoding\", func() {\n\t\t\tctx := context.Background()\n\t\t\texpectedErr := &errors.EncodingNotImplementedError{}\n\t\t\terr := r.Create(ctx, sdWrongEncoding)\n\t\t\tExpect(err).To(BeNil())\n\t\t\tres, err2 := r.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: sdWrongEncoding.Namespace,\n\t\t\t\t\tName:      sdWrongEncoding.Name,\n\t\t\t\t},\n\t\t\t})\n\t\t\tExpect(reflect.TypeOf(err2)).To(Equal(reflect.TypeOf(expectedErr)))\n\t\t\tExpect(res).To(Equal(reconcile.Result{}))\n\t\t})\n\t\tIt(\"Create a secretdefinition in a excluded namespace\", func() {\n\t\t\t// setup:\n\t\t\tsecretdefinition := sdExcludedNs\n\t\t\tr2 := getReconciler()\n\t\t\tr2.ExcludeNamespaces = map[string]bool{secretdefinition.Namespace: true}\n\t\t\tctx := context.Background()\n\n\t\t\t// when:\n\t\t\terr := r.Create(ctx, secretdefinition)\n\t\t\tres, err2 := r.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: secretdefinition.Namespace,\n\t\t\t\t\tName:      secretdefinition.Name,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\t// then:\n\t\t\tExpect(err).To(BeNil())\n\n\t\t\tExpect(err2).To(BeNil())\n\t\t\tExpect(res).To(Equal(reconcile.Result{}))\n\t\t})\n\t})\n\tContext(\"SecretDefinitionReconciler.upsertSecret\", func() {\n\n\t\tIt(\"Upsert a secret twice should not raise an error\", func() {\n\t\t\t// setup:\n\t\t\tsecretdefinition := sd\n\t\t\tctx := context.Background()\n\n\t\t\t// when:\n\t\t\terr := r.upsertSecret(ctx, secretdefinition, anyData)\n\t\t\terr2 := r.upsertSecret(ctx, secretdefinition, anyData)\n\n\t\t\t// then:\n\t\t\tExpect(err).To(BeNil())\n\t\t\tExpect(err2).To(BeNil())\n\t\t})\n\t\tIt(\"Upsert a secret\", func() {\n\t\t\t// setup:\n\t\t\tsecretdefinition := sd\n\t\t\tctx := context.Background()\n\n\t\t\t// when:\n\t\t\terr := r.upsertSecret(ctx, secretdefinition, anyData)\n\n\t\t\t// then:\n\t\t\tExpect(err).To(BeNil())\n\t\t})\n\t})\n\tContext(\"SecretDefinitionReconciler.getObjectMetaFromSecretDefinition\", func() {\n\n\t\tIt(\"getObjectMetaFromSecretDefinition should return an ObjectMeta\", func() {\n\t\t\t// when:\n\t\t\tobjectMeta := getObjectMetaFromSecretDefinition(sd)\n\n\t\t\t// then:\n\t\t\tExpect(sd.Namespace).To(Equal(objectMeta.Namespace))\n\t\t\tExpect(sd.Name).To(Equal(objectMeta.Name))\n\n\t\t\tfor k := range sd.Labels {\n\t\t\t\tExpect(objectMeta.Labels).Should(HaveKey(k))\n\t\t\t}\n\t\t\tfor k := range sd.Annotations {\n\t\t\t\tExpect(objectMeta.Annotations).Should(HaveKey(k))\n\t\t\t}\n\t\t})\n\t\tIt(\"getObjectMetaFromSecretDefinition should add custom labels and annotations to the objectMeta\", func() {\n\t\t\t// when:\n\t\t\tobjectMeta := getObjectMetaFromSecretDefinition(sd)\n\n\t\t\t// then:\n\t\t\tExpect(objectMeta.Labels).Should(HaveKey(\"app.kubernetes.io/managed-by\"))\n\t\t\tExpect(objectMeta.Annotations).Should(HaveKey(\"secrets-manager.tuenti.io/lastUpdateTime\"))\n\t\t})\n\t\tIt(\"getObjectMetaFromSecretDefinition should skip expected annotations\", func() {\n\t\t\t// when:\n\t\t\tobjectMeta := getObjectMetaFromSecretDefinition(sdWithSkipAnnotations)\n\n\t\t\t// then:\n\t\t\tExpect(objectMeta.Labels).Should(Not(HaveKey(corev1.LastAppliedConfigAnnotation)))\n\t\t})\n\n\t})\n\tContext(\"Manager.MultiNamespacedCache\", func() {\n\n\t\tIt(\"Creates secret in watched namespace\", func(done Done) {\n\t\t\tscheme := getScheme()\n\n\t\t\tdecodedBytes, _ := base64.StdEncoding.DecodeString(encodedValue)\n\t\t\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\t\t\tScheme:             scheme,\n\t\t\t\tMetricsBindAddress: \"0\",\n\t\t\t\tLeaderElection:     false,\n\t\t\t\tNewCache:           cache.MultiNamespacedCacheBuilder([]string{\"watched\"}),\n\t\t\t})\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(mgr).ToNot(BeNil())\n\t\t\tr2 := getReconciler()\n\t\t\tr2.SetupWithManager(mgr, \"test1\")\n\n\t\t\t// Stream generates values with DoSomething and sends them to out\n\t\t\t// until DoSomething returns an error or ctx.Done is closed.\n\t\t\tctx, cancelfunc := context.WithCancel(context.Background())\n\n\t\t\tgo func() {\n\t\t\t\tdefer GinkgoRecover()\n\t\t\t\tExpect(mgr.Start(ctx)).NotTo(HaveOccurred())\n\t\t\t\tclose(done)\n\t\t\t}()\n\n\t\t\tr2.Create(ctx, sdWatched)\n\t\t\t// Sleep for 4 * the reconcile interval set on the controller (just to be safe)\n\t\t\ttime.Sleep(4 * time.Second)\n\t\t\tdata, err := r2.getCurrentState(ctx, \"watched\", sdWatched.Spec.Name)\n\t\t\tExpect(err).To(BeNil())\n\t\t\tExpect(data).To(Equal(map[string][]byte{\"watched\": decodedBytes}))\n\t\t\tcancelfunc()\n\n\t\t}, 10)\n\n\t\tIt(\"Doesn't create secret in unwatched namespace\", func(done Done) {\n\t\t\tscheme := getScheme()\n\n\t\t\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\t\t\tScheme:             scheme,\n\t\t\t\tMetricsBindAddress: \"0\",\n\t\t\t\tLeaderElection:     false,\n\t\t\t\tNewCache:           cache.MultiNamespacedCacheBuilder([]string{\"watched\"}),\n\t\t\t})\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(mgr).ToNot(BeNil())\n\t\t\tr2 := getReconciler()\n\t\t\tr2.SetupWithManager(mgr, \"test2\")\n\n\t\t\tctx, cancelfunc := context.WithCancel(context.Background())\n\t\t\tgo func() {\n\t\t\t\tdefer GinkgoRecover()\n\t\t\t\tExpect(mgr.Start(ctx)).NotTo(HaveOccurred())\n\t\t\t\tclose(done)\n\t\t\t}()\n\n\t\t\tr2.Create(ctx, sdNotWatched)\n\t\t\t// Sleep for 4 * the reconcile interval set on the controller (just to be safe)\n\t\t\ttime.Sleep(4 * time.Second)\n\t\t\tdata, err := r2.getCurrentState(ctx, \"notwatched\", sdNotWatched.Spec.Name)\n\t\t\tExpect(err.Error()).To(Equal(\"secrets \\\"secret-notwatched\\\" not found\"))\n\t\t\tExpect(data).To(BeEmpty())\n\t\t\tcancelfunc()\n\n\t\t}, 10)\n\n\t\tIt(\"Creates secrets in multiple watched namespaces\", func(done Done) {\n\t\t\tscheme := getScheme()\n\n\t\t\tdecodedBytes, _ := base64.StdEncoding.DecodeString(encodedValue)\n\t\t\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\t\t\tScheme:             scheme,\n\t\t\t\tMetricsBindAddress: \"0\",\n\t\t\t\tLeaderElection:     false,\n\t\t\t\tNewCache:           cache.MultiNamespacedCacheBuilder([]string{\"watched1\", \"watched2\"}),\n\t\t\t})\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(mgr).ToNot(BeNil())\n\t\t\tr2 := getReconciler()\n\t\t\tr2.SetupWithManager(mgr, \"test3\")\n\n\t\t\tctx, cancelfunc := context.WithCancel(context.Background())\n\t\t\tgo func() {\n\t\t\t\tdefer GinkgoRecover()\n\t\t\t\tExpect(mgr.Start(ctx)).NotTo(HaveOccurred())\n\n\t\t\t\tclose(done)\n\t\t\t}()\n\n\t\t\tr2.Create(ctx, sdMultiWatched1)\n\t\t\tr2.Create(ctx, sdMultiWatched2)\n\t\t\t// Sleep for 4 * the reconcile interval set on the controller (just to be safe)\n\t\t\ttime.Sleep(4 * time.Second)\n\t\t\tdata, err2 := r2.getCurrentState(ctx, \"watched1\", sdMultiWatched1.Spec.Name)\n\t\t\tExpect(err2).To(BeNil())\n\t\t\tExpect(data).To(Equal(map[string][]byte{\"multival1\": decodedBytes}))\n\n\t\t\tdata2, err3 := r2.getCurrentState(ctx, \"watched2\", sdMultiWatched2.Spec.Name)\n\t\t\tExpect(err3).To(BeNil())\n\t\t\tExpect(data2).To(Equal(map[string][]byte{\"multival2\": decodedBytes}))\n\n\t\t\tcancelfunc()\n\n\t\t}, 10)\n\t})\n})\n"
  },
  {
    "path": "controllers/suite_test.go",
    "content": "/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\n\tsecretsmanagerv1alpha1 \"github.com/tuenti/secrets-manager/api/v1alpha1\"\n\t\"k8s.io/client-go/rest\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest/printer\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar cfg *rest.Config\nvar k8sClient client.Client\nvar r *SecretDefinitionReconciler\nvar testEnv *envtest.Environment\nvar mgr ctrl.Manager\nvar scheme *runtime.Scheme\n\ntype fakeBackendSecret struct {\n\tPath    string\n\tKey     string\n\tContent string\n}\n\ntype fakeBackend struct {\n\tfakeSecrets []fakeBackendSecret\n}\n\nfunc newFakeBackend(fakeSecrets []fakeBackendSecret) fakeBackend {\n\treturn fakeBackend{\n\t\tfakeSecrets: fakeSecrets,\n\t}\n}\n\nfunc (f fakeBackend) ReadSecret(path string, key string) (string, error) {\n\tfor _, fakeSecret := range f.fakeSecrets {\n\t\tif fakeSecret.Path == path && fakeSecret.Key == key {\n\t\t\treturn fakeSecret.Content, nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"Not found\")\n\n}\n\nfunc getReconciler() *SecretDefinitionReconciler {\n\treturn r\n}\n\nfunc getConfig() *rest.Config {\n\treturn cfg\n}\n\nfunc getScheme() *runtime.Scheme {\n\treturn scheme\n}\n\nfunc TestSecretDefinitionController(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecsWithDefaultAndCustomReporters(t,\n\t\t\"Controller Suite\",\n\t\t[]Reporter{printer.NewlineReporter{}})\n}\n\nvar _ = BeforeSuite(func(done Done) {\n\tnamespaces := [...]string{\"notwatched\", \"watched\", \"watched1\", \"watched2\"}\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths: []string{filepath.Join(\"..\", \"config\", \"crd\", \"bases\")},\n\t}\n\tvar err error\n\n\tcfg, err = testEnv.Start()\n\tExpect(err).ToNot(HaveOccurred())\n\tExpect(cfg).ToNot(BeNil())\n\n\tscheme = runtime.NewScheme()\n\tcorev1.AddToScheme(scheme)\n\tsecretsmanagerv1alpha1.AddToScheme(scheme)\n\n\terr = secretsmanagerv1alpha1.AddToScheme(scheme)\n\tExpect(err).ToNot(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tmgr, err = ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme:             scheme,\n\t\tMetricsBindAddress: \"0\",\n\t\tLeaderElection:     false,\n\t})\n\tExpect(err).ToNot(HaveOccurred())\n\tExpect(mgr).ToNot(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme})\n\tExpect(err).ToNot(HaveOccurred())\n\n\tfor _, ns := range namespaces {\n\t\tnsSpec := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: \"\"}}\n\t\tnsSpec.Name = ns\n\t\tk8sClient.Create(context.Background(), nsSpec)\n\t}\n\n\tr = &SecretDefinitionReconciler{\n\t\tBackend: newFakeBackend([]fakeBackendSecret{\n\t\t\t{\"secret/data/pathtosecret1\", \"value\", \"bG9yZW0gaXBzdW0gZG9ybWEK\"},\n\t\t}),\n\t\tClient:               k8sClient,\n\t\tAPIReader:            k8sClient,\n\t\tReconciliationPeriod: 1 * time.Second,\n\t\tLog:                  logf.Log.WithName(\"controllers-test\").WithName(\"SecretDefinition\"),\n\t}\n\terr = r.SetupWithManager(mgr, \"testing\")\n\t//Expect(err).ToNot(HaveOccurred())*/\n\n\tExpect(err).ToNot(HaveOccurred())\n\n\tclose(done)\n}, 60)\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\terr := testEnv.Stop()\n\tExpect(err).ToNot(HaveOccurred())\n})\n"
  },
  {
    "path": "deploy/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.16 as builder\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the go source\nCOPY main.go main.go\nCOPY api/ api/\nCOPY controllers/ controllers/\nCOPY backend/ backend/\nCOPY errors/ errors/\nCOPY hack/ hack/\nARG SECRETS_MANAGER_VERSION\n\n# Build\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \"-X main.version=${SECRETS_MANAGER_VERSION}\" -a -o secrets-manager main.go\n\n\n#\n# Prod image\n#\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot as release\nWORKDIR /\nCOPY --from=builder /workspace/secrets-manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/secrets-manager\"]\n\n\n#\n# Dev image\n#\n\nFROM builder as dev\n\nENV ENVTEST_ASSETS_DIR=testbin\nENV ENVTEST_K8S_VERSION=1.19.2\n# kubebuilder needed to run tests in development environment\nRUN curl -L -O https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.1.0/kubebuilder_linux_amd64\nRUN mv kubebuilder_linux_amd64 kubebuilder \\\n    && chmod 755 kubebuilder \\\n    && mv kubebuilder /usr/local/bin\nRUN export PATH=$PATH:/usr/local/bin\n"
  },
  {
    "path": "deploy/version/get.sh",
    "content": "#!/bin/bash\n\nawk '{ split($0,parts,\"=\"); print parts[2]}' $(pwd)/deploy/version/version.properties"
  },
  {
    "path": "deploy/version/update.sh",
    "content": "#!/bin/bash\n\nversion_properties_file=$(pwd)/deploy/version/version.properties\n\nupdate_major() {\n    new_version=\"v$(awk '{ split($0,parts,\"=v\"); split(parts[2],version,\".\"); print version[1]+1 \".\" version[2] \".\" version[3]}' $version_properties_file)\";\n    echo \"version=${new_version}\" > $version_properties_file\n}\n\nupdate_minor() {\n    new_version=\"v$(awk '{ split($0,parts,\"=v\"); split(parts[2],version,\".\"); print version[1] \".\" version[2]+1 \".\" version[3]}' $version_properties_file)\";\n    echo \"version=${new_version}\" > $version_properties_file\n}\n\nupdate_patch() {\n    new_version=\"v$(awk '{ split($0,parts,\"=v\"); split(parts[2],version,\".\"); print version[1] \".\" version[2] \".\" version[3]+1}' $version_properties_file)\";\n    echo \"version=${new_version}\" > $version_properties_file\n}\n\nupdate_kind=$1\n\ncase $update_kind in\n    \"--major\" | \"-M\")\n        echo \"Updating major version\"\n        update_major\n        ;;\n    \"--minor\" | \"-m\")\n        echo \"Updating minor version\"\n        update_minor\n        ;;\n    \"--patch\" | \"-p\")\n        echo \"Updating patch version\"\n        update_patch\n        ;;\n    *)\n        update_minor\n        ;;\nesac\n"
  },
  {
    "path": "deploy/version/version.properties",
    "content": "version=v2.1.0\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: '3.4'\nservices:\n  secrets-manager-local:\n    build:\n      context: .\n      target: dev\n      dockerfile: ./deploy/Dockerfile\n    volumes:\n      - \"./:/workspace\"\n\n  tests:\n    build:\n      context: .\n      target: dev\n      dockerfile: ./deploy/Dockerfile\n    volumes:\n      - \"./:/workspace\"\n    command: >\n      bash -c \"pwd && mkdir -p controllers/$$ENVTEST_ASSETS_DIR &&\n      test -f controllers/$$ENVTEST_ASSETS_DIR/setup-envtest.sh || curl -sSLo controllers/$$ENVTEST_ASSETS_DIR/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.8.3/hack/setup-envtest.sh &&\n      source controllers/$$ENVTEST_ASSETS_DIR/setup-envtest.sh &&\n      fetch_envtest_tools controllers/$$ENVTEST_ASSETS_DIR &&\n      setup_envtest_env $$ENVTEST_ASSETS_DIR &&\n      go test -v ./backend... ./errors/... ./controllers/... -coverprofile cover.out\"\n\n  secrets-manager:\n    build:\n      context: .\n      target: release\n      dockerfile: ./deploy/Dockerfile\n"
  },
  {
    "path": "errors/errors.go",
    "content": "package errors\n\nimport \"fmt\"\n\n// Error Types constants\nconst (\n\tUnknownErrorType                   = \"UnknownError\"\n\tBackendNotImplementedErrorType     = \"BackendNotImplementedError\"\n\tBackendSecretNotFoundErrorType     = \"BackendSecretNotFoundError\"\n\tBackendSecretForbiddenErrorType    = \"BackendSecretForbiddenError\"\n\tK8sSecretNotFoundErrorType         = \"K8sSecretNotFoundError\"\n\tEncodingNotImplementedErrorType    = \"EncodingNotImplementedError\"\n\tVaultEngineNotImplementedErrorType = \"VaultEngineNotImplementedError\"\n\tVaultTokenNotRenewableErrorType    = \"VaultTokenNotRenewableError\"\n)\n\n// BackendNotImplementedError will be raised if the selected backend is not implemented\ntype BackendNotImplementedError struct {\n\tErrType string\n\tBackend string\n}\n\n// BackendSecretNotFoundError will be raised if secret is not found in the selected backend\ntype BackendSecretNotFoundError struct {\n\tErrType string\n\tPath    string\n\tKey     string\n}\n\n// K8sSecretNotFoundError will be raised if secret is not found by its name in the given namespace\ntype K8sSecretNotFoundError struct {\n\tErrType   string\n\tName      string\n\tNamespace string\n}\n\n// EncodingNotImplementedError will be raised if the selected encoding is not implemented\ntype EncodingNotImplementedError struct {\n\tErrType  string\n\tEncoding string\n}\n\n// VaultEngineNotImplementedError will be raised if the selected engine is not implemented\ntype VaultEngineNotImplementedError struct {\n\tErrType string\n\tEngine  string\n}\n\n// VaultTokenNotRenewableError will be raised if secrets-manager Vault token is not renewable\ntype VaultTokenNotRenewableError struct {\n\tErrType string\n}\n\nfunc getErrorType(err error) string {\n\tswitch err.(type) {\n\tcase *BackendNotImplementedError:\n\t\treturn BackendNotImplementedErrorType\n\tcase *BackendSecretNotFoundError:\n\t\treturn BackendSecretNotFoundErrorType\n\tcase *K8sSecretNotFoundError:\n\t\treturn K8sSecretNotFoundErrorType\n\tcase *EncodingNotImplementedError:\n\t\treturn EncodingNotImplementedErrorType\n\tcase *VaultEngineNotImplementedError:\n\t\treturn VaultEngineNotImplementedErrorType\n\tcase *VaultTokenNotRenewableError:\n\t\treturn VaultTokenNotRenewableErrorType\n\tdefault:\n\t\treturn UnknownErrorType\n\t}\n}\n\nfunc (e BackendNotImplementedError) Error() string {\n\treturn fmt.Sprintf(\"[%s] backend %s not supported\", e.ErrType, e.Backend)\n}\n\nfunc (e BackendSecretNotFoundError) Error() string {\n\treturn fmt.Sprintf(\"[%s] secret key %s not found at %s\", e.ErrType, e.Key, e.Path)\n}\n\nfunc (e K8sSecretNotFoundError) Error() string {\n\treturn fmt.Sprintf(\"[%s] secret '%s/%s' not found\", e.ErrType, e.Namespace, e.Name)\n}\n\nfunc (e EncodingNotImplementedError) Error() string {\n\treturn fmt.Sprintf(\"[%s] encoding %s not supported\", e.ErrType, e.Encoding)\n}\n\nfunc (e VaultEngineNotImplementedError) Error() string {\n\treturn fmt.Sprintf(\"[%s] vault engine %s not supported\", e.ErrType, e.Engine)\n}\n\nfunc (e VaultTokenNotRenewableError) Error() string {\n\treturn fmt.Sprintf(\"[%s] vault token not renewable\", e.ErrType)\n}\n\n// IsBackendNotImplemented returns true if the error is type of BackendNotImplementedError and false otherwise\nfunc IsBackendNotImplemented(err error) bool {\n\treturn getErrorType(err) == BackendNotImplementedErrorType\n}\n\n// IsBackendSecretNotFound returns true if the error is type of BackendSecretNotFound and false otherwise\nfunc IsBackendSecretNotFound(err error) bool {\n\treturn getErrorType(err) == BackendSecretNotFoundErrorType\n}\n\n// IsK8sSecretNotFound returns true if the error is type of K8sSecretNotFound and false otherwise\nfunc IsK8sSecretNotFound(err error) bool {\n\treturn getErrorType(err) == K8sSecretNotFoundErrorType\n}\n\n// IsEncodingNotImplemented returns true if the error is type of EncodingNotImplementedError and false otherwise\nfunc IsEncodingNotImplemented(err error) bool {\n\treturn getErrorType(err) == EncodingNotImplementedErrorType\n}\n\n// IsVaultEngineNotImplemented returns true if the error is type of VaultEngineNotImplementedError and false otherwise\nfunc IsVaultEngineNotImplemented(err error) bool {\n\treturn getErrorType(err) == VaultEngineNotImplementedErrorType\n}\n\n// IsVaultTokenNotRenewable returns true if the error is type of VaultTokenNotRenewableError and false otherwise\nfunc IsVaultTokenNotRenewable(err error) bool {\n\treturn getErrorType(err) == VaultTokenNotRenewableErrorType\n}\n"
  },
  {
    "path": "errors/errors_test.go",
    "content": "package errors\n\nimport (\n\te \"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestErrorString(t *testing.T) {\n\terr1 := &BackendNotImplementedError{ErrType: BackendNotImplementedErrorType, Backend: \"foo\"}\n\tassert.EqualError(t, err1, fmt.Sprintf(\"[%s] backend %s not supported\", err1.ErrType, err1.Backend))\n\terr2 := &BackendSecretNotFoundError{ErrType: BackendSecretNotFoundErrorType, Path: \"foo\", Key: \"bar\"}\n\tassert.EqualError(t, err2, fmt.Sprintf(\"[%s] secret key %s not found at %s\", err2.ErrType, err2.Key, err2.Path))\n\terr3 := &K8sSecretNotFoundError{ErrType: K8sSecretNotFoundErrorType, Name: \"foo\", Namespace: \"bar\"}\n\tassert.EqualError(t, err3, fmt.Sprintf(\"[%s] secret '%s/%s' not found\", err3.ErrType, err3.Namespace, err3.Name))\n\terr5 := &EncodingNotImplementedError{ErrType: EncodingNotImplementedErrorType, Encoding: \"foo\"}\n\tassert.EqualError(t, err5, fmt.Sprintf(\"[%s] encoding %s not supported\", err5.ErrType, err5.Encoding))\n\terr6 := &VaultEngineNotImplementedError{ErrType: VaultEngineNotImplementedErrorType, Engine: \"foo\"}\n\tassert.EqualError(t, err6, fmt.Sprintf(\"[%s] vault engine %s not supported\", err6.ErrType, err6.Engine))\n\terr7 := &VaultTokenNotRenewableError{ErrType: VaultTokenNotRenewableErrorType}\n\tassert.EqualError(t, err7, fmt.Sprintf(\"[%s] vault token not renewable\", err7.ErrType))\n}\n\nfunc TestGetErrorType(t *testing.T) {\n\terr1 := e.New(\"foo\")\n\tassert.Equal(t, getErrorType(err1), UnknownErrorType)\n\terr2 := &BackendNotImplementedError{ErrType: BackendNotImplementedErrorType}\n\tassert.Equal(t, getErrorType(err2), BackendNotImplementedErrorType)\n\terr3 := &BackendSecretNotFoundError{ErrType: BackendSecretNotFoundErrorType}\n\tassert.Equal(t, getErrorType(err3), BackendSecretNotFoundErrorType)\n\terr4 := &K8sSecretNotFoundError{ErrType: K8sSecretNotFoundErrorType}\n\tassert.Equal(t, getErrorType(err4), K8sSecretNotFoundErrorType)\n\terr6 := &EncodingNotImplementedError{ErrType: EncodingNotImplementedErrorType}\n\tassert.Equal(t, getErrorType(err6), EncodingNotImplementedErrorType)\n\terr7 := &VaultEngineNotImplementedError{ErrType: VaultEngineNotImplementedErrorType}\n\tassert.Equal(t, getErrorType(err7), VaultEngineNotImplementedErrorType)\n\terr8 := &VaultTokenNotRenewableError{ErrType: VaultTokenNotRenewableErrorType}\n\tassert.Equal(t, getErrorType(err8), VaultTokenNotRenewableErrorType)\n}\n\nfunc TestIsBackendNotImplemented(t *testing.T) {\n\terr := &BackendNotImplementedError{ErrType: BackendNotImplementedErrorType}\n\tassert.True(t, IsBackendNotImplemented(err))\n\terr2 := e.New(\"foo\")\n\tassert.False(t, IsBackendNotImplemented(err2))\n}\n\nfunc TestIsBackendSecretNotFound(t *testing.T) {\n\terr := &BackendSecretNotFoundError{ErrType: BackendSecretNotFoundErrorType}\n\tassert.True(t, IsBackendSecretNotFound(err))\n\terr2 := e.New(\"foo\")\n\tassert.False(t, IsBackendSecretNotFound(err2))\n}\n\nfunc TestIsK8sSecretNotFound(t *testing.T) {\n\terr := &K8sSecretNotFoundError{ErrType: K8sSecretNotFoundErrorType}\n\tassert.True(t, IsK8sSecretNotFound(err))\n\terr2 := e.New(\"foo\")\n\tassert.False(t, IsK8sSecretNotFound(err2))\n}\n\nfunc TestIsEncodingNotImplemented(t *testing.T) {\n\terr := &EncodingNotImplementedError{ErrType: EncodingNotImplementedErrorType}\n\tassert.True(t, IsEncodingNotImplemented(err))\n\terr2 := e.New(\"foo\")\n\tassert.False(t, IsEncodingNotImplemented(err2))\n}\n\nfunc TestIsVaultEngineNotImplemented(t *testing.T) {\n\terr := &VaultEngineNotImplementedError{ErrType: VaultEngineNotImplementedErrorType}\n\tassert.True(t, IsVaultEngineNotImplemented(err))\n}\n\nfunc TestIsVaultTokenNotRenewable(t *testing.T) {\n\terr := &VaultTokenNotRenewableError{ErrType: VaultTokenNotRenewableErrorType}\n\tassert.True(t, IsVaultTokenNotRenewable(err))\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/tuenti/secrets-manager\n\ngo 1.16\n\nrequire (\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2\n\tgithub.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.6.0\n\tgithub.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect\n\tgithub.com/go-logr/logr v0.3.0\n\tgithub.com/gorilla/mux v1.7.4\n\tgithub.com/hashicorp/vault/api v1.2.0\n\tgithub.com/onsi/ginkgo v1.14.1\n\tgithub.com/onsi/gomega v1.10.2\n\tgithub.com/prometheus/client_golang v1.7.1\n\tgithub.com/stretchr/testify v1.7.0\n\tgolang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect\n\tgolang.org/x/net v0.0.0-20220403103023-749bd193bc2b\n\tk8s.io/api v0.20.2\n\tk8s.io/apimachinery v0.20.2\n\tk8s.io/client-go v0.20.2\n\tsigs.k8s.io/controller-runtime v0.8.3\n)\n"
  },
  {
    "path": "go.sum",
    "content": "bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0/go.mod h1:ZPW/Z0kLCTdDZaDbYTetxc9Cxl/2lNqxYHYNOF2bti0=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 h1:zBJcBJwte0x6PcPK7XaWDMvK2o2ZM2f1sMaqNNavQ5g=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.0/go.mod h1:TmXReXZ9yPp5D5TBRMTAtyz+UyOl15Py4hL5E5p6igQ=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 h1:mM/yraAumqMMIYev6zX0oxHqX6hreUs5wXf76W47r38=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2/go.mod h1:+nVKciyKD2J9TyVcEQ82Bo9b+3F92PiQfHrIE/zqLqM=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 h1:sLZ/Y+P/5RRtsXWylBjB5lkgixYfm0MQPiwrSX//JSo=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.6.0 h1:bupm+qpGJpsHKCDBfrtUIQQuODnMLz9iey76vvw8q+c=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.6.0/go.mod h1:mmFwM6gatrMBYRa2qirJkkehnh91KdrpAgcCT4IeD5s=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 h1:lirjIOHv5RrmDbZXw9lUz/fY68uU05qR4uIef58WMvQ=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1/go.mod h1:j1J9XXIo/eXD7YSrr73sYZTEY/AQ0+/Q6Aa96z1e2j8=\ngithub.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=\ngithub.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.11.1 h1:eVvIXUKiTgv++6YnWb42DUA1YL7qDugnKP0HljexdnQ=\ngithub.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=\ngithub.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=\ngithub.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=\ngithub.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro=\ngithub.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=\ngithub.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=\ngithub.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=\ngithub.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=\ngithub.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=\ngithub.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=\ngithub.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=\ngithub.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=\ngithub.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=\ngithub.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=\ngithub.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=\ngithub.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=\ngithub.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=\ngithub.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=\ngithub.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=\ngithub.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=\ngithub.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs=\ngithub.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4=\ngithub.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU=\ngithub.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=\ngithub.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=\ngithub.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=\ngithub.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=\ngithub.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=\ngithub.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=\ngithub.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=\ngithub.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=\ngithub.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=\ngithub.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=\ngithub.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=\ngithub.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM=\ngithub.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=\ngithub.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=\ngithub.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=\ngithub.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=\ngithub.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=\ngithub.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE=\ngithub.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY=\ngithub.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=\ngithub.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI=\ngithub.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=\ngithub.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788=\ngithub.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=\ngithub.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=\ngithub.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE=\ngithub.com/hashicorp/vault/api v1.2.0 h1:ysGFc6XRGbv05NsWPzuO5VTv68Lj8jtwATxRLFOpP9s=\ngithub.com/hashicorp/vault/api v1.2.0/go.mod h1:dAjw0T5shMnrfH7Q/Mst+LrcTKvStZBVs1PICEDpUqY=\ngithub.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=\ngithub.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M=\ngithub.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U=\ngithub.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=\ngithub.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc=\ngithub.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=\ngithub.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=\ngithub.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=\ngithub.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=\ngithub.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=\ngithub.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=\ngithub.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=\ngithub.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=\ngithub.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=\ngithub.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=\ngithub.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=\ngithub.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=\ngithub.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=\ngithub.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=\ngithub.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=\ngo.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=\ngo.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=\ngolang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=\ngolang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=\ngolang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=\ngolang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8=\ngolang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k=\ngomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=\ngoogle.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=\ngopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=\ngotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nk8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=\nk8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw=\nk8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8=\nk8s.io/apiextensions-apiserver v0.20.1 h1:ZrXQeslal+6zKM/HjDXLzThlz/vPSxrfK3OqL8txgVQ=\nk8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk=\nk8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=\nk8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg=\nk8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=\nk8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=\nk8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=\nk8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ=\nk8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE=\nk8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=\nk8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=\nk8s.io/component-base v0.20.2 h1:LMmu5I0pLtwjpp5009KLuMGFqSc2S2isGw8t1hpYKLE=\nk8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0=\nk8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=\nk8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=\nk8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=\nk8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c=\nk8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=\nk8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20210111153108-fddb29f9d009 h1:0T5IaWHO3sJTEmCP6mUlBvMukxPKUQWqiI/YuiBNMiQ=\nk8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=\nsigs.k8s.io/controller-runtime v0.8.3 h1:GMHvzjTmaWHQB8HadW+dIvBoJuLvZObYJ5YoZruPRao=\nsigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=\nsigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=\nsigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=\n"
  },
  {
    "path": "hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2021.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "main.go",
    "content": "/*\nCopyright 2021.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\n\t//logf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tsecretsmanagerv1alpha1 \"github.com/tuenti/secrets-manager/api/v1alpha1\"\n\t\"github.com/tuenti/secrets-manager/backend\"\n\t\"github.com/tuenti/secrets-manager/controllers\"\n\t//+kubebuilder:scaffold:imports\n)\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tcorev1.AddToScheme(scheme)\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\tutilruntime.Must(secretsmanagerv1alpha1.AddToScheme(scheme))\n\t//+kubebuilder:scaffold:scheme\n}\n\n// To be filled from build ldflags\nvar version string\n\nfunc main() {\n\tvar metricsAddr string\n\tvar controllerName string\n\tvar enableLeaderElection bool\n\tvar enableDebugLog bool\n\tvar versionFlag bool\n\tvar reconcilePeriod time.Duration\n\tvar selectedBackend string\n\tvar watchNamespaces string\n\tvar excludeNamespaces string\n\tvar mgr ctrl.Manager\n\tvar namespaceList []string\n\n\tbackendCfg := backend.Config{}\n\n\tvar probeAddr string\n\tflag.StringVar(&metricsAddr, \"metrics-addr\", \":8080\", \"The address the metric endpoint binds to.\")\n\tflag.StringVar(&controllerName, \"controller-name\", \"SecretDefinition\", \"If running secrets manager in multiple namespaces, set the controller name to something unique avoid 'duplicate metrics collector registration attempted' errors.\")\n\tflag.BoolVar(&enableLeaderElection, \"enable-leader-election\", false,\n\t\t\"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.\")\n\tflag.StringVar(&selectedBackend, \"backend\", \"vault\", \"Selected backend. One of vault or azure-kv\")\n\tflag.BoolVar(&enableDebugLog, \"enable-debug-log\", false, \"Enable this to get more logs verbosity and debug messages.\")\n\tflag.BoolVar(&versionFlag, \"version\", false, \"Display Secret Manager version\")\n\tflag.DurationVar(&reconcilePeriod, \"reconcile-period\", 5*time.Second, \"How often the controller will re-queue secretdefinition events\")\n\tflag.DurationVar(&backendCfg.BackendTimeout, \"config.backend-timeout\", 5*time.Second, \"Backend connection timeout\")\n\tflag.StringVar(&backendCfg.VaultURL, \"vault.url\", \"https://127.0.0.1:8200\", \"Vault address. VAULT_ADDR environment would take precedence.\")\n\tflag.StringVar(&backendCfg.VaultAuthMethod, \"vault.auth-method\", \"approle\", \"Vault authentication method. Supported: approle, kubernetes.\")\n\tflag.StringVar(&backendCfg.VaultRoleID, \"vault.role-id\", \"\", \"Vault approle role id. VAULT_ROLE_ID environment would take precedence.\")\n\tflag.StringVar(&backendCfg.VaultSecretID, \"vault.secret-id\", \"\", \"Vault approle secret id. VAULT_SECRET_ID environment would take precedence.\")\n\tflag.StringVar(&backendCfg.VaultKubernetesRole, \"vault.kubernetes-role\", \"\", \"Vault kubernetes role name.\")\n\tflag.Int64Var(&backendCfg.VaultMaxTokenTTL, \"vault.max-token-ttl\", 300, \"Max seconds to consider a token expired.\")\n\tflag.DurationVar(&backendCfg.VaultTokenPollingPeriod, \"vault.token-polling-period\", 15*time.Second, \"Polling interval to check token expiration time.\")\n\tflag.IntVar(&backendCfg.VaultRenewTTLIncrement, \"vault.renew-ttl-increment\", 600, \"TTL time for renewed token.\")\n\tflag.StringVar(&backendCfg.VaultEngine, \"vault.engine\", \"kv2\", \"Vault secret engine. Only KV version 1 and 2 supported\")\n\tflag.StringVar(&backendCfg.VaultApprolePath, \"vault.approle-path\", \"approle\", \"Vault approle login path\")\n\tflag.StringVar(&backendCfg.VaultKubernetesPath, \"vault.kubernetes-path\", \"kubernetes\", \"Vault kubernetes login path\")\n\tflag.StringVar(&backendCfg.AzureKVName, \"azure-kv.name\", \"\", \"Azure KeyVault name. AZURE_KV_NAME environment would take precedence\")\n\tflag.StringVar(&backendCfg.AzureKVTenantID, \"azure-kv.tenant-id\", \"\", \"Azure KeyVault Tenant ID. AZURE_TENANT_ID environment would take precedence\")\n\tflag.StringVar(&backendCfg.AzureKVClientID, \"azure-kv.client-id\", \"\", \"Azure KeyVault ClientID used to authenticate. AZURE_CLIENT_ID environment would take precedence\")\n\tflag.StringVar(&backendCfg.AzureKVClientSecret, \"azure-kv.client-secret\", \"\", \"Azure KeyVault Client Secret used to authenticate. AZURE_CLIENT_SECRET environment would take precedence\")\n\tflag.StringVar(&backendCfg.AzureKVManagedClientID, \"azure-kv.managed-client-id\", \"\", \"Azure Managed Identity Client ID used to authenticate. AZURE_MANAGED_CLIENT_ID environment would take precedence\")\n\tflag.StringVar(&backendCfg.AzureKVManagedResourceID, \"azure-kv.managed-resource-id\", \"\", \"Azure Managed Identity Resource ID used to authenticate. AZURE_MANAGED_RESOURCE_ID environment would take precedence\")\n\tflag.StringVar(&watchNamespaces, \"watch-namespaces\", \"\", \"Comma separated list of namespaces that secrets-manager will watch for SecretDefinitions. By default all namespaces are watched.\")\n\tflag.StringVar(&excludeNamespaces, \"exclude-namespaces\", \"\", \"Comma separated list of namespaces that secrets-manager will not watch for SecretDefinitions. By default all namespaces are watched.\")\n\n\t//New\n\topts := zap.Options{\n\t\tDevelopment: enableDebugLog,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\tbackendLog := ctrl.Log.WithName(\"backend\")\n\n\tif versionFlag {\n\t\tfmt.Printf(\"Secrets Manager %s\\n\", version)\n\t\tos.Exit(0)\n\t}\n\n\tif os.Getenv(\"VAULT_ADDR\") != \"\" {\n\t\tbackendCfg.VaultURL = os.Getenv(\"VAULT_ADDR\")\n\t}\n\n\tif os.Getenv(\"VAULT_ROLE_ID\") != \"\" {\n\t\tbackendCfg.VaultRoleID = os.Getenv(\"VAULT_ROLE_ID\")\n\t}\n\n\tif os.Getenv(\"VAULT_SECRET_ID\") != \"\" {\n\t\tbackendCfg.VaultSecretID = os.Getenv(\"VAULT_SECRET_ID\")\n\t}\n\n\tif os.Getenv(\"AZURE_KV_NAME\") != \"\" {\n\t\tbackendCfg.AzureKVName = os.Getenv(\"AZURE_KV_NAME\")\n\t}\n\n\t// Capture Azure SP Env auth method environment variables\n\t// This will let inner code to authenticate using a mix of env and args\n\tif os.Getenv(\"AZURE_TENANT_ID\") != \"\" {\n\t\tbackendCfg.AzureKVTenantID = os.Getenv(\"AZURE_TENANT_ID\")\n\t}\n\n\tif os.Getenv(\"AZURE_CLIENT_ID\") != \"\" {\n\t\tbackendCfg.AzureKVClientID = os.Getenv(\"AZURE_CLIENT_ID\")\n\t}\n\n\tif os.Getenv(\"AZURE_CLIENT_SECRET\") != \"\" {\n\t\tbackendCfg.AzureKVClientSecret = os.Getenv(\"AZURE_CLIENT_SECRET\")\n\t}\n\n\tif os.Getenv(\"AZURE_MANAGED_CLIENT_ID\") != \"\" {\n\t\tbackendCfg.AzureKVManagedClientID = os.Getenv(\"AZURE_MANAGED_CLIENT_ID\")\n\t}\n\n\tif os.Getenv(\"AZURE_MANAGED_RESOURCE_ID\") != \"\" {\n\t\tbackendCfg.AzureKVManagedResourceID = os.Getenv(\"AZURE_MANAGED_RESOURCE_ID\")\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tbackendClient, err := backend.NewBackendClient(ctx, selectedBackend, backendLog, backendCfg)\n\tif err != nil {\n\t\tsetupLog.Error(err, \"could not build backend client\")\n\t\tos.Exit(1)\n\t}\n\n\tnsSlice := func(ns string) []string {\n\t\ttrimmed := strings.Trim(strings.TrimSpace(ns), \"\\\"\")\n\t\treturn strings.Split(trimmed, \",\")\n\t}\n\n\texcludeNs := make(map[string]bool)\n\tif len(excludeNamespaces) > 0 {\n\t\tfor _, ns := range nsSlice(excludeNamespaces) {\n\t\t\texcludeNs[ns] = true\n\t\t}\n\t}\n\n\tif len(strings.TrimSpace(watchNamespaces)) > 0 {\n\t\tsetupLog.Info(\"setting restricted namespace list for controller\")\n\t\tnamespaceList = nsSlice(watchNamespaces)\n\t\tsetupLog.Info(\"watching namespaces: \" + watchNamespaces)\n\t\tmgr, err = ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\t\tScheme:                 scheme,\n\t\t\tMetricsBindAddress:     metricsAddr,\n\t\t\tHealthProbeBindAddress: probeAddr,\n\t\t\tLeaderElection:         enableLeaderElection,\n\t\t\tLeaderElectionID:       \"5ac9a181.secrets-manager.tuenti.io\",\n\t\t\tNewCache:               cache.MultiNamespacedCacheBuilder(namespaceList),\n\t\t})\n\t\tif err != nil {\n\t\t\tsetupLog.Error(err, \"unable to start manager\")\n\t\t\tos.Exit(1)\n\t\t}\n\t} else {\n\t\tmgr, err = ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\t\tScheme:                 scheme,\n\t\t\tMetricsBindAddress:     metricsAddr,\n\t\t\tHealthProbeBindAddress: probeAddr,\n\t\t\tLeaderElection:         enableLeaderElection,\n\t\t\tLeaderElectionID:       \"5ac9a181.secrets-manager.tuenti.io\",\n\t\t})\n\t\tif err != nil {\n\t\t\tsetupLog.Error(err, \"unable to start manager\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tif err = (&controllers.SecretDefinitionReconciler{\n\t\tClient:               mgr.GetClient(),\n\t\tBackend:              *backendClient,\n\t\tAPIReader:            mgr.GetAPIReader(),\n\t\tLog:                  ctrl.Log.WithName(\"controllers\").WithName(\"SecretDefinition\"),\n\t\tReconciliationPeriod: reconcilePeriod,\n\t\tExcludeNamespaces:    excludeNs,\n\t}).SetupWithManager(mgr, controllerName); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"SecretDefinition\")\n\t\tos.Exit(1)\n\t}\n\t//+kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"unable to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"unable to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"problem running manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "scripts/setup-dev-env.sh",
    "content": "#!/bin/bash\n\n# Download tools\ngo get -v github.com/golang/mock/gomock\ngo get -v github.com/golang/mock/mockgen\ngo get -v github.com/Masterminds/glide\n\n# Install mockgen\ngo install github.com/golang/mock/mockgen\n"
  }
]