diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 00000000..35049cbc
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 319dd7db..00000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-version: 2
-
-updates:
- - package-ecosystem: "gomod"
- directory: "/"
- schedule:
- interval: daily
- - package-ecosystem: "github-actions"
- directory: "/"
- schedule:
- interval: "weekly"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
deleted file mode 100644
index b89e5b09..00000000
--- a/.github/pull_request_template.md
+++ /dev/null
@@ -1,19 +0,0 @@
-## Related Issues
-
-
-## Description
-
-
-## Motivation and Context
-
-
-
-## How Has This Been Tested?
-
-
-
-
-# Checklist
-- [ ] Tests
-- [ ] Documentation
-- [ ] Linting
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/workflows/ISSUE_TEMPLATE/bug_report.md
similarity index 86%
rename from .github/ISSUE_TEMPLATE/bug_report.md
rename to .github/workflows/ISSUE_TEMPLATE/bug_report.md
index 1b12ab09..7768a8cd 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/workflows/ISSUE_TEMPLATE/bug_report.md
@@ -1,28 +1,35 @@
---
name: Bug Report
-about: Report a bug encountered
+about: Report a bug encountered
labels: bug
---
## Expected Behavior
+
## Current Behavior
+
## Possible Solution
+
## Steps to Reproduce
+
+
1.
2.
3.
4.
## Context
+
## Specifications
- - Version: `$ teller version`
- - Platform:
\ No newline at end of file
+
+- Version: `$ teller version`
+- Platform:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/workflows/ISSUE_TEMPLATE/feature_request.md
similarity index 99%
rename from .github/ISSUE_TEMPLATE/feature_request.md
rename to .github/workflows/ISSUE_TEMPLATE/feature_request.md
index ed1f5da4..c4636146 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/workflows/ISSUE_TEMPLATE/feature_request.md
@@ -7,10 +7,13 @@ labels: "enhancement"
## Feature Request
**Is your feature request related to a problem? Please describe.**
+
**Describe the solution you'd like**
+
**Describe alternatives you've considered**
+
diff --git a/.github/workflows/ISSUE_TEMPLATE/general.md b/.github/workflows/ISSUE_TEMPLATE/general.md
new file mode 100644
index 00000000..dbc7e6dc
--- /dev/null
+++ b/.github/workflows/ISSUE_TEMPLATE/general.md
@@ -0,0 +1,5 @@
+contact_links:
+
+- name: Questions, unconfirmed bugs and ideas
+ url: https://github.com/tellerops/teller/discussions/new
+ about: Please post questions, unconfirmed bugs and ideas in discussions.
diff --git a/.github/ISSUE_TEMPLATE/new_provider.md b/.github/workflows/ISSUE_TEMPLATE/new_provider.md
similarity index 76%
rename from .github/ISSUE_TEMPLATE/new_provider.md
rename to .github/workflows/ISSUE_TEMPLATE/new_provider.md
index 8bf3a44e..d71fc469 100644
--- a/.github/ISSUE_TEMPLATE/new_provider.md
+++ b/.github/workflows/ISSUE_TEMPLATE/new_provider.md
@@ -7,8 +7,9 @@ labels: "new-provider"
## New Provider Request
## Details
-- Name:
+
+- Name:
- URL:
- Modes: (read/write/read+write)
-- GitHub projects:
-- API Documentation:
+- GitHub projects:
+- API Documentation:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..538fbd22
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,106 @@
+name: Build
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ tags-ignore:
+ - "v[0-9]+.[0-9]+.[0-9]+"
+ # you can enable a schedule to build
+ # schedule:
+ # - cron: '00 01 * * *'
+jobs:
+ test:
+ name: Test Suite
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ rust: [stable]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ override: true
+
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Run cargo test
+ uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: --all-features --all
+
+ coverage:
+ name: Coverage
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ rust: [stable]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.rust }}
+ override: true
+ components: llvm-tools-preview
+
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Download grcov
+ run: |
+ mkdir -p "${HOME}/.local/bin"
+ curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.10/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "${HOME}/.local/bin"
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+
+ - name: Run xtask coverage
+ uses: actions-rs/cargo@v1
+ with:
+ command: xtask
+ args: coverage
+
+ - name: Upload to codecov.io
+ uses: codecov/codecov-action@v3
+ with:
+ files: coverage/*.lcov
+
+ lints:
+ name: Lints
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+ components: rustfmt, clippy
+
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Run cargo fmt
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --all -- --check
+
+ - name: Run cargo clippy
+ uses: actions-rs/cargo@v1
+ with:
+ command: clippy
+ args: --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index aa5b99a3..00000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: CI
-
-on:
- push:
- branches:
- - master
- pull_request:
-
-jobs:
- build:
- name: Build
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-latest, macos-latest]
- go: ['1.18']
- env:
- VERBOSE: 1
- GOFLAGS: -mod=readonly
- GOPROXY: https://proxy.golang.org
-
- steps:
- - name: Set up Go
- uses: actions/setup-go@v3
- with:
- go-version: ${{ matrix.go }}
-
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: golangci-lint
- uses: golangci/golangci-lint-action@v3
- with:
- version: v1.45.0
- args: --timeout 5m0s
-
- - name: Test
- run: make test
-
- - name: E2E
- run: make e2e
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
deleted file mode 100644
index 71b55147..00000000
--- a/.github/workflows/release.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Release
-on:
- push:
- tags:
- - 'v*.*.*'
- - 'v*.*.*-rc*'
-jobs:
- goreleaser:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Set up Go
- uses: actions/setup-go@v3
- with:
- go-version: 1.18
-
- - name: GoReleaser
- uses: goreleaser/goreleaser-action@v2
- with:
- version: v1.7.0
- args: release --rm-dist
- env:
- GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
- GORELEASER_GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..accb493c
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,212 @@
+name: Release
+on:
+ # schedule:
+ # - cron: '0 0 * * *' # midnight UTC
+
+ push:
+ tags:
+ - "v[0-9]+.[0-9]+.[0-9]+"
+ ## - release
+
+# 1. set up:
+# secrets.COMMITTER_TOKEN - for pushing update to brew tap
+# first placeholder formula in brew tap - for updating
+
+# 2. fill these in and you're done.
+env:
+ BIN_NAME: teller
+ PROJECT_NAME: teller
+ REPO_NAME: tellerops/teller
+
+jobs:
+ test:
+ name: Test Suite
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ rust: [stable]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ override: true
+
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Run cargo test
+ uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: --all-features --all
+ dist:
+ name: Dist
+ runs-on: ${{ matrix.os }}
+ needs: test
+ strategy:
+ fail-fast: false # don't fail other jobs if one fails
+ matrix:
+ build: [x86_64-linux, aarch64-macos, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc aarch64-linux,
+ include:
+ - build: x86_64-linux
+ os: ubuntu-20.04
+ rust: stable
+ target: x86_64-unknown-linux-gnu
+ cross: false
+
+ - build: x86_64-macos
+ os: macos-latest
+ rust: stable
+ target: x86_64-apple-darwin
+
+ - build: aarch64-macos
+ os: macos-latest
+ rust: stable
+ target: aarch64-apple-darwin
+
+ - build: x86_64-windows
+ os: windows-2019
+ rust: stable
+ target: x86_64-pc-windows-msvc
+
+ # - build: aarch64-macos
+ # os: macos-latest
+ # rust: stable
+ # target: aarch64-apple-darwin
+ # - build: x86_64-win-gnu
+ # os: windows-2019
+ # rust: stable-x86_64-gnu
+ # target: x86_64-pc-windows-gnu
+ # - build: win32-msvc
+ # os: windows-2019
+ # rust: stable
+ # target: i686-pc-windows-msvc
+
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+ with:
+ submodules: true
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+
+ - name: Install ${{ matrix.rust }} toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: Install OpenSSL
+ if: matrix.build == 'aarch64-linux'
+ run: sudo apt-get install libssl-dev pkg-config
+
+ - name: Build release binary
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: ${{ matrix.cross }}
+ command: build
+ args: --release --locked --target ${{ matrix.target }}
+
+ - name: Strip release binary (linux and macos)
+ if: matrix.build == 'x86_64-linux' || matrix.build == 'x86_64-macos'
+ run: strip "target/${{ matrix.target }}/release/$BIN_NAME"
+
+ - name: Strip release binary (arm)
+ if: matrix.build == 'aarch64-linux'
+ run: |
+ docker run --rm -v \
+ "$PWD/target:/target:Z" \
+ rustembedded/cross:${{ matrix.target }} \
+ aarch64-linux-gnu-strip \
+ /target/${{ matrix.target }}/release/$BIN_NAME
+
+ - name: Build archive
+ shell: bash
+ run: |
+ rm -rf dist
+ mkdir dist
+ if [ "${{ matrix.os }}" = "windows-2019" ]; then
+ cp "target/${{ matrix.target }}/release/$BIN_NAME.exe" "dist/"
+ else
+ cp "target/${{ matrix.target }}/release/$BIN_NAME" "dist/"
+ fi
+
+ - uses: actions/upload-artifact@v2.2.4
+ with:
+ name: bins-${{ matrix.build }}
+ path: dist
+
+ publish:
+ name: Publish
+ needs: [dist]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+ with:
+ submodules: false
+
+ - uses: actions/download-artifact@v2
+ # with:
+ # path: dist
+ # - run: ls -al ./dist
+ - run: ls -al bins-*
+
+ - name: Calculate tag name
+ run: |
+ name=dev
+ if [[ $GITHUB_REF == refs/tags/v* ]]; then
+ name=${GITHUB_REF:10}
+ fi
+ echo ::set-output name=val::$name
+ echo TAG=$name >> $GITHUB_ENV
+ id: tagname
+
+ - name: Build archive
+ shell: bash
+ run: |
+ set -ex
+
+ rm -rf tmp
+ mkdir tmp
+ rm -rf dist
+ mkdir dist
+
+ for dir in bins-* ; do
+ platform=${dir#"bins-"}
+ if [[ $platform =~ "windows" ]]; then
+ exe=".exe"
+ fi
+ pkgname=$PROJECT_NAME-$platform
+ mkdir tmp/$pkgname
+ # cp LICENSE README.md tmp/$pkgname
+ mv bins-$platform/$BIN_NAME$exe tmp/$pkgname
+ chmod +x tmp/$pkgname/$BIN_NAME$exe
+
+ if [ "$exe" = "" ]; then
+ tar cJf dist/$pkgname.tar.xz -C tmp $pkgname
+ else
+ (cd tmp && 7z a -r ../dist/$pkgname.zip $pkgname)
+ fi
+ done
+
+ - name: Upload binaries to release
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: dist/*
+ file_glob: true
+ tag: ${{ steps.tagname.outputs.val }}
+ overwrite: true
+
+ # homebrew: see rustwrap.yaml
diff --git a/.github/workflows/spectral.yaml b/.github/workflows/spectral.yaml
deleted file mode 100644
index 59b6c3f9..00000000
--- a/.github/workflows/spectral.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-name: Spectral
-on: [push]
-env:
- SPECTRAL_DSN: ${{ secrets.SPECTRAL_DSN }}
-jobs:
- scan:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Install Spectral
- uses: spectralops/spectral-github-action@v1
- with:
- spectral-dsn: ${{ secrets.SPECTRAL_DSN }}
- - name: Spectral Scan
- run: spectral scan
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 4f61f898..1ce36153 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,8 @@
-autobrew
-dist/
-teller
-.vscode/
-node_modules/
+git-hidden-file
+git-ignored-file
todo.txt
-.teller.writecase.yml
-coverage.out
-fixtures/sync/target.env
+/target
+notes/
+tmp/
dist/
-.idea
\ No newline at end of file
+.DS_Store
diff --git a/.golangci.yml b/.golangci.yml
deleted file mode 100644
index 6bcf4abc..00000000
--- a/.golangci.yml
+++ /dev/null
@@ -1,124 +0,0 @@
-run:
- go: 1.18
-
-linters-settings:
- dupl:
- threshold: 100
- funlen:
- lines: 100
- statements: 50
- goconst:
- min-len: 2
- min-occurrences: 2
- gocritic:
- enabled-tags:
- - diagnostic
- - experimental
- - opinionated
- - performance
- - style
- disabled-checks:
- - dupImport # https://github.com/go-critic/go-critic/issues/845
- - ifElseChain
- - octalLiteral
- - whyNoLint
- - wrapperFunc
- - hugeParam
- gocyclo:
- min-complexity: 20
- golint:
- min-confidence: 0
- gomnd:
- settings:
- mnd:
- # don't include the "operation" and "assign"
- checks: argument,case,condition,return
- govet:
- check-shadowing: true
- lll:
- line-length: 140
- maligned:
- suggest-new: true
- misspell:
- locale: US
- nolintlint:
- allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
- allow-unused: false # report any unused nolint directives
- require-explanation: false # don't require an explanation for nolint directives
- require-specific: false # don't require nolint directives to be specific about which linter is being skipped
-
-linters:
- # please, do not use `enable-all`: it's deprecated and will be removed soon.
- # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
- disable-all: true
- enable:
- - bodyclose
- - deadcode
- - dogsled
- - dupl
- - errcheck
- - exportloopref
- - exhaustive
- - funlen
- - gochecknoinits
- - goconst
- - gocritic
- - gocyclo
- - gofmt
- - goimports
- - golint
- - gomnd
- - goprintffuncname
- - gosec
- - gosimple
- - govet
- - ineffassign
- - misspell
- - nakedret
- - noctx
- - nolintlint
- - rowserrcheck
- - staticcheck
- - structcheck
- - stylecheck
- - typecheck
- - unconvert
- - unparam
- - unused
- - varcheck
-
- # don't enable:
- # - asciicheck
- # - scopelint
- # - gochecknoglobals
- # - gocognit
- # - godot
- # - godox
- # - goerr113
- # - interfacer
- # - maligned
- # - nestif
- # - prealloc
- # - testpackage
- # - revive
- # - wsl
-
-issues:
- # Excluding configuration per-path, per-linter, per-text and per-source
- exclude:
- - 'bad syntax for struct tag key'
- - 'bad syntax for struct tag pair'
-
- exclude-rules:
- - path: _test\.go
- linters:
- - gomnd
- - goconst
- - gocritic
- - gosec
-
- # https://github.com/go-critic/go-critic/issues/926
- - linters:
- - gocritic
- text: "unnecessaryDefer:"
-
diff --git a/.goreleaser.yml b/.goreleaser.yml
deleted file mode 100644
index f457beb9..00000000
--- a/.goreleaser.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-# This is an example .goreleaser.yml file with some sane defaults.
-# Make sure to check the documentation at http://goreleaser.com
-builds:
- - env:
- - CGO_ENABLED=0
- goos:
- - linux
- - windows
- - darwin
-
- ldflags:
- - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
-
-archives:
- - replacements:
- darwin: Darwin
- linux: Linux
- windows: Windows
- 386: i386
- amd64: x86_64
-
-brews:
- -
- name: teller
- tap:
- owner: spectralops
- name: homebrew-tap
- token: "{{ .Env.GORELEASER_GITHUB_TOKEN }}"
- description: A secret manager for developers - never leave your terminal for secrets
- homepage: https://github.com/spectralops/teller
- license: "Apache 2.0"
- install: |
- bin.install "teller"
- ln_s bin/"teller", bin/"tlr"
-
-checksum:
- name_template: 'checksums.txt'
-
-snapshot:
- name_template: "{{ .Tag }}-next"
-
-changelog:
- sort: asc
- filters:
- exclude:
- - '^docs:'
- - '^test:'
diff --git a/.spectral/spectral.yaml b/.spectral/spectral.yaml
deleted file mode 100644
index ab9ae2a3..00000000
--- a/.spectral/spectral.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-# Spectral
-#
-# Here's a result run of the initial configuration, placed in '.spectral/'
-#
-# $ spectral
-#
-# [sample] ------------- summary -------------
-# [sample] AWS0001 - No AWS secrets in python files.
-# sp
-# - /../spectral/src/sp.py (AKIA223)
-# [sample] total 8 byte(s), 1 file(s), 1 match(es) in 14ms
-#
-#
-
-match_ignores:
- ignores:
- - rule_id: SENF033
- path: pkg/providers/mock_providers/keypass.kdbx
- - rule_id: SENF073
- path: fixtures/sync/source.env
- - rule_id: SENF073
- path: fixtures/mirror-drift/source.env
- - rule_id: SENF073
- path: fixtures/sync/target2.env
- - rule_id: SENF073
- path: fixtures/mirror-drift/target.env
diff --git a/.teller.example.yml b/.teller.example.yml
deleted file mode 100644
index cc1752b1..00000000
--- a/.teller.example.yml
+++ /dev/null
@@ -1,141 +0,0 @@
-project: billing_development
-
-# carry owner process environment into child's env:
-# carry_env: true
-
-opts:
- region: env:AWS_REGION
- stage: development
-
-confirm: Are you sure you want to run for {{stage}}?
-
-providers:
- dotenv:
- env_sync:
- path: ~/my-dot-env.env
- env:
- FOO_BAR:
- path: ~/my-dot-env.env
- redact_with: "**FOOBAR**" # optional
-
- # # requires an API key in: HEROKU_API_KEY (you can fetch from ~/.netrc)
- # heroku:
- # env_sync:
- # path: drakula-app
- # # env:
- # # JVM_OPTS:
- # # path: drakula-app
-
- # hashicorp_vault:
- # # configures client from environment:
- # # https://github.com/hashicorp/vault/blob/api/v1.0.4/api/client.go#L28
- # env_sync: # this grabs all the mapping from the keystore itself, JSON format.
- # path: secret/data/{{stage}}/billing/web/env
- # env: # yaml/json spec: map[string]KeyPath
- # SMTP_PASS:
- # path: secret/data/{{stage}}/wordpress
- # field: smtp
-
- # aws_secretsmanager:
- # # configures client from environment:
- # # https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/#SecretsManager.GetSecretValue
- # env_sync:
- # path: {{stage}}/billing/web/env
- # env:
- # MG_KEY:
- # path: {{stage}}/billing/mg_key
-
- # aws_ssm:
- # # configures client from environment:
- # # https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/#SecretsManager.GetSecretValue
- # env:
- # FOO_BAR:
- # path: /{{stage}}/billing
- # decrypt: true
-
- # google_secretmanager:
- # # configures client from environment:
- # # GOOGLE_APPLICATION_CREDENTIALS=client-credentials.json
- # # https://cloud.google.com/secret-manager/docs/reference/libraries#setting_up_authentication
- # env:
- # MG_KEY:
- # # need to supply the relevant version (versions/1)
- # path: projects/xx84744xxxxx/secrets/MG_KEY/versions/1
-
- # etcd:
- # # configures client from environment:
- # # ETCDCTL_ENDPOINTS
- # # tls:
- # # ETCDCTL_CA_FILE
- # # ETCDCTL_CERT_FILE
- # # ETCDCTL_KEY_FILE
- # env_sync:
- # # when full sync, takes last segment as the var name
- # path: /{{stage}}/billing
- # env:
- # MG_KEY:
- # path: /{{stage}}/billing/mg
-
- # consul:
- # # configures client from environment:
- # # CONSUL_HTTP_ADDR
- # env_sync:
- # path: redis/config
- # env:
- # SAVE_TIME:
- # # need to supply the relevant version (versions/1)
- # path: redis/config/savetime
-
- # cyberark_conjur:
- # # configures client from environment:
- # # CONJUR_AUTHN_LOGIN
- # # CONJUR_AUTHN_API_KEY
- # # also, configures client from file:
- # # FILENAME: ~/.conjurrc
- # # appliance_url: https://conjur.cyberark.com
- # # account: cyberarkdemo
- # # cert_file: /root/conjur-cyberarkdemo.pem
- # env:
- # DB_USERNAME:
- # path: secrets/database/username
- # DB_PASSWORD:
- # path: secrets/database/passwords
-
- # 1password:
- # env_sync:
- # path: # Key title
- # source: # 1Password token gen include access to multiple vault. to get the secrets you must add and vaultUUID. the field is mandatory
- # env:
- # FOO_BAR:
- # path: # Key title
- # source: # 1Password token gen include access to multiple vault. to get the secrets you must add and vaultUUID. the field is mandatory
- # field: # The secret field to get. notesPlain, {label key}, password etc.
-
- # lastpass:
- # # Configure via environment variables:
- # # LASTPASS_USERNAME
- # # LASTPASS_PASSWORD
- # env_sync:
- # path: # LastPass item ID
- # env:
- # FOO_BAR:
- # path: # LastPass item ID
- # #field: by default taking password property. in case you want other property un-mark this line and set the LastPass property name
-
-
-
-# # Configure via environment variables for integration:
-# # CLOUDFLARE_API_KEY: Your Cloudflare api key.
-# # CLOUDFLARE_API_EMAIL: Your email associated with the api key.
-# # CLOUDFLARE_ACCOUNT_ID: Your account ID.
-#
-# cloudflare_workers_secrets:
-# env_sync:
-# source: script-id
-# env:
-# foo-secret:
-# path: foo-secret
-# source: script-id
-# foo-secret2:
-# path: foo-secret
-# source: script-id
\ No newline at end of file
diff --git a/.teller.yml b/.teller.yml
deleted file mode 100644
index ea802f73..00000000
--- a/.teller.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-project: teller
-
-# Set this if you want to carry over parent process' environment variables
-carry_env: true
-
-
-#
-# Variables
-#
-# Feel free to add options here to be used as a variable throughout
-# paths.
-#
-opts:
- region: env:AWS_REGION # you can get env variables with the 'env:' prefix, for default values if env not found use comma. Example: env:AWS_REGION,{DEFAULT_VALUE}
- stage: development
-
-
-#
-# Providers
-#
-providers:
-
-# dotenv:
-## env_sync:
-## path: ~/teller-env.env
-# env:
-# ANSIBLE_VAULT_PASSPHRASE:
-# path: ~/teller-env.env
-
- # Configure via environment variables for integration:
- # ANSIBLE_VAULT_PASSPHRASE: Ansible Vault Password
-
- ansible_vault:
- env_sync:
- path: ansible/vars/vault_{{stage}}.yml
-
- env:
- KEY1:
- path: ansible/vars/vault_{{stage}}.yml
- NONEXIST_KEY:
- path: ansible/vars/vault_{{stage}}.yml
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 5774a392..d04234b1 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,3 +1,3 @@
# Community Code of Conduct
-Teller follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
\ No newline at end of file
+Teller follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 60b1a948..a03f237d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Contributing to the teller
-Teller is open source and we love to receive contributions from our community — you!
+Teller is open source and we love to receive contributions from our community — you!
There are many ways to contribute,
from writing tutorials or blog posts,
@@ -23,7 +23,6 @@ or that there are particular issues that you should know about before implementi
Generally, we require that you test any code you are adding or modifying.
Once your changes are ready , submit for review and we will make sure will review it , your effort is much appreciated!
-
### Workflow
All feature development and most bug fixes hit the master branch first.
@@ -31,4 +30,3 @@ Pull requests should be reviewed by someone with commit access.
Once approved, the author of the pull request,
or reviewer if the author does not have commit access,
should "Squash and merge".
-
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 00000000..d15d2a7c
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,4214 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
+
+[[package]]
+name = "async-trait"
+version = "0.1.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
+
+[[package]]
+name = "aws-config"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2a89e0000cde82447155d64eeb71720b933b4396a6fbbebad3f8b4f88ca7b54"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-sdk-sso",
+ "aws-sdk-ssooidc",
+ "aws-sdk-sts",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "fastrand",
+ "hex",
+ "http 0.2.12",
+ "hyper",
+ "ring",
+ "time",
+ "tokio",
+ "tracing",
+ "url 2.5.0",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-credential-types"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9"
+dependencies = [
+ "aws-smithy-async",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-runtime"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4963ac9ff2d33a4231b3806c1c69f578f221a9cabb89ad2bde62ce2b442c8a7"
+dependencies = [
+ "aws-credential-types",
+ "aws-sigv4",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "fastrand",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "percent-encoding 2.3.1",
+ "pin-project-lite",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "aws-sdk-secretsmanager"
+version = "1.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16072a660b3af14e897b80c7cc60df757d856303c09c6c24de27ff16a3a57df"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "fastrand",
+ "http 0.2.12",
+ "once_cell",
+ "regex-lite",
+ "tracing",
+]
+
+[[package]]
+name = "aws-sdk-ssm"
+version = "1.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "069188fa54b42552351e228c29db2fcc9b82045d1c7499e8bf66f307cad42853"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "fastrand",
+ "http 0.2.12",
+ "once_cell",
+ "regex-lite",
+ "tracing",
+]
+
+[[package]]
+name = "aws-sdk-sso"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32fcc572fd5c58489ec205ec3e4e5f7d63018898a485cbf922a462af496bc300"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "http 0.2.12",
+ "once_cell",
+ "regex-lite",
+ "tracing",
+]
+
+[[package]]
+name = "aws-sdk-ssooidc"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b6275fa8684a1192754221173b1f7a7c1260d6b0571cc2b8af09468eb0cffe5"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "bytes",
+ "http 0.2.12",
+ "once_cell",
+ "regex-lite",
+ "tracing",
+]
+
+[[package]]
+name = "aws-sdk-sts"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30acd58272fd567e4853c5075d838be1626b59057e0249c9be5a1a7eb13bf70f"
+dependencies = [
+ "aws-credential-types",
+ "aws-runtime",
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-json",
+ "aws-smithy-query",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-smithy-xml",
+ "aws-types",
+ "http 0.2.12",
+ "once_cell",
+ "regex-lite",
+ "tracing",
+]
+
+[[package]]
+name = "aws-sigv4"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d6f29688a4be9895c0ba8bef861ad0c0dac5c15e9618b9b7a6c233990fc263"
+dependencies = [
+ "aws-credential-types",
+ "aws-smithy-http",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "bytes",
+ "form_urlencoded",
+ "hex",
+ "hmac",
+ "http 0.2.12",
+ "http 1.1.0",
+ "once_cell",
+ "percent-encoding 2.3.1",
+ "sha2",
+ "time",
+ "tracing",
+]
+
+[[package]]
+name = "aws-smithy-async"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c"
+dependencies = [
+ "futures-util",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "aws-smithy-http"
+version = "0.60.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a7de001a1b9a25601016d8057ea16e31a45fdca3751304c8edf4ad72e706c08"
+dependencies = [
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "bytes",
+ "bytes-utils",
+ "futures-core",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "once_cell",
+ "percent-encoding 2.3.1",
+ "pin-project-lite",
+ "pin-utils",
+ "tracing",
+]
+
+[[package]]
+name = "aws-smithy-json"
+version = "0.60.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6"
+dependencies = [
+ "aws-smithy-types",
+]
+
+[[package]]
+name = "aws-smithy-query"
+version = "0.60.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb"
+dependencies = [
+ "aws-smithy-types",
+ "urlencoding",
+]
+
+[[package]]
+name = "aws-smithy-runtime"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44e7945379821074549168917e89e60630647e186a69243248f08c6d168b975a"
+dependencies = [
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "bytes",
+ "fastrand",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "http-body 1.0.0",
+ "hyper",
+ "hyper-rustls 0.24.2",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "rustls 0.21.10",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "aws-smithy-runtime-api"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cc56a5c96ec741de6c5e6bf1ce6948be969d6506dfa9c39cffc284e31e4979b"
+dependencies = [
+ "aws-smithy-async",
+ "aws-smithy-types",
+ "bytes",
+ "http 0.2.12",
+ "http 1.1.0",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-smithy-types"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abe14dceea1e70101d38fbf2a99e6a34159477c0fb95e68e05c66bd7ae4c3729"
+dependencies = [
+ "base64-simd",
+ "bytes",
+ "bytes-utils",
+ "futures-core",
+ "http 0.2.12",
+ "http 1.1.0",
+ "http-body 0.4.6",
+ "http-body 1.0.0",
+ "http-body-util",
+ "itoa",
+ "num-integer",
+ "pin-project-lite",
+ "pin-utils",
+ "ryu",
+ "serde",
+ "time",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "aws-smithy-xml"
+version = "0.60.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55"
+dependencies = [
+ "xmlparser",
+]
+
+[[package]]
+name = "aws-types"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a43b56df2c529fe44cb4d92bd64d0479883fb9608ff62daede4df5405381814"
+dependencies = [
+ "aws-credential-types",
+ "aws-smithy-async",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "http 0.2.12",
+ "rustc_version",
+ "tracing",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
+
+[[package]]
+name = "base64-simd"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195"
+dependencies = [
+ "outref",
+ "vsimd",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bollard"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82e7850583ead5f8bbef247e2a3c37a19bd576e8420cd262a6711921827e1e5"
+dependencies = [
+ "base64 0.13.1",
+ "bollard-stubs",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "http 0.2.12",
+ "hyper",
+ "hyperlocal",
+ "log",
+ "pin-project-lite",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_urlencoded",
+ "thiserror",
+ "tokio",
+ "tokio-util",
+ "url 2.5.0",
+ "winapi",
+]
+
+[[package]]
+name = "bollard-stubs"
+version = "1.42.0-rc.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864"
+dependencies = [
+ "serde",
+ "serde_with 1.14.0",
+]
+
+[[package]]
+name = "bstr"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
+name = "bytes-utils"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35"
+dependencies = [
+ "bytes",
+ "either",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "chrono-tz"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f"
+dependencies = [
+ "parse-zoneinfo",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "atty",
+ "bitflags 1.3.2",
+ "clap_lex 0.2.4",
+ "indexmap 1.9.3",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex 0.7.0",
+ "strsim 0.11.1",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "comfy-table"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
+dependencies = [
+ "crossterm",
+ "strum 0.26.2",
+ "strum_macros 0.26.2",
+ "unicode-width",
+]
+
+[[package]]
+name = "console"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "consulrs"
+version = "0.1.0"
+source = "git+https://github.com/jmgilman/consulrs?rev=a14fddbdf3695e2e338145134c4b42f823e03370#a14fddbdf3695e2e338145134c4b42f823e03370"
+dependencies = [
+ "async-trait",
+ "base64 0.13.1",
+ "consulrs_derive",
+ "derive_builder 0.10.2",
+ "http 0.2.12",
+ "reqwest",
+ "rustify",
+ "rustify_derive",
+ "serde",
+ "serde_json",
+ "serde_with 1.14.0",
+ "thiserror",
+ "tracing",
+ "url 2.5.0",
+]
+
+[[package]]
+name = "consulrs_derive"
+version = "0.1.0"
+source = "git+https://github.com/jmgilman/consulrs?rev=a14fddbdf3695e2e338145134c4b42f823e03370#a14fddbdf3695e2e338145134c4b42f823e03370"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "synstructure",
+]
+
+[[package]]
+name = "content_inspector"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32c"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crossterm"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+dependencies = [
+ "bitflags 2.5.0",
+ "crossterm_winapi",
+ "libc",
+ "parking_lot",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "csv"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "darling"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
+dependencies = [
+ "darling_core 0.12.4",
+ "darling_macro 0.12.4",
+]
+
+[[package]]
+name = "darling"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+dependencies = [
+ "darling_core 0.13.4",
+ "darling_macro 0.13.4",
+]
+
+[[package]]
+name = "darling"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
+dependencies = [
+ "darling_core 0.14.4",
+ "darling_macro 0.14.4",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
+dependencies = [
+ "darling_core 0.20.8",
+ "darling_macro 0.20.8",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
+dependencies = [
+ "darling_core 0.12.4",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+dependencies = [
+ "darling_core 0.13.4",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
+dependencies = [
+ "darling_core 0.14.4",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
+dependencies = [
+ "darling_core 0.20.8",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
+dependencies = [
+ "derive_builder_macro 0.10.2",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3"
+dependencies = [
+ "derive_builder_macro 0.11.2",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
+dependencies = [
+ "derive_builder_macro 0.12.0",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
+dependencies = [
+ "darling 0.12.4",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
+dependencies = [
+ "darling 0.14.4",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
+dependencies = [
+ "darling 0.14.4",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
+dependencies = [
+ "derive_builder_core 0.10.2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68"
+dependencies = [
+ "derive_builder_core 0.11.2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
+dependencies = [
+ "derive_builder_core 0.12.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "deunicode"
+version = "1.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e"
+
+[[package]]
+name = "dialoguer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87"
+dependencies = [
+ "console",
+ "shell-words",
+ "tempfile",
+ "zeroize",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
+dependencies = [
+ "console",
+ "shell-words",
+ "tempfile",
+ "thiserror",
+ "zeroize",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dockertest"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce5e89cd7c59faf3cf0e31369fce2382807dd794d4fcce6380adcefdf5987796"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "base64 0.13.1",
+ "bollard",
+ "dyn-clone",
+ "futures",
+ "lazy_static",
+ "rand",
+ "secrecy",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "dockertest-server"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b74f2753acbecdf22c035603b1f40eed1a1106ab994c57d6e739ad88ea58dd01"
+dependencies = [
+ "derive_builder 0.11.2",
+ "dockertest",
+ "futures",
+ "rand",
+ "tempfile",
+ "type-map",
+]
+
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "duct"
+version = "0.13.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c"
+dependencies = [
+ "libc",
+ "once_cell",
+ "os_pipe",
+ "shared_child",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
+
+[[package]]
+name = "either"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "exitcode"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
+
+[[package]]
+name = "eyre"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding 2.3.1",
+]
+
+[[package]]
+name = "fs-err"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata 0.4.6",
+ "regex-syntax 0.8.3",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "google-apis-common"
+version = "6.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78672323eaeeb181cadd4d07333f1bcc8c2840a55b58afa8ab052664cd4a54ad"
+dependencies = [
+ "base64 0.13.1",
+ "chrono",
+ "http 0.2.12",
+ "hyper",
+ "itertools 0.10.5",
+ "mime",
+ "serde",
+ "serde_json",
+ "serde_with 2.3.3",
+ "tokio",
+ "tower-service",
+ "url 1.7.2",
+ "yup-oauth2",
+]
+
+[[package]]
+name = "google-secretmanager1"
+version = "5.0.4+20240223"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5b806517ba1ad137eb05b445ae04343a7b6469e7167084ec4e3ae4e4ba4002f"
+dependencies = [
+ "anyhow",
+ "google-apis-common",
+ "http 0.2.12",
+ "hyper",
+ "hyper-rustls 0.24.2",
+ "itertools 0.10.5",
+ "mime",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tower-service",
+ "url 1.7.2",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.12",
+ "indexmap 2.2.6",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "http"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.12",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+dependencies = [
+ "bytes",
+ "http 1.1.0",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humansize"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "humantime-serde"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
+dependencies = [
+ "humantime",
+ "serde",
+]
+
+[[package]]
+name = "hyper"
+version = "0.14.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
+dependencies = [
+ "futures-util",
+ "http 0.2.12",
+ "hyper",
+ "log",
+ "rustls 0.21.10",
+ "rustls-native-certs 0.6.3",
+ "tokio",
+ "tokio-rustls 0.24.1",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070"
+dependencies = [
+ "futures-util",
+ "http 0.2.12",
+ "hyper",
+ "log",
+ "rustls 0.22.3",
+ "rustls-native-certs 0.7.0",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls 0.25.0",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
+[[package]]
+name = "hyperlocal"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"
+dependencies = [
+ "futures-util",
+ "hex",
+ "hyper",
+ "pin-project",
+ "tokio",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata 0.4.6",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+]
+
+[[package]]
+name = "insta"
+version = "1.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc"
+dependencies = [
+ "console",
+ "lazy_static",
+ "linked-hash-map",
+ "pest",
+ "pest_derive",
+ "regex",
+ "serde",
+ "similar",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "openssl"
+version = "0.10.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
+dependencies = [
+ "bitflags 2.5.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "os_pipe"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+
+[[package]]
+name = "outref"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pest"
+version = "2.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.6",
+ "regex-syntax 0.8.3",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.3",
+]
+
+[[package]]
+name = "regex-lite"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "reqwest"
+version = "0.11.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
+dependencies = [
+ "base64 0.21.7",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper",
+ "hyper-rustls 0.24.2",
+ "hyper-tls",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding 2.3.1",
+ "pin-project-lite",
+ "rustls 0.21.10",
+ "rustls-pemfile 1.0.4",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "system-configuration",
+ "tokio",
+ "tokio-native-tls",
+ "tokio-rustls 0.24.1",
+ "tower-service",
+ "url 2.5.0",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
+ "winreg",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustify"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "bytes",
+ "http 0.2.12",
+ "reqwest",
+ "rustify_derive",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "thiserror",
+ "tracing",
+ "url 2.5.0",
+]
+
+[[package]]
+name = "rustify_derive"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58135536c18c04f4634bedad182a3f41baf33ef811cc38a3ec7b7061c57134c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "regex",
+ "serde_urlencoded",
+ "syn 1.0.109",
+ "synstructure",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
+dependencies = [
+ "bitflags 2.5.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki 0.101.7",
+ "sct",
+]
+
+[[package]]
+name = "rustls"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki 0.102.2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile 1.0.4",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile 2.1.2",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64 0.21.7",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
+dependencies = [
+ "base64 0.22.0",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
+
+[[package]]
+name = "ryu"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
+[[package]]
+name = "secrecy"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+
+[[package]]
+name = "serde"
+version = "1.0.198"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.198"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.116"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_variant"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
+dependencies = [
+ "serde",
+ "serde_with_macros 1.5.2",
+]
+
+[[package]]
+name = "serde_with"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
+dependencies = [
+ "base64 0.13.1",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "serde",
+ "serde_json",
+ "serde_with_macros 2.3.3",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
+dependencies = [
+ "darling 0.13.4",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
+dependencies = [
+ "darling 0.20.8",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap 2.2.6",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shared_child"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "similar"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "slug"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "snapbox"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b831b6e80fbcd2889efa75b185d24005f85981431495f995292b25836519d84"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "content_inspector",
+ "dunce",
+ "filetime",
+ "libc",
+ "normalize-line-endings",
+ "os_pipe",
+ "similar",
+ "snapbox-macros",
+ "tempfile",
+ "wait-timeout",
+ "walkdir",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1c4b838b05d15ab22754068cb73500b2f3b07bf09d310e15b27f88160f1de40"
+dependencies = [
+ "anstream",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "stringreader"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
+dependencies = [
+ "strum_macros 0.25.3",
+]
+
+[[package]]
+name = "strum"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
+
+[[package]]
+name = "strum_macros"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "unicode-xid",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "teller-cli"
+version = "0.1.0"
+dependencies = [
+ "clap 4.5.4",
+ "comfy-table",
+ "console",
+ "dialoguer 0.11.0",
+ "dockertest-server",
+ "exitcode",
+ "eyre",
+ "fs-err",
+ "insta",
+ "proc-macro2",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_yaml",
+ "strum 0.25.0",
+ "teller-core",
+ "teller-providers",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+ "tracing-tree",
+ "trycmd",
+]
+
+[[package]]
+name = "teller-core"
+version = "0.1.0"
+dependencies = [
+ "aho-corasick",
+ "csv",
+ "duct",
+ "fs-err",
+ "ignore",
+ "insta",
+ "lazy_static",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_variant",
+ "serde_yaml",
+ "shell-words",
+ "stringreader",
+ "strum 0.25.0",
+ "teller-providers",
+ "tera",
+ "thiserror",
+ "unicode-width",
+]
+
+[[package]]
+name = "teller-providers"
+version = "0.1.0"
+dependencies = [
+ "async-trait",
+ "aws-config",
+ "aws-sdk-secretsmanager",
+ "aws-sdk-ssm",
+ "consulrs",
+ "crc32c",
+ "dockertest-server",
+ "dotenvy",
+ "fs-err",
+ "google-secretmanager1",
+ "home",
+ "insta",
+ "lazy_static",
+ "rustify",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_variant",
+ "serde_yaml",
+ "strum 0.25.0",
+ "thiserror",
+ "tokio",
+ "vaultrs",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tera"
+version = "1.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding 2.3.1",
+ "pest",
+ "pest_derive",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unic-segment",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
+
+[[package]]
+name = "thiserror"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+dependencies = [
+ "rustls 0.21.10",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
+dependencies = [
+ "rustls 0.22.3",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
+dependencies = [
+ "indexmap 2.2.6",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log 0.2.0",
+]
+
+[[package]]
+name = "tracing-tree"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d"
+dependencies = [
+ "nu-ansi-term",
+ "tracing-core",
+ "tracing-log 0.1.4",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "trycmd"
+version = "0.14.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d41014f614932fff67cd3b780e0eb0ecb14e698a831a0e555ef2a5137be968d5"
+dependencies = [
+ "glob",
+ "humantime",
+ "humantime-serde",
+ "rayon",
+ "serde",
+ "shlex",
+ "snapbox",
+ "toml_edit",
+]
+
+[[package]]
+name = "type-map"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f"
+dependencies = [
+ "rustc-hash",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
+dependencies = [
+ "idna 0.1.5",
+ "matches",
+ "percent-encoding 1.0.1",
+]
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna 0.5.0",
+ "percent-encoding 2.3.1",
+]
+
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "uuid"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "vaultrs"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb996bb053adadc767f8b0bda2a80bc2b67d24fe89f2b959ae919e200d79a19"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "derive_builder 0.12.0",
+ "http 0.2.12",
+ "reqwest",
+ "rustify",
+ "rustify_derive",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tracing",
+ "url 2.5.0",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "vsimd"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.59",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "web-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.25.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
+[[package]]
+name = "winnow"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winreg"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "xmlparser"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
+
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "xtaskops",
+]
+
+[[package]]
+name = "xtaskops"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464ca5c2bac1d1fcdbef9da6c09c6a14f2c7ac6c8845f574ab12065a2b72bb8b"
+dependencies = [
+ "anyhow",
+ "clap 3.2.25",
+ "derive_builder 0.12.0",
+ "dialoguer 0.10.4",
+ "duct",
+ "fs_extra",
+ "glob",
+]
+
+[[package]]
+name = "yup-oauth2"
+version = "8.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45b7ff561fdc7809a2adad8bce73e157d01129074098e6405d0d7dfa2d087782"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "base64 0.21.7",
+ "futures",
+ "http 0.2.12",
+ "hyper",
+ "hyper-rustls 0.25.0",
+ "itertools 0.12.1",
+ "log",
+ "percent-encoding 2.3.1",
+ "rustls 0.22.3",
+ "rustls-pemfile 1.0.4",
+ "seahash",
+ "serde",
+ "serde_json",
+ "time",
+ "tokio",
+ "tower-service",
+ "url 2.5.0",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 00000000..c5309fcb
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,25 @@
+
+[workspace]
+resolver = "2"
+members = ["teller-cli", "teller-core", "teller-providers", "xtask"]
+default-members = ["teller-cli"]
+
+
+[workspace.dependencies]
+serde = "1"
+serde_json = "1"
+serde_yaml = "0.9"
+serde_derive = "1"
+serde_variant = "0.1.2"
+async-trait = "0.1.80"
+lazy_static = "1.4.0"
+strum = { version = "0.25", features = ["derive"] }
+shell-words = "1"
+duct = "0.13.6"
+thiserror = "1.0.49"
+aho-corasick = "1"
+tokio = { version = "1", features = ["full"] }
+tera = { version = "1" }
+
+# dev deps
+insta = { version = "1.29.0", features = ["redactions", "yaml", "filters"] }
diff --git a/LICENSE.txt b/LICENSE.txt
index 6b84b80b..e1a96dba 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
- limitations under the License.
+ limitations under the License.
\ No newline at end of file
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index 123a41d3..86c13bdb 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -2,6 +2,5 @@
The current maintainers of the Teller project are:
-* Dotan Nahum [jondot](https://github.com/jondot), , core maintainer
-* Elad Kaplan [kaplanelad](https://github.com/kaplanelad), , core maintainer
-* Lior Reuven [lreuven](https://github.com/lreuven), , project management
+- Dotan Nahum [jondot](https://github.com/jondot), , core maintainer
+- Elad Kaplan [kaplanelad](https://github.com/kaplanelad), , core maintainer
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 09522042..00000000
--- a/Makefile
+++ /dev/null
@@ -1,51 +0,0 @@
-setup-mac:
- brew install golangci-lint
- go install github.com/golang/mock/mockgen@v1.5.0
-mocks:
- mockgen -source pkg/providers/aws_secretsmanager.go -destination pkg/providers/mock_providers/aws_secretsmanager_mock.go
- mockgen -source pkg/providers/aws_ssm.go -destination pkg/providers/mock_providers/aws_ssm_mock.go
- mockgen -source pkg/providers/cloudflare_workers_kv.go -destination pkg/providers/mock_providers/cloudflare_workers_kv_mock.go
- mockgen -source pkg/providers/cloudflare_workers_secrets.go -destination pkg/providers/mock_providers/cloudflare_workers_secrets_mock.go
- mockgen -source pkg/providers/consul.go -destination pkg/providers/mock_providers/consul_mock.go
- mockgen -source pkg/providers/dotenv.go -destination pkg/providers/mock_providers/dotenv_mock.go
- mockgen -source pkg/providers/doppler.go -destination pkg/providers/mock_providers/doppler_mock.go
- mockgen -source pkg/providers/etcd.go -destination pkg/providers/mock_providers/etcd_mock.go
- mockgen -source pkg/providers/google_secretmanager.go -destination pkg/providers/mock_providers/google_secretmanager_mock.go
- mockgen -source pkg/providers/hashicorp_vault.go -destination pkg/providers/mock_providers/hashicorp_vault_mock.go
- mockgen -source pkg/providers/heroku.go -destination pkg/providers/mock_providers/heroku_mock.go
- mockgen -source pkg/providers/vercel.go -destination pkg/providers/mock_providers/vercel_mock.go
- mockgen -source pkg/providers/onepassword.go -destination pkg/providers/mock_providers/onepassword_mock.go
- mockgen -source pkg/providers/gopass.go -destination pkg/providers/mock_providers/gopass_mock.go
- mockgen -source pkg/providers/github.go -destination pkg/providers/mock_providers/github_mock.go
- mockgen -source pkg/providers/azure_keyvault.go -destination pkg/providers/mock_providers/azure_keyvault_mock.go
- mockgen -source pkg/providers/keeper_secretsmanager.go -destination pkg/providers/mock_providers/keeper_secretsmanager_mock.go
-readme:
- yarn readme
-lint:
- golangci-lint run
-test:
- go test -v ./pkg/... -cover
-
-integration:
- go test -v ./pkg/integration_test -cover -tags=integration
-
-integration_api:
- go test -v ./pkg/integration_test -cover -tags="integration_api integration"
-
-deps:
- go mod tidy && go mod vendor
-
-release:
- goreleaser --rm-dist
-
-build:
- go build -ldflags "-s -w -X main.version=0.0.0 -X main.commit=0000000000000000000000000000000000000000 -X main.date=2022-01-01"
-
-e2e: build
- BINARY_PATH="$(shell pwd)/teller" go test -v ./e2e
-
-coverage:
- go test ./pkg/... -coverprofile=coverage.out
- go tool cover -func=coverage.out
-
-.PHONY: deps setup-mac release readme lint mocks coverage
diff --git a/README.md b/README.md
index 70cf0279..c6ccfbf2 100644
--- a/README.md
+++ b/README.md
@@ -13,14 +13,11 @@
:pager: Create easy and clean workflows for working with cloud environments
:mag_right: Scan for secrets and fight secret sprawl
-
-
-
-
+
@@ -34,77 +31,69 @@ You can use Teller to tidy your own environment or for your team as a process an
![](media/providers.png)
-## Quick Start with `teller` (or `tlr`)
+## Quick Start with `teller`
-You can install `teller` with homebrew:
+**Download a binary**
+Grab a binary from [releases](https://github.com/tellerops/teller/releases)
-```
-$ brew tap spectralops/tap && brew install teller
-```
-
-You can now use `teller` or `tlr` (if you like shortcuts!) in your terminal.
-
-![](media/teller.gif)
+**Build from source**
+Using this method will allow you to eye-ball the source code, review it, and build a copy yourself.
-`teller` will pull variables from your various cloud providers, vaults and others, and will populate your current working session (in various ways!, see more below) so you can work safely and much more productively.
+This will install the binary locally on your machine:
-`teller` needs a tellerfile. This is a `.teller.yml` file that lives in your repo, or one that you point teller to with `teller -c your-conf.yml`.
-
-## Using a Github Action
+```bash
+$ cd teller-cli
+$ cargo install --path .
+```
-For those using Github Action, you can have a 1-click experience of installing Teller in your CI:
+**Create a new configuration**
-```yaml
-- name: Setup Teller
- uses: spectralops/setup-teller@v1
-- name: Run a Teller task (show, scan, run, etc.)
- run: teller run [args]
+```
+$ teller new
+? Select your secret providers ›
+⬚ hashicorp_consul
+⬚ aws_secretsmanager
+⬚ ssm
+⬚ dotenv
+⬚ hashicorp
+⬚ google_secretmanager
```
-For more, check our [setup teller action](https://github.com/marketplace/actions/setup-teller) on the marketplace.
+Then, edit the newly created `.teller.yml` to set the maps and keys that you need for your providers.
-## Create your configuration
+## A look at `teller.yml`
+The teller YAML describes your providers and within each provider a `map` that describes:
-Run `teller new` and follow the wizard, pick the providers you like and it will generate a `.teller.yml` for you.
+* What is the root path to fetch key-values from
+* For each such map, its unique `id` which will serve you for operations later
+* For each map, an optional specific key name mapping - you can rename keys that you will fetch from the source provider
-Alternatively, you can use the following minimal template or [view a full example](.teller.example.yml):
+Here's an example configuration file. Note that it also include templating constructs -- such as fetching environment variables while loading the configuration:
```yaml
-project: project_name
-opts:
- stage: development
-
-# remove if you don't like the prompt
-confirm: Are you sure you want to run in {{stage}}?
-
providers:
- # uses environment vars to configure
- # https://github.com/hashicorp/vault/blob/api/v1.0.4/api/client.go#L28
- hashicorp_vault:
- env_sync:
- path: secret/data/{{stage}}/services/billing
-
- # this will fuse vars with the below .env file
- # use if you'd like to grab secrets from outside of the project tree
- dotenv:
- env_sync:
- path: ~/billing.env.{{stage}}
-```
-
-Now you can just run processes with:
+ hashi_1:
+ kind: hashicorp
+ maps:
+ - id: test-load
+ path: /{{ get_env(name="TEST_LOAD_1", default="test") }}/users/user1
+ # if empty, map everything
+ # == means map to same key name
+ # otherwise key on left becomes right
+ # in the future: key_transform: camelize, snake_case for automapping the keys
+ keys:
+ GITHUB_TOKEN: ==
+ mg: FOO_BAR
+ dot_1:
+ kind: dotenv
+ maps:
+ - id: stg
+ path: VAR_{{ get_env(name="STAGE", default="development") }}
-```
-$ teller run node src/server.js
-Service is up.
-Loaded configuration: Mailgun, SMTP
-Port: 5050
```
-Behind the scenes: `teller` fetched the correct variables, placed those (and _just_ those) in `ENV` for the `node` process to use.
+You can now address these providers as `hashi_1` or `dot_1`. Teller pulls the specified data from all providers by default.
-# Best practices
-
-Go and have a look at a collection of our [best practices](./best-practices.md)
# Features
@@ -117,7 +106,7 @@ Got bitten by using `.env.production` and exposing it in the local project itsel
Using `teller` and a `.teller.yml` file that exposes nothing to the prying eyes, you can work fluently and seamlessly with zero risk, also no need for quotes:
```
-$ teller run -- your-process arg1 arg2... --switch1 ...
+$ teller run --reset --shell -- node index.js
```
## :mag_right: Inspecting variables
@@ -158,40 +147,24 @@ It can also integrate into your CI and serve as a shift-left security tool for y
Look for your vault-kept secrets in your code by running:
-```
+```bash
$ teller scan
```
You can run it as a linter in your CI like so:
-```
-run: teller scan --silent
+```yaml
+run: teller scan --error-if-found
```
It will break your build if it finds something (returns exit code `1`).
-Use Teller for productively and securely running your processes and you get this for free -- nothing to configure. If you have data that you're bringing that you're sure isn't sensitive, flag it in your `teller.yml`:
-
-```
-dotenv:
- env:
- FOO:
- path: ~/my-dot-env.env
- severity: none # will skip scanning. possible values: high | medium | low | none
-```
-
-By default we treat all entries as sensitive, with value `high`.
+You can also export results as JSON with `--json` and scan binary files with `-b`.
## :recycle: Redact secrets from process outputs, logs, and files
You can use `teller` as a redaction tool across your infrastructure, and run processes while redacting their output as well as clean up logs and live tails of logs.
-Run a process and redact its output in real time:
-
-```
-$ teller run --redact -- your-process arg1 arg2
-```
-
Pipe any process output, tail or logs into teller to redact those, live:
```
@@ -206,102 +179,28 @@ $ tail -f /var/log/apache.log | teller redact
Finally, if you've got some files you want to redact, you can do that too:
-```
+```bash
$ teller redact --in dirty.csv --out clean.csv
```
If you omit `--in` Teller will take `stdin`, and if you omit `--out` Teller will output to `stdout`.
-## :beetle: Detect secrets and value drift
-You can detect _secret drift_ by comparing values from different providers against each other. It might be that you want to pin a set of keys in different providers to always be the same value; when they aren't -- that means you have a drift.
-
-In most cases, keys in providers would be similar which we call _mirrored_ providers. Example:
-
-```
-Provider1:
- MG_PASS=foo***
-
-Provider2:
- MG_PASS=foo*** # Both keys are called MG_PASS
-```
+## :scroll: Populate templates
-To detected mirror drifts, we use `teller mirror-drift`.
+You can populate custom templates:
```bash
-$ teller mirror-drift --source global-dotenv --target my-dotenv
-
-Drifts detected: 2
-
-changed [] global-dotenv FOO_BAR "n***** != my-dotenv FOO_BAR ne*****
-missing [] global-dotenv FB 3***** ??
+$ teller template --in config-templ.t
```
-Use `mirror-drift --sync ...` in order to declare that the two providers should represent a completely synchronized mirror (all keys, all values).
-
-As always, the specific provider definitions are in your `teller.yml` file.
+Template format is [Tera](https://keats.github.io/tera) which is very similar to liquid or handlebars.
-## :beetle: Detect secrets and value drift (graph links between providers)
-
-Some times you want to check drift between two providers, and two unrelated keys. For example:
-
-```
-Provider1:
- MG_PASS=foo***
-
-Provider2:
- MAILGUN_PASS=foo***
-```
-
-This poses a challenge. We need some way to "wire" the keys `MG_PASS` and `MAILGUN_PASS` and declare a relationship of source (`MG_PASS`) and destination, or sink (`MAILGUN_PASS`).
-
-For this, you can label mappings as `source` and couple with the appropriate sink as `sink`, effectively creating a graph of wirings. We call this `graph-drift` (use same label value for both to wire them together). Then, source values will be compared against sink values in your configuration:
+Here is an example template:
```yaml
-providers:
- dotenv:
- env_sync:
- path: ~/my-dot-env.env
- source: s1
- dotenv2:
- kind: dotenv
- env_sync:
- path: ~/other-dot-env.env
- sink: s1
-```
-
-And run
-
-```
-$ teller graph-drift dotenv dotenv2 -c your-config.yml
-```
-
-![](https://user-images.githubusercontent.com/83390/117453797-07512380-af4e-11eb-949e-cc875e854fad.png)
-
-## :scroll: Populate templates
-
-Have a kickstarter project you want to populate quickly with some variables (not secrets though!)?
-
-Have a production project that just _has_ to have a file to read that contains your variables?
-
-You can use `teller` to inject variables into your own templates (based on [go templates](https://golang.org/pkg/text/template/)).
-
-With this template:
-
-```go
-Hello, {{.Teller.EnvByKey "FOO_BAR" "default-value" }}!
-```
-
-Run:
-
-```
-$ teller template my-template.tmpl out.txt
-```
-
-Will get you, assuming `FOO_BAR=Spock`:
-
-```
-Hello, Spock!
+production_var: {{ key(name="PRINT_NAME")}}
+production_mood: {{ key(name="PRINT_MOOD")}}
```
## :arrows_counterclockwise: Copy/sync data between providers
@@ -310,30 +209,35 @@ In cases where you want to sync between providers, you can do that with `teller
**Specific mapping key sync**
+You can use the `/` format to copy a mapping from a provider to another provider:
+
```bash
-$ teller copy --from dotenv1 --to dotenv2,heroku1
+$ teller copy --from source/dev --to target/prod,<...>
```
-This will:
+In this simplistic example, we use the following configuration file
-1. Grab all mapped values from source (`dotenv1`)
-2. For each target provider, find the matching mapped key, and copy the value from source into it
-
-**Full copy sync**
-
-```bash
-$ teller copy --sync --from dotenv1 --to dotenv2,heroku1
+```yaml
+providers:
+ dot1:
+ kind: dotenv
+ maps:
+ - id: one
+ path: one.env
+ dot2:
+ kind: dotenv
+ maps:
+ - id: two
+ path: two.env
```
This will:
-1. Grab all mapped values from source (`dotenv1`)
-2. For each target provider, perform a full copy of values from source into the mapped `env_sync` key
+1. Grab all mapped values from source mapping
+2. For each target provider, find the matching mapping, and copy the values from source into it
-Notes:
-- The mapping per provider is as configured in your `teller.yaml` file, in the `env_sync` or `env` properties.
-- This sync will try to copy _all_ values from the source.
+By default copying will **update** target mapping (upsert data), if you want to replace you can use `--replace`.
## :bike: Write and multi-write to providers
@@ -342,86 +246,48 @@ Teller providers supporting _write_ use cases which allow writing values _into_
Remember, for this feature it still revolves around definitions in your `teller.yml` file:
```bash
-$ teller put FOO_BAR=$MY_NEW_PASS --providers dotenv -c .teller.write.yml
+$ teller put --providers new --map-id one NEW_VAR=s33kret
```
-A few notes:
-
-- Values are key-value pair in the format: `key=value` and you can specify multiple pairs at once
-- When you're specifying a literal sensitive value, make sure to use an ENV variable so that nothing sensitive is recorded in your history
-- The flag `--providers` lets you push to one or more providers at once
-- `FOO_BAR` must be a mapped key in your configuration for each provider you want to update
-
-Sometimes you don't have a mapped key in your configuration file and want to perform an ad-hoc write, you can do that with `--path`:
+In this example, this configuration is being used:
-```
-$ teller put SMTP_PASS=newpass --path secret/data/foo --providers hashicorp_vault
+```yaml
+providers:
+ new:
+ kind: dotenv
+ maps:
+ - id: one
+ path: new.env
```
A few notes:
-- The pair `SMTP_PASS=newpass` will be pushed to the specified path
-- While you can push to multiple providers, please make sure the _path semantics_ are the same
+- Values are key-value pair in the format: `key=value` and you can specify multiple pairs at once
+- When you're specifying a literal sensitive value, make sure to use an ENV variable so that nothing sensitive is recorded in your history
+- The flag `--providers` lets you push to one or more providers at once
## :x: Delete and multi-delete from providers
Teller providers support _deleting_ values _from_ providers.
-This feature revolves around definitions in your `teller.yml` file:
-
```bash
-$ teller delete FOO_BAR --providers dotenv -c .teller.yml
+$ teller delete --providers new --map-id one DELETE_ME
```
A few notes:
- You can specify multiple keys to delete, for example:
-
- ```bash
- $ teller delete FOO BAR BAZ --providers dotenv
- ```
-
- The flag `--providers` lets you push to one or more providers at once
-- All keys must be a mapped key in your configuration for each provider you want to delete from
-
-Sometimes you don't have a mapped key in your configuration file and want to perform an ad-hoc delete. You can do that with the `--path` flag:
-
-```bash
-$ teller delete FOO BAR --path ~/my-env-file.env --providers dotenv
-```
-
-You can also delete all keys at once for a given path, without specifying them one by one:
-
-```bash
-$ teller delete --all-keys --path ~/my-env-file.env --providers dotenv
-```
-## :white_check_mark: Prompts and options
-
-There are a few options that you can use:
-
-- **carry_env** - carry the environment from the parent process into the child process. By default we isolate the child process from the parent process. (default: _false_)
-
-- **confirm** - an interactive question to prompt the user before taking action (such as running a process). (default: _empty_)
-
-- **opts** - a dict for our own variable/setting substitution mechanism. For example:
-
-```
-opts:
- region: env:AWS_REGION
- stage: qa
-```
-
-And now you can use paths like `/{{stage}}/{{region}}/billing-svc` where ever you want (this templating is available for the **confirm** question too).
-
-If you prefix a value with `env:` it will get pulled from your current environment.
## `YAML` Export in YAML format
+XXX TODO: rewrite how the command export works
+
You can export in a YAML format, suitable for [GCloud](https://cloud.google.com/functions/docs/env-var):
```
-$ teller yaml
+$ teller export yaml
```
Example format:
@@ -433,10 +299,11 @@ KEY: VALUE
## `JSON` Export in JSON format
+
You can export in a JSON format, suitable for piping through `jq` or other workflows:
```
-$ teller json
+$ teller export json
```
Example format:
@@ -449,864 +316,23 @@ Example format:
# Providers
-For each provider, there are a few points to understand:
-
-- Sync - full sync support. Can we provide a path to a whole environment and have it synced (all keys, all values). Some of the providers support this and some don't.
-- Key format - some of the providers expect a path-like key, some env-var like, and some don't care. We'll specify for each.
-
-## General provider configuration
-
-We use the following general structure to specify sync mapping for all providers:
-
-```yaml
-# you can use either `env_sync` or `env` or both
-env_sync:
- path: ... # path to mapping
- remap:
- PROVIDER_VAR1: VAR3 # Maps PROVIDER_VAR1 to local env var VAR3
-env:
- VAR1:
- path: ... # path to value or mapping
- field: # optional: use if path contains a k/v dict
- decrypt: true | false # optional: use if provider supports encryption at the value side
- severity: high | medium | low | none # optional: used for secret scanning, default is high. 'none' means not a secret
- redact_with: "**XXX**" # optional: used as a placeholder swapping the secret with it. default is "**REDACTED**"
- VAR2:
- path: ...
-```
-
-### Remapping Provider Variables
-
-Providers which support syncing a list of keys and values can be remapped to different environment variable keys. Typically, when teller syncs paths from `env_sync`, the key returned from the provider is directly mapped to the environment variable key. In some cases it might be necessary to have the provider key mapped to a different variable without changing the provider settings. This can be useful when using `env_sync` for [Hashicorp Vault Dynamic Database credentials](https://www.vaultproject.io/docs/secrets/databases):
-
-```yaml
-env_sync:
- path: database/roles/my-role
- remap:
- username: PGUSER
- password: PGPASSWORD
-```
-
-Additionally, you can remap key settings by using `remap_with` instead of `remap`:
-```yaml
-env_sync:
- path: database/roles/my-role
- remap_with: # Use either remap or remap_with, not both.
- username:
- field: PGUSER
- severity: none
- password:
- field: PGPASSWORD
- severity: high
- redact_with: "**XXX**"
-```
-
-After remapping, the local environment variable `PGUSER` will contain the provider value for `username` and `PGPASSWORD` will contain the provider value for `password`.
-
-## Hashicorp Vault
-
-### Authentication
-
-If you have the Vault CLI configured and working, there's no special action to take.
-
-Configuration is environment based, as defined by client standard. See variables [here](https://github.com/hashicorp/vault/blob/api/v1.0.4/api/client.go#L28).
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write`
-- Key format - path based, usually starts with `secret/data/`, and more generically `[engine name]/data`
-
-### Example Config
-
-```yaml
-hashicorp_vault:
- env_sync:
- path: secret/data/demo/billing/web/env
- env:
- SMTP_PASS:
- path: secret/data/demo/wordpress
- field: smtp
-```
-
-## Consul
-
-### Authentication
-
-If you have the Consul CLI working and configured, there's no special action to take.
-
-Configuration is environment based, as defined by client standard. See variables [here](https://github.com/hashicorp/consul/blob/master/api/api.go#L28).
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write`
-- Key format
- - `env_sync` - path based, we use the last segment as the variable name
- - `env` - any string, no special requirement
-
-### Example Config
-
-```yaml
-consul:
- env_sync:
- path: ops/config
- env:
- SLACK_HOOK:
- path: ops/config/slack
-```
-
-## Heroku
-
-### Authentication
-
-Requires an API key populated in your environment in: `HEROKU_API_KEY` (you can fetch it from your ~/.netrc).
-
-Generate new token with Heroku cli: `heroku authorizations:create` then use the TOKEN value.
-
-### Features
+You can get a list of the providers and their described configuration values [in the documentation](https://docs.rs/teller).
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write`
-- Key format
- - `env_sync` - name of your Heroku app
- - `env` - the actual env variable name in your Heroku settings
+### Testing check list:
-### Example Config
+* [ ] **docker on windows**: if you have a container based test that uses Docker, make sure to exclude it on Windows using `#[cfg(not(windows))]`
-```yaml
-heroku:
- env_sync:
- path: my-app-dev
- env:
- MG_KEY:
- path: my-app-dev
-```
-
-## Etcd
-
-### Authentication
-
-If you have `etcdctl` already working there's no special action to take.
-
-We follow how `etcdctl` takes its authentication settings. These environment variables need to be populated
-
-- `ETCDCTL_ENDPOINTS`
-
-For TLS:
-
-- `ETCDCTL_CA_FILE`
-- `ETCDCTL_CERT_FILE`
-- `ETCDCTL_KEY_FILE`
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write`
-- Key format
- - `env_sync` - path based
- - `env` - path based
-
-### Example Config
-
-```yaml
-etcd:
- env_sync:
- path: /prod/billing-svc
- env:
- MG_KEY:
- path: /prod/billing-svc/vars/mg
-```
-
-## AWS Secrets Manager
-
-### Authentication
-
-Your standard `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` need to be populated in your environment
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write+delete`
-- Key format
- - `env_sync` - path based
- - `env` - path based
-- Handles plain text secrets (rather than key/value pairs) via the `plaintext: true` property
-
-### Example Config
-
-```yaml
-aws_secretsmanager:
- env_sync:
- path: /prod/billing-svc
- env:
- MG_KEY:
- path: /prod/billing-svc/vars/mg
-```
-
-Plain text secrets are useful for files; instead of using the usual JSON encoded key/value pairs. This example shows how to get a plaintext secret:
-
-```yaml
-aws_secretsmanager:
- env_sync:
- path: /prod/billing-svc
- env:
- MY_FILE:
- path: /prod/billing-svc/some-file
- plaintext: true
-```
-
-
-## AWS Parameter store
-
-### Authentication
-
-Your standard `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` need to be populated in your environment
-
-### Features
-
-- Sync - `no`
-- Mapping - `no`
-- Modes - `read+write+delete`
-- Key format
- - `env` - path based
- - `decrypt` - available in this provider, will use KMS automatically
-
-### Example Config
-
-```yaml
-aws_ssm:
- env:
- FOO_BAR:
- path: /prod/billing-svc/vars
- decrypt: true
-```
-
-## Google Secret Manager
-
-### Authentication
-
-You should populate `GOOGLE_APPLICATION_CREDENTIALS=account.json` in your environment to your relevant `account.json` that you get from Google.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write+delete`
-- Key format
- - `env` - path based, needs to include a version
- - `env_sync` - your project's path (gets the secrets latest version), when using --sync a new secret version will be created
- - `decrypt` - available in this provider, will use KMS automatically
-
-### Example Config
-
-```yaml
-google_secretmanager:
- env_sync:
- # secrets version is not relevant here since we are getting the latest version
- path: projects/44882
- env:
- MG_KEY:
- # need to supply the relevant version (versions/1)
- path: projects/44882/secrets/MG_KEY/versions/1
-```
-
-if your secrets in google secret manager are stored as JSON or K:V you can add the field property to query the secret body
-```yaml
-google_secretmanager:
- env_sync:
- # secrets version is not relevant here since we are getting the latest version
- path: projects/44882
- env:
- MG_KEY:
- # need to supply the relevant version (versions/1)
- path: projects/44882/secrets/MG_KEY/versions/1
- field: MG_KEY
-```
-
-## .ENV (dotenv)
-
-### Authentication
-
-No need. You'll be pointing to a one or more `.env` files on your disk.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write+delete`
-- Key format
- - `env` - env key like
-
-### Example Config
-
-You can mix and match any number of files, sitting anywhere on your drive.
-
-```yaml
-dotenv:
- env_sync:
- path: ~/my-dot-env.env
- env:
- MG_KEY:
- path: ~/my-dot-env.env
-```
-
-## Doppler
-
-### Authentication
-
-Install the [doppler cli][dopplercli] then run `doppler login`. You'll also need to configure your desired "project" for any given directory using `doppler configure`. Alternatively, you can set a global project by running `doppler configure set project ` from your home directory.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read`
-- Key format
- - `env` - env key like
-
-### Example Config
-
-```yaml
-doppler:
- env_sync:
- path: prd
- env:
- MG_KEY:
- path: prd
- field: OTHER_MG_KEY # (optional)
-```
-
-[dopplercli]: https://docs.doppler.com/docs/cli
-
-## Vercel
-
-### Authentication
-
-Requires an API key populated in your environment in: `VERCEL_TOKEN`.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read`, [write: accepting PR](https://github.com/spectralops/teller)
-- Key format
- - `env_sync` - name of your Vercel app
- - `env` - the actual env variable name in your Vercel settings
-
-### Example Config
-
-```yaml
-vercel:
- env_sync:
- path: my-app-dev
- env:
- MG_KEY:
- path: my-app-dev
-```
-
-## CyberArk Conjur
-
-### Authentication
-
-Requires a username and API key populated in your environment:
-
-- `CONJUR_AUTHN_LOGIN`
-- `CONJUR_AUTHN_API_KEY`
-
-Requires a .conjurrc file in your User's home directory:
-
-```yaml
----
-account: conjurdemo
-plugins: []
-appliance_url: https://conjur.example.com
-cert_file: ""
-```
-
-- `account` is the organization account created during initial deployment
-- `plugins` will be blank
-- `appliance_url` should be the Base URI for the Conjur service
-- `cert_file` should be the public key certificate if running in self-signed mode
-
-### Features
-
-- Sync - `no` [sync: accepting PR](https://github.com/spectralops/teller)
-- Mapping - `yes`
-- Modes - `read+write`
-- Key format
- - `env_sync` - not supported to comply with least-privilege model
- - `env` - the secret variable path in Conjur Secrets Manager
-
-### Example Config
-
-```yaml
-cyberark_conjur:
- env:
- DB_USERNAME:
- path: /secrets/prod/pgsql/username
- DB_PASSWORD:
- path: /secrets/prod/pgsql/password
-```
-
-## Cloudflare Workers KV
-
-### Authentication
-
-requires the following environment variables to be set:
-
-`CLOUDFLARE_API_KEY`: Your Cloudflare api key.
-`CLOUDFLARE_API_EMAIL`: Your email associated with the api key.
-`CLOUDFLARE_ACCOUNT_ID`: Your account ID.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read` (write coming soon)
-- Key format
- - `env_sync` - The KV namespace ID
- - `field` - the actual key stored in the KV store
- - `env` - the actual key stored in the KV store
-
-### Example Config
-
-```yaml
-opts:
- kv-namespace-id:
-
-providers:
- cloudflare_workers_kv:
- env_sync:
- path: "{{kv-namespace-id}}"
- remap:
- # looks up the key `test_key` and maps it to `TEST`.
- test_key: TEST
- env:
- SOME_SECRET:
- path: "{{kv-namespace-id}}"
- # Accesses the key `SOME_SECRET` in the KV namespace.
- REMAPPED_KEY:
- path: "{{kv-namespace-id}}"
- # Accesses the field `SOME_KEY` in the KV namespace and maps it to REMAPPED_KEY.
- field: SOME_KEY
-```
-
-## Cloudflare Workers Secrets
-
-### Usage:
-
-```sh
-$ teller put foo-secret=000000 --providers cloudflare_workers_secrets
-$ teller put foo-secret=123 foo-secret2=456 --providers cloudflare_workers_secrets --sync # take from env_sync for using the same source for multiple secrets
-$ teller delete foo-secret foo-secret2 --providers cloudflare_workers_secrets
-```
-
-### Authentication
-
-requires the following environment variables to be set:
-
-`CLOUDFLARE_API_KEY`: Your Cloudflare api key.
-`CLOUDFLARE_API_EMAIL`: Your email associated with the api key.
-`CLOUDFLARE_ACCOUNT_ID`: Your account ID.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `write`
-- Key format
- - `source` - The script name
- - `path` - Name of the secret, when using `--sync` the path will overridden by the given parameters
-
-### Example Config
-
-```yaml
-providers:
- cloudflare_workers_secrets:
- env_sync:
- source: script-id
- env:
- foo-secret:
- path: foo-secret
- source: script-id
- foo-secret2:
- path: foo-secret
- source: script-id
-```
-
-## 1Password
-
-### Authentication
-
-To integrate with the 1Password API, you should have system-to-system secret management running in your infrastructure/localhost [more details here](https://support.1password.com/connect-deploy-docker/).
-
-Requires the following environment variables to be set:
-`OP_CONNECT_HOST` - The hostname of the 1Password Connect API
-`OP_CONNECT_TOKEN` - The API token to be used to authenticate the client to a 1Password Connect API.
-
-### Features
-
-- Sync - `no`
-- Mapping - `yes`, returns all fields on specific secret item
-- Modes - `read+write`
-- Key format
- - `env_sync` - Will return all the fields under the secret item.
- - `path` - Mandatory field: Secret item name. (expected unique secret item with the same name)
- - `source` - Mandatory field: vaultUUID to query
- - `env`
- - `path` - Mandatory field: Secret item name. (expected unique secret item with the same name)
- - `source` - Mandatory field: vaultUUID to query
- - `field` - Mandatory field: the specific field name (notesPlain, {custom label name}, password, type etc).
-
-### Example Config
-
-```yaml
-1password:
- env_sync:
- path: security-note
- source: VAULT-ID
- env:
- SECURITY_NOTE_TITLE:
- path: security-note-title
- source: VAULT-ID
- field: lable1
- NOTE_SECRET:
- path: login-title
- source: VAULT-ID
- field: password
- CREDIT_CARD:
- path: credit-card-title
- source: VAULT-ID
- field: type
-```
-
-### Run Example:
-
-Example:
-
-```sh
-OP_CONNECT_HOST="http://localhost:8080" OP_CONNECT_TOKEN="" teller yaml
-```
-
-## Gopass
-
-### Authentication
-
-If you have the Gopass working and configured, there's no special action to take.
-
-Configuration is environment based, as defined by client standard. See variables [here](https://github.com/gopasspw/gopass/blob/master/docs/config.md).
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write`
-- Key format
- - `env_sync` - path based, we use the last segment as the variable name
- - `env` - any string, no special requirement
-
-### Example Config
-
-```yaml
-gopass:
- env_sync:
- path: foo
- env:
- SLACK_HOOK:
- path: path: foo/bar
-```
-
-## LastPass
-
-### Authentication
-
-Requires the following environment variables to be set:
-`LASTPASS_USERNAME` - LastPass username
-`LASTPASS_PASSWORD` - LastPass password
-
-### Features
-
-- Sync - `no`
-- Mapping - `no`
-- Modes - `read`
-- Key format
- - `env_sync` - Will return all the fields under the secret item.
- - `path` - Mandatory field: Secret item ID.
- - `env`
- - `path` - Mandatory field: Secret item ID.
- - `field` - Optional field: by default taking `password` property. in case you want other property un-mark this line and set the LastPass property name.
-
-## GitHub
-
-### Usage:
-
-```sh
-$ teller put FROM_TELLER=00000 FROM_TELLER_2=00002 --providers github --sync # Add secrets with dynamic secrets name (take from env_sync)
-$ teller put FROM_TELLER=1111 FROM_TELLER_2=222 --providers github # Add defined secrets name from env key (YAML key will be the name of the secret)
-$ teller delete FROM_TELLER --providers github # Delete specific secret
-$ teller delete FROM_TELLER FROM_TELLER_2 --providers github --all-keys --path={OWNER}/{REPO-NAME} # Delete all repo secrets, limited to 100 secrets per repository
-```
-
-### Authentication
-
-requires the following environment variables to be set:
-
-`GITHUB_AUTH_TOKEN`: GitHub token.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `write`
-- Key format
- - `path` - Contain the repo owner and the repo name with `/` separator
-
-### Example Config
-
-```yaml
-providers:
- github:
- env_sync:
- path: owner/repo-name
- env:
- FROM_TELLER:
- path: owner/repo-name
- FROM_TELLER_2:
- path: owner/repo-name
-```
-
-## KeyPass
-
-### Authentication
-
-requires the following environment variables to be set:
-
-`KEYPASS_PASSWORD`: Password database credentials
-`KEYPASS_DB_PATH`: Database path
-
-### Features
-
-- Sync - `no`
-- Mapping - `yes`
-- Modes - `read`
-
-### Example Config
-
-```yaml
-providers:
- keypass:
- env_sync:
- path: redis/config
- # source: Optional, all fields is the default. Supported fields: Notes, Title, Password, URL, UserName
- env:
- ETC_DSN:
- path: redis/config/foobar
- # source: Optional, Password is the default. Supported fields: Notes, Title, Password, URL, UserName
-```
-
-## FileSystem
-
-Allows to work against filesystem structure. for example:
-
-```
-.
-└── /folder
- ├── settings/
- │ ├── billing-svc
- │ └── all/
- │ ├── foo
- └── bar
-```
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write`
-
-### Example Config
-
-```yaml
-providers:
- filesystem:
- env_sync:
- path: /folder/settings
- env:
- ETC_DSN:
- path: /folder/bar
-```
-
-## ProcessEnv
-
-Load the environment variables from the parent process as needed.
-
-### Authentication
-
-No need.
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read`
-- Key format
- - `env_sync` - Will return all the environment variables in the parent process
- - `path` - Value isn't required or used
- - `env`
- - `field` - Optional field: specific environment variable in the parent process
-
-### Example Config
-
-```yaml
-providers:
- process_env:
- env:
- ETC_DSN:
- # Optional: accesses the environment variable `SOME_KEY` and maps it to ETC_DSN
- field: SOME_KEY
-```
-
-## Azure
-
-### Authentication
-
-Two options for getting Azure authentication are available:
-
-1. Standard Azure [environment variable](https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authorization). (Enable by default)
-2. For get credentials via [az client](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) add `AZURE_CLI=1` to your environment variable
-
-Then set your vault-name by specific as environment variable: `KVAULT_NAME`
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read+write+delete`
-
-### Example Config
-
-```yaml
-providers:
- azure_keyvault:
- env_sync:
- path: azure
- env:
- FOO:
- path: bar
-```
-
-## Keeper Secrets Manager
-
-### Authentication
-
-Configuration is environment based - you should populate `KSM_CONFIG=base64_config` or `KSM_CONFIG_FILE=ksm_config.json` in your environment with a valid [Secrets Manager Configuration](https://docs.keeper.io/secrets-manager/secrets-manager/about/secrets-manager-configuration#creating-a-secrets-manager-configuration). If both environment variables are set then `KSM_CONFIG` is used.
-
-Note:
-- _Secrets Manager CLI configuration file format (INI) is different from KSM SDK configuration format (JSON) but the CLI [command](https://docs.keeper.io/secrets-manager/secrets-manager/secrets-manager-command-line-interface/profile-command#export) `ksm profile export profile_name` can be used to export some of the individual profiles into JSON config file compatible with the provider._
-
-### Features
-
-- Sync - `yes`
-- Mapping - `yes`
-- Modes - `read`
-- Key format
- - `env_sync` - path is single record UID. Field labels (if empty -> field types) are used as keys, any duplicates will have a numeric suffix.
- - `env` - any string, conforming to Keeper [Notation](https://docs.keeper.io/secrets-manager/secrets-manager/about/keeper-notation) _(`keeper://` prefix not required)_
-
-### Example Config
-
-```yaml
-keeper_secretsmanager:
- env_sync:
- path: ABCDEFGHIJKLMNOPQRSTUV
- env:
- PGUSER:
- path: ABCDEFGHIJKLMNOPQRSTUV/field/login
- CERT:
- path: ABCDEFGHIJKLMNOPQRSTUV/custom_field/ssl_cert
-```
-
-# Semantics
-
-## Addressing
-
-- Stores that support key-value interfaces: `path` is the direct location of the value, no need for the `env` key or `field`.
-- Stores that support key-valuemap interfaces: `path` is the location of the map, while the `env` key or `field` (if exists) will be used to do the additional final addressing.
-- For env-sync (fetching value map out of a store), path will be a direct pointer at the key-valuemap
-
-## Errors
-
-- **Principle of non-recovery**: where possible an error is return when it is not recoverable (nothing to do), but when it is -- providers should attempt recovery (e.g. retry API call)
-- **Missing key/value**: it's possible that when trying to fetch value in a provider - it's missing. This is not an error, rather an indication of a missing entry is returned (`EnvEntry#IsFound`)
-
-# Security Model
-
-## Project Practices
-
-- We `vendor` our dependencies and push them to the repo. This creates an immutable, independent build, that's also free from risks of fetching unknown code in CI/release time.
-
-## Providers
-
-For every provider, we are federating all authentication and authorization concern to the system of origin. In other words, if for example you connect to your organization's Hashicorp Vault, we assume you already have a secure way to do that, which is "blessed" by the organization.
-
-In addition, we don't offer any way to specify connection details to these systems in writing (in configuration files or other), and all connection details, to all providers, should be supplied via environment variables.
-
-That allows us to keep two important points:
-
-1. Don't undermine the user's security model and threat modeling for the sake of productivity (security AND productivity CAN be attained)
-2. Don't encourage the user to do what we're here for -- save secrets and sensitive details from being forgotten in various places.
-
-## Developer Guide
-
-- [Add new provider](./doc/new-provider.md)
-- Quick testing as you code: `make lint && make test`
-- Checking your work before PR, run also integration tests: `make integration`
+* [ ] **resource semantics**: while building providers, align with the semantics of _empty_ and _not found_ as two different semantics: if a provider supports an explicit "not found" semantic (404, NotFound, etc.), use `Error::NotFound`. Otherwise when a provider signals a "not found" semantic as an empty data bag, return an empty `KV[]` (i.e. do not translate a sematic of "empty" into "not found").
### Testing
-Testing is composed of _unit tests_ and _integration tests_. Integration tests are based on _testcontainers_ as well as live sandbox APIs (where containers are not available)
-
-- Unit tests are a mix of pure and mocks based tests, abstracting each provider's interface with a custom _client_
-- View [integration tests](/pkg/integration_test)
-
-To run all unit tests without integration:
-
-```
-$ make test
-```
-
-To run all unit tests including container-based integration:
+Testing is done with:
```
-$ make integration
+$ cargo test --all --all-features
```
-To run all unit tests including container and live API based integration (this is effectively **all integration tests**):
-
-```
-$ make integration_api
-```
-
-Running all tests:
-
-```
-$ make test
-$ make integration_api
-```
-
-### Linting
-
-Linting is treated as a form of testing (using `golangci`, configuration [here](.golangci.yml)), to run:
-
-```
-$ make lint
-```
+And requires Docker (or equivalent) on your machine.
### Thanks:
@@ -1318,4 +344,4 @@ Teller follows [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/ma
# Copyright
-Copyright (c) 2021 [@jondot](http://twitter.com/jondot). See [LICENSE](LICENSE.txt) for further details.
+Copyright (c) 2024 [@jondot](http://twitter.com/jondot). See [LICENSE](LICENSE.txt) for further details.
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index add1b20b..00000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Teller Roadmap
-
-This document lists the new features being considered for the future and that are being experimented / worked on currently. We would like our contributors and users to know what features could come in the near future. We definitely welcome suggestions and ideas from everyone about the roadmap and features (feel free to open an [issue](https://github.com/SpectralOps/teller/issues)).
-
-## Currently worked on
-
-### Additional use cases
-
-* Secret value policy - allow for validating fetched secrets against a policy such as secret strength, raising a warning if policy is not met
-* Secret enclave. Create a local secret engine to be stored co-located with code. This will become _just another provider_ to pick from
-* Zero-trust / last-mile encryption. Have Teller perform the last-mile encryption at the point of fetching secrets allowing for zero-trust secret management. (this may share implementation details with the secret enclave)
-
-
-## Planned Features
-
-### Additional use cases
-
-* Push-only providers, where only write is supported. Typically CI providers only allow for secrets to be written-in but not read-from. We want to allow for a "push" use case where users could sync secrets from vaults into operational platforms such as CI and production. That means creating the concept of _write only providers_.
-* Kubernetes secrets sidecar. Enable a seamless way to have secrets fetched just-in-time for processes as a sidecar.
-* Native Jenkins plugin as secrets engine. Have Jenkins point to a secret store that's actually backed by Teller - which delegates to your favorite vaults.
-
-### Additional Providers
-
-**Read/Write**
-
-* 1password (via local tooling)
-* Lastpass (via local tooling)
-* gopass
-
-
-
-**Write only**
-
-* Github
-* Gitlab
-* CircleCI
-* Travis
-
-
diff --git a/best-practices.md b/best-practices.md
deleted file mode 100644
index 53e5a76c..00000000
--- a/best-practices.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Intro
-This is a list of some of the best practices using this tool. Please contribute if you have any additions or changes to current or non existing best practices
-
-* [`Different envs`](#different-envs).
-
-# Different envs
-We primarily use 2 methods:
-
-* A single file, where each environment is a parameter, see prompts and options, where you can populate one with an environment variable env:STAGE and STAGE=dev teller ...
-* Keep a config file per environment, similar to what you would do with .env file (.env.example, .env.production, etc.) -- but with teller none of configuration files contain any sensitive information (as opposed to .env) so you're safe.
-
-The best practice really depends on the size of your team and how you prefer to work. We imagine if the team is small, and the use cases are not many, a single file would be great. If the team is large, or maybe you're enabling other teams -- keeping a file per environment would be better, and this way you can "distribute" your teller files per use case in a central way.
\ No newline at end of file
diff --git a/doc/new-provider.md b/doc/new-provider.md
deleted file mode 100644
index cffcec75..00000000
--- a/doc/new-provider.md
+++ /dev/null
@@ -1,97 +0,0 @@
-# How to add a new provider
-
-Adding a new Teller provider is very easy, but you still need to know where to start. We summarize the steps very shortly to make your life easier
-
-## Provider implementation
-
-1. Copy the file [example.go](../examples/providers/example.go) from [examples/providers/example.go](../examples/providers/example.go) and make sure to implement all the required behaviors. The [example.go](../examples/providers/example.go) file is a skeleton for adding a new provider, it contains stubs for an interface which declares the required functionality that any provider must have.
-2. After copying to the providers dir, remove the comment from the `init` function to register your provider
-
-```go
- // func init() {
-// metaInto := core.MetaInfo{
-// Description: "ProviderName",
-// Name: "provider_name",
-// Authentication: "If you have the Consul CLI working and configured, there's no special action to take.\nConfiguration is environment based, as defined by client standard. See variables [here](https://github.com/hashicorp/consul/blob/master/api/api.go#L28).",
-// ConfigTemplate: `
-// provider:
-// env:
-// KEY_EAXMPLE:
-// path: pathToKey
-// `,
-// Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
-// }
-// RegisterProvider(metaInto, NewExample)
-// }
-```
-
-Set `Description, Name, and Authentication`, as well as `OpMatrix` that descibes the action this provider supports, based on your implementation.
-
-3. Add a provider template configuration in path: [pkg/wizard_template.go](../pkg/wizard_template.go). This will be used to auto-generate a configuration.
-
-```go
-{{- if index .ProviderKeys "example" }}
- # Add here some authentication requirements, like a token that should be in the user's environment.
- example:
- env_sync:
- path: redis/config
- env:
- ETC_DSN:
- path: redis/config/foobar
-
-{{end}}
-```
-
-You're done! :rocket:
-
-### Verify your work:
-
-Run the command `go run main.go new` and run through the flow in the wizard.
-Ensure that you see your provider in the `Select your secret providers` question.
-
-After the `teller.yml` file is created, run the command `go run main.go yaml`, you should see the message :
-
-```sh
-FATA[0000] could not load all variables from the given existing providers error="provider \"Example\" does not implement write yet"
-```
-
-This means that you configured the provider successfully and are ready to implement the functions in it.
-
-### Notes
-
-- Since each provider uses some kind of system behind it (e.g. Hashicorp Vault provider connects to the Hashicorp Vault itself) try to wrap the access to the backend or system with your own abstract client-provider with an interface. It will help you to test your provider easier.
-- Use provider logger for better visibility when an error occurs.
-- Add the new provider to provider mapping in [README.md](../README.md#remapping-provider-variables).
-
-### Adding third-party packages
-
-We `vendor` our dependencies and push them to the repo. This creates an immutable, independent build, that's also free from risks of fetching unknown code in CI/release time.
-
-After adding your packages to import in your provider file, run the commands:
-
-```sh
-$ go mod tidy
-$ go mod vendor
-```
-
-## Adding tests
-
-Create an `example_test.go` file in [pkg/providers](../pkg/providers) folder.
-
-In case you warp the client-provider with an interface you can run a mock generator with the [mock](https://github.com/golang/mock) framework and add this command to the [Makefile](../Makefile)
-
-```sh
-mockgen -source pkg/providers/example.go -destination pkg/providers/mock_providers/example_mock.go
-```
-
-Test guidelines:
-
-- Create a `TestExample` function and call [AssertProvider](../pkg/providers/helpers_test.go) for testing main functionality.
-- Create a `TestExampleFailures` for testing error handling.
-- You can also add more tests for testing private functions.
-- Run `make lint` to validate linting.
-- Run `make test` for make sure that all the test pass.
-
-```
-
-```
diff --git a/e2e/README.md b/e2e/README.md
deleted file mode 100644
index 07d1eacb..00000000
--- a/e2e/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# End to End Tests
-
-This package contains integration tests.
-
-## Running
-```sh
-make e2e
-```
-
-## Create E2E test
-
-You have two different options to create an E2E test in Teller.
-
-### Simple
-The simple and fast way is based on `yml` file configuration. All you need to do is create a *.yml file in [test folder](./tests/) with the following fields:
-
-| Field | Description |
-|-------------------------------|------------------------------------
-| `name` | E2E name.
-| `command` | Command to execute. You need to keep ``; this placeholder will replace with the `BINARY_PATH` binary value.
-| `config_file_name` | Configuration file name. If empty, the configuration file will not be created.
-| `config_content` | Configuration file content.
-| `init_snapshot` | List of files that were going to create before Teller binary is executed.
-| `init_snapshot.path` | Create file in path.
-| `init_snapshot.file_name` | File name.
-| `init_snapshot.content` | File content.
-| `replace_stdout_content` | Replace dynamic stdout content to static. for example, replace current timestemp to static text.
-| `expected_snapshot` | Compare the init_snapshot folder with the expected snapshot content. If empty, this compare check will be ignored.
-| `expected_snapshot.path` | Create file in path.
-| `expected_snapshot.file_name` | File name.
-| `expected_snapshot.content` | File content.
-| `expected_stdout` | Check if Teller stdout equals this value. If empty, this check will be ignored.
-| `expected_stderr` | Check if Teller stderr equals this value. If empty, this check will be ignored.
-
-### Advanced
-In case the E2E `yml`not flexible enough you have the option to create a `*.go` file in [test folder](./tests/). the go file most to:
-1. Implement [TestCaseDescriber](./register/interface.go) interface
-2. Must to user `init` function and register to [AddSuite](./register/register.go)
\ No newline at end of file
diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go
deleted file mode 100644
index 202158e0..00000000
--- a/e2e/e2e_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package e2e
-
-import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "testing"
-
- "github.com/spectralops/teller/e2e/register"
- _ "github.com/spectralops/teller/e2e/tests"
- "github.com/spectralops/teller/e2e/testutils"
- "github.com/stretchr/testify/assert"
-)
-
-const (
- replaceToStaticPath = "DYNAMIC-FULL-PATH"
- replaceToShortStaticPath = "DYNAMIC-SHORT-PATH"
- removeBinaryPlaceholder = ""
- testsFolder = "tests"
-)
-
-func TestE2E(t *testing.T) { //nolint
- t.Parallel()
-
- // validate given binary path
- binaryPath, err := getBinaryPath()
- if err != nil {
- assert.FailNow(t, err.Error())
- }
-
- snapshotSuites, err := testutils.GetYmlSnapshotSuites(testsFolder)
- assert.Nil(t, err)
-
- // consider to replace `diff` command which is depended on OS to golang plugin.
- // could't find something better
- differ := testutils.NewExecDiffer()
- // Loop on all test/*.yml files
- for _, snapshot := range snapshotSuites {
-
- t.Run(snapshot.Name, func(t *testing.T) {
-
- // create a temp folder for the test
- tempFolder, err := os.MkdirTemp(t.TempDir(), strings.ReplaceAll(snapshot.Name, " ", ""))
- // descrive the base snapshot data of the test
- snapshotFolder := filepath.Join(tempFolder, "snapshot")
- assert.Nil(t, err, "could not create temp folder")
- defer os.RemoveAll(tempFolder)
-
- if len(snapshot.InitSnapshot) > 0 {
- err = snapshot.CreateSnapshotData(snapshot.InitSnapshot, snapshotFolder)
- assert.Nil(t, err)
- }
-
- if snapshot.ConfigFileName != "" {
- err = snapshot.CrateConfig(snapshotFolder)
- assert.Nil(t, err)
- }
-
- flagsCommand := strings.TrimPrefix(snapshot.Command, removeBinaryPlaceholder)
- stdout, stderr, err := testutils.ExecCmd(binaryPath, strings.Split(flagsCommand, " "), snapshotFolder)
- if stdout == "" {
- assert.Nil(t, err, stderr)
- }
-
- // In case the stdout/stderr include the dynamic folder path, we want to replace with static-content for better snapshot text compare
- stdout, stderr = replaceFolderName(stdout, stderr, snapshotFolder)
-
- if len(snapshot.ReplaceStdOutContent) > 0 {
- for _, r := range snapshot.ReplaceStdOutContent {
- var re = regexp.MustCompile(r.Search)
- stdout = re.ReplaceAllString(stdout, r.Replace)
- }
- }
- if len(snapshot.ReplaceStdErrContent) > 0 {
- for _, r := range snapshot.ReplaceStdErrContent {
- var re = regexp.MustCompile(r.Search)
- stderr = re.ReplaceAllString(stderr, r.Replace)
- }
- }
-
- if snapshot.ExpectedStdOut != "" {
- assert.Equal(t, snapshot.ExpectedStdOut, stdout)
- }
-
- if snapshot.ExpectedStdErr != "" {
- assert.Equal(t, snapshot.ExpectedStdErr, stderr)
- }
-
- if len(snapshot.ExpectedSnapshot) > 0 {
- destSnapshotFolder := filepath.Join(tempFolder, "dest")
- err = snapshot.CreateSnapshotData(snapshot.ExpectedSnapshot, destSnapshotFolder)
- assert.Nil(t, err)
-
- diffResult, err := testutils.FolderDiff(differ, destSnapshotFolder, snapshotFolder, []string{snapshot.ConfigFileName})
- if err != nil {
- t.Fatalf("snapshot folder is not equal. results: %v", diffResult)
- }
- assert.Nil(t, err)
- }
- })
- }
- // loop on register suites (from *.go files)
- for name, suite := range register.GetSuites() {
- t.Run(name, func(t *testing.T) {
-
- // creates temp dir for test path.
- tempFolder, err := os.MkdirTemp(t.TempDir(), strings.ReplaceAll(name, " ", ""))
- assert.Nil(t, err, "could not create temp folder")
- defer os.RemoveAll(tempFolder)
-
- // initialized test case
- testInstance := suite(tempFolder)
-
- err = testInstance.SetupTest()
- assert.Nil(t, err)
-
- // get Teller flags command
- flags := testInstance.GetFlags()
-
- stdout, stderr, err := testutils.ExecCmd(binaryPath, flags, tempFolder)
- assert.Nil(t, err)
-
- stdout, stderr = replaceFolderName(stdout, stderr, tempFolder)
- err = testInstance.Check(stdout, stderr)
- assert.Nil(t, err)
- })
- }
-}
-
-func replaceFolderName(stdout, stderr, workingDirectory string) (string, string) {
- stdout = strings.ReplaceAll(stdout, workingDirectory, replaceToStaticPath)
- stderr = strings.ReplaceAll(stderr, workingDirectory, replaceToStaticPath)
- shortFolderPath := workingDirectory[0:13]
- stdout = strings.ReplaceAll(stdout, shortFolderPath, replaceToShortStaticPath)
- stderr = strings.ReplaceAll(stderr, shortFolderPath, replaceToShortStaticPath)
-
- return stdout, stderr
-}
-
-func getBinaryPath() (string, error) {
- binaryPath, isExists := os.LookupEnv("BINARY_PATH")
- if !isExists {
- return "", errors.New("missing `BINARY_PATH`")
- }
-
- info, err := os.Stat(binaryPath)
- errors.Is(err, os.ErrNotExist)
-
- if err != nil || info.IsDir() {
- return "", fmt.Errorf("%s not found", binaryPath)
- }
- return binaryPath, nil
-}
diff --git a/e2e/register/interface.go b/e2e/register/interface.go
deleted file mode 100644
index 1b15d345..00000000
--- a/e2e/register/interface.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package register
-
-type NewSuite func(tempFolderPath string) TestCaseDescriber
-
-type TestCaseDescriber interface {
- SetupTest() error
- GetFlags() []string
- Check(stdOut, stderr string) error
-}
diff --git a/e2e/register/register.go b/e2e/register/register.go
deleted file mode 100644
index f9053ff2..00000000
--- a/e2e/register/register.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package register
-
-var suite = map[string]NewSuite{}
-
-func AddSuite(name string, test NewSuite) {
- suite[name] = test
-}
-
-func GetSuites() map[string]NewSuite {
- return suite
-}
diff --git a/e2e/tests/custom-config-file.yml b/e2e/tests/custom-config-file.yml
deleted file mode 100644
index bf47cd6b..00000000
--- a/e2e/tests/custom-config-file.yml
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: Custom config name
-command: show -c .custom-teller.yml
-config_file_name: .custom-teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
-expected_snapshot:
-expected_stderr: |
- -*- teller: loaded variables for test using .custom-teller.yml -*-
-
- [filesystem DYNAMIC-SHORT-PATH...tings/test/foo] FOO = sh*****
-
-expected_stdout:
diff --git a/e2e/tests/delete.yml b/e2e/tests/delete.yml
deleted file mode 100644
index dfdfdf51..00000000
--- a/e2e/tests/delete.yml
+++ /dev/null
@@ -1,48 +0,0 @@
----
-name: Delete multiple entires
-command: delete FOO BAR --providers filesystem
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_snapshot:
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-
-expected_stderr: |
- Delete FOO (DYNAMIC-FULL-PATH/settings/test/foo) in filesystem: OK.
- Delete BAR (DYNAMIC-FULL-PATH/settings/test/bar) in filesystem: OK.
-
-expected_stdout:
\ No newline at end of file
diff --git a/e2e/tests/json.yml b/e2e/tests/json.yml
deleted file mode 100644
index c5e56d39..00000000
--- a/e2e/tests/json.yml
+++ /dev/null
@@ -1,42 +0,0 @@
----
-name: Show JSON entries
-command: json
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_snapshot:
-expected_stdout: >-
- {
- "BAR": "shazam",
- "FOO": "shazam",
- "secret-a": "mailman",
- "secret-b": "shazam",
- "secret-c": "shazam-1"
- }
-expected_stderr:
\ No newline at end of file
diff --git a/e2e/tests/put-sync.yml b/e2e/tests/put-sync.yml
deleted file mode 100644
index 680fbacb..00000000
--- a/e2e/tests/put-sync.yml
+++ /dev/null
@@ -1,58 +0,0 @@
----
-name: Put with --sync flag
-command: put key-1=key-1-content key-2=key-2-content --providers filesystem --sync
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
- - path: settings/test/all
- file_name: key-1
- content: key-1-content
- - path: settings/test/all
- file_name: key-2
- content: key-2-content
-expected_stderr: |
- Synced filesystem (DYNAMIC-FULL-PATH/settings/test/all): OK.
-
-expected_stdout:
\ No newline at end of file
diff --git a/e2e/tests/put.yml b/e2e/tests/put.yml
deleted file mode 100644
index ec3b400d..00000000
--- a/e2e/tests/put.yml
+++ /dev/null
@@ -1,53 +0,0 @@
----
-name: Put multiple entires
-command: put FOO=new-foo-value BAR=new-bar-value --providers filesystem
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_snapshot:
- - path: settings/test
- file_name: foo
- content: new-foo-value
- - path: settings/test
- file_name: bar
- content: new-bar-value
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_stderr: |
- Put BAR (DYNAMIC-FULL-PATH/settings/test/bar) in filesystem: OK.
- Put FOO (DYNAMIC-FULL-PATH/settings/test/foo) in filesystem: OK.
-
-expected_stdout:
\ No newline at end of file
diff --git a/e2e/tests/redact.yml b/e2e/tests/redact.yml
deleted file mode 100644
index 91630414..00000000
--- a/e2e/tests/redact.yml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-name: redact input
-command: redact --in dirty
-config_file_name: .teller.yml
-config_content: >
- project: redaction test
-
- providers:
- filesystem:
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: secret
- - path:
- file_name: dirty
- content: |
- content
- secret
- content
-expected_snapshot:
-replace_stdout_content:
-expected_stdout: |
- content
- **REDACTED**
- content
-expected_stderr:
\ No newline at end of file
diff --git a/e2e/tests/run-redact.yml b/e2e/tests/run-redact.yml
deleted file mode 100644
index 5c6988e1..00000000
--- a/e2e/tests/run-redact.yml
+++ /dev/null
@@ -1,33 +0,0 @@
----
-name: Run with --redact flag
-command: run --redact -- bash script
-config_file_name: .teller.yml
-config_content: >
- project: redaction test
-
- providers:
- filesystem:
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: secret
- - path:
- file_name: script
- content: |
- #!/usr/bin/env bash
- for _ in {1..5};
- do
- echo secret value;
- done
-expected_snapshot:
-replace_stdout_content:
-expected_stdout: |
- **REDACTED** value
- **REDACTED** value
- **REDACTED** value
- **REDACTED** value
- **REDACTED** value
-expected_stderr:
\ No newline at end of file
diff --git a/e2e/tests/scan.yml b/e2e/tests/scan.yml
deleted file mode 100644
index 4f8bcb11..00000000
--- a/e2e/tests/scan.yml
+++ /dev/null
@@ -1,47 +0,0 @@
----
-name: scan entries
-command: scan
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/files/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- severity: low
- BAR:
- path: {{.Folder}}/settings/test/bar
- severity: medium
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test
- file_name: file
- content: content
- - path: files/all
- file_name: secret-a
- content: mailman
- - path: files/all
- file_name: secret-b
- content: 1111
-expected_snapshot:
-replace_stdout_content:
- - search: "matches in [0-9].[0-9]+(.*)"
- replace: matches in 1.000ms
-expected_stdout: |
- [high] files/all/secret-a (1,0): found match for filesystem/secret-a (ma*****)
- [high] files/all/secret-b (1,0): found match for filesystem/secret-b (11*****)
- [low] settings/test/bar (1,0): found match for filesystem/FOO (sh*****)
- [medium] settings/test/bar (1,0): found match for filesystem/BAR (sh*****)
- [low] settings/test/foo (1,0): found match for filesystem/FOO (sh*****)
- [medium] settings/test/foo (1,0): found match for filesystem/BAR (sh*****)
- Scanning for 4 entries: found 6 matches in 1.000ms
-expected_stderr:
diff --git a/e2e/tests/sh.yml b/e2e/tests/sh.yml
deleted file mode 100644
index dc19a76e..00000000
--- a/e2e/tests/sh.yml
+++ /dev/null
@@ -1,46 +0,0 @@
----
-name: Sh print
-command: sh
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
- - path: settings/test/all
- file_name: secret-d
- content: ()"';@ \(\)\"\'\;\@
-
-expected_snapshot:
-expected_stderr:
-expected_stdout: |
- #!/bin/sh
- export secret-d='()"'"'"';@ \(\)\"\'"'"'\;\@'
- export secret-c='shazam-1'
- export secret-b='shazam'
- export secret-a='mailman'
- export FOO='shazam'
- export BAR='shazam'
diff --git a/e2e/tests/show.yml b/e2e/tests/show.yml
deleted file mode 100644
index bbe49baa..00000000
--- a/e2e/tests/show.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-name: Show entries
-command: show
-config_file_name: .teller.yml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_snapshot:
-expected_stderr: |
- -*- teller: loaded variables for test using .teller.yml -*-
-
- [filesystem DYNAMIC-SHORT-PATH...tings/test/bar] BAR = sh*****
- [filesystem DYNAMIC-SHORT-PATH...tings/test/foo] FOO = sh*****
- [filesystem DYNAMIC-SHORT-PATH...tings/test/all] secret-a = ma*****
- [filesystem DYNAMIC-SHORT-PATH...tings/test/all] secret-b = sh*****
- [filesystem DYNAMIC-SHORT-PATH...tings/test/all] secret-c = sh*****
-
-expected_stdout:
\ No newline at end of file
diff --git a/e2e/tests/version.go b/e2e/tests/version.go
deleted file mode 100644
index d08c9548..00000000
--- a/e2e/tests/version.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package test
-
-import (
- "errors"
- "regexp"
-
- "github.com/spectralops/teller/e2e/register"
-)
-
-func init() { //nolint
- register.AddSuite("version", NewSuiteVersionCommand)
-}
-
-type SuiteVersionCommand struct {
- tempFolderPath string
-}
-
-func NewSuiteVersionCommand(tempFolderPath string) register.TestCaseDescriber {
- return &SuiteVersionCommand{
- tempFolderPath: tempFolderPath,
- }
-}
-
-func (v *SuiteVersionCommand) SetupTest() error {
- return nil
-}
-
-func (v *SuiteVersionCommand) GetFlags() []string {
- return []string{"version"}
-}
-
-func (v *SuiteVersionCommand) Check(stdOut, stderr string) error {
- var re = regexp.MustCompile(`(?m)Teller ([0-9]+)(\.[0-9]+)?(\.[0-9]+)
-Revision [a-z0-9]{40}, date: [0-9]{4}-[0-9]{2}-[0-9]{2}`)
-
- if re.MatchString(stdOut) {
- return nil
- }
-
- return errors.New("invalid teller version")
-}
diff --git a/e2e/tests/yaml.yml b/e2e/tests/yaml.yml
deleted file mode 100644
index 94c6b4e1..00000000
--- a/e2e/tests/yaml.yml
+++ /dev/null
@@ -1,41 +0,0 @@
----
-name: yaml
-command: yaml
-config_content: >
- project: test
-
- providers:
- filesystem:
- env_sync:
- path: {{.Folder}}/settings/test/all
- env:
- FOO:
- path: {{.Folder}}/settings/test/foo
- BAR:
- path: {{.Folder}}/settings/test/bar
-config_file_name: .teller.yml
-init_snapshot:
- - path: settings/test
- file_name: foo
- content: shazam
- - path: settings/test
- file_name: bar
- content: shazam
- - path: settings/test/all
- file_name: secret-a
- content: mailman
- - path: settings/test/all
- file_name: secret-b
- content: shazam
- - path: settings/test/all
- file_name: secret-c
- content: shazam-1
-expected_snapshot:
-expected_stdout: |
- BAR: shazam
- FOO: shazam
- secret-a: mailman
- secret-b: shazam
- secret-c: shazam-1
-
-expected_stderr:
\ No newline at end of file
diff --git a/e2e/testutils/config.go b/e2e/testutils/config.go
deleted file mode 100644
index 791d4fbd..00000000
--- a/e2e/testutils/config.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package testutils
-
-import (
- "os"
- "path/filepath"
- "strings"
- "text/template"
-
- "github.com/karrick/godirwalk"
- "gopkg.in/yaml.v2"
-)
-
-type SnapshotSuite struct {
- Name string `yaml:"name,omitempty"`
- Command string `yaml:"command,omitempty"`
- ConfigFileName string `yaml:"config_file_name,omitempty"`
- Config string `yaml:"config_content,omitempty"`
- InitSnapshot []SnapshotData `yaml:"init_snapshot,omitempty"`
- ExpectedSnapshot []SnapshotData `yaml:"expected_snapshot,omitempty"`
- ExpectedStdOut string `yaml:"expected_stdout,omitempty"`
- ExpectedStdErr string `yaml:"expected_stderr,omitempty"`
- ReplaceStdOutContent []ReplaceStdContent `yaml:"replace_stdout_content,omitempty"`
- ReplaceStdErrContent []ReplaceStdContent `yaml:"replace_stderr_content,omitempty"`
-}
-
-type SnapshotData struct {
- Path string `yaml:"path"`
- FileName string `yaml:"file_name"`
- Content string `yaml:"content"`
-}
-
-type ReplaceStdContent struct {
- Search string `yaml:"search"`
- Replace string `yaml:"replace"`
-}
-
-// GetYmlSnapshotSuites returns list of snapshot suite from yml files
-func GetYmlSnapshotSuites(folder string) ([]*SnapshotSuite, error) {
-
- SnapshotSuite := []*SnapshotSuite{}
- err := godirwalk.Walk(folder, &godirwalk.Options{
- Callback: func(osPathname string, de *godirwalk.Dirent) error {
-
- if strings.HasSuffix(osPathname, "yml") {
- snapshotSuite, err := loadSnapshotSuite(osPathname)
- if err != nil {
- return err
- }
- SnapshotSuite = append(SnapshotSuite, snapshotSuite)
- }
-
- return nil
- },
- Unsorted: true,
- })
-
- return SnapshotSuite, err
-}
-
-// loadSnapshotSuite convert yml file to SnapshotSuite struct
-func loadSnapshotSuite(file string) (*SnapshotSuite, error) {
- ymlTestFile, err := os.ReadFile(file)
- if err != nil {
- return nil, err
- }
- snapshotSuite := &SnapshotSuite{}
- err = yaml.Unmarshal(ymlTestFile, snapshotSuite)
- if err != nil {
- return nil, err
- }
- return snapshotSuite, nil
-}
-
-func (s *SnapshotSuite) CrateConfig(dir string) error {
- f, err := os.Create(filepath.Join(dir, s.ConfigFileName))
-
- if err != nil {
- return err
- }
-
- defer f.Close()
-
- t, err := template.New("t").Parse(s.Config)
- if err != nil {
- return err
- }
-
- type Data struct {
- Folder string
- }
-
- return t.Execute(f, Data{Folder: dir})
-}
-
-// CreateSnapshotData creates filesystem data from the given snapshotData.
-// For example, SnapshotData struct descrive the filesystem structure
-// └── /folder
-//
-// ├── settings/
-// │ ├── billing-svc
-// │ └── all/
-// │ ├── foo
-// └── bar
-func (s *SnapshotSuite) CreateSnapshotData(snapshotData []SnapshotData, dir string) error {
-
- for _, data := range snapshotData {
- err := os.MkdirAll(filepath.Join(dir, data.Path), os.ModePerm)
- if err != nil {
- return err
- }
- err = os.WriteFile(filepath.Join(dir, data.Path, data.FileName), []byte(data.Content), 0644) //nolint
- if err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/e2e/testutils/diff.go b/e2e/testutils/diff.go
deleted file mode 100644
index 2786cf5b..00000000
--- a/e2e/testutils/diff.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package testutils
-
-type Differ interface {
- Diff(dir1, dir2 string, ignores []string) (string, error)
-}
-
-func FolderDiff(d Differ, dir1, dir2 string, ignores []string) (string, error) {
- return d.Diff(dir1, dir2, ignores)
-}
diff --git a/e2e/testutils/exec.go b/e2e/testutils/exec.go
deleted file mode 100644
index 9018d71d..00000000
--- a/e2e/testutils/exec.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package testutils
-
-import (
- "bytes"
- "os/exec"
-)
-
-type DifferExec struct {
-}
-
-func ExecCmd(name string, arg []string, workingDirectory string) (stdout, stderr string, err error) {
-
- var r []string
- for _, str := range arg {
- if str != "" {
- r = append(r, str)
- }
- }
- cmd := exec.Command(name, r...)
- var stdoutBuff bytes.Buffer
- var stderrBuff bytes.Buffer
- cmd.Dir = workingDirectory
- cmd.Stdout = &stdoutBuff
- cmd.Stderr = &stderrBuff
-
- err = cmd.Run()
-
- return stdoutBuff.String(), stderrBuff.String(), err
-}
-
-func NewExecDiffer() Differ {
- return &DifferExec{}
-}
-
-func (de *DifferExec) Diff(dir1, dir2 string, ignores []string) (string, error) {
-
- flags := []string{"-qr"}
- for _, ignore := range ignores {
- flags = append(flags, "-x", ignore)
- }
-
- flags = append(flags, dir1, dir2)
- stdout, _, err := ExecCmd("diff", flags, "")
-
- return stdout, err
-
-}
diff --git a/examples/providers/example.go b/examples/providers/example.go
deleted file mode 100644
index a65189fe..00000000
--- a/examples/providers/example.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package providers
-
-import (
- "fmt"
-
- "github.com/spectralops/teller/pkg/core"
-
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type Example struct {
- logger logging.Logger
-}
-
-//nolint
-
-// func init() {
-// metaInto := core.MetaInfo{
-// Description: "ProviderName",
-// Name: "provider_name",
-// Authentication: "If you have the Consul CLI working and configured, there's no special action to take.\nConfiguration is environment based, as defined by client standard. See variables [here](https://github.com/hashicorp/consul/blob/master/api/api.go#L28).",
-// ConfigTemplate: `
-// provider:
-// env:
-// KEY_EAXMPLE:
-// path: pathToKey
-// `,
-// Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
-// }
-// RegisterProvider(metaInto, NewExample)
-// }
-
-// NewExample creates new provider instance
-func NewExample(logger logging.Logger) (core.Provider, error) {
-
- return &Example{
- logger: logger,
- }, nil
-}
-
-// Name return the provider name
-func (e *Example) Name() string {
- return "Example"
-}
-
-// Put will create a new single entry
-func (e *Example) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", e.Name())
-}
-
-// PutMapping will create a multiple entries
-func (e *Example) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", e.Name())
-}
-
-// GetMapping returns a multiple entries
-func (e *Example) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
-
- return []core.EnvEntry{}, fmt.Errorf("provider %q does not implement write yet", e.Name())
-}
-
-// Get returns a single entry
-func (e *Example) Get(p core.KeyPath) (*core.EnvEntry, error) {
-
- return &core.EnvEntry{}, fmt.Errorf("provider %q does not implement write yet", e.Name())
-}
-
-// Delete will delete entry
-func (e *Example) Delete(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", e.Name())
-}
-
-// DeleteMapping will delete the given path recessively
-func (e *Example) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", e.Name())
-}
diff --git a/fixtures/mirror-drift/source.env b/fixtures/mirror-drift/source.env
deleted file mode 100644
index 921268ad..00000000
--- a/fixtures/mirror-drift/source.env
+++ /dev/null
@@ -1,3 +0,0 @@
-ONE=1
-TWO=2
-THREE=3
\ No newline at end of file
diff --git a/fixtures/mirror-drift/target.env b/fixtures/mirror-drift/target.env
deleted file mode 100644
index 3ce551e3..00000000
--- a/fixtures/mirror-drift/target.env
+++ /dev/null
@@ -1,2 +0,0 @@
-ONE=5
-TWO=2
\ No newline at end of file
diff --git a/fixtures/mirror-drift/teller.yml b/fixtures/mirror-drift/teller.yml
deleted file mode 100644
index 7e30b543..00000000
--- a/fixtures/mirror-drift/teller.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-project: mirror-drift
-
-providers:
- source:
- kind: dotenv
- env_sync:
- path: ../fixtures/mirror-drift/source.env
- target:
- kind: dotenv
- env_sync:
- path: ../fixtures/mirror-drift/target.env
\ No newline at end of file
diff --git a/fixtures/providers-export/providers-meta.json b/fixtures/providers-export/providers-meta.json
deleted file mode 100644
index 91020bc9..00000000
--- a/fixtures/providers-export/providers-meta.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "version": "1.1",
- "providers": {
- "Provider_1": {
- "Description": "Description of Provider 1",
- "Name": "Provider_1",
- "Authentication": "Provider 1 authentication instructions",
- "ConfigTemplate": "Provider 1 config template",
- "Ops": {
- "Delete": false,
- "DeleteMapping": false,
- "Put": true,
- "PutMapping": true,
- "Get": true,
- "GetMapping": true
- }
- },
- "Provider_2": {
- "Description": "Description of Provider 2",
- "Name": "Provider_2",
- "Authentication": "Provider 2 authentication instructions",
- "ConfigTemplate": "Provider 2 config template",
- "Ops": {
- "Delete": false,
- "DeleteMapping": false,
- "Put": true,
- "PutMapping": true,
- "Get": true,
- "GetMapping": true
- }
- }
- }
-}
\ No newline at end of file
diff --git a/fixtures/sync/source.env b/fixtures/sync/source.env
deleted file mode 100644
index 921268ad..00000000
--- a/fixtures/sync/source.env
+++ /dev/null
@@ -1,3 +0,0 @@
-ONE=1
-TWO=2
-THREE=3
\ No newline at end of file
diff --git a/fixtures/sync/target2.env b/fixtures/sync/target2.env
deleted file mode 100644
index 3b11c4ed..00000000
--- a/fixtures/sync/target2.env
+++ /dev/null
@@ -1,4 +0,0 @@
-FOO="2"
-ONE="1"
-THREE="3"
-TWO="2"
\ No newline at end of file
diff --git a/fixtures/sync/teller.yml b/fixtures/sync/teller.yml
deleted file mode 100644
index a142059b..00000000
--- a/fixtures/sync/teller.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-project: sync
-
-providers:
- source:
- kind: dotenv
- env_sync:
- path: ../fixtures/sync/source.env
- target:
- kind: dotenv
- env_sync:
- path: ../fixtures/sync/target.env
- target2:
- kind: dotenv
- env_sync:
- path: ../fixtures/sync/target2.env
\ No newline at end of file
diff --git a/go.mod b/go.mod
deleted file mode 100644
index 16e19fb1..00000000
--- a/go.mod
+++ /dev/null
@@ -1,175 +0,0 @@
-module github.com/spectralops/teller
-
-go 1.18
-
-require (
- cloud.google.com/go v0.78.0
- github.com/1Password/connect-sdk-go v1.2.0
- github.com/AlecAivazis/survey/v2 v2.2.8
- github.com/Azure/azure-sdk-for-go v52.5.0+incompatible
- github.com/Azure/go-autorest/autorest v0.11.18
- github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e
- github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
- github.com/alecthomas/kong v0.2.15
- github.com/aws/aws-sdk-go-v2 v1.24.1
- github.com/aws/aws-sdk-go-v2/config v1.26.5
- github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2
- github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7
- github.com/cloudflare/cloudflare-go v0.25.0
- github.com/cyberark/conjur-api-go v0.7.1
- github.com/dghubble/sling v1.3.0
- github.com/fatih/color v1.14.1
- github.com/golang/mock v1.6.0
- github.com/google/go-github/v43 v43.0.0
- github.com/googleapis/gax-go/v2 v2.0.5
- github.com/gopasspw/gopass v1.15.4
- github.com/hashicorp/consul/api v1.8.1
- github.com/hashicorp/vault/api v1.0.4
- github.com/heroku/heroku-go/v5 v5.2.1
- github.com/jftuga/ellipsis v1.0.0
- github.com/joho/godotenv v1.3.0
- github.com/karrick/godirwalk v1.16.1
- github.com/mattn/lastpass-go v0.0.0-20160926001517-82bef8502f75
- github.com/mitchellh/go-homedir v1.1.0
- github.com/samber/lo v1.11.0
- github.com/sirupsen/logrus v1.7.0
- github.com/sosedoff/ansible-vault-go v0.2.0
- github.com/stretchr/testify v1.8.1
- github.com/testcontainers/testcontainers-go v0.10.1-0.20210331130832-54854fb15ccb
- github.com/tobischo/gokeepasslib/v3 v3.2.5
- go.etcd.io/etcd/api/v3 v3.5.0-alpha.0
- go.etcd.io/etcd/client/v3 v3.5.0-alpha.0
- go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0
- golang.org/x/crypto v0.8.0
- golang.org/x/oauth2 v0.5.0
- google.golang.org/api v0.40.0
- google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c
- gopkg.in/yaml.v2 v2.4.0
- gopkg.in/yaml.v3 v3.0.1
-)
-
-require github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
-
-require (
- filippo.io/age v1.1.1 // indirect
- filippo.io/edwards25519 v1.0.0 // indirect
- github.com/Azure/go-autorest v14.2.0+incompatible // indirect
- github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
- github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 // indirect
- github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
- github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
- github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
- github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
- github.com/Azure/go-autorest/logger v0.2.1 // indirect
- github.com/Azure/go-autorest/tracing v0.6.0 // indirect
- github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 // indirect
- github.com/Microsoft/hcsshim v0.8.15 // indirect
- github.com/ProtonMail/go-crypto v0.0.0-20230201104953-d1d05f4e2bfb // indirect
- github.com/aead/argon2 v0.0.0-20180111183520-a87724528b07 // indirect
- github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
- github.com/alecthomas/colour v0.1.0 // indirect
- github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2 // indirect
- github.com/alessio/shellescape v1.4.1 // indirect
- github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
- github.com/atotto/clipboard v0.1.4 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
- github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
- github.com/aws/smithy-go v1.19.0 // indirect
- github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
- github.com/blang/semver/v4 v4.0.0 // indirect
- github.com/caspr-io/yamlpath v0.0.0-20200722075116-502e8d113a9b // indirect
- github.com/cenkalti/backoff v2.2.1+incompatible // indirect
- github.com/cloudflare/circl v1.3.2 // indirect
- github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102 // indirect
- github.com/containerd/containerd v1.5.0-beta.1 // indirect
- github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 // indirect
- github.com/coreos/go-semver v0.3.0 // indirect
- github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect
- github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
- github.com/danieljoos/wincred v1.1.2 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/dimchansky/utfbom v1.1.1 // indirect
- github.com/docker/distribution v2.7.1+incompatible // indirect
- github.com/docker/docker v20.10.5+incompatible // indirect
- github.com/docker/go-connections v0.4.0 // indirect
- github.com/docker/go-units v0.4.0 // indirect
- github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
- github.com/godbus/dbus/v5 v5.1.0 // indirect
- github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
- github.com/golang/protobuf v1.5.2 // indirect
- github.com/golang/snappy v0.0.1 // indirect
- github.com/google/go-cmp v0.5.9 // indirect
- github.com/google/go-github v17.0.0+incompatible // indirect
- github.com/google/go-querystring v1.1.0 // indirect
- github.com/google/uuid v1.2.0 // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
- github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
- github.com/hashicorp/go-hclog v0.12.0 // indirect
- github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/hashicorp/go-retryablehttp v0.5.4 // indirect
- github.com/hashicorp/go-rootcerts v1.0.2 // indirect
- github.com/hashicorp/go-sockaddr v1.0.2 // indirect
- github.com/hashicorp/golang-lru v0.5.4 // indirect
- github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/hashicorp/serf v0.9.5 // indirect
- github.com/hashicorp/vault/sdk v0.1.13 // indirect
- github.com/inconshreveable/mousetrap v1.0.0 // indirect
- github.com/jmespath/go-jmespath v0.4.0 // indirect
- github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
- github.com/keeper-security/secrets-manager-go/core v1.6.2
- github.com/mattn/go-colorable v0.1.13 // indirect
- github.com/mattn/go-isatty v0.0.17 // indirect
- github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
- github.com/miekg/dns v1.1.35 // indirect
- github.com/mitchellh/mapstructure v1.1.2 // indirect
- github.com/moby/sys/mount v0.2.0 // indirect
- github.com/moby/sys/mountinfo v0.4.0 // indirect
- github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
- github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.0.1 // indirect
- github.com/opencontainers/runc v1.0.0-rc93 // indirect
- github.com/opentracing/opentracing-go v1.2.0 // indirect
- github.com/pborman/uuid v1.2.0 // indirect
- github.com/pierrec/lz4 v2.0.5+incompatible // indirect
- github.com/pkg/errors v0.9.1 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/rogpeppe/go-internal v1.9.0 // indirect
- github.com/rs/zerolog v1.29.0 // indirect
- github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/ryanuber/go-glob v1.0.0 // indirect
- github.com/sergi/go-diff v1.1.0 // indirect
- github.com/spf13/cobra v0.0.5 // indirect
- github.com/spf13/pflag v1.0.5 // indirect
- github.com/twpayne/go-pinentry v0.2.0 // indirect
- github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect
- github.com/uber/jaeger-lib v2.4.0+incompatible // indirect
- github.com/urfave/cli/v2 v2.24.3 // indirect
- github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
- github.com/zalando/go-keyring v0.2.2 // indirect
- go.opencensus.io v0.22.5 // indirect
- go.uber.org/atomic v1.10.0 // indirect
- go.uber.org/multierr v1.9.0 // indirect
- go.uber.org/zap v1.16.0 // indirect
- golang.org/x/exp v0.0.0-20230212135524-a684f29349b6 // indirect
- golang.org/x/net v0.9.0 // indirect
- golang.org/x/sync v0.1.0 // indirect
- golang.org/x/sys v0.7.0 // indirect
- golang.org/x/term v0.7.0 // indirect
- golang.org/x/text v0.9.0 // indirect
- golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
- google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/grpc v1.35.0 // indirect
- google.golang.org/protobuf v1.28.1 // indirect
- gopkg.in/gookit/color.v1 v1.1.6 // indirect
- gopkg.in/square/go-jose.v2 v2.3.1 // indirect
-)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index d4bc7073..00000000
--- a/go.sum
+++ /dev/null
@@ -1,1414 +0,0 @@
-bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
-cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
-cloud.google.com/go v0.78.0 h1:oKpsiyKMfVpwR3zSAkQixGzlVE5ovitBuO0qSmCf0bI=
-cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg=
-filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE=
-filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
-filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
-github.com/1Password/connect-sdk-go v1.2.0 h1:WbIvmbDUpA89nyH0l3LF2iRSFJAv86d2D7IjVNjw6iw=
-github.com/1Password/connect-sdk-go v1.2.0/go.mod h1:qK2bF/GweAq812xj+HGfbauaE6cKX1MXfKhpAvoHEq8=
-github.com/AlecAivazis/survey/v2 v2.0.8/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
-github.com/AlecAivazis/survey/v2 v2.2.8 h1:TgxCwybKdBckmC+/P9/5h49rw/nAHe/itZL0dgHs+Q0=
-github.com/AlecAivazis/survey/v2 v2.2.8/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk=
-github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go v52.5.0+incompatible h1:/NLBWHCnIHtZyLPc1P7WIqi4Te4CC23kIQyK3Ep/7lA=
-github.com/Azure/azure-sdk-for-go v52.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
-github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
-github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
-github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM=
-github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
-github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
-github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 h1:8DQB8yl7aLQuP+nuR5e2RO6454OvFlSTXXaNHshc16s=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
-github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
-github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
-github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
-github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
-github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
-github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
-github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
-github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
-github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e h1:X5yHJzv4R12/zF9PR6OlroESbv6Bbz+L5LlZWY+qGkw=
-github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e/go.mod h1:BYiWQL3TJGl8TjMYVeWYxJJK46arOR4J/2urnIJE/jE=
-github.com/HdrHistogram/hdrhistogram-go v1.0.1 h1:GX8GAYDuhlFQnI2fRDHQhTlkHMz8bEn0jTI6LJU0mpw=
-github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnloDp7xxV0YvstAE7nKTaM=
-github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
-github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 h1:mw6pDQqv38/WGF1cO/jF5t/jyAJ2yi7CmtFLLO5tGFI=
-github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
-github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
-github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
-github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
-github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
-github.com/Microsoft/hcsshim v0.8.15 h1:Aof83YILRs2Vx3GhHqlvvfyx1asRJKMFIMeVlHsZKtI=
-github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
-github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
-github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
-github.com/ProtonMail/go-crypto v0.0.0-20230201104953-d1d05f4e2bfb h1:Vx1Bw/nGULx+FuY7Sw+8ZDpOx9XOdA+mOfo678SqkbU=
-github.com/ProtonMail/go-crypto v0.0.0-20230201104953-d1d05f4e2bfb/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
-github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
-github.com/aead/argon2 v0.0.0-20180111183520-a87724528b07 h1:i9/M2RadeVsPBMNwXFiaYkXQi9lY9VuZeI4Onavd3pA=
-github.com/aead/argon2 v0.0.0-20180111183520-a87724528b07/go.mod h1:Tnm/osX+XXr9R+S71o5/F0E60sRkPVALdhWw25qPImQ=
-github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
-github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
-github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
-github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
-github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
-github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
-github.com/alecthomas/kong v0.2.15 h1:HP3K1XuFn0wGSWFGVW67V+65tXw/Ht8FDYiLNAuX2Ug=
-github.com/alecthomas/kong v0.2.15/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
-github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2 h1:G5TeG64Ox4OWq2YwlsxS7nOedU8vbGgNRTRDAjGvDCk=
-github.com/alecthomas/repr v0.0.0-20201120212035-bb82daffcca2/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
-github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
-github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
-github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
-github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
-github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
-github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
-github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
-github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2 h1:A5sGOT/mukuU+4At1vkSIWAN8tPwPCoYZBp7aruR540=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2/go.mod h1:qutL00aW8GSo2D0I6UEOqMvRS3ZyuBrOC1BLe5D2jPc=
-github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
-github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
-github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
-github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
-github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
-github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
-github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
-github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
-github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE=
-github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
-github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
-github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
-github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
-github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
-github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
-github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
-github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
-github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
-github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
-github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
-github.com/caspr-io/yamlpath v0.0.0-20200722075116-502e8d113a9b h1:2K3B6Xm7/lnhOugeGB3nIk50bZ9zhuJvXCEfUuL68ik=
-github.com/caspr-io/yamlpath v0.0.0-20200722075116-502e8d113a9b/go.mod h1:4rP9T6iHCuPAIDKdNaZfTuuqSIoQQvFctNWIAUI1rlg=
-github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
-github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
-github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
-github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
-github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk=
-github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
-github.com/cloudflare/cloudflare-go v0.25.0 h1:GwyKwGq8ciGNjKiTpjj6RvU3+uJNuPBNjlUkeQRx0yU=
-github.com/cloudflare/cloudflare-go v0.25.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
-github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=
-github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
-github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
-github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
-github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
-github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102 h1:Qf4HiqfvmB7zS6scsmNgTLmByHbq8n9RTF39v+TzP7A=
-github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
-github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
-github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
-github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
-github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
-github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.5.0-beta.1 h1:IK6yirB4X7wpKyFSikWiT++nZsyIxGAAgNEv3fEGuls=
-github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
-github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
-github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8dskcM7wQ28gONkukbQkM4qpj4RnYbpFzrI=
-github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
-github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
-github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
-github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
-github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
-github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=
-github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
-github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
-github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
-github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=
-github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
-github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
-github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
-github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
-github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
-github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
-github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
-github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
-github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
-github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=
-github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
-github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
-github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=
-github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
-github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
-github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U=
-github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cyberark/conjur-api-go v0.7.1 h1:nMWB1s0d8a8fGTeAkQIb8KLY9kTFwevgNg6tE2lMcao=
-github.com/cyberark/conjur-api-go v0.7.1/go.mod h1:HZ5RoBhAB2KwnxyXbQ29DwpviRVg7SMRq7QhwtFjN3Q=
-github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
-github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
-github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
-github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
-github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
-github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
-github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
-github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
-github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU=
-github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY=
-github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
-github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
-github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
-github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
-github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
-github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
-github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v20.10.5+incompatible h1:o5WL5onN4awYGwrW7+oTn5x9AF2prw7V0Ox8ZEkoCdg=
-github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
-github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
-github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
-github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
-github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
-github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
-github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
-github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
-github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
-github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
-github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
-github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
-github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
-github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
-github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
-github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
-github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
-github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
-github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
-github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
-github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
-github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
-github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
-github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
-github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
-github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
-github.com/google/go-github/v43 v43.0.0 h1:y+GL7LIsAIF2NZlJ46ZoC/D1W1ivZasT0lnWHMYPZ+U=
-github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
-github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
-github.com/gopasspw/gopass v1.15.4 h1:ebs6oQgtxrpvM9IaXmG67fd69+kczuF37PBcUutWzaw=
-github.com/gopasspw/gopass v1.15.4/go.mod h1:u98G2rOj5h92lLq9ltKUfZOHpjgQoS7ZgG6kqIcmQvQ=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
-github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
-github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
-github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
-github.com/hashicorp/consul/api v1.8.1 h1:BOEQaMWoGMhmQ29fC26bi0qb7/rId9JzZP2V0Xmx7m8=
-github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk=
-github.com/hashicorp/consul/sdk v0.7.0 h1:H6R9d008jDcHPQPAqPNuydAshJ4v5/8URdFnUvK/+sc=
-github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=
-github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
-github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
-github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
-github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
-github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE=
-github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
-github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
-github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
-github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
-github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
-github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
-github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
-github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
-github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
-github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
-github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU=
-github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
-github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8=
-github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
-github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
-github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
-github.com/heroku/heroku-go/v5 v5.2.1 h1:5g379GyHuOI3qhb1ujFwQ13Kjt96M+KMkV8s7omg+do=
-github.com/heroku/heroku-go/v5 v5.2.1/go.mod h1:d+1QrZyjbnQJG1f8xIoVvMQRFLt3XRVZOdlm26Sr73U=
-github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
-github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/interagent/schematic v0.0.0-20180830170528-b5e8ba7aa570/go.mod h1:4X9u5iNUePRrRDdwjok6skjlQBXTcNfWa4C3uS1+5SQ=
-github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
-github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
-github.com/jftuga/ellipsis v1.0.0 h1:ERi1XBFERM2YpadkvM1P9bxQKgOC40Hr6TCKkvLBDtY=
-github.com/jftuga/ellipsis v1.0.0/go.mod h1:phJ3vQPi8MPrtRKdo0aESNJdw56f09SLVX0k/FY+jr0=
-github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
-github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
-github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/keeper-security/secrets-manager-go/core v1.6.2 h1:bRZUJI/s5WwVbceSNlKyKqYuBNKkZCyNPH4lU2GYiF0=
-github.com/keeper-security/secrets-manager-go/core v1.6.2/go.mod h1:dtlaeeds9+SZsbDAZnQRsDSqEAK9a62SYtqhNql+VgQ=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4=
-github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
-github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
-github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
-github.com/mattn/lastpass-go v0.0.0-20160926001517-82bef8502f75 h1:zeHg49NDkgehebyDdIxz8fqUgG/rmuVs1A0lOInmRJg=
-github.com/mattn/lastpass-go v0.0.0-20160926001517-82bef8502f75/go.mod h1:QuMpIepjU37tLzqJ6/kNGOr8x9obTTL5gNi1DPUSY3o=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
-github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
-github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
-github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
-github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
-github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
-github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
-github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM=
-github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM=
-github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM=
-github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
-github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
-github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
-github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
-github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
-github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
-github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
-github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
-github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
-github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
-github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
-github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM=
-github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
-github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
-github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
-github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
-github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
-github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
-github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
-github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
-github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
-github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
-github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
-github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
-github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
-github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
-github.com/samber/lo v1.11.0 h1:JfeYozXL1xfkhRUFOfH13ociyeiLSC/GRJjGKI668xM=
-github.com/samber/lo v1.11.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
-github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
-github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
-github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
-github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
-github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
-github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/sosedoff/ansible-vault-go v0.2.0 h1:XqkBdqbXgTuFQ++NdrZvSdUTNozeb6S3V5x7FVs17vg=
-github.com/sosedoff/ansible-vault-go v0.2.0/go.mod h1:wMU54HNJfY0n0KIgbpA9m15NBfaUDlJrAsaZp0FwzkI=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
-github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
-github.com/testcontainers/testcontainers-go v0.10.1-0.20210331130832-54854fb15ccb h1:IK+y9L4aToxLHSy0na5lIGr9lP51Zip/si8lgauVo/E=
-github.com/testcontainers/testcontainers-go v0.10.1-0.20210331130832-54854fb15ccb/go.mod h1:zFYk0JndthnMHEwtVRHCpLwIP/Ik1G7mvIAQ2MdZ+Ig=
-github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
-github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tobischo/gokeepasslib/v3 v3.2.5 h1:BW0HorAp/Eo5XsjA3pgyrLaRzn9J5tGq8NBOADpE39g=
-github.com/tobischo/gokeepasslib/v3 v3.2.5/go.mod h1:iwxOzUuk/ccA0mitrFC4MovT1p0IRY8EA35L4u1x/ug=
-github.com/twpayne/go-pinentry v0.2.0 h1:hS5NEJiilop9xP9pBX/1NYduzDlGGMdg1KamTBTrOWw=
-github.com/twpayne/go-pinentry v0.2.0/go.mod h1:r6buhMwARxnnL0VRBqfd1tE6Fadk1kfP00GRMutEspY=
-github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
-github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ=
-github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
-github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/urfave/cli/v2 v2.24.3 h1:7Q1w8VN8yE0MJEHP06bv89PjYsN4IHWED2s1v/Zlfm0=
-github.com/urfave/cli/v2 v2.24.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
-github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
-github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
-github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
-github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
-github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
-github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
-github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
-github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
-github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
-github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
-github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
-github.com/zalando/go-keyring v0.1.1-0.20210112083600-4d37811583ad/go.mod h1:OIC+OZ28XbmwFxU/Rp9V7eKzZjamBJwRzC8UFJH9+L8=
-github.com/zalando/go-keyring v0.2.2 h1:f0xmpYiSrHtSNAVgwip93Cg8tuF45HJM6rHq/A5RI/4=
-github.com/zalando/go-keyring v0.2.2/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
-go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
-go.etcd.io/etcd/api/v3 v3.5.0-alpha.0 h1:+e5nrluATIy3GP53znpkHMFzPTHGYyzvJGFCbuI6ZLc=
-go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw=
-go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 h1:dr1EOILak2pu4Nf5XbRIOCNIBjcz6UmkQd7hHRXwxaM=
-go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8=
-go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 h1:3yLUEC0nFCxw/RArImOyRUI4OAFbg4PFpBbAhSNzKNY=
-go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY=
-go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
-go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
-golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
-golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230212135524-a684f29349b6 h1:Ic9KukPQ7PegFzHckNiMTQXGgEszA7mY2Fn4ZMtnMbw=
-golang.org/x/exp v0.0.0-20230212135524-a684f29349b6/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
-golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
-golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
-google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
-google.golang.org/api v0.40.0 h1:uWrpz12dpVPn7cojP82mk02XDgTJLDPc2KbVTxrWb4A=
-google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c h1:7A9LQhrZmuCPI79/sYSbscFqBp4XFYf6oaIQuV1xji4=
-google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
-google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
-google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
-gopkg.in/gookit/color.v1 v1.1.6 h1:5fB10p6AUFjhd2ayq9JgmJWr9WlTrguFdw3qlYtKNHk=
-gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
-gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
-gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
-gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
-gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
-k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
-k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
-k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
-k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
-k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
-k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
-k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
-k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/install.ps1 b/install.ps1
new file mode 100644
index 00000000..284377e4
--- /dev/null
+++ b/install.ps1
@@ -0,0 +1,55 @@
+#!/usr/bin/env pwsh
+# Copyright 2018 the Deno authors. All rights reserved. MIT license.
+# TODO(everyone): Keep this script simple and easily auditable.
+
+$ErrorActionPreference = 'Stop'
+
+$Project = "teller"
+$Repo = "tellerops/teller"
+
+if ($v) {
+ $Version = "v${v}"
+}
+if ($args.Length -eq 1) {
+ $Version = $args.Get(0)
+}
+
+$Install = $env:BP_INSTALL
+$BinDir = if ($Install) {
+ "$Install"
+} else {
+ "$Home\.$Project-bin"
+}
+
+$Zip = "$BinDir\$Project.zip"
+$Exe = "$BinDir\$Project.exe"
+$Target = 'x86_64-windows'
+
+# GitHub requires TLS 1.2
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+$Uri = if (!$Version) {
+ "https://github.com/$Repo/releases/latest/download/${Project}-${Target}.zip"
+} else {
+ "https://github.com/$Repo/releases/download/${Version}/$Project-${Target}.zip"
+}
+
+if (!(Test-Path $BinDir)) {
+ New-Item $BinDir -ItemType Directory | Out-Null
+}
+
+curl.exe -Lo $Zip $Uri
+
+tar.exe xf $Zip -C $BinDir --strip-components 1
+
+Remove-Item $Zip
+
+$User = [EnvironmentVariableTarget]::User
+$Path = [Environment]::GetEnvironmentVariable('Path', $User)
+if (!(";$Path;".ToLower() -like "*;$BinDir;*".ToLower())) {
+ [Environment]::SetEnvironmentVariable('Path', "$Path;$BinDir", $User)
+ $Env:Path += ";$BinDir"
+}
+
+Write-Output "$Project was installed successfully to $Exe"
+Write-Output "Run with '--help' to get started"
diff --git a/install.sh b/install.sh
new file mode 100644
index 00000000..cf22ea0f
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+# Copyright 2019 the Deno authors. All rights reserved. MIT license.
+# Copyright 2022 the Backpack authors. All rights reserved. MIT license.
+# TODO(everyone): Keep this script simple and easily auditable.
+
+set -e
+
+# assumes bin name same as project name
+project="teller"
+bin_name="teller"
+repo="tellerops/teller"
+
+if [ "$OS" = "Windows_NT" ]; then
+ target="x86_64-windows"
+else
+ case $(uname -sm) in
+ "Darwin x86_64") target="x86_64-macos" ;;
+ "Darwin arm64") target="aarch64-macos" ;;
+ *) target="x86_64-linux" ;;
+ esac
+fi
+
+if [ $# -eq 0 ]; then
+ uri="https://github.com/${repo}/releases/latest/download/${project}-${target}.tar.xz"
+else
+ uri="https://github.com/${repo}/releases/download/${1}/${project}-${target}.tar.xz"
+fi
+
+install="${PROJ_INSTALL:-$HOME/.$project-bin}"
+bin_dir="$install"
+exe="$bin_dir/$bin_name"
+
+if [ ! -d "$bin_dir" ]; then
+ mkdir -p "$bin_dir"
+fi
+
+curl --fail --location --progress-bar --output "$exe.tar.xz" "$uri"
+tar zxf "$exe.tar.xz" -C "$bin_dir" --strip-components 1
+chmod +x "$exe"
+rm "$exe.tar.xz"
+
+echo "$project was installed successfully to $exe"
+if command -v $exe >/dev/null; then
+ echo "Run '$exe --help' to get started"
+else
+ case $SHELL in
+ /bin/zsh) shell_profile=".zshrc" ;;
+ *) shell_profile=".bashrc" ;;
+ esac
+ echo "Manually add the directory to your \$HOME/$shell_profile (or similar)"
+ echo " export PROJ_INSTALL=\"$install\""
+ echo " export PATH=\"\$PROJ_INSTALL:\$PATH\""
+ echo "Run '$exe --help' to get started"
+fi
diff --git a/main.go b/main.go
deleted file mode 100644
index 8d2ca618..00000000
--- a/main.go
+++ /dev/null
@@ -1,330 +0,0 @@
-package main
-
-import (
- "errors"
- "fmt"
- "io"
- "io/fs"
- "os"
-
- "github.com/alecthomas/kong"
- "github.com/spectralops/teller/pkg"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-var CLI struct {
- Config string `short:"c" help:"Path to teller YAML file"`
- LogLevel string `short:"l" help:"Application log level"`
-
- Run struct {
- Redact bool `optional name:"redact" help:"Redact output of the child process"`
- Cmd []string `arg name:"cmd" help:"Command to execute"`
- } `cmd help:"Run a command"`
-
- Version struct {
- } `cmd aliases:"v" help:"Teller version"`
-
- New struct {
- } `cmd help:"Create a new teller configuration file"`
-
- Show struct {
- } `cmd help:"Print in a human friendly, secure format"`
-
- Providers struct {
- Path string `optional name:"path" help:"Path for saving providers JSON file"`
- } `cmd help:"Export providers metadata to a local JSON file" hidden: ""`
-
- Yaml struct {
- } `cmd help:"Print values in a YAML format (suitable for GCloud)"`
-
- JSON struct {
- } `cmd help:"Print values in a JSON format"`
-
- Sh struct {
- } `cmd help:"Print ready to be eval'd exports for your shell"`
-
- Env struct {
- } `cmd help:"Print in a .env format for Docker and others"`
-
- Template struct {
- TemplatePath string `arg name:"template_path" help:"Path to the template source (Go template format)"`
- Out string `arg name:"out" help:"Output file"`
- } `cmd help:"Inject vars from a template by given source path (single file or folder)"`
-
- Redact struct {
- In string `optional name:"in" help:"Input file"`
- Out string `optional name:"out" help:"Output file"`
- } `cmd help:"Redacts secrets from a process output"`
-
- Scan struct {
- Path string `arg optional name:"path" help:"Scan root, default: '.'"`
- Silent bool `optional name:"silent" help:"No text, just exit code"`
- } `cmd help:"Scans your codebase for sensitive keys"`
-
- GraphDrift struct {
- Providers []string `arg optional name:"providers" help:"A list of providers to check for drift"`
- } `cmd help:"Detect secret and value drift between providers"`
-
- Put struct {
- Kvs map[string]string `arg name:"kvs" help:"A list of key/value pairs, where key is from your tellerfile mapping"`
- Providers []string `name:"providers" help:"A list of providers to put the new value into"`
- Sync bool `optional name:"sync" help:"Sync all given k/vs to the env_sync key"`
- Path string `optional name:"path" help:"Take literal path and not from config"`
- } `cmd help:"Put a new value"`
-
- Copy struct {
- From string `name:"from" help:"A provider name to sync from"`
- To []string `name:"to" help:"A list of provider names to copy values from the source provider to"`
- Sync bool `optional name:"sync" help:"Sync all given k/vs to the env_sync key"`
- } `cmd help:"Sync data from a source provider directly to multiple target providers"`
-
- MirrorDrift struct {
- Source string `name:"source" help:"A source to check drift against"`
- Target string `name:"target" help:"A target to check against source"`
- } `cmd help:"Check same-key (mirror) value drift between source and target"`
-
- Delete struct {
- Keys []string `arg optional name:"keys" help:"A list of keys, where key is from your tellerfile mapping"`
- Providers []string `name:"providers" help:"A list of providers to delete the key from"`
- Path string `optional name:"path" help:"Take literal path and not from config"`
- AllKeys bool `optional name:"all-keys" help:"Deletes all keys for a given path. Applicable only when used together with the 'path' flag"`
- } `cmd help:"Delete a secret"`
-}
-
-var (
- version = "dev"
- commit = "none"
- date = "unknown"
- defaultLogLevel = "error"
-)
-
-//nolint
-func main() {
- //print help if no command given
- if len(os.Args) < 2 {
- os.Args = append(os.Args, "--help")
- }
- ctx := kong.Parse(&CLI)
-
- logger := logging.GetRoot()
- if CLI.LogLevel != "" {
- defaultLogLevel = CLI.LogLevel
- }
- logger.SetLevel(defaultLogLevel)
-
- // below commands don't require a tellerfile
- //nolint
- switch ctx.Command() {
- case "version":
- fmt.Printf("Teller %v\n", version)
- fmt.Printf("Revision %v, date: %v\n", commit, date)
- os.Exit(0)
- case "providers":
- providersMetaList := providers.GetAllProvidersMeta()
- providersMetaJSON, err := providers.GenerateProvidersMetaJSON(version, providersMetaList)
- if err != nil {
- logger.WithError(err).Fatal("could not get providers meta, %s", err)
- }
-
- saveErr := utils.WriteFileInPath("providers-meta.json", CLI.Providers.Path, []byte(providersMetaJSON))
- if saveErr != nil {
- logger.WithError(err).Fatal("could not save providers meta to a local file, %s", saveErr)
- }
- fmt.Printf("Providers meta has been exported successfully\n")
-
- os.Exit(0)
- }
-
- //
- // load or create new file
- //
- const (
- defaultTellerFile = ".teller.yml"
- // Alternative default teller file, it uses official YAML extension
- // See https://github.com/tellerops/teller/issues/162
- secondDefaultTellerFile = ".teller.yaml"
- )
-
- telleryml := defaultTellerFile
- if CLI.Config != "" {
- telleryml = CLI.Config
- }
-
- if ctx.Command() == "new" {
- teller := pkg.Teller{
- Porcelain: &pkg.Porcelain{Out: os.Stderr},
- Logger: logger,
- }
- if _, err := os.Stat(telleryml); err == nil && !teller.Porcelain.AskForConfirmation(fmt.Sprintf("The file %s already exists. Do you want to override the configuration with new settings?", telleryml)) {
- os.Exit(0)
- }
-
- err := teller.SetupNewProject(telleryml)
- if err != nil {
- logger.WithError(err).Fatal("could not create configuration")
- }
- os.Exit(0)
- }
-
- tlrfile, err := pkg.NewTellerFile(telleryml)
- if isDefaultFilePathErr(CLI.Config, err) {
- tlrfile, err = pkg.NewTellerFile(secondDefaultTellerFile)
- }
- if err != nil {
- logger.WithError(err).WithField("file", telleryml).Fatal("could not read file")
- }
-
- teller := pkg.NewTeller(tlrfile, CLI.Run.Cmd, CLI.Run.Redact, logger)
-
- // below commands don't require collecting
- //nolint
- switch ctx.Command() {
- case "put ":
- err := teller.Put(CLI.Put.Kvs, CLI.Put.Providers, CLI.Put.Sync, CLI.Put.Path)
- if err != nil {
- logger.WithError(err).Fatal("put command field")
- }
- os.Exit(0)
- case "copy":
- err := teller.Sync(CLI.Copy.From, CLI.Copy.To, CLI.Copy.Sync)
- if err != nil {
- logger.WithError(err).WithFields(map[string]interface{}{
- "from": CLI.Copy.From,
- "to": CLI.Copy.To,
- "sync_flag": CLI.Copy.Sync,
- }).Fatal("could not copy data between providers")
- }
- os.Exit(0)
- case "mirror-drift":
- drifts, err := teller.MirrorDrift(CLI.MirrorDrift.Source, CLI.MirrorDrift.Target)
- if err != nil {
- logger.WithError(err).Fatal("mirror-drift command field")
- }
- if len(drifts) > 0 {
- teller.Porcelain.PrintDrift(drifts)
- os.Exit(1)
- }
- os.Exit(0)
- case "delete":
- err := teller.Delete(CLI.Delete.Keys, CLI.Delete.Providers, CLI.Delete.Path, CLI.Delete.AllKeys)
- if err != nil {
- logger.WithError(err).Fatal("could not delete key")
- }
- os.Exit(0)
- case "delete ":
- err := teller.Delete(CLI.Delete.Keys, CLI.Delete.Providers, CLI.Delete.Path, CLI.Delete.AllKeys)
- if err != nil {
- logger.WithError(err).Fatal("could not delete keys")
- }
- os.Exit(0)
- }
- // collecting
-
- err = teller.Collect()
- if err != nil {
- logger.WithError(err).Fatal("could not load all variables from the given existing providers")
- }
-
- // all of the below require a tellerfile
- switch ctx.Command() {
- case "run ":
- if len(CLI.Run.Cmd) < 1 {
- logger.Fatal("Error: No command given")
- }
- teller.Exec()
-
- case "graph-drift ":
- fallthrough
- case "graph-drift":
- drifts := teller.Drift(CLI.GraphDrift.Providers)
- if len(drifts) > 0 {
- teller.Porcelain.PrintDrift(drifts)
- os.Exit(1)
- }
-
- case "redact":
- // redact (stdin)
- // redact --in FILE --out FOUT
- // redact --in FILE (stdout)
- var fin io.Reader = os.Stdin
- var fout io.Writer = os.Stdout
-
- if CLI.Redact.In != "" {
- f, err := os.Open(CLI.Redact.In)
- if err != nil {
- logger.WithError(err).Fatal("could not open file")
- }
- fin = f
- }
-
- if CLI.Redact.Out != "" {
- f, err := os.Create(CLI.Redact.Out)
- if err != nil {
- logger.WithError(err).Fatal("could not create file")
- }
-
- fout = f
- }
-
- if err := teller.RedactLines(fin, fout); err != nil {
- logger.WithError(err).Fatal("could not redact lines")
- }
-
- case "sh":
- fmt.Print(teller.ExportEnv())
-
- case "env":
- fmt.Print(teller.ExportDotenv())
-
- case "yaml":
- out, err := teller.ExportYAML()
- if err != nil {
- logger.WithError(err).Fatal("could not export to YAML")
- }
- fmt.Print(out)
-
- case "json":
- out, err := teller.ExportJSON()
- if err != nil {
- logger.WithError(err).Fatal("could not export to JSON")
- }
- fmt.Print(out)
-
- case "show":
- teller.PrintEnvKeys()
-
- case "scan":
- findings, err := teller.Scan(CLI.Scan.Path, CLI.Scan.Silent)
-
- if err != nil {
- logger.WithError(err).WithField("path", CLI.Scan.Path).Fatal("scan error")
- }
- num := len(findings)
- if num > 0 {
- os.Exit(1)
- }
-
- case "template ":
- err := teller.Template(CLI.Template.TemplatePath, CLI.Template.Out)
- if err != nil {
- logger.WithError(err).WithFields(map[string]interface{}{
- "template_path": CLI.Template.TemplatePath,
- "template_output": CLI.Template.Out,
- }).Fatal("could not populate template")
- }
-
- default:
- println(ctx.Command())
- teller.PrintEnvKeys()
- }
-}
-
-func isDefaultFilePathErr(config string, err error) bool {
- // Ignore if explicitly set to '.teller.yml'.
- if config != "" {
- return false
- }
- return errors.Is(err, fs.ErrNotExist)
-}
diff --git a/media/cover.png b/media/cover.png
deleted file mode 100644
index c37617d2..00000000
Binary files a/media/cover.png and /dev/null differ
diff --git a/media/teller.gif b/media/teller.gif
deleted file mode 100644
index 1183a9f9..00000000
Binary files a/media/teller.gif and /dev/null differ
diff --git a/package.json b/package.json
deleted file mode 100644
index e0e0f0ff..00000000
--- a/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "private": true,
- "name": "teller",
- "version": "1.0.0",
- "main": "index.js",
- "license": "MIT",
- "scripts": {
- "readme": "doctoc --maxlevel 2 README.md"
- },
- "devDependencies": {
- "doctoc": "^2.0.0"
- }
-}
\ No newline at end of file
diff --git a/pkg/core/populate.go b/pkg/core/populate.go
deleted file mode 100644
index c5123807..00000000
--- a/pkg/core/populate.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package core
-
-import (
- "fmt"
- "os"
- "strings"
-)
-
-const (
- populateFromEnvironment = "env:"
- populateWithDefault = ","
-)
-
-type Opts map[string]string
-type Populate struct {
- rules map[string]string
-}
-
-func NewPopulate(opts Opts) *Populate {
- rules := make(map[string]string)
- for k, v := range opts {
- val := v
- if strings.HasPrefix(v, populateFromEnvironment) {
- evar := strings.TrimPrefix(v, populateFromEnvironment)
- evar, defaultValue := parseDefaultValue(evar)
- val = os.Getenv(evar)
- if val == "" {
- val = defaultValue
- }
- }
- rules[fmt.Sprintf("{{%s}}", k)] = val
- }
-
- return &Populate{
- rules: rules,
- }
-}
-
-func (p *Populate) FindAndReplace(path string) string {
- populated := path
- for k, v := range p.rules {
- populated = strings.ReplaceAll(populated, k, v)
- }
- return populated
-}
-
-func (p *Populate) KeyPath(kp KeyPath) KeyPath {
- return kp.SwitchPath(p.FindAndReplace(kp.Path))
-}
-
-// parseDefaultValue returns that field name and the default value if `populateWithDefault` was found
-// Example 1: FOO,BAR -> the function return FOO, BAR
-// Example 2: FOO -> the function return FOO, "" (empty value)
-func parseDefaultValue(evar string) (key, defaultValue string) {
- if strings.Contains(evar, populateWithDefault) {
- data := strings.SplitN(evar, populateWithDefault, 2) //nolint
- if len(data) == 2 { //nolint
- return data[0], strings.TrimSpace(data[1])
- }
- }
- return evar, ""
-}
diff --git a/pkg/core/populate_test.go b/pkg/core/populate_test.go
deleted file mode 100644
index 7362c721..00000000
--- a/pkg/core/populate_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package core
-
-import (
- "os"
- "testing"
-
- "github.com/alecthomas/assert"
-)
-
-func TestPopulatePath(t *testing.T) {
- os.Setenv("TELLER_TEST_FOO", "test-foo")
-
- p := NewPopulate(map[string]string{
- "foo": "bar",
- "teller-env": "env:TELLER_TEST_FOO",
- "teller-env-default": "env:TELLER_TEST_DEFAULT,default-value",
- })
-
- assert.Equal(t, p.FindAndReplace("foo/{{foo}}/qux/{{foo}}"), "foo/bar/qux/bar")
- assert.Equal(t, p.FindAndReplace("foo/qux"), "foo/qux")
- assert.Equal(t, p.FindAndReplace("foo/{{none}}"), "foo/{{none}}")
- assert.Equal(t, p.FindAndReplace("foo/{{teller-env}}"), "foo/test-foo")
- assert.Equal(t, p.FindAndReplace("foo/{{teller-env-default}}"), "foo/default-value")
- assert.Equal(t, p.FindAndReplace(""), "")
-
- kp := KeyPath{
- Path: "{{foo}}/hey",
- Env: "env",
- Decrypt: true,
- Optional: true,
- }
- assert.Equal(t, p.KeyPath(kp), KeyPath{
- Path: "bar/hey",
- Env: "env",
- Decrypt: true,
- Optional: true,
- })
- kp = KeyPath{
- Path: "{{teller-env}}/hey",
- Env: "env",
- Decrypt: true,
- Optional: true,
- }
- assert.Equal(t, p.KeyPath(kp), KeyPath{
- Path: "test-foo/hey",
- Env: "env",
- Decrypt: true,
- Optional: true,
- })
- kp = KeyPath{
- Path: "{{teller-env-default}}/hey",
- Env: "env",
- Decrypt: true,
- Optional: true,
- }
- assert.Equal(t, p.KeyPath(kp), KeyPath{
- Path: "default-value/hey",
- Env: "env",
- Decrypt: true,
- Optional: true,
- })
-}
-
-func TestPopulateDefaultValue(t *testing.T) {
- key, value := parseDefaultValue("TELLER_TEST_FOO")
- assert.Equal(t, key, "TELLER_TEST_FOO")
- assert.Equal(t, value, "")
-
- key, value = parseDefaultValue("TELLER_TEST_FOO, default,,value")
- assert.Equal(t, key, "TELLER_TEST_FOO")
- assert.Equal(t, value, "default,,value")
-}
diff --git a/pkg/core/types.go b/pkg/core/types.go
deleted file mode 100644
index 42296892..00000000
--- a/pkg/core/types.go
+++ /dev/null
@@ -1,260 +0,0 @@
-package core
-
-import (
- "strings"
-
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type Severity string
-
-const (
- High Severity = "high"
- Medium Severity = "medium"
- Low Severity = "low"
- None Severity = "none"
-
- // PlainTextKey the key used for plaintext secrets
- PlainTextKey = "plaintext"
-)
-
-type RemapKeyPath struct {
- Field string `yaml:"field,omitempty"`
- Severity Severity `yaml:"severity,omitempty"`
- RedactWith string `yaml:"redact_with,omitempty"`
-}
-
-type KeyPath struct {
- Env string `yaml:"env,omitempty"`
- Path string `yaml:"path"`
- Field string `yaml:"field,omitempty"`
- Remap *map[string]string `yaml:"remap,omitempty"`
- RemapWith *map[string]RemapKeyPath `yaml:"remap_with,omitempty"`
- Decrypt bool `yaml:"decrypt,omitempty"`
- Optional bool `yaml:"optional,omitempty"`
- Plaintext bool `yaml:"plaintext,omitempty"`
- Severity Severity `yaml:"severity,omitempty" default:"high"`
- RedactWith string `yaml:"redact_with,omitempty" default:"**REDACTED**"`
- Source string `yaml:"source,omitempty"`
- Sink string `yaml:"sink,omitempty"`
-}
-
-type WizardAnswers struct {
- Project string
- Providers []string
- ProviderKeys map[string]bool
- Confirm bool
-}
-
-func (k *KeyPath) EffectiveKey() string {
- if k.Plaintext {
- return PlainTextKey
- }
- key := k.Env
- if k.Field != "" {
- key = k.Field
- }
- return key
-}
-
-func (k *KeyPath) EffectiveRemap() map[string]RemapKeyPath {
- remap := make(map[string]RemapKeyPath)
- if k.Remap != nil {
- for k, v := range *k.Remap {
- remap[k] = RemapKeyPath{Field: v}
- }
- } else if k.RemapWith != nil {
- remap = *k.RemapWith
- }
- return remap
-}
-
-func (k *KeyPath) Missing() EnvEntry {
- return EnvEntry{
- IsFound: false,
- Key: k.Env,
- Field: k.Field,
- ResolvedPath: k.Path,
- }
-}
-
-func (k *KeyPath) Found(v string) EnvEntry {
- return EnvEntry{
- IsFound: true,
- Key: k.Env,
- Field: k.Field,
- Value: v,
- ResolvedPath: k.Path,
- }
-}
-
-// NOTE: consider doing what 'updateParams' does in these builders
-func (k *KeyPath) FoundWithKey(key, v string) EnvEntry {
- return EnvEntry{
- IsFound: true,
- Key: key,
- Field: k.Field,
- Value: v,
- ResolvedPath: k.Path,
- }
-}
-
-func (k *KeyPath) WithEnv(env string) KeyPath {
- return KeyPath{
- Env: env,
- Path: k.Path,
- Field: k.Field,
- Decrypt: k.Decrypt,
- Optional: k.Optional,
- Source: k.Source,
- Sink: k.Sink,
- Plaintext: k.Plaintext,
- }
-}
-func (k *KeyPath) SwitchPath(path string) KeyPath {
- return KeyPath{
- Path: path,
- Field: k.Field,
- Env: k.Env,
- Decrypt: k.Decrypt,
- Optional: k.Optional,
- Source: k.Source,
- Sink: k.Sink,
- Plaintext: k.Plaintext,
- }
-}
-
-type DriftedEntriesBySource []DriftedEntry
-
-func (a DriftedEntriesBySource) Len() int { return len(a) }
-func (a DriftedEntriesBySource) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a DriftedEntriesBySource) Less(i, j int) bool { return a[i].Source.Source < a[j].Source.Source }
-
-type EntriesByProvider []EnvEntry
-
-func (a EntriesByProvider) Len() int { return len(a) }
-func (a EntriesByProvider) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a EntriesByProvider) Less(i, j int) bool {
- firstProviderName := strings.ToLower(a[i].ProviderName)
- secondProviderName := strings.ToLower(a[j].ProviderName)
- if firstProviderName != secondProviderName {
- return firstProviderName < secondProviderName
- }
- return a[i].Key < a[j].Key
-}
-
-type EntriesByKey []EnvEntry
-
-func (a EntriesByKey) Len() int { return len(a) }
-func (a EntriesByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a EntriesByKey) Less(i, j int) bool { return a[i].Key > a[j].Key }
-
-type EntriesByValueSize []EnvEntry
-
-func (a EntriesByValueSize) Len() int { return len(a) }
-func (a EntriesByValueSize) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a EntriesByValueSize) Less(i, j int) bool { return len(a[i].Value) > len(a[j].Value) }
-
-type EnvEntry struct {
- Key string
- Field string
- Value string
- ProviderName string
- ResolvedPath string
- Severity Severity
- RedactWith string
- Source string
- Sink string
- IsFound bool
-}
-
-func (ee *EnvEntry) AddressingKeyPath() *KeyPath {
- return &KeyPath{
- Env: ee.Key,
- Field: ee.Field,
- Path: ee.ResolvedPath,
- }
-}
-
-type DriftedEntry struct {
- Diff string
- Source EnvEntry
- Target EnvEntry
-}
-type EnvEntryLookup struct {
- Entries []EnvEntry
-}
-
-func (ee *EnvEntryLookup) EnvBy(key, provider, path, dflt string) string {
- for i := range ee.Entries {
- e := ee.Entries[i]
- if e.Key == key && e.ProviderName == provider && e.ResolvedPath == path {
- return e.Value
- }
- }
- return dflt
-}
-func (ee *EnvEntryLookup) EnvByKey(key, dflt string) string {
- for i := range ee.Entries {
- e := ee.Entries[i]
- if e.Key == key {
- return e.Value
- }
-
- }
- return dflt
-}
-
-func (ee *EnvEntryLookup) EnvByKeyAndProvider(key, provider, dflt string) string {
- for i := range ee.Entries {
- e := ee.Entries[i]
- if e.Key == key && e.ProviderName == provider {
- return e.Value
- }
-
- }
- return dflt
-}
-
-type Provider interface {
- // in this case 'env' is empty, but EnvEntries are the value
- GetMapping(p KeyPath) ([]EnvEntry, error)
-
- // in this case env is filled
- Get(p KeyPath) (*EnvEntry, error)
-
- Put(p KeyPath, val string) error
- PutMapping(p KeyPath, m map[string]string) error
-
- Delete(p KeyPath) error
- DeleteMapping(p KeyPath) error
-}
-
-type MetaInfo struct {
- Description string
- Name string
- Authentication string
- ConfigTemplate string
- Ops OpMatrix
-}
-type OpMatrix struct {
- Delete bool
- DeleteMapping bool
- Put bool
- PutMapping bool
- Get bool
- GetMapping bool
-}
-
-type Match struct {
- Path string
- Line string
- LineNumber int
- MatchIndex int
- Entry EnvEntry
-}
-
-type RegisteredProvider struct {
- Meta MetaInfo
- Builder func(logger logging.Logger) (Provider, error)
-}
diff --git a/pkg/integration_test/consul_test.go b/pkg/integration_test/consul_test.go
deleted file mode 100644
index 85fa9795..00000000
--- a/pkg/integration_test/consul_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-//go:build integration
-// +build integration
-
-package integration_test
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/hashicorp/consul/api"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "github.com/stretchr/testify/assert"
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/wait"
-)
-
-type ConsulData = map[string]interface{}
-
-func TestGetConsul(t *testing.T) {
- ctx := context.Background()
- const testToken = "vault-token"
-
- req := testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- AlwaysPullImage: false,
- Image: "consul:1.9.5",
- ExposedPorts: []string{"8500/tcp"},
- Env: map[string]string{},
- WaitingFor: wait.ForLog("Started gRPC server").WithStartupTimeout(20 * time.Second)},
-
- Started: true,
- }
-
- vaultContainer, err := testcontainers.GenericContainer(ctx, req)
- assert.NoError(t, err)
- defer vaultContainer.Terminate(ctx) //nolint
-
- ip, err := vaultContainer.Host(ctx)
- assert.NoError(t, err)
- port, err := vaultContainer.MappedPort(ctx, "8500")
- assert.NoError(t, err)
- host := fmt.Sprintf("http://%s:%s", ip, port.Port())
-
- //
- // pre-insert data w/API
- //
- config := &api.Config{Address: host}
- client, err := api.NewClient(config)
- assert.NoError(t, err)
- _, err = client.KV().Put(&api.KVPair{Key: "path/to/svc/MG_KEY", Value: []byte("value1")}, &api.WriteOptions{})
- assert.NoError(t, err)
-
- //
- // use provider to read data
- //
- t.Setenv("CONSUL_HTTP_ADDR", host)
- p, err := providers.NewConsul(logging.New())
- assert.NoError(t, err)
- kvp := core.KeyPath{Env: "MG_KEY", Path: "path/to/svc/MG_KEY"}
- res, err := p.Get(kvp)
-
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "value1", res.Value)
- assert.Equal(t, "path/to/svc/MG_KEY", res.ResolvedPath)
-
- err = p.Put(kvp, "changed-secret")
- assert.NoError(t, err)
-
- res, err = p.Get(kvp)
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "changed-secret", res.Value)
- assert.Equal(t, "path/to/svc/MG_KEY", res.ResolvedPath)
-
- err = p.PutMapping(kvp.SwitchPath("path/to/allmap"), map[string]string{"K1": "v1", "K2": "v2"})
- assert.NoError(t, err)
- ents, err := p.GetMapping(kvp.SwitchPath("path/to/allmap"))
- assert.NoError(t, err)
- assert.Equal(t, 2, len(ents))
- assert.Equal(t, "K1", ents[1].Key)
- assert.Equal(t, "K2", ents[0].Key)
- assert.Equal(t, "v1", ents[1].Value)
- assert.Equal(t, "v2", ents[0].Value)
-}
diff --git a/pkg/integration_test/dotenv_test.go b/pkg/integration_test/dotenv_test.go
deleted file mode 100644
index 0c438bb6..00000000
--- a/pkg/integration_test/dotenv_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-//go:build integration
-// +build integration
-
-package integration_test
-
-import (
- "os"
- "testing"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetDotEnv(t *testing.T) {
- //
- // pre-insert data
- //
- f, err := os.CreateTemp(t.TempDir(), "dotenv-*")
- assert.NoError(t, err)
- f.WriteString("MG_KEY=123\n")
- f.Close()
-
- //
- // use provider to read data
- //
- p, err := providers.NewDotenv(logging.New())
- assert.NoError(t, err)
- kvp := core.KeyPath{Env: "MG_KEY", Path: f.Name()}
- res, err := p.Get(kvp)
-
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "123", res.Value)
- assert.Equal(t, f.Name(), res.ResolvedPath)
-
- err = p.Put(kvp, "changed-secret")
- assert.NoError(t, err)
-
- res, err = p.Get(kvp)
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "changed-secret", res.Value)
- assert.Equal(t, f.Name(), res.ResolvedPath)
-
- err = p.PutMapping(kvp, map[string]string{"K1": "val1", "K2": "val2"})
- assert.NoError(t, err)
-
- res, err = p.Get(core.KeyPath{Env: "K1", Path: f.Name()})
- assert.NoError(t, err)
- assert.Equal(t, "K1", res.Key)
- assert.Equal(t, "val1", res.Value)
-
- err = p.Delete(core.KeyPath{Env: "MG_KEY", Path: f.Name()})
- assert.NoError(t, err)
-
- entries, err := p.GetMapping(core.KeyPath{Path: f.Name()})
- for _, entry := range entries {
- assert.NotEqual(t, "MG_KEY", entry.Key)
- }
- assert.NoError(t, err)
-
- err = p.DeleteMapping(kvp)
- assert.NoError(t, err)
-
- _, err = os.Stat(f.Name())
- assert.True(t, os.IsNotExist(err))
-}
diff --git a/pkg/integration_test/etcd_test.go b/pkg/integration_test/etcd_test.go
deleted file mode 100644
index ace91749..00000000
--- a/pkg/integration_test/etcd_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-//go:build integration
-// +build integration
-
-package integration_test
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "github.com/stretchr/testify/assert"
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/wait"
- clientv3 "go.etcd.io/etcd/client/v3"
-)
-
-func TestGetEtcd(t *testing.T) {
- ctx := context.Background()
- const testToken = "vault-token"
-
- req := testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- AlwaysPullImage: false,
- Image: "gcr.io/etcd-development/etcd:v3.3",
- ExposedPorts: []string{"2379/tcp"},
- Cmd: []string{
- "etcd",
- "--name", "etcd0",
- "--advertise-client-urls", "http://0.0.0.0:2379",
- "--listen-client-urls", "http://0.0.0.0:2379",
- },
- Env: map[string]string{},
- WaitingFor: wait.ForLog("ready to serve client requests").WithStartupTimeout(20 * time.Second)},
- Started: true,
- }
-
- vaultContainer, err := testcontainers.GenericContainer(ctx, req)
- assert.NoError(t, err)
- defer vaultContainer.Terminate(ctx) //nolint
-
- ip, err := vaultContainer.Host(ctx)
- assert.NoError(t, err)
- port, err := vaultContainer.MappedPort(ctx, "2379/tcp")
- assert.NoError(t, err)
- host := fmt.Sprintf("http://%s:%s", ip, port.Port())
-
- //
- // pre-insert data w/API
- //
- cfg := clientv3.Config{
- Endpoints: []string{host},
- }
- client, err := clientv3.New(cfg)
- assert.NoError(t, err)
- _, err = client.Put(context.TODO(), "path/to/svc/MG_KEY", "value1")
- assert.NoError(t, err)
- //
- // use provider to read data
- //
- t.Setenv("ETCDCTL_ENDPOINTS", host)
- p, err := providers.NewEtcd(logging.New())
- assert.NoError(t, err)
- kvp := core.KeyPath{Env: "MG_KEY", Path: "path/to/svc/MG_KEY"}
- res, err := p.Get(kvp)
-
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "value1", res.Value)
- assert.Equal(t, "path/to/svc/MG_KEY", res.ResolvedPath)
-
- err = p.Put(kvp, "changed-secret")
- assert.NoError(t, err)
-
- res, err = p.Get(kvp)
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "changed-secret", res.Value)
- assert.Equal(t, "path/to/svc/MG_KEY", res.ResolvedPath)
-
- err = p.PutMapping(kvp.SwitchPath("path/to/allmap"), map[string]string{"K1": "v1", "K2": "v2"})
- assert.NoError(t, err)
- ents, err := p.GetMapping(kvp.SwitchPath("path/to/allmap"))
- assert.NoError(t, err)
- assert.Equal(t, 2, len(ents))
- assert.Equal(t, "K1", ents[1].Key)
- assert.Equal(t, "K2", ents[0].Key)
- assert.Equal(t, "v1", ents[1].Value)
- assert.Equal(t, "v2", ents[0].Value)
-}
diff --git a/pkg/integration_test/heroku_test.go b/pkg/integration_test/heroku_test.go
deleted file mode 100644
index 50d8b03c..00000000
--- a/pkg/integration_test/heroku_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-//go:build integration_api
-// +build integration_api
-
-package integration_test
-
-import (
- "context"
- "os"
- "testing"
-
- heroku "github.com/heroku/heroku-go/v5"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetHeroku(t *testing.T) {
-
- //
- // TEST PRECONDITION:
- //
- // HEROKU_API_KEY populated
- // 'teller-heroku-integration' app exists
- //
-
- //
- // pre-insert data w/API
- //
- heroku.DefaultTransport.BearerToken = os.Getenv("HEROKU_API_KEY")
- svc := heroku.NewService(heroku.DefaultClient)
-
- v := "value1"
- _, err := svc.ConfigVarUpdate(context.TODO(), "teller-heroku-integration", map[string]*string{"MG_KEY": &v, "K1": nil, "K2": nil})
- assert.NoError(t, err)
-
- //
- // use provider to read data
- //
- p, err := providers.NewHeroku(logging.New())
- assert.NoError(t, err)
- kvp := core.KeyPath{Env: "MG_KEY", Path: "teller-heroku-integration"}
- res, err := p.Get(kvp)
-
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "value1", res.Value)
- assert.Equal(t, "teller-heroku-integration", res.ResolvedPath)
-
- err = p.Put(kvp, "changed-secret")
- assert.NoError(t, err)
-
- res, err = p.Get(kvp)
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "changed-secret", res.Value)
- assert.Equal(t, "teller-heroku-integration", res.ResolvedPath)
-
- err = p.PutMapping(kvp, map[string]string{"K1": "v1", "K2": "v2"})
- assert.NoError(t, err)
- ents, err := p.GetMapping(kvp)
- assert.NoError(t, err)
- assert.Equal(t, 3, len(ents))
- assert.Equal(t, "MG_KEY", ents[0].Key)
- assert.Equal(t, "changed-secret", ents[0].Value)
- assert.Equal(t, "K1", ents[2].Key)
- assert.Equal(t, "v1", ents[2].Value)
- assert.Equal(t, "K2", ents[1].Key)
- assert.Equal(t, "v2", ents[1].Value)
-}
diff --git a/pkg/integration_test/vault_test.go b/pkg/integration_test/vault_test.go
deleted file mode 100644
index 962f4bda..00000000
--- a/pkg/integration_test/vault_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-//go:build integration
-// +build integration
-
-package integration_test
-
-import (
- "context"
- "fmt"
- "testing"
- "time"
-
- "github.com/hashicorp/vault/api"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "github.com/stretchr/testify/assert"
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/wait"
-)
-
-type SecretData = map[string]interface{}
-
-func TestGetVaultSecret(t *testing.T) {
- ctx := context.Background()
- const testToken = "vault-token"
-
- req := testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- AlwaysPullImage: false,
- Image: "vault:1.6.3",
- ExposedPorts: []string{"8200/tcp"},
- Env: map[string]string{"VAULT_DEV_ROOT_TOKEN_ID": testToken},
- WaitingFor: wait.ForLog("Vault server started!").WithStartupTimeout(20 * time.Second)},
-
- Started: true,
- }
-
- vaultContainer, err := testcontainers.GenericContainer(ctx, req)
- assert.NoError(t, err)
- defer vaultContainer.Terminate(ctx) //nolint
-
- ip, err := vaultContainer.Host(ctx)
- assert.NoError(t, err)
- port, err := vaultContainer.MappedPort(ctx, "8200")
- assert.NoError(t, err)
- host := fmt.Sprintf("http://%s:%s", ip, port.Port())
-
- //
- // pre-insert data w/API
- //
- config := &api.Config{Address: host}
- client, err := api.NewClient(config)
- assert.NoError(t, err)
- client.SetToken(testToken)
- secretData := SecretData{
- "MG_KEY": "value1",
- }
- _, err = client.Logical().Write("secret/data/settings/prod/billing-svc", SecretData{"data": secretData})
- assert.NoError(t, err)
-
- //
- // use provider to read data
- //
- t.Setenv("VAULT_ADDR", host)
- t.Setenv("VAULT_TOKEN", testToken)
- p, err := providers.NewHashicorpVault(logging.New())
- assert.NoError(t, err)
- kvp := core.KeyPath{Env: "MG_KEY", Path: "secret/data/settings/prod/billing-svc"}
- res, err := p.Get(kvp)
-
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "value1", res.Value)
- assert.Equal(t, "secret/data/settings/prod/billing-svc", res.ResolvedPath)
-
- err = (p.(*providers.HashicorpVault)).Put(kvp, "changed-secret")
- assert.NoError(t, err)
-
- res, err = p.Get(kvp)
- assert.NoError(t, err)
- assert.Equal(t, "MG_KEY", res.Key)
- assert.Equal(t, "changed-secret", res.Value)
- assert.Equal(t, "secret/data/settings/prod/billing-svc", res.ResolvedPath)
-}
diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go
deleted file mode 100644
index 9a621e8c..00000000
--- a/pkg/logging/logging.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package logging
-
-import (
- "io"
- "strings"
-
- "github.com/sirupsen/logrus"
-)
-
-var (
- defaultLogger = New()
-)
-
-type Logger interface {
- Panic(fmt string, a ...interface{})
- Fatal(fmt string, a ...interface{})
- Error(fmt string, a ...interface{})
- Warn(fmt string, a ...interface{})
- Info(fmt string, a ...interface{})
- Debug(fmt string, a ...interface{})
- Trace(fmt string, a ...interface{})
-
- WithFields(map[string]interface{}) Logger
- WithField(string, interface{}) Logger
- WithError(err error) Logger
-
- GetLevel() string
- SetLevel(string)
-
- SetOutputFormat(format string)
- SetCallerReporter()
-}
-
-type StandardLogger struct {
- logger *logrus.Logger
- fields map[string]interface{}
-}
-
-// New returns a new application logger.
-func New() *StandardLogger {
- return &StandardLogger{
- logger: logrus.New(),
- }
-}
-
-// GetRoot returns the root logger application.
-func GetRoot() *StandardLogger {
- return defaultLogger
-}
-
-// SetOutput sets the underlying logrus output.
-func (l *StandardLogger) SetOutput(w io.Writer) {
- l.logger.SetOutput(w)
-}
-
-// SetOutputFormat change the logger format.
-// available formats: text/json
-func (l *StandardLogger) SetOutputFormat(format string) {
- var formatter logrus.Formatter
-
- switch strings.ToLower(format) {
- case "text":
- formatter = &logrus.TextFormatter{
- FullTimestamp: true,
- DisableLevelTruncation: true,
- PadLevelText: true,
- QuoteEmptyFields: true,
- }
- case "json":
- formatter = &logrus.JSONFormatter{
- PrettyPrint: false,
- }
- default:
- return // using default logger format
- }
-
- l.logger.SetFormatter(formatter)
-}
-
-// SetCallerReporter add calling method as a field.
-func (l *StandardLogger) SetCallerReporter() {
- l.logger.SetReportCaller(true)
-}
-
-// WithFields create new logger instance with the given default fields.
-//
-// Example:
-// logger :=log.WithFields(map[string]interface{}{"test": 2222})
-// logger.Info("Teller message") -> `INFO[0000] Teller message test=2222`
-func (l *StandardLogger) WithFields(fields map[string]interface{}) Logger {
- cp := *l
- cp.fields = make(map[string]interface{})
- for k, v := range l.fields {
- cp.fields[k] = v
- }
- for k, v := range fields {
- cp.fields[k] = v
- }
- return &cp
-}
-
-// WithField create new logger instance with singe field.
-func (l *StandardLogger) WithField(name string, value interface{}) Logger {
- return l.WithFields(map[string]interface{}{name: value})
-}
-
-// WithError add an error as single field to the logger.
-func (l *StandardLogger) WithError(err error) Logger {
- return l.WithField("error", err)
-}
-
-// GetLevel return the current logging level.
-func (l *StandardLogger) GetLevel() string {
- return l.logger.GetLevel().String()
-}
-
-// SetLevel sets the logger level.
-func (l *StandardLogger) SetLevel(level string) {
-
- switch level {
- case "panic'":
- l.logger.SetLevel(logrus.PanicLevel)
- case "fatal":
- l.logger.SetLevel(logrus.FatalLevel)
- case "error":
- l.logger.SetLevel(logrus.ErrorLevel)
- case "warn", "warning":
- l.logger.SetLevel(logrus.WarnLevel)
- case "info":
- l.logger.SetLevel(logrus.InfoLevel)
- case "debug":
- l.logger.SetLevel(logrus.DebugLevel)
- case "trace":
- l.logger.SetLevel(logrus.TraceLevel)
- case "null", "none":
- l.logger.SetOutput(io.Discard)
- default:
- l.Warn("unknown log level %v", level)
- l.logger.SetLevel(logrus.ErrorLevel)
- }
-}
-
-func (l *StandardLogger) Panic(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Panicf(fmt, a...)
-}
-
-func (l *StandardLogger) Fatal(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Fatalf(fmt, a...)
-}
-
-func (l *StandardLogger) Error(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Errorf(fmt, a...)
-}
-
-func (l *StandardLogger) Warn(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Errorf(fmt, a...)
-}
-
-func (l *StandardLogger) Info(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Infof(fmt, a...)
-}
-
-func (l *StandardLogger) Debug(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Debugf(fmt, a...)
-}
-
-func (l *StandardLogger) Trace(fmt string, a ...interface{}) {
- l.logger.WithFields(l.getFields()).Tracef(fmt, a...)
-}
-
-func (l *StandardLogger) getFields() map[string]interface{} {
- return l.fields
-}
diff --git a/pkg/logging/logging_test.go b/pkg/logging/logging_test.go
deleted file mode 100644
index 518ae17a..00000000
--- a/pkg/logging/logging_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package logging
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
-)
-
-func TestSetOutput(t *testing.T) {
-
- buf := new(bytes.Buffer)
- logger := New()
-
- logger.SetOutput(buf)
- logger.Error("error message")
-
- assert.NotEmpty(t, buf.String())
-
-}
-
-func TestJSONFormat(t *testing.T) {
-
- buf := new(bytes.Buffer)
- logger := New()
-
- logger.SetOutput(buf)
- logger.SetOutputFormat("json")
- logger.Error("error message")
-
- var js interface{}
- assert.True(t, json.Unmarshal(buf.Bytes(), &js) == nil)
-
-}
-
-func TestTextFormat(t *testing.T) {
-
- buf := new(bytes.Buffer)
- logger := New()
-
- logger.SetOutput(buf)
- logger.SetOutputFormat("text")
- logger.Error("error message")
-
- assert.Contains(t, buf.String(), "level=error")
- assert.Contains(t, buf.String(), "msg=\"error message\"")
-
-}
-
-func TestWithFields(t *testing.T) {
-
- buf := new(bytes.Buffer)
- logger := New()
-
- logger.SetOutput(buf)
- logger.SetOutputFormat("text")
- logger.WithFields(map[string]interface{}{"field-a": "test", "field-b": "test"}).Error("error message")
-
- assert.Contains(t, buf.String(), "field-a=test")
- assert.Contains(t, buf.String(), "field-b=test")
-
-}
-
-func TestWithField(t *testing.T) {
-
- buf := new(bytes.Buffer)
- logger := New()
-
- logger.SetOutput(buf)
- logger.SetOutputFormat("text")
- logger.WithField("field-a", "test").Error("error message")
-
- assert.Contains(t, buf.String(), "field-a=test")
-
-}
-
-func TestWithError(t *testing.T) {
-
- buf := new(bytes.Buffer)
- logger := New()
-
- logger.SetOutput(buf)
- logger.SetOutputFormat("text")
- logger.WithError(errors.New("some-error")).Error("error message")
-
- assert.Contains(t, buf.String(), "error=some-error")
-
-}
-
-func TestLevelSetter(t *testing.T) {
-
- logger := New()
- logger.SetLevel("debug")
- assert.Equal(t, "debug", logger.GetLevel())
-
-}
diff --git a/pkg/porcelain.go b/pkg/porcelain.go
deleted file mode 100644
index 40a935a5..00000000
--- a/pkg/porcelain.go
+++ /dev/null
@@ -1,216 +0,0 @@
-package pkg
-
-import (
- "bytes"
- "fmt"
- "io"
- "math"
- "os"
- "sort"
- "strings"
- "time"
-
- survey "github.com/AlecAivazis/survey/v2"
- "github.com/samber/lo"
-
- "github.com/fatih/color"
- "github.com/jftuga/ellipsis"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-type Porcelain struct {
- Out io.Writer
-}
-
-func (p *Porcelain) StartWizard() (*core.WizardAnswers, error) {
- wd, _ := os.Getwd()
- workingfolder := utils.LastSegment(wd)
-
- providers := BuiltinProviders{}
- providerNames := providers.ProviderHumanToMachine()
- displayProviders := lo.Keys(providerNames)
- sort.Strings(displayProviders)
- // the questions to ask
- var qs = []*survey.Question{
- {
- Name: "project",
- Prompt: &survey.Input{
- Message: "Project name?",
- Default: workingfolder,
- },
- Validate: survey.Required,
- },
- {
- Name: "providers",
- Prompt: &survey.MultiSelect{
- Message: "Select your secret providers",
- PageSize: 10,
- Options: displayProviders,
- },
- },
- {
- Name: "confirm",
- Prompt: &survey.Confirm{
- Message: "Would you like extra confirmation before accessing secrets?",
- },
- },
- }
-
- answers := core.WizardAnswers{}
- // perform the questions
- err := survey.Ask(qs, &answers)
- if err != nil {
- return nil, err
- }
-
- if len(answers.Providers) == 0 {
- return nil, fmt.Errorf("you need to select at least one provider")
- }
-
- answers.ProviderKeys = map[string]bool{}
- for _, plabel := range answers.Providers {
- answers.ProviderKeys[providerNames[plabel]] = true
- }
-
- return &answers, nil
-}
-
-func (p *Porcelain) DidCreateNewFile(fname string) {
- green := color.New(color.FgGreen).SprintFunc()
- fmt.Fprintf(p.Out, "Created file: %v\n", green(fname))
-}
-
-func (p *Porcelain) AskForConfirmation(msg string) bool {
- yesno := false
- prompt := &survey.Confirm{
- Message: msg,
- }
- _ = survey.AskOne(prompt, &yesno)
- return yesno
-}
-
-func (p *Porcelain) VSpace(size int) {
- fmt.Fprint(p.Out, strings.Repeat("\n", size))
-}
-
-func (p *Porcelain) PrintContext(projectName, loadedFrom string) {
- green := color.New(color.FgGreen).SprintFunc()
- white := color.New(color.FgWhite).SprintFunc()
-
- fmt.Fprintf(p.Out, "-*- %s: loaded variables for %s using %s -*-\n", white("teller"), green(projectName), green(loadedFrom))
-}
-
-func (p *Porcelain) PrintEntries(entries []core.EnvEntry) {
- var buf bytes.Buffer
- yellow := color.New(color.FgYellow).SprintFunc()
- gray := color.New(color.FgHiBlack).SprintFunc()
- green := color.New(color.FgGreen).SprintFunc()
- red := color.New(color.FgRed).SprintFunc()
-
- for i := range entries {
- v := entries[i]
- ep := ellipsis.Shorten(v.ResolvedPath, 30) //nolint: gomnd
- if !v.IsFound {
- fmt.Fprintf(&buf, "[%s %s %s] %s\n", yellow(v.ProviderName), gray(ep), red("missing"), green(v.Key))
- } else {
- fmt.Fprintf(&buf, "[%s %s] %s %s %s\n", yellow(v.ProviderName), gray(ep), green(v.Key), gray("="), maskedValue(v.Value))
- }
- }
-
- out := buf.String()
-
- fmt.Fprint(p.Out, out)
-}
-func maskedValue(v string) string {
- return fmt.Sprintf("%s*****", v[:int(math.Min(float64(len(v)), 2))]) //nolint: gomnd
-}
-
-func (p *Porcelain) PrintMatches(matches []core.Match) {
- yellow := color.New(color.FgYellow).SprintFunc()
- gray := color.New(color.FgHiBlack).SprintFunc()
- green := color.New(color.FgGreen).SprintFunc()
- white := color.New(color.FgWhite).SprintFunc()
- red := color.New(color.FgRed).SprintFunc()
-
- sort.Slice(matches, func(i, j int) bool {
- return matches[i].Path < matches[j].Path
- })
-
- for _, m := range matches { //nolint
- sevcolor := white
- if m.Entry.Severity == core.High {
- sevcolor = red
- }
- if m.Entry.Severity == core.Medium {
- sevcolor = yellow
- }
- fmt.Printf("[%s] %s (%s,%s): found match for %s/%s (%s)\n", sevcolor(m.Entry.Severity), green(m.Path), yellow(m.LineNumber), yellow(m.MatchIndex), gray(m.Entry.ProviderName), red(m.Entry.Key), gray(maskedValue(m.Entry.Value)))
- }
-}
-
-func (p *Porcelain) PrintMatchSummary(findings []core.Match, entries []core.EnvEntry, elapsed time.Duration) {
- yellow := color.New(color.FgYellow).SprintFunc()
- goodbad := color.New(color.FgGreen).SprintFunc()
- if len(findings) > 0 {
- goodbad = color.New(color.FgRed).SprintFunc()
- }
-
- fmt.Printf("Scanning for %v entries: found %v matches in %v\n", yellow(len(entries)), goodbad(len(findings)), goodbad(elapsed))
-}
-
-func (p *Porcelain) PrintDrift(drifts []core.DriftedEntry) {
- green := color.New(color.FgGreen).SprintFunc()
- gray := color.New(color.FgHiBlack).SprintFunc()
- red := color.New(color.FgRed).SprintFunc()
-
- if len(drifts) > 0 {
- fmt.Fprintf(p.Out, "Drifts detected: %v\n\n", len(drifts))
-
- for i := range drifts {
- d := drifts[i]
- if d.Diff == "changed" {
- fmt.Fprintf(p.Out, "%v [%v] %v %v %v != %v %v %v\n", d.Diff, d.Source.Source, green(d.Source.ProviderName), green(d.Source.Key), gray(maskedValue(d.Source.Value)), red(d.Target.ProviderName), red(d.Target.Key), gray(maskedValue(d.Target.Value)))
- } else {
- fmt.Fprintf(p.Out, "%v [%v] %v %v %v ??\n", d.Diff, d.Source.Source, green(d.Source.ProviderName), green(d.Source.Key), gray(maskedValue(d.Source.Value)))
- }
- }
- }
-
-}
-
-func (p *Porcelain) DidPutKVP(kvp core.KeyPath, pname string, sync bool) {
- green := color.New(color.FgGreen).SprintFunc()
- yellow := color.New(color.FgYellow).SprintFunc()
- gray := color.New(color.FgHiBlack).SprintFunc()
- if sync {
- fmt.Fprintf(p.Out, "Synced %v (%v): OK.\n", green(pname), gray(kvp.Path))
- } else {
- fmt.Fprintf(p.Out, "Put %v (%v) in %v: OK.\n", yellow(kvp.Env), gray(kvp.Path), green(pname))
- }
-}
-
-func (p *Porcelain) NoPutKVP(k, pname string) {
- green := color.New(color.FgGreen).SprintFunc()
- yellow := color.New(color.FgYellow).SprintFunc()
- fmt.Fprintf(p.Out, "Put %v in %v: no such key '%v' in mapping\n", yellow(k), green(pname), yellow(k))
-}
-
-func (p *Porcelain) DidDeleteKP(kp core.KeyPath, pname string) {
- green := color.New(color.FgGreen).SprintFunc()
- yellow := color.New(color.FgYellow).SprintFunc()
- gray := color.New(color.FgHiBlack).SprintFunc()
- fmt.Fprintf(p.Out, "Delete %v (%v) in %v: OK.\n", yellow(kp.Env), gray(kp.Path), green(pname))
-}
-
-func (p *Porcelain) NoDeleteKP(k, pname string) {
- green := color.New(color.FgGreen).SprintFunc()
- yellow := color.New(color.FgYellow).SprintFunc()
- fmt.Fprintf(p.Out, "Delete %v in %v: no such key '%v' in mapping\n", yellow(k), green(pname), yellow(k))
-}
-
-func (p *Porcelain) DidDeleteP(path, pname string) {
- green := color.New(color.FgGreen).SprintFunc()
- gray := color.New(color.FgHiBlack).SprintFunc()
- fmt.Fprintf(p.Out, "Delete mapping in path %v in %v: OK.\n", gray(path), green(pname))
-}
diff --git a/pkg/porcelain_test.go b/pkg/porcelain_test.go
deleted file mode 100644
index 66bdda38..00000000
--- a/pkg/porcelain_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package pkg
-
-import (
- "bytes"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/spectralops/teller/pkg/core"
-)
-
-func TestPorcelainNonInteractive(t *testing.T) {
- var b bytes.Buffer
- p := Porcelain{
- Out: &b,
- }
- p.DidCreateNewFile("myfile.yaml")
- assert.Equal(t, b.String(), "Created file: myfile.yaml\n")
- b.Reset()
-
- p.PrintContext("project", "place")
- assert.Equal(t, b.String(), "-*- teller: loaded variables for project using place -*-\n")
- b.Reset()
-
- p.PrintEntries([]core.EnvEntry{
- {IsFound: true, Key: "k", Value: "v", ProviderName: "test-provider", ResolvedPath: "path/kv"},
- })
- assert.Equal(t, b.String(), "[test-provider path/kv] k = v*****\n")
- b.Reset()
-}
-
-func TestPorcelainPrintDrift(t *testing.T) {
- var b bytes.Buffer
- p := Porcelain{
- Out: &b,
- }
- p.PrintDrift([]core.DriftedEntry{
- {
- Diff: "changed",
- Source: core.EnvEntry{
-
- Source: "s1", Key: "k", Value: "v", ProviderName: "test-provider", ResolvedPath: "path/kv",
- },
-
- Target: core.EnvEntry{
-
- Sink: "s1", Key: "k", Value: "x", ProviderName: "test-provider", ResolvedPath: "path/kv",
- },
- },
- {
- Diff: "changed",
- Source: core.EnvEntry{
- Source: "s2", Key: "k2", Value: "1", ProviderName: "test-provider", ResolvedPath: "path/kv",
- },
-
- Target: core.EnvEntry{
- Sink: "s2", Key: "k2", Value: "2", ProviderName: "test-provider", ResolvedPath: "path/kv",
- },
- },
- })
- assert.Equal(t, b.String(), `Drifts detected: 2
-
-changed [s1] test-provider k v***** != test-provider k x*****
-changed [s2] test-provider k2 1***** != test-provider k2 2*****
-`)
-}
diff --git a/pkg/provider_test.go b/pkg/provider_test.go
deleted file mode 100644
index 8e133813..00000000
--- a/pkg/provider_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package pkg
-
-import (
- "fmt"
- "testing"
-
- "github.com/alecthomas/assert"
-)
-
-func TestGetProvider(t *testing.T) {
- providers := &BuiltinProviders{}
- p, err := providers.GetProvider("missing")
- assert.Nil(t, p)
- assert.NotNil(t, err)
- assert.Contains(t, err.Error(), "provider 'missing' does not exist")
-
- for _, v := range providers.ProviderHumanToMachine() {
- _, err := providers.GetProvider(v)
- if err != nil {
- assert.NotContains(t, err.Error(), fmt.Sprintf("provider %s does not exist", v))
- }
- }
-}
diff --git a/pkg/providers.go b/pkg/providers.go
deleted file mode 100644
index 50411e20..00000000
--- a/pkg/providers.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package pkg
-
-import (
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers"
-)
-
-type Providers interface {
- GetProvider(name string) (core.Provider, error)
- ProviderHumanToMachine() map[string]string
-}
-
-type BuiltinProviders struct {
-}
-
-func (p *BuiltinProviders) ProviderHumanToMachine() map[string]string {
- providersMeta := providers.GetAllProvidersMeta()
- descriptionToNameMap := make(map[string]string)
- for _, meta := range providersMeta {
- descriptionToNameMap[meta.Description] = meta.Name
- }
- return descriptionToNameMap
-}
-
-func (p *BuiltinProviders) GetProvider(name string) (core.Provider, error) {
- return providers.ResolveProvider(name)
-}
diff --git a/pkg/providers/ansible_vault.go b/pkg/providers/ansible_vault.go
deleted file mode 100644
index 22d3549c..00000000
--- a/pkg/providers/ansible_vault.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "sort"
-
- "github.com/joho/godotenv"
- vault "github.com/sosedoff/ansible-vault-go"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type AnsibleVaultClient interface {
- Read(p string) (map[string]string, error)
-}
-
-type AnsibleVaultReader struct {
- passPhrase string
-}
-
-func (a AnsibleVaultReader) Read(p string) (map[string]string, error) {
- content, err := vault.DecryptFile(p, a.passPhrase)
- if err != nil {
- return nil, err
- }
- return godotenv.Unmarshal(content)
-}
-
-type AnsibleVault struct {
- logger logging.Logger
- client AnsibleVaultClient
-}
-
-//nolint
-func init() {
- metaInto := core.MetaInfo{
- Description: "Ansible Vault",
- Name: "ansible_vault",
- Authentication: "ANSIBLE_VAULT_PASSPHRASE.",
- ConfigTemplate: `
- # Configure via environment variables for integration:
- # ANSIBLE_VAULT_PASSPHRASE: Ansible Vault Password
-
- ansible_vault:
- env_sync:
- path: ansible/vars/vault_{{stage}}.yml
-
- env:
- KEY1:
- path: ansible/vars/vault_{{stage}}.yml
- NONEXIST_KEY:
- path: ansible/vars/vault_{{stage}}.yml
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: false, PutMapping: false},
- }
- RegisterProvider(metaInto, NewAnsibleVault)
-}
-
-// NewAnsibleVault creates new provider instance
-func NewAnsibleVault(logger logging.Logger) (core.Provider, error) {
- ansibleVaultPassphrase := os.Getenv("ANSIBLE_VAULT_PASSPHRASE")
- return &AnsibleVault{
- logger: logger,
- client: &AnsibleVaultReader{
- passPhrase: ansibleVaultPassphrase,
- },
- }, nil
-}
-
-// Name return the provider name
-func (a *AnsibleVault) Name() string {
- return "AnsibleVault"
-}
-
-// Put will create a new single entry
-func (a *AnsibleVault) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", a.Name())
-}
-
-// PutMapping will create a multiple entries
-func (a *AnsibleVault) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", a.Name())
-}
-
-// GetMapping returns a multiple entries
-func (a *AnsibleVault) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- // Read existing secret
- a.logger.WithField("path", p.Path).Debug("read secret")
- kvs, err := a.client.Read(p.Path)
- if err != nil {
- return nil, err
- }
-
- var entries []core.EnvEntry
- for k, v := range kvs {
- entries = append(entries, p.FoundWithKey(k, v))
- }
- sort.Sort(core.EntriesByKey(entries))
-
- return entries, nil
-}
-
-// Get returns a single entry
-func (a *AnsibleVault) Get(p core.KeyPath) (*core.EnvEntry, error) { //nolint:dupl
- a.logger.WithField("path", p.Path).Debug("read secret")
-
- kvs, err := a.client.Read(p.Path)
- if err != nil {
- return nil, err
- }
-
- k := p.EffectiveKey()
- val, ok := kvs[k]
- if !ok {
- a.logger.WithFields(map[string]interface{}{"path": p.Path, "key": k}).Debug("key not found")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(val)
- return &ent, nil
-}
-
-// Delete will delete entry
-func (a *AnsibleVault) Delete(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", a.Name())
-}
-
-// DeleteMapping will delete the given path recessively
-func (a *AnsibleVault) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", a.Name())
-}
diff --git a/pkg/providers/ansible_vault_test.go b/pkg/providers/ansible_vault_test.go
deleted file mode 100644
index 1e89d90e..00000000
--- a/pkg/providers/ansible_vault_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package providers
-
-import (
- "testing"
-
- "github.com/golang/mock/gomock"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestAnsibleVault(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockAnsibleVaultClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- out := map[string]string{
- "MG_KEY": "shazam",
- "SMTP_PASS": "mailman",
- }
- client.EXPECT().Read(gomock.Eq(path)).Return(out, nil).AnyTimes()
- client.EXPECT().Read(gomock.Eq(pathmap)).Return(out, nil).AnyTimes()
-
- s := AnsibleVault{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
diff --git a/pkg/providers/aws_secretsmanager.go b/pkg/providers/aws_secretsmanager.go
deleted file mode 100644
index 1b6f1bf9..00000000
--- a/pkg/providers/aws_secretsmanager.go
+++ /dev/null
@@ -1,284 +0,0 @@
-package providers
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "os"
- "sort"
- "strings"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
- smtypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-type AWSSecretsManagerClient interface {
- GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error)
- CreateSecret(ctx context.Context, params *secretsmanager.CreateSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.CreateSecretOutput, error)
- PutSecretValue(ctx context.Context, params *secretsmanager.PutSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.PutSecretValueOutput, error)
- DescribeSecret(ctx context.Context, params *secretsmanager.DescribeSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.DescribeSecretOutput, error)
- DeleteSecret(ctx context.Context, params *secretsmanager.DeleteSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.DeleteSecretOutput, error)
-}
-
-type AWSSecretsManager struct {
- client AWSSecretsManagerClient
- logger logging.Logger
- deletionDisableRecoveryWindow bool
- treatSecretMarkedForDeletionAsNonExisting bool
- deletionRecoveryWindowInDays int64
-}
-
-var defaultDeletionRecoveryWindowInDays int64 = 7
-
-const versionSplit = ","
-
-// nolint
-func init() {
- metaInfo := core.MetaInfo{
- Name: "aws_secretsmanager",
- Description: "AWS Secrets Manager",
- Authentication: "Your standard `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` need to be populated in your environment. `AWS_ENDPOINT` is used to allow usage of localstack",
- ConfigTemplate: `
- # configure only from environment
- aws_secretsmanager:
- env_sync:
- path: prod/foo/bar
- env:
- FOO_BAR:
- path: prod/foo/bar
- field: SOME_KEY
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true, Delete: true, DeleteMapping: true},
- }
- RegisterProvider(metaInfo, NewAWSSecretsManager)
-}
-
-func NewAWSSecretsManager(logger logging.Logger) (core.Provider, error) {
- customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
- awsEndpointOverride := os.Getenv("AWS_ENDPOINT")
- if awsEndpointOverride != "" {
- return aws.Endpoint{
- PartitionID: "aws",
- URL: awsEndpointOverride,
- SigningRegion: region,
- }, nil
- }
-
- // returning EndpointNotFoundError will allow the service to fallback to its default resolution
- return aws.Endpoint{}, &aws.EndpointNotFoundError{}
- })
-
- cfg, err := config.LoadDefaultConfig(context.Background(), config.WithEndpointResolver(customResolver))
- if err != nil {
- return nil, err
- }
-
- client := secretsmanager.NewFromConfig(cfg)
-
- return &AWSSecretsManager{
- client: client,
- logger: logger,
- deletionRecoveryWindowInDays: defaultDeletionRecoveryWindowInDays,
- deletionDisableRecoveryWindow: false,
- treatSecretMarkedForDeletionAsNonExisting: true,
- }, nil
-}
-
-func (a *AWSSecretsManager) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- kvs, err := a.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- var entries []core.EnvEntry
- for k, v := range kvs {
- entries = append(entries, p.FoundWithKey(k, v))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (a *AWSSecretsManager) Put(kp core.KeyPath, val string) error {
- k := kp.EffectiveKey()
- return a.PutMapping(kp, map[string]string{k: val})
-}
-
-func (a *AWSSecretsManager) PutMapping(kp core.KeyPath, m map[string]string) error {
- secrets, err := a.getSecret(kp)
- if err != nil {
- return err
- }
-
- secretAlreadyExist := len(secrets) != 0
-
- secrets = utils.Merge(secrets, m)
- secretBytes, err := json.Marshal(secrets)
- if err != nil {
- return err
- }
-
- secretString := string(secretBytes)
- ctx := context.Background()
- if secretAlreadyExist {
- // secret already exist - put new value
- a.logger.WithField("path", kp.Path).Debug("secret already exists, update the existing one")
- _, err = a.client.PutSecretValue(ctx, &secretsmanager.PutSecretValueInput{SecretId: &kp.Path, SecretString: &secretString})
- return err
- }
-
- // create secret
- a.logger.WithField("path", kp.Path).Debug("create secret")
- _, err = a.client.CreateSecret(ctx, &secretsmanager.CreateSecretInput{Name: &kp.Path, SecretString: &secretString})
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (a *AWSSecretsManager) Get(kp core.KeyPath) (*core.EnvEntry, error) {
- kvs, err := a.getSecret(kp)
- if err != nil {
- return nil, err
- }
-
- k := kp.EffectiveKey()
- val, ok := kvs[k]
- if !ok {
- a.logger.WithField("key", k).Debug("key not found in kvs secrets")
- ent := kp.Missing()
- return &ent, nil
- }
-
- ent := kp.Found(val)
- return &ent, nil
-}
-
-func (a *AWSSecretsManager) Delete(kp core.KeyPath) error {
- kvs, err := a.getSecret(kp)
- if err != nil {
- return err
- }
-
- k := kp.EffectiveKey()
- delete(kvs, k)
-
- if len(kvs) == 0 {
- return a.DeleteMapping(kp)
- }
-
- secretBytes, err := json.Marshal(kvs)
- if err != nil {
- return err
- }
-
- secretString := string(secretBytes)
- ctx := context.Background()
- a.logger.WithField("path", kp.Path).Debug("put secret value")
- _, err = a.client.PutSecretValue(ctx, &secretsmanager.PutSecretValueInput{SecretId: &kp.Path, SecretString: &secretString})
- return err
-}
-
-func (a *AWSSecretsManager) DeleteMapping(kp core.KeyPath) error {
- kvs, err := a.getSecret(kp)
- if err != nil {
- return err
- }
-
- if kvs == nil {
- // already deleted
- a.logger.WithField("path", kp.Path).Debug("already deleted")
- return nil
- }
-
- ctx := context.Background()
- a.logger.WithField("path", kp.Path).Debug("delete secret")
- _, err = a.client.DeleteSecret(ctx, &secretsmanager.DeleteSecretInput{
- SecretId: &kp.Path,
- RecoveryWindowInDays: &a.deletionRecoveryWindowInDays,
- ForceDeleteWithoutRecovery: &a.deletionDisableRecoveryWindow,
- })
-
- return err
-}
-
-func (a *AWSSecretsManager) getSecret(kp core.KeyPath) (map[string]string, error) {
- a.logger.WithField("path", kp.Path).Debug("get secret value")
- valueInput := secretsmanager.GetSecretValueInput{SecretId: &kp.Path}
-
- splitVersion := strings.Split(kp.Path, versionSplit)
- //nolint:gomnd
- if len(splitVersion) == 2 {
- a.logger.WithFields(map[string]interface{}{
- "path": splitVersion[0],
- "version": splitVersion[1],
- }).Debug("add version")
- valueInput.SecretId = &splitVersion[0]
- valueInput.VersionId = &splitVersion[1]
- }
-
- res, err := a.client.GetSecretValue(context.Background(), &valueInput)
-
- var (
- resNotFoundErr *smtypes.ResourceNotFoundException
- invalidReqErr *smtypes.InvalidRequestException
- )
-
- switch {
- case err == nil:
- if res == nil || res.SecretString == nil {
- return nil, fmt.Errorf("data not found at %q", kp.Path)
- }
- if kp.Plaintext {
- return map[string]string{
- core.PlainTextKey: *res.SecretString,
- }, nil
- }
-
- var secret map[string]interface{}
- err = json.Unmarshal([]byte(*res.SecretString), &secret)
- if err != nil {
- return nil, err
- }
-
- stringParse := map[string]string{}
- for k, v := range secret {
- stringParse[k] = fmt.Sprintf("%v", v)
- }
- return stringParse, nil
- case errors.As(err, &resNotFoundErr):
- // doesn't exist - do not treat as an error
- return nil, nil
- case a.treatSecretMarkedForDeletionAsNonExisting && errors.As(err, &invalidReqErr):
- // see whether it is marked for deletion
- markedForDeletion, markedForDeletionErr := a.isSecretMarkedForDeletion(kp)
- if err != nil {
- return nil, markedForDeletionErr
- }
-
- if markedForDeletion {
- // doesn't exist anymore - do not treat as an error
- return nil, nil
- }
-
- return nil, nil
- }
-
- return nil, err
-
-}
-
-func (a *AWSSecretsManager) isSecretMarkedForDeletion(kp core.KeyPath) (bool, error) {
- data, err := a.client.DescribeSecret(context.Background(), &secretsmanager.DescribeSecretInput{SecretId: &kp.Path})
- if err != nil {
- return false, err
- }
-
- return data.DeletedDate != nil, nil
-}
diff --git a/pkg/providers/aws_secretsmanager_test.go b/pkg/providers/aws_secretsmanager_test.go
deleted file mode 100644
index ac880622..00000000
--- a/pkg/providers/aws_secretsmanager_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestAWSSecretsManager(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockAWSSecretsManagerClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- in := secretsmanager.GetSecretValueInput{SecretId: &path}
- inmap := secretsmanager.GetSecretValueInput{SecretId: &pathmap}
- data := `{"MG_KEY":"shazam", "SMTP_PASS":"mailman"}`
- out := secretsmanager.GetSecretValueOutput{
- SecretString: &data,
- }
- client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(&in)).Return(&out, nil).AnyTimes()
- client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(&inmap)).Return(&out, nil).AnyTimes()
- s := AWSSecretsManager{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestAWSSecretsManagerPlainText(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockAWSSecretsManagerClient(ctrl)
- path := "settings/prod/billing-svc"
- in := secretsmanager.GetSecretValueInput{SecretId: &path}
- data := `hello-world`
- out := secretsmanager.GetSecretValueOutput{
- SecretString: &data,
- }
- client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(&in)).Return(&out, nil).AnyTimes()
- s := AWSSecretsManager{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProviderPlainText(t, &s, data)
-}
-
-func TestAWSSecretsManagerFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockAWSSecretsManagerClient(ctrl)
- client.EXPECT().GetSecretValue(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := AWSSecretsManager{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/aws_ssm.go b/pkg/providers/aws_ssm.go
deleted file mode 100644
index 2a793fce..00000000
--- a/pkg/providers/aws_ssm.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package providers
-
-import (
- "context"
- "fmt"
- "os"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/service/ssm"
- "github.com/aws/aws-sdk-go-v2/service/ssm/types"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type AWSSSMClient interface {
- GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
- PutParameter(ctx context.Context, params *ssm.PutParameterInput, optFns ...func(*ssm.Options)) (*ssm.PutParameterOutput, error)
- DeleteParameter(ctx context.Context, params *ssm.DeleteParameterInput, optFns ...func(*ssm.Options)) (*ssm.DeleteParameterOutput, error)
-}
-type AWSSSM struct {
- client AWSSSMClient
- logger logging.Logger
-}
-
-const awsssmName = "aws_ssm"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "AWS SSM (aka paramstore)",
- Name: awsssmName,
- Authentication: "Your standard `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` need to be populated in your environment. `AWS_ENDPOINT` is used to allow usage of localstack",
- ConfigTemplate: `
- # configure only from environment
- aws_ssm:
- env:
- FOO_BAR:
- path: /prod/foobar
- decrypt: true
- `,
- Ops: core.OpMatrix{Get: true, Put: true, Delete: true},
- }
- RegisterProvider(metaInfo, NewAWSSSM)
-}
-
-func NewAWSSSM(logger logging.Logger) (core.Provider, error) {
- customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
- awsEndpointOverride := os.Getenv("AWS_ENDPOINT")
- if awsEndpointOverride != "" {
- return aws.Endpoint{
- PartitionID: "aws",
- URL: awsEndpointOverride,
- SigningRegion: region,
- }, nil
- }
-
- // returning EndpointNotFoundError will allow the service to fallback to its default resolution
- return aws.Endpoint{}, &aws.EndpointNotFoundError{}
- })
-
- cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithEndpointResolver(customResolver))
- if err != nil {
- return nil, err
- }
-
- client := ssm.NewFromConfig(cfg)
-
- return &AWSSSM{client: client, logger: logger}, nil
-}
-
-func (a *AWSSSM) Put(kp core.KeyPath, val string) error {
-
- _, err := a.client.PutParameter(context.TODO(), &ssm.PutParameterInput{
- Name: &kp.Path,
- Value: &val,
- Overwrite: aws.Bool(true),
- Type: types.ParameterTypeString,
- })
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (a *AWSSSM) PutMapping(kp core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := kp.SwitchPath(k)
- err := a.Put(ap, v)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (a *AWSSSM) GetMapping(kp core.KeyPath) ([]core.EnvEntry, error) {
- return nil, fmt.Errorf("does not support full env sync (path: %s)", kp.Path)
-}
-
-func (a *AWSSSM) Delete(kp core.KeyPath) error {
- _, err := a.client.DeleteParameter(context.TODO(), &ssm.DeleteParameterInput{Name: &kp.Path})
- return err
-}
-
-func (a *AWSSSM) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("does not support full env sync (path: %s)", kp.Path)
-}
-
-func (a *AWSSSM) Get(p core.KeyPath) (*core.EnvEntry, error) {
- secret, err := a.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- if secret == nil {
- a.logger.WithField("path", p.Path).Debug("secret is empty")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(*secret)
- return &ent, nil
-}
-
-func (a *AWSSSM) getSecret(kp core.KeyPath) (*string, error) {
- a.logger.WithField("path", kp.Path).Debug("get entry")
- res, err := a.client.GetParameter(context.TODO(), &ssm.GetParameterInput{Name: &kp.Path, WithDecryption: &kp.Decrypt})
- if err != nil {
- return nil, err
- }
-
- if res == nil || res.Parameter.Value == nil {
- return nil, nil
- }
-
- return res.Parameter.Value, nil
-}
diff --git a/pkg/providers/aws_ssm_test.go b/pkg/providers/aws_ssm_test.go
deleted file mode 100644
index bcb1d2bd..00000000
--- a/pkg/providers/aws_ssm_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/aws/aws-sdk-go-v2/aws"
-
- "github.com/alecthomas/assert"
- "github.com/aws/aws-sdk-go-v2/service/ssm"
- "github.com/aws/aws-sdk-go-v2/service/ssm/types"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestAWSSSM(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockAWSSSMClient(ctrl)
- path := "settings/prod/billing-svc"
- val := "shazam"
-
- in := ssm.GetParameterInput{Name: &path, WithDecryption: aws.Bool(true)}
- out := ssm.GetParameterOutput{
- Parameter: &types.Parameter{
- Value: &val,
- },
- }
- client.EXPECT().GetParameter(gomock.Any(), gomock.Eq(&in)).Return(&out, nil).AnyTimes()
- s := AWSSSM{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, false)
-}
-
-func TestAWSSSMFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockAWSSSMClient(ctrl)
- client.EXPECT().GetParameter(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := AWSSSM{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/azure_keyvault.go b/pkg/providers/azure_keyvault.go
deleted file mode 100644
index facfb589..00000000
--- a/pkg/providers/azure_keyvault.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package providers
-
-import (
- "context"
- "fmt"
- "os"
- "path"
-
- "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
- kvauth "github.com/Azure/azure-sdk-for-go/services/keyvault/auth"
- "github.com/Azure/go-autorest/autorest"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-const AzureVaultDomain = "vault.azure.net"
-
-type AzureKeyVaultClient interface {
- SetSecret(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (result keyvault.SecretBundle, err error)
- GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
- GetSecrets(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultPage, err error)
- DeleteSecret(ctx context.Context, vaultBaseURL string, secretName string) (result keyvault.DeletedSecretBundle, err error)
-}
-
-type AzureKeyVault struct {
- client AzureKeyVaultClient
- logger logging.Logger
- vaultName string
- vaultBaseURL string
-}
-
-const azureName = "azure_keyvault"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Azure Key Vault",
- Name: azureName,
- Authentication: "TODO(XXX)",
- ConfigTemplate: `
- # you can mix and match many files
- azure_keyvault:
- env_sync:
- path: azure
- env:
- FOO_BAR:
- path: foobar
- `,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true, Delete: true},
- }
- RegisterProvider(metaInfo, NewAzureKeyVault)
-}
-
-func NewAzureKeyVault(logger logging.Logger) (core.Provider, error) {
- vaultName := os.Getenv("KVAULT_NAME")
- if vaultName == "" {
- return nil, fmt.Errorf("cannot find KVAULT_NAME for azure key vault")
- }
-
- var authorizer autorest.Authorizer
- var err error
-
- if _, ok := os.LookupEnv("AZURE_CLI"); ok {
- authorizer, err = kvauth.NewAuthorizerFromCLI()
- } else {
- authorizer, err = kvauth.NewAuthorizerFromEnvironment()
- }
-
- if err != nil {
- return nil, err
- }
-
- basicClient := keyvault.New()
- basicClient.Authorizer = authorizer
- return &AzureKeyVault{client: &basicClient,
- vaultName: vaultName,
- logger: logger,
- vaultBaseURL: "https://" + vaultName + "." + AzureVaultDomain,
- }, nil
-}
-
-func (a *AzureKeyVault) Name() string {
- return "azure_keyvault"
-}
-
-func (a *AzureKeyVault) Put(p core.KeyPath, val string) error {
- a.logger.WithField("path", p.Path).Debug("set secret")
- _, err := a.client.SetSecret(context.TODO(), a.vaultBaseURL, p.Path, keyvault.SecretSetParameters{
- Value: &val,
- })
- return err
-}
-
-func (a *AzureKeyVault) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := p.SwitchPath(k)
- err := a.Put(ap, v)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-func (a *AzureKeyVault) GetMapping(kp core.KeyPath) ([]core.EnvEntry, error) {
- r := []core.EnvEntry{}
- ctx := context.Background()
- a.logger.WithField("vault_base_url", a.vaultBaseURL).Debug("get secrets")
- secretList, err := a.client.GetSecrets(ctx, a.vaultBaseURL, nil)
- if err != nil {
- return nil, err
- }
-
- for secretList.NotDone() {
- for _, secret := range secretList.Values() {
- value, err := a.getSecret(core.KeyPath{Path: path.Base(*secret.ID)})
- if err != nil {
- return nil, err
- }
- if value.Value != nil {
- ent := kp.FoundWithKey(path.Base(*secret.ID), *value.Value)
- r = append(r, ent)
- }
- }
-
- err := secretList.NextWithContext(ctx)
- if err != nil {
- return nil, err
- }
- }
- return r, nil
-}
-
-func (a *AzureKeyVault) Get(p core.KeyPath) (*core.EnvEntry, error) {
- secretResp, err := a.getSecret(p)
- if err != nil {
- return nil, err
- }
- if secretResp.Value == nil {
- a.logger.WithField("path", p.Path).Debug("secret is empty")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(*secretResp.Value)
- return &ent, nil
-}
-
-func (a *AzureKeyVault) Delete(kp core.KeyPath) error {
- _, err := a.client.DeleteSecret(context.TODO(), a.vaultBaseURL, kp.Path)
- return err
-}
-
-func (a *AzureKeyVault) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", azureName)
-}
-
-func (a *AzureKeyVault) getSecret(kp core.KeyPath) (keyvault.SecretBundle, error) {
- a.logger.WithFields(map[string]interface{}{
- "vault_base_url": a.vaultBaseURL,
- "secret_name": kp.Path,
- }).Debug("get secret")
- return a.client.GetSecret(context.Background(), a.vaultBaseURL, kp.Path, "")
-}
diff --git a/pkg/providers/azure_keyvault_test.go b/pkg/providers/azure_keyvault_test.go
deleted file mode 100644
index 8b504f8a..00000000
--- a/pkg/providers/azure_keyvault_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package providers
-
-import (
- "context"
- "errors"
- "testing"
-
- "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func String(v string) *string { return &v }
-
-func TestAzureKeyVault(t *testing.T) {
-
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockAzureKeyVaultClient(ctrl)
-
- a := AzureKeyVault{
- client: client,
- logger: GetTestLogger(),
- vaultName: "test",
- vaultBaseURL: "https://test/",
- }
-
- path := "settings/prod/billing-svc"
- shazam := "shazam"
-
- secretList := keyvault.SecretListResult{
- Value: &[]keyvault.SecretItem{
- {ID: String("all")},
- {ID: String("all-1")},
- },
- }
-
- stopNext := func(context.Context, keyvault.SecretListResult) (keyvault.SecretListResult, error) {
- return keyvault.SecretListResult{}, nil
- }
- returnSecrets := keyvault.NewSecretListResultPage(secretList, stopNext)
- client.EXPECT().GetSecret(gomock.Any(), a.vaultBaseURL, path, "").Return(keyvault.SecretBundle{Value: &shazam}, nil).AnyTimes()
- client.EXPECT().GetSecret(gomock.Any(), a.vaultBaseURL, "all", "").Return(keyvault.SecretBundle{Value: String("mailman")}, nil).AnyTimes()
- client.EXPECT().GetSecret(gomock.Any(), a.vaultBaseURL, "all-1", "").Return(keyvault.SecretBundle{Value: String("shazam")}, nil).AnyTimes()
- client.EXPECT().GetSecrets(gomock.Any(), a.vaultBaseURL, nil).Return(returnSecrets, nil).AnyTimes()
-
- AssertProvider(t, &a, true)
-
-}
-
-func TestAzureKeyVaultFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockAzureKeyVaultClient(ctrl)
- a := AzureKeyVault{
- client: client,
- logger: GetTestLogger(),
- vaultName: "test",
- vaultBaseURL: "https://test/",
- }
-
- client.EXPECT().GetSecret(gomock.Any(), a.vaultBaseURL, "settings/{{stage}}/billing-svc", "").Return(keyvault.SecretBundle{}, errors.New("error")).AnyTimes()
-
- _, err := a.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/cloudflare_workers_kv.go b/pkg/providers/cloudflare_workers_kv.go
deleted file mode 100644
index a2b0e51e..00000000
--- a/pkg/providers/cloudflare_workers_kv.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package providers
-
-import (
- "context"
- "fmt"
- "os"
- "sort"
-
- cloudflare "github.com/cloudflare/cloudflare-go"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type CloudflareClient interface {
- WriteWorkersKV(ctx context.Context, namespaceID, key string, value []byte) (cloudflare.Response, error)
- WriteWorkersKVBulk(ctx context.Context, namespaceID string, kvs cloudflare.WorkersKVBulkWriteRequest) (cloudflare.Response, error)
- ReadWorkersKV(ctx context.Context, namespaceID string, key string) ([]byte, error)
- ListWorkersKVs(ctx context.Context, namespaceID string) (cloudflare.ListStorageKeysResponse, error)
-}
-
-type Cloudflare struct {
- client CloudflareClient
- logger logging.Logger
-}
-
-const cloudFlareWorkersKVName = "cloudflare_workers_kv"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Cloudflare Workers K/V",
- Name: cloudFlareWorkersKVName,
- Authentication: "requires the following environment variables to be set:\n`CLOUDFLARE_API_KEY`: Your Cloudflare api key.\n`CLOUDFLARE_API_EMAIL`: Your email associated with the api key.\n`CLOUDFLARE_ACCOUNT_ID`: Your account ID.\n",
- ConfigTemplate: `
- TODO(XXX): Missing
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true},
- }
- RegisterProvider(metaInfo, NewCloudflareClient)
-}
-
-func NewCloudflareClient(logger logging.Logger) (core.Provider, error) {
- api, err := cloudflare.New(
- os.Getenv("CLOUDFLARE_API_KEY"),
- os.Getenv("CLOUDFLARE_API_EMAIL"),
- )
-
- if err != nil {
- return nil, err
- }
-
- cloudflare.UsingAccount(os.Getenv("CLOUDFLARE_ACCOUNT_ID"))(api) //nolint
-
- return &Cloudflare{client: api, logger: logger}, nil
-}
-
-func (c *Cloudflare) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", cloudFlareWorkersKVName)
-}
-
-func (c *Cloudflare) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", cloudFlareWorkersKVName)
-}
-
-func (c *Cloudflare) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- entries, err := c.getSecrets(p)
- if err != nil {
- return nil, err
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (c *Cloudflare) Get(p core.KeyPath) (*core.EnvEntry, error) {
- secret, err := c.getSecret(p)
- if err != nil {
- return nil, err
- }
- ent := p.Found(string(secret))
- return &ent, nil
-}
-
-func (c *Cloudflare) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", cloudFlareWorkersKVName)
-}
-
-func (c *Cloudflare) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", cloudFlareWorkersKVName)
-}
-
-func (c *Cloudflare) getSecrets(p core.KeyPath) ([]core.EnvEntry, error) {
- c.logger.WithField("namespace_id", p.Path).Debug("get workers KVs")
- secrets, err := c.client.ListWorkersKVs(context.TODO(), p.Path)
- if err != nil {
- return nil, err
- }
-
- entries := []core.EnvEntry{}
- for _, k := range secrets.Result {
- p.Field = k.Name
- secret, err := c.getSecret(p)
- if err != nil {
- entries = append(entries, p.Missing())
- }
- entries = append(entries, p.FoundWithKey(k.Name, string(secret)))
- }
-
- return entries, nil
-}
-
-func (c *Cloudflare) getSecret(p core.KeyPath) ([]byte, error) {
- k := p.Field
- if k == "" {
- c.logger.WithField("field", p.Field).Debug("`field` attribute not configured. trying to get `env` attribute")
- k = p.Env
- }
- if k == "" {
- return nil, fmt.Errorf("Key required for fetching secrets. Received \"\"") //nolint
- }
- c.logger.WithFields(map[string]interface{}{
- "namespace_id": p.Path,
- "name": k,
- }).Debug("read worker kv")
- return c.client.ReadWorkersKV(context.TODO(), p.Path, k)
-}
diff --git a/pkg/providers/cloudflare_workers_kv_test.go b/pkg/providers/cloudflare_workers_kv_test.go
deleted file mode 100644
index 0c725267..00000000
--- a/pkg/providers/cloudflare_workers_kv_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- cloudflare "github.com/cloudflare/cloudflare-go"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestCloudflareWorkersKV(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockCloudflareClient(ctrl)
-
- // In CloudflareWorkersKV this isn't the path name, but a Workers KV namespace ID.
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- shazam := []byte("shazam")
- mailman := []byte("mailman")
-
- listOut := cloudflare.ListStorageKeysResponse{ //nolint
- cloudflare.Response{},
- []cloudflare.StorageKey{{Name: "MG_KEY"}, {Name: "SMTP_PASS"}},
- cloudflare.ResultInfo{},
- }
-
- client.EXPECT().ReadWorkersKV(gomock.Any(), path, gomock.Eq("MG_KEY")).Return(shazam, nil).AnyTimes()
- client.EXPECT().ReadWorkersKV(gomock.Any(), pathmap, gomock.Eq("MG_KEY")).Return(shazam, nil).AnyTimes()
- client.EXPECT().ReadWorkersKV(gomock.Any(), pathmap, gomock.Eq("SMTP_PASS")).Return(mailman, nil).AnyTimes()
- client.EXPECT().ListWorkersKVs(gomock.Any(), pathmap).Return(listOut, nil).AnyTimes()
-
- s := Cloudflare{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestCloudflareReadWorkersKVFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockCloudflareClient(ctrl)
- client.EXPECT().ReadWorkersKV(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := Cloudflare{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- _, missingLookupKeyError := s.Get(core.KeyPath{Field: "", Env: "", Path: "settings/{{stage}}/billing-svc"})
-
- assert.NotNil(t, err)
- assert.Equal(t, missingLookupKeyError.Error(), "Key required for fetching secrets. Received \"\"")
-}
-
-func TestCloudflareListWorkersKVsFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockCloudflareClient(ctrl)
- client.EXPECT().ListWorkersKVs(gomock.Any(), gomock.Any()).Return(cloudflare.ListStorageKeysResponse{}, errors.New("error")).AnyTimes()
- s := Cloudflare{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.GetMapping(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/cloudflare_workers_secrets.go b/pkg/providers/cloudflare_workers_secrets.go
deleted file mode 100644
index 86156967..00000000
--- a/pkg/providers/cloudflare_workers_secrets.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package providers
-
-import (
- "context"
- "errors"
- "fmt"
- "os"
-
- cloudflare "github.com/cloudflare/cloudflare-go"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-var (
- ErrCloudFlareSourceFieldIsMissing = errors.New("`source` field is missing")
-)
-
-type CloudflareSecretsClient interface {
- SetWorkersSecret(ctx context.Context, script string, req *cloudflare.WorkersPutSecretRequest) (cloudflare.WorkersPutSecretResponse, error)
- DeleteWorkersSecret(ctx context.Context, script, secretName string) (cloudflare.Response, error)
-}
-
-type CloudflareSecrets struct {
- client CloudflareSecretsClient
- logger logging.Logger
-}
-
-const CloudflareWorkersSecretName = "cloudflare_workers_secret"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Cloudflare Workers Secrets",
- Name: CloudflareWorkersSecretName,
- Authentication: "requires the following environment variables to be set:\n`CLOUDFLARE_API_KEY`: Your Cloudflare api key.\n`CLOUDFLARE_API_EMAIL`: Your email associated with the api key.\n`CLOUDFLARE_ACCOUNT_ID`: Your account ID.\n",
- ConfigTemplate: `
- # Configure via environment variables for integration:
- # CLOUDFLARE_API_KEY: Your Cloudflare api key.
- # CLOUDFLARE_API_EMAIL: Your email associated with the api key.
- # CLOUDFLARE_ACCOUNT_ID: Your account ID.
-
- cloudflare_workers_secrets:
- env_sync:
- source: # Mandatory: script field
- env:
- script-value:
- path: foo-secret
- source: # Mandatory: script field
- `,
- Ops: core.OpMatrix{Put: true, PutMapping: true, Delete: true},
- }
- RegisterProvider(metaInfo, NewCloudflareSecretsClient)
-}
-
-func NewCloudflareSecretsClient(logger logging.Logger) (core.Provider, error) {
- api, err := cloudflare.New(
- os.Getenv("CLOUDFLARE_API_KEY"),
- os.Getenv("CLOUDFLARE_API_EMAIL"),
- )
-
- if err != nil {
- return nil, err
- }
-
- cloudflare.UsingAccount(os.Getenv("CLOUDFLARE_ACCOUNT_ID"))(api) //nolint
- return &CloudflareSecrets{client: api, logger: logger}, nil
-}
-
-func (c *CloudflareSecrets) Put(p core.KeyPath, val string) error {
-
- if p.Source == "" {
- return ErrCloudFlareSourceFieldIsMissing
- }
-
- secretName, err := c.getSecretName(p)
- if err != nil {
- return err
- }
-
- secretRequest := cloudflare.WorkersPutSecretRequest{
- Name: secretName,
- Text: val,
- Type: cloudflare.WorkerSecretTextBindingType,
- }
-
- c.logger.WithFields(map[string]interface{}{
- "script": p.Source,
- "name": secretRequest.Name,
- }).Debug("set workers secret")
- _, err = c.client.SetWorkersSecret(context.TODO(), p.Source, &secretRequest)
-
- return err
-}
-
-func (c *CloudflareSecrets) PutMapping(p core.KeyPath, m map[string]string) error {
- if p.Source == "" {
- return ErrCloudFlareSourceFieldIsMissing
- }
-
- for k, v := range m {
- ap := p.WithEnv(fmt.Sprintf("%v/%v", p.Path, k))
-
- err := c.Put(ap, v)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-func (c *CloudflareSecrets) Delete(p core.KeyPath) error {
-
- if p.Source == "" {
- return ErrCloudFlareSourceFieldIsMissing
- }
-
- secretName, err := c.getSecretName(p)
- if err != nil {
- return err
- }
-
- c.logger.WithFields(map[string]interface{}{
- "script": p.Source,
- "name": secretName,
- }).Debug("delete workers secret")
- _, err = c.client.DeleteWorkersSecret(context.TODO(), p.Source, secretName)
- return err
-}
-
-func (c *CloudflareSecrets) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- return nil, fmt.Errorf("%s does not support read functionality", CloudflareWorkersSecretName)
-}
-
-func (c *CloudflareSecrets) Get(p core.KeyPath) (*core.EnvEntry, error) {
- return nil, fmt.Errorf("%s does not support read functionality", CloudflareWorkersSecretName)
-}
-
-func (c *CloudflareSecrets) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement deleteMapping yet", CloudflareWorkersSecretName)
-}
-
-func (c *CloudflareSecrets) getSecretName(p core.KeyPath) (string, error) {
-
- k := p.Field
- if k == "" {
- c.logger.WithField("field", p.Field).Debug("`field` attribute not configured. trying to get `env` attribute")
- k = p.Env
- }
- if k == "" {
- return "", fmt.Errorf("key required for fetching secrets. Received \"\"")
- }
- return k, nil
-
-}
diff --git a/pkg/providers/cloudflare_workers_secrets_test.go b/pkg/providers/cloudflare_workers_secrets_test.go
deleted file mode 100644
index 18dd879d..00000000
--- a/pkg/providers/cloudflare_workers_secrets_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package providers
-
-import (
- "testing"
-
- "github.com/alecthomas/assert"
- cloudflare "github.com/cloudflare/cloudflare-go"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestCloudflareWorkersSecretsPut(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockCloudflareSecretsClient(ctrl)
-
- expectedWorkerPutRequest := cloudflare.WorkersPutSecretRequest{
- Name: "MG_KEY",
- Text: "put-secret",
- Type: "secret_text",
- }
- client.EXPECT().SetWorkersSecret(gomock.Any(), "script-key", &expectedWorkerPutRequest).Return(cloudflare.WorkersPutSecretResponse{}, nil).AnyTimes()
-
- c := CloudflareSecrets{
- client: client,
- logger: GetTestLogger(),
- }
- assert.Nil(t, c.Put(core.KeyPath{Field: "MG_KEY", Source: "script-key"}, "put-secret"))
- assert.Nil(t, c.Put(core.KeyPath{Env: "MG_KEY", Source: "script-key"}, "put-secret"))
- assert.NotNil(t, c.Put(core.KeyPath{Path: "script-key"}, "put-secret"))
- assert.EqualError(t, c.Delete(core.KeyPath{Field: "MG_KEY"}), ErrCloudFlareSourceFieldIsMissing.Error())
-}
-
-func TestCloudflareWorkersSecretsDelete(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockCloudflareSecretsClient(ctrl)
-
- client.EXPECT().DeleteWorkersSecret(gomock.Any(), "script-key", "MG_KEY").Return(cloudflare.Response{}, nil).AnyTimes()
-
- c := CloudflareSecrets{
- client: client,
- logger: GetTestLogger(),
- }
-
- assert.Nil(t, c.Delete(core.KeyPath{Field: "MG_KEY", Source: "script-key"}))
- assert.EqualError(t, c.Delete(core.KeyPath{Field: "MG_KEY"}), ErrCloudFlareSourceFieldIsMissing.Error())
- assert.NotNil(t, c.Delete(core.KeyPath{Path: "script-key"}))
-
-}
diff --git a/pkg/providers/consul.go b/pkg/providers/consul.go
deleted file mode 100644
index d1cd994e..00000000
--- a/pkg/providers/consul.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package providers
-
-import (
- "fmt"
- "sort"
-
- "github.com/hashicorp/consul/api"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-type ConsulClient interface {
- Get(key string, q *api.QueryOptions) (*api.KVPair, *api.QueryMeta, error)
- List(prefix string, q *api.QueryOptions) (api.KVPairs, *api.QueryMeta, error)
- Put(p *api.KVPair, q *api.WriteOptions) (*api.WriteMeta, error)
-}
-
-type Consul struct {
- client ConsulClient
- logger logging.Logger
-}
-
-const consulName = "consul"
-
-//nolint
-func init() {
- metaInto := core.MetaInfo{
- Description: "Consul",
- Name: consulName,
- Authentication: "If you have the Consul CLI working and configured, there's no special action to take.\nConfiguration is environment based, as defined by client standard. See variables [here](https://github.com/hashicorp/consul/blob/master/api/api.go#L28).",
- ConfigTemplate: `
- # Configure via environment:
- # CONSUL_HTTP_ADDR
- consul:
- env_sync:
- path: redis/config
- env:
- ETC_DSN:
- path: redis/config/foobar
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
- }
- RegisterProvider(metaInto, NewConsul)
-}
-
-func NewConsul(logger logging.Logger) (core.Provider, error) {
- df := api.DefaultConfig()
- client, err := api.NewClient(df)
- if err != nil {
- return nil, err
- }
- kv := client.KV()
- return &Consul{client: kv, logger: logger}, nil
-}
-
-func (a *Consul) Put(p core.KeyPath, val string) error {
- a.logger.WithField("path", p.Path).Debug("put value")
- _, err := a.client.Put(&api.KVPair{
- Key: p.Path,
- Value: []byte(val),
- }, &api.WriteOptions{})
-
- return err
-}
-
-func (a *Consul) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := p.SwitchPath(fmt.Sprintf("%v/%v", p.Path, k))
- err := a.Put(ap, v)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-func (a *Consul) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- kvs, err := a.getSecrets(p)
- if err != nil {
- return nil, err
- }
- entries := []core.EnvEntry{}
- for _, kv := range kvs {
- k := kv.Key
- v := string(kv.Value)
- seg := utils.LastSegment(k)
- entries = append(entries, p.FoundWithKey(seg, v))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (a *Consul) Get(p core.KeyPath) (*core.EnvEntry, error) {
- kv, err := a.getSecret(p)
- if err != nil {
- return nil, fmt.Errorf("%v cannot get value: %v", consulName, err)
- }
-
- if kv == nil {
- a.logger.WithField("path", p.Path).Debug("kv is empty")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(string(kv.Value))
- return &ent, nil
-}
-
-func (a *Consul) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", consulName)
-}
-
-func (a *Consul) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", consulName)
-}
-
-func (a *Consul) getSecrets(kp core.KeyPath) (api.KVPairs, error) {
- a.logger.WithField("path", kp.Path).Debug("get all keys under a prefix")
- kvs, _, err := a.client.List(kp.Path, nil)
- return kvs, err
-}
-
-func (a *Consul) getSecret(kp core.KeyPath) (*api.KVPair, error) {
- a.logger.WithField("path", kp.Path).Debug("get value")
- kv, _, err := a.client.Get(kp.Path, nil)
- if err != nil {
- return nil, err
- }
- return kv, nil
-}
diff --git a/pkg/providers/consul_test.go b/pkg/providers/consul_test.go
deleted file mode 100644
index 93b3fd84..00000000
--- a/pkg/providers/consul_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
- "github.com/hashicorp/consul/api"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestConsul(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockConsulClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- out := api.KVPair{
- Key: "MG_KEY",
- Value: []byte("shazam"),
- }
- outlist := api.KVPairs{
- {
- Key: "SMTP_PASS",
- Value: []byte("mailman"),
- },
- {
- Key: "MG_KEY",
- Value: []byte("shazam"),
- },
- }
- client.EXPECT().Get(gomock.Eq(path), gomock.Any()).Return(&out, nil, nil).AnyTimes()
- client.EXPECT().List(gomock.Eq(pathmap), gomock.Any()).Return(outlist, nil, nil).AnyTimes()
- s := Consul{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestConsulFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockConsulClient(ctrl)
- client.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, nil, errors.New("error")).AnyTimes()
- s := Consul{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/cyberark_conjur.go b/pkg/providers/cyberark_conjur.go
deleted file mode 100644
index 36315b98..00000000
--- a/pkg/providers/cyberark_conjur.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
-
- "github.com/cyberark/conjur-api-go/conjurapi"
- "github.com/cyberark/conjur-api-go/conjurapi/authn"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type ResourceFilter struct {
- Kind string
- Search string
- Limit int
- Offset int
-}
-
-type ConjurClient interface {
- AddSecret(variableID string, secretValue string) error
- RetrieveSecret(variableID string) ([]byte, error)
-}
-
-type CyberArkConjur struct {
- client ConjurClient
- logger logging.Logger
-}
-
-const ConjurName = "cyberark_conjur"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "CyberArk Conjure",
- Name: ConjurName,
- Authentication: "Requires a username and API key populated in your environment:\n* `CONJUR_AUTHN_LOGIN`\n* `CONJUR_AUTHN_API_KEY`",
- ConfigTemplate: `
- # https://conjur.org
- # set CONJUR_AUTHN_LOGIN and CONJUR_AUTHN_API_KEY env vars
- # set .conjurrc file in user's home directory
- cyberark_conjur:
- env:
- FOO_BAR:
- path: /secrets/foo/bar
-`,
- Ops: core.OpMatrix{Get: true, Put: true},
- }
- RegisterProvider(metaInfo, NewConjurClient)
-}
-
-func NewConjurClient(logger logging.Logger) (core.Provider, error) {
- config, err := conjurapi.LoadConfig()
- if err != nil {
- return nil, err
- }
-
- conjur, err := conjurapi.NewClientFromKey(config,
- authn.LoginPair{
- Login: os.Getenv("CONJUR_AUTHN_LOGIN"),
- APIKey: os.Getenv("CONJUR_AUTHN_API_KEY"),
- },
- )
- if err != nil {
- return nil, err
- }
-
- return &CyberArkConjur{client: conjur, logger: logger}, nil
-}
-
-func (c *CyberArkConjur) Put(p core.KeyPath, val string) error {
- err := c.putSecret(p, val)
-
- return err
-}
-func (c *CyberArkConjur) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement put mapping yet", ConjurName)
-}
-
-func (c *CyberArkConjur) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- return nil, fmt.Errorf("provider %q does not implement get mapping yet", ConjurName)
-}
-
-func (c *CyberArkConjur) Get(p core.KeyPath) (*core.EnvEntry, error) {
- secret, err := c.getSecret(p)
- if err != nil {
- return nil, err
- }
- if secret == nil {
- c.logger.WithField("path", p.Path).Debug("secret is empty")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(string(secret))
- return &ent, nil
-}
-
-func (c *CyberArkConjur) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", ConjurName)
-}
-
-func (c *CyberArkConjur) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", ConjurName)
-}
-
-func (c *CyberArkConjur) getSecret(kp core.KeyPath) ([]byte, error) {
- c.logger.WithField("path", kp.Path).Debug("get a secret from the path")
- return c.client.RetrieveSecret(kp.Path)
-}
-
-func (c *CyberArkConjur) putSecret(kp core.KeyPath, val string) error {
- c.logger.WithField("path", kp.Path).Debug("create secret")
- return c.client.AddSecret(kp.Path, val)
-}
diff --git a/pkg/providers/doppler.go b/pkg/providers/doppler.go
deleted file mode 100644
index f9a8ca3a..00000000
--- a/pkg/providers/doppler.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package providers
-
-// TODO(XXX): remove this provider, no support/specialty
-
-import (
- "fmt"
- "sort"
-
- "github.com/DopplerHQ/cli/pkg/configuration"
- "github.com/DopplerHQ/cli/pkg/http"
- "github.com/DopplerHQ/cli/pkg/models"
- "github.com/DopplerHQ/cli/pkg/utils"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type DopplerClient interface {
- GetSecrets(host string, verifyTLS bool, apiKey string, project string, config string) ([]byte, http.Error)
-}
-
-type dopplerClient struct{}
-
-func (dopplerClient) GetSecrets(host string, verifyTLS bool, apiKey, project, config string) ([]byte, http.Error) {
- return http.GetSecrets(host, verifyTLS, apiKey, project, config)
-}
-
-type Doppler struct {
- client DopplerClient
- logger logging.Logger
- config models.ScopedOptions
-}
-
-const DopplerName = "doppler"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Doppler",
- Name: DopplerName,
- Ops: core.OpMatrix{Get: true},
- }
-
- RegisterProvider(metaInfo, NewDoppler)
-}
-
-func NewDoppler(logger logging.Logger) (core.Provider, error) {
- configuration.Setup()
- configuration.LoadConfig()
-
- return &Doppler{
- client: dopplerClient{},
- logger: logger,
- config: configuration.Get(configuration.Scope),
- }, nil
-}
-
-func (h *Doppler) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", DopplerName)
-}
-func (h *Doppler) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", DopplerName)
-}
-
-func (h *Doppler) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- s, err := h.getConfig(p.Path)
- if err != nil {
- return nil, err
- }
-
- entries := []core.EnvEntry{}
- for k, v := range s {
- entries = append(entries, p.FoundWithKey(k, v.ComputedValue))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (h *Doppler) Get(p core.KeyPath) (*core.EnvEntry, error) {
- s, err := h.getConfig(p.Path)
- if err != nil {
- return nil, err
- }
-
- key := p.Env
- if p.Field != "" {
- h.logger.WithField("path", p.Path).Debug("`env` attribute not configured. take `field` attribute")
- key = p.Field
- }
-
- v, ok := s[key]
- if !ok {
- h.logger.WithFields(map[string]interface{}{"key": key, "path": p.Path}).Debug("the given key not exists")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(v.ComputedValue)
-
- return &ent, nil
-}
-
-func (h *Doppler) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", DopplerName)
-}
-
-func (h *Doppler) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", DopplerName)
-}
-
-func (h *Doppler) getConfig(config string) (map[string]models.ComputedSecret, error) {
- h.logger.Debug("get secrets")
- r, herr := h.client.GetSecrets(
- h.config.APIHost.Value,
- utils.GetBool(h.config.VerifyTLS.Value, true),
- h.config.Token.Value,
- h.config.EnclaveProject.Value,
- config,
- )
- if !herr.IsNil() {
- return nil, herr.Err
- }
-
- return models.ParseSecrets(r)
-}
diff --git a/pkg/providers/doppler_test.go b/pkg/providers/doppler_test.go
deleted file mode 100644
index b349f773..00000000
--- a/pkg/providers/doppler_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package providers
-
-import (
- "testing"
-
- "github.com/DopplerHQ/cli/pkg/http"
- "github.com/DopplerHQ/cli/pkg/models"
- "github.com/golang/mock/gomock"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestDoppler(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockDopplerClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- out := []byte(`{
- "secrets": {
- "MG_KEY": {
- "computed": "shazam"
- },
- "SMTP_PASS": {
- "computed": "mailman"
- }
- }
- }`)
-
- client.EXPECT().GetSecrets(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Eq(path)).Return(out, http.Error{}).AnyTimes()
- client.EXPECT().GetSecrets(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Eq(pathmap)).Return(out, http.Error{}).AnyTimes()
-
- s := Doppler{
- client: client,
- config: models.ScopedOptions{},
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
diff --git a/pkg/providers/dotenv.go b/pkg/providers/dotenv.go
deleted file mode 100644
index 290bc895..00000000
--- a/pkg/providers/dotenv.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package providers
-
-import (
- "os"
- "path"
- "sort"
-
- "github.com/joho/godotenv"
- "github.com/mitchellh/go-homedir"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-const (
- filePerm = 0644
- dirPerm = 0755
-)
-
-type DotEnvClient interface {
- Read(p string) (map[string]string, error)
- Write(p string, kvs map[string]string) error
- Exists(p string) (bool, error)
- Delete(p string) error
-}
-type DotEnvReader struct {
-}
-
-func (d *DotEnvReader) Read(p string) (map[string]string, error) {
- p, err := homedir.Expand(p)
- if err != nil {
- return nil, err
- }
-
- content, err := os.ReadFile(p)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, nil
- }
-
- return nil, err
- }
-
- return godotenv.Unmarshal(string(content))
-}
-
-func (d *DotEnvReader) Write(p string, kvs map[string]string) error {
- content, err := godotenv.Marshal(kvs)
- if err != nil {
- return err
- }
-
- p, err = homedir.Expand(p)
- if err != nil {
- return err
- }
-
- // ensure all subdirectories exist
- err = os.MkdirAll(path.Dir(p), dirPerm)
- if err != nil {
- return err
- }
-
- return os.WriteFile(p, []byte(content), filePerm)
-}
-
-func (d *DotEnvReader) Exists(p string) (bool, error) {
- p, err := homedir.Expand(p)
- if err != nil {
- return false, err
- }
-
- _, err = os.Stat(p)
- if err != nil {
- if os.IsNotExist(err) {
- return false, nil
- }
-
- return false, err
- }
-
- return true, nil
-}
-
-func (d *DotEnvReader) Delete(p string) error {
- p, err := homedir.Expand(p)
- if err != nil {
- return err
- }
-
- return os.Remove(p)
-}
-
-type Dotenv struct {
- client DotEnvClient
- logger logging.Logger
-}
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: ".env",
- Authentication: "",
- Name: "dotenv",
- ConfigTemplate: `
- # you can mix and match many files
- dotenv:
- env_sync:
- path: ~/my-dot-env.env
- env:
- FOO_BAR:
- path: ~/my-dot-env.env
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true, Delete: true, DeleteMapping: true},
- }
-
- RegisterProvider(metaInfo, NewDotenv)
-}
-
-func NewDotenv(logger logging.Logger) (core.Provider, error) {
- return &Dotenv{
- client: &DotEnvReader{},
- logger: logger,
- }, nil
-}
-
-func (a *Dotenv) Put(p core.KeyPath, val string) error {
- k := p.EffectiveKey()
- return a.PutMapping(p, map[string]string{k: val})
-}
-
-func (a *Dotenv) PutMapping(kp core.KeyPath, m map[string]string) error {
- exists, err := a.client.Exists(kp.Path)
- if err != nil {
- a.logger.WithField("path", kp.Path).Debug("secret path not exists")
- return err
- }
-
- if !exists {
- a.logger.WithField("path", kp.Path).Debug("set secret")
- return a.client.Write(kp.Path, m)
- }
-
- a.logger.WithField("path", kp.Path).Debug("read secret")
- // get a fresh copy of a hash
- secrets, err := a.client.Read(kp.Path)
- if err != nil {
- return err
- }
-
- secrets = utils.Merge(secrets, m)
- a.logger.WithField("path", kp.Path).Debug("merge and write secrets to path")
- return a.client.Write(kp.Path, secrets)
-}
-
-func (a *Dotenv) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- a.logger.WithField("path", p.Path).Debug("read secret")
- kvs, err := a.client.Read(p.Path)
- if err != nil {
- return nil, err
- }
- var entries []core.EnvEntry
- for k, v := range kvs {
- entries = append(entries, p.FoundWithKey(k, v))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (a *Dotenv) Get(p core.KeyPath) (*core.EnvEntry, error) { //nolint:dupl
- a.logger.WithField("path", p.Path).Debug("read secret")
- kvs, err := a.client.Read(p.Path)
- if err != nil {
- return nil, err
- }
-
- k := p.EffectiveKey()
- val, ok := kvs[k]
- if !ok {
- a.logger.WithFields(map[string]interface{}{"path": p.Path, "key": k}).Debug("key not found")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(val)
- return &ent, nil
-}
-
-func (a *Dotenv) Delete(kp core.KeyPath) error {
- a.logger.WithField("path", kp.Path).Debug("read secret")
- kvs, err := a.client.Read(kp.Path)
- if err != nil {
- return err
- }
-
- k := kp.EffectiveKey()
- delete(kvs, k)
-
- if len(kvs) == 0 {
- return a.DeleteMapping(kp)
- }
-
- p, err := homedir.Expand(kp.Path)
- if err != nil {
- return err
- }
-
- return a.client.Write(p, kvs)
-}
-
-func (a *Dotenv) DeleteMapping(kp core.KeyPath) error {
- exists, err := a.client.Exists(kp.Path)
- if err != nil {
- a.logger.WithField("path", kp.Path).Debug("secret path not exists")
- return err
- }
-
- if !exists {
- // already deleted
- a.logger.WithField("path", kp.Path).Debug("secret already deleted")
- return nil
- }
-
- a.logger.WithField("path", kp.Path).Debug("delete key")
- return a.client.Delete(kp.Path)
-}
diff --git a/pkg/providers/dotenv_test.go b/pkg/providers/dotenv_test.go
deleted file mode 100644
index e0f27a49..00000000
--- a/pkg/providers/dotenv_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestDotenv(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockDotEnvClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- out := map[string]string{
- "MG_KEY": "shazam",
- "SMTP_PASS": "mailman",
- }
- client.EXPECT().Read(gomock.Eq(path)).Return(out, nil).AnyTimes()
- client.EXPECT().Read(gomock.Eq(pathmap)).Return(out, nil).AnyTimes()
- client.EXPECT().Read(gomock.Eq(pathmap)).Return(out, nil).AnyTimes()
- s := Dotenv{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestDotenvFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockDotEnvClient(ctrl)
- client.EXPECT().Read(gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := Dotenv{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/etcd.go b/pkg/providers/etcd.go
deleted file mode 100644
index f8d8e6f2..00000000
--- a/pkg/providers/etcd.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package providers
-
-import (
- "context"
- "crypto/tls"
- "fmt"
- "sort"
-
- "os"
- "strings"
-
- spb "go.etcd.io/etcd/api/v3/mvccpb"
- clientv3 "go.etcd.io/etcd/client/v3"
-
- "go.etcd.io/etcd/pkg/v3/transport"
-
- "github.com/samber/lo"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-type EtcdClient interface {
- Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error)
- Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error)
-}
-type Etcd struct {
- client EtcdClient
- logger logging.Logger
-}
-
-const EtcdName = "etcd"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Etcd",
- Name: EtcdName,
- Authentication: "These environment variables need to be populated\n* `ETCDCTL_ENDPOINTS`\nFor TLS:\n* `ETCDCTL_CA_FILE`\n* `ETCDCTL_CERT_FILE`\n* `ETCDCTL_KEY_FILE`",
- ConfigTemplate: `
- # Configure via environment:
- # ETCDCTL_ENDPOINTS
- # tls:
- # ETCDCTL_CA_FILE
- # ETCDCTL_CERT_FILE
- # ETCDCTL_KEY_FILE
- etcd:
- env_sync:
- path: /prod/foo
- env:
- ETC_DSN:
- path: /prod/foo/bar
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
- }
- RegisterProvider(metaInfo, NewEtcd)
-}
-
-func NewEtcd(logger logging.Logger) (core.Provider, error) {
- epstring := os.Getenv("ETCDCTL_ENDPOINTS")
- if epstring == "" {
- return nil, fmt.Errorf("cannot find ETCDCTL_ENDPOINTS for etcd")
- }
-
- eps := lo.Map(strings.Split(epstring, ","), func(s string, _ int) string { return strings.Trim(s, " ") })
- client, err := newClient(eps)
- if err != nil {
- return nil, err
- }
- return &Etcd{client: client, logger: logger}, nil
-}
-
-func (a *Etcd) Put(p core.KeyPath, val string) error {
- a.logger.WithField("path", p.Path).Debug("create key")
- _, err := a.client.Put(context.TODO(), p.Path, val)
- return err
-}
-func (a *Etcd) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := p.SwitchPath(fmt.Sprintf("%v/%v", p.Path, k))
- err := a.Put(ap, v)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-func (a *Etcd) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- kvs, err := a.getSecret(p, clientv3.WithPrefix())
- if err != nil {
- return nil, err
- }
- entries := []core.EnvEntry{}
- for _, kv := range kvs {
- k := string(kv.Key)
- v := string(kv.Value)
- seg := utils.LastSegment(k)
- entries = append(entries, p.FoundWithKey(seg, v))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (a *Etcd) Get(p core.KeyPath) (*core.EnvEntry, error) {
- kvs, err := a.getSecret(p)
- if err != nil {
- return nil, err
- }
- for _, kv := range kvs {
- k := string(kv.Key)
- v := string(kv.Value)
- if k == p.Path {
- ent := p.Found(v)
- return &ent, nil
- }
- }
-
- ent := p.Missing()
- return &ent, nil
-}
-
-func (a *Etcd) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", EtcdName)
-}
-
-func (a *Etcd) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", EtcdName)
-}
-
-func (a *Etcd) getSecret(kp core.KeyPath, opts ...clientv3.OpOption) ([]*spb.KeyValue, error) {
- a.logger.WithField("path", kp.Path).Debug("get key")
- res, err := a.client.Get(context.TODO(), kp.Path, opts...)
- if err != nil {
- return nil, err
- }
- return res.Kvs, nil
-}
-
-func newClient(eps []string) (*clientv3.Client, error) {
- tr, err := getTransport()
- if err != nil {
- return nil, err
- }
-
- cfg := clientv3.Config{
- TLS: tr,
- Endpoints: eps,
- }
- return clientv3.New(cfg)
-}
-func getTransport() (*tls.Config, error) {
- cafile := os.Getenv("ETCDCTL_CA_FILE")
- certfile := os.Getenv("ETCDCTL_CERT_FILE")
- keyfile := os.Getenv("ETCDCTL_KEY_FILE")
- if cafile == "" || certfile == "" || keyfile == "" {
- return nil, nil
- }
-
- tlsinfo := &transport.TLSInfo{
- CertFile: certfile,
- KeyFile: keyfile,
- TrustedCAFile: cafile,
- }
- return tlsinfo.ClientConfig()
-}
diff --git a/pkg/providers/etcd_test.go b/pkg/providers/etcd_test.go
deleted file mode 100644
index ca409b95..00000000
--- a/pkg/providers/etcd_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
- spb "go.etcd.io/etcd/api/v3/mvccpb"
- clientv3 "go.etcd.io/etcd/client/v3"
-)
-
-func TestEtcd(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockEtcdClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
-
- kv1 := &spb.KeyValue{
- Key: []byte("settings/prod/billing-svc"),
- Value: []byte("shazam"),
- }
- kv2 := &spb.KeyValue{
- Key: []byte("settings/prod/billing-svc"),
- Value: []byte("mailman"),
- }
- out := clientv3.GetResponse{
- Kvs: []*spb.KeyValue{kv1},
- }
- outmap := clientv3.GetResponse{
- Kvs: []*spb.KeyValue{kv2, kv1},
- }
- client.EXPECT().Get(gomock.Any(), gomock.Eq(path)).Return(&out, nil).AnyTimes()
- client.EXPECT().Get(gomock.Any(), gomock.Eq(pathmap), gomock.Any()).Return(&outmap, nil).AnyTimes()
- s := Etcd{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestEtcdFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockEtcdClient(ctrl)
- client.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := Etcd{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/export.go b/pkg/providers/export.go
deleted file mode 100644
index c49eec13..00000000
--- a/pkg/providers/export.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package providers
-
-import (
- "encoding/json"
-
- "github.com/spectralops/teller/pkg/core"
-)
-
-type TellerExport struct {
- Version string `json:"version"`
- Providers map[string]core.MetaInfo `json:"providers"`
-}
-
-func GenerateProvidersMetaJSON(version string, providersMetaList []core.MetaInfo) (string, error) {
- providersMetaMap := make(map[string]core.MetaInfo)
- for _, provider := range providersMetaList {
- providersMetaMap[provider.Name] = provider
- }
-
- tellerObject := TellerExport{
- Version: version,
- Providers: providersMetaMap,
- }
-
- jsonOutput, err := json.MarshalIndent(tellerObject, "", " ")
-
- if err != nil {
- return "", err
- }
-
- return string(jsonOutput), nil
-}
diff --git a/pkg/providers/export_test.go b/pkg/providers/export_test.go
deleted file mode 100644
index 7b7245fb..00000000
--- a/pkg/providers/export_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package providers
-
-import (
- "encoding/json"
- "os"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/spectralops/teller/pkg/core"
-)
-
-func TestGenerateProvidersMetaJSON(t *testing.T) {
- var providersData = []core.MetaInfo{
- {
- Name: "Provider_1",
- Description: "Description of Provider 1",
- Authentication: "Provider 1 authentication instructions",
- ConfigTemplate: "Provider 1 config template",
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
- },
- {
- Name: "Provider_2",
- Description: "Description of Provider 2",
- Authentication: "Provider 2 authentication instructions",
- ConfigTemplate: "Provider 2 config template",
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
- },
- }
-
- providersMetaJSON, _ := GenerateProvidersMetaJSON("1.1", providersData)
- providersFileContent, _ := os.ReadFile("../../fixtures/providers-export/providers-meta.json")
-
- actualProvidersJSON, _ := json.Marshal(providersMetaJSON)
- expectedProvidersJSON, _ := json.Marshal(string(providersFileContent))
-
- assert.Equal(t, string(actualProvidersJSON), string(expectedProvidersJSON))
-}
diff --git a/pkg/providers/filesystem.go b/pkg/providers/filesystem.go
deleted file mode 100644
index 6f3f8837..00000000
--- a/pkg/providers/filesystem.go
+++ /dev/null
@@ -1,175 +0,0 @@
-package providers
-
-import (
- "bytes"
- "errors"
- "fmt"
- "os"
- "path"
- "path/filepath"
- "strings"
- "unicode/utf8"
-
- "github.com/karrick/godirwalk"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type FileSystem struct {
- logger logging.Logger
- rootDirectory string
-}
-
-const FileSystemName = "FileSystem"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "File system",
- Name: FileSystemName,
- Authentication: "",
- ConfigTemplate: `
- filesystem:
- env_sync:
- path: redis/config
- env:
- ETC_DSN:
- path: redis/config/foobar
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true, Delete: true},
- }
-
- RegisterProvider(metaInfo, NewFileSystem)
-}
-
-// NewFileSystem creates new provider instance
-func NewFileSystem(logger logging.Logger) (core.Provider, error) {
- return &FileSystem{
- logger: logger,
- rootDirectory: "",
- }, nil
-}
-
-// Put will create a new single entry
-func (f *FileSystem) Put(p core.KeyPath, val string) error {
- return f.writeFile(f.getFilePath(p.Path), val)
-}
-
-// PutMapping will create a multiple entries
-func (f *FileSystem) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := p.SwitchPath(fmt.Sprintf("%v/%v", f.getFilePath(p.Path), k))
- err := f.Put(ap, v)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// GetMapping returns a multiple entries
-func (f *FileSystem) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
-
- findings := []core.EnvEntry{}
- err := godirwalk.Walk(f.getFilePath(p.Path), &godirwalk.Options{
- Callback: func(osPathname string, de *godirwalk.Dirent) error {
-
- if !de.IsRegular() || strings.HasPrefix(de.Name(), ".") {
- return nil
- }
- content, err := f.readFile(osPathname)
- if err != nil {
- f.logger.WithError(err).WithField("path", p.Path).Debug("file not found in path")
- return nil
- }
-
- if !f.IsText(content) {
- return nil
- }
- findings = append(findings, p.FoundWithKey(strings.Replace(path.Clean(osPathname), fmt.Sprintf("%s/", p.Path), "", 1), string(content)))
-
- return nil
- },
- Unsorted: true,
- })
-
- return findings, err
-}
-
-// Get returns a single entry
-func (f *FileSystem) Get(p core.KeyPath) (*core.EnvEntry, error) {
- content, err := f.readFile(f.getFilePath(p.Path))
- if err != nil {
- f.logger.WithError(err).WithField("path", p.Path).Debug("file not found in path")
- return nil, err
- }
- ent := p.Found(string(content))
- return &ent, nil
-}
-
-// Delete will delete entry
-func (f *FileSystem) Delete(kp core.KeyPath) error {
- deletePath := f.getFilePath(kp.Path)
- fileInfo, err := os.Stat(deletePath)
- if err != nil {
- return err
- }
- // to make the delete safely, we allow deleting a single file only
- if fileInfo.IsDir() {
- return errors.New("delete folder is not supported")
- }
-
- return os.Remove(deletePath)
-}
-
-// DeleteMapping will delete the given path
-func (f *FileSystem) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("provider mapping %s does not implement delete yet", FileSystemName)
-}
-
-func (f *FileSystem) getFilePath(p string) string {
- if f.rootDirectory == "" {
- return p
- }
- return filepath.Join(f.rootDirectory, p)
-}
-
-func (f *FileSystem) writeFile(to, val string) error {
- f.logger.WithField("path", to).Info("put entry value")
- dir, _ := path.Split(to)
- if _, err := os.Stat(dir); os.IsNotExist(err) {
- f.logger.WithField("dir", dir).Debug("create folder path")
- err = os.MkdirAll(dir, os.ModePerm)
- if err != nil {
- return err
- }
- }
- return os.WriteFile(to, []byte(val), 0600) //nolint
-}
-
-func (f *FileSystem) readFile(filePath string) ([]byte, error) {
-
- content, err := os.ReadFile(filePath)
- if err != nil {
- return nil, err
- }
- content = bytes.TrimSuffix(content, []byte("\n"))
- content = bytes.TrimSuffix(content, []byte("\r\n"))
- return content, nil
-}
-
-func (f *FileSystem) IsText(s []byte) bool {
- const max = 1024
- if len(s) > max {
- s = s[0:max]
- }
- for i, c := range string(s) {
- if i+utf8.UTFMax > len(s) {
- break
- }
- if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
- return false
- }
- }
- return true
-}
diff --git a/pkg/providers/filesystem_test.go b/pkg/providers/filesystem_test.go
deleted file mode 100644
index ba46ee46..00000000
--- a/pkg/providers/filesystem_test.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/spectralops/teller/pkg/core"
-)
-
-func createMockDirectoryStructure(f *FileSystem) error {
- createFileSystemData := []struct {
- path string
- fileName string
- value string
- }{
- {"settings/prod", "billing-svc", "shazam"},
- {"settings/prod/billing/all", "secret-a", "mailman"},
- {"settings/prod/billing/all", "secret-b", "shazam"},
- {"settings/prod/billing/all/folder", "secret-c", "shazam-1"},
- }
-
- for _, filePath := range createFileSystemData {
- err := os.MkdirAll(filepath.Join(f.rootDirectory, filePath.path), os.ModePerm)
- if err != nil {
- return err
- }
- err = os.WriteFile(filepath.Join(f.rootDirectory, filePath.path, filePath.fileName), []byte(filePath.value), 0644)
- if err != nil {
- return err
- }
-
- }
- return nil
-}
-
-func TestFileSystem(t *testing.T) {
-
- tempFolder, err := os.MkdirTemp(os.TempDir(), "teller-filesystem")
- assert.Nil(t, err)
- defer os.RemoveAll(tempFolder)
-
- f := &FileSystem{
- logger: GetTestLogger(),
- rootDirectory: tempFolder,
- }
-
- err = createMockDirectoryStructure(f)
- assert.NoError(t, err)
-
- AssertProvider(t, f, false)
- ents, err := f.GetMapping(core.KeyPath{Path: "settings/prod/billing/all", Decrypt: true})
- assert.Nil(t, err)
- assert.Equal(t, len(ents), 3)
-}
-
-func TestFileSystemSetEntry(t *testing.T) {
-
- tempFolder, err := os.MkdirTemp(os.TempDir(), "teller-filesystem")
- assert.Nil(t, err)
- defer os.RemoveAll(tempFolder)
-
- f := &FileSystem{
- logger: GetTestLogger(),
- rootDirectory: tempFolder,
- }
- err = createMockDirectoryStructure(f)
- assert.NoError(t, err)
-
- destFile := "create/newfolder/foo"
- _, err = f.Get(core.KeyPath{Path: destFile, Decrypt: true})
- assert.NotEmpty(t, err)
-
- err = f.Put(core.KeyPath{Path: destFile}, "new-val")
- assert.Nil(t, err)
-
- results, err := f.Get(core.KeyPath{Path: destFile, Decrypt: true})
- assert.Nil(t, err)
- assert.NotEmpty(t, results)
-
-}
-
-func TestFileSystemDeleteEntry(t *testing.T) {
-
- tempFolder, err := os.MkdirTemp(os.TempDir(), "teller-filesystem")
- assert.Nil(t, err)
- defer os.RemoveAll(tempFolder)
-
- f := &FileSystem{
- logger: GetTestLogger(),
- rootDirectory: tempFolder,
- }
- err = createMockDirectoryStructure(f)
- assert.NoError(t, err)
-
- destFile := "settings/prod/billing-svc"
- _, err = f.Get(core.KeyPath{Path: destFile, Decrypt: true})
- assert.Nil(t, err)
-
- err = f.Delete(core.KeyPath{Path: destFile, Decrypt: true})
- assert.Nil(t, err)
-
- _, err = f.Get(core.KeyPath{Path: destFile, Decrypt: true})
- assert.NotNil(t, err)
-
-}
diff --git a/pkg/providers/github.go b/pkg/providers/github.go
deleted file mode 100644
index 32dd2e99..00000000
--- a/pkg/providers/github.go
+++ /dev/null
@@ -1,217 +0,0 @@
-package providers
-
-import (
- "context"
- crypto_rand "crypto/rand"
- "encoding/base64"
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/google/go-github/v43/github"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "golang.org/x/crypto/nacl/box"
- "golang.org/x/oauth2"
-)
-
-const (
- gitHubSplitPathCount = 2
-)
-
-// GitHubActionClient describe the GitHub action client
-type GitHubActionClient interface {
- GetRepoPublicKey(ctx context.Context, owner, repo string) (*github.PublicKey, *github.Response, error)
- CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *github.EncryptedSecret) (*github.Response, error)
- DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*github.Response, error)
- ListRepoSecrets(ctx context.Context, owner, repo string, opts *github.ListOptions) (*github.Secrets, *github.Response, error)
-}
-
-type GitHub struct {
- clientActions GitHubActionClient
- logger logging.Logger
-}
-
-// NewGitHub create new GitHub provider
-const GithubName = "GitHub"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Github",
- Authentication: "Requires `GITHUB_AUTH_TOKEN`",
- Name: GithubName,
- ConfigTemplate: `
- # Configure via environment variables for integration:
- # GITHUB_AUTH_TOKEN: GitHub token
-
- github:
- env_sync:
- path: owner/github-repo
- env:
- script-value:
- path: owner/github-repo
-`,
- Ops: core.OpMatrix{Put: true, PutMapping: true, Delete: true, DeleteMapping: true},
- }
-
- RegisterProvider(metaInfo, NewGitHub)
-}
-
-func NewGitHub(logger logging.Logger) (core.Provider, error) {
- token := os.Getenv("GITHUB_AUTH_TOKEN")
- if token == "" {
- return nil, errors.New("missing `GITHUB_AUTH_TOKEN`")
- }
-
- ts := oauth2.StaticTokenSource(
- &oauth2.Token{AccessToken: token},
- )
- tc := oauth2.NewClient(context.TODO(), ts)
- client := github.NewClient(tc)
-
- return &GitHub{clientActions: client.Actions, logger: logger}, nil
-}
-
-func (g *GitHub) Put(p core.KeyPath, val string) error {
-
- owner, repoName, err := g.parsePathToOwnerAndRepo(p)
- if err != nil {
- return err
- }
-
- publicKey, _, err := g.getRepoPublicKey(owner, repoName)
- if err != nil {
- return err
- }
-
- encryptedSecret, err := g.encryptSecretWithPublicKey(publicKey, p.Env, val)
- if err != nil {
- return err
- }
-
- _, err = g.createOrUpdateRepoSecret(context.TODO(), owner, repoName, encryptedSecret)
-
- return err
-}
-
-func (g *GitHub) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := p.WithEnv(k)
- err := g.Put(ap, v)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-func (g *GitHub) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- return nil, fmt.Errorf("does not supported by the %s provider", GithubName)
-}
-
-func (g *GitHub) Get(p core.KeyPath) (*core.EnvEntry, error) {
- return nil, fmt.Errorf("does not supported by the %s provider", GithubName)
-}
-
-func (g *GitHub) Delete(p core.KeyPath) error {
- owner, repoName, err := g.parsePathToOwnerAndRepo(p)
- if err != nil {
- return err
- }
-
- _, err = g.deleteRepoSecret(context.TODO(), owner, repoName, p.Env)
- return err
-}
-
-func (g *GitHub) DeleteMapping(p core.KeyPath) error {
-
- owner, repoName, err := g.parsePathToOwnerAndRepo(p)
- if err != nil {
- return err
- }
-
- opt := github.ListOptions{PerPage: 100}
- g.logger.WithFields(map[string]interface{}{
- "owner": owner,
- "repository_name": repoName,
- }).Debug("get repo secrets")
- secrets, _, err := g.clientActions.ListRepoSecrets(context.TODO(), owner, repoName, &opt)
- if err != nil {
- return err
- }
-
- for _, secret := range secrets.Secrets {
- err := g.Delete(p.WithEnv(secret.Name))
- if err != nil {
- return err
- }
-
- }
-
- return nil
-}
-
-// parsePathToOwnerAndRepo parse the key path to the owner and repo name
-func (g *GitHub) parsePathToOwnerAndRepo(p core.KeyPath) (string, string, error) { //nolint
-
- splitData := strings.SplitN(p.Path, "/", 2) //nolint
- if len(splitData) != gitHubSplitPathCount {
- return "", "", fmt.Errorf("invalid %s path, expected owner/repo got: %s", GithubName, p.Path)
- }
- return splitData[0], splitData[1], nil
-}
-
-// GetRepoPublicKey gets a public key that should be used for secret encryption.
-func (g *GitHub) getRepoPublicKey(owner, repo string) (*github.PublicKey, *github.Response, error) {
- return g.clientActions.GetRepoPublicKey(context.TODO(), owner, repo)
-
-}
-
-// CreateOrUpdateRepoSecret creates or updates a repository secret with an encrypted value.
-func (g *GitHub) createOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *github.EncryptedSecret) (*github.Response, error) {
- g.logger.WithFields(map[string]interface{}{
- "owner": owner,
- "repository_name": repo,
- "name": eSecret.Name,
- }).Debug("put repo secret")
- return g.clientActions.CreateOrUpdateRepoSecret(ctx, owner, repo, eSecret)
-}
-
-// DeleteRepoSecret deletes a secret in a repository using the secret name.
-func (g *GitHub) deleteRepoSecret(ctx context.Context, owner, repo, name string) (*github.Response, error) {
- g.logger.WithFields(map[string]interface{}{
- "owner": owner,
- "repository_name": repo,
- "name": name,
- }).Debug("delete repo secret")
- return g.clientActions.DeleteRepoSecret(ctx, owner, repo, name)
-}
-
-// encryptSecretWithPublicKey secret secret name and value by the given GitHub public key
-func (g *GitHub) encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName, secretValue string) (*github.EncryptedSecret, error) {
-
- decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKey.GetKey())
- if err != nil {
- return nil, fmt.Errorf("base64.StdEncoding.DecodeString was unable to decode public key: %v", err)
- }
-
- var boxKey [32]byte
- copy(boxKey[:], decodedPublicKey)
- secretBytes := []byte(secretValue)
- encryptedBytes, err := box.SealAnonymous([]byte{}, secretBytes, &boxKey, crypto_rand.Reader)
- if err != nil {
- return nil, fmt.Errorf("box.SealAnonymous failed with error %w", err)
- }
-
- encryptedString := base64.StdEncoding.EncodeToString(encryptedBytes)
-
- keyID := publicKey.GetKeyID()
- encryptedSecret := &github.EncryptedSecret{
- Name: secretName,
- KeyID: keyID,
- EncryptedValue: encryptedString,
- }
- return encryptedSecret, nil
-}
diff --git a/pkg/providers/github_test.go b/pkg/providers/github_test.go
deleted file mode 100644
index 1ac5375b..00000000
--- a/pkg/providers/github_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
- "github.com/google/go-github/v43/github"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestGitHubPut(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGitHubActionClient(ctrl)
-
- keyID := "1234"
- key := "2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"
- publicKey := github.PublicKey{
- KeyID: &keyID,
- Key: &key,
- }
-
- client.EXPECT().GetRepoPublicKey(gomock.Any(), "owner-name", "error").Return(&publicKey, nil, errors.New("some error")).AnyTimes()
- client.EXPECT().GetRepoPublicKey(gomock.Any(), "owner-name", "repo-name").Return(&publicKey, nil, nil).AnyTimes()
- client.EXPECT().CreateOrUpdateRepoSecret(gomock.Any(), "owner-name", "repo-name", gomock.Any()).Return(nil, nil).AnyTimes()
-
- client.EXPECT().GetRepoPublicKey(gomock.Any(), "owner-name", "create-error").Return(&publicKey, nil, nil).AnyTimes()
- client.EXPECT().CreateOrUpdateRepoSecret(gomock.Any(), "owner-name", "create-error", gomock.Any()).Return(nil, errors.New("some error")).AnyTimes()
-
- c := GitHub{
- clientActions: client,
- logger: GetTestLogger(),
- }
-
- assert.NotNil(t, c.Put(core.KeyPath{Path: "owner-name", Field: "MG_KEY"}, "put-secret"), "owner or repo name should be invalid")
- assert.NotNil(t, c.Put(core.KeyPath{Path: "owner-name/error", Field: "MG_KEY"}, "put-secret"), "repo public key should return an error")
- assert.Nil(t, c.Put(core.KeyPath{Path: "owner-name/repo-name", Field: "MG_KEY"}, "put-secret"), "can put a new secret")
- assert.NotNil(t, c.Put(core.KeyPath{Path: "owner-name/create-error", Field: "MG_KEY"}, "put-secret"), "create or update secret should be fails")
-
-}
-
-func TestGitHubPutMapping(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGitHubActionClient(ctrl)
-
- keyID := "1234"
- key := "2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"
- publicKey := github.PublicKey{
- KeyID: &keyID,
- Key: &key,
- }
-
- client.EXPECT().GetRepoPublicKey(gomock.Any(), "owner-name", "error").Return(&publicKey, nil, errors.New("some error")).AnyTimes()
- client.EXPECT().GetRepoPublicKey(gomock.Any(), "owner-name", "repo-name").Return(&publicKey, nil, nil).AnyTimes()
- client.EXPECT().CreateOrUpdateRepoSecret(gomock.Any(), "owner-name", "repo-name", gomock.Any()).Return(nil, nil).AnyTimes()
-
- client.EXPECT().GetRepoPublicKey(gomock.Any(), "owner-name", "create-error").Return(&publicKey, nil, nil).AnyTimes()
- client.EXPECT().CreateOrUpdateRepoSecret(gomock.Any(), "owner-name", "create-error", gomock.Any()).Return(nil, errors.New("some error")).AnyTimes()
-
- c := GitHub{
- clientActions: client,
- logger: GetTestLogger(),
- }
-
- data := map[string]string{
- "key-1": "value-1",
- }
- assert.NotNil(t, c.PutMapping(core.KeyPath{Path: "owner-name", Field: "MG_KEY"}, data), "owner or repo name should be invalid")
- assert.NotNil(t, c.PutMapping(core.KeyPath{Path: "owner-name/error", Field: "MG_KEY"}, data), "repo public key should return an error")
- assert.Nil(t, c.PutMapping(core.KeyPath{Path: "owner-name/repo-name", Field: "MG_KEY"}, data), "can put a new secret")
- assert.NotNil(t, c.PutMapping(core.KeyPath{Path: "owner-name/create-error", Field: "MG_KEY"}, data), "create or update secret should be fails")
-
-}
-
-func TestGitHubDelete(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGitHubActionClient(ctrl)
-
- client.EXPECT().DeleteRepoSecret(gomock.Any(), "owner-name", "repo-name", "MG_KEY").Return(nil, nil).AnyTimes()
- client.EXPECT().DeleteRepoSecret(gomock.Any(), "owner-name", "repo-name", "MG_KEY_ERROR").Return(nil, errors.New("some error")).AnyTimes()
-
- c := GitHub{
- clientActions: client,
- logger: GetTestLogger(),
- }
-
- assert.NotNil(t, c.Delete(core.KeyPath{Path: "owner-name", Field: "MG_KEY"}), "owner or repo name should be invalid")
- assert.Nil(t, c.Delete(core.KeyPath{Path: "owner-name/repo-name", Env: "MG_KEY"}), "delete action should pass")
- assert.NotNil(t, c.Delete(core.KeyPath{Path: "owner-name/repo-name", Env: "MG_KEY_ERROR"}), "delete action should return an error")
-
-}
-
-func TestGitHubDeleteMaping(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGitHubActionClient(ctrl)
-
- secretsResponse := github.Secrets{
- TotalCount: 2,
- Secrets: []*github.Secret{
- {Name: "MG_KEY-1"},
- {Name: "MG_KEY-2"},
- },
- }
- client.EXPECT().ListRepoSecrets(gomock.Any(), "owner-name", "repo-name", gomock.Any()).Return(&secretsResponse, nil, nil).AnyTimes()
- client.EXPECT().DeleteRepoSecret(gomock.Any(), "owner-name", "repo-name", "MG_KEY-1").Return(nil, nil).AnyTimes()
- client.EXPECT().DeleteRepoSecret(gomock.Any(), "owner-name", "repo-name", "MG_KEY-2").Return(nil, nil).AnyTimes()
-
- c := GitHub{
- clientActions: client,
- logger: GetTestLogger(),
- }
-
- assert.Nil(t, c.DeleteMapping(core.KeyPath{Path: "owner-name/repo-name", Env: "MG_KEY"}), "delete action should pass")
-
-}
-
-func TestGitHubDeleteMapingWithError(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGitHubActionClient(ctrl)
-
- secretsResponse := github.Secrets{
- TotalCount: 2,
- Secrets: []*github.Secret{
- {Name: "MG_KEY-1"},
- {Name: "MG_KEY-2"},
- },
- }
- client.EXPECT().ListRepoSecrets(gomock.Any(), "owner-name", "repo-name", gomock.Any()).Return(&secretsResponse, nil, nil).AnyTimes()
- client.EXPECT().DeleteRepoSecret(gomock.Any(), "owner-name", "repo-name", "MG_KEY-1").Return(nil, nil).AnyTimes()
- client.EXPECT().DeleteRepoSecret(gomock.Any(), "owner-name", "repo-name", "MG_KEY-2").Return(nil, errors.New("some error")).AnyTimes()
-
- c := GitHub{
- clientActions: client,
- logger: GetTestLogger(),
- }
-
- assert.NotNil(t, c.DeleteMapping(core.KeyPath{Path: "owner-name/repo-name", Env: "MG_KEY"}), "delete action should pass")
-
-}
-
-func TestParsePathToOwnerAndRepo(t *testing.T) {
-
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGitHubActionClient(ctrl)
-
- c := GitHub{
- clientActions: client,
- logger: GetTestLogger(),
- }
-
- owner, repo, err := c.parsePathToOwnerAndRepo(core.KeyPath{Path: "owner-name/repo-name", Env: "MG_KEY"})
- assert.Equal(t, owner, "owner-name", "unexpected owner name from path key")
- assert.Equal(t, repo, "repo-name", "unexpected repo name from path key")
- assert.Nil(t, err)
-
- _, _, err = c.parsePathToOwnerAndRepo(core.KeyPath{Path: "owner-name", Env: "MG_KEY"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/google_secretmanager.go b/pkg/providers/google_secretmanager.go
deleted file mode 100644
index 352a8ab4..00000000
--- a/pkg/providers/google_secretmanager.go
+++ /dev/null
@@ -1,181 +0,0 @@
-package providers
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "regexp"
- "sort"
- "strings"
-
- secretmanager "cloud.google.com/go/secretmanager/apiv1"
- secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
-
- "github.com/googleapis/gax-go/v2"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "google.golang.org/api/iterator"
-)
-
-type GoogleSMClient interface {
- AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
- DestroySecretVersion(ctx context.Context, req *secretmanagerpb.DestroySecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
- ListSecrets(ctx context.Context, in *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator
- AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
-}
-type GoogleSecretManager struct {
- client GoogleSMClient
- logger logging.Logger
-}
-
-const GoogleSecretManagerName = "google_secretmanager"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Google Secret Manager",
- Name: GoogleSecretManagerName,
- Authentication: "You should populate `GOOGLE_APPLICATION_CREDENTIALS=account.json` in your environment to your relevant `account.json` that you get from Google.",
- ConfigTemplate: `
- # GOOGLE_APPLICATION_CREDENTIALS=foobar.json
- # https://cloud.google.com/secret-manager/docs/reference/libraries#setting_up_authentication
- google_secretmanager:
- env:
- FOO_GOOG:
- # need to supply the relevant version (versions/1)
- path: projects/123/secrets/FOO_GOOG/versions/1
-`,
- Ops: core.OpMatrix{Get: true, Put: true, Delete: true},
- }
-
- RegisterProvider(metaInfo, NewGoogleSecretManager)
-}
-
-func NewGoogleSecretManager(logger logging.Logger) (core.Provider, error) {
- client, err := secretmanager.NewClient(context.TODO())
- if err != nil {
- return nil, err
- }
- return &GoogleSecretManager{client: client, logger: logger}, nil
-}
-
-func (a *GoogleSecretManager) Put(p core.KeyPath, val string) error {
- reg := regexp.MustCompile(`(?i)/versions/\d+$`)
- res := reg.ReplaceAllString(p.Path, "")
- return a.addSecret(res, val)
-}
-func (a *GoogleSecretManager) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- path := fmt.Sprintf("%v/secrets/%v", p.Path, k)
- err := a.addSecret(path, v)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (a *GoogleSecretManager) GetMapping(kp core.KeyPath) ([]core.EnvEntry, error) {
- secrets, err := a.getSecrets(kp.Path)
- if err != nil {
- return nil, err
- }
-
- entries := []core.EnvEntry{}
-
- for _, val := range secrets {
- path := fmt.Sprintf("%s/%s", val, "versions/latest")
- secretVal, err := a.getSecret(path)
- if err != nil {
- return nil, err
- }
- key := strings.TrimPrefix(val, kp.Path)
- entries = append(entries, kp.FoundWithKey(key, secretVal))
- }
- sort.Sort(core.EntriesByKey(entries))
-
- return entries, nil
-}
-
-func (a *GoogleSecretManager) Get(p core.KeyPath) (*core.EnvEntry, error) {
- secret, err := a.getSecret(p.Path)
- if err != nil {
- return nil, err
- }
-
- ent := p.Found(secret)
-
- if ent.Field != "" {
- var valueSecrets map[string]interface{}
- err = json.Unmarshal([]byte(ent.Value), &valueSecrets)
- if err != nil {
- return nil, err
- }
- fieldValue := valueSecrets[ent.Field].(string)
- ent.Value = fieldValue
- }
- return &ent, nil
-}
-
-func (a *GoogleSecretManager) Delete(kp core.KeyPath) error {
- return a.deleteSecret(kp.Path)
-}
-
-func (a *GoogleSecretManager) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", GoogleSecretManagerName)
-}
-
-func (a *GoogleSecretManager) getSecret(path string) (string, error) {
- r := secretmanagerpb.AccessSecretVersionRequest{
- Name: path,
- }
- a.logger.WithField("path", r.Name).Debug("get secret")
-
- secret, err := a.client.AccessSecretVersion(context.TODO(), &r)
- if err != nil {
- return "", err
- }
- return string(secret.Payload.Data), nil
-}
-
-func (a *GoogleSecretManager) deleteSecret(path string) error {
- req := &secretmanagerpb.DestroySecretVersionRequest{
- Name: path,
- }
- _, err := a.client.DestroySecretVersion(context.TODO(), req)
- return err
-}
-
-func (a *GoogleSecretManager) addSecret(path, val string) error {
- req := &secretmanagerpb.AddSecretVersionRequest{
- Parent: path,
- Payload: &secretmanagerpb.SecretPayload{
- Data: []byte(val),
- },
- }
-
- _, err := a.client.AddSecretVersion(context.TODO(), req)
- return err
-}
-
-func (a *GoogleSecretManager) getSecrets(path string) ([]string, error) {
- req := &secretmanagerpb.ListSecretsRequest{
- Parent: path,
- }
- entries := []string{}
-
- it := a.client.ListSecrets(context.TODO(), req)
- for {
- resp, err := it.Next()
- if err == iterator.Done {
- break
- }
-
- if err != nil {
- return nil, err
- }
- entries = append(entries, resp.Name)
- }
- return entries, nil
-}
diff --git a/pkg/providers/google_secretmanager_test.go b/pkg/providers/google_secretmanager_test.go
deleted file mode 100644
index bacda83a..00000000
--- a/pkg/providers/google_secretmanager_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- secretmanager "cloud.google.com/go/secretmanager/apiv1"
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
- secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestGoogleSM(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockGoogleSMClient(ctrl)
- path := "settings/prod/billing-svc"
- sec := &secretmanagerpb.SecretPayload{
- Data: []byte("shazam"),
- }
- out := &secretmanagerpb.AccessSecretVersionResponse{
- Payload: sec,
- }
- outDelete := &secretmanagerpb.SecretVersion{
- Name: string(sec.Data),
- }
- outAdd := &secretmanagerpb.SecretVersion{
- Name: string(sec.Data),
- }
- outList := &secretmanager.SecretIterator{
- Response: string(sec.Data),
- }
- in := secretmanagerpb.AccessSecretVersionRequest{
- Name: path,
- }
- inDelete := secretmanagerpb.DestroySecretVersionRequest{
- Name: path,
- }
- inList := secretmanagerpb.ListSecretsRequest{
- Parent: path,
- }
- inAdd := secretmanagerpb.AddSecretVersionRequest{
- Parent: path,
- Payload: &secretmanagerpb.SecretPayload{
- Data: []byte("some value"),
- },
- }
- client.EXPECT().AccessSecretVersion(gomock.Any(), gomock.Eq(&in)).Return(out, nil).AnyTimes()
- client.EXPECT().DestroySecretVersion(gomock.Any(), gomock.Eq(&inDelete)).Return(outDelete, nil).AnyTimes()
- client.EXPECT().ListSecrets(gomock.Any(), gomock.Eq(&inList)).Return(outList).AnyTimes()
- client.EXPECT().AddSecretVersion(gomock.Any(), gomock.Eq(&inAdd)).Return(outAdd, nil).AnyTimes()
- s := GoogleSecretManager{
- client: client,
- logger: GetTestLogger(),
- }
- ConfigurableAssertProvider(t, &s, false, false)
-}
-
-func TestGoogleSMWithField(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockGoogleSMClient(ctrl)
- path := "settings/prod/billing-svc"
-
- data := `{"MG_KEY":"shazam", "SMTP_PASS":"mailman"}`
- sec := &secretmanagerpb.SecretPayload{
- Data: []byte(data),
- }
- out := &secretmanagerpb.AccessSecretVersionResponse{
- Payload: sec,
- }
- outDelete := &secretmanagerpb.SecretVersion{
- Name: string(sec.Data),
- }
- outAdd := &secretmanagerpb.SecretVersion{
- Name: string(sec.Data),
- }
- outList := &secretmanager.SecretIterator{
- Response: string(sec.Data),
- }
- in := secretmanagerpb.AccessSecretVersionRequest{
- Name: path,
- }
- inDelete := secretmanagerpb.DestroySecretVersionRequest{
- Name: path,
- }
- inList := secretmanagerpb.ListSecretsRequest{
- Parent: path,
- }
- inAdd := secretmanagerpb.AddSecretVersionRequest{
- Parent: path,
- Payload: &secretmanagerpb.SecretPayload{
- Data: []byte("some value"),
- },
- }
- client.EXPECT().AccessSecretVersion(gomock.Any(), gomock.Eq(&in)).Return(out, nil).AnyTimes()
- client.EXPECT().DestroySecretVersion(gomock.Any(), gomock.Eq(&inDelete)).Return(outDelete, nil).AnyTimes()
- client.EXPECT().ListSecrets(gomock.Any(), gomock.Eq(&inList)).Return(outList).AnyTimes()
- client.EXPECT().AddSecretVersion(gomock.Any(), gomock.Eq(&inAdd)).Return(outAdd, nil).AnyTimes()
- s := GoogleSecretManager{
- client: client,
- logger: GetTestLogger(),
- }
- ConfigurableAssertProvider(t, &s, false, true)
-}
-
-func TestGoogleSMFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockGoogleSMClient(ctrl)
- client.EXPECT().AccessSecretVersion(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := GoogleSecretManager{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/gopass.go b/pkg/providers/gopass.go
deleted file mode 100644
index 4ebc3eb6..00000000
--- a/pkg/providers/gopass.go
+++ /dev/null
@@ -1,141 +0,0 @@
-package providers
-
-import (
- "context"
- "fmt"
- "sort"
- "strings"
-
- "github.com/gopasspw/gopass/pkg/gopass"
- "github.com/gopasspw/gopass/pkg/gopass/api"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/utils"
-)
-
-type GopassClient interface {
- List(ctx context.Context) ([]string, error)
- Get(ctx context.Context, name, revision string) (gopass.Secret, error)
- Set(ctx context.Context, name string, sec gopass.Byter) error
-}
-
-type Gopass struct {
- client GopassClient
- logger logging.Logger
-}
-
-const GoPassName = "gopass"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Gopass",
- Name: GoPassName,
- Authentication: "Configuration is environment based, as defined by client standard. See variables [here](https://github.com/gopasspw/gopass/blob/master/docs/config.md).",
- ConfigTemplate: `
- # Override default configuration: https://github.com/gopasspw/gopass/blob/master/docs/config.md
- gopass:
- env_sync:
- path: foo
- env:
- ETC_DSN:
- path: foo/bar
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
- }
-
- RegisterProvider(metaInfo, NewGopass)
-}
-func NewGopass(logger logging.Logger) (core.Provider, error) {
- ctx := context.Background()
- gp, err := api.New(ctx)
- if err != nil {
- return nil, err
- }
- return &Gopass{client: gp, logger: logger}, nil
-}
-
-func (g *Gopass) Put(p core.KeyPath, val string) error {
- secret, err := g.getSecret(p.Path)
- if err != nil {
- return fmt.Errorf("%v cannot get value: %v", GoPassName, err)
- }
-
- secret.SetPassword(val)
- g.logger.WithField("path", p.Path).Debug("set secret")
- return g.client.Set(context.TODO(), p.Path, secret)
-}
-
-func (g *Gopass) PutMapping(p core.KeyPath, m map[string]string) error {
- for k, v := range m {
- ap := p.SwitchPath(fmt.Sprintf("%v/%v", p.Path, k))
- secret, err := g.getSecret(ap.Path)
- if err != nil {
- return fmt.Errorf("%v cannot get value: %v", GoPassName, err)
- }
-
- secret.SetPassword(v)
- g.logger.WithField("path", ap.Path).Debug("set secret")
- err = g.client.Set(context.TODO(), ap.Path, secret)
- if err != nil {
- return fmt.Errorf("%v cannot update value: %v", GoPassName, err)
- }
-
- }
- return nil
-}
-
-func (g *Gopass) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- g.logger.Debug("get all secrets")
- secretsPath, err := g.client.List(context.TODO())
- if err != nil {
- return nil, err
- }
- entries := []core.EnvEntry{}
- for _, secretPath := range secretsPath {
- if strings.HasPrefix(secretPath, p.Path) {
- secret, err := g.getSecret(secretPath)
- if err != nil {
- return nil, err
- }
- seg := utils.LastSegment(secretPath)
- entries = append(entries, p.FoundWithKey(seg, secret.Password()))
- }
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (g *Gopass) Get(p core.KeyPath) (*core.EnvEntry, error) {
-
- secret, err := g.getSecret(p.Path)
- if err != nil {
- return nil, fmt.Errorf("%v cannot get value: %v", GoPassName, err)
- }
-
- if secret == nil {
- g.logger.WithField("path", p.Path).Debug("secret is empty")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(secret.Password())
- return &ent, nil
-}
-
-func (g *Gopass) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", GoPassName)
-}
-
-func (g *Gopass) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", GoPassName)
-}
-
-func (g *Gopass) getSecret(path string) (gopass.Secret, error) {
- g.logger.WithField("path", path).Debug("get secret")
- secret, err := g.client.Get(context.TODO(), path, "")
- if err != nil {
- return nil, err
- }
- return secret, nil
-}
diff --git a/pkg/providers/gopass_test.go b/pkg/providers/gopass_test.go
deleted file mode 100644
index 4ade102b..00000000
--- a/pkg/providers/gopass_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package providers
-
-import (
- "context"
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
- "github.com/gopasspw/gopass/pkg/gopass/secrets/secparse"
-
- // "github.com/gopasspw/gopass/pkg/gopass/secrets/secparse"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestGopass(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGopassClient(ctrl)
- path := "settings/prod/billing-svc"
-
- secretShazamVal := `shazam
-settings / prod / billing-svc
-`
- secretMailmanVal := `mailman
-settings / prod / billing-svc
-`
- secretShazam, _ := secparse.Parse([]byte(secretShazamVal))
- secretMailman, _ := secparse.Parse([]byte(secretMailmanVal))
- outlist := []string{
- "settings/prod/billing-svc/all/1",
- "settings/prod/billing-svc/all/2",
- }
-
- client.EXPECT().Get(context.TODO(), gomock.Eq(path), gomock.Any()).Return(secretShazam, nil).AnyTimes()
- client.EXPECT().Get(context.TODO(), gomock.Eq("settings/prod/billing-svc/all/1"), gomock.Any()).Return(secretShazam, nil).AnyTimes()
- client.EXPECT().Get(context.TODO(), gomock.Eq("settings/prod/billing-svc/all/2"), gomock.Any()).Return(secretMailman, nil).AnyTimes()
- client.EXPECT().List(context.TODO()).Return(outlist, nil).AnyTimes()
- s := Gopass{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestGopassFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockGopassClient(ctrl)
- client.EXPECT().Get(context.TODO(), gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := Gopass{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- client.EXPECT().List(context.TODO()).Return([]string{"a"}, errors.New("error")).AnyTimes()
- assert.NotNil(t, err)
- _, err = s.GetMapping(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/hashicorp_vault.go b/pkg/providers/hashicorp_vault.go
deleted file mode 100644
index 2e6ef499..00000000
--- a/pkg/providers/hashicorp_vault.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package providers
-
-import (
- "fmt"
- "sort"
-
- "github.com/hashicorp/vault/api"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type HashicorpClient interface {
- Read(path string) (*api.Secret, error)
- Write(path string, data map[string]interface{}) (*api.Secret, error)
-}
-type HashicorpVault struct {
- client HashicorpClient
- logger logging.Logger
-}
-
-const HashicorpVaultName = "hashicorp_vault"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Hashicorp Vault",
- Name: HashicorpVaultName,
- Authentication: "Configuration is environment based, as defined by client standard. See variables [here](https://github.com/hashicorp/vault/blob/api/v1.0.4/api/client.go#L28).",
- ConfigTemplate: `
- # configure only from environment
- # https://github.com/hashicorp/vault/blob/api/v1.0.4/api/client.go#L28
- # this vars should not go through to the executing cmd
- hashicorp_vault:
- env_sync:
- path: secret/data/{{"{{stage}}"}}/billing/web/env
- env:
- SMTP_PASS:
- path: secret/data/{{"{{stage}}"}}/wordpress
- field: smtp
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: true, PutMapping: true},
- }
-
- RegisterProvider(metaInfo, NewHashicorpVault)
-}
-
-func NewHashicorpVault(logger logging.Logger) (core.Provider, error) {
- conf := api.DefaultConfig()
- err := conf.ReadEnvironment()
- if err != nil {
- return nil, err
- }
-
- client, err := api.NewClient(conf)
-
- if err != nil {
- return nil, err
- }
-
- return &HashicorpVault{client: client.Logical(), logger: logger}, nil
-}
-
-func (h *HashicorpVault) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- secret, err := h.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- // vault returns a secret kv struct as either data{} or data.data{} depending on engine
- var k map[string]interface{}
- if val, ok := secret.Data["data"]; ok {
- k = val.(map[string]interface{})
- } else {
- k = secret.Data
- }
-
- entries := []core.EnvEntry{}
- for k, v := range k {
- entries = append(entries, p.FoundWithKey(k, v.(string)))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (h *HashicorpVault) Get(p core.KeyPath) (*core.EnvEntry, error) {
- secret, err := h.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- if secret == nil {
- h.logger.WithField("path", p.Path).Debug("secret is empty")
- ent := p.Missing()
- return &ent, nil
- }
-
- // vault returns a secret kv struct as either data{} or data.data{} depending on engine
- var data map[string]interface{}
- if val, ok := secret.Data["data"]; ok {
- data = val.(map[string]interface{})
- } else {
- data = secret.Data
- }
-
- k := data[p.Env]
- if p.Field != "" {
- h.logger.WithField("path", p.Path).Debug("`env` attribute not found in returned data. take `field` attribute")
- k = data[p.Field]
- }
-
- if k == nil {
- h.logger.WithField("path", p.Path).Debug("key not found")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(k.(string))
- return &ent, nil
-}
-
-func (h *HashicorpVault) Put(p core.KeyPath, val string) error {
- k := p.Env
- if p.Field != "" {
- h.logger.WithField("path", p.Path).Debug("`env` attribute not configured. take `field` attribute")
- k = p.Field
- }
- m := map[string]string{k: val}
- h.logger.WithField("path", p.Path).Debug("write secret")
- _, err := h.client.Write(p.Path, map[string]interface{}{"data": m})
- return err
-}
-func (h *HashicorpVault) PutMapping(p core.KeyPath, m map[string]string) error {
- h.logger.WithField("path", p.Path).Debug("write secret")
- _, err := h.client.Write(p.Path, map[string]interface{}{"data": m})
- return err
-}
-
-func (h *HashicorpVault) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", HashicorpVaultName)
-}
-
-func (h *HashicorpVault) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", HashicorpVaultName)
-}
-
-func (h *HashicorpVault) getSecret(kp core.KeyPath) (*api.Secret, error) {
- h.logger.WithField("path", kp.Path).Debug("read secret")
- secret, err := h.client.Read(kp.Path)
- if err != nil {
- return nil, err
- }
-
- if secret == nil || len(secret.Data) == 0 {
- return nil, fmt.Errorf("secret not found in path: %s", kp.Path)
- }
-
- if len(secret.Warnings) > 0 {
- fmt.Println(secret.Warnings)
- }
-
- return secret, nil
-}
diff --git a/pkg/providers/hashicorp_vault_test.go b/pkg/providers/hashicorp_vault_test.go
deleted file mode 100644
index 016d8068..00000000
--- a/pkg/providers/hashicorp_vault_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/hashicorp/vault/api"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestHashicorpVault(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockHashicorpClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
-
- tests := map[string]struct {
- out api.Secret
- }{
- "data.data": {
- out: api.Secret{
- Data: map[string]interface{}{
- "data": map[string]interface{}{
- "MG_KEY": "shazam",
- "SMTP_PASS": "mailman",
- },
- },
- },
- },
- "data": {
- out: api.Secret{
- Data: map[string]interface{}{
- "MG_KEY": "shazam",
- "SMTP_PASS": "mailman",
- },
- },
- },
- }
-
- for name, tc := range tests {
- t.Run(name, func(t *testing.T) {
- client.EXPECT().Read(gomock.Eq(path)).Return(&tc.out, nil).AnyTimes()
- client.EXPECT().Read(gomock.Eq(pathmap)).Return(&tc.out, nil).AnyTimes()
- s := HashicorpVault{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
- })
- }
-}
-
-func TestHashicorpVaultFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockHashicorpClient(ctrl)
- client.EXPECT().Read(gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := HashicorpVault{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/helpers_test.go b/pkg/providers/helpers_test.go
deleted file mode 100644
index c52e9d87..00000000
--- a/pkg/providers/helpers_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package providers
-
-import (
- "testing"
-
- "github.com/alecthomas/assert"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-func AssertProvider(t *testing.T, s core.Provider, sync bool) {
- p := core.NewPopulate(map[string]string{"stage": "prod"})
-
- kpmap := p.KeyPath(core.KeyPath{Field: "MG_KEY", Path: "settings/{{stage}}/billing-svc/all", Decrypt: true})
- kp := p.KeyPath(core.KeyPath{Field: "MG_KEY", Path: "settings/{{stage}}/billing-svc", Decrypt: true})
- kpenv := p.KeyPath(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc", Decrypt: true})
-
- ent, err := s.Get(kp)
- assert.Nil(t, err)
- assert.Equal(t, ent.Value, "shazam")
-
- ent, err = s.Get(kpenv)
- assert.Nil(t, err)
- assert.Equal(t, ent.Value, "shazam")
-
- if sync {
- ents, err := s.GetMapping(kpmap)
- assert.Nil(t, err)
- assert.Equal(t, len(ents), 2)
- assert.Equal(t, ents[0].Value, "mailman")
- assert.Equal(t, ents[1].Value, "shazam")
- }
-}
-
-func AssertProviderPlainText(t *testing.T, s core.Provider, expected string) {
- p := core.NewPopulate(map[string]string{"stage": "prod"})
-
- kp := p.KeyPath(core.KeyPath{Field: "MG_KEY", Path: "settings/{{stage}}/billing-svc", Decrypt: true, Plaintext: true})
- kpenv := p.KeyPath(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc", Decrypt: true, Plaintext: true})
-
- ent, err := s.Get(kp)
- assert.Nil(t, err)
- assert.Equal(t, ent.Value, expected)
-
- ent, err = s.Get(kpenv)
- assert.Nil(t, err)
- assert.Equal(t, ent.Value, expected)
-}
-
-func ConfigurableAssertProvider(t *testing.T, s core.Provider, sync bool, setField bool) {
- p := core.NewPopulate(map[string]string{"stage": "prod"})
-
- fieldValue := ""
- if setField == true {
- fieldValue = "MG_KEY"
- }
-
- kpmap := p.KeyPath(core.KeyPath{Field: fieldValue, Path: "settings/{{stage}}/billing-svc/all", Decrypt: true})
- kp := p.KeyPath(core.KeyPath{Field: fieldValue, Path: "settings/{{stage}}/billing-svc", Decrypt: true})
- kpenv := p.KeyPath(core.KeyPath{Field: fieldValue, Env: fieldValue, Path: "settings/{{stage}}/billing-svc", Decrypt: true})
-
- ent, err := s.Get(kp)
- assert.Nil(t, err)
- assert.Equal(t, ent.Value, "shazam")
-
- ent, err = s.Get(kpenv)
- assert.Nil(t, err)
- assert.Equal(t, ent.Value, "shazam")
-
- if sync {
- ents, err := s.GetMapping(kpmap)
- assert.Nil(t, err)
- assert.Equal(t, len(ents), 2)
- assert.Equal(t, ents[0].Value, "mailman")
- assert.Equal(t, ents[1].Value, "shazam")
- }
-}
-
-func GetTestLogger() logging.Logger {
- logger := logging.New()
- logger.SetLevel("null")
- return logger
-}
diff --git a/pkg/providers/heroku.go b/pkg/providers/heroku.go
deleted file mode 100644
index a6dc8dc8..00000000
--- a/pkg/providers/heroku.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package providers
-
-import (
- "context"
- "fmt"
- "os"
- "sort"
-
- heroku "github.com/heroku/heroku-go/v5"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type HerokuClient interface {
- ConfigVarInfoForApp(ctx context.Context, appIdentity string) (heroku.ConfigVarInfoForAppResult, error)
- ConfigVarUpdate(ctx context.Context, appIdentity string, o map[string]*string) (heroku.ConfigVarUpdateResult, error)
-}
-type Heroku struct {
- client HerokuClient
- logger logging.Logger
-}
-
-const HerokuName = "heroku"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Heroku",
- Name: HerokuName,
- Authentication: "Requires an API key populated in your environment in: `HEROKU_API_KEY` (you can fetch it from your ~/.netrc).",
- ConfigTemplate: `
- # requires an API key in: HEROKU_API_KEY (you can fetch yours from ~/.netrc)
- heroku:
- # sync a complete environment
- env_sync:
- path: drakula-demo
-
- # # pick and choose variables
- # env:
- # JVM_OPTS:
- # path: drakula-demo
-`,
- Ops: core.OpMatrix{GetMapping: true, Get: true, Put: true, PutMapping: true},
- }
-
- RegisterProvider(metaInfo, NewHeroku)
-}
-
-func NewHeroku(logger logging.Logger) (core.Provider, error) {
- heroku.DefaultTransport.BearerToken = os.Getenv("HEROKU_API_KEY")
-
- svc := heroku.NewService(heroku.DefaultClient)
- return &Heroku{client: svc, logger: logger}, nil
-}
-
-func (h *Heroku) Put(p core.KeyPath, val string) error {
- k := p.EffectiveKey()
- h.logger.WithField("path", p.Path).Debug("put variable")
- _, err := h.client.ConfigVarUpdate(context.TODO(), p.Path, map[string]*string{k: &val})
- return err
-}
-func (h *Heroku) PutMapping(p core.KeyPath, m map[string]string) error {
- vars := map[string]*string{}
- for k := range m {
- v := m[k]
- vars[k] = &v
- }
- h.logger.WithField("path", p.Path).Debug("put multiple values")
- _, err := h.client.ConfigVarUpdate(context.TODO(), p.Path, vars)
- return err
-}
-
-func (h *Heroku) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- secret, err := h.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- k := secret
-
- entries := []core.EnvEntry{}
- for k, v := range k {
- val := ""
- if v != nil {
- val = *v
- }
- entries = append(entries, p.FoundWithKey(k, val))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (h *Heroku) Get(p core.KeyPath) (*core.EnvEntry, error) { //nolint:dupl
- secret, err := h.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- data := secret
- k := data[p.Env]
- if p.Field != "" {
- h.logger.WithField("path", p.Path).Debug("`env` attribute not found in returned data. take `field` attribute")
- k = data[p.Field]
- }
-
- if k == nil {
- h.logger.WithField("path", p.Path).Debug("requested entry not found")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(*k)
- return &ent, nil
-}
-
-func (h *Heroku) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", HerokuName)
-}
-
-func (h *Heroku) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", HerokuName)
-}
-
-func (h *Heroku) getSecret(kp core.KeyPath) (heroku.ConfigVarInfoForAppResult, error) {
- h.logger.WithField("path", kp.Path).Debug("get field")
- return h.client.ConfigVarInfoForApp(context.TODO(), kp.Path)
-}
diff --git a/pkg/providers/heroku_test.go b/pkg/providers/heroku_test.go
deleted file mode 100644
index 64d4187c..00000000
--- a/pkg/providers/heroku_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestHeroku(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockHerokuClient(ctrl)
- // in heroku this isn't the path name, but an app name,
- // but for testing it doesn't matter
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- shazam := "shazam"
- mailman := "mailman"
- out := map[string]*string{
- "MG_KEY": &shazam,
- "SMTP_PASS": &mailman,
- }
- client.EXPECT().ConfigVarInfoForApp(gomock.Any(), gomock.Eq(path)).Return(out, nil).AnyTimes()
- client.EXPECT().ConfigVarInfoForApp(gomock.Any(), gomock.Eq(pathmap)).Return(out, nil).AnyTimes()
- s := Heroku{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestHerokuFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockHerokuClient(ctrl)
- client.EXPECT().ConfigVarInfoForApp(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := Heroku{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/keeper_secretsmanager.go b/pkg/providers/keeper_secretsmanager.go
deleted file mode 100644
index 6f4f4ee6..00000000
--- a/pkg/providers/keeper_secretsmanager.go
+++ /dev/null
@@ -1,234 +0,0 @@
-package providers
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "strconv"
- "strings"
-
- ksm "github.com/keeper-security/secrets-manager-go/core"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type KsmClient interface {
- GetSecret(p core.KeyPath) (*core.EnvEntry, error)
- GetSecrets(p core.KeyPath) ([]core.EnvEntry, error)
-}
-
-type KeeperSecretsManager struct {
- client KsmClient
- logger logging.Logger
-}
-
-const keeperName = "keeper_secretsmanager"
-
-//nolint
-func init() {
- metaInto := core.MetaInfo{
- Description: "Keeper Secrets Manager",
- Name: keeperName,
- Authentication: "You should populate `KSM_CONFIG=Base64ConfigString or KSM_CONFIG_FILE=ksm_config.json` in your environment.",
- ConfigTemplate: `
- keeper_secretsmanager:
- env_sync:
- path: record_uid
- env:
- FOO_BAR:
- path: UID/custom_field/foo_bar
- # use Keeper Notation to pull data from typed records
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true},
- }
- RegisterProvider(metaInto, NewKeeperSecretsManager)
-}
-
-type SecretsManagerClient struct {
- sm *ksm.SecretsManager
-}
-
-func (c SecretsManagerClient) GetSecret(p core.KeyPath) (*core.EnvEntry, error) {
- nr, err := c.sm.GetNotationResults(p.Path)
- if err != nil {
- return nil, err
- }
-
- ent := core.EnvEntry{}
- if len(nr) > 0 {
- ent = p.Found(nr[0])
- } else {
- ent = p.Missing()
- }
-
- return &ent, nil
-}
-
-func (c SecretsManagerClient) GetSecrets(p core.KeyPath) ([]core.EnvEntry, error) {
- // p.Path must be a record UID
- // TODO: Add Path = folderUID... expect too many dulpicates, prefix key name with RUID?
- type KeyValuePair struct {
- key, value string
- }
-
- r := []core.EnvEntry{}
-
- recs, err := c.sm.GetSecrets([]string{p.Path})
- if err != nil {
- return nil, err
- }
- if len(recs) < 1 {
- return r, nil
- }
-
- entries := []KeyValuePair{}
- rec := recs[0]
- fields := rec.GetFieldsBySection(ksm.FieldSectionBoth)
- for _, field := range fields {
- fmap, ok := field.(map[string]interface{})
- if !ok {
- continue
- }
-
- iValues, ok := fmap["value"].([]interface{})
- if !ok || len(iValues) < 1 {
- continue
- }
-
- value := extractValue(iValues)
- if value == "" {
- continue
- }
-
- key := extractKey(fmap)
- entries = append(entries, KeyValuePair{key: key, value: value})
- }
-
- // avoid duplicate key names
- keymap := map[string]struct{}{}
- for _, e := range entries {
- key := e.key
- if _, found := keymap[e.key]; found {
- n := 1
- for {
- n++
- mkey := key + "_" + strconv.Itoa(n)
- if _, found := keymap[mkey]; !found {
- key = mkey
- break
- }
- }
- }
- keymap[key] = struct{}{}
- ent := p.FoundWithKey(key, e.value)
- r = append(r, ent)
- }
-
- return r, nil
-}
-
-func extractKey(fieldMap map[string]interface{}) string {
- key := ""
- if fLabel, ok := fieldMap["label"].(string); ok {
- key = strings.TrimSpace(fLabel)
- }
- if key == "" {
- if fType, ok := fieldMap["type"].(string); ok {
- key = strings.TrimSpace(fType)
- }
- }
- key = strings.ReplaceAll(key, " ", "_")
- return key
-}
-
-func extractValue(iValues []interface{}) string {
- value := ""
- _, isArray := iValues[0].([]interface{})
- _, isObject := iValues[0].(map[string]interface{})
- isJSON := len(iValues) > 1 || isArray || isObject
- if isJSON {
- if len(iValues) == 1 {
- if val, err := json.Marshal(iValues[0]); err == nil {
- value = string(val)
- }
- } else if val, err := json.Marshal(iValues); err == nil {
- value = string(val)
- }
- } else {
- val := iValues[0]
- // JavaScript number type, IEEE754 double precision float
- if fval, ok := val.(float64); ok && fval == float64(int(fval)) {
- val = int(fval) // convert to int
- }
- value = fmt.Sprintf("%v", val)
- }
- return value
-}
-
-func NewKsmClient() (KsmClient, error) {
- config := os.Getenv("KSM_CONFIG")
- configPath := os.Getenv("KSM_CONFIG_FILE")
- if config == "" && configPath == "" {
- return nil, fmt.Errorf("cannot find KSM_CONFIG or KSM_CONFIG_FILE for %s", keeperName)
- }
-
- // with both options present KSM_CONFIG overrides KSM_CONFIG_FILE
- var options *ksm.ClientOptions = nil
- if config != "" {
- options = &ksm.ClientOptions{Config: ksm.NewMemoryKeyValueStorage(config)}
- } else if stat, err := os.Stat(configPath); err == nil && stat.Size() > 2 {
- options = &ksm.ClientOptions{Config: ksm.NewFileKeyValueStorage(configPath)}
- }
-
- if options == nil {
- return nil, fmt.Errorf("failed to initialize KSM Client Options")
- }
-
- sm := ksm.NewSecretsManager(options)
- if sm == nil {
- return nil, fmt.Errorf("failed to initialize KSM Client")
- }
-
- return SecretsManagerClient{
- sm: sm,
- }, nil
-}
-
-func NewKeeperSecretsManager(logger logging.Logger) (core.Provider, error) {
- ksmClient, err := NewKsmClient()
- if err != nil {
- return nil, err
- }
- return &KeeperSecretsManager{
- client: ksmClient,
- logger: logger,
- }, nil
-}
-
-func (k *KeeperSecretsManager) Name() string {
- return keeperName
-}
-
-func (k *KeeperSecretsManager) Get(p core.KeyPath) (*core.EnvEntry, error) {
- return k.client.GetSecret(p)
-}
-
-func (k *KeeperSecretsManager) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- return k.client.GetSecrets(p)
-}
-
-func (k *KeeperSecretsManager) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", k.Name())
-}
-
-func (k *KeeperSecretsManager) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write mapping yet", k.Name())
-}
-
-func (k *KeeperSecretsManager) Delete(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", k.Name())
-}
-
-func (k *KeeperSecretsManager) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete mapping yet", k.Name())
-}
diff --git a/pkg/providers/keeper_secretsmanager_test.go b/pkg/providers/keeper_secretsmanager_test.go
deleted file mode 100644
index dbbfbd79..00000000
--- a/pkg/providers/keeper_secretsmanager_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestKeeperSecretsManager(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockKsmClient(ctrl)
-
- a := KeeperSecretsManager{
- client: client,
- logger: GetTestLogger(),
- }
-
- path := core.KeyPath{Path: "settings/prod/billing-svc/all", Field: "MG_KEY", Decrypt: true}
- key1 := core.KeyPath{Path: "settings/prod/billing-svc", Field: "MG_KEY", Decrypt: true}
- key2 := core.KeyPath{Path: "settings/prod/billing-svc", Env: "MG_KEY", Decrypt: true}
-
- returnSecrets := []core.EnvEntry{
- {Key: "MM_KEY", Value: "mailman"},
- {Key: "MG_KEY", Value: "shazam"},
- }
- client.EXPECT().GetSecret(key1).Return(&core.EnvEntry{Value: "shazam"}, nil).AnyTimes()
- client.EXPECT().GetSecret(key2).Return(&core.EnvEntry{Value: "shazam"}, nil).AnyTimes()
- client.EXPECT().GetSecrets(path).Return(returnSecrets, nil).AnyTimes()
- client.EXPECT().GetSecrets(nil).Return(returnSecrets, nil).AnyTimes()
-
- AssertProvider(t, &a, true)
-}
-
-func TestKeeperSecretsManagerFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockKsmClient(ctrl)
-
- a := KeeperSecretsManager{
- client: client,
- logger: GetTestLogger(),
- }
-
- path := core.KeyPath{Path: "settings/{{stage}}/billing-svc/all"}
- key1 := core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"}
- client.EXPECT().GetSecret(key1).Return(&core.EnvEntry{}, errors.New("error")).AnyTimes()
- client.EXPECT().GetSecrets(path).Return([]core.EnvEntry{}, errors.New("error")).AnyTimes()
-
- _, err := a.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-
- _, err = a.GetMapping(core.KeyPath{Path: "settings/{{stage}}/billing-svc/all"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/keypass.go b/pkg/providers/keypass.go
deleted file mode 100644
index d82e5b12..00000000
--- a/pkg/providers/keypass.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package providers
-
-import (
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/spectralops/teller/pkg/core"
-
- "github.com/spectralops/teller/pkg/logging"
- "github.com/tobischo/gokeepasslib/v3"
-)
-
-var (
- // keyPathFields describe all the available fields in KeyPass entry
- keyPathFields = []string{"Notes", "Password", "URL", "UserName"}
-)
-
-type KeyPass struct {
- logger logging.Logger
- data map[string]gokeepasslib.Entry
-}
-
-const KeyPassName = "KeyPass"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Keypass",
- Name: KeyPassName,
- Authentication: "Set the following env vars:\n`KEYPASS_PASSWORD`: Password database credentials\n`KEYPASS_DB_PATH`: Database path",
- ConfigTemplate: `
- # Configure via environment variables for integration:
- # KEYPASS_PASSWORD: KeyPass password
- # KEYPASS_DB_PATH: Path to DB file
-
- keypass:
- env_sync:
- path: redis/config
- # source: Optional, all fields is the default. Supported fields: Notes, Title, Password, URL, UserName
- env:
- ETC_DSN:
- path: redis/config/foobar
- # source: Optional, Password is the default. Supported fields: Notes, Title, Password, URL, UserName
-`,
- Ops: core.OpMatrix{GetMapping: true, Get: true},
- }
-
- RegisterProvider(metaInfo, NewKeyPass)
-}
-
-// NewKeyPass creates new provider instance
-func NewKeyPass(logger logging.Logger) (core.Provider, error) {
- password := os.Getenv("KEYPASS_PASSWORD")
- if password == "" {
- return nil, errors.New("missing `KEYPASS_PASSWORD`")
- }
- dbPath := os.Getenv("KEYPASS_DB_PATH")
- if dbPath == "" {
- return nil, errors.New("missing `KEYPASS_DB_PATH`")
- }
-
- file, err := os.Open(dbPath)
- if err != nil {
- return nil, err
- }
- defer file.Close()
-
- db := gokeepasslib.NewDatabase()
- db.Credentials = gokeepasslib.NewPasswordCredentials(password)
- err = gokeepasslib.NewDecoder(file).Decode(db)
-
- if err != nil {
- return nil, err
- }
-
- err = db.UnlockProtectedEntries()
- if err != nil {
- return nil, err
- }
- keyPass := &KeyPass{
- logger: logger,
- }
- keyPass.data = keyPass.prepareGroups("", db.Content.Root.Groups, nil)
- return keyPass, nil
-}
-
-// Put will create a new single entry
-func (k *KeyPass) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", KeyPassName)
-}
-
-// PutMapping will create a multiple entries
-func (k *KeyPass) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", KeyPassName)
-}
-
-// GetMapping returns a multiple entries
-func (k *KeyPass) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
-
- results := []core.EnvEntry{}
- for path, entry := range k.data { //nolint
- // get entries that start with the given path
- if strings.HasPrefix(path, p.Path) {
- if p.Source == "" {
- // getting all entries fields
- for _, field := range keyPathFields {
- val := entry.Get(field).Value.Content
- // skip on empty field
- if val == "" {
- k.logger.WithFields(map[string]interface{}{
- "field": field,
- "path": path,
- }).Debug("empty field")
- continue
- }
- results = append(results, p.FoundWithKey(fmt.Sprintf("%s/%s", path, strings.ToLower(field)), val))
- }
- } else {
- fieldContent := entry.Get(p.Source)
- if fieldContent == nil {
- k.logger.WithFields(map[string]interface{}{
- "source": p.Source,
- "path": path,
- }).Debug("field not found")
- continue
- }
- val := fieldContent.Value.Content
- if val != "" {
- results = append(results, p.FoundWithKey(path, val))
- }
- }
- }
- }
- return results, nil
-}
-
-// Get returns a single entry
-func (k *KeyPass) Get(p core.KeyPath) (*core.EnvEntry, error) {
- ent := p.Missing()
- entry, found := k.data[p.Path]
- if !found {
- k.logger.WithField("path", p.Path).Debug("secret not found in path")
- return nil, fmt.Errorf("%v path: %s not exists", KeyPassName, p.Path)
- }
- source := p.Source
- if source == "" {
- k.logger.WithField("path", p.Path).Debug("source attribute is empty, setting default field")
- source = "Password"
- }
- k.logger.WithFields(map[string]interface{}{
- "path": p.Path,
- "source": source,
- }).Debug("get keypass field")
- ent = p.Found(entry.Get(source).Value.Content)
-
- return &ent, nil
-}
-
-// Delete will delete entry
-func (k *KeyPass) Delete(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", KeyPassName)
-}
-
-// DeleteMapping will delete the given path recessively
-func (k *KeyPass) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", KeyPassName)
-}
-
-// prepareGroups all KeyPass entries for easy seearch
-func (k *KeyPass) prepareGroups(path string, groups []gokeepasslib.Group, mapData map[string]gokeepasslib.Entry) map[string]gokeepasslib.Entry {
- if mapData == nil {
- mapData = map[string]gokeepasslib.Entry{}
- }
- for _, group := range groups { //nolint
- // if entries found, adding the entry data fo the list
- if len(group.Entries) > 0 {
- for _, entry := range group.Entries { //nolint
- if path == "" { // prevent unexpected leading slash for entries in root
- mapData[fmt.Sprintf("%s/%s", group.Name, entry.GetTitle())] = entry
- } else {
- mapData[fmt.Sprintf("%s/%s/%s", path, group.Name, entry.GetTitle())] = entry
- }
-
- }
- }
- if len(group.Groups) > 0 {
- // call recursively prepareGroups function get collect entries
- return k.prepareGroups(strings.TrimPrefix(fmt.Sprintf("%s/%s", path, group.Name), "/"), group.Groups, mapData)
- }
- }
- return mapData
-}
diff --git a/pkg/providers/keypass_test.go b/pkg/providers/keypass_test.go
deleted file mode 100644
index 7cd69c1c..00000000
--- a/pkg/providers/keypass_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package providers
-
-import (
- "os"
- "path"
- "runtime"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/spectralops/teller/pkg/core"
-)
-
-func TestKetPass(t *testing.T) {
-
- _, filename, _, _ := runtime.Caller(0) //nolint
- os.Setenv("KEYPASS_PASSWORD", "1234")
-
- os.Setenv("KEYPASS_DB_PATH", path.Join(path.Dir(filename), "mock_providers", "keypass.kdbx"))
-
- k, err := NewKeyPass(GetTestLogger())
- assert.Nil(t, err)
- AssertProvider(t, k, false)
- p := core.NewPopulate(map[string]string{"stage": "prod"})
- kpmap := p.KeyPath(core.KeyPath{Field: "MG_KEY", Path: "settings/{{stage}}/billing-svc/all", Decrypt: true})
- ents, err := k.GetMapping(kpmap)
- assert.Nil(t, err)
- assert.Equal(t, len(ents), 4)
-}
-
-func TestKeypassFailures(t *testing.T) {
-
- _, filename, _, _ := runtime.Caller(0) //nolint
- os.Setenv("KEYPASS_PASSWORD", "1234")
-
- os.Setenv("KEYPASS_DB_PATH", path.Join(path.Dir(filename), "mock_providers", "keypass.kdbx"))
-
- k, _ := NewKeyPass(GetTestLogger())
- _, err := k.Get(core.KeyPath{Env: "NOT_EXISTS", Path: "settings"})
- assert.NotNil(t, err)
-
-}
diff --git a/pkg/providers/lastpass.go b/pkg/providers/lastpass.go
deleted file mode 100644
index 63afafde..00000000
--- a/pkg/providers/lastpass.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package providers
-
-import (
- "bufio"
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/spectralops/teller/pkg/core"
-
- "github.com/mattn/lastpass-go"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-const (
- findingNoteCount = 2
-)
-
-type LastPass struct {
- accounts map[string]*lastpass.Account
- logger logging.Logger
-}
-
-const LastPassName = "lastpass"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "Lastpass",
- Authentication: "TODO(XXX)",
- Name: LastPassName,
- ConfigTemplate: `
- # Configure via environment variables:
- # LASTPASS_USERNAME
- # LASTPASS_PASSWORD
-
- lastpass:
- env_sync:
- path: # LastPass item ID
- env:
- ETC_DSN:
- path: # Lastpass item ID
- # field: by default taking password property. in case you want other property un-mark this line and set the lastpass property name.
-`,
- Ops: core.OpMatrix{GetMapping: true, Get: true},
- }
-
- RegisterProvider(metaInfo, NewLastPass)
-}
-
-func NewLastPass(logger logging.Logger) (core.Provider, error) {
- username := os.Getenv("LASTPASS_USERNAME")
- masterPassword := os.Getenv("LASTPASS_PASSWORD")
-
- vault, err := lastpass.CreateVault(username, masterPassword)
- if err != nil {
- return nil, err
- }
-
- accountsMap := map[string]*lastpass.Account{}
- for _, account := range vault.Accounts {
- accountsMap[account.Id] = account
- }
-
- return &LastPass{accounts: accountsMap, logger: logger}, nil
-}
-
-func (l *LastPass) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", LastPassName)
-}
-
-func (l *LastPass) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", LastPassName)
-}
-
-func (l *LastPass) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
-
- item, err := l.getSecretByID(p.Path)
- if err != nil {
- return nil, err
- }
-
- entries := []core.EnvEntry{}
- entries = append(entries, p.FoundWithKey("Name", item.Name), p.FoundWithKey("Password", item.Password), p.FoundWithKey("Url", item.Url))
-
- for k, v := range l.notesToMap(item.Notes) {
- entries = append(entries, p.FoundWithKey(strings.ReplaceAll(k, " ", "_"), v))
- }
-
- return entries, nil
-}
-
-func (l *LastPass) Get(p core.KeyPath) (*core.EnvEntry, error) {
-
- item, err := l.getSecretByID(p.Path)
- if err != nil {
- return nil, err
- }
-
- var ent = p.Missing()
- // if field not defined, password field returned
- if p.Field == "" {
- l.logger.WithField("path", p.Path).Debug("field attribute is empty, return item password attribute")
- ent = p.Found(item.Password)
- } else {
- l.logger.WithFields(map[string]interface{}{
- "path": p.Path,
- "field": p.Field,
- }).Debug("field attribute present, search filed name in notes list")
- key, err := l.getNodeByKeyName(p.Field, item.Notes)
- if err == nil {
- ent = p.Found(key)
- }
- }
-
- return &ent, nil
-}
-
-func (l *LastPass) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", LastPassName)
-}
-
-func (l *LastPass) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", LastPassName)
-}
-
-func (l *LastPass) getSecretByID(id string) (*lastpass.Account, error) {
-
- if item, found := l.accounts[id]; found {
- return item, nil
- }
- return nil, errors.New("item ID not found")
-
-}
-
-// notesToMap parse LastPass note convention to map string
-//
-// Example:
-// `
-// card:a
-// Type:b
-// `
-// TO:
-// {"card": "a", "Type": "b"}
-func (l *LastPass) notesToMap(notes string) map[string]string {
-
- results := map[string]string{}
- scanner := bufio.NewScanner(strings.NewReader(notes))
- for scanner.Scan() {
- findings := strings.SplitN(scanner.Text(), ":", 2) //nolint: gomnd
- if len(findings) == findingNoteCount {
- results[strings.TrimSpace(findings[0])] = strings.TrimSpace(findings[1])
- }
- }
- return results
-}
-
-// getNodeByKeyName parse LastPass note convention and search if one of the note equal to the given key
-func (l *LastPass) getNodeByKeyName(key, notes string) (string, error) {
-
- scanner := bufio.NewScanner(strings.NewReader(notes))
- for scanner.Scan() {
- findings := strings.SplitN(scanner.Text(), ":", 2) //nolint: gomnd
- if len(findings) == findingNoteCount && findings[0] == key {
- return strings.TrimSpace(findings[1]), nil
- }
- }
- l.logger.WithFields(map[string]interface{}{
- "field": key,
- }).Debug("field attribute not found in notes list")
- return "", errors.New("key not found")
-}
diff --git a/pkg/providers/lastpass_test.go b/pkg/providers/lastpass_test.go
deleted file mode 100644
index c4db94e3..00000000
--- a/pkg/providers/lastpass_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package providers
-
-import (
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/mattn/lastpass-go"
- "github.com/spectralops/teller/pkg/core"
-)
-
-func TestLastPass(t *testing.T) {
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
-
- notes := `MG_KEY: shazam
-Sec key: secret-from-note
-`
- lastPassProvider := LastPass{
- accounts: map[string]*lastpass.Account{
- path: {
- Id: "id",
- Name: "secret-name",
- Username: "username",
- Password: "shazam",
- Url: "http://test.com",
- Group: "",
- Notes: notes,
- },
- pathmap: {
- Id: "id-2",
- Name: "secret-name-2",
- Username: "shazam",
- Password: "username-2",
- Url: "http://test.com",
- Group: "",
- Notes: notes,
- },
- },
- logger: GetTestLogger(),
- }
- AssertProvider(t, &lastPassProvider, false)
-
- p := core.NewPopulate(map[string]string{"stage": "prod"})
- kpmap := p.KeyPath(core.KeyPath{Field: "MG_KEY", Path: "settings/{{stage}}/billing-svc/all", Decrypt: true})
-
- ents, err := lastPassProvider.GetMapping(kpmap)
- assert.Nil(t, err)
- assert.Equal(t, len(ents), 5)
-
- // collect all the fields that returned from GetMapping
- allValues := []string{}
- for _, f := range ents {
- allValues = append(allValues, f.Value)
- }
-
- assert.Contains(t, allValues, "secret-name-2")
- assert.Contains(t, allValues, "username-2")
- assert.Contains(t, allValues, "http://test.com")
- assert.Contains(t, allValues, "shazam")
- assert.Contains(t, allValues, "secret-from-note")
-
-}
-
-func TestLastPassFailures(t *testing.T) {
- lastPassProvider := LastPass{
- accounts: map[string]*lastpass.Account{},
- logger: GetTestLogger(),
- }
-
- _, err := lastPassProvider.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
- _, err = lastPassProvider.GetMapping(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/mock_providers/ansible_vault_mock.go b/pkg/providers/mock_providers/ansible_vault_mock.go
deleted file mode 100644
index 7fd71ba8..00000000
--- a/pkg/providers/mock_providers/ansible_vault_mock.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/ansible_vault.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockAnsibleVaultClient is a mock of AnsibleVaultClient interface.
-type MockAnsibleVaultClient struct {
- ctrl *gomock.Controller
- recorder *MockAnsibleVaultClientMockRecorder
-}
-
-// MockAnsibleVaultClientMockRecorder is the mock recorder for MockAnsibleVaultClient.
-type MockAnsibleVaultClientMockRecorder struct {
- mock *MockAnsibleVaultClient
-}
-
-// NewMockAnsibleVaultClient creates a new mock instance.
-func NewMockAnsibleVaultClient(ctrl *gomock.Controller) *MockAnsibleVaultClient {
- mock := &MockAnsibleVaultClient{ctrl: ctrl}
- mock.recorder = &MockAnsibleVaultClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockAnsibleVaultClient) EXPECT() *MockAnsibleVaultClientMockRecorder {
- return m.recorder
-}
-
-// Read mocks base method.
-func (m *MockAnsibleVaultClient) Read(p string) (map[string]string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Read", p)
- ret0, _ := ret[0].(map[string]string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Read indicates an expected call of Read.
-func (mr *MockAnsibleVaultClientMockRecorder) Read(p interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockAnsibleVaultClient)(nil).Read), p)
-}
diff --git a/pkg/providers/mock_providers/aws_secretsmanager_mock.go b/pkg/providers/mock_providers/aws_secretsmanager_mock.go
deleted file mode 100644
index c3d25077..00000000
--- a/pkg/providers/mock_providers/aws_secretsmanager_mock.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/aws_secretsmanager.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- secretsmanager "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockAWSSecretsManagerClient is a mock of AWSSecretsManagerClient interface.
-type MockAWSSecretsManagerClient struct {
- ctrl *gomock.Controller
- recorder *MockAWSSecretsManagerClientMockRecorder
-}
-
-// MockAWSSecretsManagerClientMockRecorder is the mock recorder for MockAWSSecretsManagerClient.
-type MockAWSSecretsManagerClientMockRecorder struct {
- mock *MockAWSSecretsManagerClient
-}
-
-// NewMockAWSSecretsManagerClient creates a new mock instance.
-func NewMockAWSSecretsManagerClient(ctrl *gomock.Controller) *MockAWSSecretsManagerClient {
- mock := &MockAWSSecretsManagerClient{ctrl: ctrl}
- mock.recorder = &MockAWSSecretsManagerClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockAWSSecretsManagerClient) EXPECT() *MockAWSSecretsManagerClientMockRecorder {
- return m.recorder
-}
-
-// CreateSecret mocks base method.
-func (m *MockAWSSecretsManagerClient) CreateSecret(ctx context.Context, params *secretsmanager.CreateSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.CreateSecretOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "CreateSecret", varargs...)
- ret0, _ := ret[0].(*secretsmanager.CreateSecretOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// CreateSecret indicates an expected call of CreateSecret.
-func (mr *MockAWSSecretsManagerClientMockRecorder) CreateSecret(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAWSSecretsManagerClient)(nil).CreateSecret), varargs...)
-}
-
-// DeleteSecret mocks base method.
-func (m *MockAWSSecretsManagerClient) DeleteSecret(ctx context.Context, params *secretsmanager.DeleteSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.DeleteSecretOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "DeleteSecret", varargs...)
- ret0, _ := ret[0].(*secretsmanager.DeleteSecretOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DeleteSecret indicates an expected call of DeleteSecret.
-func (mr *MockAWSSecretsManagerClientMockRecorder) DeleteSecret(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSecret", reflect.TypeOf((*MockAWSSecretsManagerClient)(nil).DeleteSecret), varargs...)
-}
-
-// DescribeSecret mocks base method.
-func (m *MockAWSSecretsManagerClient) DescribeSecret(ctx context.Context, params *secretsmanager.DescribeSecretInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.DescribeSecretOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "DescribeSecret", varargs...)
- ret0, _ := ret[0].(*secretsmanager.DescribeSecretOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DescribeSecret indicates an expected call of DescribeSecret.
-func (mr *MockAWSSecretsManagerClientMockRecorder) DescribeSecret(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeSecret", reflect.TypeOf((*MockAWSSecretsManagerClient)(nil).DescribeSecret), varargs...)
-}
-
-// GetSecretValue mocks base method.
-func (m *MockAWSSecretsManagerClient) GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "GetSecretValue", varargs...)
- ret0, _ := ret[0].(*secretsmanager.GetSecretValueOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetSecretValue indicates an expected call of GetSecretValue.
-func (mr *MockAWSSecretsManagerClientMockRecorder) GetSecretValue(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecretValue", reflect.TypeOf((*MockAWSSecretsManagerClient)(nil).GetSecretValue), varargs...)
-}
-
-// PutSecretValue mocks base method.
-func (m *MockAWSSecretsManagerClient) PutSecretValue(ctx context.Context, params *secretsmanager.PutSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.PutSecretValueOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "PutSecretValue", varargs...)
- ret0, _ := ret[0].(*secretsmanager.PutSecretValueOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// PutSecretValue indicates an expected call of PutSecretValue.
-func (mr *MockAWSSecretsManagerClientMockRecorder) PutSecretValue(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutSecretValue", reflect.TypeOf((*MockAWSSecretsManagerClient)(nil).PutSecretValue), varargs...)
-}
diff --git a/pkg/providers/mock_providers/aws_ssm_mock.go b/pkg/providers/mock_providers/aws_ssm_mock.go
deleted file mode 100644
index 9e7d17d7..00000000
--- a/pkg/providers/mock_providers/aws_ssm_mock.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/aws_ssm.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- ssm "github.com/aws/aws-sdk-go-v2/service/ssm"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockAWSSSMClient is a mock of AWSSSMClient interface.
-type MockAWSSSMClient struct {
- ctrl *gomock.Controller
- recorder *MockAWSSSMClientMockRecorder
-}
-
-// MockAWSSSMClientMockRecorder is the mock recorder for MockAWSSSMClient.
-type MockAWSSSMClientMockRecorder struct {
- mock *MockAWSSSMClient
-}
-
-// NewMockAWSSSMClient creates a new mock instance.
-func NewMockAWSSSMClient(ctrl *gomock.Controller) *MockAWSSSMClient {
- mock := &MockAWSSSMClient{ctrl: ctrl}
- mock.recorder = &MockAWSSSMClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockAWSSSMClient) EXPECT() *MockAWSSSMClientMockRecorder {
- return m.recorder
-}
-
-// DeleteParameter mocks base method.
-func (m *MockAWSSSMClient) DeleteParameter(ctx context.Context, params *ssm.DeleteParameterInput, optFns ...func(*ssm.Options)) (*ssm.DeleteParameterOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "DeleteParameter", varargs...)
- ret0, _ := ret[0].(*ssm.DeleteParameterOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DeleteParameter indicates an expected call of DeleteParameter.
-func (mr *MockAWSSSMClientMockRecorder) DeleteParameter(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteParameter", reflect.TypeOf((*MockAWSSSMClient)(nil).DeleteParameter), varargs...)
-}
-
-// GetParameter mocks base method.
-func (m *MockAWSSSMClient) GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "GetParameter", varargs...)
- ret0, _ := ret[0].(*ssm.GetParameterOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetParameter indicates an expected call of GetParameter.
-func (mr *MockAWSSSMClientMockRecorder) GetParameter(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParameter", reflect.TypeOf((*MockAWSSSMClient)(nil).GetParameter), varargs...)
-}
-
-// PutParameter mocks base method.
-func (m *MockAWSSSMClient) PutParameter(ctx context.Context, params *ssm.PutParameterInput, optFns ...func(*ssm.Options)) (*ssm.PutParameterOutput, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, params}
- for _, a := range optFns {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "PutParameter", varargs...)
- ret0, _ := ret[0].(*ssm.PutParameterOutput)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// PutParameter indicates an expected call of PutParameter.
-func (mr *MockAWSSSMClientMockRecorder) PutParameter(ctx, params interface{}, optFns ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, params}, optFns...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutParameter", reflect.TypeOf((*MockAWSSSMClient)(nil).PutParameter), varargs...)
-}
diff --git a/pkg/providers/mock_providers/azure_keyvault_mock.go b/pkg/providers/mock_providers/azure_keyvault_mock.go
deleted file mode 100644
index 9b763579..00000000
--- a/pkg/providers/mock_providers/azure_keyvault_mock.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/azure_keyvault.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- keyvault "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockAzureKeyVaultClient is a mock of AzureKeyVaultClient interface.
-type MockAzureKeyVaultClient struct {
- ctrl *gomock.Controller
- recorder *MockAzureKeyVaultClientMockRecorder
-}
-
-// MockAzureKeyVaultClientMockRecorder is the mock recorder for MockAzureKeyVaultClient.
-type MockAzureKeyVaultClientMockRecorder struct {
- mock *MockAzureKeyVaultClient
-}
-
-// NewMockAzureKeyVaultClient creates a new mock instance.
-func NewMockAzureKeyVaultClient(ctrl *gomock.Controller) *MockAzureKeyVaultClient {
- mock := &MockAzureKeyVaultClient{ctrl: ctrl}
- mock.recorder = &MockAzureKeyVaultClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockAzureKeyVaultClient) EXPECT() *MockAzureKeyVaultClientMockRecorder {
- return m.recorder
-}
-
-// DeleteSecret mocks base method.
-func (m *MockAzureKeyVaultClient) DeleteSecret(ctx context.Context, vaultBaseURL, secretName string) (keyvault.DeletedSecretBundle, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "DeleteSecret", ctx, vaultBaseURL, secretName)
- ret0, _ := ret[0].(keyvault.DeletedSecretBundle)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DeleteSecret indicates an expected call of DeleteSecret.
-func (mr *MockAzureKeyVaultClientMockRecorder) DeleteSecret(ctx, vaultBaseURL, secretName interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSecret", reflect.TypeOf((*MockAzureKeyVaultClient)(nil).DeleteSecret), ctx, vaultBaseURL, secretName)
-}
-
-// GetSecret mocks base method.
-func (m *MockAzureKeyVaultClient) GetSecret(ctx context.Context, vaultBaseURL, secretName, secretVersion string) (keyvault.SecretBundle, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetSecret", ctx, vaultBaseURL, secretName, secretVersion)
- ret0, _ := ret[0].(keyvault.SecretBundle)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetSecret indicates an expected call of GetSecret.
-func (mr *MockAzureKeyVaultClientMockRecorder) GetSecret(ctx, vaultBaseURL, secretName, secretVersion interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecret", reflect.TypeOf((*MockAzureKeyVaultClient)(nil).GetSecret), ctx, vaultBaseURL, secretName, secretVersion)
-}
-
-// GetSecrets mocks base method.
-func (m *MockAzureKeyVaultClient) GetSecrets(ctx context.Context, vaultBaseURL string, maxresults *int32) (keyvault.SecretListResultPage, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetSecrets", ctx, vaultBaseURL, maxresults)
- ret0, _ := ret[0].(keyvault.SecretListResultPage)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetSecrets indicates an expected call of GetSecrets.
-func (mr *MockAzureKeyVaultClientMockRecorder) GetSecrets(ctx, vaultBaseURL, maxresults interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecrets", reflect.TypeOf((*MockAzureKeyVaultClient)(nil).GetSecrets), ctx, vaultBaseURL, maxresults)
-}
-
-// SetSecret mocks base method.
-func (m *MockAzureKeyVaultClient) SetSecret(ctx context.Context, vaultBaseURL, secretName string, parameters keyvault.SecretSetParameters) (keyvault.SecretBundle, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "SetSecret", ctx, vaultBaseURL, secretName, parameters)
- ret0, _ := ret[0].(keyvault.SecretBundle)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// SetSecret indicates an expected call of SetSecret.
-func (mr *MockAzureKeyVaultClientMockRecorder) SetSecret(ctx, vaultBaseURL, secretName, parameters interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSecret", reflect.TypeOf((*MockAzureKeyVaultClient)(nil).SetSecret), ctx, vaultBaseURL, secretName, parameters)
-}
diff --git a/pkg/providers/mock_providers/cloudflare_workers_kv_mock.go b/pkg/providers/mock_providers/cloudflare_workers_kv_mock.go
deleted file mode 100644
index 951774f5..00000000
--- a/pkg/providers/mock_providers/cloudflare_workers_kv_mock.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/cloudflare_workers_kv.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- cloudflare "github.com/cloudflare/cloudflare-go"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockCloudflareClient is a mock of CloudflareClient interface.
-type MockCloudflareClient struct {
- ctrl *gomock.Controller
- recorder *MockCloudflareClientMockRecorder
-}
-
-// MockCloudflareClientMockRecorder is the mock recorder for MockCloudflareClient.
-type MockCloudflareClientMockRecorder struct {
- mock *MockCloudflareClient
-}
-
-// NewMockCloudflareClient creates a new mock instance.
-func NewMockCloudflareClient(ctrl *gomock.Controller) *MockCloudflareClient {
- mock := &MockCloudflareClient{ctrl: ctrl}
- mock.recorder = &MockCloudflareClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockCloudflareClient) EXPECT() *MockCloudflareClientMockRecorder {
- return m.recorder
-}
-
-// ListWorkersKVs mocks base method.
-func (m *MockCloudflareClient) ListWorkersKVs(ctx context.Context, namespaceID string) (cloudflare.ListStorageKeysResponse, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListWorkersKVs", ctx, namespaceID)
- ret0, _ := ret[0].(cloudflare.ListStorageKeysResponse)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// ListWorkersKVs indicates an expected call of ListWorkersKVs.
-func (mr *MockCloudflareClientMockRecorder) ListWorkersKVs(ctx, namespaceID interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWorkersKVs", reflect.TypeOf((*MockCloudflareClient)(nil).ListWorkersKVs), ctx, namespaceID)
-}
-
-// ReadWorkersKV mocks base method.
-func (m *MockCloudflareClient) ReadWorkersKV(ctx context.Context, namespaceID, key string) ([]byte, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ReadWorkersKV", ctx, namespaceID, key)
- ret0, _ := ret[0].([]byte)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// ReadWorkersKV indicates an expected call of ReadWorkersKV.
-func (mr *MockCloudflareClientMockRecorder) ReadWorkersKV(ctx, namespaceID, key interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadWorkersKV", reflect.TypeOf((*MockCloudflareClient)(nil).ReadWorkersKV), ctx, namespaceID, key)
-}
-
-// WriteWorkersKV mocks base method.
-func (m *MockCloudflareClient) WriteWorkersKV(ctx context.Context, namespaceID, key string, value []byte) (cloudflare.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "WriteWorkersKV", ctx, namespaceID, key, value)
- ret0, _ := ret[0].(cloudflare.Response)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// WriteWorkersKV indicates an expected call of WriteWorkersKV.
-func (mr *MockCloudflareClientMockRecorder) WriteWorkersKV(ctx, namespaceID, key, value interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteWorkersKV", reflect.TypeOf((*MockCloudflareClient)(nil).WriteWorkersKV), ctx, namespaceID, key, value)
-}
-
-// WriteWorkersKVBulk mocks base method.
-func (m *MockCloudflareClient) WriteWorkersKVBulk(ctx context.Context, namespaceID string, kvs cloudflare.WorkersKVBulkWriteRequest) (cloudflare.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "WriteWorkersKVBulk", ctx, namespaceID, kvs)
- ret0, _ := ret[0].(cloudflare.Response)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// WriteWorkersKVBulk indicates an expected call of WriteWorkersKVBulk.
-func (mr *MockCloudflareClientMockRecorder) WriteWorkersKVBulk(ctx, namespaceID, kvs interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteWorkersKVBulk", reflect.TypeOf((*MockCloudflareClient)(nil).WriteWorkersKVBulk), ctx, namespaceID, kvs)
-}
diff --git a/pkg/providers/mock_providers/cloudflare_workers_secrets_mock.go b/pkg/providers/mock_providers/cloudflare_workers_secrets_mock.go
deleted file mode 100644
index 3a15cb4a..00000000
--- a/pkg/providers/mock_providers/cloudflare_workers_secrets_mock.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/cloudflare_workers_secrets.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- cloudflare "github.com/cloudflare/cloudflare-go"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockCloudflareSecretsClient is a mock of CloudflareSecretsClient interface.
-type MockCloudflareSecretsClient struct {
- ctrl *gomock.Controller
- recorder *MockCloudflareSecretsClientMockRecorder
-}
-
-// MockCloudflareSecretsClientMockRecorder is the mock recorder for MockCloudflareSecretsClient.
-type MockCloudflareSecretsClientMockRecorder struct {
- mock *MockCloudflareSecretsClient
-}
-
-// NewMockCloudflareSecretsClient creates a new mock instance.
-func NewMockCloudflareSecretsClient(ctrl *gomock.Controller) *MockCloudflareSecretsClient {
- mock := &MockCloudflareSecretsClient{ctrl: ctrl}
- mock.recorder = &MockCloudflareSecretsClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockCloudflareSecretsClient) EXPECT() *MockCloudflareSecretsClientMockRecorder {
- return m.recorder
-}
-
-// DeleteWorkersSecret mocks base method.
-func (m *MockCloudflareSecretsClient) DeleteWorkersSecret(ctx context.Context, script, secretName string) (cloudflare.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "DeleteWorkersSecret", ctx, script, secretName)
- ret0, _ := ret[0].(cloudflare.Response)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DeleteWorkersSecret indicates an expected call of DeleteWorkersSecret.
-func (mr *MockCloudflareSecretsClientMockRecorder) DeleteWorkersSecret(ctx, script, secretName interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWorkersSecret", reflect.TypeOf((*MockCloudflareSecretsClient)(nil).DeleteWorkersSecret), ctx, script, secretName)
-}
-
-// SetWorkersSecret mocks base method.
-func (m *MockCloudflareSecretsClient) SetWorkersSecret(ctx context.Context, script string, req *cloudflare.WorkersPutSecretRequest) (cloudflare.WorkersPutSecretResponse, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "SetWorkersSecret", ctx, script, req)
- ret0, _ := ret[0].(cloudflare.WorkersPutSecretResponse)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// SetWorkersSecret indicates an expected call of SetWorkersSecret.
-func (mr *MockCloudflareSecretsClientMockRecorder) SetWorkersSecret(ctx, script, req interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkersSecret", reflect.TypeOf((*MockCloudflareSecretsClient)(nil).SetWorkersSecret), ctx, script, req)
-}
diff --git a/pkg/providers/mock_providers/consul_mock.go b/pkg/providers/mock_providers/consul_mock.go
deleted file mode 100644
index 9b5c93fd..00000000
--- a/pkg/providers/mock_providers/consul_mock.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/consul.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- api "github.com/hashicorp/consul/api"
-)
-
-// MockConsulClient is a mock of ConsulClient interface.
-type MockConsulClient struct {
- ctrl *gomock.Controller
- recorder *MockConsulClientMockRecorder
-}
-
-// MockConsulClientMockRecorder is the mock recorder for MockConsulClient.
-type MockConsulClientMockRecorder struct {
- mock *MockConsulClient
-}
-
-// NewMockConsulClient creates a new mock instance.
-func NewMockConsulClient(ctrl *gomock.Controller) *MockConsulClient {
- mock := &MockConsulClient{ctrl: ctrl}
- mock.recorder = &MockConsulClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockConsulClient) EXPECT() *MockConsulClientMockRecorder {
- return m.recorder
-}
-
-// Get mocks base method.
-func (m *MockConsulClient) Get(key string, q *api.QueryOptions) (*api.KVPair, *api.QueryMeta, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Get", key, q)
- ret0, _ := ret[0].(*api.KVPair)
- ret1, _ := ret[1].(*api.QueryMeta)
- ret2, _ := ret[2].(error)
- return ret0, ret1, ret2
-}
-
-// Get indicates an expected call of Get.
-func (mr *MockConsulClientMockRecorder) Get(key, q interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockConsulClient)(nil).Get), key, q)
-}
-
-// List mocks base method.
-func (m *MockConsulClient) List(prefix string, q *api.QueryOptions) (api.KVPairs, *api.QueryMeta, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "List", prefix, q)
- ret0, _ := ret[0].(api.KVPairs)
- ret1, _ := ret[1].(*api.QueryMeta)
- ret2, _ := ret[2].(error)
- return ret0, ret1, ret2
-}
-
-// List indicates an expected call of List.
-func (mr *MockConsulClientMockRecorder) List(prefix, q interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockConsulClient)(nil).List), prefix, q)
-}
-
-// Put mocks base method.
-func (m *MockConsulClient) Put(p *api.KVPair, q *api.WriteOptions) (*api.WriteMeta, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Put", p, q)
- ret0, _ := ret[0].(*api.WriteMeta)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Put indicates an expected call of Put.
-func (mr *MockConsulClientMockRecorder) Put(p, q interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockConsulClient)(nil).Put), p, q)
-}
diff --git a/pkg/providers/mock_providers/cyberark_conjur_mock.go b/pkg/providers/mock_providers/cyberark_conjur_mock.go
deleted file mode 100644
index 7ffe59ea..00000000
--- a/pkg/providers/mock_providers/cyberark_conjur_mock.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/cyberark_conjur.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockConjurClient is a mock of ConjurClient interface.
-type MockConjurClient struct {
- ctrl *gomock.Controller
- recorder *MockConjurClientMockRecorder
-}
-
-// MockConjurClientMockRecorder is the mock recorder for MockConjurClient.
-type MockConjurClientMockRecorder struct {
- mock *MockConjurClient
-}
-
-// NewMockConjurClient creates a new mock instance.
-func NewMockConjurClient(ctrl *gomock.Controller) *MockConjurClient {
- mock := &MockConjurClient{ctrl: ctrl}
- mock.recorder = &MockConjurClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockConjurClient) EXPECT() *MockConjurClientMockRecorder {
- return m.recorder
-}
-
-// AddSecret mocks base method.
-func (m *MockConjurClient) AddSecret(variableId, secretValue string) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "AddSecret", variableId, secretValue)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// AddSecret indicates an expected call of AddSecret.
-func (mr *MockConjurClientMockRecorder) AddSecret(variableId, secretValue interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSecret", reflect.TypeOf((*MockConjurClient)(nil).AddSecret), variableId, secretValue)
-}
-
-// RetrieveSecret mocks base method.
-func (m *MockConjurClient) RetrieveSecret(variableId string) ([]byte, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "RetrieveSecret", variableId)
- ret0, _ := ret[0].([]byte)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// RetrieveSecret indicates an expected call of RetrieveSecret.
-func (mr *MockConjurClientMockRecorder) RetrieveSecret(variableId interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetrieveSecret", reflect.TypeOf((*MockConjurClient)(nil).RetrieveSecret), variableId)
-}
diff --git a/pkg/providers/mock_providers/doppler_mock.go b/pkg/providers/mock_providers/doppler_mock.go
deleted file mode 100644
index dec6c87a..00000000
--- a/pkg/providers/mock_providers/doppler_mock.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/doppler.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- http "github.com/DopplerHQ/cli/pkg/http"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockDopplerClient is a mock of DopplerClient interface.
-type MockDopplerClient struct {
- ctrl *gomock.Controller
- recorder *MockDopplerClientMockRecorder
-}
-
-// MockDopplerClientMockRecorder is the mock recorder for MockDopplerClient.
-type MockDopplerClientMockRecorder struct {
- mock *MockDopplerClient
-}
-
-// NewMockDopplerClient creates a new mock instance.
-func NewMockDopplerClient(ctrl *gomock.Controller) *MockDopplerClient {
- mock := &MockDopplerClient{ctrl: ctrl}
- mock.recorder = &MockDopplerClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockDopplerClient) EXPECT() *MockDopplerClientMockRecorder {
- return m.recorder
-}
-
-// GetSecrets mocks base method.
-func (m *MockDopplerClient) GetSecrets(host string, verifyTLS bool, apiKey, project, config string) ([]byte, http.Error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetSecrets", host, verifyTLS, apiKey, project, config)
- ret0, _ := ret[0].([]byte)
- ret1, _ := ret[1].(http.Error)
- return ret0, ret1
-}
-
-// GetSecrets indicates an expected call of GetSecrets.
-func (mr *MockDopplerClientMockRecorder) GetSecrets(host, verifyTLS, apiKey, project, config interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecrets", reflect.TypeOf((*MockDopplerClient)(nil).GetSecrets), host, verifyTLS, apiKey, project, config)
-}
diff --git a/pkg/providers/mock_providers/dotenv_mock.go b/pkg/providers/mock_providers/dotenv_mock.go
deleted file mode 100644
index c11e1ddb..00000000
--- a/pkg/providers/mock_providers/dotenv_mock.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/dotenv.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockDotEnvClient is a mock of DotEnvClient interface.
-type MockDotEnvClient struct {
- ctrl *gomock.Controller
- recorder *MockDotEnvClientMockRecorder
-}
-
-// MockDotEnvClientMockRecorder is the mock recorder for MockDotEnvClient.
-type MockDotEnvClientMockRecorder struct {
- mock *MockDotEnvClient
-}
-
-// NewMockDotEnvClient creates a new mock instance.
-func NewMockDotEnvClient(ctrl *gomock.Controller) *MockDotEnvClient {
- mock := &MockDotEnvClient{ctrl: ctrl}
- mock.recorder = &MockDotEnvClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockDotEnvClient) EXPECT() *MockDotEnvClientMockRecorder {
- return m.recorder
-}
-
-// Delete mocks base method.
-func (m *MockDotEnvClient) Delete(p string) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Delete", p)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// Delete indicates an expected call of Delete.
-func (mr *MockDotEnvClientMockRecorder) Delete(p interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDotEnvClient)(nil).Delete), p)
-}
-
-// Exists mocks base method.
-func (m *MockDotEnvClient) Exists(p string) (bool, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Exists", p)
- ret0, _ := ret[0].(bool)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Exists indicates an expected call of Exists.
-func (mr *MockDotEnvClientMockRecorder) Exists(p interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockDotEnvClient)(nil).Exists), p)
-}
-
-// Read mocks base method.
-func (m *MockDotEnvClient) Read(p string) (map[string]string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Read", p)
- ret0, _ := ret[0].(map[string]string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Read indicates an expected call of Read.
-func (mr *MockDotEnvClientMockRecorder) Read(p interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockDotEnvClient)(nil).Read), p)
-}
-
-// Write mocks base method.
-func (m *MockDotEnvClient) Write(p string, kvs map[string]string) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Write", p, kvs)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// Write indicates an expected call of Write.
-func (mr *MockDotEnvClientMockRecorder) Write(p, kvs interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockDotEnvClient)(nil).Write), p, kvs)
-}
diff --git a/pkg/providers/mock_providers/etcd_mock.go b/pkg/providers/mock_providers/etcd_mock.go
deleted file mode 100644
index 3bdcfaff..00000000
--- a/pkg/providers/mock_providers/etcd_mock.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/etcd.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- clientv3 "go.etcd.io/etcd/client/v3"
-)
-
-// MockEtcdClient is a mock of EtcdClient interface.
-type MockEtcdClient struct {
- ctrl *gomock.Controller
- recorder *MockEtcdClientMockRecorder
-}
-
-// MockEtcdClientMockRecorder is the mock recorder for MockEtcdClient.
-type MockEtcdClientMockRecorder struct {
- mock *MockEtcdClient
-}
-
-// NewMockEtcdClient creates a new mock instance.
-func NewMockEtcdClient(ctrl *gomock.Controller) *MockEtcdClient {
- mock := &MockEtcdClient{ctrl: ctrl}
- mock.recorder = &MockEtcdClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockEtcdClient) EXPECT() *MockEtcdClientMockRecorder {
- return m.recorder
-}
-
-// Get mocks base method.
-func (m *MockEtcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, key}
- for _, a := range opts {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "Get", varargs...)
- ret0, _ := ret[0].(*clientv3.GetResponse)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Get indicates an expected call of Get.
-func (mr *MockEtcdClientMockRecorder) Get(ctx, key interface{}, opts ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, key}, opts...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockEtcdClient)(nil).Get), varargs...)
-}
-
-// Put mocks base method.
-func (m *MockEtcdClient) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, key, val}
- for _, a := range opts {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "Put", varargs...)
- ret0, _ := ret[0].(*clientv3.PutResponse)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Put indicates an expected call of Put.
-func (mr *MockEtcdClientMockRecorder) Put(ctx, key, val interface{}, opts ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, key, val}, opts...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockEtcdClient)(nil).Put), varargs...)
-}
diff --git a/pkg/providers/mock_providers/github_mock.go b/pkg/providers/mock_providers/github_mock.go
deleted file mode 100644
index 2fffe082..00000000
--- a/pkg/providers/mock_providers/github_mock.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/github.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- github "github.com/google/go-github/v43/github"
-)
-
-// MockGitHubActionClient is a mock of GitHubActionClient interface.
-type MockGitHubActionClient struct {
- ctrl *gomock.Controller
- recorder *MockGitHubActionClientMockRecorder
-}
-
-// MockGitHubActionClientMockRecorder is the mock recorder for MockGitHubActionClient.
-type MockGitHubActionClientMockRecorder struct {
- mock *MockGitHubActionClient
-}
-
-// NewMockGitHubActionClient creates a new mock instance.
-func NewMockGitHubActionClient(ctrl *gomock.Controller) *MockGitHubActionClient {
- mock := &MockGitHubActionClient{ctrl: ctrl}
- mock.recorder = &MockGitHubActionClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockGitHubActionClient) EXPECT() *MockGitHubActionClientMockRecorder {
- return m.recorder
-}
-
-// CreateOrUpdateRepoSecret mocks base method.
-func (m *MockGitHubActionClient) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *github.EncryptedSecret) (*github.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "CreateOrUpdateRepoSecret", ctx, owner, repo, eSecret)
- ret0, _ := ret[0].(*github.Response)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// CreateOrUpdateRepoSecret indicates an expected call of CreateOrUpdateRepoSecret.
-func (mr *MockGitHubActionClientMockRecorder) CreateOrUpdateRepoSecret(ctx, owner, repo, eSecret interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateRepoSecret", reflect.TypeOf((*MockGitHubActionClient)(nil).CreateOrUpdateRepoSecret), ctx, owner, repo, eSecret)
-}
-
-// DeleteRepoSecret mocks base method.
-func (m *MockGitHubActionClient) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*github.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "DeleteRepoSecret", ctx, owner, repo, name)
- ret0, _ := ret[0].(*github.Response)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DeleteRepoSecret indicates an expected call of DeleteRepoSecret.
-func (mr *MockGitHubActionClientMockRecorder) DeleteRepoSecret(ctx, owner, repo, name interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRepoSecret", reflect.TypeOf((*MockGitHubActionClient)(nil).DeleteRepoSecret), ctx, owner, repo, name)
-}
-
-// GetRepoPublicKey mocks base method.
-func (m *MockGitHubActionClient) GetRepoPublicKey(ctx context.Context, owner, repo string) (*github.PublicKey, *github.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetRepoPublicKey", ctx, owner, repo)
- ret0, _ := ret[0].(*github.PublicKey)
- ret1, _ := ret[1].(*github.Response)
- ret2, _ := ret[2].(error)
- return ret0, ret1, ret2
-}
-
-// GetRepoPublicKey indicates an expected call of GetRepoPublicKey.
-func (mr *MockGitHubActionClientMockRecorder) GetRepoPublicKey(ctx, owner, repo interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepoPublicKey", reflect.TypeOf((*MockGitHubActionClient)(nil).GetRepoPublicKey), ctx, owner, repo)
-}
-
-// ListRepoSecrets mocks base method.
-func (m *MockGitHubActionClient) ListRepoSecrets(ctx context.Context, owner, repo string, opts *github.ListOptions) (*github.Secrets, *github.Response, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListRepoSecrets", ctx, owner, repo, opts)
- ret0, _ := ret[0].(*github.Secrets)
- ret1, _ := ret[1].(*github.Response)
- ret2, _ := ret[2].(error)
- return ret0, ret1, ret2
-}
-
-// ListRepoSecrets indicates an expected call of ListRepoSecrets.
-func (mr *MockGitHubActionClientMockRecorder) ListRepoSecrets(ctx, owner, repo, opts interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRepoSecrets", reflect.TypeOf((*MockGitHubActionClient)(nil).ListRepoSecrets), ctx, owner, repo, opts)
-}
diff --git a/pkg/providers/mock_providers/google_secretmanager_mock.go b/pkg/providers/mock_providers/google_secretmanager_mock.go
deleted file mode 100644
index a78b3c56..00000000
--- a/pkg/providers/mock_providers/google_secretmanager_mock.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/google_secretmanager.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- secretmanager "cloud.google.com/go/secretmanager/apiv1"
- gomock "github.com/golang/mock/gomock"
- gax "github.com/googleapis/gax-go/v2"
- secretmanager0 "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
-)
-
-// MockGoogleSMClient is a mock of GoogleSMClient interface.
-type MockGoogleSMClient struct {
- ctrl *gomock.Controller
- recorder *MockGoogleSMClientMockRecorder
-}
-
-// MockGoogleSMClientMockRecorder is the mock recorder for MockGoogleSMClient.
-type MockGoogleSMClientMockRecorder struct {
- mock *MockGoogleSMClient
-}
-
-// NewMockGoogleSMClient creates a new mock instance.
-func NewMockGoogleSMClient(ctrl *gomock.Controller) *MockGoogleSMClient {
- mock := &MockGoogleSMClient{ctrl: ctrl}
- mock.recorder = &MockGoogleSMClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockGoogleSMClient) EXPECT() *MockGoogleSMClientMockRecorder {
- return m.recorder
-}
-
-// AccessSecretVersion mocks base method.
-func (m *MockGoogleSMClient) AccessSecretVersion(ctx context.Context, req *secretmanager0.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanager0.AccessSecretVersionResponse, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, req}
- for _, a := range opts {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "AccessSecretVersion", varargs...)
- ret0, _ := ret[0].(*secretmanager0.AccessSecretVersionResponse)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// AccessSecretVersion indicates an expected call of AccessSecretVersion.
-func (mr *MockGoogleSMClientMockRecorder) AccessSecretVersion(ctx, req interface{}, opts ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, req}, opts...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessSecretVersion", reflect.TypeOf((*MockGoogleSMClient)(nil).AccessSecretVersion), varargs...)
-}
-
-// AddSecretVersion mocks base method.
-func (m *MockGoogleSMClient) AddSecretVersion(ctx context.Context, req *secretmanager0.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanager0.SecretVersion, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, req}
- for _, a := range opts {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "AddSecretVersion", varargs...)
- ret0, _ := ret[0].(*secretmanager0.SecretVersion)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// AddSecretVersion indicates an expected call of AddSecretVersion.
-func (mr *MockGoogleSMClientMockRecorder) AddSecretVersion(ctx, req interface{}, opts ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, req}, opts...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSecretVersion", reflect.TypeOf((*MockGoogleSMClient)(nil).AddSecretVersion), varargs...)
-}
-
-// DestroySecretVersion mocks base method.
-func (m *MockGoogleSMClient) DestroySecretVersion(ctx context.Context, req *secretmanager0.DestroySecretVersionRequest, opts ...gax.CallOption) (*secretmanager0.SecretVersion, error) {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, req}
- for _, a := range opts {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "DestroySecretVersion", varargs...)
- ret0, _ := ret[0].(*secretmanager0.SecretVersion)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// DestroySecretVersion indicates an expected call of DestroySecretVersion.
-func (mr *MockGoogleSMClientMockRecorder) DestroySecretVersion(ctx, req interface{}, opts ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, req}, opts...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DestroySecretVersion", reflect.TypeOf((*MockGoogleSMClient)(nil).DestroySecretVersion), varargs...)
-}
-
-// ListSecrets mocks base method.
-func (m *MockGoogleSMClient) ListSecrets(ctx context.Context, in *secretmanager0.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator {
- m.ctrl.T.Helper()
- varargs := []interface{}{ctx, in}
- for _, a := range opts {
- varargs = append(varargs, a)
- }
- ret := m.ctrl.Call(m, "ListSecrets", varargs...)
- ret0, _ := ret[0].(*secretmanager.SecretIterator)
- return ret0
-}
-
-// ListSecrets indicates an expected call of ListSecrets.
-func (mr *MockGoogleSMClientMockRecorder) ListSecrets(ctx, in interface{}, opts ...interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- varargs := append([]interface{}{ctx, in}, opts...)
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSecrets", reflect.TypeOf((*MockGoogleSMClient)(nil).ListSecrets), varargs...)
-}
diff --git a/pkg/providers/mock_providers/gopass_mock.go b/pkg/providers/mock_providers/gopass_mock.go
deleted file mode 100644
index a0f8b564..00000000
--- a/pkg/providers/mock_providers/gopass_mock.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/gopass.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- gopass "github.com/gopasspw/gopass/pkg/gopass"
-)
-
-// MockGopassClient is a mock of GopassClient interface.
-type MockGopassClient struct {
- ctrl *gomock.Controller
- recorder *MockGopassClientMockRecorder
-}
-
-// MockGopassClientMockRecorder is the mock recorder for MockGopassClient.
-type MockGopassClientMockRecorder struct {
- mock *MockGopassClient
-}
-
-// NewMockGopassClient creates a new mock instance.
-func NewMockGopassClient(ctrl *gomock.Controller) *MockGopassClient {
- mock := &MockGopassClient{ctrl: ctrl}
- mock.recorder = &MockGopassClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockGopassClient) EXPECT() *MockGopassClientMockRecorder {
- return m.recorder
-}
-
-// Get mocks base method.
-func (m *MockGopassClient) Get(ctx context.Context, name, revision string) (gopass.Secret, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Get", ctx, name, revision)
- ret0, _ := ret[0].(gopass.Secret)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Get indicates an expected call of Get.
-func (mr *MockGopassClientMockRecorder) Get(ctx, name, revision interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockGopassClient)(nil).Get), ctx, name, revision)
-}
-
-// List mocks base method.
-func (m *MockGopassClient) List(ctx context.Context) ([]string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "List", ctx)
- ret0, _ := ret[0].([]string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// List indicates an expected call of List.
-func (mr *MockGopassClientMockRecorder) List(ctx interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockGopassClient)(nil).List), ctx)
-}
-
-// Set mocks base method.
-func (m *MockGopassClient) Set(ctx context.Context, name string, sec gopass.Byter) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Set", ctx, name, sec)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// Set indicates an expected call of Set.
-func (mr *MockGopassClientMockRecorder) Set(ctx, name, sec interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockGopassClient)(nil).Set), ctx, name, sec)
-}
diff --git a/pkg/providers/mock_providers/hashicorp_vault_mock.go b/pkg/providers/mock_providers/hashicorp_vault_mock.go
deleted file mode 100644
index dc3e9992..00000000
--- a/pkg/providers/mock_providers/hashicorp_vault_mock.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/hashicorp_vault.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- api "github.com/hashicorp/vault/api"
-)
-
-// MockHashicorpClient is a mock of HashicorpClient interface.
-type MockHashicorpClient struct {
- ctrl *gomock.Controller
- recorder *MockHashicorpClientMockRecorder
-}
-
-// MockHashicorpClientMockRecorder is the mock recorder for MockHashicorpClient.
-type MockHashicorpClientMockRecorder struct {
- mock *MockHashicorpClient
-}
-
-// NewMockHashicorpClient creates a new mock instance.
-func NewMockHashicorpClient(ctrl *gomock.Controller) *MockHashicorpClient {
- mock := &MockHashicorpClient{ctrl: ctrl}
- mock.recorder = &MockHashicorpClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockHashicorpClient) EXPECT() *MockHashicorpClientMockRecorder {
- return m.recorder
-}
-
-// Read mocks base method.
-func (m *MockHashicorpClient) Read(path string) (*api.Secret, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Read", path)
- ret0, _ := ret[0].(*api.Secret)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Read indicates an expected call of Read.
-func (mr *MockHashicorpClientMockRecorder) Read(path interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockHashicorpClient)(nil).Read), path)
-}
-
-// Write mocks base method.
-func (m *MockHashicorpClient) Write(path string, data map[string]interface{}) (*api.Secret, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Write", path, data)
- ret0, _ := ret[0].(*api.Secret)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// Write indicates an expected call of Write.
-func (mr *MockHashicorpClientMockRecorder) Write(path, data interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockHashicorpClient)(nil).Write), path, data)
-}
diff --git a/pkg/providers/mock_providers/heroku_mock.go b/pkg/providers/mock_providers/heroku_mock.go
deleted file mode 100644
index c6c05901..00000000
--- a/pkg/providers/mock_providers/heroku_mock.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/heroku.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- context "context"
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- v5 "github.com/heroku/heroku-go/v5"
-)
-
-// MockHerokuClient is a mock of HerokuClient interface.
-type MockHerokuClient struct {
- ctrl *gomock.Controller
- recorder *MockHerokuClientMockRecorder
-}
-
-// MockHerokuClientMockRecorder is the mock recorder for MockHerokuClient.
-type MockHerokuClientMockRecorder struct {
- mock *MockHerokuClient
-}
-
-// NewMockHerokuClient creates a new mock instance.
-func NewMockHerokuClient(ctrl *gomock.Controller) *MockHerokuClient {
- mock := &MockHerokuClient{ctrl: ctrl}
- mock.recorder = &MockHerokuClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockHerokuClient) EXPECT() *MockHerokuClientMockRecorder {
- return m.recorder
-}
-
-// ConfigVarInfoForApp mocks base method.
-func (m *MockHerokuClient) ConfigVarInfoForApp(ctx context.Context, appIdentity string) (v5.ConfigVarInfoForAppResult, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ConfigVarInfoForApp", ctx, appIdentity)
- ret0, _ := ret[0].(v5.ConfigVarInfoForAppResult)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// ConfigVarInfoForApp indicates an expected call of ConfigVarInfoForApp.
-func (mr *MockHerokuClientMockRecorder) ConfigVarInfoForApp(ctx, appIdentity interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigVarInfoForApp", reflect.TypeOf((*MockHerokuClient)(nil).ConfigVarInfoForApp), ctx, appIdentity)
-}
-
-// ConfigVarUpdate mocks base method.
-func (m *MockHerokuClient) ConfigVarUpdate(ctx context.Context, appIdentity string, o map[string]*string) (v5.ConfigVarUpdateResult, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ConfigVarUpdate", ctx, appIdentity, o)
- ret0, _ := ret[0].(v5.ConfigVarUpdateResult)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// ConfigVarUpdate indicates an expected call of ConfigVarUpdate.
-func (mr *MockHerokuClientMockRecorder) ConfigVarUpdate(ctx, appIdentity, o interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigVarUpdate", reflect.TypeOf((*MockHerokuClient)(nil).ConfigVarUpdate), ctx, appIdentity, o)
-}
diff --git a/pkg/providers/mock_providers/keeper_secretsmanager_mock.go b/pkg/providers/mock_providers/keeper_secretsmanager_mock.go
deleted file mode 100644
index 4fb634f8..00000000
--- a/pkg/providers/mock_providers/keeper_secretsmanager_mock.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/keeper_secretsmanager.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- core "github.com/spectralops/teller/pkg/core"
-)
-
-// MockKsmClient is a mock of KsmClient interface.
-type MockKsmClient struct {
- ctrl *gomock.Controller
- recorder *MockKsmClientMockRecorder
-}
-
-// MockKsmClientMockRecorder is the mock recorder for MockKsmClient.
-type MockKsmClientMockRecorder struct {
- mock *MockKsmClient
-}
-
-// NewMockKsmClient creates a new mock instance.
-func NewMockKsmClient(ctrl *gomock.Controller) *MockKsmClient {
- mock := &MockKsmClient{ctrl: ctrl}
- mock.recorder = &MockKsmClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockKsmClient) EXPECT() *MockKsmClientMockRecorder {
- return m.recorder
-}
-
-// GetSecret mocks base method.
-func (m *MockKsmClient) GetSecret(p core.KeyPath) (*core.EnvEntry, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetSecret", p)
- ret0, _ := ret[0].(*core.EnvEntry)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetSecret indicates an expected call of GetSecret.
-func (mr *MockKsmClientMockRecorder) GetSecret(p interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecret", reflect.TypeOf((*MockKsmClient)(nil).GetSecret), p)
-}
-
-// GetSecrets mocks base method.
-func (m *MockKsmClient) GetSecrets(p core.KeyPath) ([]core.EnvEntry, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetSecrets", p)
- ret0, _ := ret[0].([]core.EnvEntry)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetSecrets indicates an expected call of GetSecrets.
-func (mr *MockKsmClientMockRecorder) GetSecrets(p interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecrets", reflect.TypeOf((*MockKsmClient)(nil).GetSecrets), p)
-}
diff --git a/pkg/providers/mock_providers/keypass.kdbx b/pkg/providers/mock_providers/keypass.kdbx
deleted file mode 100644
index 589fb155..00000000
Binary files a/pkg/providers/mock_providers/keypass.kdbx and /dev/null differ
diff --git a/pkg/providers/mock_providers/onepassword_mock.go b/pkg/providers/mock_providers/onepassword_mock.go
deleted file mode 100644
index 1787eb86..00000000
--- a/pkg/providers/mock_providers/onepassword_mock.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/onepassword.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- onepassword "github.com/1Password/connect-sdk-go/onepassword"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockOnePasswordClient is a mock of OnePasswordClient interface.
-type MockOnePasswordClient struct {
- ctrl *gomock.Controller
- recorder *MockOnePasswordClientMockRecorder
-}
-
-// MockOnePasswordClientMockRecorder is the mock recorder for MockOnePasswordClient.
-type MockOnePasswordClientMockRecorder struct {
- mock *MockOnePasswordClient
-}
-
-// NewMockOnePasswordClient creates a new mock instance.
-func NewMockOnePasswordClient(ctrl *gomock.Controller) *MockOnePasswordClient {
- mock := &MockOnePasswordClient{ctrl: ctrl}
- mock.recorder = &MockOnePasswordClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockOnePasswordClient) EXPECT() *MockOnePasswordClientMockRecorder {
- return m.recorder
-}
-
-// GetItemByTitle mocks base method.
-func (m *MockOnePasswordClient) GetItemByTitle(title, vaultUUID string) (*onepassword.Item, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetItemByTitle", title, vaultUUID)
- ret0, _ := ret[0].(*onepassword.Item)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetItemByTitle indicates an expected call of GetItemByTitle.
-func (mr *MockOnePasswordClientMockRecorder) GetItemByTitle(title, vaultUUID interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetItemByTitle", reflect.TypeOf((*MockOnePasswordClient)(nil).GetItemByTitle), title, vaultUUID)
-}
-
-// UpdateItem mocks base method.
-func (m *MockOnePasswordClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "UpdateItem", item, vaultUUID)
- ret0, _ := ret[0].(*onepassword.Item)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// UpdateItem indicates an expected call of UpdateItem.
-func (mr *MockOnePasswordClientMockRecorder) UpdateItem(item, vaultUUID interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateItem", reflect.TypeOf((*MockOnePasswordClient)(nil).UpdateItem), item, vaultUUID)
-}
diff --git a/pkg/providers/mock_providers/vercel_mock.go b/pkg/providers/mock_providers/vercel_mock.go
deleted file mode 100644
index 82382fc3..00000000
--- a/pkg/providers/mock_providers/vercel_mock.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/providers/vercel.go
-
-// Package mock_providers is a generated GoMock package.
-package mock_providers
-
-import (
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockVercelClient is a mock of VercelClient interface.
-type MockVercelClient struct {
- ctrl *gomock.Controller
- recorder *MockVercelClientMockRecorder
-}
-
-// MockVercelClientMockRecorder is the mock recorder for MockVercelClient.
-type MockVercelClientMockRecorder struct {
- mock *MockVercelClient
-}
-
-// NewMockVercelClient creates a new mock instance.
-func NewMockVercelClient(ctrl *gomock.Controller) *MockVercelClient {
- mock := &MockVercelClient{ctrl: ctrl}
- mock.recorder = &MockVercelClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockVercelClient) EXPECT() *MockVercelClientMockRecorder {
- return m.recorder
-}
-
-// GetProject mocks base method.
-func (m *MockVercelClient) GetProject(path string) (map[string]*string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetProject", path)
- ret0, _ := ret[0].(map[string]*string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetProject indicates an expected call of GetProject.
-func (mr *MockVercelClientMockRecorder) GetProject(path interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProject", reflect.TypeOf((*MockVercelClient)(nil).GetProject), path)
-}
diff --git a/pkg/providers/onepassword.go b/pkg/providers/onepassword.go
deleted file mode 100644
index c4aa28d8..00000000
--- a/pkg/providers/onepassword.go
+++ /dev/null
@@ -1,152 +0,0 @@
-package providers
-
-import (
- "fmt"
-
- "github.com/1Password/connect-sdk-go/connect"
- "github.com/1Password/connect-sdk-go/onepassword"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type OnePasswordClient interface {
- GetItemByTitle(title, vaultUUID string) (*onepassword.Item, error)
- UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
-}
-
-type OnePassword struct {
- client OnePasswordClient
- logger logging.Logger
-}
-
-const OnePasswordName = "1password"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Description: "1Password",
- Name: OnePasswordName,
- Authentication: `
-To integrate with the 1Password API, you should have system-to-system secret management running in your infrastructure/localhost [more details here](https://support.1password.com/connect-deploy-docker/).
-
-Requires the following environment variables to be set:
-` + "`OP_CONNECT_HOST`" + ` - The hostname of the 1Password Connect API
-` + "`OP_CONNECT_TOKEN`" + ` - The API token to be used to authenticate the client to a 1Password Connect API.
-`,
- ConfigTemplate: `,
- # Configure via environment variables:
- # OP_CONNECT_HOST
- # OP_CONNECT_TOKEN
- 1password:
- env_sync:
- path: # Key title
- source: # 1Password token gen include access to multiple vault. to get the secrets you must add and vaultUUID. the field is mandatory
- env:
- FOO_BAR:
- path: # Key title
- source: # 1Password token gen include access to multiple vault. to get the secrets you must add and vaultUUID. the field is mandatory
- field: # The secret field to get. notesPlain, {label key}, password etc.
-`,
- Ops: core.OpMatrix{
- Put: true,
- GetMapping: true,
- Get: true,
- },
- }
-
- RegisterProvider(metaInfo, NewOnePassword)
-}
-
-func NewOnePassword(logger logging.Logger) (core.Provider, error) {
- client, err := connect.NewClientFromEnvironment()
- if err != nil {
- return nil, err
- }
- return &OnePassword{client: client, logger: logger}, nil
-}
-
-func (o *OnePassword) Put(p core.KeyPath, val string) error {
-
- item, err := o.getItemByTitle(p)
- if err != nil {
- return err
- }
-
- for _, field := range item.Fields {
- if field.Label == p.Field {
- field.Value = val
- o.logger.WithFields(map[string]interface{}{
- "item_id": item.ID,
- "vault_id": p.Source,
- }).Debug("update item")
- _, err := o.client.UpdateItem(item, p.Source)
- return err
- }
- }
-
- return fmt.Errorf("label %v not found", p.Field)
-}
-
-func (o *OnePassword) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write multiple keys", OnePasswordName)
-}
-
-func (o *OnePassword) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
-
- item, err := o.getItemByTitle(p)
- if err != nil {
- return nil, err
- }
-
- entries := []core.EnvEntry{}
- for _, field := range item.Fields {
- entries = append(entries, p.FoundWithKey(field.Label, field.Value))
- }
-
- return entries, nil
-}
-
-func (o *OnePassword) Get(p core.KeyPath) (*core.EnvEntry, error) {
-
- item, err := o.getItemByTitle(p)
- if err != nil {
- return nil, err
- }
-
- var ent = p.Missing()
- for _, field := range item.Fields {
- if field.Label == p.Field || field.Label == p.Env {
- ent = p.Found(field.Value)
- break
- }
- o.logger.WithFields(map[string]interface{}{
- "field": p.Field,
- "env": p.Env,
- "label": field.Label,
- }).Debug("item not found from list")
- }
-
- return &ent, nil
-}
-
-func (o *OnePassword) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", OnePasswordName)
-}
-
-func (o *OnePassword) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", OnePasswordName)
-}
-
-func (o *OnePassword) getItemByTitle(p core.KeyPath) (*onepassword.Item, error) {
-
- o.logger.WithFields(map[string]interface{}{
- "item_id": p.Path,
- "vault_id": p.Source,
- }).Debug("get item by title")
- item, err := o.client.GetItemByTitle(p.Path, p.Source)
- if err != nil {
- return nil, fmt.Errorf("key %s not found in vaultUUID %s, error: %v", p.Path, p.Source, err)
- }
-
- return item, nil
-}
diff --git a/pkg/providers/onepassword_test.go b/pkg/providers/onepassword_test.go
deleted file mode 100644
index f869ffc9..00000000
--- a/pkg/providers/onepassword_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/1Password/connect-sdk-go/onepassword"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestOnePassword(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockOnePasswordClient(ctrl)
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
-
- out := onepassword.Item{
- Fields: []*onepassword.ItemField{
- {Label: "MG_KEY", Value: "shazam"},
- },
- }
- outlist := onepassword.Item{
- Fields: []*onepassword.ItemField{
- {Label: "MG_KEY_1", Value: "mailman"},
- {Label: "MG_KEY", Value: "shazam"},
- },
- }
- client.EXPECT().GetItemByTitle(gomock.Eq(path), gomock.Any()).Return(&out, nil).AnyTimes()
- client.EXPECT().GetItemByTitle(gomock.Eq(pathmap), gomock.Any()).Return(&outlist, nil).AnyTimes()
- s := OnePassword{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestOnePasswordFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mock_providers.NewMockOnePasswordClient(ctrl)
- client.EXPECT().GetItemByTitle(gomock.Any(), gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := OnePassword{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
- _, err = s.GetMapping(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/providers/process_env.go b/pkg/providers/process_env.go
deleted file mode 100644
index 66a57475..00000000
--- a/pkg/providers/process_env.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "sort"
- "strings"
-
- "github.com/spectralops/teller/pkg/core"
-
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type ProcessEnv struct {
- logger logging.Logger
-}
-
-//nolint
-func init() {
- metaInto := core.MetaInfo{
- Description: "ProcessEnv",
- Name: "process_env",
- Authentication: "No Authentication needed",
- ConfigTemplate: `
- # Configure via environment:
- process_env:
- env:
- ETC_DSN:
- # Optional: accesses the environment variable 'SOME_KEY' and maps it to ETC_DSN
- field: SOME_KEY
-`,
- Ops: core.OpMatrix{Get: true, GetMapping: true, Put: false, PutMapping: false},
- }
- RegisterProvider(metaInto, NewProcessEnv)
-}
-
-// NewProcessEnv creates new provider instance
-func NewProcessEnv(logger logging.Logger) (core.Provider, error) {
- return &ProcessEnv{
- logger: logger,
- }, nil
-}
-
-// Name return the provider name
-func (a *ProcessEnv) Name() string {
- return "process_env"
-}
-
-// GetMapping returns a multiple entries
-func (a *ProcessEnv) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- a.logger.Debug("read secret")
-
- kvs := make(map[string]string)
- for _, envs := range os.Environ() {
- pair := strings.SplitN(envs, "=", 2) //nolint: gomnd
- kvs[pair[0]] = pair[1]
- }
- var entries []core.EnvEntry
- for k, v := range kvs {
- entries = append(entries, p.FoundWithKey(k, v))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-// Get returns a single entry
-func (a *ProcessEnv) Get(p core.KeyPath) (*core.EnvEntry, error) {
- a.logger.Debug("read secret")
-
- k := p.EffectiveKey()
- val, ok := os.LookupEnv(k)
- if !ok {
- a.logger.WithFields(map[string]interface{}{"key": k}).Debug("key not found")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(val)
- return &ent, nil
-}
-
-// Delete will delete entry
-func (a *ProcessEnv) Delete(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement delete yet", a.Name())
-}
-
-// DeleteMapping will delete the given path recessively
-func (a *ProcessEnv) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("provider %s does not implement deleteMapping yet", a.Name())
-}
-
-// Put will create a new single entry
-func (a *ProcessEnv) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %s does not implement put yet", a.Name())
-}
-
-// PutMapping will create a multiple entries
-func (a *ProcessEnv) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %s does not implement putMapping yet", a.Name())
-}
diff --git a/pkg/providers/process_env_test.go b/pkg/providers/process_env_test.go
deleted file mode 100644
index cf2a3bad..00000000
--- a/pkg/providers/process_env_test.go
+++ /dev/null
@@ -1 +0,0 @@
-package providers
diff --git a/pkg/providers/register.go b/pkg/providers/register.go
deleted file mode 100644
index e38b1101..00000000
--- a/pkg/providers/register.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package providers
-
-import (
- "fmt"
- "strings"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-var providersMap = map[string]core.RegisteredProvider{}
-
-func RegisterProvider(metaInfo core.MetaInfo, builder func(logger logging.Logger) (core.Provider, error)) {
- loweredProviderName := strings.ToLower(metaInfo.Name)
- if _, ok := providersMap[loweredProviderName]; ok {
- panic(fmt.Sprintf("provider '%s' already exists", loweredProviderName))
- }
- providersMap[loweredProviderName] = core.RegisteredProvider{Meta: metaInfo, Builder: builder}
-}
-
-func ResolveProvider(providerName string) (core.Provider, error) {
- loweredProviderName := strings.ToLower(providerName)
- if registeredProvider, ok := providersMap[loweredProviderName]; ok {
- logger := logging.GetRoot().WithField("provider_name", loweredProviderName)
- return registeredProvider.Builder(logger)
- }
- return nil, fmt.Errorf("provider '%s' does not exist", providerName)
-
-}
-
-func ResolveProviderMeta(providerName string) (core.MetaInfo, error) {
- loweredProviderName := strings.ToLower(providerName)
- if registeredProvider, ok := providersMap[loweredProviderName]; ok {
- return registeredProvider.Meta, nil
- }
- return core.MetaInfo{}, fmt.Errorf("provider '%s' does not exist", providerName)
-}
-
-func GetAllProvidersMeta() []core.MetaInfo {
- metaInfoList := []core.MetaInfo{}
- for _, value := range providersMap {
- metaInfoList = append(metaInfoList, value.Meta)
- }
- return metaInfoList
-}
diff --git a/pkg/providers/vercel.go b/pkg/providers/vercel.go
deleted file mode 100644
index 80b80291..00000000
--- a/pkg/providers/vercel.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "sort"
-
- "github.com/dghubble/sling"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
-)
-
-type VercelClient interface {
- GetProject(path string) (map[string]*string, error)
-}
-type VercelAPI struct {
- http *sling.Sling
-}
-
-func NewVercelAPI(token string) *VercelAPI {
- bearer := "Bearer " + token
- httpClient := sling.New().Base(VercelAPIBase).Set("Authorization", bearer)
- return &VercelAPI{http: httpClient}
-}
-
-func (v *VercelAPI) GetProject(path string) (map[string]*string, error) {
- projectsPath := "/v1" + ProjectEndPoint + "/" + path
- project := new(VercelProject)
- _, err := v.http.Get(projectsPath).ReceiveSuccess(project)
- return project.envMap(), err
-}
-
-type Vercel struct {
- client VercelClient
- logger logging.Logger
-}
-
-type VercelProject struct {
- Env []struct {
- Key string `json:"key"`
- Value string `json:"value"`
- Type string `json:"type"`
- } `json:"env"`
-}
-
-func (vp *VercelProject) envMap() map[string]*string {
- val := make(map[string]*string)
- for i := 0; i < len(vp.Env); i++ {
- // pick only plain type variables (ignore secrets)
- cur := vp.Env[i]
- if cur.Type == "plain" {
- val[cur.Key] = &cur.Value
- }
- }
- return val
-}
-
-/*
-https://vercel.com/docs/api#endpoints/secrets
-*/
-
-const VercelAPIBase = "https://api.vercel.com/"
-
-const ProjectEndPoint = "/projects"
-
-const VercelName = "vercel"
-
-//nolint
-func init() {
- metaInfo := core.MetaInfo{
- Name: "vercel",
- Description: "Vercel",
- Authentication: "Requires an API key populated in your environment in: `VERCEL_TOKEN`.",
- ConfigTemplate: `
- # requires an API token in: VERCEL_TOKEN
- vercel:
- # sync a complete environment
- env_sync:
- path: drakula-demo
-
- # # pick and choose variables
- # env:
- # JVM_OPTS:
- # path: drakula-demo
-`,
- Ops: core.OpMatrix{
- Get: true,
- GetMapping: true,
- },
- }
-
- RegisterProvider(metaInfo, NewVercel)
-}
-
-func NewVercel(logger logging.Logger) (core.Provider, error) {
- vercelToken := os.Getenv("VERCEL_TOKEN")
- if vercelToken == "" {
- return nil, fmt.Errorf("please set VERCEL_TOKEN")
- }
- return &Vercel{client: NewVercelAPI(vercelToken), logger: logger}, nil
-}
-
-func (ve *Vercel) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- secret, err := ve.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- k := secret
- entries := []core.EnvEntry{}
- for k, v := range k {
- val := ""
- if v != nil {
- val = *v
- }
- entries = append(entries, p.FoundWithKey(k, val))
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-func (ve *Vercel) Get(p core.KeyPath) (*core.EnvEntry, error) { //nolint:dupl
- secret, err := ve.getSecret(p)
- if err != nil {
- return nil, err
- }
-
- data := secret
- k := data[p.Env]
- if p.Field != "" {
- ve.logger.WithField("path", p.Path).Debug("`env` attribute not found in returned data. take `field` attribute")
- k = data[p.Field]
- }
-
- if k == nil {
- ve.logger.WithField("path", p.Path).Debug("requested entry not found")
- ent := p.Missing()
- return &ent, nil
- }
-
- ent := p.Found(*k)
- return &ent, nil
-}
-
-func (ve *Vercel) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", VercelName)
-}
-func (ve *Vercel) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", VercelName)
-}
-
-func (ve *Vercel) Delete(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", VercelName)
-}
-
-func (ve *Vercel) DeleteMapping(kp core.KeyPath) error {
- return fmt.Errorf("%s does not implement delete yet", VercelName)
-}
-
-func (ve *Vercel) getSecret(kp core.KeyPath) (map[string]*string, error) {
- /* https://vercel.com/docs/api#endpoints/projects/get-a-single-project */
- ve.logger.WithField("path", kp.Path).Debug("get secret")
- project, err := ve.client.GetProject(kp.Path)
- if err != nil {
- return nil, err
- }
-
- return project, nil
-}
diff --git a/pkg/providers/vercel_test.go b/pkg/providers/vercel_test.go
deleted file mode 100644
index ad9d8399..00000000
--- a/pkg/providers/vercel_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package providers
-
-import (
- "errors"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/golang/mock/gomock"
-
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/providers/mock_providers"
-)
-
-func TestVercel(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockVercelClient(ctrl)
- // in heroku this isn't the path name, but an app name,
- // but for testing it doesn't matter
- path := "settings/prod/billing-svc"
- pathmap := "settings/prod/billing-svc/all"
- shazam := "shazam"
- mailman := "mailman"
- out := map[string]*string{
- "MG_KEY": &shazam,
- "SMTP_PASS": &mailman,
- }
- client.EXPECT().GetProject(gomock.Eq(path)).Return(out, nil).AnyTimes()
- client.EXPECT().GetProject(gomock.Eq(pathmap)).Return(out, nil).AnyTimes()
- s := Vercel{
- client: client,
- logger: GetTestLogger(),
- }
- AssertProvider(t, &s, true)
-}
-
-func TestVercelFailures(t *testing.T) {
- ctrl := gomock.NewController(t)
- // Assert that Bar() is invoked.
- defer ctrl.Finish()
- client := mock_providers.NewMockVercelClient(ctrl)
- client.EXPECT().GetProject(gomock.Any()).Return(nil, errors.New("error")).AnyTimes()
- s := Vercel{
- client: client,
- logger: GetTestLogger(),
- }
- _, err := s.Get(core.KeyPath{Env: "MG_KEY", Path: "settings/{{stage}}/billing-svc"})
- assert.NotNil(t, err)
-}
diff --git a/pkg/redactor.go b/pkg/redactor.go
deleted file mode 100644
index 17835bf5..00000000
--- a/pkg/redactor.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package pkg
-
-import (
- "bufio"
- "fmt"
- "io"
- "sort"
- "strings"
-
- "github.com/spectralops/teller/pkg/core"
-)
-
-const (
- bufferSize = 64 * 1024
- maxBufferSize = 10 * 1024 * 1024
-)
-
-type Redactor struct {
- io.WriteCloser
- err <-chan error
-}
-
-func NewRedactor(dist io.Writer, entries []core.EnvEntry) *Redactor {
- entries = append([]core.EnvEntry(nil), entries...)
- sort.Sort(core.EntriesByValueSize(entries))
-
- r, w := io.Pipe()
- ch := make(chan error)
- go func() {
- defer close(ch)
-
- s := bufio.NewScanner(r)
- buf := make([]byte, 0, bufferSize)
- s.Buffer(buf, maxBufferSize) // 10MB lines correlating to 10MB files max (bundles?)
-
- for s.Scan() {
- line := s.Text()
- for i := range entries {
- line = strings.ReplaceAll(line, entries[i].Value, entries[i].RedactWith)
- }
- if _, err := fmt.Fprintln(dist, line); err != nil {
- ch <- r.CloseWithError(err)
- return
- }
- }
- ch <- s.Err()
- }()
-
- return &Redactor{
- WriteCloser: w,
- err: ch,
- }
-}
-
-func (r *Redactor) Close() error {
- err := r.WriteCloser.Close()
- <-r.err
-
- return err
-}
diff --git a/pkg/redactor_test.go b/pkg/redactor_test.go
deleted file mode 100644
index 1aec073a..00000000
--- a/pkg/redactor_test.go
+++ /dev/null
@@ -1,188 +0,0 @@
-package pkg
-
-import (
- "bytes"
- "io"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/spectralops/teller/pkg/core"
-)
-
-func TestRedactorOverlap(t *testing.T) {
- cases := []struct {
- name string
- entries []core.EnvEntry
- s string
- sr string
- }{
- {
- name: "overlap",
- // in this case we don't want '123' to appear in the clear after all redactions are made.
- // it can happen if the smaller secret get replaced first because both
- // secrets overlap. we need to ensure the wider secrets always get
- // replaced first.
- entries: []core.EnvEntry{
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "OTHER_KEY",
- Value: "hello",
- RedactWith: "**OTHER_KEY**",
- },
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "SOME_KEY",
- Value: "hello123",
- RedactWith: "**SOME_KEY**",
- },
- },
- s: `
-func Foobar(){
- secret := "hello"
- callService(secret, "hello123")
- // hello, hello123
-}
-`,
- sr: `
-func Foobar(){
- secret := "**OTHER_KEY**"
- callService(secret, "**SOME_KEY**")
- // **OTHER_KEY**, **SOME_KEY**
-}
-`,
- },
- {
- name: "multiple",
- entries: []core.EnvEntry{
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "SOME_KEY",
- Value: "shazam",
- RedactWith: "**SOME_KEY**",
- },
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "OTHER_KEY",
- Value: "loot",
- RedactWith: "**OTHER_KEY**",
- },
- },
- s: `
-func Foobar(){
- secret := "loot"
- callService(secret, "shazam")
-}
-`,
- sr: `
-func Foobar(){
- secret := "**OTHER_KEY**"
- callService(secret, "**SOME_KEY**")
-}
-`,
- },
- }
-
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- buf := bytes.NewBuffer(nil)
- redactor := NewRedactor(buf, c.entries)
-
- _, err := io.WriteString(redactor, c.s)
- assert.NoError(t, err)
-
- err = redactor.Close()
- assert.NoError(t, err)
-
- assert.Equal(t, buf.String(), c.sr)
- })
- }
-}
-func TestRedactorMultiple(t *testing.T) {
- entries := []core.EnvEntry{
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "SOME_KEY",
- Value: "shazam",
- RedactWith: "**SOME_KEY**",
- },
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "OTHER_KEY",
- Value: "loot",
- RedactWith: "**OTHER_KEY**",
- },
- }
- s := `
-func Foobar(){
- secret := "loot"
- callService(secret, "shazam")
-}
-`
- sr := `
-func Foobar(){
- secret := "**OTHER_KEY**"
- callService(secret, "**SOME_KEY**")
-}
-`
-
- buf := bytes.NewBuffer(nil)
- redactor := NewRedactor(buf, entries)
-
- _, err := io.WriteString(redactor, s)
- assert.NoError(t, err)
-
- err = redactor.Close()
- assert.NoError(t, err)
-
- assert.Equal(t, buf.String(), sr)
-}
-
-func TestRedactor(t *testing.T) {
- entries := []core.EnvEntry{
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "SOME_KEY",
- Value: "shazam",
- RedactWith: "**NOPE**",
- },
- }
- s := `
-func Foobar(){
- secret := "shazam"
- callService(secret, "shazam")
-}
-`
- sr := `
-func Foobar(){
- secret := "**NOPE**"
- callService(secret, "**NOPE**")
-}
-`
-
- buf := bytes.NewBuffer(nil)
- redactor := NewRedactor(buf, entries)
-
- _, err := io.WriteString(redactor, s)
- assert.NoError(t, err)
-
- err = redactor.Close()
- assert.NoError(t, err)
-
- assert.Equal(t, buf.String(), sr)
-}
-
-func TestRedactor_Close(t *testing.T) {
- buf := bytes.NewBuffer(nil)
- redactor := NewRedactor(buf, nil)
-
- assert.NoError(t, redactor.Close())
- // can be close more than once.
- assert.NoError(t, redactor.Close())
-}
diff --git a/pkg/teller.go b/pkg/teller.go
deleted file mode 100644
index 1acaff32..00000000
--- a/pkg/teller.go
+++ /dev/null
@@ -1,737 +0,0 @@
-package pkg
-
-import (
- "bufio"
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strings"
- "text/template"
- "time"
-
- "github.com/karrick/godirwalk"
- "github.com/samber/lo"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
- "gopkg.in/yaml.v3"
-)
-
-// Teller
-// Cmd - command to execute if any given.
-// Porcelain - wrapping teller in a nice porcelain; in other words the textual UI for teller.
-// Providers - the available providers to use.
-// Entries - when loaded, these contains the mapped entries. Load them with Collect()
-// Templating - Teller's templating options.
-type Teller struct {
- Redact bool
- Cmd []string
- Config *TellerFile
- Porcelain *Porcelain
- Populate *core.Populate
- Providers Providers
- Entries []core.EnvEntry
- Templating *Templating
- Logger logging.Logger
-}
-
-// Create a new Teller instance, using a tellerfile, and a command to execute (if any)
-func NewTeller(tlrfile *TellerFile, cmd []string, redact bool, logger logging.Logger) *Teller {
- opts := core.Opts{"project": tlrfile.Project}
- for k, v := range tlrfile.Opts {
- opts[k] = v
- }
- return &Teller{
- Redact: redact,
- Config: tlrfile,
- Cmd: cmd,
- Providers: &BuiltinProviders{},
- Populate: core.NewPopulate(opts),
- Porcelain: &Porcelain{Out: os.Stderr},
- Templating: &Templating{},
- Logger: logger,
- }
-}
-
-// execute a command, and take care to sanitize the child process environment (conditionally)
-func (tl *Teller) execCmd(cmd string, cmdArgs []string, withRedaction bool) error {
- command := exec.Command(cmd, cmdArgs...)
- if !tl.Config.CarryEnv {
- command.Env = lo.Map(tl.Entries, func(ent core.EnvEntry, _ int) string {
- return fmt.Sprintf("%s=%s", ent.Key, ent.Value)
- })
-
- command.Env = append(command.Env, lo.Map([]string{"USER", "HOME", "PATH"}, func(k string, _ int) string { return fmt.Sprintf("%s=%s", k, os.Getenv(k)) })...)
-
- } else {
- for i := range tl.Entries {
- b := tl.Entries[i]
- os.Setenv(b.Key, b.Value)
- }
- }
-
- command.Stdin = os.Stdin
- command.Stdout = os.Stdout
- command.Stderr = os.Stderr
- if withRedaction {
- o := NewRedactor(command.Stdout, tl.Entries)
- defer o.Close()
- command.Stdout = o
-
- e := NewRedactor(command.Stderr, tl.Entries)
- defer e.Close()
- command.Stderr = e
- }
-
- return command.Run()
-}
-
-func (tl *Teller) PrintEnvKeys() {
- tl.sortByProviderName()
- tl.Porcelain.PrintContext(tl.Config.Project, tl.Config.LoadedFrom)
- tl.Porcelain.VSpace(1)
- tl.Porcelain.PrintEntries(tl.Entries)
-}
-
-// Export variables into a shell sourceable format
-func (tl *Teller) ExportEnv() string {
- var b bytes.Buffer
-
- fmt.Fprintf(&b, "#!/bin/sh\n")
- for i := range tl.Entries {
- v := tl.Entries[i]
- value := strings.ReplaceAll(v.Value, "'", "'\"'\"'")
- fmt.Fprintf(&b, "export %s='%s'\n", v.Key, value)
- }
- return b.String()
-}
-
-// Export variables into a .env format (basically a KEY=VAL format, that's also compatible with Docker)
-func (tl *Teller) ExportDotenv() string {
- var b bytes.Buffer
-
- for i := range tl.Entries {
- v := tl.Entries[i]
- fmt.Fprintf(&b, "%s=%s\n", v.Key, v.Value)
- }
- return b.String()
-}
-
-func (tl *Teller) ExportYAML() (out string, err error) {
- valmap := map[string]string{}
-
- for i := range tl.Entries {
- v := tl.Entries[i]
- valmap[v.Key] = v.Value
- }
- content, err := yaml.Marshal(valmap)
- if err != nil {
- return "", err
- }
- return string(content), nil
-}
-
-func (tl *Teller) ExportJSON() (out string, err error) {
- valmap := map[string]string{}
-
- for i := range tl.Entries {
- v := tl.Entries[i]
- valmap[v.Key] = v.Value
- }
- content, err := json.MarshalIndent(valmap, "", " ")
- if err != nil {
- return "", err
- }
- return string(content), nil
-}
-
-func renderWizardTemplate(fname string, answers *core.WizardAnswers) error {
- t, err := template.New("t").Parse(TellerFileTemplate)
- if err != nil {
- return err
- }
- f, err := os.Create(fname)
- if err != nil {
- return err
- }
- err = t.Execute(f, answers)
- if err != nil {
- return err
- }
- return nil
-}
-
-// Start an interactive wizard, that will create a file when completed.
-func (tl *Teller) SetupNewProject(fname string) error {
- answers, err := tl.Porcelain.StartWizard()
- if err != nil {
- return err
- }
- err = renderWizardTemplate(fname, answers)
- if err != nil {
- return err
- }
-
- tl.Porcelain.DidCreateNewFile(fname)
- return nil
-}
-
-// Execute a command with teller. This requires all entries to be loaded beforehand with Collect()
-func (tl *Teller) RedactLines(r io.Reader, w io.Writer) error {
- o := NewRedactor(w, tl.Entries)
- defer o.Close()
-
- _, err := io.Copy(o, r)
- return err
-}
-
-// Execute a command with teller. This requires all entries to be loaded beforehand with Collect()
-func (tl *Teller) Exec() {
- tl.Porcelain.PrintContext(tl.Config.Project, tl.Config.LoadedFrom)
- if tl.Config.Confirm != "" {
- tl.Porcelain.VSpace(1)
- tl.Porcelain.PrintEntries(tl.Entries)
- tl.Porcelain.VSpace(1)
- if !tl.Porcelain.AskForConfirmation(tl.Populate.FindAndReplace(tl.Config.Confirm)) {
- return
- }
- }
-
- err := tl.execCmd(tl.Cmd[0], tl.Cmd[1:], tl.Redact)
- if err != nil {
- tl.Logger.WithError(err).Fatal("could not execute command")
- }
-}
-
-func hasBindata(line []byte) bool {
- for _, el := range line {
- if el == 0 {
- return true
- }
- }
- return false
-}
-func checkForMatches(path string, entries []core.EnvEntry) ([]core.Match, error) {
- file, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer file.Close()
-
- retval := []core.Match{}
-
- scanner := bufio.NewScanner(file)
- //nolint
- buf := make([]byte, 0, 64*1024)
- //nolint
- scanner.Buffer(buf, 10*1024*1024) // 10MB lines correlating to 10MB files max (bundles?)
-
- var lineNumber = 0
- for scanner.Scan() {
- lineNumber++
- line := scanner.Bytes()
- if hasBindata(line) {
- // This is a binary file. Skip it!
- return retval, nil
- }
-
- linestr := string(line)
- for i := range entries {
- ent := entries[i]
- if !ent.IsFound || ent.Value == "" || ent.Severity == core.None {
- continue
- }
- if matchIndex := strings.Index(linestr, ent.Value); matchIndex != -1 {
- m := core.Match{
- Path: path, Line: linestr, LineNumber: lineNumber, MatchIndex: matchIndex, Entry: ent}
- retval = append(retval, m)
- }
- }
- }
-
- if err := scanner.Err(); err != nil {
- return nil, err
- }
- return retval, nil
-}
-
-// Scan for entries. Each of the mapped entries is considered highly sensitive unless stated other wise (with sensitive: high|medium|low|none)
-// as such, we can offer a security scan to locate those in the current codebase (if the entries are sensitive and are placed inside a vault or
-// similar store, what's the purpose of hardcoding these? let's help ourselves and locate throughout all the files in the path given)
-func (tl *Teller) Scan(path string, silent bool) ([]core.Match, error) {
- if path == "" {
- path = "."
- }
-
- start := time.Now()
- findings := []core.Match{}
- err := godirwalk.Walk(path, &godirwalk.Options{
- Callback: func(osPathname string, de *godirwalk.Dirent) error {
- // Following string operation is not most performant way
- // of doing this, but common enough to warrant a simple
- // example here:
- if strings.Contains(osPathname, ".git") {
- return godirwalk.SkipThis
- }
- if de.IsRegular() {
- ms, err := checkForMatches(osPathname, tl.Entries)
- if err == nil {
- findings = append(findings, ms...)
- }
- // else {
- // can't open, can't scan
- // fmt.Println("error: %v", err)
- // }
- }
- return nil
- },
- Unsorted: true, // (optional) set true for faster yet non-deterministic enumeration (see godoc)
- })
-
- elapsed := time.Since(start)
- if len(findings) > 0 && !silent {
- tl.Porcelain.PrintMatches(findings)
- tl.Porcelain.VSpace(1)
- }
-
- if !silent {
- tl.Porcelain.PrintMatchSummary(findings, tl.Entries, elapsed)
- }
- return findings, err
-}
-
-// Template Teller vars from a given path (can be file or folder)
-func (tl *Teller) Template(from, to string) error {
-
- fileInfo, err := os.Stat(from)
- if err != nil {
- return fmt.Errorf("invald path. err: %v", err)
- }
-
- if fileInfo.IsDir() {
- return tl.templateFolder(from, to)
- }
-
- return tl.templateFile(from, to)
-}
-
-// templateFolder scan given folder and inject Teller vars for each search file
-func (tl *Teller) templateFolder(from, to string) error {
-
- err := godirwalk.Walk(from, &godirwalk.Options{
- Callback: func(osPathname string, de *godirwalk.Dirent) error {
- if de.IsDir() {
- return nil
- }
- copyTo := filepath.Join(to, strings.Replace(osPathname, from, "", 1))
- return tl.templateFile(osPathname, copyTo)
- },
- Unsorted: true,
- })
-
- return err
-}
-
-// templateFile inject Teller vars into a single file
-func (tl *Teller) templateFile(from, to string) error {
- tfile, err := os.ReadFile(from)
- if err != nil {
- return fmt.Errorf("cannot read template '%v': %v", from, err)
- }
-
- res, err := tl.Templating.ForTemplate(string(tfile), tl.Entries)
- if err != nil {
- return fmt.Errorf("cannot render template '%v': %v", from, err)
- }
-
- info, _ := os.Stat(from)
-
- // crate destination path if not exists
- toFolder := filepath.Dir(to)
- if _, err = os.Stat(toFolder); os.IsNotExist(err) {
- err = os.MkdirAll(toFolder, os.ModePerm)
- if err != nil {
- return fmt.Errorf("cannot create folder '%v': %v", to, err)
- }
- }
-
- err = os.WriteFile(to, []byte(res), info.Mode())
- if err != nil {
- return fmt.Errorf("cannot save to '%v': %v", to, err)
- }
- return nil
-}
-
-func updateParams(ent *core.EnvEntry, from *core.KeyPath, pname string) {
- ent.ProviderName = pname
- ent.Source = from.Source
- ent.Sink = from.Sink
-
- if from.Severity == "" {
- ent.Severity = core.High
- } else {
- ent.Severity = from.Severity
- }
-
- if from.RedactWith == "" {
- ent.RedactWith = "**REDACTED**"
- } else {
- ent.RedactWith = from.RedactWith
- }
-}
-
-func (tl *Teller) CollectFromProvider(pname string) ([]core.EnvEntry, error) {
-
- entries := []core.EnvEntry{}
- conf, ok := tl.Config.Providers[pname]
- p, err := tl.Providers.GetProvider(pname)
- m, _ := providers.ResolveProviderMeta(pname)
- if err != nil && ok && conf.Kind != "" {
- // ok, maybe same provider, with 'kind'?
- p, err = tl.Providers.GetProvider(conf.Kind)
- }
-
- // still no provider? bail.
- if err != nil {
- tl.Logger.Debug("provider not found in providers list with the name: %s or config kind: %s", pname, conf.Kind)
- return nil, err
- }
- logger := tl.Logger.WithField("provider_name", m.Name)
- if conf.EnvMapping != nil {
- es, err := p.GetMapping(tl.Populate.KeyPath(*conf.EnvMapping))
- if err != nil {
- return nil, err
- }
-
- logger.Debug("found %d entries from env mapping", len(es))
- //nolint
- for k, v := range es {
- updateParams(&es[k], conf.EnvMapping, pname)
- // optionally remap environment variables synced from the provider
- remap := conf.EnvMapping.EffectiveRemap()
- if val, ok := remap[v.Key]; ok {
- if val.Field != "" {
- logger.Debug("rename entry from %s to %s", v.Key, val.Field)
- es[k].Key = val.Field
- }
- if val.Severity != "" {
- es[k].Severity = val.Severity
- }
- if val.RedactWith != "" {
- es[k].RedactWith = val.RedactWith
- }
- }
- }
-
- entries = append(entries, es...)
- } else {
- logger.Debug("config EnvMapping not configure")
- }
-
- logger.Debug("total fetch entries from mapping %d", len(entries))
- if conf.Env != nil {
- //nolint
- for k, v := range *conf.Env {
- logger.Debug("get value from path: %s", k)
- ent, err := p.Get(tl.Populate.KeyPath(v.WithEnv(k)))
- if err != nil {
- if v.Optional {
- logger.Debug("optional field is set to path: %s", k)
- continue
- } else {
- return nil, err
- }
- } else {
- //nolint
- updateParams(ent, &v, pname)
- entries = append(entries, *ent)
- }
- }
- } else {
- logger.Debug("config env not configure")
- }
- logger.Debug("total fetch entries %d", len(entries))
- return entries, nil
-}
-
-func (tl *Teller) CollectFromProviderMap(ps *ProvidersMap) ([]core.EnvEntry, error) {
- entries := []core.EnvEntry{}
- for pname := range *ps {
- pents, err := tl.CollectFromProvider(pname)
- if err != nil {
- return nil, err
- }
- entries = append(entries, pents...)
- }
-
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-
-// The main "load all variables from all providers" logic. Walks over all definitions in the tellerfile
-// and then: fetches, converts, creates a new EnvEntry. We're also mapping the sensitivity aspects of it.
-// Note that for a similarly named entry - last one wins.
-func (tl *Teller) Collect() error {
- t := tl.Config
- entries, err := tl.CollectFromProviderMap(&t.Providers)
- if err != nil {
- return err
- }
-
- tl.Entries = entries
- return nil
-}
-
-func (tl *Teller) sortByProviderName() {
- sort.Sort(core.EntriesByProvider(tl.Entries))
-}
-
-func (tl *Teller) Drift(providerNames []string) []core.DriftedEntry {
- sources := map[string]core.EnvEntry{}
- targets := map[string][]core.EnvEntry{}
- filtering := len(providerNames) > 0
- for i := range tl.Entries {
- ent := tl.Entries[i]
- if filtering && !lo.Contains(providerNames, ent.ProviderName) {
- continue
- }
- if ent.Source != "" {
- sources[ent.Source+":"+ent.Key] = ent
- } else if ent.Sink != "" {
- k := ent.Sink + ":" + ent.Key
- ents := targets[k]
- if ents == nil {
- targets[k] = []core.EnvEntry{ent}
- } else {
- targets[k] = append(ents, ent)
- }
- }
- }
-
- drifts := []core.DriftedEntry{}
-
- //nolint
- for sk, source := range sources {
- ents := targets[sk]
- if ents == nil {
- drifts = append(drifts, core.DriftedEntry{Diff: "missing", Source: source})
- }
-
- for _, e := range ents {
- if e.Value != source.Value {
- drifts = append(drifts, core.DriftedEntry{Diff: "changed", Source: source, Target: e})
- }
- }
- }
-
- sort.Sort(core.DriftedEntriesBySource(drifts))
- return drifts
-}
-
-func (tl *Teller) GetProviderByName(pname string) (*MappingConfig, core.Provider, error) {
- pcfg, ok := tl.Config.Providers[pname]
- if !ok {
- return nil, nil, fmt.Errorf("provider %v not found", pname)
- }
- p := pname
- if pcfg.Kind != "" {
- p = pcfg.Kind
- }
- provider, err := tl.Providers.GetProvider(p)
- return &pcfg, provider, err
-}
-
-func (tl *Teller) Put(kvmap map[string]string, providerNames []string, sync bool, directPath string) error {
- for _, pname := range providerNames {
- pcfg, provider, err := tl.GetProviderByName(pname)
- if err != nil {
- return fmt.Errorf("cannot create provider %v: %v", pname, err)
- }
- logger := tl.Logger.WithFields(map[string]interface{}{
- "provider_name": pname,
- "flag_sync": sync,
- "direct_path": directPath,
- })
- logger.Debug("put secret")
-
- useDirectPath := directPath != ""
-
- // XXXWIP design - decide porcelain or not, errors?
- if sync {
- var kvp core.KeyPath
- if useDirectPath {
- kvp = core.KeyPath{Path: directPath}
- } else {
- if pcfg.EnvMapping == nil {
- return fmt.Errorf("there is no env sync mapping for provider '%v'", pname)
- }
- kvp = *pcfg.EnvMapping
- }
- kvpResolved := tl.Populate.KeyPath(kvp)
- logger.Trace("calling PutMapping provider function")
- err := provider.PutMapping(kvpResolved, kvmap)
- if err != nil {
- return fmt.Errorf("cannot put (sync) %v in provider %v: %v", kvpResolved.Path, pname, err)
- }
- tl.Porcelain.DidPutKVP(kvpResolved, pname, true)
- } else {
- if pcfg.Env == nil {
- return fmt.Errorf("there is no specific key mapping to map to for provider '%v'", pname)
- }
-
- keys := make([]string, 0, len(kvmap))
- for k := range kvmap {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- for _, k := range keys {
- // get the kvp for specific mapping
- ok := false
- var kvp core.KeyPath
-
- if useDirectPath {
- kvp = core.KeyPath{Path: directPath}
- ok = true
- } else {
- kvp, ok = (*pcfg.Env)[k]
- }
-
- if ok {
- kvpResolved := tl.Populate.KeyPath(kvp.WithEnv(k))
- logger.Trace("calling Put provider function")
- err := provider.Put(kvpResolved, kvmap[k])
- if err != nil {
- return fmt.Errorf("cannot put %v in provider %v: %v", k, pname, err)
- }
- tl.Porcelain.DidPutKVP(kvpResolved, pname, false)
- } else {
- tl.Porcelain.NoPutKVP(k, pname)
- }
- }
- }
- }
-
- return nil
-}
-
-func (tl *Teller) Sync(from string, to []string, sync bool) error {
- entries, err := tl.CollectFromProvider(from)
- if err != nil {
- return err
- }
- kvmap := map[string]string{}
- for i := range entries {
- ent := entries[i]
- kvmap[ent.Key] = ent.Value
- }
-
- err = tl.Put(kvmap, to, sync, "")
- return err
-}
-
-func (tl *Teller) MirrorDrift(source, target string) ([]core.DriftedEntry, error) {
- drifts := []core.DriftedEntry{}
- sourceEntries, err := tl.CollectFromProvider(source)
- if err != nil {
- return nil, err
- }
-
- targetEntries, err := tl.CollectFromProvider(target)
- if err != nil {
- return nil, err
- }
-
- for i := range sourceEntries {
- sent := sourceEntries[i]
- tentry, ok := lo.Find(targetEntries, func(ent core.EnvEntry) bool {
- return sent.Key == ent.Key
- })
-
- if !ok {
- drifts = append(drifts, core.DriftedEntry{Diff: "missing", Source: sent})
- continue
- }
-
- if sent.Value != tentry.Value {
- drifts = append(drifts, core.DriftedEntry{Diff: "changed", Source: sent, Target: tentry})
- }
- }
-
- return drifts, nil
-}
-
-func (tl *Teller) Delete(keys, providerNames []string, directPath string, allKeys bool) error {
- if len(providerNames) == 0 {
- return errors.New("at least one provider has to be specified")
- }
-
- logger := tl.Logger.WithFields(map[string]interface{}{
- "providers": providerNames,
- "allKeys": allKeys,
- "direct_path": directPath,
- })
- logger.Debug("delete keys")
-
- if len(keys) == 0 && (!allKeys || directPath == "") {
- return errors.New("at least one key is expected")
- }
-
- useDirectPath := directPath != ""
- for _, pname := range providerNames {
- pcfg, provider, err := tl.GetProviderByName(pname)
- if err != nil {
- return fmt.Errorf("cannot get provider %v: %v", pname, err)
- }
-
- if pcfg.Env == nil {
- return fmt.Errorf("there is no specific key mapping to map to for provider '%v'", pname)
- }
-
- if allKeys && useDirectPath {
- logger.WithField("path", directPath).Debug("calling DeleteMapping provider function")
- err := provider.DeleteMapping(core.KeyPath{Path: directPath})
- if err != nil {
- return fmt.Errorf("cannot delete path %q in provider %q: %v", directPath, pname, err)
- }
-
- tl.Porcelain.DidDeleteP(directPath, pname)
- return nil
- }
-
- for _, key := range keys {
- // get the kp for specific mapping
- var (
- kp core.KeyPath
- ok bool
- )
-
- if useDirectPath {
- kp = core.KeyPath{Path: directPath}
- ok = true
- } else {
- kp, ok = (*pcfg.Env)[key]
- }
-
- if !ok {
- tl.Porcelain.NoDeleteKP(key, pname)
- continue
- }
-
- kpResolved := tl.Populate.KeyPath(kp.WithEnv(key))
- err := provider.Delete(kpResolved)
- if err != nil {
- return fmt.Errorf("cannot delete %v in provider %q: %v", key, pname, err)
- }
-
- tl.Porcelain.DidDeleteKP(kpResolved, pname)
- }
- }
-
- return nil
-}
diff --git a/pkg/teller_test.go b/pkg/teller_test.go
deleted file mode 100644
index 33073e4f..00000000
--- a/pkg/teller_test.go
+++ /dev/null
@@ -1,794 +0,0 @@
-package pkg
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "testing"
-
- "github.com/alecthomas/assert"
- "github.com/spectralops/teller/pkg/core"
- "github.com/spectralops/teller/pkg/logging"
- "github.com/spectralops/teller/pkg/providers"
-)
-
-// implements both Providers and Provider interface, for testing return only itself.
-type InMemProvider struct {
- inmem map[string]string
- alwaysError bool
-}
-
-func (im *InMemProvider) Put(p core.KeyPath, val string) error {
- return fmt.Errorf("provider %q does not implement write yet", im.Name())
-}
-func (im *InMemProvider) PutMapping(p core.KeyPath, m map[string]string) error {
- return fmt.Errorf("provider %q does not implement write yet", im.Name())
-}
-
-func (im *InMemProvider) Delete(kp core.KeyPath) error {
- if im.alwaysError {
- return errors.New("error")
- }
-
- k := kp.EffectiveKey()
-
- delete(im.inmem, fmt.Sprintf("%s/%s", kp.Path, k))
- return nil
-}
-
-func (im *InMemProvider) DeleteMapping(kp core.KeyPath) error {
- if im.alwaysError {
- return errors.New("error")
- }
-
- for key := range im.inmem {
- if !strings.HasPrefix(key, kp.Path) {
- continue
- }
-
- delete(im.inmem, key)
- }
-
- return nil
-}
-
-func (im *InMemProvider) GetProvider(name string) (core.Provider, error) {
- return im, nil //hardcode to return self
-}
-func (im *InMemProvider) ProviderHumanToMachine() map[string]string {
- return map[string]string{
- "Inmem": "inmem",
- }
-}
-
-func (im *InMemProvider) Name() string {
- return "inmem"
-}
-
-func (im *InMemProvider) Meta() core.MetaInfo {
- return core.MetaInfo{}
-}
-
-//nolint
-func init() {
- inmemProviderMeta := core.MetaInfo{
- Name: "inmem-provider",
- Description: "test-provider",
- }
-
- inmemProviderErrorMeta := core.MetaInfo{
- Name: "inmem-provider-error",
- Description: "test-provider-error",
- }
-
- providers.RegisterProvider(inmemProviderMeta, NewInMemProvider)
- providers.RegisterProvider(inmemProviderErrorMeta, NewInMemErrorProvider)
-}
-
-func NewInMemProvider(logger logging.Logger) (core.Provider, error) {
- return &InMemProvider{
- inmem: map[string]string{
- "prod/billing/FOO": "foo_shazam",
- "prod/billing/MG_KEY": "mg_shazam",
- "prod/billing/BEFORE_REMAP": "test_env_remap",
- },
- alwaysError: false,
- }, nil
-
-}
-
-func NewInMemErrorProvider(logger logging.Logger) (core.Provider, error) {
- return &InMemProvider{
- inmem: map[string]string{
- "prod/billing/FOO": "foo_shazam",
- "prod/billing/MG_KEY": "mg_shazam",
- "prod/billing/BEFORE_REMAP": "test_env_remap",
- },
- alwaysError: true,
- }, nil
-
-}
-
-func (im *InMemProvider) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
- if im.alwaysError {
- return nil, errors.New("error")
- }
-
- var entries []core.EnvEntry
-
- for k, v := range im.inmem {
- entries = append(entries, core.EnvEntry{
- Key: k,
- Value: v,
- ResolvedPath: p.Path,
- ProviderName: im.Name(),
- })
- }
- sort.Sort(core.EntriesByKey(entries))
- return entries, nil
-}
-func (im *InMemProvider) Get(p core.KeyPath) (*core.EnvEntry, error) {
- if im.alwaysError {
- return nil, errors.New("error")
- }
- s := im.inmem[p.Path]
- return &core.EnvEntry{
- Key: p.Env,
- Value: s,
- ResolvedPath: p.Path,
- ProviderName: im.Name(),
- }, nil
-}
-
-func getLogger() logging.Logger {
- logger := logging.New()
- logger.SetLevel("null")
- return logger
-}
-
-func TestNewTeller(t *testing.T) {
- tlrfile := &TellerFile{
- Project: "teller",
- Opts: map[string]string{
- "foo": "bar",
- },
- }
- cmd := []string{"teller", "show"}
- logger := getLogger()
-
- tl := NewTeller(tlrfile, cmd, true, logger)
-
- assert.True(t, tl.Redact)
- assert.Equal(t, tl.Config, tlrfile)
- assert.Equal(t, tl.Cmd, cmd)
- assert.Equal(t, tl.Populate, core.NewPopulate(map[string]string{
- "project": "teller",
- "foo": "bar",
- }))
- assert.Equal(t, tl.Logger, logger)
-}
-
-func TestTellerExports(t *testing.T) {
- tl := Teller{
- Logger: getLogger(),
- Entries: []core.EnvEntry{},
- Providers: &BuiltinProviders{},
- }
-
- b := tl.ExportEnv()
- assert.Equal(t, b, "#!/bin/sh\n")
-
- tl = Teller{
- Logger: getLogger(),
- Entries: []core.EnvEntry{
- {Key: "k", Value: "v", ProviderName: "test-provider", ResolvedPath: "path/kv"},
- },
- }
-
- b = tl.ExportEnv()
- assert.Equal(t, b, "#!/bin/sh\nexport k='v'\n")
-
- b, err := tl.ExportYAML()
- assert.NoError(t, err)
- assert.Equal(t, b, "k: v\n")
- b, err = tl.ExportJSON()
- assert.NoError(t, err)
- assert.Equal(t, b, "{\n \"k\": \"v\"\n}")
-}
-
-func TestTellerShExportEscaped(t *testing.T) {
- tl := Teller{
- Logger: getLogger(),
- Entries: []core.EnvEntry{},
- Providers: &BuiltinProviders{},
- }
-
- b := tl.ExportEnv()
- assert.Equal(t, b, "#!/bin/sh\n")
-
- tl = Teller{
- Logger: getLogger(),
- Entries: []core.EnvEntry{
- {Key: "k", Value: `()"';@ \(\)\"\'\;\@`, ProviderName: "test-provider", ResolvedPath: "path/kv"},
- },
- }
-
- b = tl.ExportEnv()
- assert.Equal(t, b, "#!/bin/sh\nexport k='()\"'\"'\"';@ \\(\\)\\\"\\'\"'\"'\\;\\@'\n")
-}
-
-func TestTellerCollect(t *testing.T) {
- var b bytes.Buffer
- tl := Teller{
- Logger: getLogger(),
- Providers: &BuiltinProviders{},
- Porcelain: &Porcelain{
- Out: &b,
- },
- Populate: core.NewPopulate(map[string]string{"stage": "prod"}),
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- Providers: map[string]MappingConfig{
- "inmem-provider": {
- Env: &map[string]core.KeyPath{
- "MG_KEY": {
- Path: "{{stage}}/billing/MG_KEY",
- },
- "FOO_BAR": {
- Path: "{{stage}}/billing/FOO",
- },
- },
- },
- },
- },
- }
- err := tl.Collect()
- assert.Nil(t, err)
- assert.Equal(t, len(tl.Entries), 2)
- assert.Equal(t, tl.Entries[0].Key, "MG_KEY")
- assert.Equal(t, tl.Entries[0].Value, "mg_shazam")
- assert.Equal(t, tl.Entries[0].ResolvedPath, "prod/billing/MG_KEY")
- assert.Equal(t, tl.Entries[0].ProviderName, "inmem-provider")
-
- assert.Equal(t, tl.Entries[1].Key, "FOO_BAR")
- assert.Equal(t, tl.Entries[1].Value, "foo_shazam")
- assert.Equal(t, tl.Entries[1].ResolvedPath, "prod/billing/FOO")
- assert.Equal(t, tl.Entries[1].ProviderName, "inmem-provider")
-}
-
-func TestTellerCollectWithSync(t *testing.T) {
- var b bytes.Buffer
- tl := Teller{
- Logger: getLogger(),
- Providers: &BuiltinProviders{},
- Porcelain: &Porcelain{
- Out: &b,
- },
- Populate: core.NewPopulate(map[string]string{"stage": "prod"}),
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- Providers: map[string]MappingConfig{
- "inmem-provider": {
- EnvMapping: &core.KeyPath{
- Path: "{{stage}}/billing",
- Remap: &map[string]string{
- "prod/billing/BEFORE_REMAP": "prod/billing/REMAPED",
- },
- },
- },
- },
- },
- }
- err := tl.Collect()
- assert.Nil(t, err)
- assert.Equal(t, len(tl.Entries), 3)
- assert.Equal(t, tl.Entries[0].Key, "prod/billing/REMAPED")
- assert.Equal(t, tl.Entries[0].Value, "test_env_remap")
- assert.Equal(t, tl.Entries[0].ResolvedPath, "prod/billing")
- assert.Equal(t, tl.Entries[0].ProviderName, "inmem-provider")
-
- assert.Equal(t, tl.Entries[1].Key, "prod/billing/MG_KEY")
- assert.Equal(t, tl.Entries[1].Value, "mg_shazam")
- assert.Equal(t, tl.Entries[1].ResolvedPath, "prod/billing")
- assert.Equal(t, tl.Entries[1].ProviderName, "inmem-provider")
-
- assert.Equal(t, tl.Entries[2].Key, "prod/billing/FOO")
- assert.Equal(t, tl.Entries[2].Value, "foo_shazam")
- assert.Equal(t, tl.Entries[2].ResolvedPath, "prod/billing")
- assert.Equal(t, tl.Entries[2].ProviderName, "inmem-provider")
-}
-
-func TestTellerCollectWithSyncRemapWith(t *testing.T) {
- var b bytes.Buffer
- tl := Teller{
- Logger: getLogger(),
- Providers: &BuiltinProviders{},
- Porcelain: &Porcelain{
- Out: &b,
- },
- Populate: core.NewPopulate(map[string]string{"stage": "prod"}),
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- Providers: map[string]MappingConfig{
- "inmem-provider": {
- EnvMapping: &core.KeyPath{
- Path: "{{stage}}/billing",
- RemapWith: &map[string]core.RemapKeyPath{
- "prod/billing/BEFORE_REMAP": {
- Field: "prod/billing/REMAPED",
- Severity: core.None,
- RedactWith: "-",
- },
- },
- },
- },
- },
- },
- }
- err := tl.Collect()
- assert.Nil(t, err)
- assert.Equal(t, len(tl.Entries), 3)
- assert.Equal(t, tl.Entries[0].Key, "prod/billing/REMAPED")
- assert.Equal(t, tl.Entries[0].Severity, core.None)
- assert.Equal(t, tl.Entries[0].RedactWith, "-")
- assert.Equal(t, tl.Entries[0].Value, "test_env_remap")
- assert.Equal(t, tl.Entries[0].ResolvedPath, "prod/billing")
- assert.Equal(t, tl.Entries[0].ProviderName, "inmem-provider")
-
- assert.Equal(t, tl.Entries[1].Key, "prod/billing/MG_KEY")
- assert.Equal(t, tl.Entries[1].Value, "mg_shazam")
- assert.Equal(t, tl.Entries[1].ResolvedPath, "prod/billing")
- assert.Equal(t, tl.Entries[1].ProviderName, "inmem-provider")
-
- assert.Equal(t, tl.Entries[2].Key, "prod/billing/FOO")
- assert.Equal(t, tl.Entries[2].Value, "foo_shazam")
- assert.Equal(t, tl.Entries[2].ResolvedPath, "prod/billing")
- assert.Equal(t, tl.Entries[2].ProviderName, "inmem-provider")
-}
-
-func TestTellerCollectWithErrors(t *testing.T) {
- var b bytes.Buffer
- tl := Teller{
- Logger: getLogger(),
- Providers: &BuiltinProviders{},
- Porcelain: &Porcelain{
- Out: &b,
- },
- Populate: core.NewPopulate(map[string]string{"stage": "prod"}),
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- Providers: map[string]MappingConfig{
- "inmem-provider-error": {
- EnvMapping: &core.KeyPath{
- Path: "{{stage}}/billing",
- },
- },
- },
- },
- }
- err := tl.Collect()
- assert.NotNil(t, err)
-}
-func TestTellerPorcelainNonInteractive(t *testing.T) {
- var b bytes.Buffer
-
- entries := []core.EnvEntry{}
-
- tl := Teller{
- Logger: getLogger(),
- Entries: entries,
- Porcelain: &Porcelain{
- Out: &b,
- },
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- },
- }
-
- tl.PrintEnvKeys()
- assert.Equal(t, b.String(), "-*- teller: loaded variables for test-project using nowhere -*-\n\n")
- b.Reset()
-
- tl.Entries = append(tl.Entries, core.EnvEntry{
- IsFound: true,
- Key: "k", Value: "v", ProviderName: "test-provider", ResolvedPath: "path/kv",
- })
-
- tl.PrintEnvKeys()
- assert.Equal(t, b.String(), "-*- teller: loaded variables for test-project using nowhere -*-\n\n[test-provider path/kv] k = v*****\n")
-
-}
-
-func TestTellerEntriesOutputSort(t *testing.T) {
- var b bytes.Buffer
-
- entries := []core.EnvEntry{}
-
- tl := Teller{
- Logger: getLogger(),
- Entries: entries,
- Porcelain: &Porcelain{
- Out: &b,
- },
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- },
- }
-
- tl.Entries = append(tl.Entries, core.EnvEntry{
- IsFound: true,
- Key: "c", Value: "c", ProviderName: "test-provider", ResolvedPath: "path/kv",
- })
- tl.Entries = append(tl.Entries, core.EnvEntry{
- IsFound: true,
- Key: "a", Value: "a", ProviderName: "test-provider", ResolvedPath: "path/kv",
- })
- tl.Entries = append(tl.Entries, core.EnvEntry{
- IsFound: true,
- Key: "b", Value: "b", ProviderName: "test-provider", ResolvedPath: "path/kv",
- })
- tl.Entries = append(tl.Entries, core.EnvEntry{
- IsFound: true,
- Key: "k", Value: "v", ProviderName: "alpha", ResolvedPath: "path/kv",
- })
- tl.Entries = append(tl.Entries, core.EnvEntry{
- IsFound: true,
- Key: "k", Value: "v", ProviderName: "BETA", ResolvedPath: "path/kv",
- })
-
- tl.PrintEnvKeys()
- assert.Equal(t, b.String(), "-*- teller: loaded variables for test-project using nowhere -*-\n\n[alpha path/kv] k = v*****\n[BETA path/kv] k = v*****\n[test-provider path/kv] a = a*****\n[test-provider path/kv] b = b*****\n[test-provider path/kv] c = c*****\n")
-}
-
-func TestTellerDrift(t *testing.T) {
- tl := Teller{
- Logger: getLogger(),
- Entries: []core.EnvEntry{
- {Key: "k", Value: "v", Source: "s1", ProviderName: "test-provider", ResolvedPath: "path/kv"},
- {Key: "k", Value: "v", Sink: "s1", ProviderName: "test-provider2", ResolvedPath: "path/kv"},
- {Key: "kX", Value: "vx", Source: "s1", ProviderName: "test-provider", ResolvedPath: "path/kv"},
- {Key: "kX", Value: "CHANGED", Sink: "s1", ProviderName: "test-provider2", ResolvedPath: "path/kv"},
-
- // these do not have sink/source
- {Key: "k--", Value: "00", ProviderName: "test-provider", ResolvedPath: "path/kv"},
- {Key: "k--", Value: "11", ProviderName: "test-provider2", ResolvedPath: "path/kv"},
- },
- }
-
- drifts := tl.Drift([]string{})
-
- assert.Equal(t, len(drifts), 1)
- d := drifts[0]
- assert.Equal(t, d.Source.Value, "vx")
- assert.Equal(t, d.Target.Value, "CHANGED")
-}
-
-func TestTellerMirrorDrift(t *testing.T) {
- tlrfile, err := NewTellerFile("../fixtures/mirror-drift/teller.yml")
- if err != nil {
- fmt.Printf("Error: %v", err)
- os.Exit(1)
- }
-
- tl := NewTeller(tlrfile, []string{}, false, getLogger())
-
- drifts, err := tl.MirrorDrift("source", "target")
- assert.NoError(t, err)
-
- assert.Equal(t, len(drifts), 2)
- d := drifts[0]
- assert.Equal(t, d.Source.Key, "THREE")
- assert.Equal(t, d.Source.Value, "3")
- assert.Equal(t, d.Diff, "missing")
- assert.Equal(t, d.Target.Value, "")
-
- d = drifts[1]
- assert.Equal(t, d.Source.Key, "ONE")
- assert.Equal(t, d.Source.Value, "1")
- assert.Equal(t, d.Diff, "changed")
- assert.Equal(t, d.Target.Value, "5")
-}
-
-func TestTellerSync(t *testing.T) {
- tlrfile, err := NewTellerFile("../fixtures/sync/teller.yml")
- if err != nil {
- fmt.Printf("Error: %v", err)
- os.Exit(1)
- }
-
- tl := NewTeller(tlrfile, []string{}, false, getLogger())
-
- err = os.WriteFile("../fixtures/sync/target.env", []byte(`
-FOO=1
-`), 0644)
- assert.NoError(t, err)
-
- err = os.WriteFile("../fixtures/sync/target2.env", []byte(`
-FOO=2
-`), 0644)
-
- assert.NoError(t, err)
-
- err = tl.Sync("source", []string{"target", "target2"}, true)
-
- assert.NoError(t, err)
-
- content, err := os.ReadFile("../fixtures/sync/target.env")
- assert.NoError(t, err)
-
- assert.Equal(t, string(content), `FOO="1"
-ONE="1"
-THREE="3"
-TWO="2"`)
-
- content, err = os.ReadFile("../fixtures/sync/target2.env")
- assert.NoError(t, err)
-
- assert.Equal(t, string(content), `FOO="2"
-ONE="1"
-THREE="3"
-TWO="2"`)
-}
-
-func TestTemplateFile(t *testing.T) {
-
- tlrfile, err := NewTellerFile("../fixtures/sync/teller.yml")
- if err != nil {
- fmt.Printf("Error: %v", err)
- os.Exit(1)
- }
-
- tl := NewTeller(tlrfile, []string{}, false, getLogger())
- tl.Entries = append(tl.Entries, core.EnvEntry{Key: "TEST-PLACEHOLDER", Value: "secret-here"})
-
- tempFolder, _ := os.MkdirTemp(os.TempDir(), "test-template")
- defer os.RemoveAll(tempFolder)
-
- templatePath := filepath.Join(tempFolder, "target.tpl") // prepare template file path
- destinationPath := filepath.Join(tempFolder, "starget.envs") // prepare destination file path
-
- err = os.WriteFile(templatePath, []byte(`Hello, {{.Teller.EnvByKey "TEST-PLACEHOLDER" "default-value" }}!`), 0644)
- assert.NoError(t, err)
-
- err = tl.templateFile(templatePath, destinationPath)
- assert.NoError(t, err)
-
- txt, err := ioutil.ReadFile(destinationPath)
- assert.NoError(t, err)
- assert.Equal(t, string(txt), "Hello, secret-here!")
-
-}
-
-func TestTemplateFolder(t *testing.T) {
-
- tlrfile, err := NewTellerFile("../fixtures/sync/teller.yml")
- if err != nil {
- fmt.Printf("Error: %v", err)
- os.Exit(1)
- }
-
- tl := NewTeller(tlrfile, []string{}, false, getLogger())
- tl.Entries = append(tl.Entries, core.EnvEntry{Key: "TEST-PLACEHOLDER", Value: "secret-here"})
- tl.Entries = append(tl.Entries, core.EnvEntry{Key: "TEST-PLACEHOLDER-2", Value: "secret2-here"})
-
- rootTempDir := os.TempDir()
- tempFolder, _ := os.MkdirTemp(rootTempDir, "test-template") // create temp root folder
- // Create template folders structure
- templateFolder := filepath.Join(tempFolder, "from")
- err = os.MkdirAll(templateFolder, os.ModePerm)
- assert.NoError(t, err)
- err = os.MkdirAll(filepath.Join(templateFolder, "folder1", "folder2"), os.ModePerm)
- assert.NoError(t, err)
-
- // copy to:
- copyToFolder := filepath.Join(tempFolder, "to")
-
- err = os.MkdirAll(copyToFolder, os.ModePerm)
- assert.NoError(t, err)
-
- defer os.RemoveAll(tempFolder)
-
- err = os.WriteFile(filepath.Join(templateFolder, "target.tpl"), []byte(`Hello, {{.Teller.EnvByKey "TEST-PLACEHOLDER" "default-value" }}!`), 0644)
- assert.NoError(t, err)
- err = os.WriteFile(filepath.Join(templateFolder, "folder1", "folder2", "target2.tpl"), []byte(`Hello, {{.Teller.EnvByKey "TEST-PLACEHOLDER-2" "default-value" }}!`), 0644)
- assert.NoError(t, err)
-
- err = tl.templateFolder(templateFolder, copyToFolder)
- assert.NoError(t, err)
- fmt.Println(copyToFolder)
-
- txt, err := ioutil.ReadFile(filepath.Join(copyToFolder, "target.tpl"))
- assert.NoError(t, err)
- assert.Equal(t, string(txt), "Hello, secret-here!")
-
- txt, err = ioutil.ReadFile(filepath.Join(copyToFolder, "folder1", "folder2", "target2.tpl"))
- assert.NoError(t, err)
- assert.Equal(t, string(txt), "Hello, secret2-here!")
-
-}
-
-func TestTellerDelete(t *testing.T) {
- fooPath := "/sample/path/FOO"
- p := &InMemProvider{
- inmem: map[string]string{
- fooPath: "foo",
- "/sample/path/BAR": "bar",
- },
- alwaysError: false,
- }
- tl := Teller{
- Logger: getLogger(),
- Providers: p,
- Porcelain: &Porcelain{
- Out: ioutil.Discard,
- },
- Populate: core.NewPopulate(map[string]string{"stage": "prod"}),
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- Providers: map[string]MappingConfig{
- "inmem-provider": {
- Env: &map[string]core.KeyPath{
- "FOO": {
- Path: "/sample/path",
- Env: "FOO",
- },
- "BAR": {
- Path: "/sample/path",
- Env: "BAR",
- },
- },
- },
- },
- },
- }
-
- keysToDelete := []string{"FOO"}
- err := tl.Delete(keysToDelete, []string{"inmem-provider"}, "", false)
- assert.NoError(t, err)
-
- assert.Equal(t, len(p.inmem), 1)
- _, ok := p.inmem[fooPath]
- assert.False(t, ok)
-
- keysToDelete = []string{"BAR"}
- err = tl.Delete(keysToDelete, []string{"inmem-provider"}, "/sample/path", false)
- assert.NoError(t, err)
-
- assert.Equal(t, len(p.inmem), 0)
-}
-
-func TestTellerDeleteAll(t *testing.T) {
- p := &InMemProvider{
- inmem: map[string]string{
- "/sample/path/FOO": "foo",
- "/sample/path/BAR": "bar",
- },
- alwaysError: false,
- }
- tl := Teller{
- Logger: getLogger(),
- Providers: p,
- Porcelain: &Porcelain{
- Out: ioutil.Discard,
- },
- Populate: core.NewPopulate(map[string]string{"stage": "prod"}),
- Config: &TellerFile{
- Project: "test-project",
- LoadedFrom: "nowhere",
- Providers: map[string]MappingConfig{
- "inmem-provider": {
- Env: &map[string]core.KeyPath{
- "FOO": {
- Path: "/sample/path",
- Env: "FOO",
- },
- "BAR": {
- Path: "/sample/path",
- Env: "BAR",
- },
- },
- },
- },
- },
- }
-
- err := tl.Delete([]string{}, []string{"inmem-provider"}, "/sample/path", true)
- assert.NoError(t, err)
-
- assert.Equal(t, len(p.inmem), 0)
-}
-
-func TestTeller_execCmd(t *testing.T) {
- cmd := "bash"
- cmdArgs := []string{"-c", "for i in {1..3}; do echo $SOME_KEY; echo $OTHER_KEY 1>&2; done"}
- entries := []core.EnvEntry{
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "OTHER_KEY",
- Value: "hello",
- RedactWith: "**OTHER_KEY**",
- },
- {
- ProviderName: "test",
- ResolvedPath: "/some/path",
- Key: "SOME_KEY",
- Value: "hello123",
- RedactWith: "**SOME_KEY**",
- },
- }
-
- tests := []struct {
- name string
- carryEnv bool
- withRedaction bool
- }{
- {
- name: "CarryEnv: false, withRedaction: false",
- carryEnv: false,
- withRedaction: false,
- },
- {
- name: "CarryEnv: true, withRedaction: false",
- carryEnv: true,
- withRedaction: false,
- },
- {
- name: "CarryEnv: false, withRedaction: true",
- carryEnv: false,
- withRedaction: true,
- },
- {
- name: "CarryEnv: true, withRedaction: true",
- carryEnv: true,
- withRedaction: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- oldStdout, oldStderr := os.Stdout, os.Stderr
- t.Cleanup(func() {
- os.Stdout, os.Stderr = oldStdout, oldStderr
- })
-
- var err error
- os.Stdout, err = os.CreateTemp(t.TempDir(), "stdout")
- assert.NoError(t, err)
- os.Stderr, err = os.CreateTemp(t.TempDir(), "stderr")
- assert.NoError(t, err)
-
- tl := &Teller{
- Config: &TellerFile{
- CarryEnv: tt.carryEnv,
- },
- Entries: entries,
- }
- assert.NoError(t, tl.execCmd(cmd, cmdArgs, tt.withRedaction))
-
- os.Stdout.Seek(0, io.SeekStart)
- o, _ := io.ReadAll(os.Stdout)
- os.Stderr.Seek(0, io.SeekStart)
- e, _ := io.ReadAll(os.Stderr)
- if tt.withRedaction {
- assert.Equal(t, "**SOME_KEY**\n**SOME_KEY**\n**SOME_KEY**\n", string(o))
- assert.Equal(t, "**OTHER_KEY**\n**OTHER_KEY**\n**OTHER_KEY**\n", string(e))
- } else {
- assert.Equal(t, "hello123\nhello123\nhello123\n", string(o))
- assert.Equal(t, "hello\nhello\nhello\n", string(e))
- }
- })
- }
-}
diff --git a/pkg/tellerfile.go b/pkg/tellerfile.go
deleted file mode 100644
index bf66879d..00000000
--- a/pkg/tellerfile.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package pkg
-
-import (
- "os"
-
- "github.com/spectralops/teller/pkg/core"
- "gopkg.in/yaml.v2"
-)
-
-type ProvidersMap map[string]MappingConfig
-type TellerFile struct {
- Opts map[string]string `yaml:"opts,omitempty"`
- Confirm string `yaml:"confirm,omitempty"`
- Project string `yaml:"project,omitempty"`
- CarryEnv bool `yaml:"carry_env,omitempty"`
- Providers ProvidersMap `yaml:"providers,omitempty"`
- LoadedFrom string
-}
-
-type MappingConfig struct {
- Kind string `yaml:"kind,omitempty"`
- EnvMapping *core.KeyPath `yaml:"env_sync,omitempty"`
- Env *map[string]core.KeyPath `yaml:"env,omitempty"`
-}
-
-func NewTellerFile(s string) (*TellerFile, error) {
- yamlFile, err := os.ReadFile(s)
- if err != nil {
- return nil, err
- }
- t := &TellerFile{}
- err = yaml.Unmarshal(yamlFile, t)
- if err != nil {
- return nil, err
- }
- t.LoadedFrom = s
- return t, nil
-}
diff --git a/pkg/templating.go b/pkg/templating.go
deleted file mode 100644
index c1167db5..00000000
--- a/pkg/templating.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package pkg
-
-import (
- "bytes"
- "text/template"
-
- "github.com/spectralops/teller/pkg/core"
-)
-
-type Templating struct {
-}
-type viewmodel struct {
- Teller *core.EnvEntryLookup
-}
-
-func (t *Templating) New() *Templating {
- return &Templating{}
-}
-
-func (t *Templating) ForTemplate(tmpl string, entries []core.EnvEntry) (string, error) {
- lookup := core.EnvEntryLookup{
- Entries: entries,
- }
-
- tt, err := template.New("t").Parse(tmpl)
- if err != nil {
- return "", err
- }
-
- var output bytes.Buffer
-
- err = tt.Execute(&output, viewmodel{Teller: &lookup})
- if err != nil {
- return "", err
- }
-
- return output.String(), nil
-}
-
-func (t *Templating) ForGlob() *Templating {
- return &Templating{}
-}
diff --git a/pkg/templating_test.go b/pkg/templating_test.go
deleted file mode 100644
index 3c3113e4..00000000
--- a/pkg/templating_test.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package pkg
-
-import (
- "testing"
-)
-
-func TestTemplating(t *testing.T) {
- //assert.Fail(t)
-}
diff --git a/pkg/utils/files.go b/pkg/utils/files.go
deleted file mode 100644
index 5c66be1a..00000000
--- a/pkg/utils/files.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package utils
-
-import (
- "os"
- "path/filepath"
-)
-
-const (
- filePermission = 0600
-)
-
-func WriteFileInPath(filename, to string, content []byte) error {
- if to != "" {
- if _, err := os.Stat(to); os.IsNotExist(err) {
- err = os.MkdirAll(to, os.ModePerm)
- if err != nil {
- return err
- }
- }
- }
- err := os.WriteFile(filepath.Join(to, filename), content, filePermission)
-
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/pkg/utils/strings.go b/pkg/utils/strings.go
deleted file mode 100644
index eb8bb507..00000000
--- a/pkg/utils/strings.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package utils
-
-import "strings"
-
-func LastSegment(s string) string {
- segs := strings.Split(s, "/")
- return segs[len(segs)-1]
-}
-
-func Merge(first, second map[string]string) map[string]string {
- target := make(map[string]string)
- for k, v := range first {
- target[k] = v
- }
- for k, v := range second {
- target[k] = v
- }
-
- return target
-}
diff --git a/pkg/wizard_template.go b/pkg/wizard_template.go
deleted file mode 100644
index cc5bd54f..00000000
--- a/pkg/wizard_template.go
+++ /dev/null
@@ -1,303 +0,0 @@
-package pkg
-
-var TellerFileTemplate = `
-project: {{.Project}}
-{{- if .Confirm }}
-confirm: Are you sure you want to run on {{"{{stage}}"}}?
-{{ end }}
-
-# Set this if you want to carry over parent process' environment variables
-# carry_env: true
-
-
-#
-# Variables
-#
-# Feel free to add options here to be used as a variable throughout
-# paths.
-#
-opts:
- region: env:AWS_REGION # you can get env variables with the 'env:' prefix, for default values if env not found use comma. Example: env:AWS_REGION,{DEFAULT_VALUE}
- stage: development
-
-
-#
-# Providers
-#
-providers:
-
-{{- if index .ProviderKeys "heroku" }}
- # requires an API key in: HEROKU_API_KEY (you can fetch yours from ~/.netrc)
- heroku:
- # sync a complete environment
- env_sync:
- path: drakula-demo
-
- # # pick and choose variables
- # env:
- # JVM_OPTS:
- # path: drakula-demo
-{{end}}
-
-{{- if index .ProviderKeys "vercel" }}
- # requires an API token in: VERCEL_TOKEN
- vercel:
- # sync a complete environment
- env_sync:
- path: drakula-demo
-
- # # pick and choose variables
- # env:
- # JVM_OPTS:
- # path: drakula-demo
-{{end}}
-
-{{- if index .ProviderKeys "hashicorp_vault" }}
- # configure only from environment
- # https://github.com/hashicorp/vault/blob/api/v1.0.4/api/client.go#L28
- # this vars should not go through to the executing cmd
- hashicorp_vault:
- env_sync:
- path: secret/data/{{"{{stage}}"}}/billing/web/env
- env:
- SMTP_PASS:
- path: secret/data/{{"{{stage}}"}}/wordpress
- field: smtp
-{{end}}
-
-{{- if index .ProviderKeys "aws_secretsmanager" }}
- # configure only from environment
- # filter secret versioning by adding comma separating in path value (path: prod/foo/bar,).
- aws_secretsmanager:
- env_sync:
- path: prod/foo/bar
- env:
- FOO_BAR:
- path: prod/foo/bar
- field: SOME_KEY
-{{end}}
-
-{{- if index .ProviderKeys "aws_ssm" }}
- # configure only from environment
- aws_ssm:
- env:
- FOO_BAR:
- path: /prod/foobar
- decrypt: true
-{{end}}
-
-{{- if index .ProviderKeys "google_secretmanager" }}
- # GOOGLE_APPLICATION_CREDENTIALS=foobar.json
- # https://cloud.google.com/secret-manager/docs/reference/libraries#setting_up_authentication
- google_secretmanager:
- env:
- FOO_GOOG:
- # need to supply the relevant version (versions/1)
- path: projects/123/secrets/FOO_GOOG/versions/1
-{{end}}
-
-{{- if index .ProviderKeys "etcd" }}
- # Configure via environment:
- # ETCDCTL_ENDPOINTS
- # tls:
- # ETCDCTL_CA_FILE
- # ETCDCTL_CERT_FILE
- # ETCDCTL_KEY_FILE
- etcd:
- env_sync:
- path: /prod/foo
- env:
- ETC_DSN:
- path: /prod/foo/bar
-{{end}}
-
-{{- if index .ProviderKeys "consul" }}
- # Configure via environment:
- # CONSUL_HTTP_ADDR
- consul:
- env_sync:
- path: redis/config
- env:
- ETC_DSN:
- path: redis/config/foobar
-{{end}}
-
-{{- if index .ProviderKeys "dotenv" }}
- # you can mix and match many files
- dotenv:
- env_sync:
- path: ~/my-dot-env.env
- env:
- FOO_BAR:
- path: ~/my-dot-env.env
-{{end}}
-
-{{- if index .ProviderKeys "azure_keyvault" }}
- # you can mix and match many files
- azure_keyvault:
- env_sync:
- path: azure
- env:
- FOO_BAR:
- path: foobar
-{{end}}
-
-{{- if index .ProviderKeys "doppler" }}
- # set your doppler project with "doppler configure set project "
- doppler:
- env_sync:
- path: prd
- env:
- FOO_BAR:
- path: prd
-{{end}}
-
-{{- if index .ProviderKeys "cyberark_conjur" }}
- # https://conjur.org
- # set CONJUR_AUTHN_LOGIN and CONJUR_AUTHN_API_KEY env vars
- # set .conjurrc file in user's home directory
- cyberark_conjur:
- env:
- FOO_BAR:
- path: /secrets/foo/bar
-{{end}}
-
-{{- if index .ProviderKeys "1password" }}
- # Configure via environment variables:
- # OP_CONNECT_HOST
- # OP_CONNECT_TOKEN
- 1password:
- env_sync:
- path: # Key title
- source: # 1Password token gen include access to multiple vault. to get the secrets you must add and vaultUUID. the field is mandatory
- env:
- FOO_BAR:
- path: # Key title
- source: # 1Password token gen include access to multiple vault. to get the secrets you must add and vaultUUID. the field is mandatory
- field: # The secret field to get. notesPlain, {label key}, password etc.
-{{end}}
-
-{{- if index .ProviderKeys "gopass" }}
- # Override default configuration: https://github.com/gopasspw/gopass/blob/master/docs/config.md
- gopass:
- env_sync:
- path: foo
- env:
- ETC_DSN:
- path: foo/bar
-{{end}}
-
-{{- if index .ProviderKeys "lastpass" }}
- # Configure via environment variables:
- # LASTPASS_USERNAME
- # LASTPASS_PASSWORD
-
- lastpass:
- env_sync:
- path: # LastPass item ID
- env:
- ETC_DSN:
- path: # Lastpass item ID
- # field: by default taking password property. in case you want other property un-mark this line and set the lastpass property name.
-{{end}}
-
-{{- if index .ProviderKeys "cloudflare_workers_secrets" }}
-
- # Configure via environment variables for integration:
- # CLOUDFLARE_API_KEY: Your Cloudflare api key.
- # CLOUDFLARE_API_EMAIL: Your email associated with the api key.
- # CLOUDFLARE_ACCOUNT_ID: Your account ID.
-
- cloudflare_workers_secrets:
- env_sync:
- source: # Mandatory: script field
- env:
- script-value:
- path: foo-secret
- source: # Mandatory: script field
-{{end}}
-
-{{- if index .ProviderKeys "github" }}
-
- # Configure via environment variables for integration:
- # GITHUB_AUTH_TOKEN: GitHub token
-
- github:
- env_sync:
- path: owner/github-repo
- env:
- script-value:
- path: owner/github-repo
-
-{{end}}
-
-{{- if index .ProviderKeys "keypass" }}
-
- # Configure via environment variables for integration:
- # KEYPASS_PASSWORD: KeyPass password
- # KEYPASS_DB_PATH: Path to DB file
-
- keypass:
- env_sync:
- path: redis/config
- # source: Optional, all fields is the default. Supported fields: Notes, Title, Password, URL, UserName
- env:
- ETC_DSN:
- path: redis/config/foobar
- # source: Optional, Password is the default. Supported fields: Notes, Title, Password, URL, UserName
-
-{{end}}
-
-{{- if index .ProviderKeys "filesystem" }}
-
- filesystem:
- env_sync:
- path: redis/config
- env:
- ETC_DSN:
- path: redis/config/foobar
-
-{{end}}
-
-{{- if index .ProviderKeys "process_env" }}
-
- process_env:
- env:
- ETC_DSN:
- field: SOME_KEY # Optional, accesses the environment variable SOME_KEY and maps it to ETC_DSN
-
-{{end}}
-
-{{- if index .ProviderKeys "ansible_vault" }}
-
- # Configure via environment variables for integration:
- # ANSIBLE_VAULT_PASSPHRASE: Ansible Vault Password
-
- ansible_vault:
- env_sync:
- path: ansible/vars/vault_{{stage}}.yml
-
- env:
- KEY1:
- path: ansible/vars/vault_{{stage}}.yml
- NONEXIST_KEY:
- path: ansible/vars/vault_{{stage}}.yml
-
-{{end}}
-
-{{- if index .ProviderKeys "keeper_secretsmanager" }}
-
- # requires a configuration in: KSM_CONFIG=base64_config or file path KSM_CONFIG_FILE=ksm_config.json
- keeper_secretsmanager:
- env_sync:
- path: RECORD_UID
- # all non-empty fields are mapped by their labels, if empty then by field type, and index 1,2,...,N
-
- env:
- USER:
- path: RECORD_UID/field/login
- # use Keeper Notation to select individual field values
- # https://docs.keeper.io/secrets-manager/secrets-manager/about/keeper-notation
-
-{{end}}
-`
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 00000000..5d56faf9
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 00000000..2d9cafa5
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,3 @@
+unstable_features = true
+group_imports = "StdExternalCrate"
+format_strings = true
diff --git a/rustwrap.yaml b/rustwrap.yaml
new file mode 100644
index 00000000..2c4ec9ed
--- /dev/null
+++ b/rustwrap.yaml
@@ -0,0 +1,33 @@
+# replace everything that starts with __V_
+
+targets:
+ - platform: win32
+ arch: x64
+ url_template: https://github.com/tellerops/teller/releases/download/v__VERSION__/teller-x86_64-windows.zip
+ archive: dist/releases/teller-x86_64-windows.zip
+ - platform: linux
+ arch: x64
+ url_template: https://github.com/tellerops/teller/releases/download/v__VERSION__/teller-x86_64-linux.tar.xz
+ archive: dist/releases/teller-x86_64-linux.tar.xz
+ - platform: darwin
+ arch: arm64
+ url_template: https://github.com/tellerops/teller/releases/download/v__VERSION__/teller-aarch64-macos.tar.xz
+ archive: dist/releases/teller-aarch64-macos.tar.xz
+
+brew:
+ name: teller
+ publish: false
+s/homebrew-tap
+ recipe_fname: teller.rb
+ recipe_template: |
+ class Teller < Formula
+ desc "desc"
+ homepage "http://github.com/tellerops/teller"
+ url "__URL__[arm64]"
+ version "__VERSION__"
+ sha256 "__SHA__[arm64]"
+
+ def install
+ bin.install "teller"
+ end
+ end
diff --git a/teller-cli/Cargo.toml b/teller-cli/Cargo.toml
new file mode 100644
index 00000000..05694271
--- /dev/null
+++ b/teller-cli/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "teller-cli"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+serde = { workspace = true }
+serde_json = { workspace = true }
+serde_yaml = { workspace = true }
+serde_derive = { workspace = true }
+fs-err = "2.9.0"
+eyre = "0.6.8"
+thiserror = { workspace = true }
+tokio = { workspace = true }
+# async-trait = "*"
+tracing = "^0.1.34"
+tracing-tree = { version = "0.2.1" }
+tracing-subscriber = { version = "^0.3.11", features = ["env-filter"] }
+strum = { workspace = true }
+proc-macro2 = "1.0.63" # Remove once https://github.com/rust-lang/rust/issues/113152 is fixed.
+clap = { version = "4.3.0", features = ["cargo", "derive"] }
+exitcode = { version = "^1.1.2" }
+console = { version = "*" }
+comfy-table = { version = "*" }
+dialoguer = { version = "0.11.0" }
+teller-providers = { version = "*", path = "../teller-providers" }
+teller-core = { version = "*", path = "../teller-core" }
+
+[dev-dependencies]
+insta = { workspace = true }
+dockertest-server = { version = "0.1.7", features = ["hashi", "cloud"] }
+trycmd = "0.14.10"
+
+[[bin]]
+name = "teller"
diff --git a/teller-cli/fixtures/config.yml b/teller-cli/fixtures/config.yml
new file mode 100644
index 00000000..79d6c0f7
--- /dev/null
+++ b/teller-cli/fixtures/config.yml
@@ -0,0 +1,18 @@
+providers:
+ hashi_1:
+ kind: hashicorp # name is = kind if empty
+ maps:
+ - id: test
+ path: /{{ get_env(name="TEST_LOAD_1", default="test") }}/users/user1
+ # if empty, map everything
+ # == means map to same key name
+ # otherwise key on left becomes right
+ # in the future: key_transform: camelize, snake_case for automapping the keys
+ keys:
+ GITHUB_TOKEN: ==
+ mg: FOO_BAR
+ dot_1:
+ kind: dotenv # name is = kind if empty
+ maps:
+ - id: test
+ path: VAR_{{ get_env(name="STAGE", default="development") }}
diff --git a/teller-cli/fixtures/dotenv.yml b/teller-cli/fixtures/dotenv.yml
new file mode 100644
index 00000000..7b5f2d18
--- /dev/null
+++ b/teller-cli/fixtures/dotenv.yml
@@ -0,0 +1,6 @@
+providers:
+ dot_1:
+ kind: dotenv
+ maps:
+ - id: test
+ path: fixtures/vars.env
diff --git a/teller-cli/fixtures/flow.env b/teller-cli/fixtures/flow.env
new file mode 100644
index 00000000..1d7b8023
--- /dev/null
+++ b/teller-cli/fixtures/flow.env
@@ -0,0 +1,3 @@
+
+FOO=bar
+HELLO=world
diff --git a/teller-cli/fixtures/flow_test.yml b/teller-cli/fixtures/flow_test.yml
new file mode 100644
index 00000000..8dfff147
--- /dev/null
+++ b/teller-cli/fixtures/flow_test.yml
@@ -0,0 +1,39 @@
+providers:
+ hashi_1:
+ kind: hashicorp
+ options:
+ address: {{ address }}
+ token: {{ token }}
+ maps:
+ - id: pg-dev
+ path: secret/dev/postgres
+ protocol: kv2
+ sm_1:
+ kind: aws_secretsmanager
+ options:
+ region: us-east-1
+ access_key_id: stub
+ secret_access_key: stub
+ provider_name: faked
+ endpoint_url: {{ endpoint_url }}
+ maps:
+ - id: pg-dev
+ path: /dev/postgres
+ ssm_1:
+ kind: ssm
+ options:
+ region: us-east-1
+ access_key_id: stub
+ secret_access_key: stub
+ provider_name: faked
+ endpoint_url: {{ endpoint_url }}
+ maps:
+ - id: pg-dev
+ path: /dev/postgres
+ keys:
+ USER: USER_NAME
+ dot_1:
+ kind: dotenv
+ maps:
+ - id: tvars
+ path: fixtures/flow_test_vars.env
diff --git a/teller-cli/fixtures/flow_test_vars.env b/teller-cli/fixtures/flow_test_vars.env
new file mode 100644
index 00000000..8a9b809a
--- /dev/null
+++ b/teller-cli/fixtures/flow_test_vars.env
@@ -0,0 +1 @@
+USER_NAME=linus
diff --git a/teller-cli/fixtures/google_sm_integration.yml b/teller-cli/fixtures/google_sm_integration.yml
new file mode 100644
index 00000000..d741ed4a
--- /dev/null
+++ b/teller-cli/fixtures/google_sm_integration.yml
@@ -0,0 +1,12 @@
+providers:
+ gsm1:
+ kind: google_secretmanager
+ maps:
+ - id: test
+ path: projects/{{ get_env(name="TEST_PROVIDER_GSM_PROJECT", default="test") }}
+ # if empty, map everything
+ # == means map to same key name
+ # otherwise key on left becomes right
+ # in the future: key_transform: camelize, snake_case for automapping the keys
+ keys:
+ TLR_TEST_SKRT: ==
diff --git a/teller-cli/fixtures/hashi.yml b/teller-cli/fixtures/hashi.yml
new file mode 100644
index 00000000..eea66ff2
--- /dev/null
+++ b/teller-cli/fixtures/hashi.yml
@@ -0,0 +1,14 @@
+providers:
+ hashi_1:
+ kind: hashicorp # name is = kind if empty
+ maps:
+ - id: test
+ path: data1/mysecret
+ protocol: kv1
+ # if empty, map everything
+ # == means map to same key name
+ # otherwise key on left becomes right
+ # in the future: key_transform: camelize, snake_case for automapping the keys
+ keys:
+ GITHUB_TOKEN: ==
+ a: ==
diff --git a/teller-cli/fixtures/scan/content.bin b/teller-cli/fixtures/scan/content.bin
new file mode 100644
index 00000000..bd812fcc
Binary files /dev/null and b/teller-cli/fixtures/scan/content.bin differ
diff --git a/teller-cli/fixtures/scan/has-matches.txt b/teller-cli/fixtures/scan/has-matches.txt
new file mode 100644
index 00000000..cac8fb16
--- /dev/null
+++ b/teller-cli/fixtures/scan/has-matches.txt
@@ -0,0 +1,5 @@
+hello world,
+
+this should match: trooper123.
+
+done.
diff --git a/teller-cli/fixtures/scan/multiple.txt b/teller-cli/fixtures/scan/multiple.txt
new file mode 100644
index 00000000..2652c63c
--- /dev/null
+++ b/teller-cli/fixtures/scan/multiple.txt
@@ -0,0 +1,6 @@
+all of these should match:
+ this: pass1
+ and that: pass1
+ and also,
+ pass1
+
\ No newline at end of file
diff --git a/teller-cli/fixtures/scan/nested/nested-match.txt b/teller-cli/fixtures/scan/nested/nested-match.txt
new file mode 100644
index 00000000..129cb721
--- /dev/null
+++ b/teller-cli/fixtures/scan/nested/nested-match.txt
@@ -0,0 +1,5 @@
+hello world,
+
+this should match: nested111.
+
+done.
diff --git a/teller-cli/fixtures/scan/no-matches.txt b/teller-cli/fixtures/scan/no-matches.txt
new file mode 100644
index 00000000..e5f1ae98
--- /dev/null
+++ b/teller-cli/fixtures/scan/no-matches.txt
@@ -0,0 +1 @@
+this should not match
diff --git a/teller-cli/fixtures/smoke.yml b/teller-cli/fixtures/smoke.yml
new file mode 100644
index 00000000..b6fc32fd
--- /dev/null
+++ b/teller-cli/fixtures/smoke.yml
@@ -0,0 +1,22 @@
+providers:
+ ssm_1:
+ kind: ssm
+ maps:
+ - id: ssm-prod
+ path: /ssm/prod
+ keys:
+ GITHUB_TOKEN: ==
+ mg: FOO_BAR
+ hashi_1:
+ kind: hashicorp
+ maps:
+ - id: hashi-stg
+ path: /hashi/staging
+ keys:
+ GITHUB_TOKEN: ==
+ mg: FOO_BAR
+ dot_1:
+ kind: dotenv # name is = kind if empty
+ maps:
+ - id: dotvars
+ path: fixtures/vars.env
diff --git a/teller-cli/fixtures/vars.env b/teller-cli/fixtures/vars.env
new file mode 100644
index 00000000..35d61703
--- /dev/null
+++ b/teller-cli/fixtures/vars.env
@@ -0,0 +1,2 @@
+HELLO=world
+foo=bar
diff --git a/teller-cli/src/bin/teller.rs b/teller-cli/src/bin/teller.rs
new file mode 100644
index 00000000..eab7883d
--- /dev/null
+++ b/teller-cli/src/bin/teller.rs
@@ -0,0 +1,19 @@
+use std::process::exit;
+
+use clap::Parser;
+use eyre::Result;
+use teller_cli::{cli, tracing};
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let args = cli::Cli::parse();
+
+ tracing(args.verbose);
+
+ let resp = cli::run(&args).await?;
+
+ if let Some(msg) = resp.message {
+ println!("{msg}");
+ }
+ exit(resp.code);
+}
diff --git a/teller-cli/src/cli.rs b/teller-cli/src/cli.rs
new file mode 100644
index 00000000..5ad042f6
--- /dev/null
+++ b/teller-cli/src/cli.rs
@@ -0,0 +1,334 @@
+use std::path::{Path, PathBuf};
+
+use clap::{Args, Parser, Subcommand, ValueEnum};
+use eyre::eyre;
+use teller_core::{exec, export, teller::Teller};
+use teller_providers::{config::KV, providers::ProviderKind};
+
+use crate::{
+ io::{self, or_stdin, or_stdout},
+ new, scan, Response,
+};
+
+#[derive(Debug, Clone, Parser)] // requires `derive` feature
+#[command(name = "teller")]
+#[command(about = "A multi provider secret management tool", long_about = None)]
+pub struct Cli {
+ /// Path to your teller.yml config
+ #[arg(short, long)]
+ pub config: Option,
+
+ /// Path to your teller.yml config
+ #[arg(long)]
+ pub verbose: bool,
+
+ /// A teller command
+ #[command(subcommand)]
+ pub command: Commands,
+}
+#[derive(Debug, Clone, Subcommand)]
+pub enum Commands {
+ /// Run a command
+ Run {
+ /// Reset environment variables before running
+ #[arg(short, long)]
+ reset: bool,
+ /// Run command as shell command
+ #[arg(short, long)]
+ shell: bool,
+ /// The command to run
+ #[arg(value_name = "COMMAND", raw = true)]
+ command: Vec,
+ },
+
+ /// Scan files
+ Scan(ScanArgs),
+ /// Export key-secret pairs to a specified format
+ Export {
+ /// The format to export to
+ #[arg(value_enum, index = 1)]
+ format: Format,
+ },
+ /// Redact text using fetched secrets
+ Redact {
+ /// Input file (stdin if none given)
+ #[arg(name = "in", short, long)]
+ in_file: Option,
+ /// Output file (stdout if none given)
+ #[arg(short, long)]
+ out: Option,
+ },
+
+ /// Render a key-value aware template
+ Template {
+ /// Input template (stdin if none given)
+ #[arg(name = "in", short, long)]
+ in_file: Option,
+ /// Output destination (stdout if none given)
+ #[arg(short, long)]
+ out: Option,
+ },
+
+ /// Export compatible with ENV
+ Env {},
+
+ /// Print all currently accessible data
+ Show {},
+
+ /// Export as source-able shell script
+ Sh {},
+
+ /// Create a new Teller configuration
+ New(NewArgs),
+
+ /// Put new key-values onto a list of providers on a specified path
+ Put {
+ #[arg(long, short)]
+ map_id: String,
+
+ #[arg(long, value_delimiter = ',')]
+ providers: Vec,
+
+ #[clap(value_parser = parse_key_val::)]
+ kvs: Vec<(String, String)>,
+ },
+
+ /// Delete specific keys or complete paths
+ Delete {
+ #[arg(long, short)]
+ map_id: String,
+
+ #[arg(long, value_delimiter = ',')]
+ providers: Vec,
+
+ keys: Vec,
+ },
+ Copy {
+ #[arg(long, short)]
+ from: String,
+
+ #[arg(long, short, value_delimiter = ',')]
+ to: Vec,
+
+ #[arg(long, short)]
+ replace: bool,
+ },
+}
+
+fn parse_key_val(
+ s: &str,
+) -> std::result::Result<(T, U), Box>
+where
+ T: std::str::FromStr,
+ T::Err: std::error::Error + Send + Sync + 'static,
+ U: std::str::FromStr,
+ U::Err: std::error::Error + Send + Sync + 'static,
+{
+ let pos = s
+ .find('=')
+ .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
+ Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+pub enum Format {
+ /// Export as CSV
+ CSV,
+ /// Export as YAML
+ YAML,
+ /// Export as JSON
+ JSON,
+ /// Export as env variables
+ ENV,
+}
+
+#[allow(clippy::struct_excessive_bools)]
+#[derive(Debug, Clone, Args)] // requires `derive` feature
+pub struct ScanArgs {
+ /// Root folder to scan recursively
+ #[arg(short, long, default_value = ".")]
+ pub root: String,
+ /// Include hidden and ignored files
+ #[arg(short, long)]
+ pub all: bool,
+ /// Returns exit code 1 if has finding
+ #[arg(long)]
+ pub error_if_found: bool,
+ /// Include binary files
+ #[arg(short, long)]
+ pub binary: bool,
+ /// Output matches as JSON
+ #[arg(short, long)]
+ pub json: bool,
+}
+
+const DEFAULT_FILE_PATH: &str = ".teller.yml";
+
+#[derive(Debug, Clone, Args)]
+pub struct NewArgs {
+ /// Stuff to add
+ #[arg(short, long, conflicts_with = "std", default_value=DEFAULT_FILE_PATH)]
+ pub filename: PathBuf,
+
+ /// Print configuration to the STDOUT
+ #[arg(long)]
+ pub std: bool,
+
+ /// Force teller configuration file if exists
+ #[arg(long)]
+ pub force: bool,
+
+ #[arg(long, value_delimiter = ',')]
+ pub providers: Vec,
+}
+
+async fn load_teller(config: Option) -> eyre::Result {
+ let config_arg = config.unwrap_or_else(|| "teller.yml".to_string());
+ let config_path = Path::new(&config_arg);
+ let teller = Teller::from_yaml(config_path).await?;
+ Ok(teller)
+}
+
+/// Run the CLI logic
+///
+/// # Errors
+///
+/// This function will return an error if operation fails
+#[allow(clippy::future_not_send)]
+#[allow(clippy::too_many_lines)]
+pub async fn run(args: &Cli) -> eyre::Result {
+ match args.command.clone() {
+ Commands::Run {
+ reset,
+ shell,
+ command,
+ } => {
+ let teller = load_teller(args.config.clone()).await?;
+ let pwd = std::env::current_dir()?;
+ let opts = exec::Opts {
+ pwd: pwd.as_path(),
+ sh: shell,
+ reset_env: reset,
+ capture: false,
+ };
+ teller
+ .run(
+ command
+ .iter()
+ .map(String::as_str)
+ .collect::>()
+ .as_slice(),
+ &opts,
+ )
+ .await?;
+ Response::ok()
+ }
+ Commands::Scan(cmdargs) => {
+ let teller = load_teller(args.config.clone()).await?;
+ scan::run(&teller, &cmdargs).await
+ }
+ Commands::Export { format } => {
+ let teller_format = match format {
+ Format::CSV => export::Format::CSV,
+ Format::YAML => export::Format::YAML,
+ Format::JSON => export::Format::JSON,
+ Format::ENV => export::Format::ENV,
+ };
+ let teller = load_teller(args.config.clone()).await?;
+ let out = teller.export(&teller_format).await?;
+ Response::ok_with_message(out)
+ }
+ Commands::Redact { in_file, out } => {
+ let teller = load_teller(args.config.clone()).await?;
+ teller
+ .redact(&mut or_stdin(in_file)?, &mut or_stdout(out)?)
+ .await?;
+ Response::ok()
+ }
+ Commands::Template { in_file, out } => {
+ let mut input = String::new();
+ or_stdin(in_file)?.read_to_string(&mut input)?;
+ let teller = load_teller(args.config.clone()).await?;
+ let rendered = teller.template(&input).await?;
+ let mut out = or_stdout(out)?;
+ out.write_all(rendered.as_bytes())?;
+ out.flush()?;
+ Response::ok()
+ }
+ Commands::Env {} => {
+ let teller = load_teller(args.config.clone()).await?;
+ let out = teller.export(&export::Format::ENV).await?;
+ Response::ok_with_message(out)
+ }
+ Commands::New(new_args) => new::run(&new_args),
+ Commands::Show {} => {
+ let teller = load_teller(args.config.clone()).await?;
+ let kvs = teller.collect().await?;
+ io::print_kvs(&kvs);
+ Response::ok()
+ }
+ Commands::Sh {} => {
+ let teller = load_teller(args.config.clone()).await?;
+ let out = teller.export(&export::Format::Shell).await?;
+ Response::ok_with_message(out)
+ }
+ Commands::Put {
+ kvs,
+ map_id,
+ providers,
+ } => {
+ let kvs = kvs
+ .iter()
+ .map(|(k, v)| KV::from_kv(k, v))
+ .collect::>();
+ let teller = load_teller(args.config.clone()).await?;
+ teller
+ .put(kvs.as_slice(), map_id.as_str(), providers.as_slice())
+ .await?;
+ Response::ok()
+ }
+ Commands::Delete {
+ map_id,
+ providers,
+ keys,
+ } => {
+ let teller = load_teller(args.config.clone()).await?;
+ teller
+ .delete(keys.as_slice(), &map_id, providers.as_slice())
+ .await?;
+ Response::ok()
+ }
+ Commands::Copy { from, to, replace } => {
+ // a copy report should state how many keys were copied and to where.
+ // invent a new kvrl (key-value resource location) format: kvurl://dotenv/?meta
+ // / like server/resource-path
+ // ?path=varbatim/path/to/location request specific path overriding resource routing
+ //
+ // dotenv/map-id -> foo/map-id: copied 4 key(s).
+ // dotenv/map-id -> f/map-id: copied 4 key(s).
+ // copied 4 key(s) [in replace mode] from `dotenv:path-id` to `foo:path-id`, `bar:path-id`
+ let teller = load_teller(args.config.clone()).await?;
+ let (from_provider, from_map_id) = from.split_once('/').ok_or_else(|| {
+ eyre!(
+ "cannot parse '--from': '{}', did you format it as: '/' ?",
+ from
+ )
+ })?;
+ for to_provider in to {
+ let (to_provider, to_map_id) = to_provider.split_once('/').ok_or_else(|| {
+ eyre!(
+ "cannot parse '--to': '{}', did you format it as: '/' ?",
+ to_provider
+ )
+ })?;
+ teller
+ .copy(from_provider, from_map_id, to_provider, to_map_id, replace)
+ .await?;
+ }
+
+ Response::ok()
+ }
+ }
+}
diff --git a/teller-cli/src/io.rs b/teller-cli/src/io.rs
new file mode 100644
index 00000000..8a46050d
--- /dev/null
+++ b/teller-cli/src/io.rs
@@ -0,0 +1,45 @@
+use std::io::{self, BufRead, BufReader, BufWriter, Write};
+
+use eyre::Result;
+use fs_err::File;
+use teller_providers::config::KV;
+
+/// Read from a file or stdin
+///
+/// # Errors
+///
+/// This function will return an error if IO fails
+pub fn or_stdin(file: Option) -> Result> {
+ let out: Box = file.map_or_else(
+ || Ok(Box::new(BufReader::new(io::stdin())) as Box),
+ |file_path| File::open(file_path).map(|f| Box::new(BufReader::new(f)) as Box),
+ )?;
+
+ Ok(out)
+}
+
+/// Write to a file or stdout
+///
+/// # Errors
+///
+/// This function will return an error if IO fails
+pub fn or_stdout(file: Option) -> Result> {
+ let out = file.map_or_else(
+ || Ok(Box::new(BufWriter::new(std::io::stdout())) as Box),
+ |file_path| File::open(file_path).map(|f| Box::new(BufWriter::new(f)) as Box),
+ )?;
+ Ok(out)
+}
+
+pub fn print_kvs(kvs: &[KV]) {
+ for kv in kvs {
+ println!(
+ "[{}]: {} = {}***",
+ kv.provider
+ .as_ref()
+ .map_or_else(|| "n/a".to_string(), |p| format!("{} ({})", p.name, p.kind)),
+ kv.key,
+ kv.value.get(0..2).unwrap_or_default()
+ );
+ }
+}
diff --git a/teller-cli/src/lib.rs b/teller-cli/src/lib.rs
new file mode 100644
index 00000000..ad1ff388
--- /dev/null
+++ b/teller-cli/src/lib.rs
@@ -0,0 +1,57 @@
+pub mod cli;
+pub mod io;
+pub mod new;
+pub mod scan;
+pub mod wizard;
+use eyre::Result;
+use tracing::level_filters::LevelFilter;
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
+#[allow(clippy::module_name_repetitions)]
+pub struct Response {
+ pub code: exitcode::ExitCode,
+ pub message: Option,
+}
+impl Response {
+ #[allow(clippy::missing_const_for_fn)]
+ #[allow(clippy::unnecessary_wraps)]
+ fn fail() -> Result {
+ Ok(Self {
+ code: 1,
+ message: None,
+ })
+ }
+ #[allow(clippy::missing_const_for_fn)]
+ #[allow(clippy::unnecessary_wraps)]
+ fn ok() -> Result {
+ Ok(Self {
+ code: exitcode::OK,
+ message: None,
+ })
+ }
+
+ #[allow(clippy::missing_const_for_fn)]
+ #[allow(clippy::unnecessary_wraps)]
+ fn ok_with_message(message: String) -> Result {
+ Ok(Self {
+ code: exitcode::OK,
+ message: Some(message),
+ })
+ }
+}
+
+pub fn tracing(verbose: bool) {
+ let level = if verbose {
+ LevelFilter::INFO
+ } else {
+ LevelFilter::OFF
+ };
+ Registry::default()
+ .with(tracing_tree::HierarchicalLayer::new(2))
+ .with(
+ EnvFilter::builder()
+ .with_default_directive(level.into())
+ .with_env_var("LOG")
+ .from_env_lossy(),
+ )
+ .init();
+}
diff --git a/teller-cli/src/new.rs b/teller-cli/src/new.rs
new file mode 100644
index 00000000..92b15e6b
--- /dev/null
+++ b/teller-cli/src/new.rs
@@ -0,0 +1,70 @@
+use std::fs;
+
+use eyre::Result;
+use teller_core::config::{Config, RenderTemplate};
+use teller_providers::providers;
+
+use super::Response;
+use crate::{cli::NewArgs, wizard};
+
+pub const CMD_NAME: &str = "new";
+
+/// Create a new teller configuration
+///
+/// # Errors
+///
+/// This function will return an error if operation fails
+#[allow(clippy::future_not_send)]
+pub fn run(args: &NewArgs) -> Result {
+ let providers: Vec = args.providers.clone();
+
+ let file = {
+ let mut file_path = args.filename.clone();
+ let ext = file_path
+ .extension()
+ .and_then(std::ffi::OsStr::to_str)
+ .unwrap_or("");
+
+ if ext != "yaml" || ext != "yml" {
+ file_path.set_extension("yml");
+ }
+
+ file_path
+ };
+
+ let w = {
+ let mut wizard = wizard::AppConfig::new(args.force);
+
+ if !args.std {
+ wizard.with_file_validation(file.as_path());
+ }
+
+ if !providers.is_empty() {
+ wizard.with_providers(providers);
+ }
+ wizard
+ };
+ let results = match w.start() {
+ Ok(r) => r,
+ Err(e) => match e {
+ wizard::Error::ProviderNotFound(_)
+ | wizard::Error::Prompt(_)
+ | wizard::Error::InvalidSelection => return Err(eyre::Error::new(e)),
+ wizard::Error::ConfigurationAlreadyExists => return Response::ok(),
+ },
+ };
+
+ let template = Config::render_template(&RenderTemplate {
+ providers: results.providers,
+ })?;
+
+ if args.std {
+ Response::ok_with_message(template)
+ } else {
+ if let Some(folder) = &file.parent() {
+ fs::create_dir_all(folder)?;
+ }
+ fs::write(&file, template)?;
+ Response::ok_with_message(format!("Configuration saved in: {:?}", file.display()))
+ }
+}
diff --git a/teller-cli/src/scan.rs b/teller-cli/src/scan.rs
new file mode 100644
index 00000000..889060b6
--- /dev/null
+++ b/teller-cli/src/scan.rs
@@ -0,0 +1,63 @@
+use comfy_table::presets::NOTHING;
+use comfy_table::{Cell, Table};
+use eyre::Result;
+use teller_core::{scan, teller::Teller};
+
+use crate::cli::ScanArgs;
+use crate::Response;
+
+fn hide_chars(s: &str) -> String {
+ let mut result = String::new();
+ let chars_to_display = s.chars().take(2).collect::();
+ let asterisks = "*".repeat(3);
+ result.push_str(&chars_to_display);
+ result.push_str(&asterisks);
+ result
+}
+
+/// Scan a folder for secrets fetched from providers
+///
+/// # Errors
+///
+/// This function will return an error if the operation fails
+#[allow(clippy::future_not_send)]
+pub async fn run(teller: &Teller, args: &ScanArgs) -> Result {
+ let opts = scan::Opts {
+ include_all: args.all,
+ include_binary: args.binary,
+ };
+
+ let kvs = teller.collect().await?;
+ let res = teller.scan(&args.root, &kvs, &opts)?;
+ let count = res.len();
+ eprintln!("scanning for {} item(s) in {}", kvs.len(), args.root);
+ if args.json {
+ println!("{}", serde_json::to_string_pretty(&res)?);
+ } else {
+ let mut table = Table::new();
+ table.load_preset(NOTHING);
+ for m in res {
+ let pos = m.position.unwrap_or((0, 0));
+ table.add_row(vec![
+ Cell::new(format!("{}:{}", pos.0, pos.1)),
+ Cell::new(m.path.to_string_lossy()),
+ Cell::new(hide_chars(&m.query.value)),
+ Cell::new(
+ m.query
+ .provider
+ .map_or_else(|| "n/a".to_string(), |p| p.kind.to_string())
+ .to_string(),
+ ),
+ Cell::new(m.query.path.map_or_else(|| "n/a".to_string(), |p| p.path)),
+ ]);
+ }
+ println!("{table}");
+ }
+ eprintln!("found {count} result(s)");
+
+ if args.error_if_found && count > 0 {
+ Response::fail()
+ } else {
+ Response::ok()
+ }
+}
diff --git a/teller-cli/src/wizard.rs b/teller-cli/src/wizard.rs
new file mode 100644
index 00000000..e0166ebd
--- /dev/null
+++ b/teller-cli/src/wizard.rs
@@ -0,0 +1,121 @@
+use std::collections::HashMap;
+use std::path::{Path, PathBuf};
+
+use dialoguer::{theme::ColorfulTheme, Confirm, MultiSelect};
+use strum::IntoEnumIterator;
+use teller_providers::providers::ProviderKind;
+
+pub type Result = std::result::Result;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ #[error("Provider: {0} not exists")]
+ ProviderNotFound(String),
+
+ #[error(transparent)]
+ Prompt(#[from] dialoguer::Error),
+
+ #[error("Config file already exists")]
+ ConfigurationAlreadyExists,
+
+ #[error("Invalid prompt selection")]
+ InvalidSelection,
+}
+
+pub struct AppConfig {
+ file_path: Option,
+ providers: Option>,
+ pub override_file: bool,
+}
+
+pub struct Results {
+ pub providers: Vec,
+}
+
+/// Creating wizard flow for crating Teller configuration file
+impl AppConfig {
+ #[must_use]
+ pub const fn new(override_file: bool) -> Self {
+ Self {
+ file_path: None,
+ providers: None,
+ override_file,
+ }
+ }
+
+ pub fn with_file_validation(&mut self, file_path: &Path) -> &mut Self {
+ self.file_path = Some(file_path.to_path_buf());
+ self
+ }
+
+ pub fn with_providers(&mut self, providers: Vec) -> &mut Self {
+ self.providers = Some(providers);
+ self
+ }
+
+ /// Start wizard flow
+ ///
+ /// # Errors
+ /// this function return an errors when from `Error` options
+ pub fn start(&self) -> Result {
+ if let Some(file_path) = &self.file_path {
+ if file_path.exists()
+ && !self.override_file
+ && !Self::confirm_override_file(file_path.as_path())?
+ {
+ return Err(Error::ConfigurationAlreadyExists {});
+ }
+ }
+
+ let providers = match &self.providers {
+ Some(providers) => providers.clone(),
+ None => Self::select_providers()?,
+ };
+ Ok(Results { providers })
+ }
+
+ fn confirm_override_file(file_path: &Path) -> Result {
+ Ok(Confirm::with_theme(&ColorfulTheme::default())
+ .with_prompt(format!(
+ "Teller config {:?} already exists. Do you want to override the configuration \
+ with new settings",
+ file_path.display()
+ ))
+ .interact()?)
+ }
+
+ /// Prompt provider selection
+ ///
+ /// # Errors
+ /// When has a problem with prompt selection
+ fn select_providers() -> Result> {
+ let providers = ProviderKind::iter()
+ .map(|provider| (provider.to_string(), provider))
+ .collect::>();
+
+ let names = &providers
+ .keys()
+ .map(std::string::String::as_str)
+ .collect::>();
+
+ let selected_providers = MultiSelect::with_theme(&ColorfulTheme::default())
+ .with_prompt("Select your secret providers")
+ .items(names)
+ .report(false)
+ .interact()?;
+
+ let mut selected = vec![];
+ for selection in selected_providers {
+ let Some(provider_name) = names.get(selection) else {
+ return Err(Error::InvalidSelection);
+ };
+
+ match providers.get(*provider_name) {
+ Some(p) => selected.push(p.clone()),
+ _ => return Err(Error::ProviderNotFound((*provider_name).to_string())),
+ };
+ }
+
+ Ok(selected)
+ }
+}
diff --git a/teller-cli/tests/cli_tests.rs b/teller-cli/tests/cli_tests.rs
new file mode 100644
index 00000000..be10b16f
--- /dev/null
+++ b/teller-cli/tests/cli_tests.rs
@@ -0,0 +1,25 @@
+use std::fs;
+
+fn prep_data_for_mutating_tests() {
+ fs::write("tests/cmd/put.in/new.env", "EMPTY=true\n").expect("writing a fixture file");
+ fs::write(
+ "tests/cmd/delete.in/new.env",
+ "EMPTY=true\nDELETE_ME=true\n",
+ )
+ .expect("writing a fixture file");
+ fs::write("tests/cmd/copy.in/target.env", "TARGET_ONLY=true\n")
+ .expect("writing a fixture file");
+}
+#[test]
+fn cli_tests() {
+ fs::write("tests/cmd/scan.in/git-hidden-file", "happy").expect("writing a fixture file");
+ prep_data_for_mutating_tests();
+
+ let c = trycmd::TestCases::new();
+ c.case("tests/cmd/*.trycmd");
+ #[cfg(windows)]
+ c.skip("tests/cmd/run.trycmd");
+
+ c.run();
+ prep_data_for_mutating_tests();
+}
diff --git a/teller-cli/tests/cmd/copy.in/source.env b/teller-cli/tests/cmd/copy.in/source.env
new file mode 100644
index 00000000..242f94a2
--- /dev/null
+++ b/teller-cli/tests/cmd/copy.in/source.env
@@ -0,0 +1,2 @@
+EMPTY=true
+DEV_DB=magic
diff --git a/teller-cli/tests/cmd/copy.in/target.env b/teller-cli/tests/cmd/copy.in/target.env
new file mode 100644
index 00000000..80f6149b
--- /dev/null
+++ b/teller-cli/tests/cmd/copy.in/target.env
@@ -0,0 +1 @@
+TARGET_ONLY=true
diff --git a/teller-cli/tests/cmd/copy.in/teller.yml b/teller-cli/tests/cmd/copy.in/teller.yml
new file mode 100644
index 00000000..d2799f24
--- /dev/null
+++ b/teller-cli/tests/cmd/copy.in/teller.yml
@@ -0,0 +1,11 @@
+providers:
+ source:
+ kind: dotenv
+ maps:
+ - id: dev
+ path: source.env
+ target:
+ kind: dotenv
+ maps:
+ - id: prod
+ path: target.env
diff --git a/teller-cli/tests/cmd/copy.trycmd b/teller-cli/tests/cmd/copy.trycmd
new file mode 100644
index 00000000..f276706e
--- /dev/null
+++ b/teller-cli/tests/cmd/copy.trycmd
@@ -0,0 +1,24 @@
+```console
+$ teller show
+[source (dotenv)]: DEV_DB = ma***
+[source (dotenv)]: EMPTY = tr***
+[target (dotenv)]: TARGET_ONLY = tr***
+
+$ teller copy --from source/dev --to target/prod
+
+$ teller show
+[source (dotenv)]: DEV_DB = ma***
+[source (dotenv)]: EMPTY = tr***
+[target (dotenv)]: DEV_DB = ma***
+[target (dotenv)]: EMPTY = tr***
+[target (dotenv)]: TARGET_ONLY = tr***
+
+$ teller copy --from source/dev --to target/prod --replace
+
+$ teller show
+[source (dotenv)]: DEV_DB = ma***
+[source (dotenv)]: EMPTY = tr***
+[target (dotenv)]: DEV_DB = ma***
+[target (dotenv)]: EMPTY = tr***
+
+```
diff --git a/teller-cli/tests/cmd/delete.in/new.env b/teller-cli/tests/cmd/delete.in/new.env
new file mode 100644
index 00000000..af0a9111
--- /dev/null
+++ b/teller-cli/tests/cmd/delete.in/new.env
@@ -0,0 +1,2 @@
+EMPTY=true
+DELETE_ME=true
diff --git a/teller-cli/tests/cmd/delete.in/teller.yml b/teller-cli/tests/cmd/delete.in/teller.yml
new file mode 100644
index 00000000..e02abce0
--- /dev/null
+++ b/teller-cli/tests/cmd/delete.in/teller.yml
@@ -0,0 +1,6 @@
+providers:
+ new:
+ kind: dotenv
+ maps:
+ - id: one
+ path: new.env
diff --git a/teller-cli/tests/cmd/delete.trycmd b/teller-cli/tests/cmd/delete.trycmd
new file mode 100644
index 00000000..b0a140da
--- /dev/null
+++ b/teller-cli/tests/cmd/delete.trycmd
@@ -0,0 +1,20 @@
+```console
+$ teller show
+[new (dotenv)]: DELETE_ME = tr***
+[new (dotenv)]: EMPTY = tr***
+
+$ teller delete --providers new --map-id one DELETE_ME
+
+$ teller show
+[new (dotenv)]: EMPTY = tr***
+
+$ teller delete --providers new --map-id one
+
+$ teller show
+? 1
+Error: NOT FOUND "new.env": file is empty
+
+Location:
+ [..]
+
+```
diff --git a/teller-cli/tests/cmd/export.in/one.env b/teller-cli/tests/cmd/export.in/one.env
new file mode 100644
index 00000000..96d17208
--- /dev/null
+++ b/teller-cli/tests/cmd/export.in/one.env
@@ -0,0 +1,2 @@
+PRINT_NAME=linus
+FOO_BAR=foo
diff --git a/teller-cli/tests/cmd/export.in/teller.yml b/teller-cli/tests/cmd/export.in/teller.yml
new file mode 100644
index 00000000..719d24a0
--- /dev/null
+++ b/teller-cli/tests/cmd/export.in/teller.yml
@@ -0,0 +1,11 @@
+providers:
+ dot1:
+ kind: dotenv
+ maps:
+ - id: one
+ path: one.env
+ dot2:
+ kind: dotenv
+ maps:
+ - id: two
+ path: two.env
diff --git a/teller-cli/tests/cmd/export.in/two.env b/teller-cli/tests/cmd/export.in/two.env
new file mode 100644
index 00000000..18b198b4
--- /dev/null
+++ b/teller-cli/tests/cmd/export.in/two.env
@@ -0,0 +1,2 @@
+PRINT_MOOD=happy
+FOO_BAZ=baz
diff --git a/teller-cli/tests/cmd/export.trycmd b/teller-cli/tests/cmd/export.trycmd
new file mode 100644
index 00000000..6190e5b6
--- /dev/null
+++ b/teller-cli/tests/cmd/export.trycmd
@@ -0,0 +1,41 @@
+```console
+$ teller -c teller.yml export yaml
+FOO_BAR: foo
+FOO_BAZ: baz
+PRINT_MOOD: happy
+PRINT_NAME: linus
+
+
+$ teller -c teller.yml export csv
+FOO_BAR,foo
+PRINT_NAME,linus
+FOO_BAZ,baz
+PRINT_MOOD,happy
+
+
+$ teller -c teller.yml export json
+{"FOO_BAR":"foo","FOO_BAZ":"baz","PRINT_MOOD":"happy","PRINT_NAME":"linus"}
+
+$ teller -c teller.yml export env
+FOO_BAR=foo
+PRINT_NAME=linus
+FOO_BAZ=baz
+PRINT_MOOD=happy
+
+
+$ teller -c teller.yml sh
+#!/bin/sh
+export FOO_BAR='foo'
+export PRINT_NAME='linus'
+export FOO_BAZ='baz'
+export PRINT_MOOD='happy'
+
+
+$ teller -c teller.yml env
+FOO_BAR=foo
+PRINT_NAME=linus
+FOO_BAZ=baz
+PRINT_MOOD=happy
+
+
+```
diff --git a/teller-cli/tests/cmd/new.trycmd b/teller-cli/tests/cmd/new.trycmd
new file mode 100644
index 00000000..48d75e76
--- /dev/null
+++ b/teller-cli/tests/cmd/new.trycmd
@@ -0,0 +1,19 @@
+```console
+$ teller new --providers inmem,dotenv --force --filename tmp/.test.teller.yml
+Configuration saved in: "tmp/.test.teller.yml"
+
+$ teller new --providers inmem,dotenv --std
+providers:
+ dotenv_1:
+ kind: dotenv
+ maps:
+ - id: ''
+ path: example/dev
+ inmem_1:
+ kind: inmem
+ maps:
+ - id: ''
+ path: example/dev
+
+
+```
diff --git a/teller-cli/tests/cmd/put.in/new.env b/teller-cli/tests/cmd/put.in/new.env
new file mode 100644
index 00000000..0d685322
--- /dev/null
+++ b/teller-cli/tests/cmd/put.in/new.env
@@ -0,0 +1 @@
+EMPTY=true
diff --git a/teller-cli/tests/cmd/put.in/teller.yml b/teller-cli/tests/cmd/put.in/teller.yml
new file mode 100644
index 00000000..e02abce0
--- /dev/null
+++ b/teller-cli/tests/cmd/put.in/teller.yml
@@ -0,0 +1,6 @@
+providers:
+ new:
+ kind: dotenv
+ maps:
+ - id: one
+ path: new.env
diff --git a/teller-cli/tests/cmd/put.trycmd b/teller-cli/tests/cmd/put.trycmd
new file mode 100644
index 00000000..2b150718
--- /dev/null
+++ b/teller-cli/tests/cmd/put.trycmd
@@ -0,0 +1,11 @@
+```console
+$ teller show
+[new (dotenv)]: EMPTY = tr***
+
+$ teller put --providers new --map-id one NEW_VAR=s33kret
+
+$ teller show
+[new (dotenv)]: EMPTY = tr***
+[new (dotenv)]: NEW_VAR = s3***
+
+```
diff --git a/teller-cli/tests/cmd/run.in/index.js b/teller-cli/tests/cmd/run.in/index.js
new file mode 100644
index 00000000..3be8e3c4
--- /dev/null
+++ b/teller-cli/tests/cmd/run.in/index.js
@@ -0,0 +1,6 @@
+
+console.log('hello from nodejs')
+console.log(process.env['PRINT_NAME'])
+console.log(process.env['PRINT_MOOD'])
+console.log(process.env['FOO_BAR'])
+console.log(process.env['FOO_BAZ'])
diff --git a/teller-cli/tests/cmd/run.in/one.env b/teller-cli/tests/cmd/run.in/one.env
new file mode 100644
index 00000000..96d17208
--- /dev/null
+++ b/teller-cli/tests/cmd/run.in/one.env
@@ -0,0 +1,2 @@
+PRINT_NAME=linus
+FOO_BAR=foo
diff --git a/teller-cli/tests/cmd/run.in/teller.yml b/teller-cli/tests/cmd/run.in/teller.yml
new file mode 100644
index 00000000..719d24a0
--- /dev/null
+++ b/teller-cli/tests/cmd/run.in/teller.yml
@@ -0,0 +1,11 @@
+providers:
+ dot1:
+ kind: dotenv
+ maps:
+ - id: one
+ path: one.env
+ dot2:
+ kind: dotenv
+ maps:
+ - id: two
+ path: two.env
diff --git a/teller-cli/tests/cmd/run.in/two.env b/teller-cli/tests/cmd/run.in/two.env
new file mode 100644
index 00000000..18b198b4
--- /dev/null
+++ b/teller-cli/tests/cmd/run.in/two.env
@@ -0,0 +1,2 @@
+PRINT_MOOD=happy
+FOO_BAZ=baz
diff --git a/teller-cli/tests/cmd/run.trycmd b/teller-cli/tests/cmd/run.trycmd
new file mode 100644
index 00000000..cbfabddb
--- /dev/null
+++ b/teller-cli/tests/cmd/run.trycmd
@@ -0,0 +1,9 @@
+```console
+$ teller -c teller.yml run --reset --shell -- node index.js
+hello from nodejs
+linus
+happy
+foo
+baz
+
+```
diff --git a/teller-cli/tests/cmd/scan.in/content.bin b/teller-cli/tests/cmd/scan.in/content.bin
new file mode 100644
index 00000000..995f844c
Binary files /dev/null and b/teller-cli/tests/cmd/scan.in/content.bin differ
diff --git a/teller-cli/tests/cmd/scan.in/one.env b/teller-cli/tests/cmd/scan.in/one.env
new file mode 100644
index 00000000..96d17208
--- /dev/null
+++ b/teller-cli/tests/cmd/scan.in/one.env
@@ -0,0 +1,2 @@
+PRINT_NAME=linus
+FOO_BAR=foo
diff --git a/teller-cli/tests/cmd/scan.in/teller.yml b/teller-cli/tests/cmd/scan.in/teller.yml
new file mode 100644
index 00000000..719d24a0
--- /dev/null
+++ b/teller-cli/tests/cmd/scan.in/teller.yml
@@ -0,0 +1,11 @@
+providers:
+ dot1:
+ kind: dotenv
+ maps:
+ - id: one
+ path: one.env
+ dot2:
+ kind: dotenv
+ maps:
+ - id: two
+ path: two.env
diff --git a/teller-cli/tests/cmd/scan.in/two.env b/teller-cli/tests/cmd/scan.in/two.env
new file mode 100644
index 00000000..18b198b4
--- /dev/null
+++ b/teller-cli/tests/cmd/scan.in/two.env
@@ -0,0 +1,2 @@
+PRINT_MOOD=happy
+FOO_BAZ=baz
diff --git a/teller-cli/tests/cmd/scan.trycmd b/teller-cli/tests/cmd/scan.trycmd
new file mode 100644
index 00000000..8d471e7f
--- /dev/null
+++ b/teller-cli/tests/cmd/scan.trycmd
@@ -0,0 +1,28 @@
+```console
+$ teller -c teller.yml scan
+scanning for 4 item(s) in .
+ 2:9 ./one.env fo*** dotenv one.env
+ 2:9 ./two.env ba*** dotenv two.env
+ 1:12 ./two.env ha*** dotenv two.env
+ 1:12 ./one.env li*** dotenv one.env
+found 4 result(s)
+
+$ teller -c teller.yml scan -a
+scanning for 4 item(s) in .
+ 2:9 ./one.env fo*** dotenv one.env
+ 2:9 ./two.env ba*** dotenv two.env
+ 1:1 ./git-hidden-file ha*** dotenv two.env
+ 1:12 ./two.env ha*** dotenv two.env
+ 1:12 ./one.env li*** dotenv one.env
+found 5 result(s)
+
+$ teller -c teller.yml scan -b
+scanning for 4 item(s) in .
+ 2:9 ./one.env fo*** dotenv one.env
+ 2:9 ./two.env ba*** dotenv two.env
+ 2:1 ./content.bin ha*** dotenv two.env
+ 1:12 ./two.env ha*** dotenv two.env
+ 1:12 ./one.env li*** dotenv one.env
+found 5 result(s)
+
+```
diff --git a/teller-cli/tests/cmd/template.in/config-templ.t b/teller-cli/tests/cmd/template.in/config-templ.t
new file mode 100644
index 00000000..2e839adf
--- /dev/null
+++ b/teller-cli/tests/cmd/template.in/config-templ.t
@@ -0,0 +1,2 @@
+production_var: {{ key(name="PRINT_NAME")}}
+production_mood: {{ key(name="PRINT_MOOD")}}
diff --git a/teller-cli/tests/cmd/template.in/one.env b/teller-cli/tests/cmd/template.in/one.env
new file mode 100644
index 00000000..96d17208
--- /dev/null
+++ b/teller-cli/tests/cmd/template.in/one.env
@@ -0,0 +1,2 @@
+PRINT_NAME=linus
+FOO_BAR=foo
diff --git a/teller-cli/tests/cmd/template.in/teller.yml b/teller-cli/tests/cmd/template.in/teller.yml
new file mode 100644
index 00000000..719d24a0
--- /dev/null
+++ b/teller-cli/tests/cmd/template.in/teller.yml
@@ -0,0 +1,11 @@
+providers:
+ dot1:
+ kind: dotenv
+ maps:
+ - id: one
+ path: one.env
+ dot2:
+ kind: dotenv
+ maps:
+ - id: two
+ path: two.env
diff --git a/teller-cli/tests/cmd/template.in/two.env b/teller-cli/tests/cmd/template.in/two.env
new file mode 100644
index 00000000..18b198b4
--- /dev/null
+++ b/teller-cli/tests/cmd/template.in/two.env
@@ -0,0 +1,2 @@
+PRINT_MOOD=happy
+FOO_BAZ=baz
diff --git a/teller-cli/tests/cmd/template.trycmd b/teller-cli/tests/cmd/template.trycmd
new file mode 100644
index 00000000..7ba16b92
--- /dev/null
+++ b/teller-cli/tests/cmd/template.trycmd
@@ -0,0 +1,6 @@
+```console
+$ teller -c teller.yml template --in config-templ.t
+production_var: linus
+production_mood: happy
+
+```
diff --git a/teller-cli/tests/flow_test.rs b/teller-cli/tests/flow_test.rs
new file mode 100644
index 00000000..981c9b24
--- /dev/null
+++ b/teller-cli/tests/flow_test.rs
@@ -0,0 +1,145 @@
+use std::collections::HashMap;
+
+use dockertest_server::{
+ servers::{
+ cloud::{LocalStackServer, LocalStackServerConfig},
+ hashi::{VaultServer, VaultServerConfig},
+ },
+ Test,
+};
+use fs_err as fs;
+use insta::assert_debug_snapshot;
+use teller_core::config::Config;
+use teller_providers::{
+ config::{ProviderInfo, KV},
+ providers::ProviderKind,
+ registry::Registry,
+};
+
+fn build_providers() -> Test {
+ let mut test = Test::new();
+ test.register(
+ VaultServerConfig::builder()
+ .port(9200)
+ .version("1.8.2".into())
+ .build()
+ .unwrap(),
+ );
+ test.register(
+ LocalStackServerConfig::builder()
+ .env(
+ vec![(
+ "SERVICES".to_string(),
+ "iam,sts,ssm,kms,secretsmanager".to_string(),
+ )]
+ .into_iter()
+ .collect(),
+ )
+ .port(4561)
+ .version("2.0.2".into())
+ .build()
+ .unwrap(),
+ );
+ test
+}
+
+#[test]
+#[cfg(not(windows))]
+fn providers_smoke_test() {
+ use std::env;
+
+ if env::var("RUNNER_OS").unwrap_or_default() == "macOS" {
+ return;
+ }
+
+ let test = build_providers();
+
+ test.run(|instance| async move {
+ let user = "linus";
+ let password = "torvalds123";
+ let vault_server: VaultServer = instance.server();
+ let localstack_server: LocalStackServer = instance.server();
+
+ fs::write(
+ "fixtures/flow.env",
+ r"
+FOO=bar
+HELLO=world
+",
+ )
+ .unwrap();
+ let mut vars = HashMap::new();
+ vars.insert("address".to_string(), vault_server.external_url());
+ vars.insert("token".to_string(), vault_server.token);
+ vars.insert("endpoint_url".to_string(), localstack_server.external_url());
+
+ let config = Config::with_vars(
+ &fs::read_to_string("fixtures/flow_test.yml").unwrap(),
+ &vars,
+ )
+ .unwrap();
+ let registry = Registry::new(&config.providers).await.unwrap();
+
+ // (1) start: put on hashi
+ let hashi = registry.get("hashi_1").unwrap();
+ let hashi_pm0 = &config.providers.get("hashi_1").unwrap().maps[0];
+ hashi
+ .put(
+ hashi_pm0,
+ &[
+ KV::from_value(
+ user,
+ "USER",
+ "USER",
+ hashi_pm0,
+ ProviderInfo {
+ kind: ProviderKind::Hashicorp,
+ name: "test".to_string(),
+ },
+ ),
+ KV::from_value(
+ password,
+ "PASSWORD",
+ "PASSWORD",
+ hashi_pm0,
+ ProviderInfo {
+ kind: ProviderKind::Hashicorp,
+ name: "test".to_string(),
+ },
+ ),
+ ],
+ )
+ .await
+ .unwrap();
+ let res = hashi.get(hashi_pm0).await;
+ assert_debug_snapshot!("flow-test-hashi-0", res);
+
+ // (2) push results into secretsmanager
+ let kvs = res.unwrap();
+
+ let smgr = registry.get("sm_1").unwrap();
+ let smgr_pm0 = &config.providers.get("sm_1").unwrap().maps[0];
+ smgr.put(smgr_pm0, &kvs[..]).await.unwrap();
+ let res = smgr.get(smgr_pm0).await;
+ assert_debug_snapshot!("flow-test-smgr-0", res);
+
+ // (3) push results into ssm - remember it has custom key mapping so
+ // check that in snapshots (USER -> USER_NAME, and drops the pass)
+ let kvs = res.unwrap();
+
+ let ssm = registry.get("ssm_1").unwrap();
+ let ssm_pm0 = &config.providers.get("ssm_1").unwrap().maps[0];
+ ssm.put(ssm_pm0, &kvs[..]).await.unwrap();
+ let res = ssm.get(ssm_pm0).await;
+ assert_debug_snapshot!("flow-test-ssm-0", res);
+
+ // (4) lastly, write it into dotenv file, read it back, and snapshot it
+ let kvs = res.unwrap();
+
+ let dot = registry.get("dot_1").unwrap();
+ let dot_pm0 = &config.providers.get("dot_1").unwrap().maps[0];
+ dot.put(dot_pm0, &kvs[..]).await.unwrap();
+ let res = dot.get(dot_pm0).await;
+ assert_debug_snapshot!("flow-test-dot-0", res);
+ });
+}
diff --git a/teller-cli/tests/snapshots/flow_test__flow-test-dot-0.snap b/teller-cli/tests/snapshots/flow_test__flow-test-dot-0.snap
new file mode 100644
index 00000000..25f1859a
--- /dev/null
+++ b/teller-cli/tests/snapshots/flow_test__flow-test-dot-0.snap
@@ -0,0 +1,33 @@
+---
+source: teller-cli/tests/flow_test.rs
+expression: res
+---
+Ok(
+ [
+ KV {
+ value: "linus",
+ key: "USER_NAME",
+ from_key: "USER_NAME",
+ path: Some(
+ PathInfo {
+ id: "tvars",
+ path: "fixtures/flow_test_vars.env",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Dotenv,
+ name: "dot_1",
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ ],
+)
diff --git a/teller-cli/tests/snapshots/flow_test__flow-test-hashi-0.snap b/teller-cli/tests/snapshots/flow_test__flow-test-hashi-0.snap
new file mode 100644
index 00000000..0e76f0e3
--- /dev/null
+++ b/teller-cli/tests/snapshots/flow_test__flow-test-hashi-0.snap
@@ -0,0 +1,58 @@
+---
+source: teller-cli/tests/flow_test.rs
+expression: res
+---
+Ok(
+ [
+ KV {
+ value: "torvalds123",
+ key: "PASSWORD",
+ from_key: "PASSWORD",
+ path: Some(
+ PathInfo {
+ id: "pg-dev",
+ path: "secret/dev/postgres",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Hashicorp,
+ name: "hashi_1",
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ KV {
+ value: "linus",
+ key: "USER",
+ from_key: "USER",
+ path: Some(
+ PathInfo {
+ id: "pg-dev",
+ path: "secret/dev/postgres",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Hashicorp,
+ name: "hashi_1",
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ ],
+)
diff --git a/teller-cli/tests/snapshots/flow_test__flow-test-smgr-0.snap b/teller-cli/tests/snapshots/flow_test__flow-test-smgr-0.snap
new file mode 100644
index 00000000..959d9746
--- /dev/null
+++ b/teller-cli/tests/snapshots/flow_test__flow-test-smgr-0.snap
@@ -0,0 +1,58 @@
+---
+source: teller-cli/tests/flow_test.rs
+expression: res
+---
+Ok(
+ [
+ KV {
+ value: "torvalds123",
+ key: "PASSWORD",
+ from_key: "PASSWORD",
+ path: Some(
+ PathInfo {
+ id: "pg-dev",
+ path: "/dev/postgres",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: AWSSecretsManager,
+ name: "sm_1",
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ KV {
+ value: "linus",
+ key: "USER",
+ from_key: "USER",
+ path: Some(
+ PathInfo {
+ id: "pg-dev",
+ path: "/dev/postgres",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: AWSSecretsManager,
+ name: "sm_1",
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ ],
+)
diff --git a/teller-cli/tests/snapshots/flow_test__flow-test-ssm-0.snap b/teller-cli/tests/snapshots/flow_test__flow-test-ssm-0.snap
new file mode 100644
index 00000000..8d244a06
--- /dev/null
+++ b/teller-cli/tests/snapshots/flow_test__flow-test-ssm-0.snap
@@ -0,0 +1,33 @@
+---
+source: teller-cli/tests/flow_test.rs
+expression: res
+---
+Ok(
+ [
+ KV {
+ value: "linus",
+ key: "USER_NAME",
+ from_key: "USER",
+ path: Some(
+ PathInfo {
+ id: "pg-dev",
+ path: "/dev/postgres",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: SSM,
+ name: "ssm_1",
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ ],
+)
diff --git a/teller-core/.gitattributes b/teller-core/.gitattributes
new file mode 100644
index 00000000..5b61fd39
--- /dev/null
+++ b/teller-core/.gitattributes
@@ -0,0 +1 @@
+fixtures/* -text
diff --git a/teller-core/Cargo.toml b/teller-core/Cargo.toml
new file mode 100644
index 00000000..59171d2d
--- /dev/null
+++ b/teller-core/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "teller-core"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = { workspace = true }
+serde_json = { workspace = true }
+serde_yaml = { workspace = true }
+serde_derive = { workspace = true }
+serde_variant = { workspace = true }
+lazy_static = { workspace = true }
+strum = { workspace = true }
+shell-words = "1"
+duct = "0.13.6"
+thiserror = { workspace = true }
+fs-err = "2.9.0"
+ignore = "*"
+unicode-width = "*"
+aho-corasick = { workspace = true }
+tera = { workspace = true }
+csv = "1.2.1"
+teller-providers = { version = "*", path = "../teller-providers" }
+
+[dev-dependencies]
+insta = { workspace = true }
+stringreader = "*"
diff --git a/teller-core/fixtures/config.yml b/teller-core/fixtures/config.yml
new file mode 100644
index 00000000..dcaf5fc7
--- /dev/null
+++ b/teller-core/fixtures/config.yml
@@ -0,0 +1,18 @@
+providers:
+ hashi_1:
+ kind: hashicorp # name is = kind if empty
+ maps:
+ - id: test-load
+ path: /{{ get_env(name="TEST_LOAD_1", default="test") }}/users/user1
+ # if empty, map everything
+ # == means map to same key name
+ # otherwise key on left becomes right
+ # in the future: key_transform: camelize, snake_case for automapping the keys
+ keys:
+ GITHUB_TOKEN: ==
+ mg: FOO_BAR
+ dot_1:
+ kind: dotenv # name is = kind if empty
+ maps:
+ - id: stg
+ path: VAR_{{ get_env(name="STAGE", default="development") }}
diff --git a/teller-core/src/config.rs b/teller-core/src/config.rs
new file mode 100644
index 00000000..b2ff00d9
--- /dev/null
+++ b/teller-core/src/config.rs
@@ -0,0 +1,149 @@
+use std::cmp::Ordering;
+use std::path::PathBuf;
+use std::{
+ collections::{BTreeMap, HashMap},
+ path::Path,
+};
+
+use fs_err as fs;
+use serde_derive::{Deserialize, Serialize};
+use teller_providers::config::{PathMap, ProviderCfg, KV};
+use teller_providers::providers::ProviderKind;
+use tera::{Context, Tera};
+
+use crate::Result;
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct Config {
+ pub providers: BTreeMap,
+}
+
+#[derive(Serialize)]
+pub struct RenderTemplate {
+ pub providers: Vec,
+}
+
+fn apply_eqeq(config: &mut Config) {
+ config.providers.iter_mut().for_each(|(_name, provider)| {
+ provider.maps.iter_mut().for_each(|pm| {
+ pm.keys.iter_mut().for_each(|(k, v)| {
+ // THINK: replace with:
+ // 1. templating: {{id}} (identity), {{snake_case}} (snake case it)
+ // 2. other symbols: == id, ^^ capitalize, snake case __ lower snake case
+ if v == "==" {
+ v.clone_from(k);
+ }
+ });
+ });
+ });
+}
+
+impl Config {
+ /// Config from text
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if serialization fails
+ pub fn with_vars(text: &str, vars: &HashMap) -> Result {
+ let rendered_text = Tera::one_off(text, &Context::from_serialize(vars)?, false)?;
+ let mut config: Self = serde_yaml::from_str(&rendered_text)?;
+
+ apply_eqeq(&mut config);
+
+ Ok(config)
+ }
+
+ /// Config from text
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if serialization fails
+ pub fn from_text(text: &str) -> Result {
+ Self::with_vars(text, &HashMap::new())
+ }
+
+ /// Config from file
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if IO fails
+ pub fn from_path(path: &Path) -> Result {
+ Self::from_text(&fs::read_to_string(path)?)
+ }
+
+ /// Create configuration template file
+ ///
+ /// # Errors
+ /// When could not convert config to string
+ pub fn render_template(data: &RenderTemplate) -> Result {
+ let res: BTreeMap = data
+ .providers
+ .iter()
+ .map(|p| {
+ (
+ format!("{p}_1"),
+ ProviderCfg {
+ kind: p.clone(),
+ maps: vec![PathMap::from_path("example/dev")],
+ ..ProviderCfg::default()
+ },
+ )
+ })
+ .collect();
+
+ let config = Self { providers: res };
+
+ let a: String = serde_yaml::to_string(&config)?;
+ Ok(a)
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
+pub struct Match {
+ pub path: PathBuf,
+ pub position: Option<(usize, usize)>,
+ pub offset: usize,
+ pub query: KV,
+}
+
+impl PartialOrd for Match {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Match {
+ fn cmp(&self, other: &Self) -> Ordering {
+ let query_cmp = self.query.cmp(&other.query);
+
+ if query_cmp != Ordering::Equal {
+ return query_cmp;
+ }
+
+ self.offset.cmp(&other.offset)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta::assert_yaml_snapshot;
+
+ use super::*;
+ #[test]
+ fn load_config() {
+ std::env::set_var("TEST_LOAD_1", "DEV");
+ let config = Config::from_path(Path::new("fixtures/config.yml")).unwrap();
+ assert_eq!(config.providers.len(), 2);
+ assert_yaml_snapshot!(config);
+ }
+
+ #[test]
+ fn can_render_template_config() {
+ let data = RenderTemplate {
+ providers: vec![ProviderKind::Inmem, ProviderKind::Dotenv],
+ };
+
+ let config = Config::render_template(&data).unwrap();
+ assert_yaml_snapshot!(config);
+ }
+}
diff --git a/teller-core/src/exec.rs b/teller-core/src/exec.rs
new file mode 100644
index 00000000..a16d966a
--- /dev/null
+++ b/teller-core/src/exec.rs
@@ -0,0 +1,193 @@
+use std::{collections::HashMap, path::Path, process::Output};
+
+// use crate::{Error, Result};
+// use teller_providers::errors::{Error, Result};
+use crate::{Error, Result};
+pub struct Opts<'a> {
+ pub pwd: &'a Path,
+ pub capture: bool,
+ pub sh: bool,
+ pub reset_env: bool,
+}
+
+const ENV_OK: &[&str] = &[
+ "USER",
+ "HOME",
+ "PATH",
+ "TMPDIR",
+ "SHELL",
+ "SSH_AUTH_SOCK",
+ "LANG",
+ "LC_ALL",
+ "TEMPDIR",
+ "TERM",
+ "COLORTERM",
+ "LOGNAME",
+];
+
+/// Run a command
+///
+/// # Errors
+///
+/// This function will return an error if running command fails
+pub fn cmd(cmdstr: &str, env_kvs: &[(String, String)], opts: &Opts<'_>) -> Result {
+ let words = if opts.sh {
+ shell_command_argv(cmdstr.into())
+ } else {
+ shell_words::split(cmdstr)?.iter().map(Into::into).collect()
+ };
+ cmd_slice(
+ words
+ .iter()
+ .map(String::as_str)
+ .collect::>()
+ .as_slice(),
+ env_kvs,
+ opts,
+ )
+}
+
+fn cmd_slice(words: &[&str], env_kvs: &[(String, String)], opts: &Opts<'_>) -> Result {
+ // env handling
+ let mut env_map: HashMap<_, _> = if opts.reset_env {
+ std::env::vars()
+ .filter(|(k, _)| ENV_OK.contains(&k.as_str()))
+ .collect()
+ } else {
+ std::env::vars().collect()
+ };
+
+ for (k, v) in env_kvs {
+ env_map.insert(k.clone(), v.clone());
+ }
+
+ // no shell
+ let (first, rest) = words
+ .split_first()
+ .ok_or_else(|| Error::Message("command has not enough arguments".to_string()))?;
+
+ let mut expr = duct::cmd(Path::new(first), rest)
+ .dir(opts.pwd)
+ .full_env(&env_map);
+
+ if opts.capture {
+ expr = expr.stdout_capture();
+ }
+
+ Ok(expr.run()?)
+}
+
+#[cfg(unix)]
+fn shell_command_argv(command: String) -> Vec {
+ use std::env;
+
+ let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into());
+ vec![shell, "-c".into(), command]
+}
+
+#[cfg(windows)]
+fn shell_command_argv(command: String) -> Vec {
+ let comspec = std::env::var_os("COMSPEC")
+ .and_then(|s| s.into_string().ok())
+ .unwrap_or_else(|| "cmd.exe".into());
+ vec![comspec, "/C".into(), command]
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::Path;
+
+ use insta::assert_debug_snapshot;
+ use teller_providers::config::ProviderInfo;
+ use teller_providers::config::KV;
+ use teller_providers::providers::ProviderKind;
+
+ use super::cmd;
+ use super::Opts;
+
+ #[test]
+ #[cfg(not(windows))]
+ fn run_echo() {
+ let out = cmd(
+ "echo $MY_VAR",
+ &std::iter::once(&KV::from_literal(
+ "/foo/bar",
+ "MY_VAR",
+ "shazam",
+ ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: "test".to_string(),
+ },
+ ))
+ .map(|kv| (kv.key.clone(), kv.value.clone()))
+ .collect::>(),
+ &Opts {
+ pwd: Path::new("."),
+ capture: true,
+ reset_env: true,
+ sh: true,
+ },
+ )
+ .unwrap();
+ let s = String::from_utf8_lossy(&out.stdout[..]);
+ assert_debug_snapshot!(s);
+ }
+
+ #[ignore]
+ #[test]
+ fn env_reset() {
+ let out = cmd(
+ "/usr/bin/env",
+ &std::iter::once(&KV::from_literal(
+ "/foo/bar",
+ "MY_VAR",
+ "shazam",
+ ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: "test".to_string(),
+ },
+ ))
+ .map(|kv| (kv.key.clone(), kv.value.clone()))
+ .collect::>(),
+ &Opts {
+ pwd: Path::new("."),
+ capture: true,
+ reset_env: false, // <-- notice this!
+ sh: false,
+ },
+ )
+ .unwrap();
+ let stdout = String::from_utf8_lossy(&out.stdout[..]).to_string();
+
+ // dirty secret here
+ assert!(stdout.contains("GITHUB_TOKEN="));
+
+ let out = cmd(
+ "/usr/bin/env",
+ &std::iter::once(&KV::from_literal(
+ "/foo/bar",
+ "MY_VAR",
+ "shazam",
+ ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: "test".to_string(),
+ },
+ ))
+ .map(|kv| (kv.key.clone(), kv.value.clone()))
+ .collect::>(),
+ &Opts {
+ pwd: Path::new("."),
+ capture: true,
+ reset_env: true, // <-- reset env
+ sh: false,
+ },
+ )
+ .unwrap();
+ let stdout = String::from_utf8_lossy(&out.stdout[..]).to_string();
+
+ assert!(stdout.contains("USER="));
+ assert!(stdout.contains("PATH="));
+ // no secret here!
+ assert!(!stdout.contains("GITHUB_TOKEN="));
+ }
+}
diff --git a/teller-core/src/export.rs b/teller-core/src/export.rs
new file mode 100644
index 00000000..71bbe39b
--- /dev/null
+++ b/teller-core/src/export.rs
@@ -0,0 +1,102 @@
+use std::collections::BTreeMap;
+use std::str::FromStr;
+
+use csv::WriterBuilder;
+use lazy_static::lazy_static;
+use serde_derive::{Deserialize, Serialize};
+use serde_variant::to_variant_name;
+use strum::EnumIter;
+use strum::IntoEnumIterator;
+use teller_providers::config::KV;
+
+use crate::{Error, Result};
+
+lazy_static! {
+ pub static ref POSSIBLE_VALUES: String = {
+ let providers: Vec = Format::iter()
+ .map(|provider| provider.to_string())
+ .collect();
+ providers.join(", ")
+ };
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, EnumIter)]
+pub enum Format {
+ #[serde(rename = "csv")]
+ CSV,
+ #[serde(rename = "yaml")]
+ YAML,
+ #[serde(rename = "json")]
+ JSON,
+ #[serde(rename = "env")]
+ ENV,
+ #[serde(rename = "shell")]
+ Shell,
+}
+
+impl std::fmt::Display for Format {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ to_variant_name(self).expect("only enum supported").fmt(f)
+ }
+}
+
+impl FromStr for Format {
+ type Err = &'static str;
+
+ fn from_str(input: &str) -> Result {
+ let providers = Self::iter()
+ .map(|provider| (provider.to_string(), provider))
+ .collect::>();
+
+ providers.get(input).map_or_else(
+ || Err(&POSSIBLE_VALUES as &'static str),
+ |provider| Ok(provider.clone()),
+ )
+ }
+}
+
+impl Format {
+ /// Export current format type to string
+ ///
+ /// # Errors
+ ///
+ pub fn export(&self, kvs: &[KV]) -> Result {
+ match self {
+ Self::YAML => Ok(serde_yaml::to_string(&KV::to_data(kvs))?),
+ Self::JSON => Ok(serde_json::to_string(&KV::to_data(kvs))?),
+ Self::CSV => Self::export_csv(kvs),
+ Self::ENV => Ok(Self::export_env(kvs)),
+ Self::Shell => Ok(Self::export_shell(kvs)),
+ }
+ }
+
+ fn export_shell(kvs: &[KV]) -> String {
+ let mut out = String::new();
+ out.push_str("#!/bin/sh\n");
+
+ for kv in kvs {
+ out.push_str(&format!("export {}='{}'\n", kv.key, kv.value));
+ }
+ out
+ }
+
+ fn export_env(kvs: &[KV]) -> String {
+ let mut out = String::new();
+ for kv in kvs {
+ out.push_str(&format!("{}={}\n", kv.key, kv.value));
+ }
+ out
+ }
+
+ fn export_csv(kvs: &[KV]) -> Result {
+ let mut wtr = WriterBuilder::new().from_writer(vec![]);
+ for kv in kvs {
+ wtr.write_record(&[kv.key.clone(), kv.value.clone()])?;
+ }
+ Ok(String::from_utf8(
+ wtr.into_inner()
+ .map_err(Box::from)
+ .map_err(Error::CSVInner)?,
+ )?)
+ }
+}
diff --git a/teller-core/src/io.rs b/teller-core/src/io.rs
new file mode 100644
index 00000000..7ade64cc
--- /dev/null
+++ b/teller-core/src/io.rs
@@ -0,0 +1,20 @@
+use std::{
+ io::{self, Read},
+ path::Path,
+};
+
+use fs_err::File;
+
+pub fn is_binary_file(path: &Path) -> io::Result {
+ let mut file = File::open(path)?;
+ let mut buffer = [0; 1024]; // Read the first 1024 bytes of the file
+
+ let bytes_read = file.read(&mut buffer)?;
+ for item in buffer.iter().take(bytes_read) {
+ if *item == 0 {
+ return Ok(true); // Found a null byte, indicating a binary file
+ }
+ }
+
+ Ok(false) // No null byte found, likely a text file.
+}
diff --git a/teller-core/src/lib.rs b/teller-core/src/lib.rs
new file mode 100644
index 00000000..df33951d
--- /dev/null
+++ b/teller-core/src/lib.rs
@@ -0,0 +1,47 @@
+pub mod config;
+pub mod exec;
+pub mod export;
+mod io;
+pub mod redact;
+pub mod scan;
+pub mod teller;
+pub mod template;
+
+use std::string::FromUtf8Error;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ #[error("{0}")]
+ Message(String),
+
+ #[error(transparent)]
+ Shellwords(#[from] shell_words::ParseError),
+
+ #[error(transparent)]
+ IO(#[from] std::io::Error),
+
+ #[error(transparent)]
+ Provider(#[from] teller_providers::Error),
+
+ #[error(transparent)]
+ Handlebars(Box),
+
+ #[error(transparent)]
+ Json(#[from] serde_json::Error),
+
+ #[error(transparent)]
+ YAML(#[from] serde_yaml::Error),
+
+ #[error(transparent)]
+ CSV(#[from] csv::Error),
+
+ #[error(transparent)]
+ CSVInner(Box),
+
+ #[error(transparent)]
+ Tera(#[from] tera::Error),
+
+ #[error(transparent)]
+ Utf(#[from] FromUtf8Error),
+}
+pub type Result = std::result::Result;
diff --git a/teller-core/src/redact.rs b/teller-core/src/redact.rs
new file mode 100644
index 00000000..d057c1f0
--- /dev/null
+++ b/teller-core/src/redact.rs
@@ -0,0 +1,114 @@
+use std::{
+ borrow::Cow,
+ io::{BufRead, Write},
+};
+
+// use crate::{Result, KV};
+use teller_providers::config::KV;
+
+pub struct Redactor {}
+
+impl Redactor {
+ #[must_use]
+ pub const fn new() -> Self {
+ Self {}
+ }
+
+ /// Redact a reader into writer
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if IO fails
+ pub fn redact(
+ &self,
+ reader: R,
+ mut writer: W,
+ kvs: &[KV],
+ ) -> std::io::Result<()> {
+ for line in reader.lines().map_while(Result::ok) {
+ let redacted = self.redact_string(line.as_str(), kvs);
+ writer.write_all(redacted.as_bytes())?;
+ writer.write_all(&[b'\n'])?; // TODO: support crlf for windows
+ writer.flush()?;
+ }
+ Ok(())
+ }
+
+ #[must_use]
+ pub fn redact_string<'a>(&'a self, message: &'a str, kvs: &[KV]) -> Cow<'_, str> {
+ if self.has_match(message, kvs) {
+ let mut redacted = message.to_string();
+ for kv in kvs {
+ redacted = redacted.replace(
+ &kv.value,
+ kv.meta
+ .as_ref()
+ .and_then(|m| m.redact_with.as_ref())
+ .map_or("[REDACTED]", |s| s.as_str()),
+ );
+ }
+ Cow::Owned(redacted)
+ } else {
+ Cow::Borrowed(message)
+ }
+ }
+
+ #[must_use]
+ pub fn has_match<'a>(&'a self, message: &'a str, kvs: &[KV]) -> bool {
+ kvs.iter().any(|kv| message.contains(&kv.value))
+ }
+}
+
+impl Default for Redactor {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::io::{BufReader, BufWriter};
+
+ use stringreader::StringReader;
+ use teller_providers::{config::ProviderInfo, providers::ProviderKind};
+
+ use super::*;
+
+ #[test]
+ fn redact_none() {
+ let data = "foobar\nfoobaz\n";
+ let mut reader = BufReader::new(StringReader::new(data));
+ let mut writer = BufWriter::new(Vec::new());
+ let redactor = Redactor {};
+
+ redactor.redact(&mut reader, &mut writer, &[]).unwrap();
+ let s = String::from_utf8(writer.into_inner().unwrap()).unwrap();
+ assert_eq!(s, "foobar\nfoobaz\n");
+ }
+
+ #[test]
+ fn redact_some() {
+ let data = "foobar\nfoobaz\n";
+ let mut reader = BufReader::new(StringReader::new(data));
+ let mut writer = BufWriter::new(Vec::new());
+ let redactor = Redactor {};
+
+ redactor
+ .redact(
+ &mut reader,
+ &mut writer,
+ &[KV::from_literal(
+ "some/path",
+ "k",
+ "foobaz",
+ ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: "test".to_string(),
+ },
+ )],
+ )
+ .unwrap();
+ let s = String::from_utf8(writer.into_inner().unwrap()).unwrap();
+ assert_eq!(s, "foobar\n[REDACTED]\n");
+ }
+}
diff --git a/teller-core/src/scan.rs b/teller-core/src/scan.rs
new file mode 100644
index 00000000..923b8aa4
--- /dev/null
+++ b/teller-core/src/scan.rs
@@ -0,0 +1,182 @@
+use std::fs;
+
+use aho_corasick::AhoCorasick;
+use ignore::WalkBuilder;
+use teller_providers::config::KV;
+use unicode_width::UnicodeWidthStr;
+
+use crate::{config::Match, io::is_binary_file, Error, Result};
+
+#[derive(Debug, Clone, Default)]
+pub struct Opts {
+ pub include_all: bool,
+ pub include_binary: bool,
+}
+
+/// (ln, col), 1 based (not zero based)
+fn get_visual_position(text: &[u8], byte_position: usize) -> Option<(usize, usize)> {
+ if byte_position >= text.len() || text.is_empty() {
+ return None;
+ }
+
+ let lines = text
+ .iter()
+ .take(byte_position)
+ .filter(|c| **c == b'\n')
+ .count();
+ let last_ln_start = text
+ .iter()
+ .take(byte_position)
+ .rposition(|c| *c == b'\n')
+ .unwrap_or(0);
+
+ let len = UnicodeWidthStr::width(
+ String::from_utf8_lossy(&text[last_ln_start..byte_position]).as_ref(),
+ );
+
+ // index starts from 1 for both
+ Some((lines + 1, len + 1))
+}
+
+// aho
+// offset into row/col visual https://github.com/zkat/miette/blob/f4d056e1ffeb9a0bf36e2a6501365bd7e00db22d/src/handlers/graphical.rs#L619
+// match repr
+///
+/// # Errors
+///
+/// TODO
+#[allow(clippy::module_name_repetitions)]
+pub fn scan_root(root: &str, kvs: &[KV], opts: &Opts) -> Result> {
+ let patterns = kvs.iter().map(|kv| kv.value.as_str()).collect::>();
+ let finder = AhoCorasick::new(patterns).map_err(|e| Error::Message(e.to_string()))?;
+
+ let mut wb = WalkBuilder::new(root);
+
+ let mut matches = vec![];
+ for entry in wb
+ .ignore(!opts.include_all)
+ .git_ignore(!opts.include_all)
+ .hidden(opts.include_all)
+ .build()
+ .filter_map(Result::ok)
+ .filter(|ent| ent.path().is_file())
+ {
+ let path = entry.path();
+ if is_binary_file(path)? && !opts.include_binary {
+ continue;
+ }
+
+ let content = String::from_utf8_lossy(&fs::read(path)?).to_string();
+ let bytes = content.as_bytes();
+
+ finder.find_iter(&content).for_each(|aho_match| {
+ matches.push(Match {
+ path: path.to_path_buf(),
+ query: kvs[aho_match.pattern()].clone(),
+ position: get_visual_position(bytes, aho_match.start()),
+ offset: aho_match.start(),
+ });
+ });
+ }
+
+ matches.sort();
+ Ok(matches)
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ fs,
+ path::{Path, PathBuf},
+ };
+
+ use insta::assert_debug_snapshot;
+ use teller_providers::{
+ config::{ProviderInfo, KV},
+ providers::ProviderKind,
+ };
+
+ use super::*;
+ use crate::scan;
+
+ fn normalize_path_separators(path: &Path) -> PathBuf {
+ let path_str = path.to_string_lossy().replace('\\', "/");
+ PathBuf::from(path_str)
+ }
+ fn normalize_matches(ms: &[Match]) -> Vec {
+ ms.iter()
+ .map(|m| Match {
+ path: normalize_path_separators(&m.path),
+ ..m.clone()
+ })
+ .collect::>()
+ }
+
+ #[test]
+ fn test_position() {
+ assert_eq!(get_visual_position(b"", 4), None);
+ assert_eq!(get_visual_position(b"", 1), None);
+ assert_eq!(get_visual_position(b"", 0), None);
+ assert_eq!(get_visual_position(b"a", 1), None);
+
+ assert_eq!(get_visual_position(b"abcde\nfghi", 8), Some((2, 3)));
+ assert_eq!(get_visual_position(b"abcde\r\nfghi", 8), Some((2, 2)));
+
+ let text = r#" 100% ❯ j teller-rs
+ /Users/jondot/spikes/teller-rs
+ (base)
+ ~/spikes/teller-rs on master [!?] via 🦀 v1.73.0-nightly
+ 100% ❯ code .
+ (base)
+ ~/spikes/teller-rs on master [!?] via 🦀 v1.73.0-nightly
+ 100% ❯ [WARN] - (starship::utils): Executing command "/opt/homebrew/bin/git" timed out.
+ (base)
+ ~/spikes/teller-rs on master [!?] via 🦀 v1.73.0-nightly
+ 100% ❯ open /Users/jondot/Movies
+ (base)
+ ~/spikes/teller-rs on master [!?] via 🦀 v1.73.0-nightly
+ 100% ❯"#;
+ let position = get_visual_position(text.as_bytes(), 438);
+ assert_eq!(position, Some((11, 19)));
+ }
+
+ #[test]
+ fn test_scan() {
+ let provider = ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: "test".to_string(),
+ };
+ let kvs = vec![
+ KV::from_literal("/some/path", "key1", "hashicorp", provider.clone()),
+ KV::from_literal("/some/path", "key1", "dont-find-me", provider.clone()),
+ KV::from_literal("/some/path", "key1", "trooper123", provider.clone()),
+ KV::from_literal("/some/path", "key1", "pass1", provider.clone()),
+ KV::from_literal("/some/path", "key1", "nested111", provider),
+ ];
+
+ let res = scan_root("fixtures", &kvs[..], &scan::Opts::default());
+ assert_debug_snapshot!(normalize_matches(&res.unwrap()));
+
+ let res = scan_root(
+ "fixtures",
+ &kvs[..],
+ &scan::Opts {
+ include_binary: true,
+ include_all: false,
+ },
+ );
+ assert_debug_snapshot!(normalize_matches(&res.unwrap()));
+
+ fs::write("fixtures/git-ignored-file", "trooper123").expect("cannot write file");
+
+ let res = scan_root(
+ "fixtures",
+ &kvs[..],
+ &scan::Opts {
+ include_binary: false,
+ include_all: true,
+ },
+ );
+ assert_debug_snapshot!(normalize_matches(&res.unwrap()));
+ }
+}
diff --git a/teller-core/src/snapshots/teller_core__config__tests__can_render_template_config.snap b/teller-core/src/snapshots/teller_core__config__tests__can_render_template_config.snap
new file mode 100644
index 00000000..8398068c
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__config__tests__can_render_template_config.snap
@@ -0,0 +1,5 @@
+---
+source: teller-core/src/config.rs
+expression: config
+---
+"providers:\n dotenv_1:\n kind: dotenv\n maps:\n - id: ''\n path: example/dev\n inmem_1:\n kind: inmem\n maps:\n - id: ''\n path: example/dev\n"
diff --git a/teller-core/src/snapshots/teller_core__config__tests__load_config.snap b/teller-core/src/snapshots/teller_core__config__tests__load_config.snap
new file mode 100644
index 00000000..add5a9e7
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__config__tests__load_config.snap
@@ -0,0 +1,18 @@
+---
+source: teller-core/src/config.rs
+expression: config
+---
+providers:
+ dot_1:
+ kind: dotenv
+ maps:
+ - id: stg
+ path: VAR_development
+ hashi_1:
+ kind: hashicorp
+ maps:
+ - id: test-load
+ path: /DEV/users/user1
+ keys:
+ GITHUB_TOKEN: GITHUB_TOKEN
+ mg: FOO_BAR
diff --git a/teller-core/src/snapshots/teller_core__exec__tests__run_echo.snap b/teller-core/src/snapshots/teller_core__exec__tests__run_echo.snap
new file mode 100644
index 00000000..c2ac7ba7
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__exec__tests__run_echo.snap
@@ -0,0 +1,5 @@
+---
+source: src/exec.rs
+expression: s
+---
+"shazam\n"
diff --git a/teller-core/src/snapshots/teller_core__scan__tests__scan-2.snap b/teller-core/src/snapshots/teller_core__scan__tests__scan-2.snap
new file mode 100644
index 00000000..627f8937
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__scan__tests__scan-2.snap
@@ -0,0 +1,34 @@
+---
+source: teller-core/src/scan.rs
+expression: normalize_matches(res.unwrap())
+---
+[
+ Match {
+ path: "fixtures/config.yml",
+ position: Some(
+ (
+ 3,
+ 11,
+ ),
+ ),
+ offset: 32,
+ query: KV {
+ value: "hashicorp",
+ key: "key1",
+ from_key: "key1",
+ path: Some(
+ PathInfo {
+ id: "/some/path",
+ path: "/some/path",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Inmem,
+ name: "test",
+ },
+ ),
+ meta: None,
+ },
+ },
+]
diff --git a/teller-core/src/snapshots/teller_core__scan__tests__scan-3.snap b/teller-core/src/snapshots/teller_core__scan__tests__scan-3.snap
new file mode 100644
index 00000000..fd059e74
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__scan__tests__scan-3.snap
@@ -0,0 +1,62 @@
+---
+source: teller-core/src/scan.rs
+expression: normalize_matches(res.unwrap())
+---
+[
+ Match {
+ path: "fixtures/git-ignored-file",
+ position: Some(
+ (
+ 1,
+ 1,
+ ),
+ ),
+ offset: 0,
+ query: KV {
+ value: "trooper123",
+ key: "key1",
+ from_key: "key1",
+ path: Some(
+ PathInfo {
+ id: "/some/path",
+ path: "/some/path",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Inmem,
+ name: "test",
+ },
+ ),
+ meta: None,
+ },
+ },
+ Match {
+ path: "fixtures/config.yml",
+ position: Some(
+ (
+ 3,
+ 11,
+ ),
+ ),
+ offset: 32,
+ query: KV {
+ value: "hashicorp",
+ key: "key1",
+ from_key: "key1",
+ path: Some(
+ PathInfo {
+ id: "/some/path",
+ path: "/some/path",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Inmem,
+ name: "test",
+ },
+ ),
+ meta: None,
+ },
+ },
+]
diff --git a/teller-core/src/snapshots/teller_core__scan__tests__scan.snap b/teller-core/src/snapshots/teller_core__scan__tests__scan.snap
new file mode 100644
index 00000000..627f8937
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__scan__tests__scan.snap
@@ -0,0 +1,34 @@
+---
+source: teller-core/src/scan.rs
+expression: normalize_matches(res.unwrap())
+---
+[
+ Match {
+ path: "fixtures/config.yml",
+ position: Some(
+ (
+ 3,
+ 11,
+ ),
+ ),
+ offset: 32,
+ query: KV {
+ value: "hashicorp",
+ key: "key1",
+ from_key: "key1",
+ path: Some(
+ PathInfo {
+ id: "/some/path",
+ path: "/some/path",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: Inmem,
+ name: "test",
+ },
+ ),
+ meta: None,
+ },
+ },
+]
diff --git a/teller-core/src/snapshots/teller_core__template__tests__render_template.snap b/teller-core/src/snapshots/teller_core__template__tests__render_template.snap
new file mode 100644
index 00000000..e76a9f28
--- /dev/null
+++ b/teller-core/src/snapshots/teller_core__template__tests__render_template.snap
@@ -0,0 +1,7 @@
+---
+source: teller-core/src/template.rs
+expression: "render(\"hello {{ key(name='k') }}\", kvs)"
+---
+Ok(
+ "hello foobaz",
+)
diff --git a/teller-core/src/teller.rs b/teller-core/src/teller.rs
new file mode 100644
index 00000000..f9e648fd
--- /dev/null
+++ b/teller-core/src/teller.rs
@@ -0,0 +1,217 @@
+use std::collections::BTreeMap;
+use std::io::{BufRead, Write};
+use std::path::Path;
+use std::process::Output;
+
+use teller_providers::config::PathMap;
+use teller_providers::Provider;
+// use csv::WriterBuilder;
+use teller_providers::{config::KV, registry::Registry, Result as ProviderResult};
+
+use crate::redact::Redactor;
+use crate::template;
+use crate::{
+ config::{Config, Match},
+ exec, export, scan, Error, Result,
+};
+
+pub struct Teller {
+ registry: Registry,
+ config: Config,
+}
+
+impl Teller {
+ /// Build from config
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if loading fails
+ pub async fn from_config(config: &Config) -> teller_providers::Result {
+ let registry = Registry::new(&config.providers).await?;
+ Ok(Self {
+ registry,
+ config: config.clone(),
+ })
+ }
+
+ /// Build from YAML
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if loading fails
+ pub async fn from_yaml(file: &Path) -> Result {
+ let config = Config::from_path(file)?;
+ Self::from_config(&config).await.map_err(Error::Provider)
+ }
+ /// Collects kvs from all provider maps in the current configuration
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if IO fails
+ pub async fn collect(&self) -> ProviderResult> {
+ let mut res = Vec::new();
+ for (name, providercfg) in &self.config.providers {
+ if let Some(provider) = self.registry.get(name) {
+ for pm in &providercfg.maps {
+ let kvs = provider.get(pm).await?;
+ res.push(kvs);
+ }
+ }
+ }
+ Ok(res.into_iter().flatten().collect::>())
+ }
+ /// Put a list of KVs into a list of providers, on a specified path
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if put fails
+ pub async fn put(&self, kvs: &[KV], map_id: &str, providers: &[String]) -> Result<()> {
+ // a target provider has to have the specified path id
+ for provider_name in providers {
+ let (provider, pm) = self.get_pathmap_on_provider(map_id, provider_name)?;
+ provider.put(pm, kvs).await?;
+ }
+ Ok(())
+ }
+
+ /// Delete a list of keys or a complete path for every provider in the list
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if delete fails
+ pub async fn delete(&self, keys: &[String], map_id: &str, providers: &[String]) -> Result<()> {
+ // a target provider has to have the specified path id
+ for provider_name in providers {
+ let (provider, pm) = self.get_pathmap_on_provider(map_id, provider_name)?;
+ // 1. if keys is empty, use the default pathmap
+ // 2. otherwise, create a new pathmap, with a subset of keys
+ if keys.is_empty() {
+ provider.del(pm).await?;
+ } else {
+ let mut subset_keys = BTreeMap::new();
+ for key in keys {
+ subset_keys.insert(key.clone(), key.clone());
+ }
+ let mut new_pm = pm.clone();
+ new_pm.keys = subset_keys;
+ provider.del(&new_pm).await?;
+ }
+ }
+ Ok(())
+ }
+ /// Get a provider and pathmap from configuration and registry
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if operation fails
+ #[allow(clippy::borrowed_box)]
+ pub fn get_pathmap_on_provider(
+ &self,
+ map_id: &str,
+ provider_name: &String,
+ ) -> Result<(&Box, &PathMap)> {
+ let pconf = self.config.providers.get(provider_name).ok_or_else(|| {
+ Error::Message(format!(
+ "cannot find provider '{provider_name}' path configuration"
+ ))
+ })?;
+ let pm = pconf.maps.iter().find(|m| m.id == map_id).ok_or_else(|| {
+ Error::Message(format!(
+ "cannot find path id '{map_id}' in provider '{provider_name}'"
+ ))
+ })?;
+ let provider = self.registry.get(provider_name).ok_or_else(|| {
+ Error::Message(format!("cannot get initialized provider '{provider_name}'"))
+ })?;
+ Ok((provider, pm))
+ }
+ /// Run an external command with provider based environment variables
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if command fails
+ pub async fn run<'a>(&self, cmd: &[&str], opts: &exec::Opts<'a>) -> Result {
+ let cmd = shell_words::join(cmd);
+ let kvs = self.collect().await?;
+ let res = exec::cmd(
+ cmd.as_str(),
+ &kvs.iter()
+ .map(|kv| (kv.key.clone(), kv.value.clone()))
+ .collect::>()[..],
+ opts,
+ )?;
+ Ok(res)
+ }
+
+ /// Redact streams
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if Is or collecting keys fails
+ #[allow(clippy::future_not_send)]
+ pub async fn redact(&self, reader: R, writer: W) -> Result<()> {
+ let kvs = self.collect().await?;
+ let redactor = Redactor::new();
+ redactor.redact(reader, writer, kvs.as_slice())?;
+ Ok(())
+ }
+
+ /// Populate a custom template with KVs
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if template rendering fails
+ pub async fn template(&self, template: &str) -> Result {
+ let kvs = self.collect().await?;
+ let out = template::render(template, kvs)?; // consumes kvs
+ Ok(out)
+ }
+
+ /// Export KV data
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if export fails
+ pub async fn export<'a>(&self, format: &export::Format) -> Result {
+ let kvs = self.collect().await?;
+ format.export(&kvs)
+ }
+
+ /// Scan a folder recursively for secrets or values
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if IO fails
+ pub fn scan(&self, root: &str, kvs: &[KV], opts: &scan::Opts) -> Result> {
+ scan::scan_root(root, kvs, opts)
+ }
+
+ /// Copy from provider to target provider.
+ /// Note: `replace` will first delete data at target, then copy.
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if copy fails
+ pub async fn copy(
+ &self,
+ from_provider: &str,
+ from_map_id: &str,
+ to_provider: &str,
+ to_map_id: &str,
+ replace: bool,
+ ) -> Result<()> {
+ // XXX fix &str, &String params
+ let (from_provider, from_pm) =
+ self.get_pathmap_on_provider(from_map_id, &from_provider.to_string())?;
+ let data = from_provider.get(from_pm).await?;
+
+ let (to_provider, to_pm) =
+ self.get_pathmap_on_provider(to_map_id, &to_provider.to_string())?;
+
+ if replace {
+ to_provider.del(to_pm).await?;
+ }
+ to_provider.put(to_pm, &data).await?;
+ Ok(())
+ }
+}
diff --git a/teller-core/src/template.rs b/teller-core/src/template.rs
new file mode 100644
index 00000000..229c29b6
--- /dev/null
+++ b/teller-core/src/template.rs
@@ -0,0 +1,62 @@
+use teller_providers::config::KV;
+use tera::{from_value, to_value, Context, Result, Tera};
+
+struct KeyFn {
+ kvs: Vec,
+}
+impl tera::Function for KeyFn {
+ fn call(
+ &self,
+ args: &std::collections::HashMap,
+ ) -> tera::Result {
+ args.get("name").map_or_else(
+ || Err("cannot get parameter 'name'".into()),
+ |val| {
+ from_value::(val.clone()).map_or_else(
+ |_| Err("cannot get parameter 'name'".into()),
+ |v| {
+ self.kvs
+ .iter()
+ .find(|kv| kv.key == v)
+ .and_then(|kv| to_value(&kv.value).ok())
+ .ok_or_else(|| "not found".into())
+ },
+ )
+ },
+ )
+ }
+}
+
+/// Render a template with access to KVs
+///
+/// # Errors
+///
+/// This function will return an error if rendering fails
+pub fn render(template: &str, kvs: Vec) -> Result {
+ let mut tera = Tera::default();
+ tera.register_function("key", KeyFn { kvs });
+ let res = tera.render_str(template, &Context::new())?;
+ Ok(res)
+}
+
+#[cfg(test)]
+mod tests {
+ use insta::assert_debug_snapshot;
+ use teller_providers::{config::ProviderInfo, providers::ProviderKind};
+
+ use super::*;
+
+ #[test]
+ fn render_template() {
+ let kvs = &[KV::from_literal(
+ "some/path",
+ "k",
+ "foobaz",
+ ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: "test".to_string(),
+ },
+ )];
+ assert_debug_snapshot!(render("hello {{ key(name='k') }}", kvs.to_vec()));
+ }
+}
diff --git a/teller-providers/Cargo.toml b/teller-providers/Cargo.toml
new file mode 100644
index 00000000..ae8f6ea2
--- /dev/null
+++ b/teller-providers/Cargo.toml
@@ -0,0 +1,59 @@
+[package]
+name = "teller-providers"
+version = "0.1.0"
+edition = "2021"
+
+# [lib]
+# name = "teller-providers"
+
+[features]
+default = [
+ "hashicorp_vault",
+ "dotenv",
+ "ssm",
+ "aws_secretsmanager",
+ "google_secretmanager",
+ "hashicorp_consul",
+]
+
+ssm = ["aws", "dep:aws-sdk-ssm"]
+aws_secretsmanager = ["aws", "dep:aws-sdk-secretsmanager"]
+google_secretmanager = ["dep:google-secretmanager1", "dep:crc32c"]
+hashicorp_vault = ["dep:vaultrs", "dep:rustify"]
+dotenv = ["dep:dotenvy"]
+hashicorp_consul = ["dep:consulrs"]
+aws = ["dep:aws-config"]
+
+[dependencies]
+async-trait = { workspace = true }
+lazy_static = { workspace = true }
+serde_variant = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+serde_yaml = { workspace = true }
+serde_derive = { workspace = true }
+strum = { workspace = true }
+thiserror = { workspace = true }
+fs-err = "2.9.0"
+home = "0.5.5"
+# gcp
+google-secretmanager1 = { version = "5.0.2", optional = true }
+crc32c = { version = "0.6", optional = true }
+# aws
+aws-config = { version = "1.2.0", optional = true }
+# aws-ssm
+aws-sdk-ssm = { version = "1.22.0", optional = true }
+# aws-secretsmanager
+aws-sdk-secretsmanager = { version = "1.22.0", optional = true }
+# dotenv
+dotenvy = { version = "0.15.7", optional = true }
+# hashivault
+vaultrs = { version = "0.7.2", optional = true }
+rustify = { version = "0.5.3", optional = true }
+# HashiCorp Consul
+consulrs = { git = "https://github.com/jmgilman/consulrs", optional = true, rev = "a14fddbdf3695e2e338145134c4b42f823e03370" }
+
+[dev-dependencies]
+insta = { workspace = true }
+dockertest-server = { version = "0.1.7", features = ["hashi", "cloud"] }
+tokio = { workspace = true }
diff --git a/teller-providers/src/config.rs b/teller-providers/src/config.rs
new file mode 100644
index 00000000..bec92c49
--- /dev/null
+++ b/teller-providers/src/config.rs
@@ -0,0 +1,202 @@
+use std::cmp::Ordering;
+use std::collections::BTreeMap;
+
+use serde_derive::{Deserialize, Serialize};
+
+use crate::providers::ProviderKind;
+
+fn is_default(t: &T) -> bool {
+ t == &T::default()
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct ProviderCfg {
+ #[serde(rename = "kind")]
+ pub kind: ProviderKind,
+ #[serde(rename = "options", skip_serializing_if = "Option::is_none")]
+ pub options: Option,
+ #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
+ pub name: Option,
+ pub maps: Vec,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
+pub enum Sensitivity {
+ #[default]
+ None,
+ Low,
+ Medium,
+ High,
+ Critical,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
+pub struct ProviderInfo {
+ pub kind: ProviderKind,
+ pub name: String,
+}
+#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
+pub struct PathInfo {
+ pub id: String,
+ pub path: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
+pub struct MetaInfo {
+ pub sensitivity: Sensitivity,
+ pub redact_with: Option,
+ pub source: Option,
+ pub sink: Option,
+}
+#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
+pub struct KV {
+ pub value: String,
+ pub key: String, // mapped-to key
+ pub from_key: String,
+ pub path: Option, // always toplevel
+ pub provider: Option,
+ pub meta: Option,
+}
+
+impl PartialOrd for KV {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for KV {
+ fn cmp(&self, other: &Self) -> Ordering {
+ if let (Some(provider), Some(other_provider)) = (&self.provider, &other.provider) {
+ let provider_cmp = provider.kind.cmp(&other_provider.kind);
+ if provider_cmp != Ordering::Equal {
+ return provider_cmp;
+ }
+ }
+
+ self.key.cmp(&other.key)
+ }
+}
+
+impl KV {
+ #[must_use]
+ pub fn to_data(kvs: &[Self]) -> BTreeMap {
+ let mut data = BTreeMap::new();
+ for kv in kvs {
+ data.insert(kv.key.clone(), kv.value.clone());
+ }
+ data
+ }
+
+ #[must_use]
+ pub fn from_data(
+ data: &BTreeMap,
+ pm: &PathMap,
+ provider: &ProviderInfo,
+ ) -> Vec {
+ // map all of the data found
+ if pm.keys.is_empty() {
+ data.iter()
+ .map(|(k, v)| Self::from_value(v, k, k, pm, provider.clone()))
+ .collect::>()
+ } else {
+ // selectively map only keys from pathmap
+ pm.keys
+ .iter()
+ .filter_map(|(from_key, to_key)| {
+ data.get(from_key).map(|found_val| {
+ Self::from_value(found_val, from_key, to_key, pm, provider.clone())
+ })
+ })
+ .collect::>()
+ }
+ }
+ #[must_use]
+ pub fn from_value(
+ found_val: &str,
+ from_key: &str,
+ to_key: &str,
+ pm: &PathMap,
+ provider: ProviderInfo,
+ ) -> Self {
+ Self {
+ value: found_val.to_string(),
+ key: to_key.to_string(),
+ from_key: from_key.to_string(),
+ path: Some(PathInfo {
+ path: pm.path.clone(),
+ id: pm.id.to_string(),
+ }),
+ provider: Some(provider),
+ meta: Some(MetaInfo {
+ sensitivity: pm.sensitivity.clone(),
+ redact_with: pm.redact_with.clone(),
+ source: pm.source.clone(),
+ sink: pm.sink.clone(),
+ }),
+ }
+ }
+ #[must_use]
+ pub fn from_literal(path: &str, key: &str, value: &str, provider: ProviderInfo) -> Self {
+ Self {
+ value: value.to_string(),
+ key: key.to_string(),
+ from_key: key.to_string(),
+ path: Some(PathInfo {
+ id: path.to_string(),
+ path: path.to_string(),
+ }),
+ provider: Some(provider),
+ ..Default::default()
+ }
+ }
+
+ /// represents a KV without any source (e.g. created manually by a user, pending insert to
+ /// one of the providers)
+ #[must_use]
+ pub fn from_kv(key: &str, value: &str) -> Self {
+ Self {
+ value: value.to_string(),
+ key: key.to_string(),
+ from_key: key.to_string(),
+ ..Default::default()
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct PathMap {
+ pub id: String,
+ #[serde(rename = "protocol", skip_serializing_if = "Option::is_none")]
+ pub protocol: Option,
+ #[serde(rename = "path")]
+ pub path: String,
+ #[serde(default, rename = "keys", skip_serializing_if = "is_default")]
+ pub keys: BTreeMap,
+ #[serde(default, rename = "decrypt", skip_serializing_if = "is_default")]
+ pub decrypt: bool,
+ #[serde(default, rename = "sensitivity", skip_serializing_if = "is_default")]
+ pub sensitivity: Sensitivity,
+ #[serde(
+ default,
+ rename = "redact_with",
+ skip_serializing_if = "Option::is_none"
+ )]
+ pub redact_with: Option,
+ #[serde(default, rename = "source", skip_serializing_if = "Option::is_none")]
+ pub source: Option,
+ #[serde(default, rename = "sink", skip_serializing_if = "Option::is_none")]
+ pub sink: Option,
+ // ignore population if optional + we got error
+ #[serde(default, rename = "optional", skip_serializing_if = "is_default")]
+ pub optional: bool,
+}
+
+impl PathMap {
+ #[must_use]
+ pub fn from_path(path: &str) -> Self {
+ Self {
+ path: path.to_string(),
+ ..Default::default()
+ }
+ }
+}
diff --git a/teller-providers/src/lib.rs b/teller-providers/src/lib.rs
new file mode 100644
index 00000000..1f86f642
--- /dev/null
+++ b/teller-providers/src/lib.rs
@@ -0,0 +1,70 @@
+pub mod config;
+pub mod providers;
+pub mod registry;
+
+use async_trait::async_trait;
+
+use crate::config::{PathMap, ProviderInfo, KV};
+
+#[async_trait]
+pub trait Provider {
+ fn kind(&self) -> ProviderInfo;
+ /// Get a mapping
+ ///
+ /// # Errors
+ ///
+ /// ...
+ async fn get(&self, pm: &PathMap) -> Result>;
+ /// Put a mapping
+ ///
+ /// # Errors
+ ///
+ /// ...
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()>;
+ /// Delete a mapping
+ ///
+ /// # Errors
+ ///
+ /// ...
+ async fn del(&self, pm: &PathMap) -> Result<()>;
+}
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ #[error("{0}")]
+ Message(String),
+
+ #[error("{0}: {1}")]
+ PathError(String, String),
+
+ #[error(transparent)]
+ IO(#[from] std::io::Error),
+
+ #[error(transparent)]
+ Env(#[from] std::env::VarError),
+
+ #[error(transparent)]
+ Any(#[from] Box),
+
+ #[error(transparent)]
+ Json(#[from] serde_json::Error),
+
+ #[error(transparent)]
+ YAML(#[from] serde_yaml::Error),
+
+ #[error("NOT FOUND {path}: {msg}")]
+ NotFound { path: String, msg: String },
+
+ #[error("GET {path}: {msg}")]
+ GetError { path: String, msg: String },
+
+ #[error("DEL {path}: {msg}")]
+ DeleteError { path: String, msg: String },
+
+ #[error("PUT {path}: {msg}")]
+ PutError { path: String, msg: String },
+
+ #[error("LIST {path}: {msg}")]
+ ListError { path: String, msg: String },
+}
+
+pub type Result = std::result::Result;
diff --git a/teller-providers/src/providers/aws_secretsmanager.rs b/teller-providers/src/providers/aws_secretsmanager.rs
new file mode 100644
index 00000000..9055c737
--- /dev/null
+++ b/teller-providers/src/providers/aws_secretsmanager.rs
@@ -0,0 +1,318 @@
+//! AWS Secret Manager
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! aws1:
+//! kind: aws_secretsmanager
+//! # options: ...
+//! ```
+//! ## Options
+//!
+//! See [`AWSSecretsManagerOptions`]
+//!
+//!
+#![allow(clippy::borrowed_box)]
+
+use std::collections::BTreeMap;
+
+use async_trait::async_trait;
+use aws_config::{self, BehaviorVersion};
+use aws_sdk_secretsmanager as secretsmanager;
+use secretsmanager::config::{Credentials, Region};
+use secretsmanager::operation::get_secret_value::GetSecretValueError;
+use secretsmanager::{error::SdkError, operation::delete_secret::DeleteSecretError};
+use serde_derive::{Deserialize, Serialize};
+
+use super::ProviderKind;
+use crate::config::ProviderInfo;
+use crate::{
+ config::{PathMap, KV},
+ Error, Provider, Result,
+};
+
+fn handle_get_err(
+ mode: &Mode,
+ e: SdkError,
+ pm: &PathMap,
+) -> Result> {
+ match e.into_service_error() {
+ GetSecretValueError::ResourceNotFoundException(_) => {
+ if mode == &Mode::Get {
+ Err(Error::NotFound {
+ path: pm.path.to_string(),
+ msg: "not found".to_string(),
+ })
+ } else {
+ // we're ok
+ Ok(None)
+ }
+ }
+ e => {
+ if e.to_string().contains("marked deleted") {
+ Err(Error::NotFound {
+ path: pm.path.to_string(),
+ msg: "not found".to_string(),
+ })
+ } else {
+ Err(Error::GetError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })
+ }
+ }
+ }
+}
+
+fn handle_del_err(e: SdkError, pm: &PathMap) -> Result<()> {
+ match e.into_service_error() {
+ DeleteSecretError::ResourceNotFoundException(_) => {
+ // we're ok
+ Ok(())
+ }
+ e => Err(Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ }),
+ }
+}
+
+#[derive(PartialEq)]
+enum Mode {
+ Get,
+ Put,
+ Del,
+}
+
+///
+/// # AWS provider configuration
+///
+/// This holds the most commonly used and simplified configuration options for this provider. These
+/// paramters can be used in the Teller YAML configuration.
+///
+/// For indepth description of each parameter see: [AWS SDK config](https://docs.rs/aws-config/latest/aws_config/struct.SdkConfig.html)
+///
+/// If you need an additional parameter from the AWS SDK included in our simplified configuration,
+/// open an issue in Teller and request to add it.
+///
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct AWSSecretsManagerOptions {
+ pub region: Option,
+ pub access_key_id: Option,
+ pub secret_access_key: Option,
+ pub endpoint_url: Option,
+}
+
+pub struct AWSSecretsManager {
+ pub client: secretsmanager::Client,
+ pub name: String,
+}
+
+impl AWSSecretsManager {
+ #[must_use]
+ pub fn with_client(name: &str, client: secretsmanager::Client) -> Self {
+ Self {
+ client,
+ name: name.to_string(),
+ }
+ }
+ /// Create a new secretsmanager provider
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if cannot create a provider
+ pub async fn new(name: &str, opts: Option) -> Result {
+ let client = if let Some(opts) = opts {
+ let mut config = aws_config::defaults(BehaviorVersion::v2023_11_09());
+ if let (Some(key), Some(secret)) = (opts.access_key_id, opts.secret_access_key) {
+ config = config
+ .credentials_provider(Credentials::new(key, secret, None, None, "teller"));
+ }
+ if let Some(endpoint_url) = opts.endpoint_url {
+ config = config.endpoint_url(endpoint_url);
+ }
+ if let Some(region) = opts.region {
+ config = config.region(Region::new(region));
+ }
+ let ssmconf = secretsmanager::config::Builder::from(&config.load().await).build();
+ secretsmanager::Client::from_conf(ssmconf)
+ } else {
+ let config = aws_config::load_defaults(BehaviorVersion::v2023_11_09()).await;
+ let ssmconf = secretsmanager::config::Builder::from(&config).build();
+ secretsmanager::Client::from_conf(ssmconf)
+ };
+ Ok(Self {
+ client,
+ name: name.to_string(),
+ })
+ }
+}
+
+async fn get_data(
+ mode: &Mode,
+ client: &secretsmanager::Client,
+ pm: &PathMap,
+) -> Result>> {
+ let resp = client
+ .get_secret_value()
+ .secret_id(&pm.path)
+ .send()
+ .await
+ .map_or_else(
+ |e| handle_get_err(mode, e, pm),
+ |res| Ok(res.secret_string().map(std::string::ToString::to_string)),
+ )?;
+
+ if let Some(raw_string) = resp {
+ Ok(Some(serde_json::from_str::>(
+ &raw_string,
+ )?))
+ } else {
+ Ok(None)
+ }
+}
+
+async fn put_data(
+ client: &secretsmanager::Client,
+ pm: &PathMap,
+ data: &BTreeMap,
+) -> Result<()> {
+ if client
+ .get_secret_value()
+ .secret_id(&pm.path)
+ .send()
+ .await
+ .is_ok()
+ {
+ client
+ .put_secret_value()
+ .set_secret_id(Some(pm.path.clone()))
+ .secret_string(serde_json::to_string(&data)?)
+ .send()
+ .await
+ .map_err(|e| Error::PutError {
+ msg: e.to_string(),
+ path: pm.path.clone(),
+ })?;
+ } else {
+ client
+ .create_secret()
+ .set_name(Some(pm.path.clone()))
+ .secret_string(serde_json::to_string(&data)?)
+ .send()
+ .await
+ .map_err(|e| Error::PutError {
+ msg: e.to_string(),
+ path: pm.path.clone(),
+ })?;
+ };
+
+ Ok(())
+}
+
+#[async_trait]
+impl Provider for AWSSecretsManager {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::AWSSecretsManager,
+ name: self.name.clone(),
+ }
+ }
+
+ async fn get(&self, pm: &PathMap) -> Result> {
+ get_data(&Mode::Get, &self.client, pm).await?.map_or_else(
+ || Ok(vec![]),
+ |data| Ok(KV::from_data(&data, pm, &self.kind())),
+ )
+ }
+
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ let mut data = get_data(&Mode::Put, &self.client, pm)
+ .await?
+ .unwrap_or_default();
+ for kv in kvs {
+ data.insert(kv.key.clone(), kv.value.clone());
+ }
+ put_data(&self.client, pm, &data).await
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ if pm.keys.is_empty() {
+ self.client
+ .delete_secret()
+ .secret_id(&pm.path)
+ .send()
+ .await
+ .map_or_else(|e| handle_del_err(e, pm), |_| Ok(()))?;
+ } else {
+ let mut data = get_data(&Mode::Del, &self.client, pm)
+ .await?
+ .unwrap_or_default();
+ for k in pm.keys.keys() {
+ data.remove(k);
+ }
+ put_data(&self.client, pm, &data).await?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::HashMap;
+ use std::env;
+
+ use dockertest_server::servers::cloud::LocalStackServer;
+ use dockertest_server::servers::cloud::LocalStackServerConfig;
+ use dockertest_server::Test;
+
+ use crate::{providers::test_utils, Provider};
+
+ #[test]
+ #[cfg(not(windows))]
+ fn sanity_test() {
+ if env::var("RUNNER_OS").unwrap_or_default() == "macOS" {
+ return;
+ }
+
+ let env: HashMap<_, _> = vec![(
+ "SERVICES".to_string(),
+ "iam,sts,ssm,kms,secretsmanager".to_string(),
+ )]
+ .into_iter()
+ .collect();
+ let config = LocalStackServerConfig::builder()
+ .env(env)
+ .port(4561)
+ .version("2.0.2".into())
+ .build()
+ .unwrap();
+ let mut test = Test::new();
+ test.register(config);
+
+ test.run(|instance| async move {
+ let server: LocalStackServer = instance.server();
+
+ let data = serde_json::json!({
+ "region": "us-east-1",
+ "access_key_id": "stub",
+ "secret_access_key": "stub",
+ "provider_name": "faked",
+ "endpoint_url": server.external_url()
+ });
+
+ let p = Box::new(
+ super::AWSSecretsManager::new(
+ "aws_secretsmanager",
+ Some(serde_json::from_value(data).unwrap()),
+ )
+ .await
+ .unwrap(),
+ ) as Box;
+
+ test_utils::ProviderTest::new(p).run().await;
+ });
+ }
+}
diff --git a/teller-providers/src/providers/dotenv.rs b/teller-providers/src/providers/dotenv.rs
new file mode 100644
index 00000000..ac4fcbdc
--- /dev/null
+++ b/teller-providers/src/providers/dotenv.rs
@@ -0,0 +1,218 @@
+//! `dotenv` Provider
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! dotenv1:
+//! kind: dotenv
+//! # options: ...
+//! ```
+//! ## Options
+//!
+//! See [`DotEnvOptions`]
+//!
+//!
+#![allow(clippy::borrowed_box)]
+use std::fs::File;
+use std::io::prelude::*;
+use std::{
+ collections::{BTreeMap, HashMap},
+ io,
+ path::Path,
+};
+
+use async_trait::async_trait;
+use dotenvy::{self};
+use fs_err as fs;
+use serde_derive::{Deserialize, Serialize};
+
+use super::ProviderKind;
+use crate::config::ProviderInfo;
+use crate::{
+ config::{PathMap, KV},
+ Error, Provider, Result,
+};
+
+#[derive(PartialEq)]
+enum Mode {
+ Get,
+ Put,
+ Del,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct DotEnvOptions {
+ /// create a file if did not exist, when writing new data to provider
+ pub create_on_put: bool,
+}
+
+pub struct Dotenv {
+ pub name: String,
+ opts: DotEnvOptions,
+}
+impl Dotenv {
+ /// Create a new provider
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if cannot create a provider
+ pub fn new(name: &str, opts: Option) -> Result {
+ let opts = opts.unwrap_or_default();
+
+ Ok(Self {
+ name: name.to_string(),
+ opts,
+ })
+ }
+}
+
+fn load(path: &Path, mode: &Mode) -> Result> {
+ let content = fs::File::open(path)?;
+ let mut env = BTreeMap::new();
+
+ if mode == &Mode::Get {
+ let metadata = content.metadata().map_err(|e| Error::GetError {
+ path: format!("{path:?}"),
+ msg: format!("could not get file metadata. err: {e:?}"),
+ })?;
+
+ if metadata.len() == 0 {
+ return Err(Error::NotFound {
+ path: format!("{path:?}"),
+ msg: "file is empty".to_string(),
+ });
+ }
+ }
+
+ for res in dotenvy::Iter::new(&content) {
+ let (k, v) = res.map_err(|e| Error::GetError {
+ path: format!("{path:?}"),
+ msg: e.to_string(),
+ })?;
+ env.insert(k, v);
+ }
+
+ Ok(env)
+}
+// poor man's serialization, loses original comments and formatting
+fn save(path: &Path, data: &BTreeMap) -> Result {
+ let mut out = String::new();
+ for (k, v) in data {
+ let maybe_json: serde_json::Result> =
+ serde_json::from_str(v);
+
+ let json_value = if maybe_json.is_ok() {
+ serde_json::to_string(&v).map(Some).unwrap_or_default()
+ } else {
+ None
+ };
+
+ let value = json_value.unwrap_or_else(|| v.to_string());
+ out.push_str(&format!("{k}={value}\n"));
+ }
+
+ fs::write(path, &out)?;
+ Ok(out)
+}
+
+#[async_trait]
+impl Provider for Dotenv {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::Dotenv,
+ name: self.name.clone(),
+ }
+ }
+
+ async fn get(&self, pm: &PathMap) -> Result> {
+ let data = load(Path::new(&pm.path), &Mode::Get)?;
+ Ok(KV::from_data(&data, pm, &self.kind()))
+ }
+
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ // Create file if not exists + add the option to set is as false
+ self.load_modify_save(
+ pm,
+ |data| {
+ for kv in kvs {
+ data.insert(kv.key.to_string(), kv.value.to_string());
+ }
+ },
+ &Mode::Put,
+ )?;
+ Ok(())
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ self.load_modify_save(
+ pm,
+ |data| {
+ if pm.keys.is_empty() {
+ data.clear();
+ } else {
+ for k in pm.keys.keys() {
+ if data.contains_key(k) {
+ data.remove(k);
+ }
+ }
+ }
+ },
+ &Mode::Del,
+ )?;
+ Ok(())
+ }
+}
+impl Dotenv {
+ fn load_modify_save(&self, pm: &PathMap, modify: F, mode: &Mode) -> Result<()>
+ where
+ F: Fn(&mut BTreeMap),
+ {
+ if mode == &Mode::Put && self.opts.create_on_put {
+ Self::create_empty_file(&pm.path).map_err(|e| Error::GetError {
+ path: format!("{:?}", pm.path),
+ msg: format!("could not create file: {:?}. err: {e:?}", pm.path),
+ })?;
+ }
+ let file = Path::new(&pm.path);
+ let mut data = load(file, mode)?;
+ modify(&mut data);
+ save(file, &data)?;
+ Ok(())
+ }
+
+ fn create_empty_file(path: &str) -> io::Result<()> {
+ if let Some(parent_dir) = Path::new(path).parent() {
+ std::fs::create_dir_all(parent_dir)?;
+ }
+ let mut file = File::create(path)?;
+ file.write_all(b"")?;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use tokio::test;
+
+ use super::*;
+ use crate::providers::test_utils;
+
+ #[test]
+ async fn sanity_test() {
+ let opts = serde_json::json!({
+ "create_on_put": true,
+ });
+
+ let p: Box = Box::new(
+ super::Dotenv::new("dotenv", Some(serde_json::from_value(opts).unwrap())).unwrap(),
+ ) as Box;
+
+ test_utils::ProviderTest::new(p)
+ .with_root_prefix("tmp/dotenv/")
+ .run()
+ .await;
+ }
+}
diff --git a/teller-providers/src/providers/google_secretmanager.rs b/teller-providers/src/providers/google_secretmanager.rs
new file mode 100644
index 00000000..a7b9d0a5
--- /dev/null
+++ b/teller-providers/src/providers/google_secretmanager.rs
@@ -0,0 +1,417 @@
+//! Google Secret Manager
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! gsm1:
+//! kind: google_secretmanager
+//! # options: ...
+//! ```
+//! ## Options
+//!
+//! Uses default GSM options location strategy (by order):
+//!
+//! * Use `GOOGLE_APPLICATION_CREDENTIALS`
+//! * Try `$HOME/.config/gcloud/application_default_credentials.json`
+//!
+//! If you need specific configuration options for this provider, please request via opening an issue.
+//!
+use async_trait::async_trait;
+use google_secretmanager1::{
+ api::{AddSecretVersionRequest, Automatic, Replication, Secret, SecretPayload},
+ hyper::{self, client::HttpConnector},
+ hyper_rustls::{self, HttpsConnector},
+ oauth2::{
+ self,
+ authenticator::{ApplicationDefaultCredentialsTypes, Authenticator},
+ ApplicationDefaultCredentialsAuthenticator, ApplicationDefaultCredentialsFlowOpts,
+ },
+ SecretManager,
+};
+
+use super::ProviderKind;
+use crate::{
+ config::{PathMap, ProviderInfo, KV},
+ Error, Provider, Result,
+};
+
+#[async_trait]
+pub trait GSM {
+ fn get_hub(&self) -> Option<&SecretManager>>;
+ async fn list(&self, name: &str) -> Result>;
+ async fn get(&self, name: &str) -> Result>;
+ async fn put(&self, name: &str, value: &str) -> Result<()>;
+ async fn del(&self, name: &str) -> Result<()>;
+}
+
+pub struct GSMClient {
+ hub: SecretManager>,
+}
+
+impl GSMClient {
+ /// Create a GSM client
+ ///
+ /// # Errors
+ /// Fails if cannot create the client
+ pub async fn new() -> Result {
+ let authenticator = resolve_auth().await.map_err(Box::from)?;
+
+ let hub = SecretManager::new(
+ hyper::Client::builder().build(
+ hyper_rustls::HttpsConnectorBuilder::new()
+ .with_native_roots()
+ .https_or_http()
+ .enable_http1()
+ .enable_http2()
+ .build(),
+ ),
+ authenticator,
+ );
+ Ok(Self { hub })
+ }
+}
+
+#[async_trait]
+impl GSM for GSMClient {
+ fn get_hub(&self) -> Option<&SecretManager>> {
+ Some(&self.hub)
+ }
+
+ async fn list(&self, name: &str) -> Result> {
+ let hub = self.get_hub().expect("hub");
+
+ let (_, secret) = hub
+ .projects()
+ .secrets_list(name)
+ .doit()
+ .await
+ .map_err(|e| Error::ListError {
+ path: name.to_string(),
+ msg: e.to_string(),
+ })?;
+
+ let mut out = Vec::new();
+ if let Some(secrets) = secret.secrets {
+ for secret in &secrets {
+ let secret_name = secret
+ .name
+ .as_ref()
+ .expect("secretmanager API should output a secret resource name");
+
+ if let Some(value) = self.get(secret_name).await? {
+ out.push((secret_name.clone(), value));
+ }
+ }
+ }
+ Ok(out)
+ }
+
+ async fn get(&self, name: &str) -> Result> {
+ let hub = self.get_hub().expect("hub");
+ let resource = if name.contains("/versions") {
+ name.to_string()
+ } else {
+ format!("{name}/versions/latest")
+ };
+
+ let maybe_secret = hub
+ .projects()
+ .secrets_versions_access(&resource)
+ .doit()
+ .await
+ .ok();
+
+ if let Some((_, secret)) = maybe_secret {
+ let payload = secret
+ .payload
+ .ok_or_else(|| Error::Message(format!("no secret payload found in {resource}")))?;
+
+ Ok(payload
+ .data
+ .map(|d| String::from_utf8_lossy(&d).to_string()))
+ } else {
+ Ok(None)
+ }
+ }
+
+ async fn put(&self, name: &str, value: &str) -> Result<()> {
+ let hub = self.get_hub().expect("hub");
+
+ let res = hub.projects().secrets_get(name).doit().await;
+
+ // attempt adding a secret if missing
+ if let Err(err) = res {
+ let repr = err.to_string();
+
+ // F-you google cloud. resorting to checking a
+ // string representation of the error instead of navigating
+ // the horrible error story you give us.
+ if repr.contains("\"NOT_FOUND\"") {
+ if let Some((project, secret_id)) = name.split_once("/secrets/") {
+ hub.projects()
+ .secrets_create(
+ Secret {
+ replication: Some(Replication {
+ automatic: Some(Automatic::default()),
+ user_managed: None,
+ }),
+ ..Secret::default()
+ },
+ project,
+ )
+ .secret_id(secret_id)
+ .doit()
+ .await
+ .map_err(|e| Error::PutError {
+ path: name.to_string(),
+ msg: e.to_string(),
+ })?;
+ }
+ }
+ }
+
+ // add value under a secret version
+ hub.projects()
+ .secrets_add_version(
+ AddSecretVersionRequest {
+ payload: Some(SecretPayload {
+ data: Some(value.as_bytes().to_vec()),
+ data_crc32c: Some(i64::from(crc32c::crc32c(value.as_bytes()))),
+ }),
+ },
+ name,
+ )
+ .doit()
+ .await
+ .map_err(|e| Error::PutError {
+ path: name.to_string(),
+ msg: e.to_string(),
+ })?;
+
+ Ok(())
+ }
+
+ async fn del(&self, name: &str) -> Result<()> {
+ let hub = self.get_hub().expect("hub");
+
+ // only delete if exists
+ if let Ok(Some(_)) = self.get(name).await {
+ hub.projects()
+ .secrets_delete(name)
+ .doit()
+ .await
+ .map_err(|e| Error::DeleteError {
+ path: name.to_string(),
+ msg: e.to_string(),
+ })?;
+ }
+
+ Ok(())
+ }
+}
+
+async fn resolve_auth() -> Result>>
+{
+ //
+ // try SA creds (via env, GOOGLE_APPLICATION_CREDENTIALS)
+ //
+ let service_auth = match ApplicationDefaultCredentialsAuthenticator::builder(
+ ApplicationDefaultCredentialsFlowOpts::default(),
+ )
+ .await
+ {
+ ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => {
+ Ok(auth.build().await.map_err(Box::from)?)
+ }
+ ApplicationDefaultCredentialsTypes::InstanceMetadata(_) => Err(Error::Message(
+ "expected sa detail, found instance metadata".to_string(),
+ )),
+ };
+ if service_auth.is_ok() {
+ return service_auth;
+ }
+
+ //
+ // try user creds
+ //
+ let creds = home::home_dir()
+ .ok_or_else(|| Error::Message("cannot find home dir".to_string()))?
+ .join(".config/gcloud/application_default_credentials.json");
+
+ let user_secret = oauth2::read_authorized_user_secret(creds)
+ .await
+ .map_err(Box::from)?;
+
+ Ok(oauth2::AuthorizedUserAuthenticator::builder(user_secret)
+ .build()
+ .await
+ .map_err(Box::from)?)
+}
+
+pub struct GoogleSecretManager {
+ client: Box,
+ pub name: String,
+}
+
+impl GoogleSecretManager {
+ #[must_use]
+ pub fn new(name: &str, client: Box) -> Self {
+ Self {
+ client,
+ name: name.to_string(),
+ }
+ }
+}
+
+#[async_trait]
+impl Provider for GoogleSecretManager {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::GoogleSecretManager,
+ name: self.name.clone(),
+ }
+ }
+
+ async fn get(&self, pm: &PathMap) -> Result> {
+ let mut out = Vec::new();
+ if pm.keys.is_empty() {
+ // get parameters by path
+ // ("projects/1xxx34/secrets/DSN4", "foobar")
+ let values = self.client.list(&pm.path).await?;
+
+ for (resource, v) in values {
+ // projects/123/secrets/FOOBAR -> FOOBAR
+ // ^-<--<--< rsplit
+ if let Some((_, key)) = resource.rsplit_once('/') {
+ out.push(KV::from_value(&v, key, key, pm, self.kind()));
+ }
+ }
+ } else {
+ for (k, v) in &pm.keys {
+ let resp = self
+ .client
+ .get(&format!("{}/secrets/{}", pm.path, k))
+ .await?;
+ if let Some(val) = resp {
+ out.push(KV::from_value(&val, k, v, pm, self.kind()));
+ }
+ }
+ }
+
+ if out.is_empty() {
+ return Err(Error::NotFound {
+ path: pm.path.to_string(),
+ msg: "path not found".to_string(),
+ });
+ }
+ Ok(out)
+ }
+
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ for kv in kvs {
+ self.client
+ .put(&format!("{}/secrets/{}", pm.path, kv.key), &kv.value)
+ .await?;
+ }
+ Ok(())
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ if pm.keys.is_empty() {
+ let values = self.client.list(&pm.path).await?;
+
+ for (resource, _) in values {
+ self.client.del(&resource).await?;
+ }
+ } else {
+ for k in pm.keys.keys() {
+ self.client
+ .del(&format!("{}/secrets/{}", pm.path, k))
+ .await?;
+ }
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ collections::BTreeMap,
+ sync::{Arc, Mutex},
+ };
+
+ use async_trait::async_trait;
+ use google_secretmanager1::hyper::client::HttpConnector;
+ use google_secretmanager1::hyper_rustls::HttpsConnector;
+ use google_secretmanager1::SecretManager;
+
+ use crate::{
+ providers::{google_secretmanager::GSM, test_utils},
+ Provider, Result,
+ };
+
+ struct MockClient {
+ data: Arc>>,
+ }
+
+ impl MockClient {
+ /// Create a GSM client
+ ///
+ /// # Errors
+ /// Fails if cannot create the client
+ pub fn new() -> Self {
+ Self {
+ data: Arc::new(Mutex::new(BTreeMap::new())),
+ }
+ }
+ }
+
+ #[async_trait]
+ impl GSM for MockClient {
+ fn get_hub(&self) -> Option<&SecretManager>> {
+ None
+ }
+
+ async fn list(&self, name: &str) -> Result> {
+ Ok(self
+ .data
+ .lock()
+ .unwrap()
+ .iter()
+ .filter(|(k, _)| k.starts_with(name))
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect::>())
+ }
+
+ async fn get(&self, name: &str) -> Result> {
+ Ok(self.data.lock().unwrap().get(name).cloned())
+ }
+
+ async fn put(&self, name: &str, value: &str) -> Result<()> {
+ self.data
+ .lock()
+ .unwrap()
+ .insert(name.to_string(), value.to_string());
+ Ok(())
+ }
+
+ async fn del(&self, name: &str) -> Result<()> {
+ self.data.lock().unwrap().remove(name);
+ Ok(())
+ }
+ }
+
+ #[tokio::test]
+ async fn sanity_test() {
+ let mock_client = MockClient::new();
+ let p = Box::new(super::GoogleSecretManager::new(
+ "test",
+ Box::new(mock_client) as Box,
+ )) as Box;
+
+ test_utils::ProviderTest::new(p).run().await;
+ }
+}
diff --git a/teller-providers/src/providers/hashicorp_consul.rs b/teller-providers/src/providers/hashicorp_consul.rs
new file mode 100644
index 00000000..9fdafe8e
--- /dev/null
+++ b/teller-providers/src/providers/hashicorp_consul.rs
@@ -0,0 +1,318 @@
+//! Hashicorp Consul
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! consul1:
+//! kind: hashicorp_consul
+//! # options: ...
+//! ```
+//! ## Options
+//!
+//! See [`HashiCorpConsulOptions`] for more.
+//!
+#![allow(clippy::borrowed_box)]
+use std::env;
+
+use async_trait::async_trait;
+use consulrs::{
+ api::kv::requests::{
+ DeleteKeyRequestBuilder, ReadKeyRequestBuilder, ReadKeysRequestBuilder,
+ SetKeyRequestBuilder,
+ },
+ client::{ConsulClient, ConsulClientSettingsBuilder},
+ error::ClientError,
+ kv as ConsulKV,
+};
+use serde_derive::{Deserialize, Serialize};
+
+use super::ProviderKind;
+use crate::{
+ config::{PathMap, ProviderInfo, KV},
+ Error, Provider, Result,
+};
+
+#[derive(Default, Serialize, Deserialize, Debug, Clone)]
+pub struct HashiCorpConsulOptions {
+ /// Consul address. if is None, search address from `CONSUL_HTTP_ADDR`
+ pub address: Option,
+ /// Consul token. if is None, search address from `CONSUL_HTTP_TOKEN`
+ pub token: Option,
+ /// Specifies the datacenter to query.
+ pub dc: Option,
+}
+
+fn xerr(pm: &PathMap, e: ClientError) -> Error {
+ match e {
+ ClientError::RestClientError { source } => match source {
+ rustify::errors::ClientError::ServerResponseError { code, content } => {
+ match (code, content.clone()) {
+ (404, Some(content))
+ if content.contains("Invalid path for a versioned K/V secrets") =>
+ {
+ Error::PathError(
+ pm.path.clone(),
+ "missing or incompatible protocol version".to_string(),
+ )
+ }
+ (404, _) => Error::NotFound {
+ path: pm.path.clone(),
+ msg: "not found".to_string(),
+ },
+ _ => Error::Message(format!("code: {code}, {content:?}")),
+ }
+ }
+ _ => Error::Any(Box::from(source)),
+ },
+ ClientError::APIError {
+ code: 404,
+ message: _,
+ } => Error::NotFound {
+ path: pm.path.clone(),
+ msg: "not found".to_string(),
+ },
+ _ => Error::Any(Box::from(e)),
+ }
+}
+
+pub struct HashiCorpConsul {
+ pub client: ConsulClient,
+ opts: HashiCorpConsulOptions,
+ pub name: String,
+}
+
+impl HashiCorpConsul {
+ #[must_use]
+ pub fn with_client(name: &str, client: ConsulClient) -> Self {
+ Self {
+ client,
+ opts: HashiCorpConsulOptions::default(),
+ name: name.to_string(),
+ }
+ }
+
+ /// Create a new hashicorp Consul
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if cannot create a provider
+ pub fn new(name: &str, opts: Option) -> Result {
+ let opts = opts.unwrap_or_default();
+
+ let address = opts
+ .address
+ .as_ref()
+ .map_or_else(
+ || env::var("CONSUL_HTTP_ADDR"),
+ |address| Ok(address.to_string()),
+ )
+ .map_err(|_| Error::Message("Consul address not present.".to_string()))?;
+
+ let token = opts
+ .token
+ .as_ref()
+ .map_or_else(
+ || env::var("CONSUL_HTTP_TOKEN"),
+ |token| Ok(token.to_string()),
+ )
+ .unwrap_or_default();
+
+ let settings = ConsulClientSettingsBuilder::default()
+ .address(address)
+ .token(token)
+ .build()
+ .map_err(Box::from)?;
+
+ let client = ConsulClient::new(settings).map_err(Box::from)?;
+
+ Ok(Self {
+ client,
+ opts,
+ name: name.to_string(),
+ })
+ }
+}
+
+impl HashiCorpConsul {
+ fn prepare_get_builder_request(&self) -> ReadKeyRequestBuilder {
+ let mut opts: ReadKeyRequestBuilder = ReadKeyRequestBuilder::default();
+ if let Some(dc) = self.opts.dc.as_ref() {
+ opts.dc(dc.to_string());
+ }
+ opts.recurse(true);
+ opts
+ }
+
+ fn prepare_put_builder_request(&self) -> SetKeyRequestBuilder {
+ let mut opts: SetKeyRequestBuilder = SetKeyRequestBuilder::default();
+ if let Some(dc) = self.opts.dc.as_ref() {
+ opts.dc(dc.to_string());
+ }
+ opts
+ }
+
+ fn prepare_delete_builder_request(&self) -> DeleteKeyRequestBuilder {
+ let mut opts: DeleteKeyRequestBuilder = DeleteKeyRequestBuilder::default();
+ if let Some(dc) = self.opts.dc.as_ref() {
+ opts.dc(dc.to_string());
+ }
+ opts.recurse(false);
+ opts
+ }
+
+ fn prepare_keys_builder_request(&self) -> ReadKeysRequestBuilder {
+ let mut opts: ReadKeysRequestBuilder = ReadKeysRequestBuilder::default();
+ if let Some(dc) = self.opts.dc.as_ref() {
+ opts.dc(dc.to_string());
+ }
+ opts.recurse(false);
+ opts
+ }
+}
+#[async_trait]
+impl Provider for HashiCorpConsul {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::HashiCorpConsul,
+ name: self.name.clone(),
+ }
+ }
+
+ async fn get(&self, pm: &PathMap) -> Result> {
+ let res = ConsulKV::read(
+ &self.client,
+ &pm.path,
+ Some(&mut self.prepare_get_builder_request()),
+ )
+ .await
+ .map_err(|e| xerr(pm, e))?;
+
+ let mut results = vec![];
+ for kv_pair in res.response {
+ let kv_value = kv_pair.value.ok_or_else(|| Error::NotFound {
+ path: pm.path.to_string(),
+ msg: "value not found".to_string(),
+ })?;
+
+ let val: String = kv_value.try_into().map_err(|e| Error::GetError {
+ path: pm.path.to_string(),
+ msg: format!("could not decode Base64 value. err: {e:?}"),
+ })?;
+
+ let (_, key) = kv_pair.key.rsplit_once('/').unwrap_or(("", &kv_pair.key));
+
+ results.push(KV::from_value(&val, key, key, pm, self.kind()));
+ }
+
+ Ok(results)
+ }
+
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ for kv in kvs {
+ ConsulKV::set(
+ &self.client,
+ &format!("{}/{}", pm.path, kv.key),
+ kv.value.as_bytes(),
+ Some(&mut self.prepare_put_builder_request()),
+ )
+ .await
+ .map_err(|e| Error::PutError {
+ path: pm.path.to_string(),
+ msg: format!(
+ "could not put value in key {}. err: {:?}",
+ kv.key.as_str(),
+ e
+ ),
+ })?;
+ }
+ Ok(())
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ let keys = if pm.keys.is_empty() {
+ ConsulKV::keys(
+ &self.client,
+ pm.path.as_str(),
+ Some(&mut self.prepare_keys_builder_request()),
+ )
+ .await
+ .map_err(|e| Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: format!(
+ "could not get keys in path: {}. err: {:?}",
+ pm.path.as_str(),
+ e
+ ),
+ })?
+ .response
+ } else {
+ pm.keys
+ .keys()
+ .map(|kv| format!("{}/{kv}", &pm.path))
+ .collect::>()
+ };
+
+ for key in keys {
+ ConsulKV::delete(
+ &self.client,
+ key.as_str(),
+ Some(&mut self.prepare_delete_builder_request()),
+ )
+ .await
+ .map_err(|e| Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: format!("could not delete key: {}. err: {:?}", pm.path.as_str(), e),
+ })?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use dockertest_server::servers::hashi::{ConsulServer, ConsulServerConfig};
+ use dockertest_server::Test;
+
+ use super::*;
+ use crate::providers::test_utils;
+
+ const PORT: u32 = 8501;
+
+ #[test]
+ #[cfg(not(windows))]
+ fn sanity_test() {
+ if env::var("RUNNER_OS").unwrap_or_default() == "macOS" {
+ return;
+ }
+
+ let config = ConsulServerConfig::builder()
+ .port(PORT)
+ .version("1.15.4".into())
+ .build()
+ .unwrap();
+ let mut test = Test::new();
+ test.register(config);
+
+ test.run(|instance| async move {
+ let server: ConsulServer = instance.server();
+
+ let data = serde_json::json!({
+ "address": server.external_url(),
+ });
+
+ let p = Box::new(
+ super::HashiCorpConsul::new(
+ "hashicorp_consul",
+ Some(serde_json::from_value(data).unwrap()),
+ )
+ .unwrap(),
+ ) as Box;
+
+ test_utils::ProviderTest::new(p).run().await;
+ });
+ }
+}
diff --git a/teller-providers/src/providers/hashicorp_vault.rs b/teller-providers/src/providers/hashicorp_vault.rs
new file mode 100644
index 00000000..c1d90fc3
--- /dev/null
+++ b/teller-providers/src/providers/hashicorp_vault.rs
@@ -0,0 +1,314 @@
+//! Hashicorp Vault
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! vault1:
+//! kind: hashicorp_vault
+//! # options: ...
+//! ```
+//! ## Options
+//!
+//! See [`HashivaultOptions`] for more.
+//!
+#![allow(clippy::borrowed_box)]
+use std::{
+ collections::{BTreeMap, HashMap},
+ env,
+};
+
+use async_trait::async_trait;
+use serde_derive::{Deserialize, Serialize};
+use vaultrs::{
+ client::{VaultClient, VaultClientSettingsBuilder},
+ error::ClientError,
+ kv1, kv2,
+};
+
+use super::ProviderKind;
+use crate::{
+ config::{PathMap, ProviderInfo, KV},
+ Error, Provider, Result,
+};
+
+/// # Hashicorp options
+///
+/// If no options provided at all, will take `VAULT_ADDR` and `VAULT_TOKEN` env variables.
+/// If partial options provided, will only take what's provided.
+///
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct HashivaultOptions {
+ /// Vault address
+ pub address: Option,
+ /// Vault token
+ pub token: Option,
+}
+
+pub struct Hashivault {
+ pub client: VaultClient,
+ pub name: String,
+}
+
+impl Hashivault {
+ /// Create a new hashicorp vault
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if cannot create a provider
+ pub fn new(name: &str, opts: Option) -> Result {
+ let settings = if let Some(opts) = opts {
+ let mut settings = VaultClientSettingsBuilder::default();
+
+ if let Some(address) = opts.address {
+ settings.address(address);
+ }
+
+ if let Some(token) = opts.token {
+ settings.token(token);
+ }
+
+ settings.build().map_err(Box::from)?
+ } else {
+ VaultClientSettingsBuilder::default()
+ .address(env::var("VAULT_ADDR")?)
+ .token(env::var("VAULT_TOKEN")?)
+ .build()
+ .map_err(Box::from)?
+ };
+
+ let client = VaultClient::new(settings).map_err(Box::from)?;
+
+ Ok(Self {
+ client,
+ name: name.to_string(),
+ })
+ }
+}
+
+fn parse_path(pm: &PathMap) -> Result<(&str, &str, &str)> {
+ let (engine, full_path) = (pm.protocol.as_deref().unwrap_or("kv2"), pm.path.as_str());
+ let (mount, path) = full_path.split_once('/').ok_or_else(|| {
+ Error::Message(
+ "path must have initial mount seperated by '/', e.g. `secret/foo`".to_string(),
+ )
+ })?;
+ Ok((engine, mount, path))
+}
+
+fn xerr(pm: &PathMap, e: ClientError) -> Error {
+ match e {
+ ClientError::RestClientError { source } => match source {
+ rustify::errors::ClientError::ServerResponseError { code, content } => {
+ match (code, content.clone()) {
+ (404, Some(content))
+ if content.contains("Invalid path for a versioned K/V secrets") =>
+ {
+ Error::PathError(
+ pm.path.clone(),
+ "missing or incompatible protocol version".to_string(),
+ )
+ }
+ (404, _) => Error::NotFound {
+ path: pm.path.clone(),
+ msg: "not found".to_string(),
+ },
+ _ => Error::Message(format!("code: {code}, {content:?}")),
+ }
+ }
+ _ => Error::Any(Box::from(source)),
+ },
+ ClientError::APIError {
+ code: 404,
+ errors: _,
+ } => Error::NotFound {
+ path: pm.path.clone(),
+ msg: "not found".to_string(),
+ },
+ _ => Error::Any(Box::from(e)),
+ }
+}
+
+async fn get_data(client: &VaultClient, pm: &PathMap) -> Result> {
+ let (engine, mount, path) = parse_path(pm)?;
+ let data = if engine == "kv2" {
+ kv2::read(client, mount, path).await
+ } else {
+ kv1::get(client, mount, path).await
+ }
+ .map_err(|e| xerr(pm, e))?;
+
+ Ok(data)
+}
+
+async fn get_data_or_empty(client: &VaultClient, pm: &PathMap) -> Result> {
+ let data = match get_data(client, pm).await {
+ Ok(data) => data,
+ Err(Error::NotFound { path: _, msg: _ }) => BTreeMap::new(),
+ Err(e) => return Err(e),
+ };
+ Ok(data)
+}
+
+async fn put_data(
+ client: &VaultClient,
+ pm: &PathMap,
+ data: &BTreeMap,
+) -> Result<()> {
+ let (engine, mount, path) = parse_path(pm)?;
+ if engine == "kv2" {
+ kv2::set(client, mount, path, data)
+ .await
+ .map_err(|e| xerr(pm, e))?;
+ } else {
+ kv1::set(
+ client,
+ mount,
+ path,
+ &data
+ .iter()
+ .map(|(k, v)| (k.as_str(), v.as_str()))
+ .collect::>(),
+ )
+ .await
+ .map_err(|e| xerr(pm, e))?;
+ };
+ Ok(())
+}
+
+#[async_trait]
+impl Provider for Hashivault {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::Hashicorp,
+ name: self.name.clone(),
+ }
+ }
+
+ async fn get(&self, pm: &PathMap) -> Result> {
+ Ok(KV::from_data(
+ &get_data(&self.client, pm).await.map_err(|e| match e {
+ Error::NotFound { path, msg } => Error::NotFound { path, msg },
+ _ => Error::GetError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ },
+ })?,
+ pm,
+ &self.kind(),
+ ))
+ }
+
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ let mut data = get_data_or_empty(&self.client, pm)
+ .await
+ .map_err(|e| Error::PutError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })?;
+ for kv in kvs {
+ data.insert(kv.key.clone(), kv.value.clone());
+ }
+ put_data(&self.client, pm, &data)
+ .await
+ .map_err(|e| Error::PutError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })?;
+ Ok(())
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ // if pm contains specific keys, we cannot delete the path,
+ // deleting a complete path may drop everything under it (a path stores a dictionary of k/v)
+ // we want to remove the keys from the secret object and re-write it into its path.
+ if !pm.keys.is_empty() {
+ let mut data =
+ get_data_or_empty(&self.client, pm)
+ .await
+ .map_err(|e| Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })?;
+ for key in pm.keys.keys() {
+ data.remove(key);
+ }
+ put_data(&self.client, pm, &data)
+ .await
+ .map_err(|e| Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })?;
+ return Ok(());
+ }
+
+ // otherwise, delete the whole path
+ let (engine, mount, path) = parse_path(pm)?;
+ if engine == "kv2" {
+ kv2::delete_latest(&self.client, mount, path)
+ .await
+ .map_err(|e| xerr(pm, e))
+ .map_err(|e| Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })?;
+ } else {
+ kv1::delete(&self.client, mount, path)
+ .await
+ .map_err(|e| xerr(pm, e))
+ .map_err(|e| Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ })?;
+ };
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use dockertest_server::servers::hashi::{VaultServer, VaultServerConfig};
+ use dockertest_server::Test;
+
+ use super::*;
+ use crate::providers::test_utils;
+
+ const PORT: u32 = 9200;
+
+ #[test]
+ #[cfg(not(windows))]
+ fn sanity_test() {
+ if env::var("RUNNER_OS").unwrap_or_default() == "macOS" {
+ return;
+ }
+
+ let config = VaultServerConfig::builder()
+ .port(PORT)
+ .version("1.8.2".into())
+ .build()
+ .unwrap();
+ let mut test = Test::new();
+ test.register(config);
+
+ test.run(|instance| async move {
+ let server: VaultServer = instance.server();
+
+ let data = serde_json::json!({
+ "address": server.external_url(),
+ "token": server.token
+ });
+
+ let p = Box::new(
+ super::Hashivault::new(
+ "hashicorp_vault",
+ Some(serde_json::from_value(data).unwrap()),
+ )
+ .unwrap(),
+ ) as Box;
+
+ test_utils::ProviderTest::new(p).run().await;
+ });
+ }
+}
diff --git a/teller-providers/src/providers/inmem.rs b/teller-providers/src/providers/inmem.rs
new file mode 100644
index 00000000..b025df81
--- /dev/null
+++ b/teller-providers/src/providers/inmem.rs
@@ -0,0 +1,147 @@
+//! In-memory Store
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! inmem1:
+//! kind: inmem
+//! options:
+//! key1: value
+//! key1: value
+//! ```
+//! ## Options
+//!
+//! The options to the inmem store are actually its initial data
+//! representation and can be any `serde_json::Value` that can convert to
+//! a `BTreeMap` (hashmap)
+//!
+use std::{collections::BTreeMap, sync::Mutex};
+
+use async_trait::async_trait;
+
+use super::ProviderKind;
+use crate::{
+ config::{PathMap, ProviderInfo, KV},
+ Error, Provider, Result,
+};
+
+pub struct Inmem {
+ store: Mutex>>,
+ name: String,
+}
+
+impl Inmem {
+ /// Build from YAML
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if serialization fails
+ pub fn from_yaml(name: &str, yaml: &str) -> Result {
+ Ok(Self {
+ store: Mutex::new(serde_yaml::from_str(yaml)?),
+ name: name.to_string(),
+ })
+ }
+
+ /// Create an inmem provider.
+ /// `opts` is the memory map of the provider, which is a `BTreeMap>`,
+ /// or in YAML:
+ ///
+ /// ```yaml
+ /// production/foo/bar:
+ /// key: v
+ /// baz: bar
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if creation fails
+ pub fn new(name: &str, opts: Option) -> Result {
+ Ok(if let Some(opts) = opts {
+ Self {
+ store: Mutex::new(serde_json::from_value(opts)?),
+ name: name.to_string(),
+ }
+ } else {
+ Self {
+ store: Mutex::new(BTreeMap::default()),
+ name: name.to_string(),
+ }
+ })
+ }
+
+ /// Returns the get state of this [`Inmem`].
+ ///
+ /// # Panics
+ ///
+ /// Panics if lock cannot be acquired
+ pub fn get_state(&self) -> BTreeMap> {
+ self.store
+ .lock()
+ .expect("inmem store failed getting a lock")
+ .clone()
+ }
+}
+
+#[async_trait]
+impl Provider for Inmem {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::Inmem,
+ name: self.name.clone(),
+ }
+ }
+
+ #[allow(clippy::significant_drop_tightening)]
+ async fn get(&self, pm: &PathMap) -> Result> {
+ let store = self.store.lock().unwrap();
+ let data = store.get(&pm.path).ok_or_else(|| Error::NotFound {
+ path: pm.path.to_string(),
+ msg: "not found".to_string(),
+ })?;
+ Ok(KV::from_data(data, pm, &self.kind()))
+ }
+ #[allow(clippy::significant_drop_tightening)]
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ let mut store = self.store.lock().unwrap();
+ let mut data = store.get(&pm.path).cloned().unwrap_or_default();
+ for kv in kvs {
+ data.insert(kv.key.clone(), kv.value.clone());
+ }
+ store.insert(pm.path.clone(), data);
+ Ok(())
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ if pm.keys.is_empty() {
+ self.store.lock().unwrap().remove(&pm.path);
+ } else {
+ let mut store = self.store.lock().unwrap();
+ let mut data = store.get(&pm.path).cloned().unwrap_or_default();
+ for key in pm.keys.keys() {
+ data.remove(key);
+ }
+ store.insert(pm.path.clone(), data);
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use tokio::test;
+
+ use crate::providers::test_utils;
+ use crate::Provider;
+
+ #[test]
+ async fn sanity_test() {
+ let p =
+ Box::new(super::Inmem::new("test", None).unwrap()) as Box;
+
+ test_utils::ProviderTest::new(p).run().await;
+ }
+}
diff --git a/teller-providers/src/providers/mod.rs b/teller-providers/src/providers/mod.rs
new file mode 100644
index 00000000..6d46b181
--- /dev/null
+++ b/teller-providers/src/providers/mod.rs
@@ -0,0 +1,90 @@
+use std::{collections::HashMap, str::FromStr};
+
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
+use serde_variant::to_variant_name;
+use strum::{EnumIter, IntoEnumIterator};
+
+#[cfg(test)]
+mod test_utils;
+
+#[cfg(feature = "dotenv")]
+pub mod dotenv;
+pub mod inmem;
+
+#[cfg(feature = "hashicorp_vault")]
+pub mod hashicorp_vault;
+
+#[cfg(feature = "ssm")]
+pub mod ssm;
+
+#[cfg(feature = "aws_secretsmanager")]
+pub mod aws_secretsmanager;
+
+#[cfg(feature = "google_secretmanager")]
+pub mod google_secretmanager;
+
+#[cfg(feature = "hashicorp_consul")]
+pub mod hashicorp_consul;
+
+lazy_static! {
+ pub static ref PROVIDER_KINDS: String = {
+ let providers: Vec = ProviderKind::iter()
+ .map(|provider| provider.to_string())
+ .collect();
+ providers.join(", ")
+ };
+}
+#[derive(
+ Serialize, Deserialize, Debug, Clone, Default, PartialOrd, Ord, PartialEq, Eq, EnumIter,
+)]
+pub enum ProviderKind {
+ #[serde(rename = "inmem")]
+ Inmem,
+
+ #[default]
+ #[cfg(feature = "dotenv")]
+ #[serde(rename = "dotenv")]
+ Dotenv,
+
+ #[cfg(feature = "hashicorp_vault")]
+ #[serde(rename = "hashicorp")]
+ Hashicorp,
+
+ #[cfg(feature = "hashicorp_consul")]
+ #[serde(rename = "hashicorp_consul")]
+ HashiCorpConsul,
+
+ #[cfg(feature = "ssm")]
+ #[serde(rename = "ssm")]
+ SSM,
+
+ #[cfg(feature = "aws_secretsmanager")]
+ #[serde(rename = "aws_secretsmanager")]
+ AWSSecretsManager,
+
+ #[cfg(feature = "google_secretmanager")]
+ #[serde(rename = "google_secretmanager")]
+ GoogleSecretManager,
+}
+
+impl std::fmt::Display for ProviderKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ to_variant_name(self).expect("only enum supported").fmt(f)
+ }
+}
+
+impl FromStr for ProviderKind {
+ type Err = &'static str;
+
+ fn from_str(input: &str) -> Result {
+ let providers = Self::iter()
+ .map(|provider| (provider.to_string(), provider))
+ .collect::>();
+
+ providers.get(input).map_or_else(
+ || Err(&PROVIDER_KINDS as &'static str),
+ |provider| Ok(provider.clone()),
+ )
+ }
+}
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[del-keys-secret_development].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[del-keys-secret_development].snap
new file mode 100644
index 00000000..b9a67bda
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[del-keys-secret_development].snap
@@ -0,0 +1,7 @@
+---
+source: src/providers/test_utils.rs
+expression: delete_res
+---
+Ok(
+ (),
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[del-secret_multiple_app-1].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[del-secret_multiple_app-1].snap
new file mode 100644
index 00000000..b9a67bda
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[del-secret_multiple_app-1].snap
@@ -0,0 +1,7 @@
+---
+source: src/providers/test_utils.rs
+expression: delete_res
+---
+Ok(
+ (),
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-after-update-secret_multiple_app-2].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-after-update-secret_multiple_app-2].snap
new file mode 100644
index 00000000..83a00f6c
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-after-update-secret_multiple_app-2].snap
@@ -0,0 +1,33 @@
+---
+source: teller-providers/src/providers/test_utils.rs
+expression: read_after_update_res
+---
+Ok(
+ [
+ KV {
+ value: "baz",
+ key: "foo",
+ from_key: "foo",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/multiple/app-2",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ ],
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-del-keys-secret_development].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-del-keys-secret_development].snap
new file mode 100644
index 00000000..143f8a69
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-del-keys-secret_development].snap
@@ -0,0 +1,33 @@
+---
+source: teller-providers/src/providers/test_utils.rs
+expression: get_del_res
+---
+Ok(
+ [
+ KV {
+ value: "{\"DB_PASS\": \"1234\",\"DB_NAME\": \"FOO\"}",
+ key: "db",
+ from_key: "db",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/development",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ ],
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_development].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_development].snap
new file mode 100644
index 00000000..44b168bf
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_development].snap
@@ -0,0 +1,81 @@
+---
+source: teller-providers/src/providers/test_utils.rs
+expression: res
+---
+[
+ KV {
+ value: "DEBUG",
+ key: "log_level",
+ from_key: "log_level",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/development",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ KV {
+ value: "Teller",
+ key: "app",
+ from_key: "app",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/development",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+ KV {
+ value: "{\"DB_PASS\": \"1234\",\"DB_NAME\": \"FOO\"}",
+ key: "db",
+ from_key: "db",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/development",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+]
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_multiple_app-1].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_multiple_app-1].snap
new file mode 100644
index 00000000..4fef6a5d
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_multiple_app-1].snap
@@ -0,0 +1,31 @@
+---
+source: teller-providers/src/providers/test_utils.rs
+expression: res
+---
+[
+ KV {
+ value: "DEBUG",
+ key: "log_level",
+ from_key: "log_level",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/multiple/app-1",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+]
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_multiple_app-2].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_multiple_app-2].snap
new file mode 100644
index 00000000..6aed024e
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[get-secret_multiple_app-2].snap
@@ -0,0 +1,31 @@
+---
+source: teller-providers/src/providers/test_utils.rs
+expression: res
+---
+[
+ KV {
+ value: "bar",
+ key: "foo",
+ from_key: "foo",
+ path: Some(
+ PathInfo {
+ id: "",
+ path: "secret/multiple/app-2",
+ },
+ ),
+ provider: Some(
+ ProviderInfo {
+ kind: PROVIDER_KIND,
+ name: PROVIDER_NAME,
+ },
+ ),
+ meta: Some(
+ MetaInfo {
+ sensitivity: None,
+ redact_with: None,
+ source: None,
+ sink: None,
+ },
+ ),
+ },
+]
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_development].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_development].snap
new file mode 100644
index 00000000..ce029a19
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_development].snap
@@ -0,0 +1,7 @@
+---
+source: src/providers/test_utils.rs
+expression: res
+---
+Ok(
+ (),
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_multiple_app-1].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_multiple_app-1].snap
new file mode 100644
index 00000000..ce029a19
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_multiple_app-1].snap
@@ -0,0 +1,7 @@
+---
+source: src/providers/test_utils.rs
+expression: res
+---
+Ok(
+ (),
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_multiple_app-2].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_multiple_app-2].snap
new file mode 100644
index 00000000..ce029a19
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-secret_multiple_app-2].snap
@@ -0,0 +1,7 @@
+---
+source: src/providers/test_utils.rs
+expression: res
+---
+Ok(
+ (),
+)
diff --git a/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-update-secret_multiple_app-2].snap b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-update-secret_multiple_app-2].snap
new file mode 100644
index 00000000..014217e5
--- /dev/null
+++ b/teller-providers/src/providers/snapshots/teller_providers__providers__test_utils__[put-update-secret_multiple_app-2].snap
@@ -0,0 +1,7 @@
+---
+source: src/providers/test_utils.rs
+expression: update_res
+---
+Ok(
+ (),
+)
diff --git a/teller-providers/src/providers/ssm.rs b/teller-providers/src/providers/ssm.rs
new file mode 100644
index 00000000..be720d0d
--- /dev/null
+++ b/teller-providers/src/providers/ssm.rs
@@ -0,0 +1,292 @@
+//! AWS SSM
+//!
+//!
+//! ## Example configuration
+//!
+//! ```yaml
+//! providers:
+//! ssm1:
+//! kind: ssm
+//! # options: ...
+//! ```
+//! ## Options
+//!
+//! See [`SSMOptions`]
+//!
+//!
+#![allow(clippy::borrowed_box)]
+use async_trait::async_trait;
+use aws_config::{self, BehaviorVersion};
+use aws_sdk_ssm as ssm;
+use serde_derive::{Deserialize, Serialize};
+use ssm::config::{Credentials, Region};
+use ssm::{
+ error::SdkError, operation::delete_parameter::DeleteParameterError, types::ParameterType,
+};
+
+use super::ProviderKind;
+use crate::config::{PathMap, ProviderInfo, KV};
+use crate::Provider;
+use crate::{Error, Result};
+
+fn handle_delete(e: SdkError, pm: &PathMap) -> Result<()> {
+ match e.into_service_error() {
+ DeleteParameterError::ParameterNotFound(_) => {
+ // we're ok
+ Ok(())
+ }
+ e => Err(Error::DeleteError {
+ path: pm.path.to_string(),
+ msg: e.to_string(),
+ }),
+ }
+}
+
+fn join_path(left: &str, right: &str) -> String {
+ format!(
+ "{}/{}",
+ left.trim_end_matches('/'),
+ right.trim_start_matches('/')
+ )
+}
+/// # AWS SSM configuration
+///
+/// This holds the most commonly used and simplified configuration options for this provider. These
+/// paramters can be used in the Teller YAML configuration.
+///
+/// For indepth description of each parameter see: [AWS SDK config](https://docs.rs/aws-config/latest/aws_config/struct.SdkConfig.html)
+///
+/// If you need an additional parameter from the AWS SDK included in our simplified configuration,
+/// open an issue in Teller and request to add it.
+///
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SSMOptions {
+ pub region: Option,
+ pub access_key_id: Option,
+ pub secret_access_key: Option,
+ pub endpoint_url: Option,
+}
+
+pub struct SSM {
+ pub name: String,
+ pub client: ssm::Client,
+}
+impl SSM {
+ #[must_use]
+ pub fn with_client(name: &str, client: ssm::Client) -> Self {
+ Self {
+ name: name.to_string(),
+ client,
+ }
+ }
+
+ /// Create a new ssm provider
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if cannot create a provider
+ pub async fn new(name: &str, opts: Option) -> Result {
+ let client = if let Some(opts) = opts {
+ let opts: SSMOptions = serde_json::from_value(opts)?;
+
+ let mut config = aws_config::defaults(BehaviorVersion::v2023_11_09());
+ if let (Some(key), Some(secret)) = (opts.access_key_id, opts.secret_access_key) {
+ config = config
+ .credentials_provider(Credentials::new(key, secret, None, None, "teller"));
+ }
+ if let Some(endpoint_url) = opts.endpoint_url {
+ config = config.endpoint_url(endpoint_url);
+ }
+ if let Some(region) = opts.region {
+ config = config.region(Region::new(region));
+ }
+ let ssmconf = ssm::config::Builder::from(&config.load().await).build();
+ ssm::Client::from_conf(ssmconf)
+ } else {
+ let config = aws_config::load_defaults(BehaviorVersion::v2023_11_09()).await;
+ let ssmconf = ssm::config::Builder::from(&config).build();
+ ssm::Client::from_conf(ssmconf)
+ };
+ Ok(Self {
+ client,
+ name: name.to_string(),
+ })
+ }
+}
+
+#[async_trait]
+impl Provider for SSM {
+ fn kind(&self) -> ProviderInfo {
+ ProviderInfo {
+ kind: ProviderKind::SSM,
+ name: self.name.clone(),
+ }
+ }
+
+ async fn get(&self, pm: &PathMap) -> Result> {
+ let mut out = Vec::new();
+ if pm.keys.is_empty() {
+ // get parameters by path
+ let resp = self
+ .client
+ .get_parameters_by_path()
+ .path(&pm.path)
+ .with_decryption(pm.decrypt)
+ .send()
+ .await
+ .map_err(|e| Error::GetError {
+ msg: e.to_string(),
+ path: pm.path.clone(),
+ })?;
+
+ let params = resp.parameters();
+ if params.is_empty() {
+ return Err(Error::NotFound {
+ msg: "not found".to_string(),
+ path: pm.path.clone(),
+ });
+ }
+ for p in params {
+ let ssm_key = p.name().unwrap_or_default();
+ if !ssm_key.starts_with(&pm.path) {
+ return Err(Error::GetError {
+ path: pm.path.clone(),
+ msg: format!("{ssm_key} is not contained in root path"),
+ });
+ }
+
+ let relative_key = ssm_key
+ .strip_prefix(&pm.path)
+ .map(|k| k.trim_start_matches('/'))
+ .unwrap_or_default();
+
+ out.push(KV::from_value(
+ p.value().unwrap_or_default(),
+ relative_key,
+ relative_key,
+ pm,
+ self.kind(),
+ ));
+ }
+ } else {
+ for (k, v) in &pm.keys {
+ let resp = self
+ .client
+ .get_parameter()
+ .name(join_path(&pm.path, k))
+ .with_decryption(pm.decrypt)
+ .send()
+ .await
+ .map_err(|e| Error::GetError {
+ msg: e.to_string(),
+ path: pm.path.clone(),
+ })?;
+ let param = resp.parameter();
+ if let Some(p) = param {
+ out.push(KV::from_value(
+ p.value().unwrap_or_default(),
+ k,
+ v,
+ pm,
+ self.kind(),
+ ));
+ }
+ }
+ }
+
+ Ok(out)
+ }
+
+ async fn put(&self, pm: &PathMap, kvs: &[KV]) -> Result<()> {
+ for kv in kvs {
+ // proper separator sensitive concat
+ let path = format!("{}/{}", pm.path, kv.key);
+ self.client
+ .put_parameter()
+ .name(&path)
+ .value(&kv.value)
+ .overwrite(true)
+ .r#type(ParameterType::String)
+ .send()
+ .await
+ .map_err(|e| Error::PutError {
+ msg: e.to_string(),
+ path,
+ })?;
+ }
+ Ok(())
+ }
+
+ async fn del(&self, pm: &PathMap) -> Result<()> {
+ let paths = if pm.keys.is_empty() {
+ let kvs = self.get(pm).await?;
+ kvs.iter()
+ .map(|kv| join_path(&pm.path, &kv.key))
+ .collect::>()
+ } else {
+ pm.keys
+ .keys()
+ .map(|k| join_path(&pm.path, k))
+ .collect::>()
+ };
+
+ for path in paths {
+ let res = self.client.delete_parameter().name(path).send().await;
+ res.map_or_else(|e| handle_delete(e, pm), |_| Ok(()))?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::HashMap;
+ use std::env;
+
+ use dockertest_server::servers::cloud::LocalStackServer;
+ use dockertest_server::servers::cloud::LocalStackServerConfig;
+ use dockertest_server::Test;
+
+ use super::*;
+ use crate::providers::test_utils;
+
+ #[test]
+ #[cfg(not(windows))]
+ fn sanity_test() {
+ if env::var("RUNNER_OS").unwrap_or_default() == "macOS" {
+ return;
+ }
+
+ let env: HashMap<_, _> = vec![("SERVICES".to_string(), "iam,sts,ssm,kms".to_string())]
+ .into_iter()
+ .collect();
+ let config = LocalStackServerConfig::builder()
+ .env(env)
+ .port(4551)
+ .version("2.0.2".into())
+ .build()
+ .unwrap();
+ let mut test = Test::new();
+ test.register(config);
+
+ test.run(|instance| async move {
+ let server: LocalStackServer = instance.server();
+ let data = serde_json::json!({
+ "region": "us-east-1",
+ "access_key_id": "stub",
+ "secret_access_key": "stub",
+ "provider_name": "faked",
+ "endpoint_url": server.external_url(),
+ });
+
+ let p = Box::new(super::SSM::new("ssm", Some(data)).await.unwrap())
+ as Box;
+
+ test_utils::ProviderTest::new(p)
+ .with_root_prefix("/")
+ .run()
+ .await;
+ });
+ }
+}
diff --git a/teller-providers/src/providers/test_utils.rs b/teller-providers/src/providers/test_utils.rs
new file mode 100644
index 00000000..83e92556
--- /dev/null
+++ b/teller-providers/src/providers/test_utils.rs
@@ -0,0 +1,316 @@
+use std::collections::{BTreeMap, HashMap};
+
+use insta::{assert_debug_snapshot, with_settings};
+
+use crate::config::{PathMap, KV};
+use crate::{Error, Provider};
+
+pub const ROOT_PATH_A: &str = "secret/development";
+pub const ROOT_PATH_B: &str = "secret/multiple/app-1";
+pub const ROOT_PATH_C: &str = "secret/multiple/app-2";
+const PATH_A_KEY_1: &str = "db";
+const PATH_A_KEY_2: &str = "log_level";
+const PATH_A_KEY_3: &str = "app";
+const PATH_A_VALUE_1: &str = "{\"DB_PASS\": \"1234\",\"DB_NAME\": \"FOO\"}";
+const PATH_A_VALUE_2: &str = "DEBUG";
+const PATH_A_VALUE_3: &str = "Teller";
+const PATH_B_KEY_1: &str = "log_level";
+const PATH_B_VALUE_1: &str = "DEBUG";
+const PATH_C_KEY_1: &str = "foo";
+const PATH_C_VALUE_1: &str = "bar";
+const PATH_C_VALUE_1_UPDATE: &str = "baz";
+
+pub struct ProviderTest {
+ /// Adding the given prefix to all root path keys. you should use in case you want to change the root path key
+ /// in case your provider is required a different path from the test case.
+ /// In the all snapshots tests, the give value wan clean and you will not see it to aliment all the providers returns the sane response
+ pub root_prefix: Option,
+
+ pub provider: Box,
+}
+
+/// A struct representing a test suite for validating a Teller provider's functionality.
+///
+/// This struct encapsulates a set of test functions designed to validate the behavior of a Teller provider. It provides
+/// methods for inserting, retrieving, updating, and deleting key-value pairs from the provider, allowing you to verify
+/// the correctness of the provider's implementation.
+///
+/// # Example
+/// ```
+/// use tokio::test;
+/// use crate::providers::test_utils;
+/// use crate::Provider;
+///
+///
+/// async fn sanity_test() {
+/// let p = Box::new(super::Inmem::new(None).unwrap()) as Box;
+///
+/// test_utils::ProviderTestBuilder::default().run(&p).await;
+/// }
+/// ```
+impl ProviderTest {
+ pub fn new(provider: Box) -> Self {
+ Self {
+ root_prefix: None,
+ provider,
+ }
+ }
+
+ pub fn with_root_prefix(mut self, root_prefix: &str) -> Self {
+ self.root_prefix = Some(root_prefix.to_string());
+ self
+ }
+
+ pub async fn run(&self) {
+ let path_tree = self.get_tree();
+
+ self.validate_get_unexisting_key().await;
+ self.validate_put(&path_tree).await;
+ self.validate_get(&path_tree).await;
+ self.validate_update().await;
+ self.validate_delete().await;
+ self.validate_delete_keys().await;
+ }
+
+ /// Returns a tree structure of test paths with associated key-value pairs.
+ ///
+ /// This function constructs a tree structure of test paths, where each path is associated with a vector of key-value pairs.
+ /// The tree is represented as a `HashMap` where the keys are root paths, and the associated values are vectors of `KV` pairs.
+ ///
+ fn get_tree(&self) -> HashMap<&str, Vec> {
+ HashMap::from([
+ (
+ ROOT_PATH_A,
+ vec![
+ KV::from_literal(
+ "",
+ PATH_A_KEY_1,
+ PATH_A_VALUE_1,
+ self.provider.as_ref().kind(),
+ ),
+ KV::from_literal(
+ "",
+ PATH_A_KEY_2,
+ PATH_A_VALUE_2,
+ self.provider.as_ref().kind(),
+ ),
+ KV::from_literal(
+ "",
+ PATH_A_KEY_3,
+ PATH_A_VALUE_3,
+ self.provider.as_ref().kind(),
+ ),
+ ],
+ ),
+ (
+ ROOT_PATH_B,
+ vec![KV::from_literal(
+ "",
+ PATH_B_KEY_1,
+ PATH_B_VALUE_1,
+ self.provider.as_ref().kind(),
+ )],
+ ),
+ (
+ ROOT_PATH_C,
+ vec![KV::from_literal(
+ "",
+ PATH_C_KEY_1,
+ PATH_C_VALUE_1,
+ self.provider.as_ref().kind(),
+ )],
+ ),
+ ])
+ }
+
+ fn get_key_path(&self, root_path: &str) -> String {
+ self.root_prefix.as_ref().map_or_else(
+ || root_path.to_string(),
+ |prefix| format!("{prefix}{root_path}"),
+ )
+ }
+
+ /// Validates that a Teller provider implementation returns an error when attempting to retrieve a non-existent key path.
+ ///
+ /// This function checks the behavior of the provided `Provider` implementation when trying to access an invalid key path
+ /// by constructing a key path from a given root path and an invalid path segment. It then awaits the result and asserts
+ /// that it is an error.
+ async fn validate_get_unexisting_key(&self) {
+ let res = self
+ .provider
+ .as_ref()
+ .get(&PathMap::from_path(&format!("{ROOT_PATH_A}/invalid-path")))
+ .await;
+ assert!(res.is_err());
+ }
+
+ /// Validate that Teller provider successfully put all the tests tree map into the provider.
+ async fn validate_put(&self, path_tree: &HashMap<&str, Vec>) {
+ for (root_path, keys) in path_tree {
+ let path_map = PathMap::from_path(&self.get_key_path(root_path));
+ let res = self.provider.as_ref().put(&path_map, keys).await;
+ assert!(res.is_ok());
+ assert_debug_snapshot!(format!("[put-{}]", root_path.replace('/', "_"),), res);
+ }
+ }
+
+ /// Validates that a Teller provider successfully stores a tree map of key-value pairs.
+ ///
+ /// This function tests the behavior of a Teller provider by attempting to put a collection of key-value
+ /// into the provider and the function returns successfully response.
+ /// Each put response function snapshot the function response struct
+ async fn validate_get(&self, path_tree: &HashMap<&str, Vec>) {
+ for root_path in path_tree.keys() {
+ let res = self
+ .provider
+ .as_ref()
+ .get(&PathMap::from_path(&self.get_key_path(root_path)))
+ .await;
+ assert!(res.is_ok());
+
+ let mut res = res.unwrap();
+ res.sort_by(|a: &KV, b| a.value.cmp(&b.value));
+
+ with_settings!({filters => vec![
+ (format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
+ (format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
+ (format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
+ (format!("\".*{ROOT_PATH_B}").as_str(), format!("\"{ROOT_PATH_B}").as_str()),
+ (format!("\".*{ROOT_PATH_C}").as_str(), format!("\"{ROOT_PATH_C}").as_str()),
+ ]}, {
+ assert_debug_snapshot!(
+ format!("[get-{}]", root_path.replace('/', "_"),),
+ res
+ );
+ });
+ }
+ }
+
+ /// Validates the update operation on a Teller provider after inserting a tree structure.
+ ///
+ /// This function is intended to be executed after running the `validate_get` function to insert a tree structure into the
+ /// Teller provider. It performs the following steps:
+ ///
+ /// 1. Updates a specific key-value pair with new values and expects a successful response from the provider.
+ /// 2. Verifies that the updated value is reflected correctly in the provider.
+ ///
+ /// The function utilizes the provided `provider` to perform the update operation and snapshots the results for verification.
+ async fn validate_update(&self) {
+ // ** Update value and expected success response.
+ let update_keys = vec![KV::from_literal(
+ "",
+ PATH_C_KEY_1,
+ PATH_C_VALUE_1_UPDATE,
+ self.provider.as_ref().kind(),
+ )];
+ let update_res = self
+ .provider
+ .as_ref()
+ .put(
+ &PathMap::from_path(&self.get_key_path(ROOT_PATH_C)),
+ &update_keys,
+ )
+ .await;
+ assert!(update_res.is_ok());
+ assert_debug_snapshot!(
+ format!("[put-update-{}]", ROOT_PATH_C.replace('/', "_"),),
+ update_res
+ );
+
+ // ** Read the updated value and make sure that the value is updated.
+ let read_after_update_res = self
+ .provider
+ .as_ref()
+ .get(&PathMap::from_path(&self.get_key_path(ROOT_PATH_C)))
+ .await;
+ assert!(read_after_update_res.is_ok());
+
+ with_settings!({filters => vec![
+ (format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
+ (format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
+ (format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
+ (format!("\".*{ROOT_PATH_B}").as_str(), format!("\"{ROOT_PATH_B}").as_str()),
+ (format!("\".*{ROOT_PATH_C}").as_str(), format!("\"{ROOT_PATH_C}").as_str()),
+ ]}, {
+ assert_debug_snapshot!(
+ format!("[get-after-update-{}]", ROOT_PATH_C.replace('/', "_"),),
+ read_after_update_res
+ );
+ });
+ }
+
+ /// Validates the deletion of a key from a Teller provider.
+ ///
+ /// This function tests the behavior of a Teller provider by performing the following steps:
+ ///
+ /// 1. Deletes a specific key from the provider using the `del` method.
+ /// 2. Verifies that the deletion operation is successful by checking the result for success.
+ /// 3. Attempts to retrieve the deleted key and confirms that it returns an error, indicating that the key no longer exists.
+ async fn validate_delete(&self) {
+ let delete_res = self
+ .provider
+ .as_ref()
+ .del(&PathMap::from_path(&self.get_key_path(ROOT_PATH_B)))
+ .await;
+ assert!(delete_res.is_ok());
+ assert_debug_snapshot!(
+ format!("[del-{}]", ROOT_PATH_B.replace('/', "_"),),
+ delete_res
+ );
+
+ // ** Validate deletion key.
+
+ let get_del_res = self
+ .provider
+ .as_ref()
+ .get(&PathMap::from_path(&self.get_key_path(ROOT_PATH_B)))
+ .await;
+
+ assert!(matches!(
+ get_del_res,
+ Err(Error::NotFound { path: _, msg: _ })
+ ));
+ assert!(get_del_res.is_err());
+ }
+
+ /// Validates deletion specifics key from a Teller provider.
+ ///
+ /// This function tests the behavior of a Teller provider by performing the following steps:
+ ///
+ /// 1. Delete specifics keys from a path by using adding keys list.
+ /// 2. Verifies that the deletion operation is successful by run `get` again on the same path and expecting that
+ /// not see the deletion keys
+ async fn validate_delete_keys(&self) {
+ let mut path_path = PathMap::from_path(&self.get_key_path(ROOT_PATH_A));
+
+ path_path.keys = BTreeMap::from([
+ (PATH_A_KEY_2.to_string(), String::new()),
+ (PATH_A_KEY_3.to_string(), String::new()),
+ ]);
+
+ let delete_keys_res = self.provider.as_ref().del(&path_path).await;
+ println!("{delete_keys_res:#?}");
+ assert!(delete_keys_res.is_ok());
+ assert_debug_snapshot!(
+ format!("[del-keys-{}]", ROOT_PATH_A.replace('/', "_")),
+ delete_keys_res
+ );
+
+ let get_del_res = self
+ .provider
+ .as_ref()
+ .get(&PathMap::from_path(&self.get_key_path(ROOT_PATH_A)))
+ .await;
+
+ with_settings!({filters => vec![
+ (format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
+ (format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
+ (format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
+ ]}, {
+ assert_debug_snapshot!(
+ format!("[get-del-keys-{}]", ROOT_PATH_A.replace('/', "_")),
+ get_del_res
+ );
+ });
+ }
+}
diff --git a/teller-providers/src/registry.rs b/teller-providers/src/registry.rs
new file mode 100644
index 00000000..9f81f06e
--- /dev/null
+++ b/teller-providers/src/registry.rs
@@ -0,0 +1,93 @@
+use std::collections::{BTreeMap, HashMap};
+
+use crate::providers::ProviderKind;
+use crate::Result;
+use crate::{config::ProviderCfg, Provider};
+
+pub struct Registry {
+ providers: HashMap>,
+}
+
+impl Registry {
+ /// Create a registry from config
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if any provider loading failed
+ pub async fn new(providers: &BTreeMap) -> Result {
+ let mut loaded_providers = HashMap::new();
+ for (k, provider) in providers {
+ let provider: Box = match provider.kind {
+ ProviderKind::Inmem => Box::new(crate::providers::inmem::Inmem::new(
+ k,
+ provider.options.clone(),
+ )?),
+
+ #[cfg(feature = "dotenv")]
+ ProviderKind::Dotenv => Box::new(crate::providers::dotenv::Dotenv::new(
+ k,
+ provider
+ .options
+ .clone()
+ .map(serde_json::from_value)
+ .transpose()?,
+ )?),
+ #[cfg(feature = "hashicorp_vault")]
+ ProviderKind::Hashicorp => {
+ Box::new(crate::providers::hashicorp_vault::Hashivault::new(
+ k,
+ provider
+ .options
+ .clone()
+ .map(serde_json::from_value)
+ .transpose()?,
+ )?)
+ }
+ #[cfg(feature = "ssm")]
+ ProviderKind::SSM => {
+ Box::new(crate::providers::ssm::SSM::new(k, provider.options.clone()).await?)
+ }
+ #[cfg(feature = "aws_secretsmanager")]
+ ProviderKind::AWSSecretsManager => Box::new(
+ crate::providers::aws_secretsmanager::AWSSecretsManager::new(
+ k,
+ provider
+ .options
+ .clone()
+ .map(serde_json::from_value)
+ .transpose()?,
+ )
+ .await?,
+ ),
+ #[cfg(feature = "google_secretmanager")]
+ ProviderKind::GoogleSecretManager => Box::new(
+ crate::providers::google_secretmanager::GoogleSecretManager::new(
+ k,
+ Box::new(crate::providers::google_secretmanager::GSMClient::new().await?)
+ as Box,
+ ),
+ ),
+ #[cfg(feature = "hashicorp_consul")]
+ ProviderKind::HashiCorpConsul => {
+ Box::new(crate::providers::hashicorp_consul::HashiCorpConsul::new(
+ k,
+ provider
+ .options
+ .clone()
+ .map(serde_json::from_value)
+ .transpose()?,
+ )?)
+ }
+ };
+ loaded_providers.insert(k.clone(), provider);
+ }
+ Ok(Self {
+ providers: loaded_providers,
+ })
+ }
+ #[must_use]
+ #[allow(clippy::borrowed_box)]
+ pub fn get(&self, name: &str) -> Option<&Box> {
+ self.providers.get(name)
+ }
+}
diff --git a/vendor/cloud.google.com/go/LICENSE b/vendor/cloud.google.com/go/LICENSE
deleted file mode 100644
index d6456956..00000000
--- a/vendor/cloud.google.com/go/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/vendor/cloud.google.com/go/compute/metadata/metadata.go b/vendor/cloud.google.com/go/compute/metadata/metadata.go
deleted file mode 100644
index 545bd9d3..00000000
--- a/vendor/cloud.google.com/go/compute/metadata/metadata.go
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright 2014 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package metadata provides access to Google Compute Engine (GCE)
-// metadata and API service accounts.
-//
-// This package is a wrapper around the GCE metadata service,
-// as documented at https://developers.google.com/compute/docs/metadata.
-package metadata // import "cloud.google.com/go/compute/metadata"
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net"
- "net/http"
- "net/url"
- "os"
- "runtime"
- "strings"
- "sync"
- "time"
-)
-
-const (
- // metadataIP is the documented metadata server IP address.
- metadataIP = "169.254.169.254"
-
- // metadataHostEnv is the environment variable specifying the
- // GCE metadata hostname. If empty, the default value of
- // metadataIP ("169.254.169.254") is used instead.
- // This is variable name is not defined by any spec, as far as
- // I know; it was made up for the Go package.
- metadataHostEnv = "GCE_METADATA_HOST"
-
- userAgent = "gcloud-golang/0.1"
-)
-
-type cachedValue struct {
- k string
- trim bool
- mu sync.Mutex
- v string
-}
-
-var (
- projID = &cachedValue{k: "project/project-id", trim: true}
- projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
- instID = &cachedValue{k: "instance/id", trim: true}
-)
-
-var defaultClient = &Client{hc: &http.Client{
- Transport: &http.Transport{
- Dial: (&net.Dialer{
- Timeout: 2 * time.Second,
- KeepAlive: 30 * time.Second,
- }).Dial,
- },
-}}
-
-// NotDefinedError is returned when requested metadata is not defined.
-//
-// The underlying string is the suffix after "/computeMetadata/v1/".
-//
-// This error is not returned if the value is defined to be the empty
-// string.
-type NotDefinedError string
-
-func (suffix NotDefinedError) Error() string {
- return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
-}
-
-func (c *cachedValue) get(cl *Client) (v string, err error) {
- defer c.mu.Unlock()
- c.mu.Lock()
- if c.v != "" {
- return c.v, nil
- }
- if c.trim {
- v, err = cl.getTrimmed(c.k)
- } else {
- v, err = cl.Get(c.k)
- }
- if err == nil {
- c.v = v
- }
- return
-}
-
-var (
- onGCEOnce sync.Once
- onGCE bool
-)
-
-// OnGCE reports whether this process is running on Google Compute Engine.
-func OnGCE() bool {
- onGCEOnce.Do(initOnGCE)
- return onGCE
-}
-
-func initOnGCE() {
- onGCE = testOnGCE()
-}
-
-func testOnGCE() bool {
- // The user explicitly said they're on GCE, so trust them.
- if os.Getenv(metadataHostEnv) != "" {
- return true
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- resc := make(chan bool, 2)
-
- // Try two strategies in parallel.
- // See https://github.com/googleapis/google-cloud-go/issues/194
- go func() {
- req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
- req.Header.Set("User-Agent", userAgent)
- res, err := defaultClient.hc.Do(req.WithContext(ctx))
- if err != nil {
- resc <- false
- return
- }
- defer res.Body.Close()
- resc <- res.Header.Get("Metadata-Flavor") == "Google"
- }()
-
- go func() {
- addrs, err := net.DefaultResolver.LookupHost(ctx, "metadata.google.internal")
- if err != nil || len(addrs) == 0 {
- resc <- false
- return
- }
- resc <- strsContains(addrs, metadataIP)
- }()
-
- tryHarder := systemInfoSuggestsGCE()
- if tryHarder {
- res := <-resc
- if res {
- // The first strategy succeeded, so let's use it.
- return true
- }
- // Wait for either the DNS or metadata server probe to
- // contradict the other one and say we are running on
- // GCE. Give it a lot of time to do so, since the system
- // info already suggests we're running on a GCE BIOS.
- timer := time.NewTimer(5 * time.Second)
- defer timer.Stop()
- select {
- case res = <-resc:
- return res
- case <-timer.C:
- // Too slow. Who knows what this system is.
- return false
- }
- }
-
- // There's no hint from the system info that we're running on
- // GCE, so use the first probe's result as truth, whether it's
- // true or false. The goal here is to optimize for speed for
- // users who are NOT running on GCE. We can't assume that
- // either a DNS lookup or an HTTP request to a blackholed IP
- // address is fast. Worst case this should return when the
- // metaClient's Transport.ResponseHeaderTimeout or
- // Transport.Dial.Timeout fires (in two seconds).
- return <-resc
-}
-
-// systemInfoSuggestsGCE reports whether the local system (without
-// doing network requests) suggests that we're running on GCE. If this
-// returns true, testOnGCE tries a bit harder to reach its metadata
-// server.
-func systemInfoSuggestsGCE() bool {
- if runtime.GOOS != "linux" {
- // We don't have any non-Linux clues available, at least yet.
- return false
- }
- slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
- name := strings.TrimSpace(string(slurp))
- return name == "Google" || name == "Google Compute Engine"
-}
-
-// Subscribe calls Client.Subscribe on the default client.
-func Subscribe(suffix string, fn func(v string, ok bool) error) error {
- return defaultClient.Subscribe(suffix, fn)
-}
-
-// Get calls Client.Get on the default client.
-func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
-
-// ProjectID returns the current instance's project ID string.
-func ProjectID() (string, error) { return defaultClient.ProjectID() }
-
-// NumericProjectID returns the current instance's numeric project ID.
-func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
-
-// InternalIP returns the instance's primary internal IP address.
-func InternalIP() (string, error) { return defaultClient.InternalIP() }
-
-// ExternalIP returns the instance's primary external (public) IP address.
-func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
-
-// Email calls Client.Email on the default client.
-func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
-
-// Hostname returns the instance's hostname. This will be of the form
-// ".c..internal".
-func Hostname() (string, error) { return defaultClient.Hostname() }
-
-// InstanceTags returns the list of user-defined instance tags,
-// assigned when initially creating a GCE instance.
-func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
-
-// InstanceID returns the current VM's numeric instance ID.
-func InstanceID() (string, error) { return defaultClient.InstanceID() }
-
-// InstanceName returns the current VM's instance ID string.
-func InstanceName() (string, error) { return defaultClient.InstanceName() }
-
-// Zone returns the current VM's zone, such as "us-central1-b".
-func Zone() (string, error) { return defaultClient.Zone() }
-
-// InstanceAttributes calls Client.InstanceAttributes on the default client.
-func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
-
-// ProjectAttributes calls Client.ProjectAttributes on the default client.
-func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
-
-// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
-func InstanceAttributeValue(attr string) (string, error) {
- return defaultClient.InstanceAttributeValue(attr)
-}
-
-// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
-func ProjectAttributeValue(attr string) (string, error) {
- return defaultClient.ProjectAttributeValue(attr)
-}
-
-// Scopes calls Client.Scopes on the default client.
-func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
-
-func strsContains(ss []string, s string) bool {
- for _, v := range ss {
- if v == s {
- return true
- }
- }
- return false
-}
-
-// A Client provides metadata.
-type Client struct {
- hc *http.Client
-}
-
-// NewClient returns a Client that can be used to fetch metadata.
-// Returns the client that uses the specified http.Client for HTTP requests.
-// If nil is specified, returns the default client.
-func NewClient(c *http.Client) *Client {
- if c == nil {
- return defaultClient
- }
-
- return &Client{hc: c}
-}
-
-// getETag returns a value from the metadata service as well as the associated ETag.
-// This func is otherwise equivalent to Get.
-func (c *Client) getETag(suffix string) (value, etag string, err error) {
- // Using a fixed IP makes it very difficult to spoof the metadata service in
- // a container, which is an important use-case for local testing of cloud
- // deployments. To enable spoofing of the metadata service, the environment
- // variable GCE_METADATA_HOST is first inspected to decide where metadata
- // requests shall go.
- host := os.Getenv(metadataHostEnv)
- if host == "" {
- // Using 169.254.169.254 instead of "metadata" here because Go
- // binaries built with the "netgo" tag and without cgo won't
- // know the search suffix for "metadata" is
- // ".google.internal", and this IP address is documented as
- // being stable anyway.
- host = metadataIP
- }
- suffix = strings.TrimLeft(suffix, "/")
- u := "http://" + host + "/computeMetadata/v1/" + suffix
- req, err := http.NewRequest("GET", u, nil)
- if err != nil {
- return "", "", err
- }
- req.Header.Set("Metadata-Flavor", "Google")
- req.Header.Set("User-Agent", userAgent)
- res, err := c.hc.Do(req)
- if err != nil {
- return "", "", err
- }
- defer res.Body.Close()
- if res.StatusCode == http.StatusNotFound {
- return "", "", NotDefinedError(suffix)
- }
- all, err := ioutil.ReadAll(res.Body)
- if err != nil {
- return "", "", err
- }
- if res.StatusCode != 200 {
- return "", "", &Error{Code: res.StatusCode, Message: string(all)}
- }
- return string(all), res.Header.Get("Etag"), nil
-}
-
-// Get returns a value from the metadata service.
-// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
-//
-// If the GCE_METADATA_HOST environment variable is not defined, a default of
-// 169.254.169.254 will be used instead.
-//
-// If the requested metadata is not defined, the returned error will
-// be of type NotDefinedError.
-func (c *Client) Get(suffix string) (string, error) {
- val, _, err := c.getETag(suffix)
- return val, err
-}
-
-func (c *Client) getTrimmed(suffix string) (s string, err error) {
- s, err = c.Get(suffix)
- s = strings.TrimSpace(s)
- return
-}
-
-func (c *Client) lines(suffix string) ([]string, error) {
- j, err := c.Get(suffix)
- if err != nil {
- return nil, err
- }
- s := strings.Split(strings.TrimSpace(j), "\n")
- for i := range s {
- s[i] = strings.TrimSpace(s[i])
- }
- return s, nil
-}
-
-// ProjectID returns the current instance's project ID string.
-func (c *Client) ProjectID() (string, error) { return projID.get(c) }
-
-// NumericProjectID returns the current instance's numeric project ID.
-func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
-
-// InstanceID returns the current VM's numeric instance ID.
-func (c *Client) InstanceID() (string, error) { return instID.get(c) }
-
-// InternalIP returns the instance's primary internal IP address.
-func (c *Client) InternalIP() (string, error) {
- return c.getTrimmed("instance/network-interfaces/0/ip")
-}
-
-// Email returns the email address associated with the service account.
-// The account may be empty or the string "default" to use the instance's
-// main account.
-func (c *Client) Email(serviceAccount string) (string, error) {
- if serviceAccount == "" {
- serviceAccount = "default"
- }
- return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
-}
-
-// ExternalIP returns the instance's primary external (public) IP address.
-func (c *Client) ExternalIP() (string, error) {
- return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
-}
-
-// Hostname returns the instance's hostname. This will be of the form
-// "